mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-20 06:58:56 +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:
committed by
Thomas Krijnen
parent
2ac65aeb8b
commit
bfda77e3e8
@@ -1373,9 +1373,7 @@ class FinishBendPreview(bpy.types.Operator):
|
|||||||
self.report({"ERROR"}, str(exc))
|
self.report({"ERROR"}, str(exc))
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
if "FINISHED" in result:
|
if "FINISHED" in result:
|
||||||
props.is_active = False
|
preview_base.clear_preview_state(props)
|
||||||
props.start_segment_id = 0
|
|
||||||
props.end_segment_id = 0
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -1393,7 +1391,5 @@ class CancelBendPreview(bpy.types.Operator):
|
|||||||
props = preview_base.get_preview_props(context, "bend")
|
props = preview_base.get_preview_props(context, "bend")
|
||||||
if props is None or not props.is_active:
|
if props is None or not props.is_active:
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
props.is_active = False
|
preview_base.clear_preview_state(props)
|
||||||
props.start_segment_id = 0
|
|
||||||
props.end_segment_id = 0
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|||||||
@@ -163,6 +163,19 @@ def sync_uncommitted_moves(objects: list) -> None:
|
|||||||
tool.Geometry.commit_placement_if_moved(obj, apply_scale=False)
|
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 ------------------------------------------------------------
|
# --- Esc dispatch ------------------------------------------------------------
|
||||||
|
|
||||||
PREVIEW_CANCEL_OPS: tuple[tuple[str, str], ...] = (
|
PREVIEW_CANCEL_OPS: tuple[tuple[str, str], ...] = (
|
||||||
|
|||||||
@@ -2962,10 +2962,7 @@ class FinishWallFilletPreview(bpy.types.Operator):
|
|||||||
self.report({"ERROR"}, str(exc))
|
self.report({"ERROR"}, str(exc))
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
if "FINISHED" in result:
|
if "FINISHED" in result:
|
||||||
props.is_active = False
|
preview_base.clear_preview_state(props)
|
||||||
props.wall_a_id = 0
|
|
||||||
props.wall_b_id = 0
|
|
||||||
props.editing_corner_id = 0
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -2983,10 +2980,7 @@ class CancelWallFilletPreview(bpy.types.Operator):
|
|||||||
props = preview_base.get_preview_props(context, "wall_fillet")
|
props = preview_base.get_preview_props(context, "wall_fillet")
|
||||||
if props is None or not props.is_active:
|
if props is None or not props.is_active:
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
props.is_active = False
|
preview_base.clear_preview_state(props)
|
||||||
props.wall_a_id = 0
|
|
||||||
props.wall_b_id = 0
|
|
||||||
props.editing_corner_id = 0
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -139,6 +139,55 @@ class TestActivationCycle:
|
|||||||
assert props.is_active is False, f"discard_pending_previews left '{attr}' active"
|
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:
|
class TestSaveOnDiscardWired:
|
||||||
"""Pins that the SaveProject operator clears preview state before writing
|
"""Pins that the SaveProject operator clears preview state before writing
|
||||||
the IFC file — a stuck is_active flag persisted through the save would
|
the IFC file — a stuck is_active flag persisted through the save would
|
||||||
|
|||||||
Reference in New Issue
Block a user