mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-06 07:51:47 +00:00
Add clear_preview_state helper + DRY preview cleanup
Every preview operator (commit + cancel for both bend and wall fillet) was inlining the same 3-4 line cleanup: set is_active to False, zero every *_id IntProperty. The new clear_preview_state helper in preview_base.py introspects bl_rna and applies that contract generically — adopters become a single call. Two new tests pin the contract: every *_id IntProperty zeroes, non-id fields stay. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -1373,9 +1373,7 @@ class FinishBendPreview(bpy.types.Operator):
|
||||
self.report({"ERROR"}, str(exc))
|
||||
return {"CANCELLED"}
|
||||
if "FINISHED" in result:
|
||||
props.is_active = False
|
||||
props.start_segment_id = 0
|
||||
props.end_segment_id = 0
|
||||
preview_base.clear_preview_state(props)
|
||||
return result
|
||||
|
||||
|
||||
@@ -1393,7 +1391,5 @@ class CancelBendPreview(bpy.types.Operator):
|
||||
props = preview_base.get_preview_props(context, "bend")
|
||||
if props is None or not props.is_active:
|
||||
return {"CANCELLED"}
|
||||
props.is_active = False
|
||||
props.start_segment_id = 0
|
||||
props.end_segment_id = 0
|
||||
preview_base.clear_preview_state(props)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -163,6 +163,19 @@ def sync_uncommitted_moves(objects: list) -> None:
|
||||
tool.Geometry.commit_placement_if_moved(obj, apply_scale=False)
|
||||
|
||||
|
||||
def clear_preview_state(props: bpy.types.PropertyGroup) -> None:
|
||||
"""Reset a preview PropertyGroup to its idle state on commit / cancel.
|
||||
|
||||
Sets ``is_active`` to False and zeros every ``IntProperty`` whose name
|
||||
ends in ``_id`` (the entity-reference convention every preview follows).
|
||||
Other fields are left at their last value — defaults are re-applied on
|
||||
the next enable, so leaving them alone avoids a redundant write."""
|
||||
props.is_active = False
|
||||
for name, rna in props.bl_rna.properties.items():
|
||||
if name.endswith("_id") and rna.type == "INT":
|
||||
setattr(props, name, 0)
|
||||
|
||||
|
||||
# --- Esc dispatch ------------------------------------------------------------
|
||||
|
||||
PREVIEW_CANCEL_OPS: tuple[tuple[str, str], ...] = (
|
||||
|
||||
@@ -2962,10 +2962,7 @@ class FinishWallFilletPreview(bpy.types.Operator):
|
||||
self.report({"ERROR"}, str(exc))
|
||||
return {"CANCELLED"}
|
||||
if "FINISHED" in result:
|
||||
props.is_active = False
|
||||
props.wall_a_id = 0
|
||||
props.wall_b_id = 0
|
||||
props.editing_corner_id = 0
|
||||
preview_base.clear_preview_state(props)
|
||||
return result
|
||||
|
||||
|
||||
@@ -2983,10 +2980,7 @@ class CancelWallFilletPreview(bpy.types.Operator):
|
||||
props = preview_base.get_preview_props(context, "wall_fillet")
|
||||
if props is None or not props.is_active:
|
||||
return {"CANCELLED"}
|
||||
props.is_active = False
|
||||
props.wall_a_id = 0
|
||||
props.wall_b_id = 0
|
||||
props.editing_corner_id = 0
|
||||
preview_base.clear_preview_state(props)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -139,6 +139,55 @@ class TestActivationCycle:
|
||||
assert props.is_active is False, f"discard_pending_previews left '{attr}' active"
|
||||
|
||||
|
||||
class TestClearPreviewState:
|
||||
"""``clear_preview_state`` is the shared cleanup routine every preview
|
||||
operator calls on commit / cancel. The contract is: ``is_active`` flips
|
||||
to False, every ``*_id`` IntProperty zeroes, everything else stays."""
|
||||
|
||||
def test_clears_is_active_and_id_fields_on_real_property_groups(self):
|
||||
from bonsai.bim.module.model.preview_base import clear_preview_state
|
||||
|
||||
registered = _registered_previews()
|
||||
if not registered:
|
||||
pytest.skip("No previews wired in this build — registry-only entries")
|
||||
|
||||
for attr, _, props in registered:
|
||||
# Seed every *_id IntProperty with a non-zero sentinel and flip
|
||||
# the activity flag so the helper has something to clear.
|
||||
id_fields = [
|
||||
name for name, rna in props.bl_rna.properties.items() if name.endswith("_id") and rna.type == "INT"
|
||||
]
|
||||
assert id_fields, f"Preview '{attr}' has no *_id IntProperty — registry shape changed"
|
||||
for name in id_fields:
|
||||
setattr(props, name, 42)
|
||||
props.is_active = True
|
||||
|
||||
clear_preview_state(props)
|
||||
|
||||
assert props.is_active is False, f"Preview '{attr}' is_active not cleared"
|
||||
for name in id_fields:
|
||||
assert getattr(props, name) == 0, f"Preview '{attr}' field '{name}' not zeroed"
|
||||
|
||||
def test_leaves_non_id_fields_untouched(self):
|
||||
"""Non-``*_id`` fields (FloatProperty params like ``radius``,
|
||||
``start_length``) must survive the reset — they re-seed on the next
|
||||
enable, so untouching them here avoids a redundant write."""
|
||||
from bonsai.bim.module.model.preview_base import clear_preview_state
|
||||
|
||||
bend = getattr(_preview_umbrella(), "bend", None)
|
||||
if bend is None:
|
||||
pytest.skip("Bend preview not wired in this build")
|
||||
|
||||
bend.is_active = True
|
||||
bend.start_length = 0.42
|
||||
bend.radius = 0.99
|
||||
clear_preview_state(bend)
|
||||
|
||||
assert bend.is_active is False
|
||||
assert bend.start_length == pytest.approx(0.42)
|
||||
assert bend.radius == pytest.approx(0.99)
|
||||
|
||||
|
||||
class TestSaveOnDiscardWired:
|
||||
"""Pins that the SaveProject operator clears preview state before writing
|
||||
the IFC file — a stuck is_active flag persisted through the save would
|
||||
|
||||
Reference in New Issue
Block a user