Source code for pohlke.utils.data

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

"""
Defines projection presets used by the Parallel Cameras add-on.

This module centralizes all predefined projection configurations. Each preset defines angular parameters and projection mode metadata.
"""

from math import radians, isclose
from typing import TypedDict
import tomllib
import pathlib


[docs] class CameraPreset(TypedDict): """ Type of camera settings Attributes --------- name : str Human-readable preset name displayed in UI. alpha : float First projection angle in a plane, between Z and X (radians). beta : float Second projection angle in a plane, between Z and Y (radians). isOblique : bool - False → Axonometric projection - True → Oblique projection shortening : float Reduction factor for the Z axis (only for oblique projection) Examples --------- >>> example_of_camera: CameraPreset = { >>> "name" : "Isometric", >>> "alpha" : radians(30), >>> "beta" : radians(30), >>> "isOblique": False >>> "shortening": 0 >>> } """ name: str alpha: float beta: float isOblique: bool shortening: float
[docs] class CameraSettings: """ Catalog of camera presets. It reads and processes a TOML file containing projection data. Attributes --------- presets: dict[str, dict[str, CameraPreset]] The dictionnary of all projections, regrouped by type "AXONOMETRIC" or "OBLIQUE" Examples --------- >>> # Example of presets.toml : >>> # >>> # [AXONOMETRIC] >>> # "Isometric (30°/30°)" = { alpha = 30, beta = 30 } >>> # >>> # [OBLIQUE] >>> # "Military" = {alpha = 60, beta = 30, shortening = 1 } >>> # "Military Shortened" = {alpha = 60, beta = 30, shortening = 0.75 } >>> >>> current_folder = pathlib.Path(__file__).resolve().parent >>> toml_file_path = current_folder / "presets.toml" >>> CAMERA_SETTINGS = CameraSettings.from_toml(toml_file_path) >>> print(CAMERA_SETTINGS.get_presets) { "AXONOMETRIC": { 'Isometric (30°/30°)': { 'alpha': 30, 'beta': 30 } }, "OBLIQUE": { 'Military': { 'alpha': 60, 'beta': 30, 'shortening': 1 }, 'Military Shortened': { 'alpha': 60, 'beta': 30, 'shortening': 0.75 } } } """ presets: dict[str, dict[str, CameraPreset]] def __init__(self, file_path: pathlib.Path) -> None: file_path = str(file_path) with open(file_path, "rb") as file: self.presets = tomllib.load(file) @classmethod
[docs] def from_toml(cls, file_path) -> None: return cls(file_path)
@property
[docs] def axonometric(self) -> dict: """Get the axonometric presets""" return self.presets["AXONOMETRIC"]
@property
[docs] def oblique(self) -> dict: """Get the oblique presets""" return self.presets["OBLIQUE"]
@property
[docs] def get_presets(self) -> dict[str, dict[str, CameraPreset]]: """Get the presets""" return self.presets
[docs] def get_preset_menu_items(self, preset_type: str) -> dict[str, CameraPreset]: """Build the axonometric or oblique presets dropdown menu list. Parameters --------- preset_type: str The type of projection ("AXONOMETRIC" or "OBLIQUE") Returns --------- dict[str, CameraPreset] The dropdown list of presets of the requested type Examples --------- >>> # Example of presets.toml : >>> # >>> # [AXONOMETRIC] >>> # "Isometric (30°/30°)" = { alpha = 30, beta = 30 } >>> # >>> # [OBLIQUE] >>> # "Military" = {alpha = 60, beta = 30, shortening = 1 } >>> # "Military Shortened" = {alpha = 60, beta = 30, shortening = 0.75 } >>> >>> #Initialization of CAMERA_SETTINGS >>> current_folder = pathlib.Path(__file__).resolve().parent >>> toml_file_path = current_folder / "presets.toml" >>> CAMERA_SETTINGS = CameraSettings.from_toml(toml_file_path) >>> >>> print(CAMERA_SETTINGS.get_preset_menu_items("AXONOMETRIC")) "AXONOMETRIC_#1": { name :'Isometric (30°/30°)', 'alpha': radians(30), 'beta': radians(30), 'isOblique': False 'shortening': 0 }, >>> print(CAMERA_SETTINGS.get_preset_menu_items("OBLIQUE")) "OBLIQUE_#1": { name :'Military', 'alpha': radians(60), 'beta': radians(30), 'isOblique': True 'shortening': 1.0 }, "OBLIQUE_#2": { name :'Military Shortened', 'alpha': radians(60), 'beta': radians(30), 'isOblique': True 'shortening': 0.75 } """ preset_types = list(self.presets) if preset_type not in preset_types: return {} preset_items: dict[str, CameraPreset] = {} for index, (key, preset) in enumerate(self.presets[preset_type].items()): id = preset_type + "_#{}".format(str(index + 1)) preset_items[id] = { "name": key, "alpha": radians(preset["alpha"]) if preset["alpha"] else 0, "beta": radians(preset["beta"]) if preset["beta"] else 0, "isOblique": preset_type == "OBLIQUE", "shortening": preset.get("shortening", 0), } return preset_items
[docs] def get_preset_name( self, preset_type, alpha_rad: float, beta_rad: float, shortening: float ) -> str: """ Get the preset associated with alpha and beta angles Parameters --------- projection_type : str THe type of projection (AXONOMETRIC or OBLIQUE) alpha_rad : float The alpha angle of the plan (in radians) beta_rad : float The beta angle of the plan (in radians) shortening : float Reduction factor for the Z axis (only for oblique projection, 0 for axonometric projection) Returns ------- str The preset associated with the parameters or "CUSTOM" if no corresponding preset Example ------- >>> from math import radians >>> #Initialization of parameters >>> projection_type = "AXONOMETRIC" >>> alpha_rad = radians(30) >>> beta_rad = radians(30) >>> #Initialization of CAMERA_SETTINGS >>> current_folder = pathlib.Path(__file__).resolve().parent >>> toml_file_path = current_folder / "presets.toml" >>> CAMERA_SETTINGS = CameraSettings.from_toml(toml_file_path) >>> camera_type = CAMERA_SETTINGS.get_preset_name(projection_type, alpha_rad, beta_rad, 0) >>> print(camera_type) ISOMETRIC >>> alpha_rad = radians(38) >>> beta_rad = radians(30) >>> projection_type = "OBLIQUE" >>> camera_type = CAMERA_SETTINGS.get_preset_name(projection_type, alpha_rad, beta_rad, 0) >>> print(camera_type) CUSTOM """ preset_types = list(self.presets) if ( (preset_type not in preset_types) or (preset_type != "OBLIQUE" and shortening > 0) or (preset_type == "OBLIQUE" and shortening == 0) ): return "NULL" tol = 1e-5 for index, (key, preset) in enumerate(self.presets[preset_type].items()): preset_alpha = radians(preset["alpha"]) if preset["alpha"] else 0 preset_beta = radians(preset["beta"]) if preset["beta"] else 0 preset_shortening = preset.get("shortening", 0) if ( isclose(preset_alpha, alpha_rad, rel_tol=tol) and isclose(preset_beta, beta_rad, rel_tol=tol) and ( ( preset_type == "OBLIQUE" and isclose(shortening, preset_shortening, rel_tol=tol) ) or preset_type != "OBLIQUE" ) ): return preset_type + "_#{}".format(str(index + 1)) elif ( isclose(preset_alpha, beta_rad, rel_tol=tol) and isclose(preset_beta, alpha_rad, rel_tol=tol) and ( ( preset_type == "OBLIQUE" and isclose(shortening, preset_shortening, rel_tol=tol) ) or preset_type != "OBLIQUE" ) ): return preset_type + "_#{}".format(str(index + 1)) return "CUSTOM"
[docs] current_folder = pathlib.Path(__file__).resolve().parent
"""The current folder"""
[docs] toml_file_path = current_folder / "presets.toml"
"""The full file path"""
[docs] CAMERA_SETTINGS = CameraSettings.from_toml(toml_file_path)
"""The main instance CAMERA_SETTINGS"""