mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-20 23:36:20 +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,
|
roof.GenerateHippedRoof,
|
||||||
slab.DisableEditingExtrusionProfile,
|
slab.DisableEditingExtrusionProfile,
|
||||||
slab.DisableEditingSketchExtrusionProfile,
|
slab.DisableEditingSketchExtrusionProfile,
|
||||||
|
slab.AddSlabFromWall,
|
||||||
slab.DrawPolylineSlab,
|
slab.DrawPolylineSlab,
|
||||||
slab.EditExtrusionProfile,
|
slab.EditExtrusionProfile,
|
||||||
slab.EditSketchExtrusionProfile,
|
slab.EditSketchExtrusionProfile,
|
||||||
|
|||||||
@@ -42,9 +42,9 @@ class DumbSlabGenerator:
|
|||||||
def __init__(self, relating_type: ifcopenshell.entity_instance):
|
def __init__(self, relating_type: ifcopenshell.entity_instance):
|
||||||
self.relating_type = relating_type
|
self.relating_type = relating_type
|
||||||
|
|
||||||
def generate(self, draw_from_polyline=False):
|
def generate(self, insertion_type="CURSOR"):
|
||||||
self.file = tool.Ifc.get()
|
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 = []
|
thicknesses = []
|
||||||
for rel in self.relating_type.HasAssociations:
|
for rel in self.relating_type.HasAssociations:
|
||||||
if rel.is_a("IfcRelAssociatesMaterial"):
|
if rel.is_a("IfcRelAssociatesMaterial"):
|
||||||
@@ -69,16 +69,18 @@ class DumbSlabGenerator:
|
|||||||
self.container = container
|
self.container = container
|
||||||
self.container_obj = tool.Ifc.get_object(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.width = 3
|
||||||
self.length = 3
|
self.length = 3
|
||||||
self.rotation = 0
|
self.rotation = 0
|
||||||
self.location = Vector((0, 0, 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
|
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()
|
return self.derive_from_polyline()
|
||||||
else:
|
elif insertion_type == "WALLS":
|
||||||
|
return self.derive_from_walls()
|
||||||
|
elif insertion_type == "CURSOR":
|
||||||
return self.derive_from_cursor()
|
return self.derive_from_cursor()
|
||||||
|
|
||||||
def derive_from_polyline(self):
|
def derive_from_polyline(self):
|
||||||
@@ -100,6 +102,24 @@ class DumbSlabGenerator:
|
|||||||
self.location = bpy.context.scene.cursor.location
|
self.location = bpy.context.scene.cursor.location
|
||||||
return self.create_slab()
|
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):
|
def create_slab(self):
|
||||||
ifc_classes = ifcopenshell.util.type.get_applicable_entities(self.relating_type.is_a(), self.file.schema)
|
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
|
# Standard cases are deprecated, so let's cull them
|
||||||
@@ -729,6 +749,33 @@ class SetArcIndex(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
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):
|
class DrawPolylineSlab(bpy.types.Operator, PolylineOperator):
|
||||||
bl_idname = "bim.draw_polyline_slab"
|
bl_idname = "bim.draw_polyline_slab"
|
||||||
bl_label = "Draw Polyline Slab"
|
bl_label = "Draw Polyline Slab"
|
||||||
@@ -750,7 +797,7 @@ class DrawPolylineSlab(bpy.types.Operator, PolylineOperator):
|
|||||||
if not self.relating_type:
|
if not self.relating_type:
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
DumbSlabGenerator(self.relating_type).generate(True)
|
DumbSlabGenerator(self.relating_type).generate("POLYLINE")
|
||||||
|
|
||||||
def modal(self, context, event):
|
def modal(self, context, event):
|
||||||
if not self.relating_type:
|
if not self.relating_type:
|
||||||
|
|||||||
@@ -963,6 +963,17 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
if bpy.context.scene.BIMGeometryProperties.mode == "ITEM":
|
if bpy.context.scene.BIMGeometryProperties.mode == "ITEM":
|
||||||
bpy.ops.wm.call_menu(name="BIM_MT_add_representation_item")
|
bpy.ops.wm.call_menu(name="BIM_MT_add_representation_item")
|
||||||
else:
|
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():
|
for obj in tool.Blender.get_selected_objects():
|
||||||
obj.select_set(False)
|
obj.select_set(False)
|
||||||
if relating_type_id and tool.Model.get_usage_type(tool.Ifc.get().by_id(int(relating_type_id))) == "LAYER2":
|
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
|
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
|
@classmethod
|
||||||
def handle_array_on_copied_element(
|
def handle_array_on_copied_element(
|
||||||
cls, element: ifcopenshell.entity_instance, array_data: Optional[dict[str, Any]] = None
|
cls, element: ifcopenshell.entity_instance, array_data: Optional[dict[str, Any]] = None
|
||||||
|
|||||||
Reference in New Issue
Block a user