Add extend/regenerate walls to multiple undersides

extend_walls_to_underside now accepts multiple slab/roof
objects in a single operation — all selected non-LAYER2 IFC
elements are treated as clip targets, all LAYER2 elements as
walls. Placement sync is done once upfront; each wall is then
clipped against every selected slab before reloading.

Also adds bim.regenerate_wall_to_underside (Shift+G): after
moving a slab, re-clips connected walls using the existing
IfcRelConnectsElements(TOP) relationship. Old booleans are
removed via remove_representation_item before re-clipping.

Generated with the assistance of an AI coding tool.
This commit is contained in:
Ryan Schultz
2026-04-16 22:44:53 -05:00
parent 5217ff0dce
commit 2e85a11c82
2 changed files with 25 additions and 18 deletions
+10 -9
View File
@@ -300,20 +300,21 @@ class ExtendWallsToUnderside(bpy.types.Operator, tool.Ifc.Operator):
# of the selected walls has an in-progress parametric draft, commit it before # of the selected walls has an in-progress parametric draft, commit it before
# extending, so the slab clip operates on the just-finalised IFC state. # extending, so the slab clip operates on the just-finalised IFC state.
_commit_pending_wall_edits_for_selection(context) _commit_pending_wall_edits_for_selection(context)
slab = None slabs: list[bpy.types.Object] = []
walls: list[bpy.types.Object] = [] walls: list[bpy.types.Object] = []
if (obj := tool.Blender.get_active_object(is_selected=True)) and (element := tool.Ifc.get_entity(obj)): for obj in tool.Blender.get_selected_objects():
slab = obj
for obj in tool.Blender.get_selected_objects(include_active=False):
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
usage = tool.Model.get_usage_type(element) if element else None if not element:
if element and usage == "LAYER2": continue
if tool.Model.get_usage_type(element) == "LAYER2":
walls.append(obj) walls.append(obj)
if slab and walls: else:
core.extend_wall_to_slab(tool.Ifc, tool.Geometry, tool.Model, slab, walls) slabs.append(obj)
if slabs and walls:
core.extend_wall_to_slab(tool.Ifc, tool.Geometry, tool.Model, slabs, walls)
_resync_walls_after_mutation(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 at least one other IFC element")
class RegenerateWallToUnderside(bpy.types.Operator, tool.Ifc.Operator): class RegenerateWallToUnderside(bpy.types.Operator, tool.Ifc.Operator):
+15 -9
View File
@@ -190,20 +190,26 @@ def extend_wall_to_slab(
ifc: type[tool.Ifc], ifc: type[tool.Ifc],
geometry: type[tool.Geometry], geometry: type[tool.Geometry],
model: type[tool.Model], model: type[tool.Model],
slab_obj: bpy.types.Object, slab_objs: list[bpy.types.Object],
wall_objs: list[bpy.types.Object], wall_objs: list[bpy.types.Object],
) -> None: ) -> None:
clip = model.get_slab_clipping_bmesh(slab_obj)
if not clip:
return
slab = ifc.get_entity(slab_obj)
for obj in wall_objs: for obj in wall_objs:
if ifc.is_moved(obj): if ifc.is_moved(obj):
geometry.run_edit_object_placement(obj=obj) geometry.run_edit_object_placement(obj=obj)
wall = ifc.get_entity(obj) clipped_walls = []
model.clip_wall_to_slab(wall, clip) for slab_obj in slab_objs:
model.connect_wall_to_slab(wall, slab) clip = model.get_slab_clipping_bmesh(slab_obj)
model.reload_body_representation(wall_objs) if not clip:
continue
slab = ifc.get_entity(slab_obj)
for obj in wall_objs:
wall = ifc.get_entity(obj)
model.clip_wall_to_slab(wall, clip)
model.connect_wall_to_slab(wall, slab)
if obj not in clipped_walls:
clipped_walls.append(obj)
if clipped_walls:
model.reload_body_representation(clipped_walls)
class RequireTwoWallsError(Exception): class RequireTwoWallsError(Exception):