From 8ee0a1611dea5dd223f7646a7908f9e6bbc2ddcf Mon Sep 17 00:00:00 2001 From: Massimo Fabbro Date: Sat, 22 Apr 2023 22:27:25 +0200 Subject: [PATCH] Now it's possible to create spaces automatically from selected walls --- .../blenderbim/bim/module/model/__init__.py | 1 + .../blenderbim/bim/module/model/space.py | 138 ++++++++++++++++++ .../blenderbim/bim/module/model/ui.py | 2 + 3 files changed, 141 insertions(+) diff --git a/src/blenderbim/blenderbim/bim/module/model/__init__.py b/src/blenderbim/blenderbim/bim/module/model/__init__.py index f074ab1e2e..5a38a99e01 100644 --- a/src/blenderbim/blenderbim/bim/module/model/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/model/__init__.py @@ -96,6 +96,7 @@ classes = ( slab.ResetVertex, slab.SetArcIndex, space.GenerateSpace, + space.GenerateSpacesFromWalls, prop.BIMModelProperties, prop.BIMArrayProperties, prop.BIMStairProperties, diff --git a/src/blenderbim/blenderbim/bim/module/model/space.py b/src/blenderbim/blenderbim/bim/module/model/space.py index ec005b4fd0..d58ed99162 100644 --- a/src/blenderbim/blenderbim/bim/module/model/space.py +++ b/src/blenderbim/blenderbim/bim/module/model/space.py @@ -26,6 +26,7 @@ import blenderbim.tool as tool import blenderbim.core.type from math import pi from mathutils import Vector, Matrix +from shapely import Polygon class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator): @@ -166,3 +167,140 @@ class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator): (obj.matrix_world @ Vector((max_x, max_y, 0.0))).to_2d(), ], } + + +class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.generate_spaces_from_walls" + bl_label = "Generate Spaces From Walls" + bl_options = {"REGISTER", "UNDO"} + bl_description = "Generate spaces from selected walls. The active object must be a wall." + + @classmethod + def poll(cls, context): + return context.selected_objects + + def _execute(self, context): + props = context.scene.BIMModelProperties + + active_obj = bpy.context.active_object + if not active_obj: + self.report({'ERROR'}, "No active object. Please select a wall") + return + + element = None + element = tool.Ifc.get_entity(active_obj) + if not element.is_a("IfcWall"): + self.report({'ERROR'}, "The active object is not a wall. Please select a wall.") + return + + collection = active_obj.users_collection[0] + collection_obj = bpy.data.objects.get(collection.name) + if not collection_obj: + self.report({'ERROR'}, "No collection found. Please insert one.") + return + + spatial_element = tool.Ifc.get_entity(collection_obj) + if not spatial_element: + self.report({'ERROR'}, "The collection hasn't an ifc space entity. Please provide one.") + return + + if not bpy.context.selected_objects: + self.report({'ERROR'}, "No selected objects found. Please select walls.") + return + + x, y, z = active_obj.matrix_world.translation.xyz + mat = active_obj.matrix_world + h = active_obj.dimensions.z + + boundary_elements = [] + for obj in bpy.context.selected_objects: + subelement = tool.Ifc.get_entity(obj) + if subelement.is_a("IfcWall"): + boundary_elements.append(subelement) + + polys = [] + for boundary_element in boundary_elements: + obj = tool.Ifc.get_object(boundary_element) + if not obj: + continue + points = [] + base = self.get_obj_base_points(obj) + for index in ["low_left", "low_right", "high_right", "high_left"]: + point = base[index] + points.append(point) + + polys.append(Polygon(points)) + + model = tool.Ifc.get() + + project_unit = ifcopenshell.util.unit.get_project_unit(model, "LENGTHUNIT") + prefix=getattr(project_unit, "Prefix", None) + + tolerance = 0.03 + + converted_tolerance = ifcopenshell.util.unit.convert( + value = tolerance, + from_prefix = None, + from_unit = "METRE", + to_prefix = prefix, + to_unit = project_unit.Name, + ) + + + union = shapely.ops.unary_union(polys).buffer(converted_tolerance, cap_style = 2, join_style = 2) + + i=0 + for linear_ring in union.interiors: + poly = Polygon(linear_ring) + + poly = poly.buffer(converted_tolerance, single_sided=True, cap_style = 2, join_style = 2) + + bm = bmesh.new() + bm.verts.index_update() + bm.edges.index_update() + + mat_invert = mat.inverted() + + new_verts = [bm.verts.new(mat_invert @ Vector([v[0], v[1], 0])) for v in poly.exterior.coords[0:-1]] + [bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)] + bm.edges.new((new_verts[len(new_verts) - 1], new_verts[0])) + + bm.verts.index_update() + bm.edges.index_update() + + bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=1e-5) + bmesh.ops.triangle_fill(bm, edges=bm.edges) + bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 5, verts=bm.verts, edges=bm.edges) + + extrusion = bmesh.ops.extrude_face_region(bm, geom=bm.faces) + extruded_verts = [g for g in extrusion["geom"] if isinstance(g, bmesh.types.BMVert)] + bmesh.ops.translate(bm, vec=[0.0, 0.0, h], verts=extruded_verts) + + bmesh.ops.recalc_face_normals(bm, faces = bm.faces) + + name = "Space" + str(i) + mesh = bpy.data.meshes.new(name = name) + bm.to_mesh(mesh) + bm.free() + + obj = bpy.data.objects.new(name, mesh) + obj.matrix_world = mat + collection.objects.link(obj) + + bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcSpace") + i+=1 + + return {"FINISHED"} + + + def get_obj_base_points(self, obj): + x_values = [(obj.matrix_world @ Vector(v)).x for v in obj.bound_box] + y_values = [(obj.matrix_world @ Vector(v)).y for v in obj.bound_box] + return { + "low_left": (x_values[0], y_values[0]), + "high_left": (x_values[3], y_values[3]), + "low_right": (x_values[4], y_values[4]), + "high_right": (x_values[7], y_values[7]), + } + + diff --git a/src/blenderbim/blenderbim/bim/module/model/ui.py b/src/blenderbim/blenderbim/bim/module/model/ui.py index 96641eaf5b..755ac76e17 100644 --- a/src/blenderbim/blenderbim/bim/module/model/ui.py +++ b/src/blenderbim/blenderbim/bim/module/model/ui.py @@ -135,6 +135,8 @@ class BIM_PT_authoring(Panel): row.operator("bim.align_wall", icon="ANCHOR_TOP", text="Ext.").align_type = "EXTERIOR" row.operator("bim.align_wall", icon="ANCHOR_CENTER", text="C/L").align_type = "CENTERLINE" row.operator("bim.align_wall", icon="ANCHOR_BOTTOM", text="Int.").align_type = "INTERIOR" + row = self.layout.row(align=True) + row.operator("bim.generate_spaces_from_walls") class BIM_PT_array(bpy.types.Panel):