Source code for linkforge.blender.utils.transform_utils

"""Utilities for managing object transforms and parenting."""

from __future__ import annotations

from typing import Any

from ..core import Transform, Vector3
from ..core._utils.math_utils import clean_float

try:
    from mathutils import Matrix, Vector
except ImportError:
    Matrix = None  # type: ignore[assignment,misc]
    Vector = None  # type: ignore[assignment,misc]


[docs] def matrix_to_transform(matrix: Any) -> Transform: """Convert a Blender 4x4 matrix to a Core Transform. Args: matrix: Blender mathutils.Matrix (4x4) Returns: Core Transform with XYZ position and RPY rotation. """ if matrix is None or Matrix is None: return Transform.identity() translation = matrix.to_translation() rotation = matrix.to_euler("XYZ") return Transform( xyz=Vector3( clean_float(translation.x), clean_float(translation.y), clean_float(translation.z), ), rpy=Vector3( clean_float(rotation.x), clean_float(rotation.y), clean_float(rotation.z), ), )
[docs] def set_parent_keep_transform(child_obj: Any, parent_obj: Any) -> None: """Set object parent while preserving its world transform (visual location/rotation). This matches standard Blender 'Object (Keep Transform)' behavior by setting matrix_parent_inverse to the inverse of the parent's world matrix. Args: child_obj: The Blender object to be parented parent_obj: The Blender object to become the parent """ if not child_obj or not parent_obj: return # Store current world state pk_mw = child_obj.matrix_world.copy() # Apply parenting child_obj.parent = parent_obj # Set the inverse matrix to cancel out the parent's current world transform. # This effectively isolates the child from the parent's scale. child_obj.matrix_parent_inverse = parent_obj.matrix_world.inverted() # Restore world transform to ensure no drift child_obj.matrix_world = pk_mw
[docs] def clear_parent_keep_transform(child_obj: Any) -> None: """Clear object parent while preserving its world transform. Args: child_obj: The Blender object to unparent """ if not child_obj: return # Store current world state pk_mw = child_obj.matrix_world.copy() # Remove parent child_obj.parent = None # Restore world transform (since parent is now None, World == Local) child_obj.matrix_world = pk_mw
[docs] def get_local_bounding_box_center(obj: Any) -> Any: """Calculate the geometric center of an object's bounding box in local space. Args: obj: Blender object (must have bound_box attribute) Returns: mathutils.Vector representing the local center offset. """ if Vector is None: return (0.0, 0.0, 0.0) if not hasattr(obj, "bound_box") or not obj.bound_box: # Fallback for objects without bounding boxes return Vector((0.0, 0.0, 0.0)) local_corners = [Vector(corner) for corner in obj.bound_box] min_v = Vector(tuple(min(v[i] for v in local_corners) for i in range(3))) max_v = Vector(tuple(max(v[i] for v in local_corners) for i in range(3))) return (min_v + max_v) / 2