From 7720166c3101be0a5f8d28fb43e4abbf62a4fb73 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 13 Jul 2023 22:22:00 +1000 Subject: [PATCH 01/49] Fix #3393. Bug where duplicating a typed text didn't also copy its literal. --- src/blenderbim/blenderbim/tool/root.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/blenderbim/blenderbim/tool/root.py b/src/blenderbim/blenderbim/tool/root.py index fb7eaa4e51..eee7eb53c5 100644 --- a/src/blenderbim/blenderbim/tool/root.py +++ b/src/blenderbim/blenderbim/tool/root.py @@ -111,8 +111,12 @@ class Root(blenderbim.core.tool.Root): if obj.data and obj.data.BIMMeshProperties.ifc_definition_id: return tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) element = tool.Ifc.get_entity(obj) - if not obj.data and getattr(element, "ObjectType", None) == "TEXT": - return element.Representation.Representations[0] + if element.is_a("IfcTypeProduct"): + if element.RepresentationMaps: + return element.RepresentationMaps[0].MappedRepresentation + elif element.is_a("IfcProduct"): + if element.Representation: + return element.Representation.Representations[0] @classmethod def get_representation_context(cls, representation): From 6336241431c3f772b0ed5f55458deb770d89b482 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Thu, 13 Jul 2023 17:31:51 +0500 Subject: [PATCH 02/49] Cleaning up connections on delete_ifc Noticed an error if you connect wall and slab, then remove the slab and try to regenerate the wall - error occured because connection to that slab wasn't removed. The error was: Error: Python: Traceback (most recent call last): File "\3.6\scripts\addons\blenderbim\bim\module\model\workspace.py", line 381, in invoke return self.execute(context) File "\3.6\scripts\addons\blenderbim\tool\ifc.py", line 136, in execute IfcStore.execute_ifc_operator(self, context) File "\3.6\scripts\addons\blenderbim\bim\ifc.py", line 421, in execute_ifc_operator result = getattr(operator, "_execute")(context) File "\3.6\scripts\addons\blenderbim\bim\module\model\workspace.py", line 373, in _execute getattr(self, f"hotkey_{self.hotkey}")() File "\3.6\scripts\addons\blenderbim\bim\module\model\workspace.py", line 465, in hotkey_S_E bpy.ops.bim.join_wall(join_type="T") File "C:\software\Steam\steamapps\common\Blender\3.6\scripts\modules\bpy\ops.py", line 113, in __call__ ret = _op_call(self.idname_py(), None, kw) RuntimeError: Error: Python: Traceback (most recent call last): File "\3.6\scripts\addons\blenderbim\tool\ifc.py", line 136, in execute IfcStore.execute_ifc_operator(self, context) File "\3.6\scripts\addons\blenderbim\bim\ifc.py", line 421, in execute_ifc_operator result = getattr(operator, "_execute")(context) File "\3.6\scripts\addons\blenderbim\bim\module\model\wall.py", line 76, in _execute joiner.join_E(context.active_object, context.scene.cursor.location) File "\3.6\scripts\addons\blenderbim\bim\module\model\wall.py", line 1038, in join_E self.recreate_wall(element1, wall1, axis, body) File "\3.6\scripts\addons\blenderbim\bim\module\model\wall.py", line 1145, in recreate_wall height = self.clip(obj, tool.Ifc.get_object(rel.RelatingElement)) File "\3.6\scripts\addons\blenderbim\tool\ifc.py", line 89, in get_object return IfcStore.get_element(element.id()) AttributeError: 'NoneType' object has no attribute 'id' --- .../blenderbim/bim/module/model/wall.py | 27 +++++++++---------- .../blenderbim/bim/module/model/workspace.py | 3 ++- src/blenderbim/blenderbim/tool/geometry.py | 6 +++++ 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/wall.py b/src/blenderbim/blenderbim/bim/module/model/wall.py index aa94348596..7494146aee 100644 --- a/src/blenderbim/blenderbim/bim/module/model/wall.py +++ b/src/blenderbim/blenderbim/bim/module/model/wall.py @@ -60,11 +60,11 @@ class JoinWall(bpy.types.Operator, tool.Ifc.Operator): for obj in selected_objs: joiner.unjoin(obj) return {"FINISHED"} - + if not context.active_object or not context.active_object.BIMObjectProperties.ifc_definition_id: self.report({"ERROR"}, f"No active object selected") return {"CANCELLED"} - + for obj in selected_objs: tool.Geometry.clear_scale(obj) @@ -75,7 +75,7 @@ class JoinWall(bpy.types.Operator, tool.Ifc.Operator): if len(selected_objs) == 1: joiner.join_E(context.active_object, context.scene.cursor.location) return {"FINISHED"} - + if self.join_type in ("L", "V"): if len(selected_objs) != 2: self.report({"ERROR"}, f"It requires 2 selected objects to do join of type {self.join_type}") @@ -86,7 +86,7 @@ class JoinWall(bpy.types.Operator, tool.Ifc.Operator): elif self.join_type == "V": joiner.join_V(another_selected_object, context.active_object) return {"FINISHED"} - + if self.join_type == "T": elements = [tool.Ifc.get_entity(o) for o in context.selected_objects] layer2_elements = [] @@ -560,7 +560,7 @@ class DumbWallGenerator: rotation = math.atan2(normal[1], normal[0]) rotated_y_axis = Matrix.Rotation(-rotation, 4, "Z")[1].xyz - + # since wall thickness goes by local Y+ axis # we find best position for the next wall # by finding the face of another wall that will be very close to the some test point. @@ -1137,7 +1137,7 @@ class DumbWallJoiner: previous_matrix = obj.matrix_world.copy() previous_origin = previous_matrix.col[3].to_2d() - obj.matrix_world[0][3], obj.matrix_world[1][3] = self.body[0] + obj.matrix_world.col[3].xy = self.body[0] bpy.context.view_layer.update() for rel in element.ConnectedFrom: @@ -1432,6 +1432,7 @@ class DumbWallJoiner: return True def clip(self, wall1, slab2): + """returns height of the clipped wall, adds clipping plane to `clippings`""" element1 = tool.Ifc.get_entity(wall1) element2 = tool.Ifc.get_entity(slab2) @@ -1441,19 +1442,15 @@ class DumbWallJoiner: bases = [axis1["base"][0].to_3d(), axis1["base"][1].to_3d(), axis1["side"][0].to_3d(), axis1["side"][1].to_3d()] extrusion = self.get_extrusion_data(tool.Ifc.get().by_id(wall1.data.BIMMeshProperties.ifc_definition_id)) - d = wall1.matrix_world.to_quaternion() @ extrusion["direction"] + wall_dir = wall1.matrix_world.to_quaternion() @ extrusion["direction"] slab_pt = slab2.matrix_world @ Vector((0, 0, 0)) slab_dir = slab2.matrix_world.to_quaternion() @ Vector((0, 0, -1)) - tops = [mathutils.geometry.intersect_line_plane(b, b + d, slab_pt, slab_dir) for b in bases] - - i_bottom = None - i_top = None - for i, co in enumerate(tops): - if i_top is None or co[2] > i_top[2]: - i_top = co - i_bottom = bases[i] + tops = [mathutils.geometry.intersect_line_plane(b, b + wall_dir, slab_pt, slab_dir) for b in bases] + top_index = max(range(4), key=lambda i: tops[i].z) + i_top = tops[top_index] + i_bottom = bases[top_index] quaternion = slab2.matrix_world.to_quaternion() x_axis = quaternion @ Vector((1, 0, 0)) diff --git a/src/blenderbim/blenderbim/bim/module/model/workspace.py b/src/blenderbim/blenderbim/bim/module/model/workspace.py index 6fefce75b1..2b7bface16 100644 --- a/src/blenderbim/blenderbim/bim/module/model/workspace.py +++ b/src/blenderbim/blenderbim/bim/module/model/workspace.py @@ -457,7 +457,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator): if len(bpy.context.selected_objects) == 1: if self.active_material_usage == "LAYER3": - # Edit LAYER2 profile + # Edit LAYER3 profile if bpy.context.active_object and bpy.context.active_object.mode == "OBJECT": bpy.ops.bim.enable_editing_extrusion_profile() elif self.active_material_usage == "LAYER2": @@ -469,6 +469,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator): else: # Edit SWEPTSOLID profile (assuming single profile for now) bpy.ops.bim.enable_editing_extrusion_profile() + elif self.active_material_usage == "LAYER2" and selected_usages.get("PROFILE", []): # Extend PROFILEs to LAYER2 [o.select_set(False) for o in selected_usages.get("LAYER3", [])] diff --git a/src/blenderbim/blenderbim/tool/geometry.py b/src/blenderbim/blenderbim/tool/geometry.py index 5e13ab5dd2..1bd843764b 100644 --- a/src/blenderbim/blenderbim/tool/geometry.py +++ b/src/blenderbim/blenderbim/tool/geometry.py @@ -75,6 +75,12 @@ class Geometry(blenderbim.core.tool.Geometry): if element.is_a("IfcRelSpaceBoundary"): ifcopenshell.api.run("boundary.remove_boundary", tool.Ifc.get(), boundary=element) return bpy.data.objects.remove(obj) + + if element.is_a("IfcElement"): + connections = element.ConnectedTo + element.ConnectedFrom + for connection in connections: + tool.Ifc.get().remove(connection) + collection = obj.BIMObjectProperties.collection if collection: parent = ifcopenshell.util.element.get_aggregate(element) From 818226d1e2e899941656cc3ec79ddea11b6ccf09 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 13 Jul 2023 22:33:52 +1000 Subject: [PATCH 03/49] Fix #3414. The project wizard now doesn't load a fresh session by default. Note that File > Open still enforces a fresh session. --- .../blenderbim/bim/module/project/operator.py | 22 +++++++++---------- .../blenderbim/bim/module/project/ui.py | 3 +-- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/project/operator.py b/src/blenderbim/blenderbim/bim/module/project/operator.py index 3cd930fe0c..369d8d5c97 100644 --- a/src/blenderbim/blenderbim/bim/module/project/operator.py +++ b/src/blenderbim/blenderbim/bim/module/project/operator.py @@ -582,34 +582,34 @@ class LoadProject(bpy.types.Operator, IFCFileSelector): @persistent def load_handler(*args): bpy.app.handlers.load_post.remove(load_handler) - if tool.Blender.is_default_scene(): - for obj in bpy.data.objects: - bpy.data.objects.remove(obj) self.finish_loading_project(context) if self.should_start_fresh_session: - # WARNING: wm.read_homefile clears context - # which could lead to some operators to fail: + # WARNING: wm.read_homefile clears context which could lead to some + # operators to fail: # https://blender.stackexchange.com/a/282558/135166 - # So we continue using the load_post handler - # thats triggered when context is already restored + # So we continue using the load_post handler thats triggered when + # context is already restored bpy.app.handlers.load_post.append(load_handler) bpy.ops.wm.read_homefile() return {"FINISHED"} else: return self.finish_loading_project(context) - def finish_loading_project(self, context): if not self.is_existing_ifc_file(): return {"FINISHED"} + + if tool.Blender.is_default_scene(): + for obj in bpy.data.objects: + bpy.data.objects.remove(obj) + context.scene.BIMProperties.ifc_file = self.get_filepath() context.scene.BIMProjectProperties.is_loading = True context.scene.BIMProjectProperties.total_elements = len(tool.Ifc.get().by_type("IfcElement")) if not self.is_advanced: bpy.ops.bim.load_project_elements() - return {"FINISHED"} - + return {"FINISHED"} def invoke(self, context, event): context.window_manager.fileselect_add(self) @@ -987,7 +987,7 @@ class ExportIFC(bpy.types.Operator): if bpy.data.is_saved and bpy.data.is_dirty and bpy.data.filepath: bpy.ops.wm.save_mainfile(filepath=bpy.data.filepath) blenderbim.bim.handler.purge_module_data() - self.report({"INFO"}, f"IFC Project \"{os.path.basename(output_file)}\" Saved") + self.report({"INFO"}, f'IFC Project "{os.path.basename(output_file)}" Saved') if bpy.data.is_saved: bpy.ops.wm.save_mainfile("INVOKE_DEFAULT") diff --git a/src/blenderbim/blenderbim/bim/module/project/ui.py b/src/blenderbim/blenderbim/bim/module/project/ui.py index 882211e716..e67a125762 100644 --- a/src/blenderbim/blenderbim/bim/module/project/ui.py +++ b/src/blenderbim/blenderbim/bim/module/project/ui.py @@ -209,7 +209,6 @@ class BIM_PT_project(Panel): row = self.layout.row() row.label(text="File Not Saved", icon="ERROR") - def draw_create_project_ui(self, context): props = context.scene.BIMProperties pprops = context.scene.BIMProjectProperties @@ -226,7 +225,7 @@ class BIM_PT_project(Panel): row = self.layout.row(align=True) row.operator("bim.create_project") - row.operator("bim.load_project").should_start_fresh_session = True + row.operator("bim.load_project").should_start_fresh_session = False class BIM_PT_project_library(Panel): From 56e6c8e0e13964ba6b54eb34ccb11f480a8a359e Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Thu, 13 Jul 2023 14:52:00 +0200 Subject: [PATCH 04/49] Improve slightly the boolean interface in mesh properties editor --- .../blenderbim/bim/module/model/opening.py | 22 ++++++++++++------- .../blenderbim/bim/module/void/ui.py | 6 +++-- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/opening.py b/src/blenderbim/blenderbim/bim/module/model/opening.py index ef7ea63bc8..f55925fa49 100644 --- a/src/blenderbim/blenderbim/bim/module/model/opening.py +++ b/src/blenderbim/blenderbim/bim/module/model/opening.py @@ -498,10 +498,12 @@ class AddBoolean(Operator, tool.Ifc.Operator): bl_label = "Add Boolean" bl_options = {"REGISTER", "UNDO"} + @classmethod + def poll(cls, context): + return len(context.selected_objects) == 2 + def _execute(self, context): props = context.scene.BIMModelProperties - if len(context.selected_objects) != 2: - return {"FINISHED"} obj1, obj2 = context.selected_objects element1 = tool.Ifc.get_entity(obj1) element2 = tool.Ifc.get_entity(obj2) @@ -546,14 +548,18 @@ class ShowBooleans(Operator, tool.Ifc.Operator, AddObjectHelper): bl_label = "Show Booleans" bl_options = {"REGISTER", "UNDO"} + @classmethod + def poll(cls, context): + obj = context.active_object + return ( + obj.data + and hasattr(obj.data, "BIMMeshProperties") + and obj.data.BIMMeshProperties.ifc_definition_id + ) + + def _execute(self, context): obj = context.active_object - if ( - not obj.data - or not hasattr(obj.data, "BIMMeshProperties") - or not obj.data.BIMMeshProperties.ifc_definition_id - ): - return {"FINISHED"} unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) booleans = [] diff --git a/src/blenderbim/blenderbim/bim/module/void/ui.py b/src/blenderbim/blenderbim/bim/module/void/ui.py index fd1020fac9..d4a4bdea15 100644 --- a/src/blenderbim/blenderbim/bim/module/void/ui.py +++ b/src/blenderbim/blenderbim/bim/module/void/ui.py @@ -109,8 +109,10 @@ class BIM_PT_booleans(Panel): if context.active_object.data.BIMMeshProperties.ifc_definition_id: row = layout.row(align=True) row.label(text=f"{BooleansData.data['total_booleans']} Booleans Found") - row.operator("bim.add_boolean", text="", icon="ADD") - row.operator("bim.show_booleans", text="", icon="HIDE_OFF") + row.operator("bim.add_boolean", text="Apply Boolean", icon="ADD") + show_boolean_button = row.row(align=True) + show_boolean_button.operator("bim.show_booleans", text="", icon="HIDE_OFF") + show_boolean_button.enabled = BooleansData.data['total_booleans'] > 0 row.operator("bim.hide_booleans", text="", icon="HIDE_ON") elif context.active_object.data.BIMMeshProperties.ifc_boolean_id: row = layout.row() From 8411feedf406717c402961a80646d1df88eeff7d Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 14 Jul 2023 13:40:09 +1000 Subject: [PATCH 05/49] You can now open projects from the Ctrl-N shortcut. --- src/blenderbim/blenderbim/bim/module/project/ui.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/blenderbim/blenderbim/bim/module/project/ui.py b/src/blenderbim/blenderbim/bim/module/project/ui.py index e67a125762..6327df9f61 100644 --- a/src/blenderbim/blenderbim/bim/module/project/ui.py +++ b/src/blenderbim/blenderbim/bim/module/project/ui.py @@ -41,6 +41,12 @@ class BIM_MT_new_project(Menu): bl_label = "New Project" def draw(self, context): + self.layout.operator_context = "INVOKE_DEFAULT" + op = self.layout.operator("bim.load_project", text="Open IFC Project", icon="FILEBROWSER") + op.should_start_fresh_session = True + # Do we need to set it back to exec default? + # self.layout.operator_context = "EXEC_DEFAULT" + self.layout.separator() self.layout.label(text="New IFC Project", icon_value=blenderbim.bim.icons["IFC"].icon_id) self.layout.operator("bim.new_project", text="Metric (m) Project").preset = "metric_m" self.layout.operator("bim.new_project", text="Metric (mm) Project").preset = "metric_mm" From 2be8f4a1395f6210d3d2763b9d67a6f2c17cf9bc Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 14 Jul 2023 13:41:02 +1000 Subject: [PATCH 06/49] Move object data out of object tab and into new object dropdown. --- src/blenderbim/blenderbim/bim/prop.py | 2 +- src/blenderbim/blenderbim/bim/ui.py | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/prop.py b/src/blenderbim/blenderbim/bim/prop.py index 9ca579413b..c51e0611c9 100644 --- a/src/blenderbim/blenderbim/bim/prop.py +++ b/src/blenderbim/blenderbim/bim/prop.py @@ -359,7 +359,7 @@ def get_tab(self, context): class BIMAreaProperties(PropertyGroup): tab: EnumProperty(default=0, items=get_tab, name="Tab", update=update_tab) previous_tab: StringProperty(default="PROJECT", name="Previous Tab") - alt_tab: StringProperty(default="PROJECT", name="Alt Tab") + alt_tab: StringProperty(default="OBJECT", name="Alt Tab") class BIMProperties(PropertyGroup): diff --git a/src/blenderbim/blenderbim/bim/ui.py b/src/blenderbim/blenderbim/bim/ui.py index 3b6f3f2b45..281c86cdfa 100644 --- a/src/blenderbim/blenderbim/bim/ui.py +++ b/src/blenderbim/blenderbim/bim/ui.py @@ -442,12 +442,12 @@ class BIM_PT_object_metadata(Panel): bl_label = "IFC Object Metadata" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" - bl_context = "object" + bl_context = "scene" bl_order = 1 @classmethod def poll(cls, context): - return tool.Ifc.get() + return tool.Blender.is_tab(context, "OBJECT") and tool.Ifc.get() def draw(self, context): pass @@ -457,13 +457,13 @@ class BIM_PT_geometry_object(Panel): bl_label = "IFC Geometry" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" - bl_context = "object" + bl_context = "scene" bl_order = 1 bl_options = {"DEFAULT_CLOSED"} @classmethod def poll(cls, context): - return tool.Ifc.get() + return tool.Blender.is_tab(context, "OBJECT") and tool.Ifc.get() def draw(self, context): pass @@ -473,13 +473,13 @@ class BIM_PT_services_object(Panel): bl_label = "IFC Services" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" - bl_context = "object" + bl_context = "scene" bl_order = 1 bl_options = {"DEFAULT_CLOSED"} @classmethod def poll(cls, context): - return tool.Ifc.get() + return tool.Blender.is_tab(context, "OBJECT") and tool.Ifc.get() def draw(self, context): pass @@ -489,13 +489,13 @@ class BIM_PT_utilities_object(Panel): bl_label = "IFC Utilities" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" - bl_context = "object" + bl_context = "scene" bl_order = 1 bl_options = {"DEFAULT_CLOSED"} @classmethod def poll(cls, context): - return tool.Ifc.get() + return tool.Blender.is_tab(context, "OBJECT") and tool.Ifc.get() def draw(self, context): pass @@ -505,13 +505,13 @@ class BIM_PT_misc_object(Panel): bl_label = "IFC Misc." bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" - bl_context = "object" + bl_context = "scene" bl_order = 1 bl_options = {"DEFAULT_CLOSED"} @classmethod def poll(cls, context): - return tool.Ifc.get() + return tool.Blender.is_tab(context, "OBJECT") and tool.Ifc.get() def draw(self, context): pass From 924bf2a3ad0fa41746044e43e53d4926e0aaad8c Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 14 Jul 2023 13:54:24 +1000 Subject: [PATCH 07/49] Fix #3442. Update sheet list if drawing name changes. --- src/blenderbim/blenderbim/core/drawing.py | 3 +++ src/blenderbim/blenderbim/core/tool.py | 7 ++++--- src/blenderbim/blenderbim/tool/drawing.py | 4 ++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/blenderbim/blenderbim/core/drawing.py b/src/blenderbim/blenderbim/core/drawing.py index ca6f2b9b33..12bfcf0950 100644 --- a/src/blenderbim/blenderbim/core/drawing.py +++ b/src/blenderbim/blenderbim/core/drawing.py @@ -339,6 +339,9 @@ def update_drawing_name(ifc, drawing_tool, drawing=None, name=None): if drawing_tool.does_file_exist(uri): drawing_tool.update_embedded_svg_location(uri, old_location, new_location) + if drawing_tool.is_editing_sheets(): + drawing_tool.import_sheets() + def add_annotation(ifc, collector, drawing_tool, drawing=None, object_type=None): context = drawing_tool.get_annotation_context( diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 25fa289a5d..7520251c62 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -252,16 +252,16 @@ class Drawing: def delete_object(cls, obj): pass def disable_editing_assigned_product(cls, obj): pass def disable_editing_drawings(cls): pass - def disable_editing_schedules(cls): pass def disable_editing_references(cls): pass + def disable_editing_schedules(cls): pass def disable_editing_sheets(cls): pass def disable_editing_text(cls, obj): pass def does_file_exist(cls, uri): pass def enable_editing(cls, obj): pass def enable_editing_assigned_product(cls, obj): pass def enable_editing_drawings(cls): pass - def enable_editing_schedules(cls): pass def enable_editing_references(cls): pass + def enable_editing_schedules(cls): pass def enable_editing_sheets(cls): pass def enable_editing_text(cls, obj): pass def ensure_unique_drawing_name(cls, name): pass @@ -297,13 +297,14 @@ class Drawing: def get_text_literal(cls, obj): pass def get_unit_system(cls): pass def import_assigned_product(cls, obj): pass - def import_drawings(cls): pass def import_documents(cls, document_type): pass + def import_drawings(cls): pass def import_sheets(cls): pass def import_text_attributes(cls, obj): pass def is_active_drawing(cls, drawing): pass def is_camera_orthographic(cls): pass def is_drawing_active(cls): pass + def is_editing_sheets(cls): pass def move_file(cls, src, dest): pass def open_spreadsheet(cls, uri): pass def open_svg(cls, filepath): pass diff --git a/src/blenderbim/blenderbim/tool/drawing.py b/src/blenderbim/blenderbim/tool/drawing.py index 0f179915d6..5a4f98059a 100644 --- a/src/blenderbim/blenderbim/tool/drawing.py +++ b/src/blenderbim/blenderbim/tool/drawing.py @@ -466,6 +466,10 @@ class Drawing(blenderbim.core.tool.Drawing): return items return items[0] + @classmethod + def is_editing_sheets(cls): + return bpy.context.scene.DocProperties.is_editing_sheets + @classmethod def remove_literal_from_annotation(cls, obj, literal): element = tool.Ifc.get_entity(obj) From 8f609ff3835e5276c79d565d62c4a9afb0ffe124 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 14 Jul 2023 17:29:30 +1000 Subject: [PATCH 08/49] See #3421. Fix bug where some projection fills wouldn't polygonize correctly in shapely due to numerical tolerance. --- src/blenderbim/blenderbim/bim/module/drawing/operator.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index 6ef6f9bbc0..7bb376f6d7 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -23,6 +23,7 @@ import json import time import bmesh import shutil +import hashlib import shapely import subprocess import webbrowser @@ -365,7 +366,6 @@ class CreateDrawing(bpy.types.Operator): return svg_path def generate_linework(self, context): - global ifcopenshell if not ifcopenshell.util.element.get_pset(self.drawing, "EPset_Drawing", "HasLinework"): return svg_path = self.get_svg_path(cache_type="linework") @@ -395,10 +395,6 @@ class CreateDrawing(bpy.types.Operator): } cached_linework -= edited_guids - # This is a work in progress. See #1153 and #1564. - import hashlib - import ifcopenshell.draw - files = {context.scene.BIMProperties.ifc_file: tool.Ifc.get()} for ifc_path, ifc in files.items(): @@ -569,7 +565,7 @@ class CreateDrawing(bpy.types.Operator): for projection in projections: boundary_lines = [] for path in projection.findall("./{http://www.w3.org/2000/svg}path"): - start, end = [co[1:].split(",") for co in path.attrib["d"].split()] + start, end = [[round(float(o), 1) for o in co[1:].split(",")] for co in path.attrib["d"].split()] boundary_lines.append(shapely.LineString([start, end])) unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines)) closed_polygons = shapely.polygonize(unioned_boundaries.geoms) From 69fa12423dd2a9a2a40634ea90400ca7bd3cb2dd Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 14 Jul 2023 17:32:45 +1000 Subject: [PATCH 09/49] See #3421. Fix drawing bug where IfcOpenShell might return unclosed paths even though the shape is closed and therefore break cut polygon fills. --- .../blenderbim/bim/module/drawing/operator.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index 7bb376f6d7..8c5bdfd3ee 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -892,6 +892,42 @@ class CreateDrawing(bpy.types.Operator): classes.append("cut") el.set("class", " ".join(classes)) + # An element group will contain a bunch of paths representing the + # cut of that element. However IfcOpenShell may not correctly + # create closed paths. We post-process all paths with shapely to + # ensure things that should be closed (i.e. + # shapely.polygonize_full) are, and things which aren't are left + # alone (e.g. dangles, cuts, invalids). See #3421. + line_strings = [] + old_paths = [] + for path in el.findall("{http://www.w3.org/2000/svg}path"): + for subpath in path.attrib["d"].split("M")[1:]: + subpath = "M" + subpath.strip() + coords = [[round(float(o), 1) for o in co[1:].split(",")] for co in subpath.split()] + line_strings.append(shapely.LineString(coords)) + old_paths.append(path) + unioned_line_strings = shapely.union_all(shapely.GeometryCollection(line_strings)) + if hasattr(unioned_line_strings, "geoms"): + results = shapely.polygonize_full(unioned_line_strings.geoms) + else: + results = [] + + # If we succeeded in generating new path geometry, remove all the + # old paths and add new ones. + if results: + for path in old_paths: + path.getparent().remove(path) + for result in results: + for geom in result.geoms: + path = etree.SubElement(el, "path") + if isinstance(geom, shapely.Polygon): + d = "M" + " L".join([",".join([str(o) for o in co]) for co in geom.exterior.coords[0:-1]]) + " Z" + for interior in geom.interiors: + d += " M" + " L".join([",".join([str(o) for o in co]) for co in interior.coords[0:-1]]) + " Z" + elif isinstance(geom, shapely.LineString): + d = "M" + " L".join([",".join([str(o) for o in co]) for co in geom.coords]) + " Z" + path.attrib["d"] = d + # Architectural convention only merges these objects. E.g. pipe segments and fittings shouldn't merge. if not element.is_a("IfcWall") and not element.is_a("IfcSlab"): continue From 2fa7cbaabf992b9db4d84dac0f7b6dd6d02187a7 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 14 Jul 2023 09:47:15 +0500 Subject: [PATCH 10/49] Fixing confusing UI #3357 It was showing "Edit Profile" when you have slab and wall selected when it will actually extend the wall to the slab. --- .../blenderbim/bim/module/model/data.py | 18 ++++++++++++++++++ .../blenderbim/bim/module/model/workspace.py | 6 +++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/data.py b/src/blenderbim/blenderbim/bim/module/model/data.py index 66a4ca1cf5..58e54faffa 100644 --- a/src/blenderbim/blenderbim/bim/module/model/data.py +++ b/src/blenderbim/blenderbim/bim/module/model/data.py @@ -67,6 +67,7 @@ class AuthoringData: cls.data["active_material_usage"] = cls.active_material_usage() cls.data["active_representation_type"] = cls.active_representation_type() cls.data["boundary_class"] = cls.boundary_class() + cls.data["selected_material_usages"] = cls.selected_material_usages() @classmethod def boundary_class(cls): @@ -238,6 +239,23 @@ class AuthoringData: return [(str(e.id()), e.Name or "Unnamed", e.Description or "") for e in results] return [] + @classmethod + def selected_material_usages(cls): + selected_usages = {} + for obj in bpy.context.selected_objects: + element = tool.Ifc.get_entity(obj) + if not element: + continue + usage = tool.Model.get_usage_type(element) + if not usage: + representation = tool.Geometry.get_active_representation(obj) + if representation and representation.RepresentationType == "SweptSolid": + usage = "SWEPTSOLID" + else: + continue + selected_usages.setdefault(usage, []).append(obj) + return selected_usages + class ArrayData: data = {} diff --git a/src/blenderbim/blenderbim/bim/module/model/workspace.py b/src/blenderbim/blenderbim/bim/module/model/workspace.py index 2b7bface16..26f5631947 100644 --- a/src/blenderbim/blenderbim/bim/module/model/workspace.py +++ b/src/blenderbim/blenderbim/bim/module/model/workspace.py @@ -176,7 +176,11 @@ class BimToolUI: row.operator("bim.join_wall", icon="X", text="").join_type = "" elif AuthoringData.data["active_material_usage"] == "LAYER3": - add_layout_hotkey_operator(cls.layout, "Edit Profile", "S_E", "") + + if len(context.selected_objects) == 1: + add_layout_hotkey_operator(cls.layout, "Edit Profile", "S_E", "") + elif "LAYER2" in AuthoringData.data["selected_material_usages"]: + add_layout_hotkey_operator(cls.layout, "Extend Wall To Slab", "S_E", "") row = cls.layout.row(align=True) row.prop(data=cls.props, property="x_angle", text="X Angle") From 6810bbff2f378812d81b64fb3b5d92bff5330575 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 14 Jul 2023 10:20:30 +0500 Subject: [PATCH 11/49] remove_product to remove IfcRelConnectsElements --- src/blenderbim/blenderbim/tool/geometry.py | 5 --- .../ifcopenshell/api/root/remove_product.py | 6 +++- .../test/api/root/test_remove_product.py | 34 +++++++++++++++++++ 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/blenderbim/blenderbim/tool/geometry.py b/src/blenderbim/blenderbim/tool/geometry.py index 1bd843764b..94f135082a 100644 --- a/src/blenderbim/blenderbim/tool/geometry.py +++ b/src/blenderbim/blenderbim/tool/geometry.py @@ -76,11 +76,6 @@ class Geometry(blenderbim.core.tool.Geometry): ifcopenshell.api.run("boundary.remove_boundary", tool.Ifc.get(), boundary=element) return bpy.data.objects.remove(obj) - if element.is_a("IfcElement"): - connections = element.ConnectedTo + element.ConnectedFrom - for connection in connections: - tool.Ifc.get().remove(connection) - collection = obj.BIMObjectProperties.collection if collection: parent = ifcopenshell.util.element.get_aggregate(element) diff --git a/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py b/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py index 37206881be..40deb2f0e8 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py +++ b/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py @@ -125,7 +125,11 @@ class Usecase: elif inverse.is_a("IfcRelContainedInSpatialStructure"): if inverse.RelatingStructure == self.settings["product"] or len(inverse.RelatedElements) == 1: self.file.remove(inverse) - elif inverse.is_a("IfcRelConnectsPathElements"): + elif inverse.is_a() in ("IfcRelConnectsElements", "IfcRelConnectsPathElements"): + if inverse.is_a("IfcRelConnectsWithRealizingElements"): + connected_elements = (inverse.RelatingElement, inverse.RelatedElement) + if any(el not in connected_elements for el in inverse.RealizingElements): + continue self.file.remove(inverse) elif inverse.is_a("IfcRelAssignsToGroup"): if len(inverse.RelatedObjects) == 1: diff --git a/src/ifcopenshell-python/test/api/root/test_remove_product.py b/src/ifcopenshell-python/test/api/root/test_remove_product.py index d383859f88..50a50ec805 100644 --- a/src/ifcopenshell-python/test/api/root/test_remove_product.py +++ b/src/ifcopenshell-python/test/api/root/test_remove_product.py @@ -196,6 +196,40 @@ class TestRemoveProduct(test.bootstrap.IFC4): assert len(self.file.by_type("IfcColumn")) == 1 assert len(self.file.by_type("IfcBeam")) == 0 + def test_removing_connection_relationships_of_an_element(self): + element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab") + ifcopenshell.api.run( + "geometry.connect_element", + self.file, + related_element=element1, + relating_element=element2, + ) + total_entities = len(list(self.file)) + ifcopenshell.api.run("root.remove_product", self.file, product=element1) + assert len(list(self.file)) == total_entities - 2 + assert len(self.file.by_type("IfcRelConnectsElements")) == 0 + assert len(self.file.by_type("IfcSlab")) == 1 + assert len(self.file.by_type("IfcWall")) == 0 + + def test_removing_connection_relationships_of_an_element_with_additional_realizing_element(self): + element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab") + element3 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab") + self.file.createIfcRelConnectsWithRealizingElements( + ifcopenshell.guid.new(), + OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", self.file), + RelatingElement=element1, + RelatedElement=element2, + RealizingElements=(element1, element2, element3), + ) + total_entities = len(list(self.file)) + ifcopenshell.api.run("root.remove_product", self.file, product=element1) + assert len(list(self.file)) == total_entities - 1 + assert len(self.file.by_type("IfcRelConnectsElements")) == 1 + assert len(self.file.by_type("IfcSlab")) == 2 + assert len(self.file.by_type("IfcWall")) == 0 + def test_removing_all_property_relationships_of_an_element(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Foo_Bar") From be5950bcb780899aad3f204d968ebc2ec2d7a70f Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 14 Jul 2023 10:51:01 +0500 Subject: [PATCH 12/49] clean up after running test_wall_opening.py --- src/ifcopenshell-python/test/test_wall_opening.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/ifcopenshell-python/test/test_wall_opening.py b/src/ifcopenshell-python/test/test_wall_opening.py index f331297e73..6c9ed4242d 100644 --- a/src/ifcopenshell-python/test/test_wall_opening.py +++ b/src/ifcopenshell-python/test/test_wall_opening.py @@ -64,6 +64,7 @@ X = 1.0, 0.0, 0.0 Y = 0.0, 1.0, 0.0 Z = 0.0, 0.0, 1.0 + # Creates an IfcAxis2Placement3D from Location, Axis and RefDirection specified as Python tuples def create_ifcaxis2placement(f, point=O, dir1=Z, dir2=X): point = f.createIfcCartesianPoint(point) @@ -100,7 +101,6 @@ def create_ifcextrudedareasolid(f, point_list, ifcaxis2placement, extrude_dir, e def create_case(fn, openings): - f = ifcopenshell.template.create() owner_history = f.by_type("IfcOwnerHistory")[0] @@ -127,7 +127,6 @@ def create_case(fn, openings): ) for opening in openings: - opening_placement = create_ifclocalplacement( f, (opening.x, 0.0, opening.z), (0.0, 1.0, 0.0), (1.0, 0.0, 0.0), wall_placement ) @@ -147,7 +146,6 @@ def create_case(fn, openings): class TestWallOpenings: @pytest.mark.skipif(shutil.which("IfcConvert") is None, reason="Requires IfcConvert in path") def test_all(self): - cases = [ ( "wall-openings-non-intersecting-rect-circle.ifc", @@ -205,7 +203,6 @@ class TestWallOpenings: result.append([fn]) for i in range(2 if PERF else 1): - args = [ shutil.which("IfcConvert") or "IfcConvert", "-qyvvv", @@ -236,12 +233,16 @@ class TestWallOpenings: # breakpoint() + temp_files = [fn, f"{fn}.obj", f"{fn}.log.json", f"{fn}.mtl"] + for temp_fn in temp_files: + os.unlink(temp_fn) + try: import tabulate except: return - print(tabulate.tabulate(result, headers=["file", "", "--no-2d-boolean"], tablefmt="github")) + print("\n" + tabulate.tabulate(result, headers=["file", "", "--no-2d-boolean"], tablefmt="github")) if __name__ == "__main__": From b6ddc0d55eb99b01617467f87d77bac3cf1427f3 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 14 Jul 2023 14:46:36 +0500 Subject: [PATCH 13/49] avoid IfcSpatialElement errors in ifc2x3 IfcSpatialElement was added only in ifc4 --- src/blenderbim/blenderbim/bim/module/spatial/data.py | 6 +++++- src/blenderbim/blenderbim/tool/geometry.py | 2 +- src/blenderbim/blenderbim/tool/spatial.py | 6 +++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/spatial/data.py b/src/blenderbim/blenderbim/bim/module/spatial/data.py index 9623045536..930a19b84a 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/data.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/data.py @@ -43,7 +43,11 @@ class SpatialData: @classmethod def containers(cls): results = {} - for container in tool.Ifc.get().by_type("IfcSpatialElement"): + if tool.Ifc.get_schema() == "IFC2X3": + spatial_elements = tool.Ifc.get().by_type("IfcSpatialStructureElement") + else: + spatial_elements = tool.Ifc.get().by_type("IfcSpatialElement") + for container in spatial_elements: results[container.id()] = { "type": container.is_a(), "id": container.id(), diff --git a/src/blenderbim/blenderbim/tool/geometry.py b/src/blenderbim/blenderbim/tool/geometry.py index 94f135082a..3512539d35 100644 --- a/src/blenderbim/blenderbim/tool/geometry.py +++ b/src/blenderbim/blenderbim/tool/geometry.py @@ -99,7 +99,7 @@ class Geometry(blenderbim.core.tool.Geometry): if element.VoidsElements: bpy.ops.bim.remove_opening(opening_id=element.id()) else: - is_spatial = element.is_a("IfcSpatialElement") + is_spatial = element.is_a() in ("IfcSpatialElement", "IfcSpatialStructureElement") if getattr(element, "HasOpenings", None): for rel in element.HasOpenings: bpy.ops.bim.remove_opening(opening_id=rel.RelatedOpeningElement.id()) diff --git a/src/blenderbim/blenderbim/tool/spatial.py b/src/blenderbim/blenderbim/tool/spatial.py index 95369338fd..75f5f20110 100644 --- a/src/blenderbim/blenderbim/tool/spatial.py +++ b/src/blenderbim/blenderbim/tool/spatial.py @@ -52,7 +52,7 @@ class Spatial(blenderbim.core.tool.Spatial): if not structure.is_a("IfcSpatialStructureElement"): return False else: - if not structure.is_a("IfcSpatialElement"): + if not structure.is_a() in ("IfcSpatialElement", "IfcSpatialStructureElement"): return False if not hasattr(element, "ReferencedInStructures"): return False @@ -204,7 +204,7 @@ class Spatial(blenderbim.core.tool.Spatial): parent = tool.Ifc.get().by_type("IfcProject")[0] for object in ifcopenshell.util.element.get_parts(parent) or []: - if object.is_a("IfcSpatialElement"): + if object.is_a() in ("IfcSpatialElement", "IfcSpatialStructureElement"): cls.create_new_storey_li(object, 0) cls.props.is_container_update_enabled = True @@ -223,7 +223,7 @@ class Spatial(blenderbim.core.tool.Spatial): new.has_children = True if new.is_expanded: for related_object in ifcopenshell.util.element.get_parts(element) or []: - if related_object.is_a("IfcSpatialElement"): + if related_object.is_a() in ("IfcSpatialElement", "IfcSpatialStructureElement"): cls.create_new_storey_li(related_object, level_index + 1) @classmethod From ed95516b9d516885522a1e77ab91c6e86d57414a Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 14 Jul 2023 15:04:15 +0500 Subject: [PATCH 14/49] Clean up type objects on ifc import --- src/blenderbim/blenderbim/bim/import_ifc.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index 8018f7db72..23b43a567d 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -1342,12 +1342,21 @@ class IfcImporter: last_obj = obj if not last_obj: return + + # temporarily unhide types collection to make sure all objects will be cleaned + project_collection = bpy.context.view_layer.layer_collection.children[self.project["blender"].name] + types_collection = project_collection.children[self.type_collection.name] + types_collection.hide_viewport = False bpy.context.view_layer.objects.active = last_obj + context_override = {} bpy.ops.object.editmode_toggle(context_override) bpy.ops.mesh.tris_convert_to_quads(context_override) bpy.ops.mesh.normals_make_consistent(context_override) bpy.ops.object.editmode_toggle(context_override) + + types_collection.hide_viewport = True + bpy.context.view_layer.objects.active = last_obj IfcStore.edited_objs.clear() def load_file(self): From 846dbc341d9ee3ad6591b60e5020d19cca207f90 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 14 Jul 2023 15:47:36 +0500 Subject: [PATCH 15/49] root.copy_class to support copying ports in ifc2x3 --- .../ifcopenshell/api/root/copy_class.py | 48 ++++++++++++------- .../test/api/root/test_copy_class.py | 12 +++++ 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py b/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py index a2618078e1..061b695d24 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py +++ b/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py @@ -86,24 +86,40 @@ class Usecase: inverse.RelatedObjects = [to_element] pset = ifcopenshell.util.element.copy_deep(self.file, inverse.RelatingPropertyDefinition) inverse.RelatingPropertyDefinition = pset - elif inverse.is_a("IfcRelNests") and inverse.RelatingObject == from_element: - ports = [e for e in inverse.RelatedObjects if e.is_a("IfcDistributionPort")] - if ports: - new_ports = [ifcopenshell.api.run("root.copy_class", self.file, product=p) for p in ports] - inverse = ifcopenshell.util.element.copy(self.file, inverse) + elif ( + inverse.is_a("IfcRelNests") + and inverse.RelatingObject == from_element + or inverse.is_a("IfcRelConnectsPortToElement") + and inverse.RelatedElement == from_element + ): + # IfcRelConnectsPortToElement was used in IFC2X3 + if inverse.is_a("IfcRelNests"): + ports = [e for e in inverse.RelatedObjects if e.is_a("IfcDistributionPort")] + else: # IfcRelConnectsPortToElement + ports = [inverse.RelatingPort] + if not ports: + continue + new_ports = [ifcopenshell.api.run("root.copy_class", self.file, product=p) for p in ports] + inverse = ifcopenshell.util.element.copy(self.file, inverse) + + if inverse.is_a("IfcRelNests"): inverse.RelatingObject = to_element inverse.RelatedObjects = new_ports - for port in new_ports: - ifcopenshell.api.run("system.unassign_port", self.file, element=from_element, port=port) - matrix = ifcopenshell.util.placement.get_local_placement(port.ObjectPlacement) - ifcopenshell.api.run( - "geometry.edit_object_placement", - self.file, - product=port, - matrix=matrix, - is_si=False, - should_transform_children=False, - ) + else: + inverse.RelatedElement = to_element + inverse.RelatingPort = new_ports[0] + + for port in new_ports: + ifcopenshell.api.run("system.unassign_port", self.file, element=from_element, port=port) + matrix = ifcopenshell.util.placement.get_local_placement(port.ObjectPlacement) + ifcopenshell.api.run( + "geometry.edit_object_placement", + self.file, + product=port, + matrix=matrix, + is_si=False, + should_transform_children=False, + ) elif inverse.is_a("IfcRelAggregates") and inverse.RelatingObject == from_element: continue elif inverse.is_a("IfcRelContainedInSpatialStructure") and inverse.RelatingStructure == from_element: diff --git a/src/ifcopenshell-python/test/api/root/test_copy_class.py b/src/ifcopenshell-python/test/api/root/test_copy_class.py index 9512fe1014..ab65d8ccfa 100644 --- a/src/ifcopenshell-python/test/api/root/test_copy_class.py +++ b/src/ifcopenshell-python/test/api/root/test_copy_class.py @@ -232,3 +232,15 @@ class TestCopyClass(test.bootstrap.IFC4): new = ifcopenshell.api.run("root.copy_class", self.file, product=element) assert len(self.file.by_type("IfcRelAssignsToGroup")) == 1 assert new.HasAssignments[0].RelatingGroup == group + + +class TestCopyClassIFC2X3(test.bootstrap.IFC2X3): + def test_copying_distribution_ports_bug(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcFlowTerminal") + port = ifcopenshell.api.run("system.add_port", self.file) + ifcopenshell.api.run("system.assign_port", self.file, element=element, port=port) + new = ifcopenshell.api.run("root.copy_class", self.file, product=element) + new_ports = ifcopenshell.util.system.get_ports(new) + assert port not in new_ports + assert new_ports[0].is_a("IfcDistributionPort") + assert ifcopenshell.util.system.get_ports(element) == [port] From a72231cb910cb6dcb193e5da634f17ae3b55e0e6 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 14 Jul 2023 16:16:52 +0500 Subject: [PATCH 16/49] StairData refactor, solves #3416 --- .../blenderbim/bim/module/model/data.py | 17 ++++++++++++++++- .../blenderbim/bim/module/model/prop.py | 12 +++++++----- .../blenderbim/bim/module/model/ui.py | 6 ++---- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/data.py b/src/blenderbim/blenderbim/bim/module/model/data.py index 58e54faffa..e9a18c02af 100644 --- a/src/blenderbim/blenderbim/bim/module/model/data.py +++ b/src/blenderbim/blenderbim/bim/module/model/data.py @@ -290,12 +290,27 @@ class StairData: @classmethod def load(cls): cls.is_loaded = True - cls.data = {"pset_data": cls.pset_data()} + cls.data = {} + cls.data["pset_data"] = cls.pset_data() + if not cls.data["pset_data"]: + return + cls.data["general_params"] = cls.general_params() @classmethod def pset_data(cls): return tool.Model.get_modeling_bbim_pset_data(bpy.context.active_object, "BBIM_Stair") + @classmethod + def general_params(cls): + props = bpy.context.active_object.BIMStairProperties + data = cls.data["pset_data"]["data_dict"] + general_params = {} + general_props = props.get_props_kwargs(stair_type=data["stair_type"]) + for prop_name in general_props: + prop_readable_name, prop_value = get_prop_from_data(props, data, prop_name) + general_params[prop_readable_name] = prop_value + return general_params + class SverchokData: data = {} diff --git a/src/blenderbim/blenderbim/bim/module/model/prop.py b/src/blenderbim/blenderbim/bim/module/model/prop.py index 10b8473e9d..1e7fedb68a 100644 --- a/src/blenderbim/blenderbim/bim/module/model/prop.py +++ b/src/blenderbim/blenderbim/bim/module/model/prop.py @@ -201,16 +201,18 @@ class BIMStairProperties(PropertyGroup): has_top_nib: bpy.props.BoolProperty(name="Has top nib", default=True) stair_type: bpy.props.EnumProperty(name="Stair type", items=stair_types, default="CONCRETE") - def get_props_kwargs(self, convert_to_project_units=False): + def get_props_kwargs(self, convert_to_project_units=False, stair_type=None): + if not stair_type: + stair_type = self.stair_type stair_kwargs = { - "stair_type": self.stair_type, + "stair_type": stair_type, "width": self.width, "height": self.height, "number_of_treads": self.number_of_treads, "tread_run": self.tread_run, } - if self.stair_type == "CONCRETE": + if stair_type == "CONCRETE": concrete_props = { "base_slab_depth": self.base_slab_depth, "top_slab_depth": self.top_slab_depth, @@ -219,13 +221,13 @@ class BIMStairProperties(PropertyGroup): } stair_kwargs.update(concrete_props) - elif self.stair_type == "WOOD/STEEL": + elif stair_type == "WOOD/STEEL": wood_steel_props = { "tread_depth": self.tread_depth, } stair_kwargs.update(wood_steel_props) - elif self.stair_type == "GENERIC": + elif stair_type == "GENERIC": pass if not convert_to_project_units: diff --git a/src/blenderbim/blenderbim/bim/module/model/ui.py b/src/blenderbim/blenderbim/bim/module/model/ui.py index 12aa343c9b..92b456cc4e 100644 --- a/src/blenderbim/blenderbim/bim/module/model/ui.py +++ b/src/blenderbim/blenderbim/bim/module/model/ui.py @@ -269,11 +269,9 @@ class BIM_PT_stair(bpy.types.Panel): row.operator("bim.enable_editing_stair", icon="GREASEPENCIL", text="") row.operator("bim.remove_stair", icon="X", text="") row = self.layout.row(align=True) - for prop in props.get_props_kwargs(): - prop_value = stair_data[prop] - prop_value = round(prop_value, 5) if type(prop_value) is float else prop_value + for prop_name, prop_value in StairData.data["general_params"].items(): row = self.layout.row(align=True) - row.label(text=f"{props.bl_rna.properties[prop].name}") + row.label(text=prop_name) row.label(text=str(prop_value)) # calculated properties From 9c383e0c1a4ea1a8081b2415ace5fc2e618cfefa Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 14 Jul 2023 17:42:45 +0500 Subject: [PATCH 17/49] Fixed plan level annotation rotation #3440 --- .../blenderbim/bim/module/drawing/svgwriter.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py index bb65b52f67..d88375283b 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py @@ -880,8 +880,13 @@ class SvgWriter: d = "M{}".format(d[1:]) path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes))) text_position = projected_points_svg[0] - Vector((0, base_offset_y)) - vector = projected_points_svg[1] - projected_points_svg[0] - angle = math.degrees(vector.angle_signed(Vector((1, 0)))) + text_dir = projected_points_svg[1] - projected_points_svg[0] + if text_dir.x < 0: + box_alignment = "bottom-right" + text_dir *= -1 + else: + box_alignment = "bottom-left" + angle = math.degrees(text_dir.angle_signed(Vector((1, 0)))) # TODO: allow metric to be configurable def get_text(): @@ -895,7 +900,6 @@ class SvgWriter: text = "{}{}".format("" if z < 0 else "+", rl) return text - box_alignment = "bottom-left" if projected_points[0].x <= projected_points[-1].x else "bottom-right" self.draw_dimension_text( get_text, description, From af4b5e1fe62c005e174885ee0c78eb324485d125 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 14 Jul 2023 18:02:51 +0500 Subject: [PATCH 18/49] oops, forgot to clean up the test name --- src/ifcopenshell-python/test/api/root/test_copy_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ifcopenshell-python/test/api/root/test_copy_class.py b/src/ifcopenshell-python/test/api/root/test_copy_class.py index ab65d8ccfa..782ef05472 100644 --- a/src/ifcopenshell-python/test/api/root/test_copy_class.py +++ b/src/ifcopenshell-python/test/api/root/test_copy_class.py @@ -235,7 +235,7 @@ class TestCopyClass(test.bootstrap.IFC4): class TestCopyClassIFC2X3(test.bootstrap.IFC2X3): - def test_copying_distribution_ports_bug(self): + def test_copying_distribution_ports(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcFlowTerminal") port = ifcopenshell.api.run("system.add_port", self.file) ifcopenshell.api.run("system.assign_port", self.file, element=element, port=port) From 572a8c812ef59aa98c300c3220c3a88a59b8cb9e Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 14 Jul 2023 18:19:14 +0500 Subject: [PATCH 19/49] root.remove_product fix --- .../ifcopenshell/api/root/remove_product.py | 7 +-- .../test/api/root/test_remove_product.py | 54 +++++++++++++++---- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py b/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py index 40deb2f0e8..eb61cea031 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py +++ b/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py @@ -125,10 +125,11 @@ class Usecase: elif inverse.is_a("IfcRelContainedInSpatialStructure"): if inverse.RelatingStructure == self.settings["product"] or len(inverse.RelatedElements) == 1: self.file.remove(inverse) - elif inverse.is_a() in ("IfcRelConnectsElements", "IfcRelConnectsPathElements"): + elif inverse.is_a("IfcRelConnectsElements"): if inverse.is_a("IfcRelConnectsWithRealizingElements"): - connected_elements = (inverse.RelatingElement, inverse.RelatedElement) - if any(el not in connected_elements for el in inverse.RealizingElements): + if self.settings["product"] not in (inverse.RelatingElement, inverse.RelatedElement) and any( + el for el in inverse.RealizingElements if el != self.settings["product"] + ): continue self.file.remove(inverse) elif inverse.is_a("IfcRelAssignsToGroup"): diff --git a/src/ifcopenshell-python/test/api/root/test_remove_product.py b/src/ifcopenshell-python/test/api/root/test_remove_product.py index 50a50ec805..7431551d77 100644 --- a/src/ifcopenshell-python/test/api/root/test_remove_product.py +++ b/src/ifcopenshell-python/test/api/root/test_remove_product.py @@ -213,23 +213,59 @@ class TestRemoveProduct(test.bootstrap.IFC4): assert len(self.file.by_type("IfcWall")) == 0 def test_removing_connection_relationships_of_an_element_with_additional_realizing_element(self): - element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab") - element3 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab") + wall = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + slab1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab") + slab2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab") self.file.createIfcRelConnectsWithRealizingElements( ifcopenshell.guid.new(), OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", self.file), - RelatingElement=element1, - RelatedElement=element2, - RealizingElements=(element1, element2, element3), + RelatingElement=wall, + RelatedElement=slab1, + RealizingElements=(wall, slab1, slab2), ) total_entities = len(list(self.file)) - ifcopenshell.api.run("root.remove_product", self.file, product=element1) - assert len(list(self.file)) == total_entities - 1 - assert len(self.file.by_type("IfcRelConnectsElements")) == 1 + ifcopenshell.api.run("root.remove_product", self.file, product=wall) + assert len(list(self.file)) == total_entities - 2 + assert len(self.file.by_type("IfcRelConnectsElements")) == 0 assert len(self.file.by_type("IfcSlab")) == 2 assert len(self.file.by_type("IfcWall")) == 0 + def test_removing_connection_relationships_of_an_element_element_is_realizing_element(self): + wall = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + slab1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab") + slab2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab") + self.file.createIfcRelConnectsWithRealizingElements( + ifcopenshell.guid.new(), + OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", self.file), + RelatingElement=wall, + RelatedElement=slab1, + RealizingElements=(wall, slab1, slab2), + ) + total_entities = len(list(self.file)) + ifcopenshell.api.run("root.remove_product", self.file, product=slab2) + assert len(list(self.file)) == total_entities - 1 + assert len(self.file.by_type("IfcRelConnectsElements")) == 1 + assert len(self.file.by_type("IfcSlab")) == 1 + assert len(self.file.by_type("IfcWall")) == 1 + + def test_removing_connection_relationships_of_an_element_element_is_only_realizing_element(self): + wall = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + slab1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab") + slab2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab") + self.file.createIfcRelConnectsWithRealizingElements( + ifcopenshell.guid.new(), + OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", self.file), + RelatingElement=wall, + RelatedElement=slab1, + RealizingElements=(slab2,), + ) + total_entities = len(list(self.file)) + ifcopenshell.api.run("root.remove_product", self.file, product=slab2) + assert len(list(self.file)) == total_entities - 2 + assert len(self.file.by_type("IfcRelConnectsElements")) == 0 + assert len(self.file.by_type("IfcSlab")) == 1 + assert len(self.file.by_type("IfcWall")) == 1 + def test_removing_all_property_relationships_of_an_element(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Foo_Bar") From 2034fa203b36b9d118a5a21fdd709369e00ab0d3 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 14 Jul 2023 23:58:25 +1000 Subject: [PATCH 20/49] Fix #3275. You can now rename types from the type manager. --- .../blenderbim/bim/module/model/ui.py | 4 +++- .../blenderbim/bim/module/type/__init__.py | 1 + .../blenderbim/bim/module/type/operator.py | 24 ++++++++++++++++++- src/blenderbim/blenderbim/tool/root.py | 7 ++---- src/blenderbim/test/core/test_drawing.py | 3 +++ 5 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/ui.py b/src/blenderbim/blenderbim/bim/module/model/ui.py index 92b456cc4e..a3e689630b 100644 --- a/src/blenderbim/blenderbim/bim/module/model/ui.py +++ b/src/blenderbim/blenderbim/bim/module/model/ui.py @@ -41,7 +41,7 @@ from blenderbim.bim.helper import prop_with_search class LaunchTypeManager(bpy.types.Operator): bl_idname = "bim.launch_type_manager" bl_label = "Launch Type Manager" - bl_options = {"REGISTER"} + bl_options = {"REGISTER", "UNDO"} bl_description = "Display all available Construction Types to add new instances" def execute(self, context): @@ -127,6 +127,8 @@ class LaunchTypeManager(bpy.types.Operator): op.ifc_class = relating_type["ifc_class"] op.relating_type_id = relating_type["id"] + op = row.operator("bim.rename_type", icon="GREASEPENCIL", text="") + op.element = relating_type["id"] op = row.operator("bim.select_type", icon="OBJECT_DATA", text="") op.relating_type = relating_type["id"] op = row.operator("bim.duplicate_type", icon="DUPLICATE", text="") diff --git a/src/blenderbim/blenderbim/bim/module/type/__init__.py b/src/blenderbim/blenderbim/bim/module/type/__init__.py index 50accd37ea..d52d470062 100644 --- a/src/blenderbim/blenderbim/bim/module/type/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/type/__init__.py @@ -27,6 +27,7 @@ classes = ( operator.EnableEditingType, operator.PurgeUnusedTypes, operator.RemoveType, + operator.RenameType, operator.SelectSimilarType, operator.SelectType, operator.SelectTypeObjects, diff --git a/src/blenderbim/blenderbim/bim/module/type/operator.py b/src/blenderbim/blenderbim/bim/module/type/operator.py index fda53d560a..921543e5ba 100644 --- a/src/blenderbim/blenderbim/bim/module/type/operator.py +++ b/src/blenderbim/blenderbim/bim/module/type/operator.py @@ -446,7 +446,29 @@ class RemoveType(bpy.types.Operator, tool.Ifc.Operator): if obj: tool.Ifc.unlink(obj=obj) bpy.data.objects.remove(obj) - return {"FINISHED"} + + +class RenameType(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.rename_type" + bl_label = "Rename Type" + bl_options = {"REGISTER", "UNDO"} + element: bpy.props.IntProperty() + name: bpy.props.StringProperty(name="Name") + + def _execute(self, context): + element = tool.Ifc.get().by_id(self.element) + obj = tool.Ifc.get_object(element) + element.Name = self.name + if obj: + tool.Root.set_object_name(obj, element) + + def invoke(self, context, event): + element = tool.Ifc.get().by_id(self.element) + self.name = element.Name or "Unnamed" + return context.window_manager.invoke_props_dialog(self) + + def draw(self, context): + self.layout.prop(self, "name") class DuplicateType(bpy.types.Operator, tool.Ifc.Operator): diff --git a/src/blenderbim/blenderbim/tool/root.py b/src/blenderbim/blenderbim/tool/root.py index eee7eb53c5..b7424ff3bc 100644 --- a/src/blenderbim/blenderbim/tool/root.py +++ b/src/blenderbim/blenderbim/tool/root.py @@ -161,8 +161,5 @@ class Root(blenderbim.core.tool.Root): @classmethod def set_object_name(cls, obj, element): - name = obj.name - if "/" in name and name.split("/")[0][0:3] == "Ifc": - name = "/".join(name.split("/")[1:]) - name = "{}/{}".format(element.is_a(), name) - obj.name = name + name = getattr(element, "Name", getattr(element, "AxisTag", None)) + obj.name = "{}/{}".format(element.is_a(), name or "Unnamed") diff --git a/src/blenderbim/test/core/test_drawing.py b/src/blenderbim/test/core/test_drawing.py index 2cadacdc26..ba839a1869 100644 --- a/src/blenderbim/test/core/test_drawing.py +++ b/src/blenderbim/test/core/test_drawing.py @@ -492,6 +492,9 @@ class TestUpdateDrawingName: drawing.does_file_exist("absolute_layout_uri").should_be_called().will_return(True) drawing.update_embedded_svg_location("absolute_layout_uri", "old_location", "new_location").should_be_called() + drawing.is_editing_sheets().should_be_called().will_return(True) + drawing.import_sheets().should_be_called() + subject.update_drawing_name(ifc, drawing, drawing="drawing", name="name") From a2e7cb81584fddb5aa3a194f79bdfa4ad0a79fe7 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 15 Jul 2023 00:00:10 +1000 Subject: [PATCH 21/49] Fix undo bugs in the type module where some operators were not part of the undo system for some reason. --- src/blenderbim/blenderbim/bim/ifc.py | 4 +++- src/blenderbim/blenderbim/bim/module/type/operator.py | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/ifc.py b/src/blenderbim/blenderbim/bim/ifc.py index 875614e1bd..5533b4fb0c 100644 --- a/src/blenderbim/blenderbim/bim/ifc.py +++ b/src/blenderbim/blenderbim/bim/ifc.py @@ -332,10 +332,12 @@ class IfcStore: IfcStore.id_map[data["id"]] = obj if "guid" in data: IfcStore.guid_map[data["guid"]] = obj - blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback) blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback) if isinstance(obj, bpy.types.Material): blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback) + elif isinstance(obj, bpy.types.Object): + blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback) + blenderbim.bim.handler.subscribe_to(obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback) # TODO Listeners are not re-registered. Does this cause nasty problems to debug later on? # TODO We're handling id_map and guid_map, but what about edited_objs? This might cause big problems. diff --git a/src/blenderbim/blenderbim/bim/module/type/operator.py b/src/blenderbim/blenderbim/bim/module/type/operator.py index 921543e5ba..4a0e9e06cd 100644 --- a/src/blenderbim/blenderbim/bim/module/type/operator.py +++ b/src/blenderbim/blenderbim/bim/module/type/operator.py @@ -436,7 +436,7 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator): class RemoveType(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.remove_type" bl_label = "Remove Type" - bl_options = {"REGISTER"} + bl_options = {"REGISTER", "UNDO"} element: bpy.props.IntProperty() def _execute(self, context): @@ -474,7 +474,7 @@ class RenameType(bpy.types.Operator, tool.Ifc.Operator): class DuplicateType(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.duplicate_type" bl_label = "Duplicate Type" - bl_options = {"REGISTER"} + bl_options = {"REGISTER", "UNDO"} element: bpy.props.IntProperty() def _execute(self, context): @@ -500,7 +500,7 @@ class DuplicateType(bpy.types.Operator, tool.Ifc.Operator): class PurgeUnusedTypes(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.purge_unused_types" bl_label = "Purge Unused Types" - bl_options = {"REGISTER"} + bl_options = {"REGISTER", "UNDO"} def _execute(self, context): core.purge_unused_types(tool.Ifc, tool.Type) From 0a840188bf32c23d09307e69cfa6872cd292c7e6 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 15 Jul 2023 00:05:34 +1000 Subject: [PATCH 22/49] DANGER! Potentially fix a bunch of undo bugs by completely rebuilding undo id_map tracking with a much simpler solution. In the past, we used undo_pre and redo_pre and a bunch of complex commit_link and rollback_link tracking to figure out what IDs and GUIDs were added / removed during transactions and how to handle invalidated objects in the id/guid maps. I really don't know why I made it that complex. Now we simply treat all data as bad and rebuild the either map. This means slower undos / redos for large projects, but in theory the maps will never invalidate. This either fixes a lot of things or breaks a lot of things :) Watch out! --- src/blenderbim/blenderbim/bim/__init__.py | 2 - src/blenderbim/blenderbim/bim/handler.py | 20 ++--- src/blenderbim/blenderbim/bim/ifc.py | 98 ----------------------- src/blenderbim/blenderbim/tool/ifc.py | 50 ++++++++++++ 4 files changed, 55 insertions(+), 115 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/__init__.py b/src/blenderbim/blenderbim/bim/__init__.py index 9db6058d50..2d43014e6a 100644 --- a/src/blenderbim/blenderbim/bim/__init__.py +++ b/src/blenderbim/blenderbim/bim/__init__.py @@ -170,9 +170,7 @@ def register(): for cls in classes: bpy.utils.register_class(cls) bpy.app.handlers.depsgraph_update_post.append(on_register) - bpy.app.handlers.undo_pre.append(handler.undo_pre) bpy.app.handlers.undo_post.append(handler.undo_post) - bpy.app.handlers.redo_pre.append(handler.redo_pre) bpy.app.handlers.redo_post.append(handler.redo_post) bpy.app.handlers.load_post.append(handler.load_post) bpy.app.handlers.load_post.append(handler.loadIfcStore) diff --git a/src/blenderbim/blenderbim/bim/handler.py b/src/blenderbim/blenderbim/bim/handler.py index 41fd7e6f24..c8147ac556 100644 --- a/src/blenderbim/blenderbim/bim/handler.py +++ b/src/blenderbim/blenderbim/bim/handler.py @@ -54,10 +54,12 @@ def mode_callback(obj, data): def name_callback(obj, data): - # TODO Do we still need this, now that we are monitoring the undo redo objects? try: obj.name except: + # The object is invalid but somehow still has a callback. Clear all + # msgbus subscriptions to prevent useless further triggers. + bpy.msgbus.clear_by_owner(obj) return # In case the object RNA is gone during an undo / redo operation # Blender names are up to 63 UTF-8 bytes if len(bytes(obj.name, "utf-8")) >= 63: @@ -203,24 +205,13 @@ def loadIfcStore(scene): IfcStore.relink_all_objects() -@persistent -def undo_pre(scene): - IfcStore.track_undo_redo_stack_object_map() - - @persistent def undo_post(scene): if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction: IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction IfcStore.undo() purge_module_data() - IfcStore.track_undo_redo_stack_selected_objects() - IfcStore.reload_undo_redo_stack_objects() - - -@persistent -def redo_pre(scene): - IfcStore.track_undo_redo_stack_object_map() + tool.Ifc.rebuild_element_maps() @persistent @@ -229,8 +220,7 @@ def redo_post(scene): IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction IfcStore.redo() purge_module_data() - IfcStore.track_undo_redo_stack_selected_objects() - IfcStore.reload_undo_redo_stack_objects() + tool.Ifc.rebuild_element_maps() def get_application(ifc): diff --git a/src/blenderbim/blenderbim/bim/ifc.py b/src/blenderbim/blenderbim/bim/ifc.py index 5533b4fb0c..4104157c5f 100644 --- a/src/blenderbim/blenderbim/bim/ifc.py +++ b/src/blenderbim/blenderbim/bim/ifc.py @@ -44,9 +44,6 @@ class IfcStore: classification_file = None library_path = "" library_file = None - element_listeners = set() - undo_redo_stack_objects = set() - undo_redo_stack_object_names = {} current_transaction = "" last_transaction = "" history = [] @@ -161,96 +158,6 @@ class IfcStore: return return obj - @staticmethod - def add_element_listener(callback): - IfcStore.element_listeners.add(callback) - - @staticmethod - def track_undo_redo_stack_object_map(): - """Keeps track of currently mapped object names, typically during undo and redo - - When any Blender object is stored outside a Blender PointerProperty, such as - in a regular Python list, there is the likely probability that the object - will be invalidated when undo or redo occurs. Object invalidation seems to - occur whenever an object is affected during an operation. - - For example, if an operator deletes a modifier on o1, then o1 will be invalidated. - """ - for key, value in IfcStore.id_map.items(): - try: - IfcStore.undo_redo_stack_object_names[key] = value.name - except: - continue - - @staticmethod - def track_undo_redo_stack_selected_objects(): - """Keeps track of selected object names, typically during undo and redo - - When any Blender object is stored outside a Blender PointerProperty, such as - in a regular Python list, there is the likely probability that the object - will be invalidated when undo or redo occurs. Object invalidation seems to - occur for selected objects either pre/post undo/redo event, including - selected objects for consecutive undo/redos, and all children. This is - important because selected objects are often deleted from the scene. - - So if I first select o1, then o2, then o3, then press undo, o3 will be - invalidated. If instead I press undo twice, o3 and o2 will be invalidated. - """ - if bpy.context.active_object: - objects = set([o.name for o in bpy.context.selected_objects + [bpy.context.active_object]]) - objects.update([o.name for o in bpy.context.active_object.children]) - else: - objects = set([o.name for o in bpy.context.selected_objects]) - for obj in bpy.context.selected_objects: - objects.update([o.name for o in obj.children]) - IfcStore.undo_redo_stack_objects |= objects - - @staticmethod - def reload_undo_redo_stack_objects(): - """Reloads any invalidated objects after undo or redo - - After an undo or redo operation, objects may have been invalidated in - our id_map and guid_map. Invalidated objects are typically those that - have been manipulated or deleted. This checks the cache of mapped and - selected objects prior to the operation and ensures that if the object - is invalidated, they are reloaded based on the object name that was - tracked prior to the undo / redo. - """ - file = IfcStore.get_file() - if not file: - return - - # First, reload objects that were selected or active - for name in IfcStore.undo_redo_stack_objects: - obj = bpy.data.objects.get(name) - if not obj: - continue - if not obj.BIMObjectProperties.ifc_definition_id: - continue - element = file.by_id(obj.BIMObjectProperties.ifc_definition_id) - data = {"id": element.id(), "obj": obj.name} - if hasattr(element, "GlobalId"): - data["guid"] = element.GlobalId - IfcStore.commit_link_element(data) - - # Scan for any straggling invalidated objects which were indirectly affected and reload them too. - for key, value in IfcStore.id_map.items(): - try: - value.name - except: - # TODO not so sure about this obj_name check - obj_name = IfcStore.undo_redo_stack_object_names.get(key, None) - if not obj_name: - continue - obj = bpy.data.objects.get(obj_name) - if not obj or not obj.BIMObjectProperties.ifc_definition_id: - continue - element = file.by_id(obj.BIMObjectProperties.ifc_definition_id) - data = {"id": element.id(), "obj": obj.name} - if hasattr(element, "GlobalId"): - data["guid"] = element.GlobalId - IfcStore.commit_link_element(data) - @staticmethod def relink_all_objects(): if not IfcStore.get_file(): @@ -309,9 +216,6 @@ class IfcStore: blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback) blenderbim.bim.handler.subscribe_to(obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback) - for listener in IfcStore.element_listeners: - listener(element, obj) - if IfcStore.history: data = {"id": element.id(), "guid": getattr(element, "GlobalId", None), "obj": obj.name} IfcStore.history[-1]["operations"].append( @@ -437,8 +341,6 @@ class IfcStore: @staticmethod def begin_transaction(operator): - IfcStore.undo_redo_stack_objects = set() - IfcStore.undo_redo_stack_object_names = {} IfcStore.current_transaction = str(uuid.uuid4()) operator.transaction_key = IfcStore.current_transaction diff --git a/src/blenderbim/blenderbim/tool/ifc.py b/src/blenderbim/blenderbim/tool/ifc.py index 36ab2f22c5..c6050492e3 100644 --- a/src/blenderbim/blenderbim/tool/ifc.py +++ b/src/blenderbim/blenderbim/tool/ifc.py @@ -21,6 +21,8 @@ import bpy import numpy as np import ifcopenshell.api import blenderbim.core.tool +import blenderbim.bim.handler +import blenderbim.tool as tool from blenderbim.bim.ifc import IfcStore @@ -88,6 +90,54 @@ class Ifc(blenderbim.core.tool.Ifc): def get_object(cls, element): return IfcStore.get_element(element.id()) + @classmethod + def rebuild_element_maps(cls): + """Rebuilds the id_map and guid_map + + When any Blender object is stored outside a Blender PointerProperty, + such as in a regular Python list, there is the likely probability that + the object will be invalidated when undo or redo occurs. Object + invalidation seems to occur whenever an object is affected during an + operation, or selected, or has a related modifier, and so on ... to + cover all bases, this completely rebuilds the element maps. + """ + + IfcStore.id_map = {} + IfcStore.guid_map = {} + + if not cls.get(): + return + + for obj in bpy.data.objects: + bpy.msgbus.clear_by_owner(obj) + + element = cls.get_entity(obj) + if not element: + continue + IfcStore.id_map[element.id()] = obj + global_id = getattr(element, "GlobalId", None) + if global_id: + IfcStore.guid_map[global_id] = obj + + blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback) + blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback) + blenderbim.bim.handler.subscribe_to( + obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback + ) + + for obj in bpy.data.materials: + bpy.msgbus.clear_by_owner(obj) + + material = cls.get_entity(obj) + style = tool.Style.get_style(obj) + if material: + IfcStore.id_map[material.id()] = obj + if style: + IfcStore.id_map[style.id()] = obj + + blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback) + blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback) + @classmethod def link(cls, element, obj): IfcStore.link_element(element, obj) From 90fce18042aa1f21816ec6252308d02d29eebd53 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 15 Jul 2023 13:22:13 +1000 Subject: [PATCH 23/49] New icon tabbed interface so you don't need to click twice to change tab. --- src/blenderbim/blenderbim/bim/operator.py | 15 +++++++++ src/blenderbim/blenderbim/bim/prop.py | 2 ++ src/blenderbim/blenderbim/bim/ui.py | 40 ++++++++++++++++++++++- 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/bim/operator.py b/src/blenderbim/blenderbim/bim/operator.py index 75a8875c5e..5e2fbe31ac 100644 --- a/src/blenderbim/blenderbim/bim/operator.py +++ b/src/blenderbim/blenderbim/bim/operator.py @@ -40,6 +40,21 @@ from mathutils import Vector, Matrix, Euler from math import radians +class SetTab(bpy.types.Operator): + bl_idname = "bim.set_tab" + bl_label = "Set Tab" + bl_options = {"REGISTER", "UNDO"} + bl_description = "Sets the current property tab" + tab: bpy.props.StringProperty() + + def execute(self, context): + if context.area.spaces.active.search_filter: + return {"FINISHED"} + aprops = context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)] + aprops.tab = self.tab + return {"FINISHED"} + + class SwitchTab(bpy.types.Operator): bl_idname = "bim.switch_tab" bl_label = "Switch Tab" diff --git a/src/blenderbim/blenderbim/bim/prop.py b/src/blenderbim/blenderbim/bim/prop.py index c51e0611c9..fcd2140ec9 100644 --- a/src/blenderbim/blenderbim/bim/prop.py +++ b/src/blenderbim/blenderbim/bim/prop.py @@ -360,6 +360,8 @@ class BIMAreaProperties(PropertyGroup): tab: EnumProperty(default=0, items=get_tab, name="Tab", update=update_tab) previous_tab: StringProperty(default="PROJECT", name="Previous Tab") alt_tab: StringProperty(default="OBJECT", name="Alt Tab") + active_tab: BoolProperty(default=True, name="Active Tab") + inactive_tab: BoolProperty(default=False, name="Inactive Tab") class BIMProperties(PropertyGroup): diff --git a/src/blenderbim/blenderbim/bim/ui.py b/src/blenderbim/blenderbim/bim/ui.py index 281c86cdfa..0000d17aeb 100644 --- a/src/blenderbim/blenderbim/bim/ui.py +++ b/src/blenderbim/blenderbim/bim/ui.py @@ -281,9 +281,47 @@ class BIM_PT_root(Panel): def draw(self, context): try: aprops = context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)] + + row = self.layout.row() + row.scale_y = 0.5 # Yes, we actually do this. + row.operator( + "bim.set_tab", text="", emboss=False, icon_value=blenderbim.bim.icons["IFC"].icon_id + ).tab = "PROJECT" + row.operator("bim.set_tab", text="", emboss=False, icon="FILE_3D").tab = "OBJECT" + row.operator("bim.set_tab", text="", emboss=False, icon="MATERIAL").tab = "MATERIALS" + row.operator("bim.set_tab", text="", emboss=False, icon="DOCUMENTS").tab = "DRAWINGS" + row.operator("bim.set_tab", text="", emboss=False, icon="NETWORK_DRIVE").tab = "SERVICES" + row.operator("bim.set_tab", text="", emboss=False, icon="EDITMODE_HLT").tab = "STRUCTURE" + row.operator("bim.set_tab", text="", emboss=False, icon="NLA").tab = "SCHEDULING" + row.operator("bim.set_tab", text="", emboss=False, icon="PACKAGE").tab = "FM" + row.operator("bim.set_tab", text="", emboss=False, icon="COLLAPSEMENU").tab = "OTHER" + row.operator("bim.set_tab", text="", emboss=False, icon="BLENDER").tab = "BLENDER" + row.operator("bim.switch_tab", text="", emboss=False, icon="UV_SYNC_SELECT") + + # Yes, that's right, we're still doing this. + row = self.layout.row() + row.scale_y = 0.2 + for tab in [ + "PROJECT", + "OBJECT", + "MATERIALS", + "DRAWINGS", + "SERVICES", + "STRUCTURE", + "SCHEDULING", + "FM", + "OTHER", + "BLENDER", + "SWITCH", + ]: + if aprops.tab == tab: + row.prop(aprops, "active_tab", text="", icon="BLANK1") + else: + row.prop(aprops, "inactive_tab", text="", icon="BLANK1", emboss=False) + + aprops = context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)] row = self.layout.row(align=True) row.prop(aprops, "tab", text="") - row.operator("bim.switch_tab", text="", icon="UV_SYNC_SELECT") except: pass # Prior to load_post, we may not have any area properties setup From d50e43197e844db28577daaa6ff6a68bd543f89a Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Sat, 15 Jul 2023 10:28:23 +0500 Subject: [PATCH 24/49] small fix 90fce1804 --- src/blenderbim/blenderbim/bim/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/blenderbim/blenderbim/bim/__init__.py b/src/blenderbim/blenderbim/bim/__init__.py index 2d43014e6a..c82c820201 100644 --- a/src/blenderbim/blenderbim/bim/__init__.py +++ b/src/blenderbim/blenderbim/bim/__init__.py @@ -95,6 +95,7 @@ classes = [ operator.OpenUpstream, operator.OpenUri, operator.SwitchTab, + operator.SetTab, operator.ReloadIfcFile, operator.RemoveIfcFile, operator.SelectDataDir, From 53bd4bf5ea91d18b678a1aeab4a02cc67b994dac Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 15 Jul 2023 20:57:08 +1000 Subject: [PATCH 25/49] Example of UI if we sentence-cased IFC classes. Good or bad? Please comment at https://community.osarch.org/discussion/comment/16493 --- src/blenderbim/blenderbim/bim/helper.py | 6 ++++++ src/blenderbim/blenderbim/bim/module/root/data.py | 8 +++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py index 5b73dadd2a..b062aeb552 100644 --- a/src/blenderbim/blenderbim/bim/helper.py +++ b/src/blenderbim/blenderbim/bim/helper.py @@ -17,6 +17,7 @@ # along with BlenderBIM Add-on. If not, see . # from datetime import date +import re import bpy import json import math @@ -31,6 +32,11 @@ import blenderbim.tool as tool from blenderbim.bim.ifc import IfcStore +def uncamel(s): + s = re.sub('([a-z0-9])([A-Z])', r'\1 \2', s[3:] if s.startswith("Ifc") else s) + return re.sub('([A-Z])([A-Z][a-z])', r'\1 \2', s) + + def draw_attributes(props, layout, copy_operator=None, popup_active_attribute=None): """you can set attribute active in popup with `active_attribute` meaning you will be able to type into attribute's field without having to click diff --git a/src/blenderbim/blenderbim/bim/module/root/data.py b/src/blenderbim/blenderbim/bim/module/root/data.py index d77664249b..4bd5cecab7 100644 --- a/src/blenderbim/blenderbim/bim/module/root/data.py +++ b/src/blenderbim/blenderbim/bim/module/root/data.py @@ -21,6 +21,7 @@ import bpy import ifcopenshell.util.element from ifcopenshell.util.doc import get_entity_doc, get_predefined_type_doc import blenderbim.tool as tool +import blenderbim.bim.helper from blenderbim.bim.ifc import IfcStore @@ -70,7 +71,7 @@ class IfcClassData: "IfcAnnotation", "IfcRelSpaceBoundary", ] - return [(e, e, (get_entity_doc(version, e) or {}).get("description", "")) for e in products] + return [(e, blenderbim.bim.helper.uncamel(e), (get_entity_doc(version, e) or {}).get("description", "")) for e in products] @classmethod def ifc_classes(cls): @@ -86,7 +87,7 @@ class IfcClassData: # Yeah, weird isn't it. names.remove("IfcOpeningStandardCase") version = tool.Ifc.get_schema() - return [(c, c, (get_entity_doc(version, c) or {}).get("description", "")) for c in sorted(names)] + return [(c, blenderbim.bim.helper.uncamel(c), (get_entity_doc(version, c) or {}).get("description", "")) for c in sorted(names)] @classmethod def ifc_predefined_types(cls): @@ -163,9 +164,10 @@ class IfcClassData: if not element: return name = element.is_a() + name = blenderbim.bim.helper.uncamel(name) predefined_type = ifcopenshell.util.element.get_predefined_type(element) if predefined_type: - name += f"[{predefined_type}]" + name += f" [{predefined_type}]" return name @classmethod From 147df7f46b5ff028c16694f10a007dd6fb487a58 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 15 Jul 2023 21:02:30 +1000 Subject: [PATCH 26/49] Bulk rename panels to remove redundant IFC prefix --- .../blenderbim/bim/module/aggregate/ui.py | 2 +- .../blenderbim/bim/module/attribute/ui.py | 4 +-- .../blenderbim/bim/module/boundary/ui.py | 6 ++-- .../blenderbim/bim/module/brick/ui.py | 2 +- .../blenderbim/bim/module/clash/ui.py | 2 +- .../bim/module/classification/ui.py | 8 ++--- .../blenderbim/bim/module/cobie/ui.py | 2 +- .../blenderbim/bim/module/constraint/ui.py | 4 +-- .../blenderbim/bim/module/context/ui.py | 2 +- .../blenderbim/bim/module/cost/ui.py | 8 ++--- .../blenderbim/bim/module/csv/ui.py | 2 +- .../blenderbim/bim/module/debug/ui.py | 2 +- .../blenderbim/bim/module/diff/ui.py | 2 +- .../blenderbim/bim/module/document/ui.py | 4 +-- .../blenderbim/bim/module/drawing/ui.py | 4 +-- .../blenderbim/bim/module/geometry/ui.py | 10 +++---- .../blenderbim/bim/module/georeference/ui.py | 2 +- .../blenderbim/bim/module/gis/ui.py | 2 +- .../blenderbim/bim/module/group/ui.py | 4 +-- .../blenderbim/bim/module/ifcgit/ui.py | 2 +- .../blenderbim/bim/module/layer/ui.py | 2 +- .../blenderbim/bim/module/library/ui.py | 4 +-- .../blenderbim/bim/module/material/ui.py | 6 ++-- .../blenderbim/bim/module/model/grid.py | 2 +- .../blenderbim/bim/module/model/pie.py | 2 +- .../blenderbim/bim/module/model/ui.py | 16 +++++----- .../blenderbim/bim/module/owner/ui.py | 10 +++---- .../blenderbim/bim/module/patch/ui.py | 2 +- .../blenderbim/bim/module/profile/ui.py | 2 +- .../blenderbim/bim/module/project/ui.py | 6 ++-- .../blenderbim/bim/module/pset/ui.py | 22 +++++++------- .../blenderbim/bim/module/pset_template/ui.py | 2 +- .../blenderbim/bim/module/resource/ui.py | 2 +- .../blenderbim/bim/module/root/ui.py | 3 +- .../blenderbim/bim/module/search/ui.py | 4 +-- .../blenderbim/bim/module/sequence/ui.py | 8 ++--- .../blenderbim/bim/module/spatial/ui.py | 4 +-- .../blenderbim/bim/module/structural/ui.py | 16 +++++----- .../blenderbim/bim/module/style/ui.py | 10 +++---- .../blenderbim/bim/module/system/ui.py | 8 ++--- .../blenderbim/bim/module/type/ui.py | 2 +- .../blenderbim/bim/module/unit/ui.py | 2 +- .../blenderbim/bim/module/void/ui.py | 4 +-- src/blenderbim/blenderbim/bim/ui.py | 30 +++++++++---------- 44 files changed, 122 insertions(+), 121 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/ui.py b/src/blenderbim/blenderbim/bim/module/aggregate/ui.py index 50bc8007f6..b7ae5a84d2 100644 --- a/src/blenderbim/blenderbim/bim/module/aggregate/ui.py +++ b/src/blenderbim/blenderbim/bim/module/aggregate/ui.py @@ -22,7 +22,7 @@ from blenderbim.bim.ifc import IfcStore class BIM_PT_aggregate(Panel): - bl_label = "IFC Aggregates" + bl_label = "Aggregates" bl_idname = "BIM_PT_aggregate" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/attribute/ui.py b/src/blenderbim/blenderbim/bim/module/attribute/ui.py index 510b2c537e..b3c807cfb7 100644 --- a/src/blenderbim/blenderbim/bim/module/attribute/ui.py +++ b/src/blenderbim/blenderbim/bim/module/attribute/ui.py @@ -54,7 +54,7 @@ def draw_ui(context, layout, obj_type, attributes): class BIM_PT_object_attributes(Panel): - bl_label = "IFC Attributes" + bl_label = "Attributes" bl_idname = "BIM_PT_object_attributes" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -76,7 +76,7 @@ class BIM_PT_object_attributes(Panel): class BIM_PT_material_attributes(Panel): - bl_label = "IFC Material Attributes" + bl_label = "Material Attributes" bl_idname = "BIM_PT_material_attributes" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" diff --git a/src/blenderbim/blenderbim/bim/module/boundary/ui.py b/src/blenderbim/blenderbim/bim/module/boundary/ui.py index 6abad5b6e6..a62b6d5a8e 100644 --- a/src/blenderbim/blenderbim/bim/module/boundary/ui.py +++ b/src/blenderbim/blenderbim/bim/module/boundary/ui.py @@ -25,7 +25,7 @@ from blenderbim.bim.module.boundary.data import SpaceBoundariesData class BIM_PT_SceneBoundaries(Panel): - bl_label = "IFC Space Boundaries" + bl_label = "Space Boundaries" bl_id_name = "BIM_PT_scene_boundaries" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -45,7 +45,7 @@ class BIM_PT_SceneBoundaries(Panel): class BIM_PT_Boundary(Panel): - bl_label = "IFC Space Boundary" + bl_label = "Space Boundary" bl_idname = "BIM_PT_Boundary" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -117,7 +117,7 @@ class BIM_PT_Boundary(Panel): class BIM_PT_SpaceBoundaries(Panel): - bl_label = "IFC Space Boundaries" + bl_label = "Space Boundaries" bl_idname = "BIM_PT_SpaceBoundaries" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/brick/ui.py b/src/blenderbim/blenderbim/bim/module/brick/ui.py index b769057d37..0f1983da8d 100644 --- a/src/blenderbim/blenderbim/bim/module/brick/ui.py +++ b/src/blenderbim/blenderbim/bim/module/brick/ui.py @@ -108,7 +108,7 @@ class BIM_PT_brickschema(Panel): class BIM_PT_ifc_brickschema_references(Panel): - bl_label = "IFC Brickschema References" + bl_label = "Brickschema References" bl_idname = "BIM_PT_ifc_brickschema_references" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/clash/ui.py b/src/blenderbim/blenderbim/bim/module/clash/ui.py index 21e6547e57..48a04eda4e 100644 --- a/src/blenderbim/blenderbim/bim/module/clash/ui.py +++ b/src/blenderbim/blenderbim/bim/module/clash/ui.py @@ -21,7 +21,7 @@ from bpy.types import Panel class BIM_PT_ifcclash(Panel): - bl_label = "IFC Clash Sets" + bl_label = "Clash Sets" bl_idname = "BIM_PT_ifcclash" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/classification/ui.py b/src/blenderbim/blenderbim/bim/module/classification/ui.py index 8bf5f660c7..c49957f3b3 100644 --- a/src/blenderbim/blenderbim/bim/module/classification/ui.py +++ b/src/blenderbim/blenderbim/bim/module/classification/ui.py @@ -30,7 +30,7 @@ from blenderbim.bim.module.classification.data import ( class BIM_PT_classifications(Panel): - bl_label = "IFC Classifications" + bl_label = "Classifications" bl_idname = "BIM_PT_classifications" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -156,7 +156,7 @@ class ReferenceUI: class BIM_PT_classification_references(Panel, ReferenceUI): - bl_label = "IFC Classification References" + bl_label = "Classification References" bl_idname = "BIM_PT_classification_references" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -180,7 +180,7 @@ class BIM_PT_classification_references(Panel, ReferenceUI): class BIM_PT_material_classifications(Panel, ReferenceUI): - bl_label = "IFC Material Classifications" + bl_label = "Material Classifications" bl_idname = "BIM_PT_material_classifications" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -206,7 +206,7 @@ class BIM_PT_material_classifications(Panel, ReferenceUI): class BIM_PT_cost_classifications(Panel, ReferenceUI): - bl_label = "IFC Cost Classifications" + bl_label = "Cost Classifications" bl_idname = "BIM_PT_cost_classifications" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/cobie/ui.py b/src/blenderbim/blenderbim/bim/module/cobie/ui.py index 075dba6bba..333925fd29 100644 --- a/src/blenderbim/blenderbim/bim/module/cobie/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cobie/ui.py @@ -21,7 +21,7 @@ from blenderbim.bim.ifc import IfcStore class BIM_PT_cobie(Panel): - bl_label = "IFC COBie" + bl_label = "COBie" bl_idname = "BIM_PT_cobie" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/constraint/ui.py b/src/blenderbim/blenderbim/bim/module/constraint/ui.py index 38bd07bfd3..29b68a00de 100644 --- a/src/blenderbim/blenderbim/bim/module/constraint/ui.py +++ b/src/blenderbim/blenderbim/bim/module/constraint/ui.py @@ -23,7 +23,7 @@ from blenderbim.bim.module.constraint.data import ConstraintsData, ObjectConstra class BIM_PT_constraints(Panel): - bl_label = "IFC Constraints" + bl_label = "Constraints" bl_idname = "BIM_PT_constraints" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -68,7 +68,7 @@ class BIM_PT_constraints(Panel): class BIM_PT_object_constraints(Panel): - bl_label = "IFC Constraints" + bl_label = "Constraints" bl_idname = "BIM_PT_object_constraints" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/context/ui.py b/src/blenderbim/blenderbim/bim/module/context/ui.py index 58449f6d45..c8fe010625 100644 --- a/src/blenderbim/blenderbim/bim/module/context/ui.py +++ b/src/blenderbim/blenderbim/bim/module/context/ui.py @@ -23,7 +23,7 @@ from blenderbim.bim.module.context.data import ContextData class BIM_PT_context(bpy.types.Panel): - bl_label = "IFC Geometric Representation Contexts" + bl_label = "Geometric Representation Contexts" bl_idname = "BIM_PT_context" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index 3e92163157..142e3f814a 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -24,7 +24,7 @@ from blenderbim.bim.module.cost.data import CostSchedulesData class BIM_PT_cost_schedules(Panel): - bl_label = "IFC Cost Schedules" + bl_label = "Cost Schedules" bl_idname = "BIM_PT_cost_schedules" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -267,7 +267,7 @@ class BIM_PT_cost_schedules(Panel): class BIM_PT_cost_item_types(Panel): - bl_label = "IFC Cost Item Types" + bl_label = "Cost Item Types" bl_idname = "BIM_PT_cost_item_types" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -353,7 +353,7 @@ class BIM_PT_cost_item_types(Panel): class BIM_PT_cost_item_quantities(Panel): - bl_label = "IFC Cost Item Quantities" + bl_label = "Cost Item Quantities" bl_idname = "BIM_PT_cost_item_quantities" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -525,7 +525,7 @@ class BIM_PT_cost_item_quantities(Panel): class BIM_PT_cost_item_rates(Panel): - bl_label = "IFC Cost Item Rates" + bl_label = "Cost Item Rates" bl_idname = "BIM_PT_cost_item_rates" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/csv/ui.py b/src/blenderbim/blenderbim/bim/module/csv/ui.py index 7c1df7a4d2..a242039712 100644 --- a/src/blenderbim/blenderbim/bim/module/csv/ui.py +++ b/src/blenderbim/blenderbim/bim/module/csv/ui.py @@ -21,7 +21,7 @@ from blenderbim.bim.ifc import IfcStore class BIM_PT_ifccsv(Panel): - bl_label = "IFC CSV Import/Export" + bl_label = "CSV Import/Export" bl_idname = "BIM_PT_ifccsv" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/debug/ui.py b/src/blenderbim/blenderbim/bim/module/debug/ui.py index 7e06e2fb19..91fd3e1024 100644 --- a/src/blenderbim/blenderbim/bim/module/debug/ui.py +++ b/src/blenderbim/blenderbim/bim/module/debug/ui.py @@ -21,7 +21,7 @@ from bpy.types import Panel class BIM_PT_debug(Panel): - bl_label = "IFC Debug" + bl_label = "Debug" bl_idname = "BIM_PT_debug" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/diff/ui.py b/src/blenderbim/blenderbim/bim/module/diff/ui.py index 520a26ae39..edb64be484 100644 --- a/src/blenderbim/blenderbim/bim/module/diff/ui.py +++ b/src/blenderbim/blenderbim/bim/module/diff/ui.py @@ -24,7 +24,7 @@ import json class BIM_PT_diff(Panel): - bl_label = "IFC Diff" + bl_label = "Diff" bl_idname = "BIM_PT_diff" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/document/ui.py b/src/blenderbim/blenderbim/bim/module/document/ui.py index b20f0319e9..d45f7b6792 100644 --- a/src/blenderbim/blenderbim/bim/module/document/ui.py +++ b/src/blenderbim/blenderbim/bim/module/document/ui.py @@ -23,7 +23,7 @@ from blenderbim.bim.module.document.data import DocumentData, ObjectDocumentData class BIM_PT_documents(Panel): - bl_label = "IFC Documents" + bl_label = "Documents" bl_idname = "BIM_PT_documents" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -76,7 +76,7 @@ class BIM_PT_documents(Panel): class BIM_PT_object_documents(Panel): - bl_label = "IFC Documents" + bl_label = "Documents" bl_idname = "BIM_PT_object_documents" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/drawing/ui.py b/src/blenderbim/blenderbim/bim/module/drawing/ui.py index dad42d1ca6..a0a461a8dd 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/ui.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/ui.py @@ -387,7 +387,7 @@ class BIM_PT_sheets(Panel): class BIM_PT_product_assignments(Panel): - bl_label = "IFC Product Assignments" + bl_label = "Product Assignments" bl_idname = "BIM_PT_product_assignments" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -422,7 +422,7 @@ class BIM_PT_product_assignments(Panel): class BIM_PT_text(Panel): - bl_label = "IFC Text" + bl_label = "Text" bl_idname = "BIM_PT_text" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" diff --git a/src/blenderbim/blenderbim/bim/module/geometry/ui.py b/src/blenderbim/blenderbim/bim/module/geometry/ui.py index 26d1a9ddc6..d8ec59b274 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/ui.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/ui.py @@ -34,7 +34,7 @@ def outliner_menu(self, context): self.layout.operator("bim.override_outliner_delete", icon='X') class BIM_PT_representations(Panel): - bl_label = "IFC Representations" + bl_label = "Representations" bl_idname = "BIM_PT_representations" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -80,7 +80,7 @@ class BIM_PT_representations(Panel): class BIM_PT_connections(Panel): - bl_label = "IFC Connections" + bl_label = "Connections" bl_idname = "BIM_PT_connections" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -118,7 +118,7 @@ class BIM_PT_connections(Panel): class BIM_PT_mesh(Panel): - bl_label = "IFC Representation" + bl_label = "Representation" bl_idname = "BIM_PT_mesh" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -185,7 +185,7 @@ def BIM_PT_transform(self, context): class BIM_PT_derived_placements(Panel): - bl_label = "IFC Derived Placements" + bl_label = "Derived Placements" bl_idname = "BIM_PT_derived_placements" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -220,7 +220,7 @@ class BIM_PT_derived_placements(Panel): class BIM_PT_workarounds(Panel): - bl_label = "IFC Vendor Workarounds" + bl_label = "Vendor Workarounds" bl_idname = "BIM_PT_workarounds" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/georeference/ui.py b/src/blenderbim/blenderbim/bim/module/georeference/ui.py index d7c3131a0a..bb75f06f5e 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/ui.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/ui.py @@ -23,7 +23,7 @@ from blenderbim.bim.module.georeference.data import GeoreferenceData class BIM_PT_gis(Panel): - bl_label = "IFC Georeferencing" + bl_label = "Georeferencing" bl_idname = "BIM_PT_gis" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/gis/ui.py b/src/blenderbim/blenderbim/bim/module/gis/ui.py index 1ccc930928..83325ad097 100644 --- a/src/blenderbim/blenderbim/bim/module/gis/ui.py +++ b/src/blenderbim/blenderbim/bim/module/gis/ui.py @@ -20,7 +20,7 @@ from bpy.types import Panel class BIM_PT_cityjson_converter(Panel): - bl_label = "IFC CityJSON" + bl_label = "CityJSON" bl_idname = "BIM_PT_ifccityjson" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/group/ui.py b/src/blenderbim/blenderbim/bim/module/group/ui.py index 874d239bf2..ff5acd2c12 100644 --- a/src/blenderbim/blenderbim/bim/module/group/ui.py +++ b/src/blenderbim/blenderbim/bim/module/group/ui.py @@ -22,7 +22,7 @@ from blenderbim.bim.module.group.data import GroupsData, ObjectGroupsData class BIM_PT_groups(Panel): - bl_label = "IFC Groups" + bl_label = "Groups" bl_idname = "BIM_PT_groups" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -69,7 +69,7 @@ class BIM_PT_groups(Panel): class BIM_PT_object_groups(Panel): - bl_label = "IFC Groups" + bl_label = "Groups" bl_idname = "BIM_PT_object_groups" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/ifcgit/ui.py b/src/blenderbim/blenderbim/bim/module/ifcgit/ui.py index 286e900adc..d54591a509 100644 --- a/src/blenderbim/blenderbim/bim/module/ifcgit/ui.py +++ b/src/blenderbim/blenderbim/bim/module/ifcgit/ui.py @@ -6,7 +6,7 @@ from blenderbim.bim.module.ifcgit.data import IfcGitData class IFCGIT_PT_panel(bpy.types.Panel): """Scene Properties panel to interact with IFC repository data""" - bl_label = "IFC Git" + bl_label = "Git" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" diff --git a/src/blenderbim/blenderbim/bim/module/layer/ui.py b/src/blenderbim/blenderbim/bim/module/layer/ui.py index 179ea568a2..698bd448ab 100644 --- a/src/blenderbim/blenderbim/bim/module/layer/ui.py +++ b/src/blenderbim/blenderbim/bim/module/layer/ui.py @@ -22,7 +22,7 @@ from blenderbim.bim.module.layer.data import LayersData class BIM_PT_layers(Panel): - bl_label = "IFC Presentation Layers" + bl_label = "Presentation Layers" bl_idname = "BIM_PT_layers" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/library/ui.py b/src/blenderbim/blenderbim/bim/module/library/ui.py index 2057e45a98..feffc02cbd 100644 --- a/src/blenderbim/blenderbim/bim/module/library/ui.py +++ b/src/blenderbim/blenderbim/bim/module/library/ui.py @@ -23,7 +23,7 @@ from blenderbim.bim.module.library.data import LibrariesData, LibraryReferencesD class BIM_PT_libraries(Panel): - bl_label = "IFC Libraries" + bl_label = "Libraries" bl_idname = "BIM_PT_libraries" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -94,7 +94,7 @@ class BIM_PT_libraries(Panel): class BIM_PT_library_references(Panel): - bl_label = "IFC Library References" + bl_label = "Library References" bl_idname = "BIM_PT_library_references" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/material/ui.py b/src/blenderbim/blenderbim/bim/module/material/ui.py index da1490aae1..139a19f9ff 100644 --- a/src/blenderbim/blenderbim/bim/module/material/ui.py +++ b/src/blenderbim/blenderbim/bim/module/material/ui.py @@ -25,7 +25,7 @@ from blenderbim.bim.module.material.data import MaterialsData, ObjectMaterialDat class BIM_PT_materials(Panel): - bl_label = "IFC Materials" + bl_label = "Materials" bl_idname = "BIM_PT_materials" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -88,7 +88,7 @@ class BIM_PT_materials(Panel): blenderbim.bim.helper.draw_attributes(self.props.material_attributes, self.layout) class BIM_PT_material(Panel): - bl_label = "IFC Material" + bl_label = "Material" bl_idname = "BIM_PT_material" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -111,7 +111,7 @@ class BIM_PT_material(Panel): class BIM_PT_object_material(Panel): - bl_label = "IFC Object Material" + bl_label = "Object Material" bl_idname = "BIM_PT_object_material" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" diff --git a/src/blenderbim/blenderbim/bim/module/model/grid.py b/src/blenderbim/blenderbim/bim/module/model/grid.py index adb4a240c4..3159f03c3b 100644 --- a/src/blenderbim/blenderbim/bim/module/model/grid.py +++ b/src/blenderbim/blenderbim/bim/module/model/grid.py @@ -98,7 +98,7 @@ def add_object(self, context): class BIM_OT_add_object(Operator, tool.Ifc.Operator): bl_idname = "mesh.add_grid" - bl_label = "IFC Grid" + bl_label = "Grid" bl_options = {"REGISTER", "UNDO"} u_spacing: FloatProperty(name="U Spacing", default=10) diff --git a/src/blenderbim/blenderbim/bim/module/model/pie.py b/src/blenderbim/blenderbim/bim/module/model/pie.py index f29e286af9..f43af88132 100644 --- a/src/blenderbim/blenderbim/bim/module/model/pie.py +++ b/src/blenderbim/blenderbim/bim/module/model/pie.py @@ -92,7 +92,7 @@ class VIEW3D_MT_PIE_bim(bpy.types.Menu): class VIEW3D_MT_PIE_bim_class(bpy.types.Menu): - bl_label = "IFC Class" + bl_label = "Class" def draw(self, context): pie = self.layout.menu_pie() diff --git a/src/blenderbim/blenderbim/bim/module/model/ui.py b/src/blenderbim/blenderbim/bim/module/model/ui.py index a3e689630b..cb94c66d83 100644 --- a/src/blenderbim/blenderbim/bim/module/model/ui.py +++ b/src/blenderbim/blenderbim/bim/module/model/ui.py @@ -168,7 +168,7 @@ class BIM_PT_Grids(Panel): class BIM_PT_array(bpy.types.Panel): - bl_label = "IFC Array" + bl_label = "Array" bl_idname = "BIM_PT_array" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -236,7 +236,7 @@ class BIM_PT_array(bpy.types.Panel): class BIM_PT_stair(bpy.types.Panel): - bl_label = "IFC Stair" + bl_label = "Stair" bl_idname = "BIM_PT_stair" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -294,7 +294,7 @@ class BIM_PT_stair(bpy.types.Panel): class BIM_PT_sverchok(bpy.types.Panel): - bl_label = "IFC Sverchok" + bl_label = "Sverchok" bl_idname = "BIM_PT_sverchok" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -332,7 +332,7 @@ class BIM_PT_sverchok(bpy.types.Panel): class BIM_PT_window(bpy.types.Panel): - bl_label = "IFC Window" + bl_label = "Window" bl_idname = "BIM_PT_window" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -441,7 +441,7 @@ class BIM_PT_window(bpy.types.Panel): class BIM_PT_door(bpy.types.Panel): - bl_label = "IFC Door" + bl_label = "Door" bl_idname = "BIM_PT_door" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -514,7 +514,7 @@ class BIM_PT_door(bpy.types.Panel): class BIM_PT_railing(bpy.types.Panel): - bl_label = "IFC Railing" + bl_label = "Railing" bl_idname = "BIM_PT_railing" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -576,7 +576,7 @@ class BIM_PT_railing(bpy.types.Panel): class BIM_PT_roof(bpy.types.Panel): - bl_label = "IFC Roof" + bl_label = "Roof" bl_idname = "BIM_PT_roof" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -631,7 +631,7 @@ class BIM_PT_roof(bpy.types.Panel): class BIM_MT_model(Menu): bl_idname = "BIM_MT_model" - bl_label = "IFC Objects" + bl_label = "Objects" def draw(self, context): layout = self.layout diff --git a/src/blenderbim/blenderbim/bim/module/owner/ui.py b/src/blenderbim/blenderbim/bim/module/owner/ui.py index a9a5404e4e..fb515deb58 100644 --- a/src/blenderbim/blenderbim/bim/module/owner/ui.py +++ b/src/blenderbim/blenderbim/bim/module/owner/ui.py @@ -77,7 +77,7 @@ def draw_addresses(box, parent): class BIM_PT_people(bpy.types.Panel): - bl_label = "IFC People" + bl_label = "People" bl_idname = "BIM_PT_people" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -136,7 +136,7 @@ class BIM_PT_people(bpy.types.Panel): class BIM_PT_organisations(bpy.types.Panel): - bl_label = "IFC Organisations" + bl_label = "Organisations" bl_idname = "BIM_PT_organisations" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -183,7 +183,7 @@ class BIM_PT_organisations(bpy.types.Panel): class BIM_PT_owner(bpy.types.Panel): - bl_label = "IFC Owner History" + bl_label = "Owner History" bl_idname = "BIM_PT_owner" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -234,7 +234,7 @@ class BIM_PT_owner(bpy.types.Panel): class BIM_PT_actor(bpy.types.Panel): - bl_label = "IFC Actor" + bl_label = "Actor" bl_idname = "BIM_PT_actor" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -284,7 +284,7 @@ class BIM_PT_actor(bpy.types.Panel): class BIM_PT_object_actor(bpy.types.Panel): - bl_label = "IFC Actor" + bl_label = "Actor" bl_idname = "BIM_PT_object_actor" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/patch/ui.py b/src/blenderbim/blenderbim/bim/module/patch/ui.py index 7b0416bd14..3a843da8f0 100644 --- a/src/blenderbim/blenderbim/bim/module/patch/ui.py +++ b/src/blenderbim/blenderbim/bim/module/patch/ui.py @@ -23,7 +23,7 @@ from blenderbim.bim.helper import draw_attributes class BIM_PT_patch(bpy.types.Panel): - bl_label = "IFC Patch" + bl_label = "Patch" bl_idname = "BIM_PT_patch" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/profile/ui.py b/src/blenderbim/blenderbim/bim/module/profile/ui.py index 4caf801b6f..5f4b998c98 100644 --- a/src/blenderbim/blenderbim/bim/module/profile/ui.py +++ b/src/blenderbim/blenderbim/bim/module/profile/ui.py @@ -25,7 +25,7 @@ from blenderbim.bim.module.profile.prop import generate_thumbnail_for_active_pro class BIM_PT_profiles(Panel): - bl_label = "IFC Profiles" + bl_label = "Profiles" bl_idname = "BIM_PT_profiles" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/project/ui.py b/src/blenderbim/blenderbim/bim/module/project/ui.py index 6327df9f61..34fb692b07 100644 --- a/src/blenderbim/blenderbim/bim/module/project/ui.py +++ b/src/blenderbim/blenderbim/bim/module/project/ui.py @@ -76,7 +76,7 @@ def file_menu(self, context): class BIM_PT_project(Panel): - bl_label = "IFC Project" + bl_label = "Current Project" bl_idname = "BIM_PT_project" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -235,7 +235,7 @@ class BIM_PT_project(Panel): class BIM_PT_project_library(Panel): - bl_label = "IFC Project Library" + bl_label = "Project Library" bl_idname = "BIM_PT_project_library" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -277,7 +277,7 @@ class BIM_PT_project_library(Panel): class BIM_PT_links(Panel): - bl_label = "IFC Links" + bl_label = "Links" bl_idname = "BIM_PT_links" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/pset/ui.py b/src/blenderbim/blenderbim/bim/module/pset/ui.py index 6eb1ab8419..105e9824f2 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/ui.py +++ b/src/blenderbim/blenderbim/bim/module/pset/ui.py @@ -170,7 +170,7 @@ def draw_psetqto_editable_ui(box, props, prop): class BIM_PT_object_psets(Panel): - bl_label = "IFC Object Property Sets" + bl_label = "Object Property Sets" bl_idname = "BIM_PT_object_psets" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -212,7 +212,7 @@ class BIM_PT_object_psets(Panel): class BIM_PT_object_qtos(Panel): - bl_label = "IFC Object Quantity Sets" + bl_label = "Object Quantity Sets" bl_idname = "BIM_PT_object_qtos" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -249,7 +249,7 @@ class BIM_PT_object_qtos(Panel): class BIM_PT_material_psets(Panel): - bl_label = "IFC Material Property Sets" + bl_label = "Material Property Sets" bl_idname = "BIM_PT_material_psets" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -293,7 +293,7 @@ class BIM_PT_material_psets(Panel): class BIM_PT_material_set_psets(Panel): - bl_label = "IFC Material Set Property Sets" + bl_label = "Material Set Property Sets" bl_idname = "BIM_PT_material_set_psets" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -330,7 +330,7 @@ class BIM_PT_material_set_psets(Panel): class BIM_PT_material_set_item_psets(Panel): - bl_label = "IFC Material Set Item Property Sets" + bl_label = "Material Set Item Property Sets" bl_idname = "BIM_PT_material_set_item_psets" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -367,7 +367,7 @@ class BIM_PT_material_set_item_psets(Panel): class BIM_PT_task_qtos(Panel): - bl_label = "IFC Task Quantity Sets" + bl_label = "Task Quantity Sets" bl_idname = "BIM_PT_task_qtos" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -403,7 +403,7 @@ class BIM_PT_task_qtos(Panel): class BIM_PT_resource_qtos(Panel): - bl_label = "IFC Resource Quantity Sets" + bl_label = "Resource Quantity Sets" bl_idname = "BIM_PT_resource_qtos" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -437,7 +437,7 @@ class BIM_PT_resource_qtos(Panel): class BIM_PT_resource_psets(Panel): - bl_label = "IFC Resource Property Sets" + bl_label = "Resource Property Sets" bl_idname = "BIM_PT_resource_psets" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -471,7 +471,7 @@ class BIM_PT_resource_psets(Panel): class BIM_PT_profile_psets(Panel): - bl_label = "IFC Profile Property Sets" + bl_label = "Profile Property Sets" bl_idname = "BIM_PT_profile_psets" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -507,7 +507,7 @@ class BIM_PT_profile_psets(Panel): class BIM_PT_work_schedule_psets(Panel): - bl_label = "IFC Work Schedule Property Sets" + bl_label = "Work Schedule Property Sets" bl_idname = "BIM_PT_work_schedule_psets" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -539,7 +539,7 @@ class BIM_PT_work_schedule_psets(Panel): class BIM_PT_bulk_property_editor(Panel): - bl_label = "IFC Bulk Property Editor" + bl_label = "Bulk Property Editor" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "object" diff --git a/src/blenderbim/blenderbim/bim/module/pset_template/ui.py b/src/blenderbim/blenderbim/bim/module/pset_template/ui.py index 324aee459d..969a355ed3 100644 --- a/src/blenderbim/blenderbim/bim/module/pset_template/ui.py +++ b/src/blenderbim/blenderbim/bim/module/pset_template/ui.py @@ -23,7 +23,7 @@ from blenderbim.bim.module.pset_template.data import PsetTemplatesData class BIM_PT_pset_template(Panel): - bl_label = "IFC Property Set Templates" + bl_label = "Property Set Templates" bl_idname = "BIM_PT_pset_template" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/resource/ui.py b/src/blenderbim/blenderbim/bim/module/resource/ui.py index 433c0e73e1..a3ecdc92d9 100644 --- a/src/blenderbim/blenderbim/bim/module/resource/ui.py +++ b/src/blenderbim/blenderbim/bim/module/resource/ui.py @@ -23,7 +23,7 @@ from blenderbim.bim.module.resource.data import ResourceData class BIM_PT_resources(Panel): - bl_label = "IFC Resources" + bl_label = "Resources" bl_idname = "BIM_PT_resources" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/root/ui.py b/src/blenderbim/blenderbim/bim/module/root/ui.py index 2004f7ed08..1db3a14e9b 100644 --- a/src/blenderbim/blenderbim/bim/module/root/ui.py +++ b/src/blenderbim/blenderbim/bim/module/root/ui.py @@ -25,12 +25,13 @@ from blenderbim.bim.module.root.data import IfcClassData class BIM_PT_class(Panel): - bl_label = "IFC Class" + bl_label = "Class" bl_idname = "BIM_PT_class" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "object" bl_parent_id = "BIM_PT_object_metadata" + bl_options = {"HIDE_HEADER"} @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/module/search/ui.py b/src/blenderbim/blenderbim/bim/module/search/ui.py index b84c821cfa..fc9b016012 100644 --- a/src/blenderbim/blenderbim/bim/module/search/ui.py +++ b/src/blenderbim/blenderbim/bim/module/search/ui.py @@ -22,7 +22,7 @@ from blenderbim.bim.ifc import IfcStore class BIM_PT_search(Panel): - bl_label = "IFC Search" + bl_label = "Search" bl_idname = "BIM_PT_search" # bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -109,7 +109,7 @@ class BIM_UL_ifc_building_storey_filter(bpy.types.UIList): class BIM_PT_IFCSelector(Panel): - bl_label = "IFC Selector" + bl_label = "Selector" bl_idname = "BIM_PT_ifc_selector" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index 79e7191b6a..8d8d9f7afd 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -25,7 +25,7 @@ from blenderbim.bim.module.sequence.data import WorkPlansData, WorkScheduleData, class BIM_PT_work_plans(Panel): - bl_label = "IFC Work Plans" + bl_label = "Work Plans" bl_idname = "BIM_PT_work_plans" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -97,7 +97,7 @@ class BIM_PT_work_plans(Panel): class BIM_PT_work_schedules(Panel): - bl_label = "IFC Work Schedules" + bl_label = "Work Schedules" bl_idname = "BIM_PT_work_schedules" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -526,7 +526,7 @@ class BIM_PT_work_schedules(Panel): class BIM_PT_task_icom(Panel): - bl_label = "IFC Task ICOM" + bl_label = "Task ICOM" bl_idname = "BIM_PT_task_icom" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -811,7 +811,7 @@ class BIM_UL_tasks(UIList): class BIM_PT_work_calendars(Panel): - bl_label = "IFC Work Calendars" + bl_label = "Work Calendars" bl_idname = "BIM_PT_work_calendars" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/spatial/ui.py b/src/blenderbim/blenderbim/bim/module/spatial/ui.py index e6d0c62432..d40297e1a5 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/ui.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/ui.py @@ -23,7 +23,7 @@ import blenderbim.tool as tool class BIM_PT_spatial(Panel): - bl_label = "IFC Spatial Container" + bl_label = "Spatial Container" bl_idname = "BIM_PT_spatial" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -97,7 +97,7 @@ class BIM_UL_containers(UIList): class BIM_PT_SpatialManager(Panel): - bl_label = "IFC Spatial Manager" + bl_label = "Spatial Manager" bl_idname = "BIM_PT_SpatialManager" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" diff --git a/src/blenderbim/blenderbim/bim/module/structural/ui.py b/src/blenderbim/blenderbim/bim/module/structural/ui.py index bee2b9f25e..cfb3559c9f 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/ui.py +++ b/src/blenderbim/blenderbim/bim/module/structural/ui.py @@ -86,7 +86,7 @@ def draw_boundary_condition_read_only_ui(layout, boundary_condition): class BIM_PT_structural_boundary_conditions(Panel): - bl_label = "IFC Structural Boundary Conditions" + bl_label = "Structural Boundary Conditions" bl_idname = "BIM_PT_structural_boundary_conditions" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -121,7 +121,7 @@ class BIM_PT_structural_boundary_conditions(Panel): class BIM_PT_connected_structural_members(Panel): - bl_label = "IFC Connected Structural Members" + bl_label = "Connected Structural Members" bl_idname = "BIM_PT_connected_structural_members" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -174,7 +174,7 @@ class BIM_PT_connected_structural_members(Panel): class BIM_PT_structural_member(Panel): - bl_label = "IFC Structural Member" + bl_label = "Structural Member" bl_idname = "BIM_PT_structural_member" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -217,7 +217,7 @@ class BIM_PT_structural_member(Panel): class BIM_PT_structural_connection(Panel): - bl_label = "IFC Structural Connection" + bl_label = "Structural Connection" bl_idname = "BIM_PT_structural_connection" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -277,7 +277,7 @@ class BIM_PT_structural_connection(Panel): class BIM_PT_structural_analysis_models(Panel): - bl_label = "IFC Structural Analysis Models" + bl_label = "Structural Analysis Models" bl_idname = "BIM_PT_structural_analysis_models" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -352,7 +352,7 @@ class BIM_UL_structural_analysis_models(UIList): class BIM_PT_structural_load_cases(Panel): - bl_label = "IFC Structural Load Cases" + bl_label = "Structural Load Cases" bl_idname = "BIM_PT_structural_load_cases" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -448,7 +448,7 @@ class BIM_UL_structural_activities(UIList): class BIM_PT_structural_loads(Panel): - bl_label = "IFC Structural Loads" + bl_label = "Structural Loads" bl_idname = "BIM_PT_structural_loads" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -518,7 +518,7 @@ class BIM_UL_structural_loads(UIList): class BIM_PT_boundary_conditions(Panel): - bl_label = "IFC Boundary Conditions" + bl_label = "Boundary Conditions" bl_idname = "BIM_PT_boundary_conditions" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/style/ui.py b/src/blenderbim/blenderbim/bim/module/style/ui.py index 3ebbe20810..bb266d9542 100644 --- a/src/blenderbim/blenderbim/bim/module/style/ui.py +++ b/src/blenderbim/blenderbim/bim/module/style/ui.py @@ -26,7 +26,7 @@ from blenderbim.tool.style import TEXTURE_MAPS_BY_METHODS, STYLE_TEXTURE_PROPS_M class BIM_PT_styles(Panel): - bl_label = "IFC Styles" + bl_label = "Styles" bl_idname = "BIM_PT_styles" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -86,7 +86,7 @@ def draw_style_ui(self, context): class BIM_PT_style(MaterialButtonsPanel, Panel): - bl_label = "IFC Style" + bl_label = "Style" bl_idname = "BIM_PT_style" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -111,7 +111,7 @@ class BIM_PT_style(MaterialButtonsPanel, Panel): class BIM_PT_style_attributes(Panel): - bl_label = "IFC Style Attributes" + bl_label = "Style Attributes" bl_idname = "BIM_PT_style_attributes" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -159,7 +159,7 @@ class BIM_PT_style_attributes(Panel): class BIM_PT_external_style_attributes(Panel): - bl_label = "IFC External Surface Style" + bl_label = "External Surface Style" bl_idname = "BIM_PT_external_style_attributes" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -226,7 +226,7 @@ class BIM_UL_styles(UIList): class BIM_PT_STYLE_GRAPH(Panel): bl_idname = "BIM_PT_style_graph" bl_space_type = "NODE_EDITOR" - bl_label = "IFC Style Graph Settings" + bl_label = "Style Graph Settings" bl_region_type = "UI" bl_category = "BBIM" diff --git a/src/blenderbim/blenderbim/bim/module/system/ui.py b/src/blenderbim/blenderbim/bim/module/system/ui.py index 5db712324f..7436b71c4a 100644 --- a/src/blenderbim/blenderbim/bim/module/system/ui.py +++ b/src/blenderbim/blenderbim/bim/module/system/ui.py @@ -24,7 +24,7 @@ from blenderbim.bim.module.system.data import SystemData, ObjectSystemData, Port class BIM_PT_systems(Panel): - bl_label = "IFC Systems" + bl_label = "Systems" bl_idname = "BIM_PT_systems" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -74,7 +74,7 @@ class BIM_PT_systems(Panel): class BIM_PT_object_systems(Panel): - bl_label = "IFC Systems" + bl_label = "Systems" bl_idname = "BIM_PT_object_systems" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -130,7 +130,7 @@ class BIM_PT_object_systems(Panel): class BIM_PT_ports(Panel): - bl_label = "IFC Ports" + bl_label = "Ports" bl_idname = "BIM_PT_ports" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -163,7 +163,7 @@ class BIM_PT_ports(Panel): class BIM_PT_port(Panel): - bl_label = "IFC Port" + bl_label = "Port" bl_idname = "BIM_PT_port" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/type/ui.py b/src/blenderbim/blenderbim/bim/module/type/ui.py index d7feaf34bb..88d2c99caa 100644 --- a/src/blenderbim/blenderbim/bim/module/type/ui.py +++ b/src/blenderbim/blenderbim/bim/module/type/ui.py @@ -25,7 +25,7 @@ from blenderbim.bim.module.type.data import TypeData class BIM_PT_type(Panel): - bl_label = "IFC Type" + bl_label = "Type" bl_idname = "BIM_PT_type" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" diff --git a/src/blenderbim/blenderbim/bim/module/unit/ui.py b/src/blenderbim/blenderbim/bim/module/unit/ui.py index 10a3682296..19d45eb825 100644 --- a/src/blenderbim/blenderbim/bim/module/unit/ui.py +++ b/src/blenderbim/blenderbim/bim/module/unit/ui.py @@ -24,7 +24,7 @@ from blenderbim.bim.module.unit.data import UnitsData class BIM_PT_units(Panel): - bl_label = "IFC Units" + bl_label = "Units" bl_idname = "BIM_PT_units" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/blenderbim/blenderbim/bim/module/void/ui.py b/src/blenderbim/blenderbim/bim/module/void/ui.py index d4a4bdea15..f9630af001 100644 --- a/src/blenderbim/blenderbim/bim/module/void/ui.py +++ b/src/blenderbim/blenderbim/bim/module/void/ui.py @@ -23,7 +23,7 @@ from blenderbim.bim.module.void.data import BooleansData, VoidsData class BIM_PT_voids(Panel): - bl_label = "IFC Voids" + bl_label = "Voids" bl_idname = "BIM_PT_voids" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -79,7 +79,7 @@ class BIM_PT_voids(Panel): class BIM_PT_booleans(Panel): - bl_label = "IFC Booleans" + bl_label = "Booleans" bl_idname = "BIM_PT_booleans" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" diff --git a/src/blenderbim/blenderbim/bim/ui.py b/src/blenderbim/blenderbim/bim/ui.py index 0000d17aeb..92c3686a21 100644 --- a/src/blenderbim/blenderbim/bim/ui.py +++ b/src/blenderbim/blenderbim/bim/ui.py @@ -283,7 +283,7 @@ class BIM_PT_root(Panel): aprops = context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)] row = self.layout.row() - row.scale_y = 0.5 # Yes, we actually do this. + row.scale_y = 0.75 # Yes, we actually do this. row.operator( "bim.set_tab", text="", emboss=False, icon_value=blenderbim.bim.icons["IFC"].icon_id ).tab = "PROJECT" @@ -327,7 +327,7 @@ class BIM_PT_root(Panel): class BIM_PT_project_info(Panel): - bl_label = "IFC Project Info" + bl_label = "Project Info" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" @@ -341,7 +341,7 @@ class BIM_PT_project_info(Panel): class BIM_PT_project_setup(Panel): - bl_label = "IFC Project Setup" + bl_label = "Project Setup" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" @@ -356,7 +356,7 @@ class BIM_PT_project_setup(Panel): class BIM_PT_collaboration(Panel): - bl_label = "IFC Collaboration" + bl_label = "Collaboration" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" @@ -371,7 +371,7 @@ class BIM_PT_collaboration(Panel): class BIM_PT_selection(Panel): - bl_label = "IFC Selection" + bl_label = "Selection" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" @@ -386,7 +386,7 @@ class BIM_PT_selection(Panel): class BIM_PT_geometry(Panel): - bl_label = "IFC Geometry" + bl_label = "Geometry" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" @@ -401,7 +401,7 @@ class BIM_PT_geometry(Panel): class BIM_PT_4D5D(Panel): - bl_label = "IFC Costing and Scheduling" + bl_label = "Costing and Scheduling" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" @@ -416,7 +416,7 @@ class BIM_PT_4D5D(Panel): class BIM_PT_structural(Panel): - bl_label = "IFC Structural" + bl_label = "Structural" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" @@ -431,7 +431,7 @@ class BIM_PT_structural(Panel): class BIM_PT_services(Panel): - bl_label = "IFC Services" + bl_label = "Services" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" @@ -446,7 +446,7 @@ class BIM_PT_services(Panel): class BIM_PT_quality_control(Panel): - bl_label = "IFC Quality Control" + bl_label = "Quality Control" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" @@ -477,7 +477,7 @@ class BIM_PT_integrations(Panel): # Object panel groups class BIM_PT_object_metadata(Panel): - bl_label = "IFC Object Metadata" + bl_label = "Object Metadata" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" @@ -492,7 +492,7 @@ class BIM_PT_object_metadata(Panel): class BIM_PT_geometry_object(Panel): - bl_label = "IFC Geometry" + bl_label = "Geometry" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" @@ -508,7 +508,7 @@ class BIM_PT_geometry_object(Panel): class BIM_PT_services_object(Panel): - bl_label = "IFC Services" + bl_label = "Services" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" @@ -524,7 +524,7 @@ class BIM_PT_services_object(Panel): class BIM_PT_utilities_object(Panel): - bl_label = "IFC Utilities" + bl_label = "Utilities" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" @@ -540,7 +540,7 @@ class BIM_PT_utilities_object(Panel): class BIM_PT_misc_object(Panel): - bl_label = "IFC Misc." + bl_label = "Misc." bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" From 844cc629607bbd02488334310cf0df9e034b860c Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 15 Jul 2023 21:47:35 +1000 Subject: [PATCH 27/49] More panel shuffling, new Quality and Coordination tab. Geometry is now part of materials. --- src/blenderbim/blenderbim/bim/__init__.py | 1 - .../blenderbim/bim/module/brick/ui.py | 3 +- .../blenderbim/bim/module/cobie/ui.py | 7 +++- .../blenderbim/bim/module/group/ui.py | 2 +- .../blenderbim/bim/module/material/ui.py | 6 ++- .../blenderbim/bim/module/pset/ui.py | 2 +- src/blenderbim/blenderbim/bim/prop.py | 4 +- src/blenderbim/blenderbim/bim/ui.py | 42 ++++--------------- 8 files changed, 23 insertions(+), 44 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/__init__.py b/src/blenderbim/blenderbim/bim/__init__.py index c82c820201..61744b71ef 100644 --- a/src/blenderbim/blenderbim/bim/__init__.py +++ b/src/blenderbim/blenderbim/bim/__init__.py @@ -142,7 +142,6 @@ classes = [ ui.BIM_PT_object_metadata, ui.BIM_PT_geometry_object, ui.BIM_PT_services_object, - ui.BIM_PT_utilities_object, ui.BIM_PT_misc_object, ] diff --git a/src/blenderbim/blenderbim/bim/module/brick/ui.py b/src/blenderbim/blenderbim/bim/module/brick/ui.py index 0f1983da8d..18dba36b29 100644 --- a/src/blenderbim/blenderbim/bim/module/brick/ui.py +++ b/src/blenderbim/blenderbim/bim/module/brick/ui.py @@ -26,14 +26,13 @@ from blenderbim.tool.brick import BrickStore class BIM_PT_brickschema(Panel): bl_label = "Brickschema Project" bl_idname = "BIM_PT_brickschema" - bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" @classmethod def poll(cls, context): - return tool.Blender.is_tab(context, "OTHER") + return tool.Blender.is_tab(context, "FM") def draw(self, context): if not BrickschemaData.is_loaded: diff --git a/src/blenderbim/blenderbim/bim/module/cobie/ui.py b/src/blenderbim/blenderbim/bim/module/cobie/ui.py index 333925fd29..65396d7fed 100644 --- a/src/blenderbim/blenderbim/bim/module/cobie/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cobie/ui.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . +import blenderbim.tool as tool from bpy.types import Panel from blenderbim.bim.ifc import IfcStore @@ -23,11 +24,13 @@ from blenderbim.bim.ifc import IfcStore class BIM_PT_cobie(Panel): bl_label = "COBie" bl_idname = "BIM_PT_cobie" - bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_collaboration" + + @classmethod + def poll(cls, context): + return tool.Blender.is_tab(context, "FM") def draw(self, context): layout = self.layout diff --git a/src/blenderbim/blenderbim/bim/module/group/ui.py b/src/blenderbim/blenderbim/bim/module/group/ui.py index ff5acd2c12..10b33693ce 100644 --- a/src/blenderbim/blenderbim/bim/module/group/ui.py +++ b/src/blenderbim/blenderbim/bim/module/group/ui.py @@ -76,7 +76,7 @@ class BIM_PT_object_groups(Panel): bl_region_type = "WINDOW" bl_context = "object" bl_order = 1 - bl_parent_id = "BIM_PT_utilities_object" + bl_parent_id = "BIM_PT_misc_object" @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/module/material/ui.py b/src/blenderbim/blenderbim/bim/module/material/ui.py index 139a19f9ff..2cce58c0a6 100644 --- a/src/blenderbim/blenderbim/bim/module/material/ui.py +++ b/src/blenderbim/blenderbim/bim/module/material/ui.py @@ -17,6 +17,7 @@ # along with BlenderBIM Add-on. If not, see . import blenderbim.bim.helper +import blenderbim.tool as tool from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore from blenderbim.bim.helper import draw_attributes @@ -115,11 +116,12 @@ class BIM_PT_object_material(Panel): bl_idname = "BIM_PT_object_material" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" - bl_context = "object" - bl_parent_id = "BIM_PT_object_metadata" + bl_context = "scene" @classmethod def poll(cls, context): + if not tool.Blender.is_tab(context, "GEOMETRY"): + return False if not context.active_object: return False props = context.active_object.BIMObjectProperties diff --git a/src/blenderbim/blenderbim/bim/module/pset/ui.py b/src/blenderbim/blenderbim/bim/module/pset/ui.py index 105e9824f2..932c89d6bc 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/ui.py +++ b/src/blenderbim/blenderbim/bim/module/pset/ui.py @@ -544,7 +544,7 @@ class BIM_PT_bulk_property_editor(Panel): bl_region_type = "WINDOW" bl_context = "object" bl_options = {"DEFAULT_CLOSED"} - bl_parent_id = "BIM_PT_utilities_object" + bl_parent_id = "BIM_PT_misc_object" def draw(self, context): pass diff --git a/src/blenderbim/blenderbim/bim/prop.py b/src/blenderbim/blenderbim/bim/prop.py index fcd2140ec9..cf8c806b38 100644 --- a/src/blenderbim/blenderbim/bim/prop.py +++ b/src/blenderbim/blenderbim/bim/prop.py @@ -345,13 +345,13 @@ def get_tab(self, context): return [ ("PROJECT", "Project Overview", "", blenderbim.bim.icons["IFC"].icon_id, 0), ("OBJECT", "Object Information", "", "FILE_3D", 1), - ("MATERIALS", "Materials and Styles", "", "MATERIAL", 2), + ("GEOMETRY", "Geometry and Materials", "", "MATERIAL", 2), ("DRAWINGS", "Drawings and Documents", "", "DOCUMENTS", 3), ("SERVICES", "Services and Systems", "", "NETWORK_DRIVE", 4), ("STRUCTURE", "Structural Analysis", "", "EDITMODE_HLT", 5), ("SCHEDULING", "Construction Scheduling", "", "NLA", 6), ("FM", "Facility Management", "", "PACKAGE", 7), - ("OTHER", "Other Utilities", "", "COLLAPSEMENU", 8), + ("QUALITY", "Quality and Coordination", "", "COMMUNITY", 8), ("BLENDER", "Blender Properties", "", "BLENDER", 9), ] diff --git a/src/blenderbim/blenderbim/bim/ui.py b/src/blenderbim/blenderbim/bim/ui.py index 92c3686a21..368b1b26e1 100644 --- a/src/blenderbim/blenderbim/bim/ui.py +++ b/src/blenderbim/blenderbim/bim/ui.py @@ -288,13 +288,13 @@ class BIM_PT_root(Panel): "bim.set_tab", text="", emboss=False, icon_value=blenderbim.bim.icons["IFC"].icon_id ).tab = "PROJECT" row.operator("bim.set_tab", text="", emboss=False, icon="FILE_3D").tab = "OBJECT" - row.operator("bim.set_tab", text="", emboss=False, icon="MATERIAL").tab = "MATERIALS" + row.operator("bim.set_tab", text="", emboss=False, icon="MATERIAL").tab = "GEOMETRY" row.operator("bim.set_tab", text="", emboss=False, icon="DOCUMENTS").tab = "DRAWINGS" row.operator("bim.set_tab", text="", emboss=False, icon="NETWORK_DRIVE").tab = "SERVICES" row.operator("bim.set_tab", text="", emboss=False, icon="EDITMODE_HLT").tab = "STRUCTURE" row.operator("bim.set_tab", text="", emboss=False, icon="NLA").tab = "SCHEDULING" row.operator("bim.set_tab", text="", emboss=False, icon="PACKAGE").tab = "FM" - row.operator("bim.set_tab", text="", emboss=False, icon="COLLAPSEMENU").tab = "OTHER" + row.operator("bim.set_tab", text="", emboss=False, icon="COMMUNITY").tab = "QUALITY" row.operator("bim.set_tab", text="", emboss=False, icon="BLENDER").tab = "BLENDER" row.operator("bim.switch_tab", text="", emboss=False, icon="UV_SYNC_SELECT") @@ -304,13 +304,13 @@ class BIM_PT_root(Panel): for tab in [ "PROJECT", "OBJECT", - "MATERIALS", + "GEOMETRY", "DRAWINGS", "SERVICES", "STRUCTURE", "SCHEDULING", "FM", - "OTHER", + "QUALITY", "BLENDER", "SWITCH", ]: @@ -360,11 +360,10 @@ class BIM_PT_collaboration(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_options = {"DEFAULT_CLOSED"} @classmethod def poll(cls, context): - return tool.Blender.is_tab(context, "OTHER") + return tool.Blender.is_tab(context, "QUALITY") def draw(self, context): pass @@ -390,7 +389,6 @@ class BIM_PT_geometry(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_options = {"DEFAULT_CLOSED"} @classmethod def poll(cls, context): @@ -405,7 +403,6 @@ class BIM_PT_4D5D(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_options = {"DEFAULT_CLOSED"} @classmethod def poll(cls, context): @@ -420,7 +417,6 @@ class BIM_PT_structural(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_options = {"DEFAULT_CLOSED"} @classmethod def poll(cls, context): @@ -435,7 +431,6 @@ class BIM_PT_services(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_options = {"DEFAULT_CLOSED"} @classmethod def poll(cls, context): @@ -450,11 +445,10 @@ class BIM_PT_quality_control(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_options = {"DEFAULT_CLOSED"} @classmethod def poll(cls, context): - return tool.Blender.is_tab(context, "OTHER") + return tool.Blender.is_tab(context, "QUALITY") def draw(self, context): pass @@ -469,7 +463,7 @@ class BIM_PT_integrations(Panel): @classmethod def poll(cls, context): - return tool.Blender.is_tab(context, "OTHER") + return tool.Blender.is_tab(context, "QUALITY") def draw(self, context): pass @@ -497,11 +491,10 @@ class BIM_PT_geometry_object(Panel): bl_region_type = "WINDOW" bl_context = "scene" bl_order = 1 - bl_options = {"DEFAULT_CLOSED"} @classmethod def poll(cls, context): - return tool.Blender.is_tab(context, "OBJECT") and tool.Ifc.get() + return tool.Blender.is_tab(context, "GEOMETRY") and tool.Ifc.get() def draw(self, context): pass @@ -513,27 +506,10 @@ class BIM_PT_services_object(Panel): bl_region_type = "WINDOW" bl_context = "scene" bl_order = 1 - bl_options = {"DEFAULT_CLOSED"} @classmethod def poll(cls, context): - return tool.Blender.is_tab(context, "OBJECT") and tool.Ifc.get() - - def draw(self, context): - pass - - -class BIM_PT_utilities_object(Panel): - bl_label = "Utilities" - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "scene" - bl_order = 1 - bl_options = {"DEFAULT_CLOSED"} - - @classmethod - def poll(cls, context): - return tool.Blender.is_tab(context, "OBJECT") and tool.Ifc.get() + return tool.Blender.is_tab(context, "SERVICES") and tool.Ifc.get() def draw(self, context): pass From 944f2f701a21f521805dc638a7703d2a1026e44e Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 16 Jul 2023 22:58:56 +1000 Subject: [PATCH 28/49] Fix #3288. Implement undo / redo until specified named transaction. Fix fundamental undo bug where Blender doesn't track the undo UUID property if you toggle edit mode. This also has the nice side effect of implementing "Jump to point in history" so you can now use the Edit > Undo History menu. --- src/blenderbim/blenderbim/bim/handler.py | 4 +-- src/blenderbim/blenderbim/bim/ifc.py | 34 +++++++++++++++++------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/handler.py b/src/blenderbim/blenderbim/bim/handler.py index c8147ac556..27e9f34c7a 100644 --- a/src/blenderbim/blenderbim/bim/handler.py +++ b/src/blenderbim/blenderbim/bim/handler.py @@ -209,7 +209,7 @@ def loadIfcStore(scene): def undo_post(scene): if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction: IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction - IfcStore.undo() + IfcStore.undo(until_key=bpy.context.scene.BIMProperties.last_transaction) purge_module_data() tool.Ifc.rebuild_element_maps() @@ -218,7 +218,7 @@ def undo_post(scene): def redo_post(scene): if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction: IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction - IfcStore.redo() + IfcStore.redo(until_key=bpy.context.scene.BIMProperties.last_transaction) purge_module_data() tool.Ifc.rebuild_element_maps() diff --git a/src/blenderbim/blenderbim/bim/ifc.py b/src/blenderbim/blenderbim/bim/ifc.py index 4104157c5f..c94fb009a3 100644 --- a/src/blenderbim/blenderbim/bim/ifc.py +++ b/src/blenderbim/blenderbim/bim/ifc.py @@ -366,21 +366,35 @@ class IfcStore: IfcStore.future = [] @staticmethod - def undo(): + def undo(until_key=None): BrickStore.undo() if not IfcStore.history: return - event = IfcStore.history.pop() - for transaction in event["operations"][::-1]: - transaction["rollback"](transaction["data"]) - IfcStore.future.append(event) + + while IfcStore.history: + if IfcStore.history[-1]["key"] == until_key: + return + + event = IfcStore.history.pop() + for transaction in event["operations"][::-1]: + transaction["rollback"](transaction["data"]) + IfcStore.future.append(event) @staticmethod - def redo(): + def redo(until_key=None): BrickStore.redo() + if not IfcStore.future: return - event = IfcStore.future.pop() - for transaction in event["operations"]: - transaction["commit"](transaction["data"]) - IfcStore.history.append(event) + + has_encountered_key = False + while IfcStore.future: + if has_encountered_key and IfcStore.future[-1]["key"] != until_key: + return + elif IfcStore.future[-1]["key"] == until_key: + has_encountered_key = True + + event = IfcStore.future.pop() + for transaction in event["operations"]: + transaction["commit"](transaction["data"]) + IfcStore.history.append(event) From 0500e666bd37fc88bb656f3b44615fbf24be7282 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 17 Jul 2023 11:48:09 +1000 Subject: [PATCH 29/49] Add support for arbitrary point annotations and survey point, traverse points, control points, spot elevations, etc. --- .../blenderbim/bim/data/assets/symbols.svg | 13 +++++++++ .../bim/module/drawing/svgwriter.py | 27 ++++++++++++++++--- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/data/assets/symbols.svg b/src/blenderbim/blenderbim/bim/data/assets/symbols.svg index a657f279c0..efcf0ddd77 100644 --- a/src/blenderbim/blenderbim/bim/data/assets/symbols.svg +++ b/src/blenderbim/blenderbim/bim/data/assets/symbols.svg @@ -41,4 +41,17 @@ + + + + + + + + + + + + + diff --git a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py index d88375283b..9248017edb 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py @@ -332,9 +332,10 @@ class SvgWriter: return classes = self.get_attribute_classes(obj) - if len(obj.data.polygons) == 0: - self.draw_edge_annotation(obj, classes) - return + if len(obj.data.vertices) and not len(obj.data.edges): + return self.draw_point_annotation(obj, classes) + elif len(obj.data.polygons) == 0: + return self.draw_edge_annotation(obj, classes) bm = bmesh.new() bm.from_mesh(obj.data) @@ -837,6 +838,26 @@ class SvgWriter: self.svg.add(tag) line_number += len(tag.elements) + def draw_point_annotation(self, obj, classes): + x_offset = self.raw_width / 2 + y_offset = self.raw_height / 2 + + matrix_world = obj.matrix_world + projected_points = [self.project_point_onto_camera(matrix_world @ v.co) for v in obj.data.vertices] + + element = tool.Ifc.get_entity(obj) + svg_id = str(ifcopenshell.util.element.get_predefined_type(element)) + + # EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660. + point_type = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType") + if point_type: + svg_id += f"-{point_type}" + + for symbol_position in projected_points: + symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y))) + symbol_position_svg = symbol_position * self.svg_scale + self.svg.add(self.svg.use(f"#{svg_id}", insert=symbol_position_svg)) + def draw_break_annotations(self, obj): x_offset = self.raw_width / 2 y_offset = self.raw_height / 2 From 80d88b38211b1330b0a092e24f5873efaa4b3882 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 17 Jul 2023 14:32:05 +1000 Subject: [PATCH 30/49] Fix #3275. You can now rename annotation types from the annotation manager. --- src/blenderbim/blenderbim/bim/module/drawing/workspace.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/workspace.py b/src/blenderbim/blenderbim/bim/module/drawing/workspace.py index 3193a23271..b4ead1321d 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/workspace.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/workspace.py @@ -101,6 +101,8 @@ class LaunchAnnotationTypeManager(bpy.types.Operator): op = row.operator("bim.select_type", icon="OBJECT_DATA") op.relating_type = relating_type["id"] + op = row.operator("bim.rename_type", icon="GREASEPENCIL", text="") + op.element = relating_type["id"] op = row.operator("bim.duplicate_type", icon="DUPLICATE", text="") op.element = relating_type["id"] op = row.operator("bim.remove_type", icon="X", text="") From 17230e417072507651cc29f26f4a8554b0a1bedf Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 17 Jul 2023 15:46:33 +1000 Subject: [PATCH 31/49] Fix bug where loaded point geometry had unlinked meshes or placements with wrong units. --- src/blenderbim/blenderbim/bim/import_ifc.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index 23b43a567d..2a1bc11511 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -692,7 +692,7 @@ class IfcImporter: mesh = self.create_native_faceted_brep(element, mesh_name) elif native_data["type"] == "IfcFaceBasedSurfaceModel": mesh = self.create_native_faceted_brep(element, mesh_name) - mesh.BIMMeshProperties.ifc_definition_id = representation.id() + tool.Ifc.link(representation, mesh) mesh.name = mesh_name self.meshes[mesh_name] = mesh self.create_product(element, mesh=mesh) @@ -921,7 +921,7 @@ class IfcImporter: return result def create_pointcloud(self, product, representation): - placement_matrix = ifcopenshell.util.placement.get_local_placement(product.ObjectPlacement) + placement_matrix = self.get_element_matrix(product) vertex_list = [] for item in representation.Items: if item.is_a("IfcCartesianPointList"): @@ -937,6 +937,7 @@ class IfcImporter: mesh_name = f"{representation.ContextOfItems.id()}/{representation.id()}" mesh = bpy.data.meshes.new(mesh_name) mesh.from_pydata(vertex_list, [], []) + tool.Ifc.link(representation, mesh) obj = bpy.data.objects.new("{}/{}".format(product.is_a(), product.Name), mesh) self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, placement_matrix)) From e695bdbd0d411f84b6bd6c406beb91326dffa004 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 17 Jul 2023 15:47:10 +1000 Subject: [PATCH 32/49] Fix #3449. Fix bug where you couldn't edit the relating type of an annotation. --- src/blenderbim/blenderbim/bim/module/spatial/ui.py | 2 +- src/blenderbim/blenderbim/bim/module/type/data.py | 2 ++ src/blenderbim/blenderbim/bim/module/type/ui.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/spatial/ui.py b/src/blenderbim/blenderbim/bim/module/spatial/ui.py index d40297e1a5..ec66b77248 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/ui.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/ui.py @@ -72,7 +72,7 @@ class BIM_PT_spatial(Panel): if SpatialData.data["is_directly_contained"]: row.operator("bim.remove_container", icon="X", text="") else: - row.label(text="This object is not spatially contained") + row.label(text="No Spatial Container") row.operator("bim.enable_editing_container", icon="GREASEPENCIL", text="") for reference in SpatialData.data["references"]: row = self.layout.row() diff --git a/src/blenderbim/blenderbim/bim/module/type/data.py b/src/blenderbim/blenderbim/bim/module/type/data.py index 1d23c3363f..27a9ef5c13 100644 --- a/src/blenderbim/blenderbim/bim/module/type/data.py +++ b/src/blenderbim/blenderbim/bim/module/type/data.py @@ -56,6 +56,8 @@ class TypeData: return [] version = tool.Ifc.get_schema() types = ifcopenshell.util.type.get_applicable_types(element.is_a(), schema=version) + if element.is_a("IfcAnnotation"): + types.append("IfcTypeProduct") results.extend((t, t, get_entity_doc(version, t).get("description", "")) for t in types) return results diff --git a/src/blenderbim/blenderbim/bim/module/type/ui.py b/src/blenderbim/blenderbim/bim/module/type/ui.py index 88d2c99caa..ad3fdd92ce 100644 --- a/src/blenderbim/blenderbim/bim/module/type/ui.py +++ b/src/blenderbim/blenderbim/bim/module/type/ui.py @@ -92,7 +92,7 @@ class BIM_PT_type(Panel): row.operator("bim.enable_editing_type", icon="GREASEPENCIL", text="") row.operator("bim.unassign_type", icon="X", text="") else: - row.label(text="This object has no type") + row.label(text="No Relating Type") row.operator("bim.enable_editing_type", icon="GREASEPENCIL", text="") From 2463b779857d8d888f887c1d25479723b005c2c8 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 17 Jul 2023 11:32:12 +0500 Subject: [PATCH 33/49] Fixed #3441 --- src/blenderbim/blenderbim/bim/module/drawing/helper.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/helper.py b/src/blenderbim/blenderbim/bim/module/drawing/helper.py index 100880fb26..95bd35b77b 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/helper.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/helper.py @@ -192,6 +192,8 @@ def format_distance( tx_dist += str(feet) + "'" if feet and add_inches: tx_dist += " - " + if not feet and value < 0: + tx_dist += "-" if add_inches: tx_dist += str(inches) if add_inches and frac: From a101f46478a3ceb6aab0f491c7f2a363c8090365 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 17 Jul 2023 12:11:52 +0500 Subject: [PATCH 34/49] Fixing bugs on small revision clouds #3310 Bugs were: 1) svg print ZeroDivisionError if there was less than 1 pattern segment 2) svg drawing wasn't preserving edge direction if there was less than 1 pattern segment --- .../blenderbim/bim/module/drawing/svgwriter.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py index 9248017edb..6024aee181 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py @@ -502,9 +502,9 @@ class SvgWriter: return pattern def get_scale(size, direction): - vector = direction * size - shrinked_vector = size // segment_width * segment_width * direction - scale = [1 if vector[i] == 0 else vector[i] / shrinked_vector[i] for i in range(2)] + original_edge = direction * size + current_svg_segments = ceil(size / segment_width) * segment_width * direction + scale = [1 if original_edge[i] == 0 else original_edge[i] / current_svg_segments[i] for i in range(2)] return "scale(%f, %f)" % (scale[0], scale[1]) def poly_to_edges(poly): @@ -558,11 +558,16 @@ class SvgWriter: pattern_dir = pattern_edge.normalized() pattern_length = pattern_edge.length - segments = int(pattern_length // segment_width) + segments = ceil(pattern_length / segment_width) pattern_dir_step = pattern_dir * segment_width - points = [pattern_dir_step * i for i in range(segments)] + # it takes atleast 2 points to preserve the edge direction + # if there is just 1 segment then we still add second point and then hide the "marker-end" + n_points = max(segments, 2) + points = [pattern_dir_step * i for i in range(n_points)] - polyline_style = f"marker: url(#{marker_id}); stroke: none;" + polyline_style = f"marker: url(#{marker_id}); stroke: none; " + if segments == 1: + polyline_style += "marker-end: none; " polyline_transform = f"translate({start_svg.x}, {start_svg.y}) {get_scale(pattern_length, pattern_dir)}" polyline = self.svg.polyline( points=points, class_=" ".join(classes), style=polyline_style, transform=polyline_transform From d75e85b3a67b118d35096611116d9a497ba7c3bd Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 17 Jul 2023 18:13:16 +1000 Subject: [PATCH 35/49] Disable Shift-D duplication of drawings for now until drawings stabilise more. Use the dedicated duplicate drawings feature. --- src/blenderbim/blenderbim/bim/module/geometry/operator.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index a6f29b3354..874aca5e05 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -598,6 +598,9 @@ class OverrideDuplicateMove(bpy.types.Operator): relationships = tool.Root.get_decomposition_relationships(context.selected_objects) old_to_new = {} for obj in context.selected_objects: + element = tool.Ifc.get_entity(obj) + if element and element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING": + continue # For now, don't copy drawings until we stabilise a bit more. It's tricky. new_obj = obj.copy() if obj.data: new_obj.data = obj.data.copy() From 80d74866a9ae5aad06d51b2335f13d4aca7828b7 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 17 Jul 2023 18:17:19 +1000 Subject: [PATCH 36/49] More tab and panel shuffling. Top level panels now have a tab prefix. --- src/blenderbim/blenderbim/bim/__init__.py | 41 ++++--- src/blenderbim/blenderbim/bim/ifc.py | 7 ++ .../blenderbim/bim/module/aggregate/ui.py | 2 +- .../blenderbim/bim/module/attribute/ui.py | 2 +- .../blenderbim/bim/module/augin/ui.py | 2 +- .../blenderbim/bim/module/bcf/ui.py | 2 +- .../blenderbim/bim/module/bimtester/ui.py | 2 +- .../blenderbim/bim/module/boundary/ui.py | 4 +- .../blenderbim/bim/module/brick/ui.py | 5 +- .../blenderbim/bim/module/clash/ui.py | 2 +- .../bim/module/classification/ui.py | 2 +- .../blenderbim/bim/module/cobie/ui.py | 5 +- .../blenderbim/bim/module/constraint/ui.py | 2 +- .../blenderbim/bim/module/cost/ui.py | 2 +- .../blenderbim/bim/module/covetool/ui.py | 2 +- .../blenderbim/bim/module/csv/ui.py | 2 +- .../blenderbim/bim/module/debug/ui.py | 2 +- .../blenderbim/bim/module/diff/ui.py | 2 +- .../blenderbim/bim/module/document/ui.py | 2 +- .../blenderbim/bim/module/geometry/ui.py | 25 +++-- .../blenderbim/bim/module/gis/ui.py | 2 +- .../blenderbim/bim/module/group/ui.py | 2 +- .../blenderbim/bim/module/layer/ui.py | 2 +- .../blenderbim/bim/module/lca/ui.py | 2 +- .../blenderbim/bim/module/library/ui.py | 2 +- .../blenderbim/bim/module/material/ui.py | 1 + .../blenderbim/bim/module/owner/ui.py | 2 +- .../blenderbim/bim/module/patch/ui.py | 2 +- .../blenderbim/bim/module/pset/ui.py | 6 +- .../blenderbim/bim/module/resource/ui.py | 2 +- .../blenderbim/bim/module/root/ui.py | 2 +- .../blenderbim/bim/module/sequence/ui.py | 6 +- .../blenderbim/bim/module/spatial/ui.py | 2 +- .../blenderbim/bim/module/structural/ui.py | 16 +-- .../blenderbim/bim/module/system/ui.py | 8 +- .../blenderbim/bim/module/tester/ui.py | 2 +- .../blenderbim/bim/module/type/ui.py | 2 +- .../blenderbim/bim/module/void/ui.py | 2 +- src/blenderbim/blenderbim/bim/ui.py | 102 +++++++++++++++--- 39 files changed, 188 insertions(+), 92 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/__init__.py b/src/blenderbim/blenderbim/bim/__init__.py index 61744b71ef..159bd62b6e 100644 --- a/src/blenderbim/blenderbim/bim/__init__.py +++ b/src/blenderbim/blenderbim/bim/__init__.py @@ -126,23 +126,36 @@ classes = [ ui.BIM_UL_generic, ui.BIM_UL_topics, ui.BIM_ADDON_preferences, - # Scene panel groups - ui.BIM_PT_root, + # Project overview ui.BIM_PT_project_info, ui.BIM_PT_project_setup, - ui.BIM_PT_collaboration, - ui.BIM_PT_selection, ui.BIM_PT_geometry, - ui.BIM_PT_services, - ui.BIM_PT_structural, - ui.BIM_PT_4D5D, - ui.BIM_PT_quality_control, - ui.BIM_PT_integrations, - # Object panel groups - ui.BIM_PT_object_metadata, - ui.BIM_PT_geometry_object, - ui.BIM_PT_services_object, - ui.BIM_PT_misc_object, + ui.BIM_PT_selection, + # Tabs panel + ui.BIM_PT_tabs, + # Object information + ui.BIM_PT_tab_object_metadata, + ui.BIM_PT_tab_misc, + # Geometry and materials + ui.BIM_PT_tab_representations, + ui.BIM_PT_tab_geometric_relationships, + ui.BIM_PT_tab_materials, + ui.BIM_PT_tab_styles, + # Drawings and documents + # Services and systems + ui.BIM_PT_tab_services, + ui.BIM_PT_tab_services_object, + # Structural analysis + ui.BIM_PT_tab_structural, + # Construction scheduling + ui.BIM_PT_tab_4D5D, + # Facility management + ui.BIM_PT_tab_handover, + ui.BIM_PT_tab_operations, + # Quality and coordination + ui.BIM_PT_tab_quality_control, + ui.BIM_PT_tab_collaboration, + ui.BIM_PT_tab_integrations, ] for mod in modules.values(): diff --git a/src/blenderbim/blenderbim/bim/ifc.py b/src/blenderbim/blenderbim/bim/ifc.py index c94fb009a3..4af2d0453f 100644 --- a/src/blenderbim/blenderbim/bim/ifc.py +++ b/src/blenderbim/blenderbim/bim/ifc.py @@ -190,6 +190,13 @@ class IfcStore: @staticmethod def link_element(element, obj): + # Please use tool.Ifc.link() instead of this method. We want to + # refactor this class and deprecate usage of IfcStore in favour of + # tools. + if isinstance(obj, bpy.types.Mesh): + obj.BIMMeshProperties.ifc_definition_id = element.id() + return + existing_obj = IfcStore.id_map.get(element.id(), None) if existing_obj == obj: return diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/ui.py b/src/blenderbim/blenderbim/bim/module/aggregate/ui.py index b7ae5a84d2..6d5404c057 100644 --- a/src/blenderbim/blenderbim/bim/module/aggregate/ui.py +++ b/src/blenderbim/blenderbim/bim/module/aggregate/ui.py @@ -28,7 +28,7 @@ class BIM_PT_aggregate(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "object" - bl_parent_id = "BIM_PT_object_metadata" + bl_parent_id = "BIM_PT_tab_object_metadata" @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/module/attribute/ui.py b/src/blenderbim/blenderbim/bim/module/attribute/ui.py index b3c807cfb7..7ffd360c19 100644 --- a/src/blenderbim/blenderbim/bim/module/attribute/ui.py +++ b/src/blenderbim/blenderbim/bim/module/attribute/ui.py @@ -59,7 +59,7 @@ class BIM_PT_object_attributes(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "object" - bl_parent_id = "BIM_PT_object_metadata" + bl_parent_id = "BIM_PT_tab_object_metadata" @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/module/augin/ui.py b/src/blenderbim/blenderbim/bim/module/augin/ui.py index 896a8e5386..4daed80129 100644 --- a/src/blenderbim/blenderbim/bim/module/augin/ui.py +++ b/src/blenderbim/blenderbim/bim/module/augin/ui.py @@ -26,7 +26,7 @@ class BIM_PT_augin(bpy.types.Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_integrations" + bl_parent_id = "BIM_PT_tab_integrations" def draw(self, context): layout = self.layout diff --git a/src/blenderbim/blenderbim/bim/module/bcf/ui.py b/src/blenderbim/blenderbim/bim/module/bcf/ui.py index 821d028dbb..58f3c0a858 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/ui.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/ui.py @@ -29,7 +29,7 @@ class BIM_PT_bcf(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_collaboration" + bl_parent_id = "BIM_PT_tab_collaboration" def draw(self, context): layout = self.layout diff --git a/src/blenderbim/blenderbim/bim/module/bimtester/ui.py b/src/blenderbim/blenderbim/bim/module/bimtester/ui.py index 9b58f29b12..5c0843d618 100644 --- a/src/blenderbim/blenderbim/bim/module/bimtester/ui.py +++ b/src/blenderbim/blenderbim/bim/module/bimtester/ui.py @@ -27,7 +27,7 @@ class BIM_PT_qa(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_quality_control" + bl_parent_id = "BIM_PT_tab_quality_control" def draw(self, context): self.layout.use_property_split = True diff --git a/src/blenderbim/blenderbim/bim/module/boundary/ui.py b/src/blenderbim/blenderbim/bim/module/boundary/ui.py index a62b6d5a8e..1e1068077f 100644 --- a/src/blenderbim/blenderbim/bim/module/boundary/ui.py +++ b/src/blenderbim/blenderbim/bim/module/boundary/ui.py @@ -50,7 +50,7 @@ class BIM_PT_Boundary(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "object" - bl_parent_id = "BIM_PT_geometry_object" + bl_parent_id = "BIM_PT_tab_geometric_relationships" @classmethod def poll(cls, context): @@ -124,7 +124,7 @@ class BIM_PT_SpaceBoundaries(Panel): bl_region_type = "WINDOW" bl_context = "object" bl_order = 1 - bl_parent_id = "BIM_PT_geometry_object" + bl_parent_id = "BIM_PT_tab_geometric_relationships" @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/module/brick/ui.py b/src/blenderbim/blenderbim/bim/module/brick/ui.py index 18dba36b29..2303ac6498 100644 --- a/src/blenderbim/blenderbim/bim/module/brick/ui.py +++ b/src/blenderbim/blenderbim/bim/module/brick/ui.py @@ -29,10 +29,7 @@ class BIM_PT_brickschema(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - - @classmethod - def poll(cls, context): - return tool.Blender.is_tab(context, "FM") + bl_parent_id = "BIM_PT_tab_operations" def draw(self, context): if not BrickschemaData.is_loaded: diff --git a/src/blenderbim/blenderbim/bim/module/clash/ui.py b/src/blenderbim/blenderbim/bim/module/clash/ui.py index 48a04eda4e..26c32321c4 100644 --- a/src/blenderbim/blenderbim/bim/module/clash/ui.py +++ b/src/blenderbim/blenderbim/bim/module/clash/ui.py @@ -27,7 +27,7 @@ class BIM_PT_ifcclash(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_quality_control" + bl_parent_id = "BIM_PT_tab_quality_control" def draw(self, context): layout = self.layout diff --git a/src/blenderbim/blenderbim/bim/module/classification/ui.py b/src/blenderbim/blenderbim/bim/module/classification/ui.py index c49957f3b3..5a4de6bf65 100644 --- a/src/blenderbim/blenderbim/bim/module/classification/ui.py +++ b/src/blenderbim/blenderbim/bim/module/classification/ui.py @@ -162,7 +162,7 @@ class BIM_PT_classification_references(Panel, ReferenceUI): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "object" - bl_parent_id = "BIM_PT_object_metadata" + bl_parent_id = "BIM_PT_tab_object_metadata" @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/module/cobie/ui.py b/src/blenderbim/blenderbim/bim/module/cobie/ui.py index 65396d7fed..a78cff2b62 100644 --- a/src/blenderbim/blenderbim/bim/module/cobie/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cobie/ui.py @@ -27,10 +27,7 @@ class BIM_PT_cobie(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - - @classmethod - def poll(cls, context): - return tool.Blender.is_tab(context, "FM") + bl_parent_id = "BIM_PT_tab_handover" def draw(self, context): layout = self.layout diff --git a/src/blenderbim/blenderbim/bim/module/constraint/ui.py b/src/blenderbim/blenderbim/bim/module/constraint/ui.py index 29b68a00de..2b017e89c3 100644 --- a/src/blenderbim/blenderbim/bim/module/constraint/ui.py +++ b/src/blenderbim/blenderbim/bim/module/constraint/ui.py @@ -75,7 +75,7 @@ class BIM_PT_object_constraints(Panel): bl_region_type = "WINDOW" bl_context = "object" bl_order = 1 - bl_parent_id = "BIM_PT_misc_object" + bl_parent_id = "BIM_PT_tab_misc" @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index 142e3f814a..c083331c76 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -30,7 +30,7 @@ class BIM_PT_cost_schedules(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_4D5D" + bl_parent_id = "BIM_PT_tab_4D5D" @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/module/covetool/ui.py b/src/blenderbim/blenderbim/bim/module/covetool/ui.py index f74aa1a97b..4c055db03e 100644 --- a/src/blenderbim/blenderbim/bim/module/covetool/ui.py +++ b/src/blenderbim/blenderbim/bim/module/covetool/ui.py @@ -26,7 +26,7 @@ class BIM_PT_covetool(bpy.types.Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_integrations" + bl_parent_id = "BIM_PT_tab_integrations" def draw(self, context): layout = self.layout diff --git a/src/blenderbim/blenderbim/bim/module/csv/ui.py b/src/blenderbim/blenderbim/bim/module/csv/ui.py index a242039712..9f690a762a 100644 --- a/src/blenderbim/blenderbim/bim/module/csv/ui.py +++ b/src/blenderbim/blenderbim/bim/module/csv/ui.py @@ -27,7 +27,7 @@ class BIM_PT_ifccsv(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_collaboration" + bl_parent_id = "BIM_PT_tab_collaboration" def draw(self, context): layout = self.layout diff --git a/src/blenderbim/blenderbim/bim/module/debug/ui.py b/src/blenderbim/blenderbim/bim/module/debug/ui.py index 91fd3e1024..1ca48978be 100644 --- a/src/blenderbim/blenderbim/bim/module/debug/ui.py +++ b/src/blenderbim/blenderbim/bim/module/debug/ui.py @@ -27,7 +27,7 @@ class BIM_PT_debug(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_quality_control" + bl_parent_id = "BIM_PT_tab_quality_control" def draw(self, context): layout = self.layout diff --git a/src/blenderbim/blenderbim/bim/module/diff/ui.py b/src/blenderbim/blenderbim/bim/module/diff/ui.py index edb64be484..a0343e2042 100644 --- a/src/blenderbim/blenderbim/bim/module/diff/ui.py +++ b/src/blenderbim/blenderbim/bim/module/diff/ui.py @@ -30,7 +30,7 @@ class BIM_PT_diff(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_quality_control" + bl_parent_id = "BIM_PT_tab_quality_control" def draw(self, context): if not DiffData.is_loaded: diff --git a/src/blenderbim/blenderbim/bim/module/document/ui.py b/src/blenderbim/blenderbim/bim/module/document/ui.py index d45f7b6792..a656f4f576 100644 --- a/src/blenderbim/blenderbim/bim/module/document/ui.py +++ b/src/blenderbim/blenderbim/bim/module/document/ui.py @@ -83,7 +83,7 @@ class BIM_PT_object_documents(Panel): bl_region_type = "WINDOW" bl_context = "object" bl_order = 1 - bl_parent_id = "BIM_PT_misc_object" + bl_parent_id = "BIM_PT_tab_misc" @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/module/geometry/ui.py b/src/blenderbim/blenderbim/bim/module/geometry/ui.py index d8ec59b274..4725373914 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/ui.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/ui.py @@ -17,6 +17,7 @@ # along with BlenderBIM Add-on. If not, see . import bpy +import blenderbim.tool as tool from bpy.types import Panel from blenderbim.bim.ifc import IfcStore from blenderbim.bim.helper import prop_with_search @@ -29,19 +30,21 @@ def object_menu(self, context): self.layout.operator("bim.override_object_delete", icon="PLUGIN") self.layout.operator("bim.override_paste_buffer", icon="PLUGIN") + def outliner_menu(self, context): self.layout.separator() - self.layout.operator("bim.override_outliner_delete", icon='X') + self.layout.operator("bim.override_outliner_delete", icon="X") + class BIM_PT_representations(Panel): bl_label = "Representations" bl_idname = "BIM_PT_representations" - bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" - bl_context = "object" + bl_context = "scene" bl_order = 1 - bl_parent_id = "BIM_PT_geometry_object" + bl_parent_id = "BIM_PT_tab_representations" + bl_options = {"HIDE_HEADER"} @classmethod def poll(cls, context): @@ -58,20 +61,24 @@ class BIM_PT_representations(Panel): layout = self.layout props = context.active_object.BIMObjectProperties - if not RepresentationsData.data["representations"]: - layout.label(text="No representations found") - row = layout.row(align=True) prop_with_search(row, context.active_object.BIMGeometryProperties, "contexts", text="") row.operator("bim.add_representation", icon="ADD", text="") + if not RepresentationsData.data["representations"]: + layout.label(text="No Representations Found") + for representation in RepresentationsData.data["representations"]: row = self.layout.row(align=True) row.label(text=representation["ContextType"]) row.label(text=representation["ContextIdentifier"]) row.label(text=representation["TargetView"]) row.label(text=representation["RepresentationType"]) - op = row.operator("bim.switch_representation", icon="FILE_REFRESH" if representation["is_active"] else "OUTLINER_DATA_MESH", text="") + op = row.operator( + "bim.switch_representation", + icon="FILE_REFRESH" if representation["is_active"] else "OUTLINER_DATA_MESH", + text="", + ) op.should_switch_all_meshes = True op.should_reload = True op.ifc_definition_id = representation["id"] @@ -87,7 +94,7 @@ class BIM_PT_connections(Panel): bl_region_type = "WINDOW" bl_context = "object" bl_order = 1 - bl_parent_id = "BIM_PT_geometry_object" + bl_parent_id = "BIM_PT_tab_geometric_relationships" @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/module/gis/ui.py b/src/blenderbim/blenderbim/bim/module/gis/ui.py index 83325ad097..a57b8d2c1c 100644 --- a/src/blenderbim/blenderbim/bim/module/gis/ui.py +++ b/src/blenderbim/blenderbim/bim/module/gis/ui.py @@ -26,7 +26,7 @@ class BIM_PT_cityjson_converter(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_collaboration" + bl_parent_id = "BIM_PT_tab_collaboration" def draw(self, context): layout = self.layout diff --git a/src/blenderbim/blenderbim/bim/module/group/ui.py b/src/blenderbim/blenderbim/bim/module/group/ui.py index 10b33693ce..a2d6d8a362 100644 --- a/src/blenderbim/blenderbim/bim/module/group/ui.py +++ b/src/blenderbim/blenderbim/bim/module/group/ui.py @@ -76,7 +76,7 @@ class BIM_PT_object_groups(Panel): bl_region_type = "WINDOW" bl_context = "object" bl_order = 1 - bl_parent_id = "BIM_PT_misc_object" + bl_parent_id = "BIM_PT_tab_misc" @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/module/layer/ui.py b/src/blenderbim/blenderbim/bim/module/layer/ui.py index 698bd448ab..218fd4046d 100644 --- a/src/blenderbim/blenderbim/bim/module/layer/ui.py +++ b/src/blenderbim/blenderbim/bim/module/layer/ui.py @@ -28,7 +28,7 @@ class BIM_PT_layers(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_geometry_object" + bl_parent_id = "BIM_PT_tab_geometric_relationships" @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/module/lca/ui.py b/src/blenderbim/blenderbim/bim/module/lca/ui.py index a76b38d8f8..5e06fd0098 100644 --- a/src/blenderbim/blenderbim/bim/module/lca/ui.py +++ b/src/blenderbim/blenderbim/bim/module/lca/ui.py @@ -27,7 +27,7 @@ class BIM_PT_lca(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_integrations" + bl_parent_id = "BIM_PT_tab_integrations" def draw(self, context): props = context.scene.BIMLCAProperties diff --git a/src/blenderbim/blenderbim/bim/module/library/ui.py b/src/blenderbim/blenderbim/bim/module/library/ui.py index feffc02cbd..c963203cd7 100644 --- a/src/blenderbim/blenderbim/bim/module/library/ui.py +++ b/src/blenderbim/blenderbim/bim/module/library/ui.py @@ -101,7 +101,7 @@ class BIM_PT_library_references(Panel): bl_region_type = "WINDOW" bl_context = "object" bl_order = 1 - bl_parent_id = "BIM_PT_misc_object" + bl_parent_id = "BIM_PT_tab_misc" @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/module/material/ui.py b/src/blenderbim/blenderbim/bim/module/material/ui.py index 2cce58c0a6..773a3e14db 100644 --- a/src/blenderbim/blenderbim/bim/module/material/ui.py +++ b/src/blenderbim/blenderbim/bim/module/material/ui.py @@ -117,6 +117,7 @@ class BIM_PT_object_material(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" + bl_parent_id = "BIM_PT_tab_materials" @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/module/owner/ui.py b/src/blenderbim/blenderbim/bim/module/owner/ui.py index fb515deb58..622808c6f0 100644 --- a/src/blenderbim/blenderbim/bim/module/owner/ui.py +++ b/src/blenderbim/blenderbim/bim/module/owner/ui.py @@ -291,7 +291,7 @@ class BIM_PT_object_actor(bpy.types.Panel): bl_region_type = "WINDOW" bl_context = "object" bl_order = 1 - bl_parent_id = "BIM_PT_misc_object" + bl_parent_id = "BIM_PT_tab_misc" @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/module/patch/ui.py b/src/blenderbim/blenderbim/bim/module/patch/ui.py index 3a843da8f0..c5013c31ff 100644 --- a/src/blenderbim/blenderbim/bim/module/patch/ui.py +++ b/src/blenderbim/blenderbim/bim/module/patch/ui.py @@ -29,7 +29,7 @@ class BIM_PT_patch(bpy.types.Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_quality_control" + bl_parent_id = "BIM_PT_tab_quality_control" def draw(self, context): layout = self.layout diff --git a/src/blenderbim/blenderbim/bim/module/pset/ui.py b/src/blenderbim/blenderbim/bim/module/pset/ui.py index 932c89d6bc..56ff428e00 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/ui.py +++ b/src/blenderbim/blenderbim/bim/module/pset/ui.py @@ -175,7 +175,7 @@ class BIM_PT_object_psets(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "object" - bl_parent_id = "BIM_PT_object_metadata" + bl_parent_id = "BIM_PT_tab_object_metadata" @classmethod def poll(cls, context): @@ -217,7 +217,7 @@ class BIM_PT_object_qtos(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "object" - bl_parent_id = "BIM_PT_object_metadata" + bl_parent_id = "BIM_PT_tab_object_metadata" @classmethod def poll(cls, context): @@ -544,7 +544,7 @@ class BIM_PT_bulk_property_editor(Panel): bl_region_type = "WINDOW" bl_context = "object" bl_options = {"DEFAULT_CLOSED"} - bl_parent_id = "BIM_PT_misc_object" + bl_parent_id = "BIM_PT_tab_misc" def draw(self, context): pass diff --git a/src/blenderbim/blenderbim/bim/module/resource/ui.py b/src/blenderbim/blenderbim/bim/module/resource/ui.py index a3ecdc92d9..4ce36e12bf 100644 --- a/src/blenderbim/blenderbim/bim/module/resource/ui.py +++ b/src/blenderbim/blenderbim/bim/module/resource/ui.py @@ -29,7 +29,7 @@ class BIM_PT_resources(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_4D5D" + bl_parent_id = "BIM_PT_tab_4D5D" @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/module/root/ui.py b/src/blenderbim/blenderbim/bim/module/root/ui.py index 1db3a14e9b..76f5546594 100644 --- a/src/blenderbim/blenderbim/bim/module/root/ui.py +++ b/src/blenderbim/blenderbim/bim/module/root/ui.py @@ -30,7 +30,7 @@ class BIM_PT_class(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "object" - bl_parent_id = "BIM_PT_object_metadata" + bl_parent_id = "BIM_PT_tab_object_metadata" bl_options = {"HIDE_HEADER"} @classmethod diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index 8d8d9f7afd..eb0ccc477d 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -31,7 +31,7 @@ class BIM_PT_work_plans(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_4D5D" + bl_parent_id = "BIM_PT_tab_4D5D" @classmethod def poll(cls, context): @@ -103,7 +103,7 @@ class BIM_PT_work_schedules(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_4D5D" + bl_parent_id = "BIM_PT_tab_4D5D" @classmethod def poll(cls, context): @@ -817,7 +817,7 @@ class BIM_PT_work_calendars(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_4D5D" + bl_parent_id = "BIM_PT_tab_4D5D" @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/module/spatial/ui.py b/src/blenderbim/blenderbim/bim/module/spatial/ui.py index ec66b77248..8f73b18a4f 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/ui.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/ui.py @@ -28,7 +28,7 @@ class BIM_PT_spatial(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "object" - bl_parent_id = "BIM_PT_object_metadata" + bl_parent_id = "BIM_PT_tab_object_metadata" @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/module/structural/ui.py b/src/blenderbim/blenderbim/bim/module/structural/ui.py index cfb3559c9f..b828c124f4 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/ui.py +++ b/src/blenderbim/blenderbim/bim/module/structural/ui.py @@ -93,7 +93,7 @@ class BIM_PT_structural_boundary_conditions(Panel): bl_region_type = "WINDOW" bl_context = "object" bl_order = 1 - bl_parent_id = "BIM_PT_misc_object" + bl_parent_id = "BIM_PT_tab_misc" @classmethod def poll(cls, context): @@ -128,7 +128,7 @@ class BIM_PT_connected_structural_members(Panel): bl_region_type = "WINDOW" bl_context = "object" bl_order = 1 - bl_parent_id = "BIM_PT_misc_object" + bl_parent_id = "BIM_PT_tab_misc" @classmethod def poll(cls, context): @@ -181,7 +181,7 @@ class BIM_PT_structural_member(Panel): bl_region_type = "WINDOW" bl_context = "object" bl_order = 1 - bl_parent_id = "BIM_PT_misc_object" + bl_parent_id = "BIM_PT_tab_misc" @classmethod def poll(cls, context): @@ -224,7 +224,7 @@ class BIM_PT_structural_connection(Panel): bl_region_type = "WINDOW" bl_context = "object" bl_order = 1 - bl_parent_id = "BIM_PT_misc_object" + bl_parent_id = "BIM_PT_tab_misc" @classmethod def poll(cls, context): @@ -283,7 +283,7 @@ class BIM_PT_structural_analysis_models(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_structural" + bl_parent_id = "BIM_PT_tab_structural" @classmethod def poll(cls, context): @@ -358,7 +358,7 @@ class BIM_PT_structural_load_cases(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_structural" + bl_parent_id = "BIM_PT_tab_structural" @classmethod def poll(cls, context): @@ -454,7 +454,7 @@ class BIM_PT_structural_loads(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_structural" + bl_parent_id = "BIM_PT_tab_structural" @classmethod def poll(cls, context): @@ -524,7 +524,7 @@ class BIM_PT_boundary_conditions(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_structural" + bl_parent_id = "BIM_PT_tab_structural" @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/module/system/ui.py b/src/blenderbim/blenderbim/bim/module/system/ui.py index 7436b71c4a..9aea14b027 100644 --- a/src/blenderbim/blenderbim/bim/module/system/ui.py +++ b/src/blenderbim/blenderbim/bim/module/system/ui.py @@ -30,7 +30,7 @@ class BIM_PT_systems(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_services" + bl_parent_id = "BIM_PT_tab_services" @classmethod def poll(cls, context): @@ -81,7 +81,7 @@ class BIM_PT_object_systems(Panel): bl_region_type = "WINDOW" bl_context = "object" bl_order = 1 - bl_parent_id = "BIM_PT_services_object" + bl_parent_id = "BIM_PT_tab_services_object" @classmethod def poll(cls, context): @@ -137,7 +137,7 @@ class BIM_PT_ports(Panel): bl_region_type = "WINDOW" bl_context = "object" bl_order = 1 - bl_parent_id = "BIM_PT_services_object" + bl_parent_id = "BIM_PT_tab_services_object" @classmethod def poll(cls, context): @@ -170,7 +170,7 @@ class BIM_PT_port(Panel): bl_region_type = "WINDOW" bl_context = "object" bl_order = 1 - bl_parent_id = "BIM_PT_services_object" + bl_parent_id = "BIM_PT_tab_services_object" @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/module/tester/ui.py b/src/blenderbim/blenderbim/bim/module/tester/ui.py index 59a0d12281..f956b28f59 100644 --- a/src/blenderbim/blenderbim/bim/module/tester/ui.py +++ b/src/blenderbim/blenderbim/bim/module/tester/ui.py @@ -27,7 +27,7 @@ class BIM_PT_tester(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_quality_control" + bl_parent_id = "BIM_PT_tab_quality_control" def draw(self, context): self.layout.use_property_split = True diff --git a/src/blenderbim/blenderbim/bim/module/type/ui.py b/src/blenderbim/blenderbim/bim/module/type/ui.py index ad3fdd92ce..7be2c50897 100644 --- a/src/blenderbim/blenderbim/bim/module/type/ui.py +++ b/src/blenderbim/blenderbim/bim/module/type/ui.py @@ -30,7 +30,7 @@ class BIM_PT_type(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "object" - bl_parent_id = "BIM_PT_object_metadata" + bl_parent_id = "BIM_PT_tab_object_metadata" @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/module/void/ui.py b/src/blenderbim/blenderbim/bim/module/void/ui.py index f9630af001..4197189a61 100644 --- a/src/blenderbim/blenderbim/bim/module/void/ui.py +++ b/src/blenderbim/blenderbim/bim/module/void/ui.py @@ -30,7 +30,7 @@ class BIM_PT_voids(Panel): bl_region_type = "WINDOW" bl_context = "object" bl_order = 1 - bl_parent_id = "BIM_PT_geometry_object" + bl_parent_id = "BIM_PT_tab_geometric_relationships" @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/ui.py b/src/blenderbim/blenderbim/bim/ui.py index 368b1b26e1..0b44a28256 100644 --- a/src/blenderbim/blenderbim/bim/ui.py +++ b/src/blenderbim/blenderbim/bim/ui.py @@ -270,7 +270,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences): # Scene panel groups -class BIM_PT_root(Panel): +class BIM_PT_tabs(Panel): bl_label = "BlenderBIM Add-on" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -283,7 +283,6 @@ class BIM_PT_root(Panel): aprops = context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)] row = self.layout.row() - row.scale_y = 0.75 # Yes, we actually do this. row.operator( "bim.set_tab", text="", emboss=False, icon_value=blenderbim.bim.icons["IFC"].icon_id ).tab = "PROJECT" @@ -298,7 +297,7 @@ class BIM_PT_root(Panel): row.operator("bim.set_tab", text="", emboss=False, icon="BLENDER").tab = "BLENDER" row.operator("bim.switch_tab", text="", emboss=False, icon="UV_SYNC_SELECT") - # Yes, that's right, we're still doing this. + # Yes, that's right. row = self.layout.row() row.scale_y = 0.2 for tab in [ @@ -355,7 +354,7 @@ class BIM_PT_project_setup(Panel): pass -class BIM_PT_collaboration(Panel): +class BIM_PT_tab_collaboration(Panel): bl_label = "Collaboration" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -398,7 +397,7 @@ class BIM_PT_geometry(Panel): pass -class BIM_PT_4D5D(Panel): +class BIM_PT_tab_4D5D(Panel): bl_label = "Costing and Scheduling" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -412,7 +411,7 @@ class BIM_PT_4D5D(Panel): pass -class BIM_PT_structural(Panel): +class BIM_PT_tab_structural(Panel): bl_label = "Structural" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -426,7 +425,7 @@ class BIM_PT_structural(Panel): pass -class BIM_PT_services(Panel): +class BIM_PT_tab_services(Panel): bl_label = "Services" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -440,7 +439,7 @@ class BIM_PT_services(Panel): pass -class BIM_PT_quality_control(Panel): +class BIM_PT_tab_quality_control(Panel): bl_label = "Quality Control" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -454,7 +453,7 @@ class BIM_PT_quality_control(Panel): pass -class BIM_PT_integrations(Panel): +class BIM_PT_tab_integrations(Panel): bl_label = "BIM Integrations" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -470,7 +469,7 @@ class BIM_PT_integrations(Panel): # Object panel groups -class BIM_PT_object_metadata(Panel): +class BIM_PT_tab_object_metadata(Panel): bl_label = "Object Metadata" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -485,8 +484,8 @@ class BIM_PT_object_metadata(Panel): pass -class BIM_PT_geometry_object(Panel): - bl_label = "Geometry" +class BIM_PT_tab_representations(Panel): + bl_label = "Representations" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" @@ -500,7 +499,52 @@ class BIM_PT_geometry_object(Panel): pass -class BIM_PT_services_object(Panel): +class BIM_PT_tab_geometric_relationships(Panel): + bl_label = "Geometric Relationships" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + bl_order = 1 + + @classmethod + def poll(cls, context): + return tool.Blender.is_tab(context, "GEOMETRY") and tool.Ifc.get() + + def draw(self, context): + pass + + +class BIM_PT_tab_materials(Panel): + bl_label = "Materials" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + bl_order = 1 + + @classmethod + def poll(cls, context): + return tool.Blender.is_tab(context, "GEOMETRY") and tool.Ifc.get() + + def draw(self, context): + pass + + +class BIM_PT_tab_styles(Panel): + bl_label = "Styles" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + bl_order = 1 + + @classmethod + def poll(cls, context): + return tool.Blender.is_tab(context, "GEOMETRY") and tool.Ifc.get() + + def draw(self, context): + pass + + +class BIM_PT_tab_services_object(Panel): bl_label = "Services" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -515,7 +559,7 @@ class BIM_PT_services_object(Panel): pass -class BIM_PT_misc_object(Panel): +class BIM_PT_tab_misc(Panel): bl_label = "Misc." bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -531,6 +575,36 @@ class BIM_PT_misc_object(Panel): pass +class BIM_PT_tab_handover(Panel): + bl_label = "Commissioning and Handover" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + bl_order = 1 + + @classmethod + def poll(cls, context): + return tool.Blender.is_tab(context, "FM") and tool.Ifc.get() + + def draw(self, context): + pass + + +class BIM_PT_tab_operations(Panel): + bl_label = "Operations and Maintenance" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + bl_order = 2 + + @classmethod + def poll(cls, context): + return tool.Blender.is_tab(context, "FM") and tool.Ifc.get() + + def draw(self, context): + pass + + class UIData: data = {} is_loaded = False From 21a614560ab3e3e24b83ef0b66959049ccbd0739 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 17 Jul 2023 16:32:10 +0500 Subject: [PATCH 37/49] Revision Clouds - option to match revision cloud with the selected object Works similarly to tagging, example - https://imgur.com/a/Atr0nYE This feature also can be useful in the future if we integrate revision clouds with ifcgit --- .../bim/module/drawing/workspace.py | 24 +++++++++++----- src/blenderbim/blenderbim/tool/drawing.py | 28 +++++++++++++++++++ 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/workspace.py b/src/blenderbim/blenderbim/bim/module/drawing/workspace.py index b4ead1321d..2e42fabada 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/workspace.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/workspace.py @@ -248,16 +248,16 @@ class AnnotationToolUI: add_layout_hotkey_operator(cls.layout, "Add", "S_A", "Create a new annotation") - if object_type in ("TEXT", "STAIR_ARROW"): + if object_type in tool.Drawing.ANNOTATION_TYPES_SUPPORT_SETUP: add_layout_hotkey_operator( cls.layout, "Bulk Tag", "S_T", "Create new annotations and automatically adjust them to the selected objects", ) - add_layout_hotkey_operator( - cls.layout, "Readjust", "S_G", "Readjust tags based on the products they are assigned to" - ) + add_layout_hotkey_operator( + cls.layout, "Readjust", "S_G", "Readjust tags based on the products they are assigned to" + ) class Hotkey(bpy.types.Operator, Operator): @@ -289,14 +289,18 @@ class Hotkey(bpy.types.Operator, Operator): def hotkey_S_T(self): props = bpy.context.scene.BIMAnnotationProperties - object_type = props.object_type + annotation_type = props.object_type + + if annotation_type not in tool.Drawing.ANNOTATION_TYPES_SUPPORT_SETUP: + self.report({"ERROR"}, f"Annotation type {annotation_type} is not supported for tagging.") + return related_objects = bpy.context.selected_objects for related_object in related_objects: create_annotation() obj = bpy.context.active_object bpy.ops.object.mode_set(mode="OBJECT") - tool.Drawing.setup_annotation_object(obj, object_type, related_object) + tool.Drawing.setup_annotation_object(obj, annotation_type, related_object) def hotkey_S_A(self): create_annotation() @@ -314,9 +318,15 @@ class Hotkey(bpy.types.Operator, Operator): if not element or not element.is_a("IfcAnnotation"): continue + annotation_type = element.ObjectType + if annotation_type not in tool.Drawing.ANNOTATION_TYPES_SUPPORT_SETUP: + self.report({"ERROR"}, f"Annotation type {annotation_type} is not supported for readjustment.") + continue + related_product = tool.Drawing.get_assigned_product(element) if not related_product: + self.report({"ERROR"}, "Selected annotation has no product assigned.") continue related_object = tool.Ifc.get_object(related_product) - tool.Drawing.setup_annotation_object(obj, element.ObjectType, related_object) + tool.Drawing.setup_annotation_object(obj, annotation_type, related_object) diff --git a/src/blenderbim/blenderbim/tool/drawing.py b/src/blenderbim/blenderbim/tool/drawing.py index 5a4f98059a..9d3bb0f884 100644 --- a/src/blenderbim/blenderbim/tool/drawing.py +++ b/src/blenderbim/blenderbim/tool/drawing.py @@ -24,6 +24,8 @@ import lark import bmesh import shutil import logging +import shapely +from shapely.ops import unary_union import mathutils import webbrowser import subprocess @@ -97,6 +99,8 @@ class Drawing(blenderbim.core.tool.Drawing): current_pos = camera.matrix_world @ current_pos obj.location = current_pos + ANNOTATION_TYPES_SUPPORT_SETUP = ("STAIR_ARROW", "TEXT", "REVISION_CLOUD", "FILL_AREA") + @classmethod def setup_annotation_object(cls, obj, object_type, related_object=None): """Finish object's adjustments after both object and entity are created""" @@ -135,6 +139,30 @@ class Drawing(blenderbim.core.tool.Drawing): cls.ensure_annotation_in_drawing_plane(obj) assign_product = True + elif object_type == "REVISION_CLOUD": + revised_object, cloud = related_object, obj + + verts = [np.array(revised_object.matrix_world @ v.co) for v in revised_object.data.vertices] + verts = [(np.around(v[[0, 1]], decimals=3)).tolist() for v in verts] + edges = [e.vertices for e in revised_object.data.edges] + + # shapely magic + boundary_lines = [shapely.LineString([verts[v] for v in e]) for e in edges] + unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines)) + all_polygons = shapely.polygonize(unioned_boundaries.geoms).geoms + outer_shell = unary_union(all_polygons) + + bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True) + new_verts = list(outer_shell.exterior.coords) + bm_verts = [bm.verts.new(v + (0,)) for v in new_verts] + bm_edges = [bm.edges.new([bm_verts[i], bm_verts[i + 1]]) for i in range(len(new_verts) - 1)] + bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001) + + tool.Blender.apply_bmesh(obj.data, bm, obj) + cloud.location = Vector((0, 0, 0)) + cls.ensure_annotation_in_drawing_plane(cloud) + assign_product = True + if assign_product and not cls.get_assigned_product(obj_entity): tool.Ifc.run("drawing.assign_product", relating_product=related_entity, related_object=obj_entity) From 0573ffb11a646d2bf32602363078d911963c89ff Mon Sep 17 00:00:00 2001 From: ay-ex <127405908+ay-ex@users.noreply.github.com> Date: Mon, 17 Jul 2023 12:10:26 +0000 Subject: [PATCH 38/49] fix choco_update action fix according to upstream makefile change: https://github.com/blender/blender/blob/main/build_files/cmake/Modules/FindPythonLibsUnix.cmake --- choco/blenderbim/check_repo_infos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/choco/blenderbim/check_repo_infos.py b/choco/blenderbim/check_repo_infos.py index d00a38ac72..af99f3cdea 100644 --- a/choco/blenderbim/check_repo_infos.py +++ b/choco/blenderbim/check_repo_infos.py @@ -70,7 +70,7 @@ elif sys.argv[1] == "--pyver?": found = re.findall(re_blender_version_min_maj_pat, html_txt) if found: latest_blender_version_tag = f"v{found[0]}" - re_blender_python_version_maj_min = r"SET\(PYTHON_VERSION (\d+.\d+) " + re_blender_python_version_maj_min = r"^SET\(_PYTHON_VERSION_SUPPORTED (\d+\.\d+)\)$" url = f"https://raw.githubusercontent.com/blender/blender/{latest_blender_version_tag}/build_files/cmake/Modules/FindPythonLibsUnix.cmake" resp = request_repo_info(url) html_txt = str(resp.read()) From 33930642a40f68f43af24b87fef5322fc1ed9895 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 17 Jul 2023 22:21:19 +1000 Subject: [PATCH 39/49] Move all modifier panels into Representations and Materials tab. --- src/blenderbim/blenderbim/bim/__init__.py | 1 + .../blenderbim/bim/module/model/ui.py | 21 ++++++++++++------- src/blenderbim/blenderbim/bim/ui.py | 17 +++++++++++++++ 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/__init__.py b/src/blenderbim/blenderbim/bim/__init__.py index 159bd62b6e..3f4e755006 100644 --- a/src/blenderbim/blenderbim/bim/__init__.py +++ b/src/blenderbim/blenderbim/bim/__init__.py @@ -139,6 +139,7 @@ classes = [ # Geometry and materials ui.BIM_PT_tab_representations, ui.BIM_PT_tab_geometric_relationships, + ui.BIM_PT_tab_parametric_geometry, ui.BIM_PT_tab_materials, ui.BIM_PT_tab_styles, # Drawings and documents diff --git a/src/blenderbim/blenderbim/bim/module/model/ui.py b/src/blenderbim/blenderbim/bim/module/model/ui.py index cb94c66d83..75837229cb 100644 --- a/src/blenderbim/blenderbim/bim/module/model/ui.py +++ b/src/blenderbim/blenderbim/bim/module/model/ui.py @@ -172,8 +172,9 @@ class BIM_PT_array(bpy.types.Panel): bl_idname = "BIM_PT_array" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" - bl_context = "modifier" + bl_context = "scene" bl_options = {"DEFAULT_CLOSED"} + bl_parent_id = "BIM_PT_tab_parametric_geometry" @classmethod def poll(cls, context): @@ -240,8 +241,9 @@ class BIM_PT_stair(bpy.types.Panel): bl_idname = "BIM_PT_stair" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" - bl_context = "modifier" + bl_context = "scene" bl_options = {"DEFAULT_CLOSED"} + bl_parent_id = "BIM_PT_tab_parametric_geometry" @classmethod def poll(cls, context): @@ -298,8 +300,9 @@ class BIM_PT_sverchok(bpy.types.Panel): bl_idname = "BIM_PT_sverchok" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" - bl_context = "modifier" + bl_context = "scene" bl_options = {"DEFAULT_CLOSED"} + bl_parent_id = "BIM_PT_tab_parametric_geometry" @classmethod def poll(cls, context): @@ -336,8 +339,9 @@ class BIM_PT_window(bpy.types.Panel): bl_idname = "BIM_PT_window" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" - bl_context = "modifier" + bl_context = "scene" bl_options = {"DEFAULT_CLOSED"} + bl_parent_id = "BIM_PT_tab_parametric_geometry" @classmethod def poll(cls, context): @@ -445,8 +449,9 @@ class BIM_PT_door(bpy.types.Panel): bl_idname = "BIM_PT_door" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" - bl_context = "modifier" + bl_context = "scene" bl_options = {"DEFAULT_CLOSED"} + bl_parent_id = "BIM_PT_tab_parametric_geometry" @classmethod def poll(cls, context): @@ -518,8 +523,9 @@ class BIM_PT_railing(bpy.types.Panel): bl_idname = "BIM_PT_railing" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" - bl_context = "modifier" + bl_context = "scene" bl_options = {"DEFAULT_CLOSED"} + bl_parent_id = "BIM_PT_tab_parametric_geometry" @classmethod def poll(cls, context): @@ -580,8 +586,9 @@ class BIM_PT_roof(bpy.types.Panel): bl_idname = "BIM_PT_roof" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" - bl_context = "modifier" + bl_context = "scene" bl_options = {"DEFAULT_CLOSED"} + bl_parent_id = "BIM_PT_tab_parametric_geometry" @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/ui.py b/src/blenderbim/blenderbim/bim/ui.py index 0b44a28256..f320450ed5 100644 --- a/src/blenderbim/blenderbim/bim/ui.py +++ b/src/blenderbim/blenderbim/bim/ui.py @@ -505,6 +505,23 @@ class BIM_PT_tab_geometric_relationships(Panel): bl_region_type = "WINDOW" bl_context = "scene" bl_order = 1 + bl_options = {"DEFAULT_CLOSED"} + + @classmethod + def poll(cls, context): + return tool.Blender.is_tab(context, "GEOMETRY") and tool.Ifc.get() + + def draw(self, context): + pass + + +class BIM_PT_tab_parametric_geometry(Panel): + bl_label = "Parametric Geometry" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + bl_order = 1 + bl_options = {"DEFAULT_CLOSED"} @classmethod def poll(cls, context): From 42bbc9a093bee8115de8e8d500da7193db586a4f Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Mon, 17 Jul 2023 14:29:35 +0200 Subject: [PATCH 40/49] Ensure positive extrusion depth This prevents an error and mesh corruption when trying to input a negative extrusion depth --- src/blenderbim/blenderbim/bim/module/model/prop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/prop.py b/src/blenderbim/blenderbim/bim/module/model/prop.py index 1e7fedb68a..90adc41ec5 100644 --- a/src/blenderbim/blenderbim/bim/module/model/prop.py +++ b/src/blenderbim/blenderbim/bim/module/model/prop.py @@ -99,7 +99,7 @@ class BIMModelProperties(PropertyGroup): ) occurrence_name_function: bpy.props.StringProperty(name="Occurrence Name Function") getter_enum = {"ifc_class": get_ifc_class, "relating_type": get_relating_type_id} - extrusion_depth: bpy.props.FloatProperty(default=42.0, subtype="DISTANCE") + extrusion_depth: bpy.props.FloatProperty(min=0.001, default=42.0, subtype="DISTANCE") cardinal_point: bpy.props.EnumProperty( items=( # TODO: complain to buildingSMART From b9ff4f3263445073d6e6cc3e3f702cf62a914257 Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Mon, 17 Jul 2023 14:48:01 +0200 Subject: [PATCH 41/49] Fix Ifc modifier error after #3393064 because of legacy context.object For some reason in the scene properties context.object returns None even if an object is active in the viewport, so ifc modifiers would randomly throw errors. AFAIK functionally they are supposed to point to the same thing, but active_object seems to be the superior one and context.object deprecated and kept for legacy reasons. Couldn't find dev quote but I'm sure I saw it at some point --- .../blenderbim/bim/module/drawing/decoration.py | 2 +- .../blenderbim/bim/module/drawing/svgwriter.py | 2 +- .../blenderbim/bim/module/drawing/workspace.py | 6 +++--- .../blenderbim/bim/module/model/door.py | 10 +++++----- .../blenderbim/bim/module/model/railing.py | 14 +++++++------- .../blenderbim/bim/module/model/roof.py | 16 ++++++++-------- .../blenderbim/bim/module/model/stair.py | 10 +++++----- .../bim/module/model/sverchok_modifier.py | 2 +- .../blenderbim/bim/module/model/window.py | 10 +++++----- src/blenderbim/blenderbim/tool/blender.py | 4 ++-- .../scripts/geonodes_modifier_prototype.py | 2 +- src/blenderbim/test/bim/feature/pset.feature | 4 ++-- 12 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py index f5ce7c2840..4f3764df3c 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py @@ -748,7 +748,7 @@ class AngleDecorator(BaseDecorator): arcs_color = None edges_color = UNSPECIAL_ELEMENT_COLOR - if context.object == obj and obj.data.is_editmode: + if context.active_object == obj and obj.data.is_editmode: arcs_color = context.preferences.addons["blenderbim"].preferences.decorator_color_special edges_color = None diff --git a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py index 6024aee181..625bb8af47 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py @@ -961,7 +961,7 @@ class SvgWriter: p0 = points_2d[1] + dir0 * angle_radius p2 = points_2d[1] + dir1 * angle_radius points_chunk = [view3d_utils.region_2d_to_origin_3d(region, region_3d, p) for p in [p0, p3, p2]] - # points = [p.co.xyz for p in bpy.context.object.data.splines[0].points[:3]] + # points = [p.co.xyz for p in bpy.context.active_object.data.splines[0].points[:3]] bm = bmesh.new() bm.verts.index_update() diff --git a/src/blenderbim/blenderbim/bim/module/drawing/workspace.py b/src/blenderbim/blenderbim/bim/module/drawing/workspace.py index 2e42fabada..73704e32e7 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/workspace.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/workspace.py @@ -229,7 +229,7 @@ class AnnotationToolUI: @classmethod def draw_edit_object_interface(cls, context): - if DecoratorData.get_ifc_text_data(bpy.context.object): + if DecoratorData.get_ifc_text_data(bpy.context.active_object): add_layout_hotkey_operator(cls.layout, "Edit Text", "S_E", "") @classmethod @@ -306,10 +306,10 @@ class Hotkey(bpy.types.Operator, Operator): create_annotation() def hotkey_S_E(self): - if not bpy.context.object: + if not bpy.context.active_object: return - if DecoratorData.get_ifc_text_data(bpy.context.object): + if DecoratorData.get_ifc_text_data(bpy.context.active_object): bpy.ops.bim.edit_text_popup() def hotkey_S_G(self): diff --git a/src/blenderbim/blenderbim/bim/module/model/door.py b/src/blenderbim/blenderbim/bim/module/model/door.py index 838e42bf9a..6150f472c6 100644 --- a/src/blenderbim/blenderbim/bim/module/model/door.py +++ b/src/blenderbim/blenderbim/bim/module/model/door.py @@ -276,7 +276,7 @@ def create_bm_door_lining(bm, size: Vector, thickness: list, position: Vector = def update_door_modifier_bmesh(context): - obj = context.object + obj = context.active_object props = obj.BIMDoorProperties overall_width = props.overall_width @@ -456,7 +456,7 @@ def update_door_modifier_bmesh(context): bmesh.ops.translate(bm, vec=V(0, lining_offset, 0), verts=lining_offset_verts) bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001) - if bpy.context.object.mode == "EDIT": + if bpy.context.active_object.mode == "EDIT": bmesh.update_edit_mesh(obj.data) else: bm.to_mesh(obj.data) @@ -475,9 +475,9 @@ class BIM_OT_add_door(bpy.types.Operator, tool.Ifc.Operator): self.report({"ERROR"}, "You need to start IFC project first to create a door.") return {"CANCELLED"} - if context.object is not None: - spawn_location = context.object.location.copy() - context.object.select_set(False) + if context.active_object is not None: + spawn_location = context.active_object.location.copy() + context.active_object.select_set(False) else: spawn_location = bpy.context.scene.cursor.location.copy() diff --git a/src/blenderbim/blenderbim/bim/module/model/railing.py b/src/blenderbim/blenderbim/bim/module/model/railing.py index 71c07a986b..7382d68977 100644 --- a/src/blenderbim/blenderbim/bim/module/model/railing.py +++ b/src/blenderbim/blenderbim/bim/module/model/railing.py @@ -120,7 +120,7 @@ def update_railing_modifier_bmesh(context): """before using should make sure that Data contains up-to-date information. If BBIM Pset just changed should call refresh() before updating bmesh """ - obj = context.object + obj = context.active_object props = obj.BIMRailingProperties # NOTE: using Data since bmesh update will hapen very often @@ -274,9 +274,9 @@ class BIM_OT_add_railing(bpy.types.Operator, tool.Ifc.Operator): self.report({"ERROR"}, "You need to start IFC project first to create a railing.") return {"CANCELLED"} - if context.object is not None: - spawn_location = context.object.location.copy() - context.object.select_set(False) + if context.active_object is not None: + spawn_location = context.active_object.location.copy() + context.active_object.select_set(False) else: spawn_location = bpy.context.scene.cursor.location.copy() @@ -442,7 +442,7 @@ class EnableEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator): props.is_editing_path = True update_railing_modifier_bmesh(context) - if bpy.context.object.mode != "EDIT": + if bpy.context.active_object.mode != "EDIT": bpy.ops.object.mode_set(mode="EDIT") bpy.ops.wm.tool_set_by_id(tool.Blender.get_viewport_context(), name="bim.cad_tool") ProfileDecorator.install(context, exit_edit_mode_callback=lambda: cancel_editing_railing_path(context)) @@ -457,7 +457,7 @@ def cancel_editing_railing_path(context): props.is_editing_path = False update_railing_modifier_bmesh(context) - if bpy.context.object.mode == "EDIT": + if bpy.context.active_object.mode == "EDIT": bpy.ops.object.mode_set(mode="OBJECT") return {"FINISHED"} @@ -492,7 +492,7 @@ class FinishEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator): # since we know that BBIM_Railing could have changed refresh() update_railing_modifier_bmesh(context) - if bpy.context.object.mode == "EDIT": + if bpy.context.active_object.mode == "EDIT": bpy.ops.object.mode_set(mode="OBJECT") update_railing_modifier_ifc_data(context) return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/model/roof.py b/src/blenderbim/blenderbim/bim/module/model/roof.py index f3d90c0c73..8f1f3c753a 100644 --- a/src/blenderbim/blenderbim/bim/module/model/roof.py +++ b/src/blenderbim/blenderbim/bim/module/model/roof.py @@ -424,7 +424,7 @@ def update_roof_modifier_bmesh(context): """before using should make sure that Data contains up-to-date information. If BBIM Pset just changed should call refresh() before updating bmesh """ - obj = context.object + obj = context.active_object props = obj.BIMRoofProperties # NOTE: using Data since bmesh update will hapen very often @@ -506,9 +506,9 @@ class BIM_OT_add_roof(bpy.types.Operator, tool.Ifc.Operator): self.report({"ERROR"}, "You need to start IFC project first to create a roof.") return {"CANCELLED"} - if context.object is not None: - spawn_location = context.object.location.copy() - context.object.select_set(False) + if context.active_object is not None: + spawn_location = context.active_object.location.copy() + context.active_object.select_set(False) else: spawn_location = bpy.context.scene.cursor.location.copy() @@ -654,7 +654,7 @@ class EnableEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator): props.is_editing_path = True update_roof_modifier_bmesh(context) - if bpy.context.object.mode != "EDIT": + if bpy.context.active_object.mode != "EDIT": bpy.ops.object.mode_set(mode="EDIT") bpy.ops.wm.tool_set_by_id(tool.Blender.get_viewport_context(), name="bim.cad_tool") @@ -706,7 +706,7 @@ def cancel_editing_roof_path(context): props.is_editing_path = False update_roof_modifier_bmesh(context) - if bpy.context.object.mode == "EDIT": + if bpy.context.active_object.mode == "EDIT": bpy.ops.object.mode_set(mode="OBJECT") return {"FINISHED"} @@ -747,7 +747,7 @@ class FinishEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator): update_roof_modifier_bmesh(context) update_roof_modifier_ifc_data(context) - if bpy.context.object.mode == "EDIT": + if bpy.context.active_object.mode == "EDIT": bpy.ops.object.mode_set(mode="OBJECT") update_roof_modifier_ifc_data(context) return {"FINISHED"} @@ -789,7 +789,7 @@ class SetGableRoofEdgeAngle(bpy.types.Operator): # tried to avoid bmesh with foreach_get and foreach_set # but in EDIT mode it's only possible to change attributes by working with bmesh - me = context.object.data + me = context.active_object.data bm = tool.Blender.get_bmesh_for_mesh(me) # check if attribute exists or create one diff --git a/src/blenderbim/blenderbim/bim/module/model/stair.py b/src/blenderbim/blenderbim/bim/module/model/stair.py index 331173f381..dbf24d3ae7 100644 --- a/src/blenderbim/blenderbim/bim/module/model/stair.py +++ b/src/blenderbim/blenderbim/bim/module/model/stair.py @@ -215,7 +215,7 @@ def update_stair_modifier(context): props_kwargs = obj.BIMStairProperties.get_props_kwargs() vertices, edges, faces = generate_stair_2d_profile(**props_kwargs) - obj = context.object + obj = context.active_object bm = bmesh.new() bm.verts.index_update() bm.edges.index_update() @@ -234,7 +234,7 @@ def update_stair_modifier(context): translate_verts = [v for v in extruded["geom"] if isinstance(v, BMVert)] bmesh.ops.translate(bm, vec=extrusion_vector, verts=translate_verts) - if context.object.mode == "EDIT": + if context.active_object.mode == "EDIT": bmesh.update_edit_mesh(obj.data) else: bm.to_mesh(obj.data) @@ -316,9 +316,9 @@ class BIM_OT_add_clever_stair(bpy.types.Operator, tool.Ifc.Operator): self.report({"ERROR"}, "You need to start IFC project first to create a stair.") return {"CANCELLED"} - if context.object is not None: - spawn_location = context.object.location.copy() - context.object.select_set(False) + if context.active_object is not None: + spawn_location = context.active_object.location.copy() + context.active_object.select_set(False) else: spawn_location = bpy.context.scene.cursor.location.copy() diff --git a/src/blenderbim/blenderbim/bim/module/model/sverchok_modifier.py b/src/blenderbim/blenderbim/bim/module/model/sverchok_modifier.py index 4444870fe9..7c6428a4eb 100644 --- a/src/blenderbim/blenderbim/bim/module/model/sverchok_modifier.py +++ b/src/blenderbim/blenderbim/bim/module/model/sverchok_modifier.py @@ -51,7 +51,7 @@ def update_sverchok_modifier(context): bm.edges.index_update() bm.faces.index_update() - if context.object.mode == "EDIT": + if context.active_object.mode == "EDIT": bmesh.update_edit_mesh(obj.data) else: bm.to_mesh(obj.data) diff --git a/src/blenderbim/blenderbim/bim/module/model/window.py b/src/blenderbim/blenderbim/bim/module/model/window.py index d19763b445..ea5458da4d 100644 --- a/src/blenderbim/blenderbim/bim/module/model/window.py +++ b/src/blenderbim/blenderbim/bim/module/model/window.py @@ -292,7 +292,7 @@ def create_bm_window( def update_window_modifier_bmesh(context): - obj = context.object + obj = context.active_object props = obj.BIMWindowProperties panel_schema = DEFAULT_PANEL_SCHEMAS[props.window_type] accumulated_height = [0] * len(panel_schema[0]) @@ -425,7 +425,7 @@ def update_window_modifier_bmesh(context): bmesh.ops.translate(bm, vec=V(0, lining_offset, 0), verts=bm.verts) bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001) - if bpy.context.object.mode == "EDIT": + if bpy.context.active_object.mode == "EDIT": bmesh.update_edit_mesh(obj.data) else: bm.to_mesh(obj.data) @@ -444,9 +444,9 @@ class BIM_OT_add_window(bpy.types.Operator, tool.Ifc.Operator): self.report({"ERROR"}, "You need to start IFC project first to create a window.") return {"CANCELLED"} - if context.object is not None: - spawn_location = context.object.location.copy() - context.object.select_set(False) + if context.active_object is not None: + spawn_location = context.active_object.location.copy() + context.active_object.select_set(False) else: spawn_location = bpy.context.scene.cursor.location.copy() diff --git a/src/blenderbim/blenderbim/tool/blender.py b/src/blenderbim/blenderbim/tool/blender.py index e315341eaa..b829b9f16e 100644 --- a/src/blenderbim/blenderbim/tool/blender.py +++ b/src/blenderbim/blenderbim/tool/blender.py @@ -398,12 +398,12 @@ class Blender: ) bmesh.update_edit_mesh(mesh) if not obj: - if not bpy.context.object or bpy.context.object.data != mesh: + if not bpy.context.active_object or bpy.context.active_object.data != mesh: raise Exception( "Error applying bmesh in EDIT object - object is " "not provided and can't be acquired from the context. " ) - obj = bpy.context.object + obj = bpy.context.active_object obj.update_from_editmode() else: bm.to_mesh(mesh) diff --git a/src/blenderbim/scripts/geonodes_modifier_prototype.py b/src/blenderbim/scripts/geonodes_modifier_prototype.py index f536b046f1..77b7b660a6 100644 --- a/src/blenderbim/scripts/geonodes_modifier_prototype.py +++ b/src/blenderbim/scripts/geonodes_modifier_prototype.py @@ -106,7 +106,7 @@ def update_geonodes_modifier(): bm.edges.index_update() bm.faces.index_update() - if bpy.context.object.mode == "EDIT": + if bpy.context.active_object.mode == "EDIT": bmesh.update_edit_mesh(obj.data) else: bm.to_mesh(obj.data) diff --git a/src/blenderbim/test/bim/feature/pset.feature b/src/blenderbim/test/bim/feature/pset.feature index c7ad1db8c9..b4c87ffb1f 100644 --- a/src/blenderbim/test/bim/feature/pset.feature +++ b/src/blenderbim/test/bim/feature/pset.feature @@ -347,7 +347,7 @@ Scenario: Remove pset - multiple objects Scenario: Edit pset length property Given an empty IFC project And I press "mesh.add_clever_stair" - And the variable "pset" is "tool.Pset.get_element_pset(tool.Ifc.get_entity(bpy.context.object), 'Pset_StairFlightCommon').id()" + And the variable "pset" is "tool.Pset.get_element_pset(tool.Ifc.get_entity(bpy.context.active_object), 'Pset_StairFlightCommon').id()" And the variable "si_conversion" is "ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())" And I press "bim.enable_pset_editing(pset_id={pset}, obj='IfcStairFlight/StairFlight', obj_type='Object')" @@ -380,7 +380,7 @@ Scenario: Edit qset length property Given an empty IFC project And I press "mesh.add_clever_stair" And I press "bim.calculate_all_quantities" - And the variable "pset" is "tool.Pset.get_element_pset(tool.Ifc.get_entity(bpy.context.object), 'Qto_StairFlightBaseQuantities').id()" + And the variable "pset" is "tool.Pset.get_element_pset(tool.Ifc.get_entity(bpy.context.active_object), 'Qto_StairFlightBaseQuantities').id()" And the variable "si_conversion" is "ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())" And I press "bim.enable_pset_editing(pset_id={pset}, obj='IfcStairFlight/StairFlight', obj_type='Object')" From 8cbe2980728644cbe22667e44ae88e2a592882c7 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 18 Jul 2023 14:28:18 +1000 Subject: [PATCH 42/49] Utility function to create rotation matrixes in numpy. --- .../ifcopenshell/util/placement.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/ifcopenshell-python/ifcopenshell/util/placement.py b/src/ifcopenshell-python/ifcopenshell/util/placement.py index fe6e8f78bb..7219cd6856 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/placement.py +++ b/src/ifcopenshell-python/ifcopenshell/util/placement.py @@ -101,3 +101,30 @@ def get_storey_elevation(storey): matrix = get_local_placement(storey.ObjectPlacement) return matrix[2][3] return getattr(storey, "Elevation", 0.0) or 0.0 + + +def rotation(angle, axis, is_degrees=True): + theta = np.radians(angle) if is_degrees else angle + cos, sin = np.cos(theta), np.sin(theta) + + if axis == "X": + return np.array([ + [1, 0, 0, 0], + [0, cos, -sin, 0], + [0, sin, cos, 0], + [0, 0, 0, 1] + ]) + elif axis == "Y": + return np.array([ + [cos, 0, sin, 0], + [0, 1, 0, 0], + [-sin, 0, cos, 0], + [0, 0, 0, 1] + ]) + elif axis == "Z": + return np.array([ + [cos, -sin, 0, 0], + [sin, cos, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ]) From 05e05d0d5a2857baafab3277115fcc22c6d2f120 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 18 Jul 2023 18:22:56 +1000 Subject: [PATCH 43/49] Rename sverchok representation to mesh representation --- ...{add_sverchok_representation.py => add_mesh_representation.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/ifcopenshell-python/ifcopenshell/api/geometry/{add_sverchok_representation.py => add_mesh_representation.py} (100%) diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_sverchok_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_mesh_representation.py similarity index 100% rename from src/ifcopenshell-python/ifcopenshell/api/geometry/add_sverchok_representation.py rename to src/ifcopenshell-python/ifcopenshell/api/geometry/add_mesh_representation.py From 0cdce9b2d6a6985bbacd08161030694cda6f38b1 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 18 Jul 2023 18:28:46 +1000 Subject: [PATCH 44/49] Add documentation for basic geometry creation --- src/blenderbim/docs/devs/writing_docs.rst | 45 ++- .../docs/ifcopenshell-python.rst | 1 + .../ifcopenshell-python/geometry_creation.rst | 350 ++++++++++++++++++ .../images/mesh-representation.png | Bin 0 -> 36336 bytes .../images/object-placement-example.png | Bin 0 -> 69781 bytes .../images/object-placement.png | Bin 0 -> 18057 bytes .../images/wall-2pt-representation.png | Bin 0 -> 28520 bytes .../images/wall-representation.png | Bin 0 -> 28262 bytes 8 files changed, 394 insertions(+), 2 deletions(-) create mode 100644 src/ifcopenshell-python/docs/ifcopenshell-python/geometry_creation.rst create mode 100644 src/ifcopenshell-python/docs/ifcopenshell-python/images/mesh-representation.png create mode 100644 src/ifcopenshell-python/docs/ifcopenshell-python/images/object-placement-example.png create mode 100644 src/ifcopenshell-python/docs/ifcopenshell-python/images/object-placement.png create mode 100644 src/ifcopenshell-python/docs/ifcopenshell-python/images/wall-2pt-representation.png create mode 100644 src/ifcopenshell-python/docs/ifcopenshell-python/images/wall-representation.png diff --git a/src/blenderbim/docs/devs/writing_docs.rst b/src/blenderbim/docs/devs/writing_docs.rst index 87374de599..c5be24d5b2 100644 --- a/src/blenderbim/docs/devs/writing_docs.rst +++ b/src/blenderbim/docs/devs/writing_docs.rst @@ -15,7 +15,48 @@ All documentation is written in ReStructured Text and is available in the The following colours and annotation styles should be used for annotating images. All stroke widths are 3px with a corner radius of 3px. Horizontal -underlines are 5px with a corner radius of 2px. The dark green is **39b54a** and -the light green is **d9e021**. +underlines are 5px with a corner radius of 2px. The dark green is ``39b54a`` and +the light green is ``d9e021``. .. image:: documentation-style.png + +Special keywords such as **Technical Terminology** that the user should be +aware of should be bolded, titlecased, and used consistently. You *may* +use italics to emphasize words or phrases. Inline code must be ``quoted`` and +longer code snippets may use code blocks. + +.. code-block:: console + + $ cd /path/to/blenderbim + $ ls + +Be sure to specify the language to enable syntax highlighting. + +.. code-block:: python + + print("Hello, world!") + +A button may be used to point users to a critical sample file or +download. + +.. container:: blockbutton + + `Visit critical link `__ + +.. note:: + + Instead of writing "Note that XYZ ..." you should use notes sparingly to + highlight "gotchas". + +.. tip:: + + Tips may be used to add a useful but optional suggestion. + +.. warning:: + + Warnings may be used to highlight common mistakes. + +.. seealso:: + + See also blocks should be used to reference `further reading + `__ links. diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python.rst b/src/ifcopenshell-python/docs/ifcopenshell-python.rst index 7991426418..ddaa9a33eb 100644 --- a/src/ifcopenshell-python/docs/ifcopenshell-python.rst +++ b/src/ifcopenshell-python/docs/ifcopenshell-python.rst @@ -13,5 +13,6 @@ system, as well as high level analysis and authoring functions. ifcopenshell-python/hello_world ifcopenshell-python/code_examples ifcopenshell-python/geometry_processing + ifcopenshell-python/geometry_creation ifcopenshell-python/geometry_tree ifcopenshell-python/developer_guide diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_creation.rst b/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_creation.rst new file mode 100644 index 0000000000..75b3abf473 --- /dev/null +++ b/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_creation.rst @@ -0,0 +1,350 @@ +Geometry creation +================= + +Walls, doors, slabs, and other physical products in IFC can be represented with +2D or 3D geometry. Most commonly, this geometry is created using graphical +frontends, like the BlenderBIM Add-on. IfcOpenShell can create and edit +geometry with code. + +.. note:: + + Geometry is optional in IFC. For many usecases, geometry is not required, + such as in facility management. + +General concepts +---------------- + +Any IFC element may have a location in the 3D world known as the **Object +Placement**. The **Object Placement** is the "local origin" of the object. This +is sometimes known as the object's center or insertion point in other software. +The **Object Placement** is typically somewhere at a corner, center, or +midpoint of the object. The **Object Placement** may be used to identify a +rough "coordinate" location of the object's start / center, and used as a +center of transformation when moving or rotating the object's geometry. The +object's geometry is always relative to the **Object Placement**. + +.. note:: + + **Object Placements** are optional if the object has no geometry. However, + any object with a geometry must have an **Object Placement**. + + +IFC products may have multiple geometric representations, positioned relative +to the **Object Placement**. For example, a door might have a 3D body of a +"closed door" as one geometric representation, a 2D linework of an "open door" +intended to be shown in a plan, a 3D box showing the clearance of the door for +disabled access, and 3D dashed linework showing the hinge and swing of a door +in an elevation or section. Of course, you might not want to see all this +geometry at the same time. What you see depends on the context you are viewing +the door in. + +For this reason, each one of these geometric representations is called a +**Representation**. Each **Representation** belongs to a **Representation +Context**. The **Representation Context** determines how the **Representation** +is intended to be viewed. For example, a "2D Plan View" might be a +**Representation Context**. This allows the user to choose to see the +appropriate **Representation**. + + +Project units +------------- + +All coordinates in IFC are stored using project units. This means that prior to +creating **Object Placements** or **Representations** you have to define a +project length unit as a minimum. + +Assuming you are creating a project from scratch with code, here is how you +might define units: + +.. code-block:: python + + # You need a project before you can assign units. + run("root.create_entity", model, ifc_class="IfcProject") + + # Let's say we want coordinates to be in millimeters. + length = run("unit.add_si_unit", model, unit_type="LENGTHUNIT", prefix="MILLI") + run("unit.assign_unit", model, units=[length]) + + # Alternatively, you may specify without any arguments to automatically + # create millimeters, square meters, and cubic meters as a convenience for + # testing purposes. Sorry imperial folks, we prioritise metric here. + run("unit.assign_unit", model) + + +Object placements +----------------- + +The **Object Placement** describes **Location** and **Rotation**. The +**Location** is given as an XYZ coordinate, and the **Rotation** is given as +two vectors: a local X axis and a local Z axis vector. The local Y axis vector +is derived via a right-handed coordinate system. This means that the global X +axis points to "Project East", the global Y axis points to "Project North", and +the global Z axis points up (i.e. to the sky). This coordinate system is the +same system used in Blender. + +.. image:: images/object-placement.png + +The recommended way to set an **Object Placement** is to specify the placement +as a 4x4 matrix. You can use the ``numpy`` library to create and edit matrices. +A 4x4 matrix looks like this: + +.. code-block:: + + 1, 0, 0, 0 + 0, 1, 0, 0 + 0, 0, 1, 0 + 0, 0, 0, 1 + +This type of matrix is known as the **Identity Matrix**. It represents no +translation (i.e. a location at the origin of ``0, 0, 0``) and no rotation +(i.e. the X axis is ``1, 0, 0``, the Y axis is ``0, 1, 0``, and the Z axis is +``0, 0, 1``). The numbers in the matrix correlate to the location and rotation +axes as follows: + +.. code-block:: + + XAxis_X, YAxis_X, ZAxis_X, X + XAxis_Y, YAxis_Y, ZAxis_Y, Y + XAxis_Z, YAxis_Z, ZAxis_Z, Z + 0, 0, 0, 1 + +Notice how the last line is always fixed to ``0, 0, 0, 1``. For example, here +is another matrix of an object at ``2, 3, 5`` that is rotated anti-clockwise by +90 degrees. + +.. code-block:: + + 0, -1, 0, 2 + 1, 0, 0, 3 + 0, 0, 1, 5 + 0, 0, 0, 1 + +.. image:: images/object-placement-example.png + +Here's how we might do the same operation with Python code: + +.. code-block:: python + + import numpy + + # Create a wall. Our wall currently has no object placement or representations. + wall = run("root.create_entity", model, ifc_class="IfcWall") + + # Create a 4x4 identity matrix. This matrix is at the origin with no rotation. + matrix = numpy.eye(4) + + # Rotate the matix 90 degrees anti-clockwise around the Z axis (i.e. in plan). + # Anti-clockwise is positive. Clockwise is negative. + matrix = ifcopenshell.util.placement.rotation(90, "Z") @ matrix + + # Set the X, Y, Z coordinates. Notice how we rotate first then translate. + # This is because the rotation origin is always at 0, 0, 0. + matrix[:,3][0:3] = (2, 3, 5) + + # Set our wall's Object Placement using our matrix. + # `is_si=True` states that we are using SI units instead of project units. + run("geometry.edit_object_placement", model, product=wall, matrix=matrix, is_si=True) + +Representation contexts +----------------------- + +As an object may have multiple **Representations**, we need to use +**Representation Contexts** to distinguish the purpose and intended context of +each **Representation**. + +A **Representation Context** is defined in terms of X paramters: + +1. **Context Type**: 3D Model or 2D Plan +2. **Context Identifier**: The purpose of the **Representation** +3. **Target View**: The drafting convention of the **Representation** +4. **Target Scale**: The scale for the **representation** to be shown at + +The **Context Type** must either be set to **Model** for 3D **Representations** +or **Plan** for 2D **Representations**. + +The most common **Context Identifiers** you might use are: + +- Body: for the actual physical shape of the object +- Box: the bounding box of the object (useful for shape analytics) +- Axis: the parametric line determining the shape of the object +- Profile: the elevation silhouette of the object, useful for cutting out holes + for the object to fit into host elements +- Footprint: the plan view silhouette of the object, useful for certain + quantity take-off rules +- Clearance: the clearance zone of the object +- Annotation: symbolic annotations typically used in diagrams or drawings + +The most common **Target Views** you might use are: + +- MODEL_VIEW: for general 3D geometry you might see in a BIM viewer or any + generic fallback representation +- PLAN_VIEW: for 2D geometry you might see in a plan representation +- ELEVATION_VIEW: for 2D geometry you might see in an elevation representation +- SECTION_VIEW: for 2D geometry you might see in a section representation +- GRAPH_VIEW: for 2D or 3D line or frame or path connectivity diagrams you + might use for structural frame analysis, axis-based parametric modeling +- SKETCH_VIEW: for viewing abstract high-level representations such as in + bubble diagrams of spatial topology + +The vast majority of the time, you will only be interested in using a 3D Body +MODEL_VIEW **Representation Context**. + +.. code-block:: python + + # If we plan to store 3D geometry in our IFC model, we have to setup + # a "Model" context. + model3d = run("context.add_context", model, context_type="Model") + + # And/Or, if we plan to store 2D geometry, we need a "Plan" context + plan = run("context.add_context", model, context_type="Plan") + + # Now we setup the subcontexts with each of the geometric "purposes" + # we plan to store in our model. "Body" is by far the most important + # and common context, as most IFC models are assumed to be viewable + # in 3D. + body = run("context.add_context", model, + context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model3d) + + # The 3D Axis subcontext is important if any "axis-based" parametric + # geometry is going to be created. For example, a beam, or column + # may be drawn using a single 3D axis line, and for this we need an + # Axis subcontext. + run("context.add_context", model, + context_type="Model", context_identifier="Axis", target_view="GRAPH_VIEW", parent=model3d) + + # It's also important to have a 2D Axis subcontext for things like + # walls and claddings which can be drawn using a 2D axis line. + run("context.add_context", model, + context_type="Plan", context_identifier="Axis", target_view="GRAPH_VIEW", parent=plan) + + # The 3D Box subcontext is useful for clash detection or shape + # analysis, or even lazy-loading of large models. + run("context.add_context", model, + context_type="Model", context_identifier="Box", target_view="MODEL_VIEW", parent=model3d) + + # A 2D annotation subcontext for plan views are important for door + # swings, window cuts, and symbols for equipment like GPOs, fire + # extinguishers, and so on. + run("context.add_context", model, + context_type="Plan", context_identifier="Annotation", target_view="PLAN_VIEW", parent=plan) + + # You may also create 2D annotation subcontexts for sections and + # elevation views. + run("context.add_context", model, + context_type="Plan", context_identifier="Annotation", target_view="SECTION_VIEW", parent=plan) + run("context.add_context", model, + context_type="Plan", context_identifier="Annotation", target_view="ELEVATION_VIEW", parent=plan) + +Representations +--------------- + +Once you have an **Object Placement** and a **Representation Context**, you can +now create a **Representation**. + +Each **Representations** must choose a geometry modeling technique. For +example, you may specify a mesh-like geometry, which uses vertices, edges, and +faces. Alternatively, you may specify 2D profiles extruded into solid shapes +and potentially having boolean voids and subtractions. You may even specify +single edges and linework without any surfaces or solids. Representations may +even be single points, such as for survey points or structual point +connections. + +After the **Representation** is created, you will need to assign the +**Representation** to the IFC object (e.g. wall, door, slab, etc). Here's the +general pattern in code: + +.. code-block:: python + + # Let's create a new project using millimeters with a single furniture element at the origin. + model = run("project.create_file") + run("root.create_entity", model, ifc_class="IfcProject") + run("unit.assign_unit", model) + + # We want our representation to be the 3D body of the element. + # This representation context is only created once per project. + # You must reuse the same body context every time you create a new representation. + model3d = run("context.add_context", model, context_type="Model") + body = run("context.add_context", model, + context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model3d) + + # Create our element with an object placement. + element = run("root.create_entity", model, ifc_class="IfcFurniture") + run("geometry.edit_object_placement", model, product=element) + + # Let's create our representation! + # See below sections for examples on how to create representations. + representation = ... + + # Assign our new body representation back to our element + run("geometry.assign_representation", model, product=element, representation=representation) + + +Mesh representations +-------------------- + +Mesh **Representations** are specified in terms of a list of vertices, edges, +and faces. The faces may be triangles, quads, or n-gons. Faces may also contain +inner loops, or holes. Mesh **Representations** are most appropriately used for +complex shapes that only need to approximately represent physical products, +such as furniture or equipment, or flat, panellised design (e.g. triangulated +facade elements). Mesh **Representations** are also suitable for box-like +shapes that have bespoke indents, protrusions, TINs, textured, or as-built +geometry. + +In IFC, meshes may be stored as **Faceted BReps**, **Tessellations**, or +**Triangulations** (specifically only for triangles). + +.. code-block:: python + + # These vertices and faces represent a 2m square 1m high pyramid in SI units. + # Note how they are nested lists. Each nested list represents a "mesh". There may be multiple meshes. + vertices = [[(0.,0.,0.), (0.,2.,0.), (2.,2.,0.), (2.,0.,0.), (1.,1.,1.)]] + faces = [[(0,1,2,3), (0,4,1), (1,4,2), (2,4,3), (3,4,0)]] + representation = run("geometry.add_mesh_representation", model, context=body, vertices=vertices, faces=faces) + +.. image:: images/mesh-representation.png + +Wall representations +-------------------- + +Wall-like **Representations** are simple blocks with a length, height, and +thickness. They are most appropriately used for walls, insulation, bulkhead +ends, cladding, and other uniformly thick blocks that extend along an imaginary +2D line in the XY plane. + +.. note:: + + Even though the function is named ``add_wall_representation``, you may use + this geometry for any element, not just walls. + +.. code-block:: python + + # A wall-like representation, 5 meters long, 3 meters high, and 200mm thick + representation = run("geometry.add_wall_representation", model, + context=body, length=5, height=3, thickness=0.2) + +.. image:: images/wall-representation.png + +A wall-like **Representation** always starts at the **Object Placement** and +runs along the local +X axis. The thickness is always along the local Y axis. +This means that if you want the wall-like object to start and end at a +particular point, you have to set the **Object Placement** location and +rotation as appropriate. This can be done using the API: + +.. code-block:: python + + # A wall-like representation starting and ending at a particular 2D point + # It is not necessary to assign the representation after using this function. + run("geometry.create_2pt_wall", model, + element=element, context=body, p1=(1., 1.), p2=(3., 2.), elevation=0, height=3, thickness=0.2) + +.. image:: images/wall-2pt-representation.png + +Profile representations +----------------------- + +Custom representations +---------------------- + +Manual representations +---------------------- diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/images/mesh-representation.png b/src/ifcopenshell-python/docs/ifcopenshell-python/images/mesh-representation.png new file mode 100644 index 0000000000000000000000000000000000000000..b4c2d508735c009e470ff7cc8905c4bfefa17c6f GIT binary patch literal 36336 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 VvpmF%iAVqd002ovPDHLkV1oU&6D9xv literal 0 HcmV?d00001 diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/images/object-placement-example.png b/src/ifcopenshell-python/docs/ifcopenshell-python/images/object-placement-example.png new file mode 100644 index 0000000000000000000000000000000000000000..3b86432bead475107e76efcf5692094e2e2655f3 GIT binary patch literal 69781 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%0DS%5Sm0e(iA#1GQ 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 VJrs~SBQpR1002ovPDHLkV1naJb$tK; literal 0 HcmV?d00001 diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/images/object-placement.png b/src/ifcopenshell-python/docs/ifcopenshell-python/images/object-placement.png new file mode 100644 index 0000000000000000000000000000000000000000..dddb49f23e914443d61557e4b90e442a1a9de561 GIT binary patch literal 18057 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 literal 0 HcmV?d00001 diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/images/wall-representation.png b/src/ifcopenshell-python/docs/ifcopenshell-python/images/wall-representation.png new file mode 100644 index 0000000000000000000000000000000000000000..7fd262f50c2b3af1be15facaaf25cca7ac9aea36 GIT binary patch literal 28262 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 Date: Tue, 18 Jul 2023 18:29:26 +1000 Subject: [PATCH 45/49] New geometry.create_2pt_wall API function to create a wall-like representation from two points. --- .../api/geometry/create_2pt_wall.py | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/ifcopenshell-python/ifcopenshell/api/geometry/create_2pt_wall.py diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/create_2pt_wall.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/create_2pt_wall.py new file mode 100644 index 0000000000..45860fe836 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/create_2pt_wall.py @@ -0,0 +1,73 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2023 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import numpy as np +import ifcopenshell.api +import ifcopenshell.util.unit + + +class Usecase: + def __init__(self, file, element=None, context=None, p1=None, p2=None, elevation=None, height=None, thickness=None): + self.file = file + self.settings = { + "element": element, + "context": context, + "p1": p1, + "p2": p2, + "elevation": elevation, + "height": height, + "thickness": thickness, + } + + def execute(self): + self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file) + + self.settings["p1"] = np.array(self.settings["p1"]) + self.settings["p2"] = np.array(self.settings["p2"]) + + length = float(np.linalg.norm(self.settings["p2"] - self.settings["p1"])) + representation = ifcopenshell.api.run( + "geometry.add_wall_representation", + self.file, + context=self.settings["context"], + length=length, + height=self.settings["height"], + thickness=self.settings["thickness"], + ) + v = self.settings["p2"] - self.settings["p1"] + v /= float(np.linalg.norm(v)) + matrix = np.array( + [ + [v[0], -v[1], 0, self.settings["p1"][0]], + [v[1], v[0], 0, self.settings["p1"][1]], + [0, 0, 1, self.convert_si_to_unit(self.settings["elevation"])], + [0, 0, 0, 1], + ] + ) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=self.settings["element"], matrix=matrix + ) + ifcopenshell.api.run( + "geometry.assign_representation", self.file, product=self.settings["element"], representation=representation + ) + return representation + + def convert_si_to_unit(self, co): + if isinstance(co, (tuple, list)): + return [self.convert_si_to_unit(o) for o in co] + return co / self.settings["unit_scale"] From a095d938383ac02d427429a2eb1fe7234da49b02 Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Tue, 18 Jul 2023 10:43:36 +0200 Subject: [PATCH 46/49] Fix bug when using the search operator (F3) with no active object --- src/blenderbim/blenderbim/bim/module/model/opening.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/opening.py b/src/blenderbim/blenderbim/bim/module/model/opening.py index f55925fa49..c527348eab 100644 --- a/src/blenderbim/blenderbim/bim/module/model/opening.py +++ b/src/blenderbim/blenderbim/bim/module/model/opening.py @@ -552,7 +552,8 @@ class ShowBooleans(Operator, tool.Ifc.Operator, AddObjectHelper): def poll(cls, context): obj = context.active_object return ( - obj.data + obj is not None + and obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.ifc_definition_id ) From 44e98b17a00523c71a837ea1b1604935c32c5d66 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Tue, 18 Jul 2023 15:49:31 +0500 Subject: [PATCH 47/49] Fixing railing/roof bug #3416 Issue occured when you imported .ifc and then would try to edit roof/railing path and then cancel/finish editing. Added safe check to avoid using `change_object_data` in edit mode - https://projects.blender.org/blender/blender/issues/110232 --- .../blenderbim/bim/module/model/railing.py | 28 +++++++++++++++---- .../blenderbim/bim/module/model/roof.py | 9 +++--- src/blenderbim/blenderbim/core/geometry.py | 10 ++++++- src/blenderbim/blenderbim/tool/geometry.py | 2 ++ 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/railing.py b/src/blenderbim/blenderbim/bim/module/model/railing.py index 7382d68977..245a963f61 100644 --- a/src/blenderbim/blenderbim/bim/module/model/railing.py +++ b/src/blenderbim/blenderbim/bim/module/model/railing.py @@ -348,8 +348,7 @@ class EnableEditingRailing(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): obj = context.active_object props = obj.BIMRailingProperties - element = tool.Ifc.get_entity(obj) - data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Railing", "Data")) + data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Railing")["data_dict"] data["path_data"] = json.dumps(data["path_data"]) # required since we could load pset from .ifc and BIMRailingProperties won't be set @@ -366,14 +365,13 @@ class CancelEditingRailing(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): obj = context.active_object - element = tool.Ifc.get_entity(obj) - data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Railing", "Data")) + data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Railing")["data_dict"] props = obj.BIMRailingProperties # restore previous settings since editing was canceled props.set_props_kwargs_from_ifc_data(data) update_railing_modifier_bmesh(context) - + props.is_editing = False return {"FINISHED"} @@ -438,6 +436,9 @@ class EnableEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): obj = context.active_object props = obj.BIMRailingProperties + data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Railing")["data_dict"] + # required since we could load pset from .ifc and BIMRoofProperties won't be set + props.set_props_kwargs_from_ifc_data(data) props.is_editing_path = True update_railing_modifier_bmesh(context) @@ -456,9 +457,24 @@ def cancel_editing_railing_path(context): ProfileDecorator.uninstall() props.is_editing_path = False - update_railing_modifier_bmesh(context) if bpy.context.active_object.mode == "EDIT": bpy.ops.object.mode_set(mode="OBJECT") + + if props.railing_type == "FRAMELESS_PANEL": + update_railing_modifier_bmesh(context) + else: + element = tool.Ifc.get_entity(obj) + body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") + blenderbim.core.geometry.switch_representation( + tool.Ifc, + tool.Geometry, + obj=obj, + representation=body, + should_reload=True, + is_global=True, + should_sync_changes_first=False, + ) + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/model/roof.py b/src/blenderbim/blenderbim/bim/module/model/roof.py index 8f1f3c753a..13d4b402c6 100644 --- a/src/blenderbim/blenderbim/bim/module/model/roof.py +++ b/src/blenderbim/blenderbim/bim/module/model/roof.py @@ -593,8 +593,7 @@ class EnableEditingRoof(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): obj = context.active_object props = obj.BIMRoofProperties - element = tool.Ifc.get_entity(obj) - data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Roof", "Data")) + data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Roof")["data_dict"] # required since we could load pset from .ifc and BIMRoofProperties won't be set props.set_props_kwargs_from_ifc_data(data) props.is_editing = True @@ -608,8 +607,7 @@ class CancelEditingRoof(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): obj = context.active_object - element = tool.Ifc.get_entity(obj) - data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Roof", "Data")) + data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Roof")["data_dict"] props = obj.BIMRoofProperties # restore previous settings since editing was canceled @@ -650,6 +648,9 @@ class EnableEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): obj = context.active_object props = obj.BIMRoofProperties + data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Roof")["data_dict"] + # required since we could load pset from .ifc and BIMRoofProperties won't be set + props.set_props_kwargs_from_ifc_data(data) props.is_editing_path = True update_roof_modifier_bmesh(context) diff --git a/src/blenderbim/blenderbim/core/geometry.py b/src/blenderbim/blenderbim/core/geometry.py index 01dcde0555..b25ec4703a 100644 --- a/src/blenderbim/blenderbim/core/geometry.py +++ b/src/blenderbim/blenderbim/core/geometry.py @@ -88,7 +88,15 @@ def switch_representation( should_sync_changes_first=False, apply_openings=True, ): - """Function can switch to representation that wasn't yet assigned to that object. See #2766.""" + """Function can switch to representation that wasn't yet assigned to that object. See #2766. + + `should_sync_changes_first` - sync ifc representation with current state of `obj.data`; + + `should_reload` - reload `obj.data` from ifc representation; + + `is_global` - replace mesh data for all users of `obj.data`, not just `obj`; + + """ if should_sync_changes_first and geometry.is_edited(obj) and not geometry.is_box_representation(representation): representation_id = geometry.get_representation_id(representation) geometry.run_geometry_update_representation(obj=obj) diff --git a/src/blenderbim/blenderbim/tool/geometry.py b/src/blenderbim/blenderbim/tool/geometry.py index 3512539d35..5369741dbb 100644 --- a/src/blenderbim/blenderbim/tool/geometry.py +++ b/src/blenderbim/blenderbim/tool/geometry.py @@ -35,6 +35,8 @@ class Geometry(blenderbim.core.tool.Geometry): @classmethod def change_object_data(cls, obj, data, is_global=False): if is_global: + if obj.mode == "EDIT": + raise Exception("user_remap is not supported in EDIT mode") obj.data.user_remap(data) else: obj.data = data From 66ed95fbc183813fe48740ffddfbc969c2802681 Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Tue, 18 Jul 2023 14:00:30 +0200 Subject: [PATCH 48/49] Add ifc modifier utilities to tool.Blender --- src/blenderbim/blenderbim/tool/blender.py | 29 +++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/blenderbim/blenderbim/tool/blender.py b/src/blenderbim/blenderbim/tool/blender.py index b829b9f16e..c6f08fe548 100644 --- a/src/blenderbim/blenderbim/tool/blender.py +++ b/src/blenderbim/blenderbim/tool/blender.py @@ -456,3 +456,32 @@ class Blender: return bpy.ops.object.mode_set(mode="EDIT_GPENCIL", toggle=True) else: return {"CANCELLED"} + + class Modifier: + @classmethod + def is_railing(cls, element): + return tool.Pset.get_element_pset(element, "BBIM_Railing") + + @classmethod + def is_roof(cls, element): + return tool.Pset.get_element_pset(element, "BBIM_Roof") + + @classmethod + def is_window(cls, element): + return tool.Pset.get_element_pset(element, "BBIM_Window") + + @classmethod + def is_door(cls, element): + return tool.Pset.get_element_pset(element, "BBIM_Door") + + @classmethod + def is_stair(cls, element): + return tool.Pset.get_element_pset(element, "BBIM_Stair") + + @classmethod + def is_editing_parameters(cls, obj): + return obj.BIMRailingProperties.is_editing or obj.BIMRoofProperties.is_editing + + @classmethod + def is_modifier_with_non_editable_path(cls, element): + return cls.is_stair(element) or cls.is_door(element) or cls.is_window(element) From 9c7b073414816eb51a18c33e5e96ce0293aa5c1c Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Tue, 18 Jul 2023 14:02:56 +0200 Subject: [PATCH 49/49] Refactor edit mode toggle logic for profile based representations --- .../bim/module/geometry/operator.py | 77 +++++++------------ 1 file changed, 27 insertions(+), 50 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 874aca5e05..6d1f671d2d 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -1020,7 +1020,6 @@ class RefreshAggregate(bpy.types.Operator): return parents def duplicate_children(entity): - pset = ifcopenshell.util.element.get_pset(entity, "BBIM_Aggregate_Data") pset_data = json.loads(pset["Data"])[0] instance_of = pset_data["instance_of"][0] @@ -1239,6 +1238,7 @@ class OverridePasteBuffer(bpy.types.Operator): class OverrideModeSetEdit(bpy.types.Operator): + bl_description = "Switch from Object mode to Edit mode" bl_idname = "bim.override_mode_set_edit" bl_label = "IFC Mode Set Edit" bl_options = {"REGISTER", "UNDO"} @@ -1247,7 +1247,7 @@ class OverrideModeSetEdit(bpy.types.Operator): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - objs = context.selected_objects or ([context.active_object] if context.active_object else []) + selected_objs = context.selected_objects or ([context.active_object] if context.active_object else []) active_obj = context.active_object if context.active_object: @@ -1257,19 +1257,15 @@ class OverrideModeSetEdit(bpy.types.Operator): if element and element.is_a("IfcRelSpaceBoundary"): return bpy.ops.bim.enable_editing_boundary_geometry() - for obj in objs: + for obj in selected_objs: if not obj: continue - if not obj.data: obj.select_set(False) continue - element = tool.Ifc.get_entity(obj) if not element: continue - - # We are switching from OBJECT to EDIT mode. usage_type = tool.Model.get_usage_type(element) if usage_type is not None and usage_type not in ("PROFILE", "LAYER3"): # Parametric objects shall not be edited as meshes as they @@ -1277,57 +1273,37 @@ class OverrideModeSetEdit(bpy.types.Operator): # constraints. obj.select_set(False) continue - representation = tool.Geometry.get_active_representation(obj) if not representation: continue - - if ( - tool.Pset.get_element_pset(element, "BBIM_Door") - or tool.Pset.get_element_pset(element, "BBIM_Window") - or tool.Pset.get_element_pset(element, "BBIM_Stair") - ): + if tool.Blender.Modifier.is_modifier_with_non_editable_path(element): obj.select_set(False) continue + is_profile = True if usage_type == "PROFILE": - if len(context.selected_objects) == 1: - bpy.ops.bim.hotkey(hotkey="A_E", description="") - return {"FINISHED"} - else: - self.report({"INFO"}, "Only a single profile-based representation can be edited at a time.") - obj.select_set(False) - continue - - # TODO: refactor repetitive code - if tool.Pset.get_element_pset(element, "BBIM_Roof"): - if len(context.selected_objects) == 1: - bpy.ops.bim.enable_editing_roof_path() - return {"FINISHED"} - else: - self.report({"INFO"}, "Only a single profile-based representation can be edited at a time.") - obj.select_set(False) - continue - - if tool.Pset.get_element_pset(element, "BBIM_Railing"): - if len(context.selected_objects) == 1: - if obj.BIMRailingProperties.is_editing == 1: - self.report({"INFO"}, "Can't edit path while the modifier parameters are being modified") - return {"FINISHED"} - bpy.ops.bim.enable_editing_railing_path() - return {"FINISHED"} - else: - self.report({"INFO"}, "Only a single profile-based representation can be edited at a time.") - obj.select_set(False) - continue - - if ( + operator = lambda: bpy.ops.bim.hotkey(hotkey="A_E") + elif ( tool.Geometry.is_profile_based(obj.data) or usage_type == "LAYER3" or tool.Geometry.is_swept_profile(representation) ): - if len(context.selected_objects) == 1: - bpy.ops.bim.hotkey(hotkey="S_E", description="") + operator = lambda: bpy.ops.bim.hotkey(hotkey="S_E") + elif tool.Blender.Modifier.is_editing_parameters(obj): + # This should go BEFORE the modifiers + self.report({"INFO"}, "Can't edit path while the modifier parameters are being modified") + obj.select_set(False) + continue + elif tool.Blender.Modifier.is_roof(element): + operator = lambda: bpy.ops.bim.enable_editing_roof_path() + elif tool.Blender.Modifier.is_railing(element): + operator = lambda: bpy.ops.bim.enable_editing_railing_path() + else: + is_profile = False + if is_profile: + if len(context.selected_objects) == 1 and context.active_object == context.selected_objects[0]: + tool.Blender.select_and_activate_single_object(context, obj) + operator() return {"FINISHED"} else: self.report({"INFO"}, "Only a single profile-based representation can be edited at a time.") @@ -1353,17 +1329,18 @@ class OverrideModeSetEdit(bpy.types.Operator): obj.select_set(False) continue - if not context.selected_objects or len(context.selected_objects) != len(objs): + if not context.selected_objects or len(context.selected_objects) != len(selected_objs): # We are trying to edit at least one non-mesh-like object : Display a hint to the user - self.report({"INFO"}, "Only mesh-compatible representations may be edited in edit mode.") + self.report({"INFO"}, "Only mesh-compatible representations may be edited concurrently in edit mode.") if context.active_object not in context.selected_objects: # The active object is non-mesh-like. Set a valid object (or None) as active context.view_layer.objects.active = context.selected_objects[0] if context.selected_objects else None if context.active_object: return tool.Blender.toggle_edit_mode(context) + # Restore the selection if nothing worked - for obj in objs: + for obj in selected_objs: obj.select_set(True) context.view_layer.objects.active = active_obj return {"FINISHED"}