# SPDX-FileCopyrightText: 2021-2026 Julien Rippinger, Ian Bertin <alicelab.be>
#
# SPDX-License-Identifier: GPL-3.0-or-later
"""Scripting interface to use Pohlke functions in Blender scripts.
Blender console autocomplete:
>>> bpy.pohlke.
add_axonometric_camera(
add_oblique_camera(
add_preset_camera(
names
presets
"""
import logging
from math import radians
from typing import Literal
import bpy
from .utils.data import CAMERA_SETTINGS, CameraPreset
from .utils.geometry_math import (
calculate_altitude_with_z_value,
calculate_axonometric_altitude,
calculate_axonometric_rotation,
)
from .utils.helpers import GraphicalScale
[docs]
class Scriber:
"""The Pohlke scripting interface object.
This object is instantiated in :py:meth:`pohlke.register()` and
exposed as :py:mod:`bpy.pohlke`.
"""
[docs]
presets: dict[str, dict[str, CameraPreset]] = CAMERA_SETTINGS.presets
"""Presets dicitonary from the main pohlke module.
c.f. :py:class:`pohlke.utils.data.CameraSettings`
Examples
--------
>>> bpy.pohlke.presets['AXONOMETRIC']
{'Dimetric (37°/16°)': {'alpha': 37, 'beta': 16},
'Dimetric (41.5°/41.5°)': {'alpha': 41.5, 'beta': 41.5},
'Dimetric (60°/15°)': {'alpha': 60, 'beta': 15},
'Dimetric Engineering (41.4°/7.2°)': {'alpha': 41.4, 'beta': 7.2},
'Dimetric Pixel 1:2 (26.56°/26.56°)': {'alpha': 26.565, 'beta': 26.565},
'Dimetric Pixel 3:4 (36.87°/36.87°)': {'alpha': 36.873, 'beta': 36.873},
'Isometric (30°/30°)': {'alpha': 30, 'beta': 30},
'Trimetric (18°/11°)': {'alpha': 18, 'beta': 11},
'Trimetric (30°/15°)': {'alpha': 30, 'beta': 15},
'Trimetric (45°/15°)': {'alpha': 45, 'beta': 15},
'Trimetric (45°/30°)': {'alpha': 45, 'beta': 30},
'Trimetric (52°/13°)': {'alpha': 52, 'beta': 13}}
>>> bpy.pohlke.presets['OBLIQUE']
{'Cavalier 1:1': {'alpha': 45, 'beta': 45, 'shortening': 1},
'Cavalier 1:2': {'alpha': 45, 'beta': 45, 'shortening': 0.5},
'Cavalier 3:4': {'alpha': 45, 'beta': 45, 'shortening': 0.75},
'Hejduk': {'alpha': 90, 'beta': 0, 'shortening': 1},
'Military 1:1': {'alpha': 60, 'beta': 30, 'shortening': 1},
'Military 1:2': {'alpha': 60, 'beta': 30, 'shortening': 0.5},
'Military 3:4': {'alpha': 60, 'beta': 30, 'shortening': 0.75}}
>>> bpy.pohlke.presets['OBLIQUE']['Hejduk']
{'alpha': 90, 'beta': 0, 'shortening': 1}
"""
@property
[docs]
def names(self) -> list[str]:
"""Get a list of the preset names.
Examples
--------
>>> bpy.pohlke.names
['Isometric (30°/30°)',
'Dimetric Pixel 1:2 (26.56°/26.56°)',
'Dimetric Pixel 3:4 (36.87°/36.87°)',
'Dimetric (41.5°/41.5°)',
'Dimetric Engineering (41.4°/7.2°)',
'Dimetric (37°/16°)',
'Dimetric (60°/15°)',
'Trimetric (18°/11°)',
'Trimetric (30°/15°)',
'Trimetric (45°/15°)',
'Trimetric (52°/13°)',
'Trimetric (45°/30°)',
'Hejduk',
'Cavalier 1:1',
'Cavalier 3:4',
'Cavalier 1:2',
'Military 1:1',
'Military 3:4',
'Military 1:2']
>>> bpy.pohlke.names[12]
'Hejduk'
"""
preset_names = []
for item in self.presets.values():
preset_names.extend([*item])
return preset_names
[docs]
def add_axonometric_camera(
self,
alpha: float,
beta: float,
ortho_scale: float = 10.0,
position: Literal["ABOVE", "BELOW"] = "ABOVE",
orientation: Literal["0", "1", "2", "3"] = "0",
) -> bpy.types.Camera:
"""Add axonometric camera.
Parameters
----------
alpha : float
The alpha angle of the plan.
beta : float
The beta angle of the plan.
ortho_scale : 10.0, optional
The camera's orthographic scale.
position : {'ABOVE', 'BELOW'}, optional
Vertical direction of view.
orientation : {'0', '1', '2', '3'}, optional
Side of view. Quadrants are selected by index.
Returns
-------
bpy.context.active_object
The instance of the created camera object.
Examples
--------
>>> bpy.pohlke.add_axonometric_camera(18, 11)
bpy.data.objects['Camera']
"""
alpha, beta = radians(alpha), radians(beta)
altitude = calculate_axonometric_altitude(beta, alpha)
rotation = calculate_axonometric_rotation(beta, alpha)
# call Pohlke_OT_CreateCam
bpy.ops.view3d.add_pohlke_camera(
projection_type="AXONOMETRIC",
altitude=altitude,
rotation=rotation,
ortho_scale=ortho_scale,
position=position,
orientation=orientation,
)
# Populate _props to overwrite scene props
GraphicalScale.populate_props(alpha, beta, None)
info_figure = GraphicalScale.log_figure(position=position)
scale_logger = logging.getLogger("pohlke_console")
scale_logger.info(info_figure)
return bpy.context.active_object
[docs]
def add_oblique_camera( # noqa: PLR0913
self,
alpha: float,
beta: float,
shortening: float = 1.0,
ortho_scale: float = 10.0,
position: Literal["ABOVE", "BELOW"] = "ABOVE",
orientation: Literal["0", "1", "2", "3"] = "0",
) -> bpy.types.Camera:
"""Add oblique camera.
Parameters
----------
alpha : float
The alpha angle of the plan.
beta : float
The beta angle of the plan.
shortening: 1.0, optional
The vertical reduction factor.
ortho_scale : 10.0, optional
The camera's orthographic scale.
position : {'ABOVE', 'BELOW'}, optional
Vertical direction of view.
orientation : {'0', '1', '2', '3'}, optional
Side of view. Quadrants are selected by index.
Returns
-------
bpy.context.active_object
The instance of the created camera object.
Raises
------
ValueError
The sum of `alpha` and `beta` needs to be 90.
Examples
--------
>>> bpy.pohlke.add_oblique_camera(60, 30, 0.5)
bpy.data.objects['Camera']
"""
if alpha + beta != 90:
error = "Invalid angle pair. The sum of alpha and beta has to be 90 degrees."
raise ValueError(error)
alpha, beta = radians(alpha), radians(beta)
# call Pohlke_OT_CreateCam
bpy.ops.view3d.add_pohlke_camera(
projection_type="OBLIQUE",
rotation=beta,
z_value=shortening,
ortho_scale=ortho_scale,
position=position,
orientation=orientation,
)
# Populate _props to overwrite scene properties
GraphicalScale.populate_props(alpha, beta, shortening)
info_figure = GraphicalScale.log_figure(position=position)
scale_logger = logging.getLogger("pohlke_console")
scale_logger.info(info_figure)
return bpy.context.active_object
[docs]
def add_preset_camera(
self,
preset_name: str,
ortho_scale: float = 10.0,
position: Literal["ABOVE", "BELOW"] = "ABOVE",
orientation: Literal["0", "1", "2", "3"] = "0",
) -> bpy.types.Camera:
"""Add camera by preset name.
Parameters
----------
preset_name :
The name of the preset.
ortho_scale : 10.0, optional
The camera's orthographic scale.
position : {'ABOVE', 'BELOW'}, optional
Vertical direction of view.
orientation : {'0', '1', '2', '3'}, optional
Side of view. Quadrants are selected by index.
Returns
-------
bpy.context.active_object
The instance of the created camera object.
Examples
--------
>>> bpy.pohlke.add_preset_camera('Military 1:2 (60°/30°)')
bpy.data.objects['Camera']
>>> bpy.pohlke.add_preset_camera('Trimetric (18°/11°)')
bpy.data.objects['Camera.001']
>>> bpy.pohlke.add_preset_camera(bpy.pohlke.names[0])
bpy.data.objects['Camera.002']
"""
if preset_name not in self.names:
error = f"'{preset_name}' not a preset."
raise ValueError(error)
projection_type, index = self._get_preset_index(preset_name)
if projection_type == "OBLIQUE":
beta = radians(self.presets[projection_type][preset_name]["beta"])
shortening = self.presets[projection_type][preset_name]["shortening"]
altitude = calculate_altitude_with_z_value(shortening) # only for return statement
# call Pohlke_OT_CreateCam
bpy.ops.view3d.add_pohlke_camera(
projection_type=projection_type,
oblique_type=projection_type + f"_#{index + 1!s}",
rotation=beta,
z_value=shortening,
ortho_scale=ortho_scale,
position=position,
orientation=orientation,
)
if projection_type == "AXONOMETRIC":
alpha = radians(self.presets[projection_type][preset_name]["alpha"])
beta = radians(self.presets[projection_type][preset_name]["beta"])
shortening = None
altitude = calculate_axonometric_altitude(beta, alpha)
rotation = calculate_axonometric_rotation(beta, alpha)
# call Pohlke_OT_CreateCam
bpy.ops.view3d.add_pohlke_camera(
projection_type=projection_type,
axonometric_type=projection_type + f"_#{index + 1!s}",
altitude=altitude,
rotation=rotation,
ortho_scale=ortho_scale,
position=position,
orientation=orientation,
)
# Populate _props to overwrite scene properties
GraphicalScale.populate_props(alpha, beta, shortening)
info_figure = GraphicalScale.log_figure(position=position)
scale_logger = logging.getLogger("pohlke_console")
scale_logger.info(info_figure)
return bpy.context.active_object
def _get_preset_index(self, query: str) -> [str, str]:
"""Get projection and the preset index the from settings dictionary."""
for projection_type, sub_dict in self.presets.items():
# Convert the sub-dictionary keys to a list to access by index
keys_list = list(sub_dict.keys())
# Check if the query exists exactly in this list
if query in keys_list:
index = keys_list.index(query)
return [projection_type, index]
return None