mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
Add AddSlabFromWalls operator.
This operator allows users to create a slab from a selected closed loop of walls. When the slab tool is active, pressing `Shift + A` will generate a slab based on the exterior polygon of the selected walls.
This commit is contained in:
@@ -100,6 +100,7 @@ classes = (
|
||||
roof.GenerateHippedRoof,
|
||||
slab.DisableEditingExtrusionProfile,
|
||||
slab.DisableEditingSketchExtrusionProfile,
|
||||
slab.AddSlabFromWall,
|
||||
slab.DrawPolylineSlab,
|
||||
slab.EditExtrusionProfile,
|
||||
slab.EditSketchExtrusionProfile,
|
||||
|
||||
@@ -42,9 +42,9 @@ class DumbSlabGenerator:
|
||||
def __init__(self, relating_type: ifcopenshell.entity_instance):
|
||||
self.relating_type = relating_type
|
||||
|
||||
def generate(self, draw_from_polyline=False):
|
||||
def generate(self, insertion_type="CURSOR"):
|
||||
self.file = tool.Ifc.get()
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
thicknesses = []
|
||||
for rel in self.relating_type.HasAssociations:
|
||||
if rel.is_a("IfcRelAssociatesMaterial"):
|
||||
@@ -69,16 +69,18 @@ class DumbSlabGenerator:
|
||||
self.container = container
|
||||
self.container_obj = tool.Ifc.get_object(container)
|
||||
|
||||
self.depth = sum(thicknesses) * unit_scale
|
||||
self.depth = sum(thicknesses) * self.unit_scale
|
||||
self.width = 3
|
||||
self.length = 3
|
||||
self.rotation = 0
|
||||
self.location = Vector((0, 0, 0))
|
||||
self.x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else props.x_angle
|
||||
|
||||
if draw_from_polyline:
|
||||
if insertion_type == "POLYLINE":
|
||||
return self.derive_from_polyline()
|
||||
else:
|
||||
elif insertion_type == "WALLS":
|
||||
return self.derive_from_walls()
|
||||
elif insertion_type == "CURSOR":
|
||||
return self.derive_from_cursor()
|
||||
|
||||
def derive_from_polyline(self):
|
||||
@@ -100,6 +102,24 @@ class DumbSlabGenerator:
|
||||
self.location = bpy.context.scene.cursor.location
|
||||
return self.create_slab()
|
||||
|
||||
def derive_from_walls(self):
|
||||
walls, is_closed_loop = tool.Model.get_connected_walls(bpy.context.selected_objects)
|
||||
polyline_points = []
|
||||
poly = tool.Model.get_polygons_from_wall_axis(walls)
|
||||
polyline_points = [tuple([v for v in c]) for c in poly.exterior.coords]
|
||||
|
||||
self.location = Vector((polyline_points[0][0], polyline_points[0][1], self.container_obj.location.z))
|
||||
self.polyline = [tuple(Vector((p[0], p[1], 0.0)) - self.location) for p in polyline_points]
|
||||
|
||||
if len(self.polyline) <= 2:
|
||||
return
|
||||
|
||||
# Always assume a closed polyline
|
||||
if self.polyline[0] != self.polyline[-1]:
|
||||
self.polyline.append(self.polyline[0])
|
||||
|
||||
return self.create_slab()
|
||||
|
||||
def create_slab(self):
|
||||
ifc_classes = ifcopenshell.util.type.get_applicable_entities(self.relating_type.is_a(), self.file.schema)
|
||||
# Standard cases are deprecated, so let's cull them
|
||||
@@ -729,6 +749,33 @@ class SetArcIndex(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddSlabFromWall(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.draw_slab_from_wall"
|
||||
bl_label = "Draw Slab From Wall"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.space_data.type == "VIEW_3D"
|
||||
|
||||
def __init__(self):
|
||||
self.relating_type = None
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
relating_type_id = props.relating_type_id
|
||||
if relating_type_id:
|
||||
self.relating_type = tool.Ifc.get().by_id(int(relating_type_id))
|
||||
|
||||
def _execute(self, context):
|
||||
if not self.relating_type:
|
||||
return {"FINISHED"}
|
||||
walls, is_closed_loop = tool.Model.get_connected_walls(bpy.context.selected_objects)
|
||||
if not is_closed_loop:
|
||||
self.report({"WARNING"}, "Please select a closed loop of walls, or deselect the walls to add a slab using the polyline tool.")
|
||||
return {"FINISHED"}
|
||||
|
||||
DumbSlabGenerator(self.relating_type).generate("WALLS")
|
||||
return {"FINISHED"}
|
||||
|
||||
class DrawPolylineSlab(bpy.types.Operator, PolylineOperator):
|
||||
bl_idname = "bim.draw_polyline_slab"
|
||||
bl_label = "Draw Polyline Slab"
|
||||
@@ -750,7 +797,7 @@ class DrawPolylineSlab(bpy.types.Operator, PolylineOperator):
|
||||
if not self.relating_type:
|
||||
return {"FINISHED"}
|
||||
|
||||
DumbSlabGenerator(self.relating_type).generate(True)
|
||||
DumbSlabGenerator(self.relating_type).generate("POLYLINE")
|
||||
|
||||
def modal(self, context, event):
|
||||
if not self.relating_type:
|
||||
|
||||
@@ -963,6 +963,17 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if bpy.context.scene.BIMGeometryProperties.mode == "ITEM":
|
||||
bpy.ops.wm.call_menu(name="BIM_MT_add_representation_item")
|
||||
else:
|
||||
walls = False
|
||||
for obj in bpy.context.selected_objects:
|
||||
walls = tool.Ifc.get_entity(obj).is_a("IfcWall")
|
||||
if (
|
||||
walls
|
||||
and relating_type_id
|
||||
and tool.Model.get_usage_type(tool.Ifc.get().by_id(int(relating_type_id))) == "LAYER3"
|
||||
):
|
||||
bpy.ops.bim.draw_slab_from_wall("INVOKE_DEFAULT")
|
||||
return {"FINISHED"}
|
||||
|
||||
for obj in tool.Blender.get_selected_objects():
|
||||
obj.select_set(False)
|
||||
if relating_type_id and tool.Model.get_usage_type(tool.Ifc.get().by_id(int(relating_type_id))) == "LAYER2":
|
||||
|
||||
@@ -672,6 +672,71 @@ class Model(bonsai.core.tool.Model):
|
||||
]
|
||||
return axes
|
||||
|
||||
@classmethod
|
||||
def get_connected_walls(
|
||||
cls, walls: list[bpy.types.Object], opposite_direction: bool = False
|
||||
) -> list[bpy.types.Object]:
|
||||
"""
|
||||
Loop through walls by retrieving the next connected wall using the ConnectedTo attribute.
|
||||
If the function encounters the first wall again, it will return the list of connected walls and indicate that a closed loop has been formed.
|
||||
If it reaches the end of the connections, the function will call itself in the opposite direction using the ConnectedFrom method
|
||||
to obtain a list of connected walls in an open loop.
|
||||
"""
|
||||
|
||||
is_closed_loop = False
|
||||
wall1 = tool.Ifc.get_entity(walls[0])
|
||||
wall = wall1
|
||||
if not opposite_direction:
|
||||
connection = "ConnectedTo"
|
||||
relation = "RelatedElement"
|
||||
else:
|
||||
connection = "ConnectedFrom"
|
||||
relation = "RelatingElement"
|
||||
|
||||
ordered_walls = []
|
||||
ordered_walls.append(wall1)
|
||||
for i in range(len(walls)):
|
||||
if not getattr(wall, connection) or (
|
||||
next_wall := tool.Ifc.get_object(getattr(getattr(wall, connection)[0], relation)) not in walls
|
||||
):
|
||||
if opposite_direction:
|
||||
return ordered_walls, is_closed_loop
|
||||
ordered_walls, is_closed_loop = cls.get_connected_walls(walls, True)
|
||||
break
|
||||
next_wall = getattr(getattr(wall, connection)[0], relation)
|
||||
if next_wall == wall1:
|
||||
is_closed_loop = True
|
||||
break
|
||||
else:
|
||||
ordered_walls.append(next_wall)
|
||||
wall = next_wall
|
||||
|
||||
return [tool.Ifc.get_object(wall) for wall in ordered_walls], is_closed_loop
|
||||
|
||||
@classmethod
|
||||
def get_polygons_from_wall_axis(cls, walls: list[bpy.types.Object]) -> list[shapely.Polygon]:
|
||||
"""
|
||||
Get the polygons formed by the intersection of the wall axis reference and side.
|
||||
The polygon with the larger area will be considered the external polygon.
|
||||
This function only works with closed loops.
|
||||
"""
|
||||
points1 = []
|
||||
points2 = []
|
||||
for w1, w2 in zip(walls, walls[1:] + [walls[0]]):
|
||||
layers1 = tool.Model.get_material_layer_parameters(tool.Ifc.get_entity(w1))
|
||||
layers2 = tool.Model.get_material_layer_parameters(tool.Ifc.get_entity(w2))
|
||||
axis1 = tool.Model.get_wall_axis(w1, layers1)
|
||||
axis2 = tool.Model.get_wall_axis(w2, layers2)
|
||||
intersection1 = tool.Cad.intersect_edges(axis1["reference"], axis2["reference"])
|
||||
intersection2 = tool.Cad.intersect_edges(axis1["side"], axis2["side"])
|
||||
points1.append(intersection1[0])
|
||||
points2.append(intersection2[0])
|
||||
|
||||
poly1 = shapely.Polygon(points1)
|
||||
poly2 = shapely.Polygon(points2)
|
||||
|
||||
return poly1 if poly1.area > poly2.area else poly2
|
||||
|
||||
@classmethod
|
||||
def handle_array_on_copied_element(
|
||||
cls, element: ifcopenshell.entity_instance, array_data: Optional[dict[str, Any]] = None
|
||||
|
||||
Reference in New Issue
Block a user