From 88bddb9531d8978d5a8de9b48fcbb813b21e61ad Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 8 Feb 2023 12:29:40 +1100 Subject: [PATCH] See #2520. You can now duplicate drawings. --- .../blenderbim/bim/module/drawing/__init__.py | 1 + .../blenderbim/bim/module/drawing/operator.py | 24 +++++++++++++++++-- .../blenderbim/bim/module/drawing/ui.py | 4 +++- src/blenderbim/blenderbim/core/drawing.py | 13 ++++++++++ src/blenderbim/blenderbim/core/tool.py | 8 ++++--- src/blenderbim/blenderbim/tool/drawing.py | 12 +++++++++- .../test/bim/feature/drawing.feature | 18 +++++++++++--- src/blenderbim/test/core/test_drawing.py | 18 ++++++++++++++ src/blenderbim/test/tool/test_drawing.py | 18 ++++++++++++++ .../ifcopenshell/api/root/copy_class.py | 1 + .../ifcopenshell/util/element.py | 5 ++-- .../test/api/root/test_copy_class.py | 8 +++++++ 12 files changed, 117 insertions(+), 13 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/__init__.py b/src/blenderbim/blenderbim/bim/module/drawing/__init__.py index 088e630753..9046efd317 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/__init__.py @@ -42,6 +42,7 @@ classes = ( operator.DisableEditingSchedules, operator.DisableEditingSheets, operator.DisableEditingText, + operator.DuplicateDrawing, operator.EditAssignedProduct, operator.EditText, operator.EditVectorStyle, diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index 969dd08a0a..3757f55784 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -103,6 +103,22 @@ class AddDrawing(bpy.types.Operator, Operator): pass +class DuplicateDrawing(bpy.types.Operator, Operator): + bl_idname = "bim.duplicate_drawing" + bl_label = "Duplicate Drawing" + bl_options = {"REGISTER", "UNDO"} + drawing: bpy.props.IntProperty() + + def _execute(self, context): + self.props = context.scene.DocProperties + core.duplicate_drawing(tool.Ifc, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing)) + try: + drawing = tool.Ifc.get().by_id(self.props.active_drawing_id) + core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing) + except: + pass + + class CreateDrawing(bpy.types.Operator): """Creates/refreshes a .svg drawing @@ -385,7 +401,9 @@ class CreateDrawing(bpy.types.Operator): DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True ) geom_settings.set_context_ids(body_context) - it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements) + it = ifcopenshell.geom.iterator( + geom_settings, ifc, multiprocessing.cpu_count(), include=elements + ) it.set_cache(cache) processed = set() for elem in self.yield_from_iterator(it): @@ -401,7 +419,9 @@ class CreateDrawing(bpy.types.Operator): DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True ) geom_settings.set_context_ids(annotation_context) - it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements) + it = ifcopenshell.geom.iterator( + geom_settings, ifc, multiprocessing.cpu_count(), include=elements + ) it.set_cache(cache) processed = set() for elem in self.yield_from_iterator(it): diff --git a/src/blenderbim/blenderbim/bim/module/drawing/ui.py b/src/blenderbim/blenderbim/bim/module/drawing/ui.py index 692e7e2141..6ded9c3307 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/ui.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/ui.py @@ -172,7 +172,9 @@ class BIM_PT_drawings(Panel): row = self.layout.row(align=True) col = row.column() col.alignment = "LEFT" - col.operator("bim.remove_drawing", icon="X", text="").drawing = active_drawing.ifc_definition_id + row2 = col.row(align=True) + row2.operator("bim.remove_drawing", icon="X", text="").drawing = active_drawing.ifc_definition_id + row2.operator("bim.duplicate_drawing", icon="COPYDOWN", text="").drawing = active_drawing.ifc_definition_id col = row.column() col.alignment = "RIGHT" op = row.operator("bim.open_view", icon="URL", text="") diff --git a/src/blenderbim/blenderbim/core/drawing.py b/src/blenderbim/blenderbim/core/drawing.py index e79fc5ac15..657a2fbdc7 100644 --- a/src/blenderbim/blenderbim/core/drawing.py +++ b/src/blenderbim/blenderbim/core/drawing.py @@ -167,6 +167,19 @@ def add_drawing(ifc, collector, drawing, target_view=None, location_hint=None): drawing.import_drawings() +def duplicate_drawing(ifc, drawing_tool, drawing=None): + drawing_name = drawing_tool.ensure_unique_drawing_name(drawing_tool.get_name(drawing)) + new_drawing = ifc.run("root.copy_class", product=drawing) + drawing_tool.copy_drawing_representation(drawing, new_drawing) + drawing_tool.set_name(new_drawing, drawing_name) + ifc.run("group.unassign_group", group=drawing_tool.get_drawing_group(new_drawing), product=new_drawing) + group = ifc.run("group.add_group") + ifc.run("group.edit_group", group=group, attributes={"Name": drawing_name, "ObjectType": "DRAWING"}) + ifc.run("group.assign_group", group=group, products=[new_drawing]) + drawing_tool.import_drawings() + return new_drawing + + def remove_drawing(ifc, drawing_tool, drawing=None): collection = drawing_tool.get_drawing_collection(drawing) group = drawing_tool.get_drawing_group(drawing) diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 5ddbd13686..7af448590c 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -154,6 +154,8 @@ class Document: @interface class Drawing: + def activate_view(cls, camera): pass + def copy_drawing_representation(cls, source, dest): pass def create_annotation_object(cls, drawing, object_type): pass def create_camera(cls, name, matrix): pass def create_svg_schedule(cls, schedule): pass @@ -195,17 +197,17 @@ class Drawing: def import_schedules(cls): pass def import_sheets(cls): pass def import_text_attributes(cls, obj): pass + def is_camera_orthographic(cls): pass + def is_drawing_active(cls): pass def open_spreadsheet(cls, uri): pass def open_svg(cls, filepath): pass def run_root_assign_class(cls, obj=None, ifc_class=None, predefined_type=None, should_add_representation=True, context=None, ifc_representation_class=None): pass def select_assigned_product(cls, drawing): pass def set_drawing_collection_name(cls, group, collection): pass + def set_name(cls, element, name): pass def show_decorations(cls): pass def sync_object_placement(cls, obj): pass def update_text_value(cls, obj): pass - def is_drawing_active(cls): pass - def is_camera_orthographic(cls): pass - def activate_view(cls, camera): pass @interface diff --git a/src/blenderbim/blenderbim/tool/drawing.py b/src/blenderbim/blenderbim/tool/drawing.py index ee410cf7e7..2fc49256bc 100644 --- a/src/blenderbim/blenderbim/tool/drawing.py +++ b/src/blenderbim/blenderbim/tool/drawing.py @@ -36,6 +36,12 @@ import blenderbim.bim.module.drawing.helper as helper class Drawing(blenderbim.core.tool.Drawing): + @classmethod + def copy_drawing_representation(cls, source, dest): + dest.Representation = ifcopenshell.util.element.copy_deep( + tool.Ifc.get(), source.Representation, exclude=["IfcGeometricRepresentationContext"] + ) + @classmethod def create_annotation_object(cls, drawing, object_type): data_type = { @@ -410,6 +416,10 @@ class Drawing(blenderbim.core.tool.Drawing): def set_drawing_collection_name(cls, group, collection): collection.name = f"IfcGroup/{group.Name}" + @classmethod + def set_name(cls, element, name): + element.Name = name + @classmethod def show_decorations(cls): bpy.context.scene.DocProperties.should_draw_decorations = True @@ -948,4 +958,4 @@ class Drawing(blenderbim.core.tool.Drawing): project_collection.children["Views"].children[camera.users_collection[0].name].hide_viewport = False bpy.data.collections.get(camera.users_collection[0].name).hide_render = False - tool.Spatial.set_active_object(camera) \ No newline at end of file + tool.Spatial.set_active_object(camera) diff --git a/src/blenderbim/test/bim/feature/drawing.feature b/src/blenderbim/test/bim/feature/drawing.feature index 8fa405714c..28b5c61626 100644 --- a/src/blenderbim/test/bim/feature/drawing.feature +++ b/src/blenderbim/test/bim/feature/drawing.feature @@ -1,6 +1,18 @@ @drawing Feature: Drawing +Scenario: Duplicate drawing + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And the variable "wall1" is "IfcStore.get_file().by_type('IfcWall')[-1].id()" + And I press "bim.add_drawing" + And the variable "drawing" is "IfcStore.get_file().by_type('IfcAnnotation')[0].id()" + When I press "bim.duplicate_drawing(drawing={drawing})" + Then nothing happens + Scenario: Create drawing Given an empty IFC project And I add a cube @@ -12,7 +24,7 @@ Scenario: Create drawing And the variable "drawing" is "IfcStore.get_file().by_type('IfcAnnotation')[0].id()" And I set "scene.DocProperties.active_drawing_index" to "0" And I press "bim.activate_view(drawing={drawing})" - And I press "bim.create_drawing" + When I press "bim.create_drawing" Then nothing happens Scenario: Create drawing after deleting a duplicated object @@ -33,5 +45,5 @@ Scenario: Create drawing after deleting a duplicated object And I press "bim.create_drawing" And the object "IfcWall/Cube" is selected And I press "object.delete(use_global=False)" - And I press "bim.create_drawing" - Then nothing happens \ No newline at end of file + When I press "bim.create_drawing" + Then nothing happens diff --git a/src/blenderbim/test/core/test_drawing.py b/src/blenderbim/test/core/test_drawing.py index dc0e1aa681..41beb95d0f 100644 --- a/src/blenderbim/test/core/test_drawing.py +++ b/src/blenderbim/test/core/test_drawing.py @@ -243,6 +243,24 @@ class TestAddDrawing: subject.add_drawing(ifc, collector, drawing, target_view="target_view", location_hint="location_hint") +class TestDuplicateDrawing: + def test_run(self, ifc, drawing): + drawing.get_name("drawing").should_be_called().will_return("name") + drawing.ensure_unique_drawing_name("name").should_be_called().will_return("unique_name") + ifc.run("root.copy_class", product="drawing").should_be_called().will_return("new_drawing") + drawing.copy_drawing_representation("drawing", "new_drawing").should_be_called() + drawing.set_name("new_drawing", "unique_name").should_be_called() + drawing.get_drawing_group("new_drawing").should_be_called().will_return("group") + ifc.run("group.unassign_group", group="group", product="new_drawing").should_be_called() + ifc.run("group.add_group").should_be_called().will_return("new_group") + ifc.run( + "group.edit_group", group="new_group", attributes={"Name": "unique_name", "ObjectType": "DRAWING"} + ).should_be_called() + ifc.run("group.assign_group", group="new_group", products=["new_drawing"]).should_be_called() + drawing.import_drawings().should_be_called() + subject.duplicate_drawing(ifc, drawing, drawing="drawing") + + class TestRemoveDrawing: def test_run(self, ifc, drawing): drawing.get_drawing_collection("drawing").should_be_called().will_return("collection") diff --git a/src/blenderbim/test/tool/test_drawing.py b/src/blenderbim/test/tool/test_drawing.py index 18eb948078..a2ffabc918 100644 --- a/src/blenderbim/test/tool/test_drawing.py +++ b/src/blenderbim/test/tool/test_drawing.py @@ -32,6 +32,16 @@ class TestImplementsTool(NewFile): assert isinstance(subject(), blenderbim.core.tool.Drawing) +class TestCopyDrawingRepresentation(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + source = ifc.createIfcAnnotation(Representation=ifc.createIfcProductDefinitionShape()) + dest = ifc.createIfcAnnotation() + subject.copy_drawing_representation(source, dest) + assert dest.Representation.is_a("IfcProductDefinitionShape") + + class TestCreateAnnotationObject(NewFile): def test_nothing(self): pass @@ -562,6 +572,14 @@ class TestSetDrawingCollectionName(NewFile): assert collection.name == "IfcGroup/Foobaz" +class TestSetName(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + drawing = ifc.createIfcAnnotation() + subject.set_name(drawing, "Name") + assert drawing.Name == "Name" + + class TestShowDecorations(NewFile): def test_run(self): bpy.context.scene.DocProperties.should_draw_decorations = False diff --git a/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py b/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py index fe66e9f052..c5cf71a6eb 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py +++ b/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py @@ -37,6 +37,7 @@ class Usecase: * Voids are duplicated too * The copy will have the same material as the original. Parametric material set usages will be copied. + * The copy will be part of the same groups as the original. Be warned that: diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py index 540da598fb..4861af986a 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/element.py +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -502,8 +502,7 @@ def get_decomposition(element): def get_grouped_by(element): - """ - Retrieves all subelements of an element based on the group. + """Retrieves all subelements of an element based on the group. :param element: The IFC element :return: All subelements of the group @@ -513,7 +512,7 @@ def get_grouped_by(element): .. code:: python element = file.by_type("IfcGroup")[0] - subelements = ifcopenshell.util.element.get_group(element) + subelements = ifcopenshell.util.element.get_grouped_by(element) """ queue = [element] results = [] 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 e46e7f6ae7..0afe8c2133 100644 --- a/src/ifcopenshell-python/test/api/root/test_copy_class.py +++ b/src/ifcopenshell-python/test/api/root/test_copy_class.py @@ -198,3 +198,11 @@ class TestCopyClass(test.bootstrap.IFC4): assert element2.ConnectedFrom assert not new.ConnectedTo assert not new.ConnectedFrom + + def test_maintaining_group_relationships(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + group = ifcopenshell.api.run("group.add_group", self.file) + ifcopenshell.api.run("group.assign_group", self.file, group=group, products=[element]) + 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