Source code for pohlke.utils.helpers

# SPDX-FileCopyrightText: 2021-2026 Julien Rippinger, Ian Bertin <alicelab.be>
#
# SPDX-License-Identifier: GPL-3.0-or-later

"""Contains all usefull functions for the add-on."""

from math import degrees, radians
from types import SimpleNamespace
from typing import Literal

import bpy

from .data import CAMERA_SETTINGS
from .geometry_math import (
    calculate_axonometric_altitude,
    calculate_axonometric_rotation,
    calculate_oblique_attributes,
    compute_scales,
    normalize_factors,
)


[docs] def camera_attributes_updated( props: bpy.types.PropertyGroup | bpy.types.Operator, updated_angle: str, ) -> None: """Calculate the new camera parameters when an angle (alpha or beta) changes. Parameters ---------- props : Union[bpy.types.PropertyGroup, bpy.types.Operator] The Blender object containing the camera projection parameters updated_angle : str The changed angle ("beta" or "alpha") """ if props.projection_type == "OBLIQUE": if updated_angle == "beta": props.alpha = calculate_oblique_attributes(props.beta) else: props.beta = calculate_oblique_attributes(props.alpha) props.altitude_oblique = props.alpha props.rotation = props.beta camera_type = CAMERA_SETTINGS.get_preset_name( props.projection_type, props.alpha, props.beta, props.z_value, ) props.oblique_type = camera_type else: if props.alpha == props.beta == 0: if updated_angle == "alpha": props.beta = radians(1) if updated_angle == "beta": props.alpha = radians(1) elif props.alpha + props.beta >= radians(90): if updated_angle == "alpha": props.beta = radians(89) - props.alpha if updated_angle == "beta": props.alpha = radians(89) - props.beta props.altitude = calculate_axonometric_altitude(props.beta, props.alpha) props.rotation = calculate_axonometric_rotation(props.beta, props.alpha) y, z, x = compute_scales(props.beta, props.alpha) props.x_value, props.y_value, props.z_value = x, y, z props.normalized_x_value, props.normalized_y_value, props.normalized_z_value = ( normalize_factors(x, y, z) ) camera_type = CAMERA_SETTINGS.get_preset_name( props.projection_type, props.alpha, props.beta, 0, ) props.axonometric_type = camera_type
[docs] def camera_type_updated( props: bpy.types.PropertyGroup | bpy.types.Operator, ) -> None: """Calculate the new camera parameters when the camera type changes. Parameters ---------- props : Union[bpy.types.PropertyGroup, bpy.types.Operator] The Blender object containing the camera projection parameters """ if (props.projection_type == "AXONOMETRIC" and props.axonometric_type != "CUSTOM") or ( props.projection_type == "OBLIQUE" and props.oblique_type != "CUSTOM" ): settings = CAMERA_SETTINGS.get_preset_menu_items(props.projection_type) cameraType = "AXONOMETRIC_#1" if props.projection_type == "AXONOMETRIC": if props.oblique_type == "CUSTOM": props.oblique_type = "OBLIQUE_#1" cameraType = ( settings[props.axonometric_type] if props.axonometric_type != "CUSTOM" else settings["AXONOMETRIC_#1"] ) props.alpha = cameraType["alpha"] props.beta = cameraType["beta"] props.altitude = calculate_axonometric_altitude(props.beta, props.alpha) props.rotation = calculate_axonometric_rotation(props.beta, props.alpha) y, z, x = compute_scales(cameraType["beta"], cameraType["alpha"]) props.x_value, props.y_value, props.z_value = x, y, z ( props.normalized_x_value, props.normalized_y_value, props.normalized_z_value, ) = normalize_factors(x, y, z) else: if props.axonometric_type == "CUSTOM": props.axonometric_type = "AXONOMETRIC_#1" cameraType = ( settings[props.oblique_type] if props.oblique_type != "CUSTOM" else settings["OBLIQUE_#1"] ) props.alpha = cameraType["alpha"] props.beta = cameraType["beta"] props.altitude_oblique = props.alpha props.rotation = props.beta props.x_value = 1 props.y_value = 1 props.z_value = cameraType["shortening"]
[docs] class GraphicalScale: """Static class to produce the graphical scale figure. It is common to display a graphical scale on drawings in order to display the properties of the chosen axonometric or oblique projection. """ _props: SimpleNamespace = SimpleNamespace() @classmethod
[docs] def populate_props( cls, alpha: float, beta: float, shortening: float | None = None, ) -> None: """Populate internal attributes with the necessary info for the figure. Parameters ---------- alpha : float Angle in radians. beta : float Angle in radians. shortening : {None, float}, optional Oblique scale factor for z. """ cls._props.alpha = alpha cls._props.beta = beta cls._props.projection_type = "OBLIQUE" if shortening else "AXONOMETRIC" if cls._props.projection_type == "OBLIQUE": cls._props.x_value = 1 cls._props.y_value = 1 cls._props.z_value = shortening elif cls._props.projection_type == "AXONOMETRIC": cls._props.x_value, cls._props.z_value, cls._props.y_value = compute_scales( alpha, beta, ) ( cls._props.normalized_x_value, cls._props.normalized_z_value, cls._props.normalized_y_value, ) = normalize_factors( cls._props.x_value, cls._props.z_value, cls._props.y_value, )
@classmethod
[docs] def log_figure( cls, position: Literal["ABOVE", "BELOW"] = "ABOVE", ) -> str: r"""Compose the graphical scale figure. Produces a formatted string in ASCII style showing the projeciton parameters as an "axonometric scale indicator" (used on architectural drawings). Parameters ---------- position : {"ABOVE", "BELOW"}, optional Camera position, which flips the figure upside down. Returns ------- str Multiline string of the scale figure. Notes ----- The function evaluates the alpha and beta angles in order to determine the type of axonometry. The numbers are rounded and the result is an approximation. Examples -------- >>> DIMETRIC* ----------------- 131.40° 1.00 \ / 0.50 \ / \ / o 097.20° | 131.40° | | 1.00 ----------------- *approximation .5 """ alpha = degrees(cls._props.alpha) + 90 beta = degrees(cls._props.beta) + 90 if cls._props.projection_type == "OBLIQUE": projection_type = "OBLIQUE" note = "" # Check axonometry type elif cls._props.projection_type == "AXONOMETRIC": angles = {round(alpha), round(beta), round(360 - alpha - beta)} if len(angles) == 3: projection_type = "TRIMETRIC" elif len(angles) == 2: projection_type = "DIMETRIC" else: projection_type = "ISOMETRIC" projection_type = projection_type + "*" # Should add an exception for presets note = "*approximation .5" if position == "ABOVE": figure = f""" {projection_type} ----------------- {360 - alpha - beta:06.2f}° {cls._props.x_value:.2f} \\ / {cls._props.y_value:.2f} \\ / \\ / o {beta:06.2f}° | {alpha:06.2f}° | | {cls._props.z_value:.2f} ----------------- {note} """ else: # Flip figure figure = f""" {projection_type} ----------------- {cls._props.z_value:.2f} | | {beta:06.2f}° | {alpha:06.2f}° o / \\ / \\ {cls._props.x_value:.2f} / \\ {cls._props.y_value:.2f} {360 - alpha - beta:06.2f}° ----------------- {note} """ return figure