mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-19 22:50:21 +00:00
Add wall draft-resync helper + wire 6 mutation operators
After a one-shot wall IFC mutation (unjoin / split / merge / extend / join-at-corner …) the always-visible gizmos on the OTHER side of the join can be left reading stale ``BIMWallProperties`` — the IFC geometry moved but the draft props that drive the gizmo handles still point at the pre-mutation numbers, so a subsequent edit-mode enter shows the wall at its old length / position. * New ``_maybe_resync_wall_props_from_ifc(obj)``: re-primes a single wall's draft props from current IFC, with guards for non-walls, non-parametric walls, and walls in an active draft session (the draft is then the source of truth, not IFC). Must run from an operator ``_execute`` — ID writes from gizmo refresh raise. * New ``_resync_walls_after_mutation(objs)``: iterates the above across a selection. * Six existing mutation operators gain a resync call after their ``core.*`` / ``DumbWallJoiner`` mutation completes: UnjoinWalls, ExtendWallsToUnderside, ExtendWallsToWall, SplitWall, MergeWall, JoinWallsIntersection. MergeWall resyncs only the surviving wall — the active wall is the deletion target. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
import copy
|
import copy
|
||||||
import math
|
import math
|
||||||
|
from collections.abc import Iterable
|
||||||
from math import atan2, cos, degrees, pi, sin
|
from math import atan2, cos, degrees, pi, sin
|
||||||
from typing import TYPE_CHECKING, Any, ClassVar, Literal, Optional, Union, get_args
|
from typing import TYPE_CHECKING, Any, ClassVar, Literal, Optional, Union, get_args
|
||||||
|
|
||||||
@@ -167,6 +168,30 @@ def _read_wall_state_into_props(obj: bpy.types.Object, props: "BIMWallProperties
|
|||||||
props.snap_offset_baseline = props.desired_offset_baseline
|
props.snap_offset_baseline = props.desired_offset_baseline
|
||||||
|
|
||||||
|
|
||||||
|
def _maybe_resync_wall_props_from_ifc(obj: "bpy.types.Object | None") -> None:
|
||||||
|
"""Re-prime ``BIMWallProperties`` from current IFC after an IFC mutation, so
|
||||||
|
non-edit-mode gizmos read post-mutation coordinates. Must be called from an
|
||||||
|
operator's ``_execute`` — ID writes from ``GizmoGroup.refresh`` raise
|
||||||
|
``AttributeError: Writing to ID classes in this context is not allowed``.
|
||||||
|
No-op during a draft session; the draft is then the source of truth."""
|
||||||
|
if obj is None:
|
||||||
|
return
|
||||||
|
if _validate_wall_for_parametric_edit(obj) is not None:
|
||||||
|
return
|
||||||
|
props = tool.Model.get_wall_props(obj)
|
||||||
|
if props.is_editing:
|
||||||
|
return
|
||||||
|
_read_wall_state_into_props(obj, props)
|
||||||
|
|
||||||
|
|
||||||
|
def _resync_walls_after_mutation(objs: Iterable["bpy.types.Object | None"]) -> None:
|
||||||
|
"""Re-prime each wall's draft props after a one-shot IFC mutation. Safe to
|
||||||
|
call from operator ``_execute``: ID writes are allowed there, unlike gizmo
|
||||||
|
refresh."""
|
||||||
|
for obj in objs:
|
||||||
|
_maybe_resync_wall_props_from_ifc(obj)
|
||||||
|
|
||||||
|
|
||||||
class UnjoinWalls(bpy.types.Operator, tool.Ifc.Operator):
|
class UnjoinWalls(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
bl_idname = "bim.unjoin_walls"
|
bl_idname = "bim.unjoin_walls"
|
||||||
bl_label = "Unjoin Walls"
|
bl_label = "Unjoin Walls"
|
||||||
@@ -183,6 +208,7 @@ class UnjoinWalls(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
_commit_pending_wall_edits_for_selection(context)
|
_commit_pending_wall_edits_for_selection(context)
|
||||||
core.unjoin_walls(tool.Ifc, tool.Blender, tool.Geometry, DumbWallJoiner(), tool.Model)
|
core.unjoin_walls(tool.Ifc, tool.Blender, tool.Geometry, DumbWallJoiner(), tool.Model)
|
||||||
|
_resync_walls_after_mutation(tool.Blender.get_selected_objects())
|
||||||
|
|
||||||
|
|
||||||
class ExtendWallsToUnderside(bpy.types.Operator, tool.Ifc.Operator):
|
class ExtendWallsToUnderside(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
@@ -212,6 +238,7 @@ class ExtendWallsToUnderside(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
walls.append(obj)
|
walls.append(obj)
|
||||||
if slab and walls:
|
if slab and walls:
|
||||||
core.extend_wall_to_slab(tool.Ifc, tool.Geometry, tool.Model, slab, walls)
|
core.extend_wall_to_slab(tool.Ifc, tool.Geometry, tool.Model, slab, walls)
|
||||||
|
_resync_walls_after_mutation(walls)
|
||||||
else:
|
else:
|
||||||
self.report({"ERROR"}, "Please select at least one LAYER2 element and an active element")
|
self.report({"ERROR"}, "Please select at least one LAYER2 element and an active element")
|
||||||
|
|
||||||
@@ -253,6 +280,7 @@ class ExtendWallsToWall(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
)
|
)
|
||||||
tool.Model.recreate_wall(element, obj)
|
tool.Model.recreate_wall(element, obj)
|
||||||
tool.Model.recreate_wall(target_element, target_obj)
|
tool.Model.recreate_wall(target_element, target_obj)
|
||||||
|
_resync_walls_after_mutation([target_obj, *objs])
|
||||||
else:
|
else:
|
||||||
self.report({"ERROR"}, "Please select at least one LAYER2 element and one active LAYER2 element")
|
self.report({"ERROR"}, "Please select at least one LAYER2 element and one active LAYER2 element")
|
||||||
|
|
||||||
@@ -455,6 +483,7 @@ class SplitWall(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
selected_objs = tool.Model.get_selected_mesh_objects()
|
selected_objs = tool.Model.get_selected_mesh_objects()
|
||||||
for obj in selected_objs:
|
for obj in selected_objs:
|
||||||
DumbWallJoiner().split(obj, context.scene.cursor.location)
|
DumbWallJoiner().split(obj, context.scene.cursor.location)
|
||||||
|
_resync_walls_after_mutation(selected_objs)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -483,7 +512,11 @@ class MergeWall(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
active_obj = context.active_object
|
active_obj = context.active_object
|
||||||
assert active_obj
|
assert active_obj
|
||||||
selected_objs = tool.Model.get_selected_mesh_objects()
|
selected_objs = tool.Model.get_selected_mesh_objects()
|
||||||
DumbWallJoiner().merge(next(o for o in selected_objs if o != active_obj), active_obj)
|
# The merge deletes the second argument when the walls are collinear;
|
||||||
|
# only the first survives, so the resync targets the non-active wall.
|
||||||
|
surviving_obj = next(o for o in selected_objs if o != active_obj)
|
||||||
|
DumbWallJoiner().merge(surviving_obj, active_obj)
|
||||||
|
_maybe_resync_wall_props_from_ifc(surviving_obj)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -2680,4 +2713,5 @@ class JoinWallsIntersection(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
except core.RequireTwoWallsError as e:
|
except core.RequireTwoWallsError as e:
|
||||||
self.report({"ERROR"}, str(e))
|
self.report({"ERROR"}, str(e))
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
_resync_walls_after_mutation(tool.Blender.get_selected_objects())
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|||||||
Reference in New Issue
Block a user