Move handling legacy mathutils buffer types to separate methods

This commit is contained in:
Andrej730
2026-02-04 18:00:01 +05:00
parent a62b177b19
commit 95480a231c
5 changed files with 54 additions and 39 deletions
@@ -1762,40 +1762,34 @@ class CutDecorator:
shader.uniform_float("color", color)
batch.draw(shader)
def cache_camera_matrix(self):
def cache_camera_matrix(self) -> None:
assert bpy.context.scene and bpy.context.scene.camera
obj = bpy.context.scene.camera
# Explicit `dtype` for Blender <5.0 compatibility.
DecoratorData.camera_location_checksum = repr(
np.array(obj.matrix_world.translation, dtype=np.float32).tobytes()
tool.Blender.np_array_legacy(obj.matrix_world.translation).tobytes()
)
DecoratorData.camera_rotation_checksum = repr(np.array(obj.matrix_world.to_3x3(), dtype=np.float32).tobytes())
DecoratorData.camera_rotation_checksum = repr(tool.Blender.np_array_legacy(obj.matrix_world.to_3x3()).tobytes())
def is_camera_moved(self):
def is_camera_moved(self) -> bool:
if not DecoratorData.camera_location_checksum:
self.cache_camera_matrix()
return True # Let's be conservative
assert bpy.context.scene and bpy.context.scene.camera
obj = bpy.context.scene.camera
# Handle both old float64 and new float32 checksums for version compatibility
loc_checksum_bytes = eval(DecoratorData.camera_location_checksum)
if len(loc_checksum_bytes) == 24: # Old format: 3 * 8 bytes (float64)
loc_check = np.frombuffer(loc_checksum_bytes, dtype=np.float64).astype(np.float32)
else: # New format: 3 * 4 bytes (float32)
loc_check = np.frombuffer(loc_checksum_bytes, dtype=np.float32)
loc_real = np.array(obj.matrix_world.translation, dtype=np.float32).flatten()
loc_checksum_bytes: bytes = eval(DecoratorData.camera_location_checksum)
loc_check = tool.Blender.np_frombuffer_legacy(loc_checksum_bytes, 3)
loc_real = tool.Blender.np_array_legacy(obj.matrix_world.translation)
if not np.allclose(loc_check, loc_real, atol=1e-4): # 0.1 mm
self.cache_camera_matrix()
return True
# Handle both old float64 and new float32 checksums for version compatibility
rot_checksum_bytes = eval(DecoratorData.camera_rotation_checksum)
if len(rot_checksum_bytes) == 72: # Old format: 9 * 8 bytes (float64)
rot_check = np.frombuffer(rot_checksum_bytes, dtype=np.float64).astype(np.float32).reshape(3, 3)
else: # New format: 9 * 4 bytes (float32)
rot_check = np.frombuffer(rot_checksum_bytes, dtype=np.float32).reshape(3, 3)
rot_real = np.array(obj.matrix_world.to_3x3(), dtype=np.float32)
rot_checksum_bytes: bytes = eval(DecoratorData.camera_rotation_checksum)
rot_check = tool.Blender.np_frombuffer_legacy(rot_checksum_bytes, 9)
rot_real = tool.Blender.np_array_legacy(obj.matrix_world.to_3x3())
rot_dot = np.dot(rot_check, rot_real.T)
angle_rad = np.arccos(np.clip((np.trace(rot_dot) - 1) / 2, -1, 1))
if angle_rad > 0.0017453292519943296: # 0.1 degrees
+30 -1
View File
@@ -49,7 +49,7 @@ import ifcopenshell.util.element
import numpy as np
import numpy.typing as npt
from ifcopenshell import entity_instance
from mathutils import Vector
from mathutils import Matrix, Vector
import bonsai.bim
import bonsai.core.tool
@@ -2127,3 +2127,32 @@ class Blender(bonsai.core.tool.Blender):
if cls.BLENDER_5:
return "BLENDER_EEVEE"
return "BLENDER_EEVEE_NEXT"
@classmethod
def np_frombuffer_legacy(cls, bytedata: bytes, n: int) -> npt.NDArray[np.float32]:
"""
Read ``n`` float values from ``bytedata``, regardless if they are stored as ``float32`` or ``float64``.
Needed to support .blend files saved in Blender <5.0.0.
Also allows to work with .blend files from 5.0.0+ in older Blender versions.
In ``bpy.app.version >= 5.0.0`` ``mathutils`` transitioned to use ``float32`` buffer type,
while in previous version they were using ``float64``.
In some cases we are storing raw bytes (e.g. object transforms cheksums), so old .blend files
might still have ``float64`` data stored.
See https://projects.blender.org/blender/blender/issues/149283
"""
if len(bytedata) == (n * 2):
return np.frombuffer(bytedata, dtype=np.float64).astype(np.float32)
return np.frombuffer(bytedata, dtype=np.float32)
@classmethod
def np_array_legacy(cls, mathutils_type: Union[Vector, Matrix]) -> npt.NDArray[np.float32]:
"""
Converts ``mathutils`` types to ``np.float32`` arrays, regardless of Blender version.
See ``np_frombuffer_legacy`` for more details.
"""
if cls.BLENDER_5:
return np.array(mathutils_type)
return np.array(mathutils_type, dtype=np.float32)
+2 -3
View File
@@ -1152,9 +1152,8 @@ class Geometry(bonsai.core.tool.Geometry):
def record_object_position(cls, obj: bpy.types.Object) -> None:
# These are recorded separately because they have different numerical tolerances
props = tool.Blender.get_object_bim_props(obj)
# Explicit dtype for Blender <5.0 compatibility.
props.location_checksum = repr(np.array(obj.matrix_world.translation, dtype=np.float32).tobytes())
props.rotation_checksum = repr(np.array(obj.matrix_world.to_3x3(), dtype=np.float32).tobytes())
props.location_checksum = repr(tool.Blender.np_array_legacy(obj.matrix_world.translation).tobytes())
props.rotation_checksum = repr(tool.Blender.np_array_legacy(obj.matrix_world.to_3x3()).tobytes())
@classmethod
def remove_connection(cls, connection: ifcopenshell.entity_instance) -> None:
+7 -14
View File
@@ -115,24 +115,17 @@ class Ifc(bonsai.core.tool.Ifc):
return True # Let's be conservative
# Handle both old float64 and new float32 checksums for version compatibility
loc_checksum_bytes = eval(oprops.location_checksum)
if len(loc_checksum_bytes) == 24: # Old format: 3 * 8 bytes (float64)
loc_check = np.frombuffer(loc_checksum_bytes, dtype=np.float64).astype(np.float32)
else: # New format: 3 * 4 bytes (float32)
loc_check = np.frombuffer(loc_checksum_bytes, dtype=np.float32)
loc_real = np.array(obj.matrix_world.translation, dtype=np.float32).flatten()
loc_checksum_bytes: bytes = eval(oprops.location_checksum)
loc_check = tool.Blender.np_frombuffer_legacy(loc_checksum_bytes, 3)
loc_real = tool.Blender.np_array_legacy(obj.matrix_world.translation)
if not np.allclose(loc_check, loc_real, atol=1e-4): # 0.1 mm
return True
# Handle both old float64 and new float32 checksums for version compatibility
rot_checksum_bytes = eval(oprops.rotation_checksum)
if len(rot_checksum_bytes) == 72: # Old format: 9 * 8 bytes (float64)
rot_check = np.frombuffer(rot_checksum_bytes, dtype=np.float64).astype(np.float32).reshape(3, 3)
else: # New format: 9 * 4 bytes (float32)
rot_check = np.frombuffer(rot_checksum_bytes, dtype=np.float32).reshape(3, 3)
rot_real = np.array(obj.matrix_world.to_3x3(), dtype=np.float32)
rot_checksum_bytes: bytes = eval(oprops.rotation_checksum)
rot_check = tool.Blender.np_frombuffer_legacy(rot_checksum_bytes, 9)
rot_check = rot_check.reshape(3, 3)
rot_real = tool.Blender.np_array_legacy(obj.matrix_world.to_3x3())
rot_dot = np.dot(rot_check, rot_real.T)
angle_rad = np.arccos(np.clip((np.trace(rot_dot) - 1) / 2, -1, 1))
if angle_rad > 0.0017453292519943296: # 0.1 degrees
+2 -2
View File
@@ -323,8 +323,8 @@ class TestRecordObjectPosition(NewFile):
obj = bpy.data.objects.new("Object", None)
props = tool.Blender.get_object_bim_props(obj)
subject.record_object_position(obj)
assert props.location_checksum == repr(np.array(obj.matrix_world.translation, dtype=np.float32).tobytes())
assert props.rotation_checksum == repr(np.array(obj.matrix_world.to_3x3(), dtype=np.float32).tobytes())
assert props.location_checksum == repr(tool.Blender.np_array_legacy(obj.matrix_world.translation).tobytes())
assert props.rotation_checksum == repr(tool.Blender.np_array_legacy(obj.matrix_world.to_3x3()).tobytes())
class TestRemoveConnection(NewFile):