# SPDX-FileCopyrightText: 2021-2026 Julien Rippinger, Ian Bertin <alicelab.be>
#
# SPDX-License-Identifier: GPL-3.0-or-later
"""
Defines the PropertyGroup storing projection settings and update callbacks for the Parallel Cameras add-on.
"""
import bpy
from math import pi, radians
from .utils.data import CAMERA_SETTINGS
from .utils.helpers import camera_attributes_updated, camera_type_updated
# -------------------------------------------------------------------
# UPDATE CALLBACKS
# -------------------------------------------------------------------
[docs]
def camera_type_update(
self: bpy.types.PropertyGroup, context: bpy.types.Context
) -> None:
"""
Update callback triggered when projection type or preset changes, it calls :py:func:`pohlke.utils.helpers.camera_type_updated`.
Parameters
---------
self : bpy.types.PropertyGroup
Property group instance
context : bpy.types.Context
Blender context
"""
if self.is_updating:
return
self.is_updating = True
try:
camera_type_updated(self)
finally:
self.is_updating = False
[docs]
def camera_alpha_updated(
self: bpy.types.PropertyGroup, context: bpy.types.Context
) -> None:
"""
Update callback triggered when alpha value changes, it calls :py:func:`pohlke.utils.helpers.camera_attributes_updated`
Parameters
---------
self : bpy.types.PropertyGroup
Property group instance
context : bpy.types.Context
Blender context
"""
if self.is_updating:
return
self.is_updating = True
try:
camera_attributes_updated(self, "alpha")
finally:
self.is_updating = False
[docs]
def camera_beta_updated(
self: bpy.types.PropertyGroup, context: bpy.types.Context
) -> None:
"""
Update callback triggered when beta value changes, it calls :py:func:`pohlke.utils.helpers.camera_attributes_updated`
Parameters
---------
self : bpy.types.PropertyGroup
Property group instance
context : bpy.types.Context
Blender context
"""
if self.is_updating:
return
self.is_updating = True
try:
camera_attributes_updated(self, "beta")
finally:
self.is_updating = False
[docs]
def camera_z_value_updated(
self: bpy.types.PropertyGroup, context: bpy.types.Context
) -> None:
"""
Update callback triggered when z value changes.
Change the camera type if the z-axis reduction factor does not match the selected preset.
Parameters
---------
self : bpy.types.PropertyGroup
Property group instance
context : bpy.types.Context
Blender context
"""
if self.is_updating:
return
self.is_updating = True
try:
if self.projection_type == "OBLIQUE":
camera_type = CAMERA_SETTINGS.get_preset_name(
self.projection_type, self.alpha, self.beta, self.z_value
)
self.oblique_type = camera_type
finally:
self.is_updating = False
# -------------------------------------------------------------------
# PROPERTY GROUP
# -------------------------------------------------------------------
[docs]
class Pohlke_PT_CameraProperties(bpy.types.PropertyGroup):
"""
Property group storing projection parameters for the parallel cameras.
This group is attached to bpy.types.Scene and used by the UI panel to configure camera creation.
Attributes
----------
projection_type : enum
Main projection category:
* 'AXONOMETRIC' : Axonometric projections.
* 'OBLIQUE' : Oblique projections.
Triggers `camera_type_update` on change.
axonometric_type : enum
Sub-preset for axonometric cameras.
Available values are dynamically generated from the projection
definitions declared in `presets.toml` and loaded via `data.py`.
They are no longer hardcoded.
Each preset follows the naming pattern:
'AXONOMETRIC_#<n>'
where <n> is the index of the preset as defined in `presets.toml`.
A 'CUSTOM' entry is automatically added when the current
projection parameters do not match any predefined preset.
Triggers `camera_type_update` on change.
oblique_type : enum
Sub-preset for oblique cameras.
Available values are dynamically generated from the projection
definitions declared in `presets.toml` and loaded via `data.py`.
They are no longer hardcoded.
Each preset follows the naming pattern:
'OBLIQUE_#<n>'
where <n> is the index of the preset as defined in `presets.toml`.
A 'CUSTOM' entry is automatically added when the current
projection parameters do not match any predefined preset.
Triggers `camera_type_update` on change.
alpha : float
The alpha angle of the plan (in radians).
Triggers `camera_alpha_updated` on change.
beta : float
The beta angle of the plan (in radians).
Triggers `camera_beta_updated` on change.
rotation : float
Computed rotation around the vertical axis, derived from beta.
altitude : float
Computed altitude angle, derived from alpha.
altitude_oblique : float
Complementary angle used specifically for oblique projections.
is_updating : bool
Internal flag to prevent infinite update loops during property sync.
x_value : float
Scaling factor for the X axis (0.0 to 1.0).
normalized_x_value : float
Nomalized scaling factor for the X axis (0.0 to 1.0).
y_value : float
Scaling factor for the Y axis (0.0 to 1.0).
normalized_y_value : float
Nomalized scaling factor for the Y axis (0.0 to 1.0).
z_value : float
Scaling factor for the Z axis (0.0 to 1.0).
normalized_z_value : float
Nomalized scaling factor for the Z axis (0.0 to 1.0).
"""
projection_type: bpy.props.EnumProperty(
name="Type de Projection",
items=[
("AXONOMETRIC", "Axonometric", "Axonometrics projections", "NONE", 0),
("OBLIQUE", "Oblique", "Obliques projections", "NONE", 1),
],
default="AXONOMETRIC",
update=camera_type_update,
) # pyright: ignore[reportInvalidTypeForm]
axonometric_type: bpy.props.EnumProperty(
name="Presets",
description="Type of projection for the new axonometric camera",
items=[
(key, preset["name"], "Presetting an {} camera".format(preset["name"]))
for (key, preset) in CAMERA_SETTINGS.get_preset_menu_items(
"AXONOMETRIC"
).items()
]
+ [("CUSTOM", "Custom", "No camera preselection")],
default="AXONOMETRIC_#1",
update=camera_type_update,
) # pyright: ignore[reportInvalidTypeForm]
oblique_type: bpy.props.EnumProperty(
name="Presets",
description="Type of projection for the new oblique camera",
items=[
(key, preset["name"], "Presetting an {} camera".format(preset["name"]))
for (key, preset) in CAMERA_SETTINGS.get_preset_menu_items(
"OBLIQUE"
).items()
]
+ [("CUSTOM", "Custom", "No camera preselection")],
default="OBLIQUE_#1",
update=camera_type_update,
) # pyright: ignore[reportInvalidTypeForm]
alpha: bpy.props.FloatProperty(
name="Alpha",
description="Rotation of the camera around the vertical axis",
default=radians(30),
min=0.0,
step=1,
precision=4,
max=pi / 2.0,
subtype="ANGLE",
update=camera_alpha_updated,
) # pyright: ignore[reportInvalidTypeForm]
beta: bpy.props.FloatProperty(
name="Beta",
description="Altitude from the side. 0° is top view, 90° is side view",
default=radians(30),
precision=4,
min=0,
step=1,
max=pi / 2.0,
subtype="ANGLE",
update=camera_beta_updated,
) # pyright: ignore[reportInvalidTypeForm]
rotation: bpy.props.FloatProperty(
name="Rotation",
description="Rotation of the camera around the vertical axis",
default=radians(45),
min=0,
precision=4,
max=pi / 2.0,
subtype="ANGLE",
) # pyright: ignore[reportInvalidTypeForm]
altitude: bpy.props.FloatProperty(
name="Altitude",
description="Altitude from the side. 0° is top view, 90° is side view",
default=radians(35.26),
min=0,
max=pi / 2.0,
precision=4,
subtype="ANGLE",
) # pyright: ignore[reportInvalidTypeForm]
altitude_oblique: bpy.props.FloatProperty(
name="Altitude", precision=4, subtype="ANGLE"
) # pyright: ignore[reportInvalidTypeForm]
is_updating: bpy.props.BoolProperty(default=False) # pyright: ignore[reportInvalidTypeForm]
x_value: bpy.props.FloatProperty(
name="X axis", description="Size of x axis", default=0.82, min=0.01, max=1
) # pyright: ignore[reportInvalidTypeForm]
normalized_x_value: bpy.props.FloatProperty(
name="X axis",
description="Size of x axis",
default=1,
min=0.01,
max=1,
) # pyright: ignore[reportInvalidTypeForm]
y_value: bpy.props.FloatProperty(
name="Y axis", description="Size of y axis", default=0.82, min=0.01, max=1
) # pyright: ignore[reportInvalidTypeForm]
normalized_y_value: bpy.props.FloatProperty(
name="Y axis",
description="Size of y axis",
default=1,
min=0.01,
max=1,
) # pyright: ignore[reportInvalidTypeForm]
z_value: bpy.props.FloatProperty(
name="Z axis",
description="Size of z axis",
default=0.82,
step=1,
precision=2,
soft_min=0.01,
soft_max=1,
update=camera_z_value_updated,
) # pyright: ignore[reportInvalidTypeForm]
normalized_z_value: bpy.props.FloatProperty(
name="Z axis",
description="Size of z axis",
default=1,
soft_min=0.01,
soft_max=1,
) # pyright: ignore[reportInvalidTypeForm]
# -------------------------------------------------------------------
# REGISTRATION
# -------------------------------------------------------------------
_classes = (Pohlke_PT_CameraProperties,)
_register, _unregister = bpy.utils.register_classes_factory(_classes)
[docs]
def register() -> None:
"""Register the property group and attach it to the Scene type."""
_register()
bpy.types.Scene.pohlke_camera_props = bpy.props.PointerProperty(
type=Pohlke_PT_CameraProperties
)
[docs]
def unregister() -> None:
"""Unregister the property group and remove it from the Scene type."""
_unregister()
del bpy.types.Scene.pohlke_camera_props