mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 02:02:22 +00:00
Closes #7943: Add regenerate_wall_to_underside operator
When extend_walls_to_underside is applied to a wall and the roof/slab is later moved, pressing Shift+G now re-clips the wall to the slab's new position. The IFC relationship created by connect_wall_to_slab (IfcRelConnectsElements, Description="TOP") is used to look up which slabs a wall is clipped to. On regeneration, the existing manual booleans (IfcPolygonalFaceSet operands) are cleanly removed via remove_representation_item, then clip_wall_to_slab is re-applied for each connected slab. Shift+G on a LAYER2 wall that has a TOP connection now calls bim.regenerate_wall_to_underside; walls without a connection continue to call bim.recalculate_wall as before. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -85,6 +85,7 @@ classes = (
|
||||
wall.EnableEditingWall,
|
||||
wall.ExtendWallHeightToCursor,
|
||||
wall.ExtendWallsToUnderside,
|
||||
wall.RegenerateWallToUnderside,
|
||||
wall.ExtendWallsToWall,
|
||||
wall.ExtendWallsToPolylinePoint,
|
||||
wall.ExtendWallToCursor,
|
||||
|
||||
@@ -316,6 +316,24 @@ class ExtendWallsToUnderside(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.report({"ERROR"}, "Please select at least one LAYER2 element and an active element")
|
||||
|
||||
|
||||
class RegenerateWallToUnderside(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.regenerate_wall_to_underside"
|
||||
bl_label = "Regenerate Wall to Underside"
|
||||
bl_description = "Re-clip selected walls to their connected underside objects after the slab has moved"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
wall_objs = [
|
||||
obj
|
||||
for obj in tool.Blender.get_selected_objects()
|
||||
if (element := tool.Ifc.get_entity(obj)) and tool.Model.get_usage_type(element) == "LAYER2"
|
||||
]
|
||||
if wall_objs:
|
||||
core.regenerate_wall_to_underside(tool.Ifc, tool.Geometry, tool.Model, wall_objs)
|
||||
else:
|
||||
self.report({"ERROR"}, "Please select at least one LAYER2 element")
|
||||
|
||||
|
||||
class ExtendWallsToWall(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.extend_walls_to_wall"
|
||||
bl_label = "Extend Walls To Wall"
|
||||
|
||||
@@ -1294,7 +1294,13 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bpy.ops.bim.generate_space()
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
bpy.ops.bim.recalculate_wall()
|
||||
if element and any(
|
||||
rel.is_a("IfcRelConnectsElements") and rel.Description == "TOP"
|
||||
for rel in element.ConnectedFrom
|
||||
):
|
||||
bpy.ops.bim.regenerate_wall_to_underside()
|
||||
else:
|
||||
bpy.ops.bim.recalculate_wall()
|
||||
elif self.active_material_usage == "LAYER3":
|
||||
bpy.ops.bim.recalculate_slab()
|
||||
elif tool.System.get_ports(element):
|
||||
|
||||
@@ -161,6 +161,31 @@ def align_objects(
|
||||
model.align_objects(reference_obj, objs, align_type)
|
||||
|
||||
|
||||
def regenerate_wall_to_underside(
|
||||
ifc: type[tool.Ifc],
|
||||
geometry: type[tool.Geometry],
|
||||
model: type[tool.Model],
|
||||
wall_objs: list[bpy.types.Object],
|
||||
) -> None:
|
||||
"""Re-clip walls to their connected underside objects after the slab has moved."""
|
||||
clipped_objs = []
|
||||
for obj in wall_objs:
|
||||
wall = ifc.get_entity(obj)
|
||||
slab_objs = model.get_connected_slab_objs(wall)
|
||||
if not slab_objs:
|
||||
continue
|
||||
if ifc.is_moved(obj):
|
||||
geometry.run_edit_object_placement(obj=obj)
|
||||
model.remove_wall_to_underside_booleans(wall)
|
||||
for slab_obj in slab_objs:
|
||||
clip = model.get_slab_clipping_bmesh(slab_obj)
|
||||
if clip:
|
||||
model.clip_wall_to_slab(wall, clip)
|
||||
clipped_objs.append(obj)
|
||||
if clipped_objs:
|
||||
model.reload_body_representation(clipped_objs)
|
||||
|
||||
|
||||
def extend_wall_to_slab(
|
||||
ifc: type[tool.Ifc],
|
||||
geometry: type[tool.Geometry],
|
||||
|
||||
@@ -681,6 +681,7 @@ class Model:
|
||||
def export_profile(cls, obj, position=None): pass
|
||||
def generate_occurrence_name(cls, element_type, ifc_class): pass
|
||||
def get_extrusion(cls, representation): pass
|
||||
def get_connected_slab_objs(cls, wall): pass
|
||||
def get_manual_booleans(cls, element): pass
|
||||
def get_material_layer_parameters(cls, element): pass
|
||||
def get_slab_clipping_bmesh(cls, obj): pass
|
||||
@@ -696,6 +697,7 @@ class Model:
|
||||
def regenerate_profile(cls, obj): pass
|
||||
def regenerate_slab(cls, obj): pass
|
||||
def reload_body_representation(cls, obj_or_objects): pass
|
||||
def remove_wall_to_underside_booleans(cls, wall): pass
|
||||
def replace_object_ifc_representation(cls, ifc_file, ifc_context, obj, new_representation): pass
|
||||
|
||||
|
||||
|
||||
@@ -843,6 +843,29 @@ class Model(bonsai.core.tool.Model):
|
||||
items.append(item.FirstOperand)
|
||||
return booleans
|
||||
|
||||
@classmethod
|
||||
def get_connected_slab_objs(cls, wall: ifcopenshell.entity_instance) -> list[bpy.types.Object]:
|
||||
"""Return Blender objects for slabs connected to wall via IfcRelConnectsElements(TOP)."""
|
||||
result = []
|
||||
for rel in wall.ConnectedFrom:
|
||||
if rel.is_a("IfcRelConnectsElements") and rel.Description == "TOP":
|
||||
slab_obj = tool.Ifc.get_object(rel.RelatingElement)
|
||||
if slab_obj:
|
||||
result.append(slab_obj)
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def remove_wall_to_underside_booleans(cls, wall: ifcopenshell.entity_instance) -> None:
|
||||
"""Remove all IfcBooleanResult items previously added by extend_walls_to_underside."""
|
||||
manual_booleans = cls.get_manual_booleans(wall)
|
||||
if not manual_booleans:
|
||||
return
|
||||
mesh_operands = [
|
||||
b.SecondOperand for b in manual_booleans if b.SecondOperand.is_a("IfcTessellatedFaceSet")
|
||||
]
|
||||
for mesh in mesh_operands:
|
||||
tool.Geometry.remove_representation_item(mesh, wall)
|
||||
|
||||
@classmethod
|
||||
def get_manual_booleans(
|
||||
cls, element: ifcopenshell.entity_instance, representation: Optional[ifcopenshell.entity_instance] = None
|
||||
|
||||
Reference in New Issue
Block a user