mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-20 23:36:20 +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:
committed by
Thomas Krijnen
parent
97cd08ee92
commit
b10c9cd902
@@ -94,6 +94,7 @@ classes = (
|
|||||||
wall.EnableEditingWall,
|
wall.EnableEditingWall,
|
||||||
wall.ExtendWallHeightToCursor,
|
wall.ExtendWallHeightToCursor,
|
||||||
wall.ExtendWallsToUnderside,
|
wall.ExtendWallsToUnderside,
|
||||||
|
wall.RegenerateWallToUnderside,
|
||||||
wall.ExtendWallsToWall,
|
wall.ExtendWallsToWall,
|
||||||
wall.ExtendWallsToPolylinePoint,
|
wall.ExtendWallsToPolylinePoint,
|
||||||
wall.ExtendWallToCursor,
|
wall.ExtendWallToCursor,
|
||||||
|
|||||||
@@ -317,6 +317,24 @@ class ExtendWallsToUnderside(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
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")
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
class ExtendWallsToWall(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
bl_idname = "bim.extend_walls_to_wall"
|
bl_idname = "bim.extend_walls_to_wall"
|
||||||
bl_label = "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()
|
bpy.ops.bim.generate_space()
|
||||||
return
|
return
|
||||||
if self.active_material_usage == "LAYER2":
|
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":
|
elif self.active_material_usage == "LAYER3":
|
||||||
bpy.ops.bim.recalculate_slab()
|
bpy.ops.bim.recalculate_slab()
|
||||||
elif tool.System.get_ports(element):
|
elif tool.System.get_ports(element):
|
||||||
|
|||||||
@@ -161,6 +161,31 @@ def align_objects(
|
|||||||
model.align_objects(reference_obj, objs, align_type)
|
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(
|
def extend_wall_to_slab(
|
||||||
ifc: type[tool.Ifc],
|
ifc: type[tool.Ifc],
|
||||||
geometry: type[tool.Geometry],
|
geometry: type[tool.Geometry],
|
||||||
|
|||||||
@@ -679,6 +679,7 @@ class Model:
|
|||||||
def export_profile(cls, obj, position=None): pass
|
def export_profile(cls, obj, position=None): pass
|
||||||
def generate_occurrence_name(cls, element_type, ifc_class): pass
|
def generate_occurrence_name(cls, element_type, ifc_class): pass
|
||||||
def get_extrusion(cls, representation): pass
|
def get_extrusion(cls, representation): pass
|
||||||
|
def get_connected_slab_objs(cls, wall): pass
|
||||||
def get_manual_booleans(cls, element): pass
|
def get_manual_booleans(cls, element): pass
|
||||||
def get_material_layer_parameters(cls, element): pass
|
def get_material_layer_parameters(cls, element): pass
|
||||||
def get_slab_clipping_bmesh(cls, obj): pass
|
def get_slab_clipping_bmesh(cls, obj): pass
|
||||||
@@ -694,6 +695,7 @@ class Model:
|
|||||||
def regenerate_profile(cls, obj): pass
|
def regenerate_profile(cls, obj): pass
|
||||||
def regenerate_slab(cls, obj): pass
|
def regenerate_slab(cls, obj): pass
|
||||||
def reload_body_representation(cls, obj_or_objects): 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
|
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)
|
items.append(item.FirstOperand)
|
||||||
return booleans
|
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
|
@classmethod
|
||||||
def get_manual_booleans(
|
def get_manual_booleans(
|
||||||
cls, element: ifcopenshell.entity_instance, representation: Optional[ifcopenshell.entity_instance] = None
|
cls, element: ifcopenshell.entity_instance, representation: Optional[ifcopenshell.entity_instance] = None
|
||||||
|
|||||||
Reference in New Issue
Block a user