Source code for pohlke.utils.geometry_math

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

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

from math import pi, tan, atan, acos, degrees, radians, sqrt, isclose
import logging

[docs] logger = logging.getLogger(__name__)
[docs] def calculate_oblique_attributes(attribute: float) -> float: """Calculates the second attribute of an oblique projection (beta or alpha) from the first one (alpha or beta). If the input parameter is alpha, the function returns beta, and vice versa. Parameters --------- attribute : float One of the projection attributes (alpha or beta) Returns ------- float The second attribute depending on the param (alpha if attribue param is beta, and vice versa) Examples --------- >>> from math import radians, degrees >>> from utils.geometry_math import calculate_oblique_attributes >>> oblique_beta = radians(45) >>> oblique_alpha = calculate_oblique_attributes(oblique_beta) >>> print(degrees(oblique_alpha)) 45 >>> oblique_alpha = radians(30) >>> oblique_beta = calculate_oblique_attributes(oblique_alpha) >>> print(degrees(oblique_beta)) 60 """ valeur = (pi / 2) - attribute if valeur < 0: return 0 return valeur
[docs] def calculate_oblique_z_axis(alpha: float) -> float: """Calculate the Z-axis reduction factor of an oblique projection using it alpha parameter Parameters --------- alpha : float The alpha parameter of the projection Returns ------- float The Z-axis reduction factor (between 0 and 1) Examples --------- >>> from math import radians >>> from utils.geometry_math import calculate_oblique_z_axis >>> oblique_alpha = radians(45) >>> z = calculate_oblique_z_axis(oblique_alpha) >>> print(z) 1.0 """ if alpha == 0: return 0.0 valeur = 1 / tan(alpha) return round(valeur, 2)
[docs] def calculate_altitude_with_z_value(z_value: float) -> float: """Calculate the projection's altitude using it Z-axis reduction factor Parameters --------- z_value : float Z-axis reduction factor of the projection (between 0 ans 1) Returns ------- float Altitude of the projection (in radians) Examples --------- >>> from math import degrees >>> from utils.geometry_math import calculate_altitude_with_z_value >>> z = 1.0 >>> oblique_altitude = calculate_altitude_with_z_value(z) >>> print(degrees(oblique_altitude)) 45 """ if z_value == 0: return 0.0 valeur = atan(1 / z_value) return valeur
[docs] def compute_scales( beta_rad: float, alpha_rad: float, ) -> tuple[float, float, float]: """Calculate scale factors for given axonometry inclination angles. Source ------ Lien de référence : <https://axonometry.readthedocs.io/en/beta/> Parameters --------- beta_rad : float The beta angle of the plan (in radians) alpha_rad : float The beta angle of the plan (in radians) Returns ------- tuple[float, float, float] Axes scales Examples --------- >>> from math import degrees, radians >>> from utils.geometry_math import compute_scales >>> # Test with isometric projection >>> alpha_rad = radians(30) >>> beta_rad = radians(30) >>> y, z, x = compute_scales(beta_rad, alpha_rad) >>> print(x, y, z) 0.82, 0.82, 0.82 """ left_inclination = degrees(alpha_rad) right_inclination = degrees(beta_rad) def deg_tan(x: float) -> float: return tan(radians(x)) center_inclination = 90 - left_inclination - right_inclination left_tangent = deg_tan(left_inclination) center_tangent = deg_tan(center_inclination) right_tangent = deg_tan(right_inclination) left_scale = ( sqrt(1.0 - left_tangent * center_tangent) if (1.0 - left_tangent * center_tangent > 0) else 0 ) # left scale center_scale = ( sqrt(1.0 - right_tangent * left_tangent) if (1.0 - right_tangent * left_tangent > 0) else 0 ) # center scale right_scale = ( sqrt(1.0 - center_tangent * right_tangent) if (1.0 - center_tangent * right_tangent > 0) else 0 ) # right scale return left_scale, center_scale, right_scale
[docs] def tilt(angles: tuple[float, float]) -> tuple[float, float]: """Coordinate plane tilt. Source ------ Lien de référence : <https://axonometry.readthedocs.io/en/beta/> """ alpha = pi / 2 - angles[0] beta = pi / 2 - angles[1] if alpha == 0 or beta == 0: return 0, 0 OP = 1 # noqa: N806 XP = abs(OP / tan(alpha)) # noqa: N806 YP = abs(OP / tan(beta)) # noqa: N806 h = sqrt(XP * YP) gamma = atan(XP / h) delta = atan(YP / h) assert isclose(gamma + delta, pi / 2) return (gamma, delta)
[docs] def get_tilted_angles(beta_rad, alpha_rad) -> list[tuple[float]]: """Order by coordinate plane is XY, ZY, ZX. Source ------ Lien de référence : <https://axonometry.readthedocs.io/en/beta/> """ a_z, a_x, a_y = ( pi - (alpha_rad + beta_rad), pi / 2 + beta_rad, pi / 2 + alpha_rad, ) assert isclose( a_z + a_x + a_y, pi * 2, ), ( f"Something went wrong with the Axonometry angles: a_z = {int(degrees(a_z))}° / a_x = {int(degrees(a_x))}° / a_y = {int(degrees(a_y))}°" ) logger.debug( f"[Trihedron] Compute tilt for axo system: a_z = {int(degrees(a_z))}° / a_x = {int(degrees(a_x))}° / a_y = {int(degrees(a_y))}°", ) # progress counter-clockwise angle_pairs = [ (a_y - pi / 2, a_x - pi / 2), (a_z - pi / 2, a_y - pi / 2), (a_x - pi / 2, a_z - pi / 2), ] # Get angles for each coordinate plane return [tilt(angle_pair) for angle_pair in angle_pairs]
[docs] def calculate_axonometric_rotation(beta_rad: float, alpha_rad: float) -> float: """ Calculate the rotation of an axonometric projection with its alpha and beta parameters Parameters --------- beta_rad : float The beta angle of the plan (in radians) alpha_rad : float The alpha angle of the plan (in radians) Returns ------- float The rotation of the projection (in radians) Examples --------- >>> from math import radians, degrees >>> from utils.geometry_math import calculate_axonometric_rotation >>> alpha_rad = radians(30) >>> beta_rad = radians(30) >>> axonometric_rotation = calculate_axonometric_rotation(beta_rad, alpha_rad) >>> print(degrees(axonometric_rotation)) 45 """ angles = get_tilted_angles(beta_rad, alpha_rad) if len(angles) > 0 and len(angles[0]) == 2 and angles[0][1] > 0: return angles[0][1] else: return 0
[docs] def calculate_axonometric_altitude(beta_rad, alpha_rad): """ Calculate the altitude of an axonometric projection with its alpha and beta parameters Parameters --------- beta_rad : float The beta angle of the plan (in radians) alpha_rad : float The alpha angle of the plan (in radians) Returns ------- float The altitude of the projection (in radians) Examples --------- >>> from math import radians, degrees >>> from utils.geometry_math import calculate_axonometric_altitude >>> alpha_rad = radians(30) >>> beta_rad = radians(30) >>> axonometric_rotation = calculate_axonometric_altitude(beta_rad, alpha_rad) >>> print(degrees(axonometric_rotation)) 35.26 """ y, z, x = compute_scales(beta_rad, alpha_rad) if -1 <= z <= 1: return acos(z) else: return 0
[docs] def normalize_factors(x, y, z): """ Normalize axonometric reduction factors so that the largest factor is 1.0 and others are scaled proportionally. Parameters ---------- x : float The reduction factor for the X axis. y : float The reduction factor for the Y axis. z : float The reduction factor for the Z axis. Returns ------- tuple[float, float, float] A tuple containing (norm_x, norm_y, norm_z) where the maximum value is 1.0. Returns (0.0, 0.0, 0.0) if all input parameters are zero. Examples ---------- >>> from utils.geometry_math import normalize_reduction_factors >>> x, y, z = 10.0, 5.0, 2.0 >>> nx, ny, nz = normalize_reduction_factors(x, y, z) >>> print(nx, ny, nz) 1.0, 0.5, 0.2 >>> # Case with equal factors >>> normalize_reduction_factors(0.5, 0.5, 0.5) (1.0, 1.0, 1.0) """ max_val = max(x, y, z) if max_val == 0: return 0.0, 0.0, 0.0 norm_x = x / max_val norm_y = y / max_val norm_z = z / max_val return norm_x, norm_y, norm_z