BIMCameraProperties.update_representation caches a JSON "block representation" of
the camera and compares it as a string to decide whether the camera's IFC
representation needs refreshing. round() preserves the sign of zero and json.dumps
writes it out as "-0.0", so a matrix differing from the cached one only in a zero's
sign serialises differently forever: the strings never match even though the values
compare equal.
Reflected plan views hit this reliably, because Drawing.get_camera_shape_matrix
negates mat[1][1] for REFLECTED_PLAN_VIEW, and create_camera seeds the cache from
that matrix while CreateDrawing compares against camera.matrix_world.
The consequences were larger than a redundant IFC write. Every create_drawing ran
bim.update_representation, which reimports the drawing and builds a brand new Camera
datablock, resetting every PropertyGroup on it. Most visibly
BIMCameraProperties.active_drawing_style_index fell back to its default of 0, so the
drawing silently rendered with whichever style happens to be first in the list
instead of the activated one -- an RCP set to "Blender Default" (DEFAULT) rendered
as "Technical" (VIEWPORT) on every run, no matter how many times the style was
reactivated.
Normalising -0.0 to 0.0 lets the cache converge. On an affected drawing this drops
"Initialize drawing generation process" from ~0.34s to ~0.012s, since the camera is
no longer reimported every time.
Generated with the assistance of an AI coding tool.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fixes #9319.
Two independent fixes on the drawing camera path. Happy to split them into
separate PRs if you'd prefer.
1. Drawing camera rebuilt on every create_drawing
BIMCameraProperties.update_representation caches a JSON "block representation" of
the camera and compares it as a string. round() preserves the sign of zero and
json.dumps writes it as -0.0, so a matrix differing from the cached one only in a
zero's sign serialises differently forever — the strings never match even though the
values compare equal:
Reflected plan views hit this reliably, because Drawing.get_camera_shape_matrix
negates mat[1][1] for REFLECTED_PLAN_VIEW, and Loader.create_camera seeds the
cache from that matrix while CreateDrawing compares against camera.matrix_world.
The check therefore always reported stale, so every create_drawing ran
bim.update_representation, which reimports the drawing and builds a brand new
Camera datablock — resetting every PropertyGroup on it. import_camera_props
restores the pset-backed values, but nothing restores
BIMCameraProperties.active_drawing_style_index, so it fell back to its default of
0 and the drawing silently rendered with whichever style is first in the list. An
RCP set to a DEFAULT style rendered as VIEWPORT on every run, no matter how many
times the style was reactivated.
Normalising -0.0 to 0.0 lets the cache converge. On an affected drawing this also
drops Initialize drawing generation process from ~0.34s to ~0.012s, since the camera
is no longer reimported every time.
2. transform_apply on cameras in Geometry.clear_scale
clear_scale routed camera, light and speaker objects into
bpy.ops.object.transform_apply, which cannot act on that data — it reports
Objects have no data to transform and leaves the scale untouched, the no-op the
docstring already describes.
Besides warning spam on every drawing camera update, the call is a crash vector:
transform_apply resolves bpy.context from inside the temp_override, and reaching
it from a UI-invoked operator via bim.update_representation on a drawing camera
intermittently segfaults (EXCEPTION_ACCESS_VIOLATION in BPY_context_member_get,
via ctx_wm_python_context_get — full stack in #9319).
Returning early for those data types preserves behaviour, since the operator was
already doing nothing for them.
Testing
Verified on a project with a REFLECTED_PLAN_VIEW drawing, Blender 4.5.7: before the
fix the camera datablock was replaced on every create_drawing and the activated
shading style was lost each time; after, the datablock id is stable across runs, the
style holds, and the underlay renders through the intended DEFAULT path.
🤖 Generated with Claude Code