mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +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 math
|
||||
from collections.abc import Iterable
|
||||
from math import atan2, cos, degrees, pi, sin
|
||||
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
|
||||
|
||||
|
||||
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):
|
||||
bl_idname = "bim.unjoin_walls"
|
||||
bl_label = "Unjoin Walls"
|
||||
@@ -183,6 +208,7 @@ class UnjoinWalls(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
_commit_pending_wall_edits_for_selection(context)
|
||||
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):
|
||||
@@ -212,6 +238,7 @@ class ExtendWallsToUnderside(bpy.types.Operator, tool.Ifc.Operator):
|
||||
walls.append(obj)
|
||||
if slab and walls:
|
||||
core.extend_wall_to_slab(tool.Ifc, tool.Geometry, tool.Model, slab, walls)
|
||||
_resync_walls_after_mutation(walls)
|
||||
else:
|
||||
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(target_element, target_obj)
|
||||
_resync_walls_after_mutation([target_obj, *objs])
|
||||
else:
|
||||
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()
|
||||
for obj in selected_objs:
|
||||
DumbWallJoiner().split(obj, context.scene.cursor.location)
|
||||
_resync_walls_after_mutation(selected_objs)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -483,7 +512,11 @@ class MergeWall(bpy.types.Operator, tool.Ifc.Operator):
|
||||
active_obj = context.active_object
|
||||
assert active_obj
|
||||
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"}
|
||||
|
||||
|
||||
@@ -2680,4 +2713,5 @@ class JoinWallsIntersection(bpy.types.Operator, tool.Ifc.Operator):
|
||||
except core.RequireTwoWallsError as e:
|
||||
self.report({"ERROR"}, str(e))
|
||||
return {"CANCELLED"}
|
||||
_resync_walls_after_mutation(tool.Blender.get_selected_objects())
|
||||
return {"FINISHED"}
|
||||
|
||||
Reference in New Issue
Block a user