From 1621449143340384bc26e2f041a0f8994621503a Mon Sep 17 00:00:00 2001 From: Massimo Fabbro Date: Wed, 27 Sep 2023 18:28:38 +0200 Subject: [PATCH 1/4] Add predefined type to AuthoringData --- .../blenderbim/bim/module/covering/prop.py | 1 + .../bim/module/covering/workspace.py | 28 +++++++++++++++++-- .../blenderbim/bim/module/model/covering.py | 2 +- .../blenderbim/bim/module/model/data.py | 8 ++++++ 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/covering/prop.py b/src/blenderbim/blenderbim/bim/module/covering/prop.py index d66aac26cd..330632ab05 100644 --- a/src/blenderbim/blenderbim/bim/module/covering/prop.py +++ b/src/blenderbim/blenderbim/bim/module/covering/prop.py @@ -38,3 +38,4 @@ import ifcopenshell class BIMCoveringProperties(PropertyGroup): pass # depth: bpy.props.FloatProperty(name="Depth", default=0.1, subtype="DISTANCE", description="Flooring depth") + depth: bpy.props.FloatProperty(name="ceiling_height", default=2.7, subtype="DISTANCE", description="Ceiling height") diff --git a/src/blenderbim/blenderbim/bim/module/covering/workspace.py b/src/blenderbim/blenderbim/bim/module/covering/workspace.py index 336d3bc2ef..9989d7caea 100644 --- a/src/blenderbim/blenderbim/bim/module/covering/workspace.py +++ b/src/blenderbim/blenderbim/bim/module/covering/workspace.py @@ -93,8 +93,32 @@ class CoveringToolUI: row.label(text="", icon="EVENT_A") active_obj = bpy.context.active_object element = tool.Ifc.get_entity(active_obj) - if element and bpy.context.selected_objects and element.is_a("IfcWall"): - op = row.operator("bim.add_instance_flooring_coverings_from_walls") + + if AuthoringData.data["predefined_type"] == "FLOORING": + if element and bpy.context.selected_objects and element.is_a("IfcWall"): + op = row.operator("bim.add_instance_flooring_coverings_from_walls") +# elif element and bpy.context.selected_objects and element.is_a("IfcSpace"): +# op. = row.operator("bim.add_istance_flooring_from_spaces"): + else: +# op = row.operator("bim.add_instance_flooring_from_cursor") + op = row.operator("bim.add_constr_type_instance", text="Add") + op.from_invoke = True + if cls.props.relating_type_id.isnumeric(): + op.relating_type_id = int(cls.props.relating_type_id) + +# elif AuthoringData.data["predefined_type"] == "CEILING": +# row = cls.layout.row(align=True) +# row.prop(data=cls.props, property="ceiling_height", text="ceiling height") +# if element and bpy.context.selected_objects and element.is_a("IfcWall"): +# op = row.operator("bim.add_instance_ceiling_coverings_from_walls") +# elif element and bpy.context.selected_objects and element.is_a("IfcSpace"): +# op. = row.operator("bim.add_istance_flooring_from_spaces"): +# else: +# op = row.operator("bim.add_instance_ceiling_from_cursor") +# op = row.operator("bim.add_constr_type_instance", text="Add") +# op.from_invoke = True +# if cls.props.relating_type_id.isnumeric(): +# op.relating_type_id = int(cls.props.relating_type_id) else: op = row.operator("bim.add_constr_type_instance", text="Add") op.from_invoke = True diff --git a/src/blenderbim/blenderbim/bim/module/model/covering.py b/src/blenderbim/blenderbim/bim/module/model/covering.py index da5f09cb06..3f9f0f4b2d 100644 --- a/src/blenderbim/blenderbim/bim/module/model/covering.py +++ b/src/blenderbim/blenderbim/bim/module/model/covering.py @@ -25,7 +25,7 @@ import blenderbim.core.covering as core class AddInstanceFlooringCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.add_instance_flooring_coverings_from_walls" - bl_label = "Add Typed Covering From Walls" + bl_label = "Add Covering From Walls" bl_options = {"REGISTER", "UNDO"} bl_description = "Add instance flooring coverings from selected walls. The active object must be a wall and layered vertically" diff --git a/src/blenderbim/blenderbim/bim/module/model/data.py b/src/blenderbim/blenderbim/bim/module/model/data.py index b1ad345e75..c9fde070e8 100644 --- a/src/blenderbim/blenderbim/bim/module/model/data.py +++ b/src/blenderbim/blenderbim/bim/module/model/data.py @@ -53,6 +53,7 @@ class AuthoringData: cls.data["ifc_element_type"] = cls.ifc_element_type cls.data["ifc_classes"] = cls.ifc_classes() cls.data["relating_type_id"] = cls.relating_type_id() # only after .ifc_classes() + cls.data["predefined_type"] = cls.predefined_type() cls.data["type_class"] = cls.type_class() # only after .type_class() @@ -247,6 +248,13 @@ class AuthoringData: return [(str(e.id()), e.Name or "Unnamed", e.Description or "") for e in results] return [] + @classmethod + def predefined_type(cls): + relating_type_id = cls.props.relating_type_id + relating_type = tool.Ifc.get().by_id(int(relating_type_id)) + predefined_type = relating_type.PredefinedType + return predefined_type + @classmethod def selected_material_usages(cls): selected_usages = {} From 09a9753ca714f875ec36b316e629643fbfd41f6f Mon Sep 17 00:00:00 2001 From: Massimo Fabbro Date: Wed, 27 Sep 2023 21:43:42 +0200 Subject: [PATCH 2/4] refactor create spaces from walls --- .../blenderbim/bim/module/model/space.py | 27 +++++++++++ src/blenderbim/blenderbim/core/spatial.py | 46 ++++--------------- src/blenderbim/blenderbim/core/tool.py | 4 +- src/blenderbim/blenderbim/tool/spatial.py | 9 ++++ .../test/bim/feature/spatial.feature | 11 ----- 5 files changed, 49 insertions(+), 48 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/space.py b/src/blenderbim/blenderbim/bim/module/model/space.py index 5f0e8edaf3..2ae6135205 100644 --- a/src/blenderbim/blenderbim/bim/module/model/space.py +++ b/src/blenderbim/blenderbim/bim/module/model/space.py @@ -68,6 +68,13 @@ class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator): bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR") return +# core.generate_space(tool.Ifc, tool.Spatial) + + + + + + active_obj = bpy.context.active_object element = None if bpy.context.selected_objects and active_obj: @@ -233,6 +240,26 @@ class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator): # walls (i.e. prismatic) in the active object storey. # In order to run, the active object must be a wall and # there must be selected walls + + active_obj = bpy.context.active_object + element = tool.Ifc.get_entity(active_obj) + container = tool.Spatial.get_container(element) + + if not active_obj: + self.report({"ERROR"}, "No active object. Please select a wall") + return + + element = tool.Ifc.get_entity(active_obj) + if element and not element.is_a("IfcWall"): + return self.report({"ERROR"}, "The active object is not a wall. Please select a wall.") + + if not container: + self.report({"ERROR"}, "The wall is not contained.") + + if not bpy.context.selected_objects: + self.report({"ERROR"}, "No selected objects found. Please select walls.") + return + core.generate_spaces_from_walls(tool.Ifc, tool.Spatial, tool.Collector) class ToggleSpaceVisibility(bpy.types.Operator, tool.Ifc.Operator): diff --git a/src/blenderbim/blenderbim/core/spatial.py b/src/blenderbim/blenderbim/core/spatial.py index 109168847b..c8c36e500b 100644 --- a/src/blenderbim/blenderbim/core/spatial.py +++ b/src/blenderbim/blenderbim/core/spatial.py @@ -123,31 +123,13 @@ def select_decomposed_elements(spatial): spatial.select_products(spatial.get_decomposed_elements(container)) #HERE STARTS SPATIAL TOOL +def generate_space(ifc, spatial): + pass + def generate_spaces_from_walls(ifc, spatial, collector): - active_obj = bpy.context.active_object - element = ifc.get_entity(active_obj) - container = spatial.get_container(element) - - if not active_obj: - self.report({"ERROR"}, "No active object. Please select a wall") - return - - element = ifc.get_entity(active_obj) - if element and not element.is_a("IfcWall"): - return self.report({"ERROR"}, "The active object is not a wall. Please select a wall.") - - if not container: - self.report({"ERROR"}, "The wall is not contained.") - - 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 - selected_objects = bpy.context.selected_objects + z = spatial.get_active_obj_z() + h = spatial.get_active_obj_height() union = spatial.get_union_shape_from_selected_objects() @@ -157,24 +139,16 @@ def generate_spaces_from_walls(ifc, spatial, collector): bm = spatial.get_bmesh_from_polygon(poly, h) 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 + obj = spatial.get_named_obj_from_bmesh(name, bmesh = bm) spatial.set_obj_origin_to_bboxcenter(obj) + spatial.traslate_obj_to_z_location(obj, z) - if z != 0: - obj.location = obj.location + Vector((0, 0, z)) + spatial.link_obj_to_active_collection(obj) + spatial.assign_ifcspace_class_to_obj(obj) - bpy.context.view_layer.active_layer_collection.collection.objects.link(obj) - bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcSpace") - container_obj = ifc.get_object(container) - blenderbim.core.spatial.assign_container( - ifc, collector, spatial, structure_obj=container_obj, element_obj=obj - ) + spatial.assign_container_to_obj(obj) def toggle_space_visibility(ifc, spatial): model = ifc.get() diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index de5d5f31cb..6aeed3a2ea 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -821,12 +821,14 @@ class Spatial: def get_bmesh_from_polygon(cls, poly, h): pass def get_named_obj_from_bmesh(cls, name, bmesh): pass def set_obj_origin_to_bboxcenter(cls, obj): pass - def get_active_obj_z(cls, obj): pass + def get_active_obj_z(cls): pass + def get_active_obj_height(cls): pass def traslate_obj_to_z_location(cls, obj): pass def link_obj_to_active_collection(cls, obj): pass def get_2d_vertices_from_obj(cls, obj): pass def assign_swept_area_outer_curve_from_2d_vertices(cls, obj, vertices): pass def get_body_representation(cls, obj): pass + def assign_ifcspace_class_to_obj(cls, obj): pass def assign_type_to_obj(cls, obj): pass def regen_obj_representation(cls, ifc, geometry, obj, body): pass def toggle_spaces_visibility_wired_and_textured(cls, spaces): pass diff --git a/src/blenderbim/blenderbim/tool/spatial.py b/src/blenderbim/blenderbim/tool/spatial.py index f838703059..efa5d659a0 100644 --- a/src/blenderbim/blenderbim/tool/spatial.py +++ b/src/blenderbim/blenderbim/tool/spatial.py @@ -427,6 +427,11 @@ class Spatial(blenderbim.core.tool.Spatial): x, y, z = bpy.context.active_object.matrix_world.translation.xyz return z + @classmethod + def get_active_obj_height(cls): + height = bpy.context.active_object.dimensions.z + return height + @classmethod def traslate_obj_to_z_location(cls, obj, z): if z != 0: @@ -468,6 +473,10 @@ class Spatial(blenderbim.core.tool.Spatial): body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") return body + @classmethod + def assign_ifcspace_class_to_obj(cls, obj): + bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcSpace") + @classmethod def assign_type_to_obj(cls, obj): relating_type_id = bpy.context.scene.BIMModelProperties.relating_type_id diff --git a/src/blenderbim/test/bim/feature/spatial.feature b/src/blenderbim/test/bim/feature/spatial.feature index 03e54ef479..b400efbec6 100644 --- a/src/blenderbim/test/bim/feature/spatial.feature +++ b/src/blenderbim/test/bim/feature/spatial.feature @@ -116,17 +116,6 @@ Scenario: Execute generate spaces from walls When I press "bim.generate_spaces_from_walls" Then nothing happens -Scenario: Execute generate flooring coverings from walls - Given an empty IFC project - And I load the demo construction library - And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType" - And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL100'][0].id()" - And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}" - And I press "bim.add_constr_type_instance" - And the object "IfcWall/Wall" is selected - When I press "bim.generate_flooring_coverings_from_walls" - Then nothing happens - Scenario: Execute toggle space visibility Given an empty IFC project And I add a cube From cda61bff5b180039da7de918cafdf5104f21b443 Mon Sep 17 00:00:00 2001 From: Massimo Fabbro Date: Mon, 2 Oct 2023 22:12:11 +0200 Subject: [PATCH 3/4] Refactor create_space operator and better UI for covering tool --- .../bim/module/covering/workspace.py | 12 +- .../blenderbim/bim/module/model/covering.py | 2 +- .../blenderbim/bim/module/model/prop.py | 1 + .../blenderbim/bim/module/model/space.py | 161 +-------------- src/blenderbim/blenderbim/core/spatial.py | 51 ++++- src/blenderbim/blenderbim/core/tool.py | 14 ++ src/blenderbim/blenderbim/tool/spatial.py | 184 +++++++++++++++++- 7 files changed, 251 insertions(+), 174 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/covering/workspace.py b/src/blenderbim/blenderbim/bim/module/covering/workspace.py index 9989d7caea..e41145d959 100644 --- a/src/blenderbim/blenderbim/bim/module/covering/workspace.py +++ b/src/blenderbim/blenderbim/bim/module/covering/workspace.py @@ -42,13 +42,14 @@ class CoveringTool(WorkSpaceTool): bl_description = "Create and edit coverings" bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.covering") bl_widget = None + ifc_element_type = "IfcCoveringType" bl_keymap = tool.Blender.get_default_selection_keypmap() + ( ("bim.covering_hotkey", {"type": "A", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_A")]}), ) @classmethod def draw_settings(cls, context, layout, ws_tool): - CoveringToolUI.draw(context, layout, ifc_element_type="IfcCoveringType") + CoveringToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) def add_layout_hotkey(layout, text, hotkey, description): args = ["covering", layout, text, hotkey, description] @@ -71,7 +72,6 @@ class CoveringToolUI: elif AuthoringData.data["ifc_element_type"] != ifc_element_type: AuthoringData.load(ifc_element_type) - if context.region.type == "TOOL_HEADER": cls.draw_header_interface() elif context.region.type in ("UI", "WINDOW"): @@ -97,6 +97,7 @@ class CoveringToolUI: if AuthoringData.data["predefined_type"] == "FLOORING": if element and bpy.context.selected_objects and element.is_a("IfcWall"): op = row.operator("bim.add_instance_flooring_coverings_from_walls") +# PLEASE KEEP COMMENTS AS A REMINDER # elif element and bpy.context.selected_objects and element.is_a("IfcSpace"): # op. = row.operator("bim.add_istance_flooring_from_spaces"): else: @@ -183,8 +184,11 @@ class Hotkey(bpy.types.Operator, Operator): def hotkey_S_A(self): active_obj = bpy.context.active_object element = tool.Ifc.get_entity(active_obj) - if element and bpy.context.selected_objects and element.is_a("IfcWall"): - bpy.ops.bim.add_instance_flooring_coverings_from_walls() + if AuthoringData.data["predefined_type"] == "FLOORING": + if element and bpy.context.selected_objects and element.is_a("IfcWall"): + bpy.ops.bim.add_instance_flooring_coverings_from_walls() + else: + bpy.ops.bim.add_constr_type_instance() else: bpy.ops.bim.add_constr_type_instance() diff --git a/src/blenderbim/blenderbim/bim/module/model/covering.py b/src/blenderbim/blenderbim/bim/module/model/covering.py index 3f9f0f4b2d..a5d954aea0 100644 --- a/src/blenderbim/blenderbim/bim/module/model/covering.py +++ b/src/blenderbim/blenderbim/bim/module/model/covering.py @@ -25,7 +25,7 @@ import blenderbim.core.covering as core class AddInstanceFlooringCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.add_instance_flooring_coverings_from_walls" - bl_label = "Add Covering From Walls" + bl_label = "Add Flooring From Walls" bl_options = {"REGISTER", "UNDO"} bl_description = "Add instance flooring coverings from selected walls. The active object must be a wall and layered vertically" diff --git a/src/blenderbim/blenderbim/bim/module/model/prop.py b/src/blenderbim/blenderbim/bim/module/model/prop.py index 2723e7904e..2b167bd730 100644 --- a/src/blenderbim/blenderbim/bim/module/model/prop.py +++ b/src/blenderbim/blenderbim/bim/module/model/prop.py @@ -79,6 +79,7 @@ def update_type_class(self, context): def update_relating_type_id(self, context): AuthoringData.data["relating_type_id"] = AuthoringData.relating_type_id() AuthoringData.data["type_thumbnail"] = AuthoringData.type_thumbnail() + AuthoringData.data["predefined_type"] = AuthoringData.predefined_type() def update_type_page(self, context): diff --git a/src/blenderbim/blenderbim/bim/module/model/space.py b/src/blenderbim/blenderbim/bim/module/model/space.py index 2ae6135205..7a63a31604 100644 --- a/src/blenderbim/blenderbim/bim/module/model/space.py +++ b/src/blenderbim/blenderbim/bim/module/model/space.py @@ -50,13 +50,7 @@ class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator): def msg(self, context): self.layout.label(text="NO ACTIVE STOREY") - props = context.scene.BIMModelProperties - relating_type_id = props.relating_type_id - relating_type = None - if relating_type_id: - relating_type = tool.Ifc.get().by_id(int(relating_type_id)) - if not relating_type.is_a("IfcSpaceType"): - relating_type = None +# props = context.scene.BIMModelProperties collection = context.view_layer.active_layer_collection.collection collection_obj = collection.BIMCollectionProperties.obj @@ -68,158 +62,7 @@ class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator): bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR") return -# core.generate_space(tool.Ifc, tool.Spatial) - - - - - - - active_obj = bpy.context.active_object - element = None - if bpy.context.selected_objects and active_obj: - element = tool.Ifc.get_entity(active_obj) - mat = active_obj.matrix_world - local_bbox_center = 0.125 * sum((Vector(b) for b in active_obj.bound_box), Vector()) - global_bbox_center = mat @ local_bbox_center - x = global_bbox_center.x - y = global_bbox_center.y - z = (mat @ Vector(active_obj.bound_box[0])).z - - h = active_obj.dimensions.z - else: - x, y = context.scene.cursor.location.xy - z = collection_obj.matrix_world.translation.z - mat = Matrix() - mat.translation = (x, y, z) - h = 3 - - calculation_rl = context.scene.BIMModelProperties.rl3 - self.cut_point = collection_obj.matrix_world.translation.copy() + Vector((0, 0, calculation_rl)) - self.cut_normal = Vector((0, 0, 1)) - boundary_lines = [] - - gross_settings = ifcopenshell.geom.settings() - gross_settings.set(gross_settings.DISABLE_OPENING_SUBTRACTIONS, True) - - for obj in bpy.context.visible_objects: - visible_element = tool.Ifc.get_entity(obj) - - if ( - not visible_element - or obj.type != "MESH" - or not self.is_bounding_class(visible_element) - or not tool.Drawing.is_intersecting_plane(obj, self.cut_point, self.cut_normal) - ): - continue - - old_mesh = None - if visible_element.HasOpenings: - new_mesh = self.create_mesh(ifcopenshell.geom.create_shape(gross_settings, visible_element)) - old_mesh = obj.data - obj.data = new_mesh - - local_cut_point = obj.matrix_world.inverted() @ self.cut_point - local_cut_normal = obj.matrix_world.inverted().to_quaternion() @ self.cut_normal - verts, edges = tool.Drawing.bisect_mesh_with_plane(obj, local_cut_point, local_cut_normal) - - if old_mesh: - obj.data = old_mesh - bpy.data.meshes.remove(new_mesh) - - for edge in edges or []: - boundary_lines.append( - shapely.LineString( - [Vector((round(x, 3) for x in verts[edge[0]])), Vector((round(x, 3) for x in verts[edge[1]]))] - ) - ) - - unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines)) - closed_polygons = shapely.polygonize(unioned_boundaries.geoms) - - space_polygon = None - for polygon in closed_polygons.geoms: - if shapely.contains_xy(polygon, x, y): - space_polygon = shapely.force_3d(polygon) - - if not space_polygon: - return - - 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], z])) for v in space_polygon.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])) - - for interior in space_polygon.interiors: - new_verts = [bm.verts.new(mat_invert @ Vector([v[0], v[1], z])) for v in interior.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) - - mesh = bpy.data.meshes.new(name="Space") - bm.to_mesh(mesh) - bm.free() - - if element and element.is_a("IfcSpace"): - mesh.name = active_obj.data.name - mesh.BIMMeshProperties.ifc_definition_id = active_obj.data.BIMMeshProperties.ifc_definition_id - tool.Geometry.change_object_data(active_obj, mesh, is_global=True) - tool.Ifc.edit(active_obj) - else: - if relating_type: - name = tool.Model.generate_occurrence_name(relating_type, "IfcSpace") - else: - name = "Space" - obj = bpy.data.objects.new("Space", mesh) - obj.matrix_world = mat - collection.objects.link(obj) - bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcSpace") - element = tool.Ifc.get_entity(obj) - if relating_type: - blenderbim.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type) - - def is_bounding_class(self, element): - for ifc_class in ["IfcWall", "IfcColumn", "IfcMember", "IfcVirtualElement", "IfcPlate"]: - if element.is_a(ifc_class): - return True - return False - - def create_mesh(self, shape): - geometry = shape.geometry - mesh = bpy.data.meshes.new("tmp") - verts = geometry.verts - if geometry.faces: - num_vertices = len(verts) // 3 - total_faces = len(geometry.faces) - loop_start = range(0, total_faces, 3) - num_loops = total_faces // 3 - loop_total = [3] * num_loops - num_vertex_indices = len(geometry.faces) - - mesh.vertices.add(num_vertices) - mesh.vertices.foreach_set("co", verts) - mesh.loops.add(num_vertex_indices) - mesh.loops.foreach_set("vertex_index", geometry.faces) - mesh.polygons.add(num_loops) - mesh.polygons.foreach_set("loop_start", loop_start) - mesh.polygons.foreach_set("loop_total", loop_total) - mesh.polygons.foreach_set("use_smooth", [0] * total_faces) - mesh.update() - return mesh + core.generate_space(tool.Ifc, tool.Spatial, tool.Model) class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator): diff --git a/src/blenderbim/blenderbim/core/spatial.py b/src/blenderbim/blenderbim/core/spatial.py index c8c36e500b..85a09cc7b4 100644 --- a/src/blenderbim/blenderbim/core/spatial.py +++ b/src/blenderbim/blenderbim/core/spatial.py @@ -16,9 +16,7 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . -import blenderbim.core -import bpy - +import blenderbim.core.type def reference_structure(ifc, spatial, structure=None, element=None): if spatial.can_reference(structure, element): @@ -123,8 +121,51 @@ def select_decomposed_elements(spatial): spatial.select_products(spatial.get_decomposed_elements(container)) #HERE STARTS SPATIAL TOOL -def generate_space(ifc, spatial): - pass +def generate_space(ifc, spatial, model): + active_obj = spatial.get_active_obj() + selected_objects = spatial.get_selected_objects() + element = None + relating_type_id = spatial.get_relating_type_id() + + relating_type = None + if relating_type_id: + relating_type = ifc.get().by_id(int(relating_type_id)) + if not relating_type.is_a("IfcSpaceType"): + relating_type = None + + if selected_objects and active_obj: + x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_active_obj(active_obj) ##mat + element = ifc.get_entity(active_obj) + + else: + x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_cursor() ##mat + + space_polygon = spatial.get_space_polygon_from_context_visible_objects(x, y) + + if not space_polygon: + return + + bm = spatial.get_bmesh_from_polygon(space_polygon, h=h) ##mat + + mesh = spatial.get_named_mesh_from_bmesh(name = "Space", bmesh = bm) + + if element and element.is_a("IfcSpace"): + spatial.edit_active_space_obj_from_mesh(mesh) + else: + if relating_type: + name = model.generate_occurrence_name(relating_type, "IfcSpace") + else: + name = "Space" + + obj = spatial.get_named_obj_from_mesh(name, mesh) ##mat + spatial.set_obj_origin_to_cursor_position(obj) + spatial.link_obj_to_active_collection(obj) + spatial.assign_ifcspace_class_to_obj(obj) + + element = ifc.get_entity(obj) + + if relating_type: + blenderbim.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type) def generate_spaces_from_walls(ifc, spatial, collector): diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 6aeed3a2ea..fc2964d3bc 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -810,6 +810,13 @@ class Spatial: def set_relative_object_matrix(cls, target_obj, relative_to_obj, matrix): pass def show_scene_objects(cls): pass #HERE STARTS SPATIAL TOOL + def is_bounding_class(cls, visible_element): pass + def get_space_polygon_from_context_visible_objects(cls, x, y): pass + def get_boundary_lines_from_context_visible_objects(cls, visible_objects): pass + def get_gross_mesh_from_element(cls, visible_element): pass + def create_mesh_from_shape(cls, shape): pass + def get_x_y_z_h_mat_from_active_obj(cls, active_obj): pass + def get_x_y_z_h_mat_from_cursor(cls): pass def get_union_shape_from_selected_objects(cls): pass def get_boundary_elements(cls, selected_objects): pass def get_polygons(cls, boundary_elements): pass @@ -820,9 +827,16 @@ class Spatial: def get_buffered_poly_from_linear_ring(cls, linear_ring): pass def get_bmesh_from_polygon(cls, poly, h): pass def get_named_obj_from_bmesh(cls, name, bmesh): pass + def get_named_obj_from_mesh(cls, name, mesh): pass + def get_named_mesh_from_bmesh(cls, name, bmesh): pass + def edit_active_space_obj_from_mesh(cls, mesh): pass def set_obj_origin_to_bboxcenter(cls, obj): pass + def set_obj_origin_to_cursor_position(cls, obj): pass + def get_selected_objects(cls): pass + def get_active_obj(cls): pass def get_active_obj_z(cls): pass def get_active_obj_height(cls): pass + def get_relating_type_id(cls): pass def traslate_obj_to_z_location(cls, obj): pass def link_obj_to_active_collection(cls, obj): pass def get_2d_vertices_from_obj(cls, obj): pass diff --git a/src/blenderbim/blenderbim/tool/spatial.py b/src/blenderbim/blenderbim/tool/spatial.py index efa5d659a0..e8b9664e4e 100644 --- a/src/blenderbim/blenderbim/tool/spatial.py +++ b/src/blenderbim/blenderbim/tool/spatial.py @@ -270,6 +270,123 @@ class Spatial(blenderbim.core.tool.Spatial): #HERE STARTS SPATIAL TOOL + @classmethod + def is_bounding_class(cls, visible_element): + for ifc_class in ["IfcWall", "IfcColumn", "IfcMember", "IfcVirtualElement", "IfcPlate"]: + if visible_element.is_a(ifc_class): + return True + return False + + @classmethod + def get_space_polygon_from_context_visible_objects(cls, x, y): + boundary_lines = cls.get_boundary_lines_from_context_visible_objects() + unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines)) + closed_polygons = shapely.polygonize(unioned_boundaries.geoms) + space_polygon = None + for polygon in closed_polygons.geoms: + if shapely.contains_xy(polygon, x, y): + space_polygon = shapely.force_3d(polygon) + return space_polygon + + @classmethod + def get_boundary_lines_from_context_visible_objects(cls): + calculation_rl = bpy.context.scene.BIMModelProperties.rl3 + collection = bpy.context.view_layer.active_layer_collection.collection + collection_obj = collection.BIMCollectionProperties.obj + cut_point = collection_obj.matrix_world.translation.copy() + Vector((0, 0, calculation_rl)) + cut_normal = Vector((0, 0, 1)) + boundary_lines = [] + + for obj in bpy.context.visible_objects: + + + visible_element = tool.Ifc.get_entity(obj) + + if ( + not visible_element + or obj.type != "MESH" + or not cls.is_bounding_class(visible_element) + or not tool.Drawing.is_intersecting_plane(obj, cut_point, cut_normal) + ): + continue + + old_mesh = None + if visible_element.HasOpenings: + new_mesh = cls.get_gross_mesh_from_element(visible_element) + old_mesh = obj.data + obj.data = new_mesh + + local_cut_point = obj.matrix_world.inverted() @ cut_point + local_cut_normal = obj.matrix_world.inverted().to_quaternion() @ cut_normal + verts, edges = tool.Drawing.bisect_mesh_with_plane(obj, local_cut_point, local_cut_normal) + + if old_mesh: + obj.data = old_mesh + bpy.data.meshes.remove(new_mesh) + + for edge in edges or []: + boundary_lines.append( + shapely.LineString( + [Vector((round(x, 3) for x in verts[edge[0]])), Vector((round(x, 3) for x in verts[edge[1]]))] + ) + ) + + return boundary_lines + + @classmethod + def get_gross_mesh_from_element(cls, visible_element): + gross_settings = ifcopenshell.geom.settings() + gross_settings.set(gross_settings.DISABLE_OPENING_SUBTRACTIONS, True) + new_mesh = cls.create_mesh_from_shape(ifcopenshell.geom.create_shape(gross_settings, visible_element)) + return new_mesh + + @classmethod + def create_mesh_from_shape(cls, shape): + geometry = shape.geometry + mesh = bpy.data.meshes.new("tmp") + verts = geometry.verts + if geometry.faces: + num_vertices = len(verts) // 3 + total_faces = len(geometry.faces) + loop_start = range(0, total_faces, 3) + num_loops = total_faces // 3 + loop_total = [3] * num_loops + num_vertex_indices = len(geometry.faces) + + mesh.vertices.add(num_vertices) + mesh.vertices.foreach_set("co", verts) + mesh.loops.add(num_vertex_indices) + mesh.loops.foreach_set("vertex_index", geometry.faces) + mesh.polygons.add(num_loops) + mesh.polygons.foreach_set("loop_start", loop_start) + mesh.polygons.foreach_set("loop_total", loop_total) + mesh.polygons.foreach_set("use_smooth", [0] * total_faces) + mesh.update() + return mesh + + @classmethod + def get_x_y_z_h_mat_from_active_obj(cls, active_obj): + element = tool.Ifc.get_entity(active_obj) + mat = active_obj.matrix_world + local_bbox_center = 0.125 * sum((Vector(b) for b in active_obj.bound_box), Vector()) + global_bbox_center = mat @ local_bbox_center + x = global_bbox_center.x + y = global_bbox_center.y + z = (mat @ Vector(active_obj.bound_box[0])).z + + h = active_obj.dimensions.z + return x, y, z, h, mat + + @classmethod + def get_x_y_z_h_mat_from_cursor(cls): + collection = bpy.context.view_layer.active_layer_collection.collection + collection_obj = collection.BIMCollectionProperties.obj + x, y = bpy.context.scene.cursor.location.xy + z = collection_obj.matrix_world.translation.z + mat = Matrix() + h = 3 + return x, y, z, h, mat + @classmethod def get_union_shape_from_selected_objects(cls): selected_objects = bpy.context.selected_objects @@ -369,7 +486,11 @@ class Spatial(blenderbim.core.tool.Spatial): @classmethod def get_bmesh_from_polygon(cls, poly, h): - mat = bpy.context.active_object.matrix_world + if bpy.context.selected_objects and bpy.context.active_object: + mat = bpy.context.active_object.matrix_world + else: + mat = Matrix() + bm = bmesh.new() bm.verts.index_update() bm.edges.index_update() @@ -398,13 +519,32 @@ class Spatial(blenderbim.core.tool.Spatial): @classmethod def get_named_obj_from_bmesh(cls, name, bmesh): + mesh = cls.get_named_mesh_from_bmesh(name, bmesh) + obj = cls.get_named_obj_from_mesh(name, mesh) + return obj + + @classmethod + def get_named_obj_from_mesh(cls, name, mesh): + obj = bpy.data.objects.new(name, mesh) + if bpy.context.active_object: + mat = bpy.context.active_object.matrix_world + obj.matrix_world = mat + return obj + + @classmethod + def get_named_mesh_from_bmesh(cls, name, bmesh): mesh = bpy.data.meshes.new(name=name) bmesh.to_mesh(mesh) bmesh.free() - obj = bpy.data.objects.new(name, mesh) - mat = bpy.context.active_object.matrix_world - obj.matrix_world = mat - return obj + return mesh + + @classmethod + def edit_active_space_obj_from_mesh(cls, mesh): + active_obj = bpy.context.active_object + mesh.name = active_obj.data.name + mesh.BIMMeshProperties.ifc_definition_id = active_obj.data.BIMMeshProperties.ifc_definition_id + tool.Geometry.change_object_data(active_obj, mesh, is_global=True) + tool.Ifc.edit(active_obj) @classmethod def set_obj_origin_to_bboxcenter(cls, obj): @@ -422,6 +562,34 @@ class Spatial(blenderbim.core.tool.Spatial): vert.co = inverted @ aux_vector obj.location = newLoc + @classmethod + def set_obj_origin_to_cursor_position(cls, obj): + mat = obj.matrix_world + inverted = mat.inverted() + + + collection = bpy.context.view_layer.active_layer_collection.collection + collection_obj = collection.BIMCollectionProperties.obj + x, y = bpy.context.scene.cursor.location.xy + z = collection_obj.matrix_world.translation.z + + oldLoc = obj.location + newLoc = Vector((x,y,z)) + diff = newLoc - oldLoc + for vert in obj.data.vertices: + aux_vector = mat @ vert.co + aux_vector = aux_vector - diff + vert.co = inverted @ aux_vector + obj.location = newLoc + + @classmethod + def get_selected_objects(cls): + return bpy.context.selected_objects + + @classmethod + def get_active_obj(cls): + return bpy.context.active_object + @classmethod def get_active_obj_z(cls): x, y, z = bpy.context.active_object.matrix_world.translation.xyz @@ -432,6 +600,12 @@ class Spatial(blenderbim.core.tool.Spatial): height = bpy.context.active_object.dimensions.z return height + @classmethod + def get_relating_type_id(cls): + props = bpy.context.scene.BIMModelProperties + relating_type_id = props.relating_type_id + return relating_type_id + @classmethod def traslate_obj_to_z_location(cls, obj, z): if z != 0: From 6343f77c0ddef5a83ae06a36a9b1c8376b0e8f6e Mon Sep 17 00:00:00 2001 From: Massimo Fabbro Date: Thu, 5 Oct 2023 21:17:25 +0200 Subject: [PATCH 4/4] Minor spatial refactor --- src/blenderbim/blenderbim/bim/module/model/space.py | 2 +- src/blenderbim/blenderbim/core/spatial.py | 6 ++---- src/blenderbim/blenderbim/core/tool.py | 1 + src/blenderbim/blenderbim/tool/spatial.py | 5 +++++ 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/space.py b/src/blenderbim/blenderbim/bim/module/model/space.py index 7a63a31604..4dc348be99 100644 --- a/src/blenderbim/blenderbim/bim/module/model/space.py +++ b/src/blenderbim/blenderbim/bim/module/model/space.py @@ -62,7 +62,7 @@ class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator): bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR") return - core.generate_space(tool.Ifc, tool.Spatial, tool.Model) + core.generate_space(tool.Ifc, tool.Spatial, tool.Model, tool.Type) class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator): diff --git a/src/blenderbim/blenderbim/core/spatial.py b/src/blenderbim/blenderbim/core/spatial.py index 85a09cc7b4..14478ea651 100644 --- a/src/blenderbim/blenderbim/core/spatial.py +++ b/src/blenderbim/blenderbim/core/spatial.py @@ -16,7 +16,6 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . -import blenderbim.core.type def reference_structure(ifc, spatial, structure=None, element=None): if spatial.can_reference(structure, element): @@ -121,7 +120,7 @@ def select_decomposed_elements(spatial): spatial.select_products(spatial.get_decomposed_elements(container)) #HERE STARTS SPATIAL TOOL -def generate_space(ifc, spatial, model): +def generate_space(ifc, spatial, model, Type): active_obj = spatial.get_active_obj() selected_objects = spatial.get_selected_objects() element = None @@ -165,8 +164,7 @@ def generate_space(ifc, spatial, model): element = ifc.get_entity(obj) if relating_type: - blenderbim.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type) - + spatial.assign_relating_type_to_element(ifc, Type, element, relating_type) def generate_spaces_from_walls(ifc, spatial, collector): z = spatial.get_active_obj_z() diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index fc2964d3bc..5bd6c431cd 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -844,6 +844,7 @@ class Spatial: def get_body_representation(cls, obj): pass def assign_ifcspace_class_to_obj(cls, obj): pass def assign_type_to_obj(cls, obj): pass + def assign_relating_type_to_element(cls, ifc, Type, element, relating_type): pass def regen_obj_representation(cls, ifc, geometry, obj, body): pass def toggle_spaces_visibility_wired_and_textured(cls, spaces): pass diff --git a/src/blenderbim/blenderbim/tool/spatial.py b/src/blenderbim/blenderbim/tool/spatial.py index e8b9664e4e..d52d772f0f 100644 --- a/src/blenderbim/blenderbim/tool/spatial.py +++ b/src/blenderbim/blenderbim/tool/spatial.py @@ -20,6 +20,7 @@ import bpy import bmesh import shapely import ifcopenshell +import blenderbim.core.type import blenderbim.core.tool import blenderbim.core.root import blenderbim.core.spatial @@ -661,6 +662,10 @@ class Spatial(blenderbim.core.tool.Spatial): element = tool.Ifc.get_entity(obj) blenderbim.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type) + @classmethod + def assign_relating_type_to_element(cls, ifc, Type, element, relating_type): + blenderbim.core.type.assign_type(ifc, Type, element=element, type=relating_type) + @classmethod def assign_container_to_obj(cls, obj): active_obj = bpy.context.active_object