Source code for pohlke.scripting

# 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