diff --git a/src/blenderbim/blenderbim/bim/module/drawing/__init__.py b/src/blenderbim/blenderbim/bim/module/drawing/__init__.py index 1b5b99cba7..aa8d855bdc 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/__init__.py @@ -20,42 +20,43 @@ import bpy from . import ui, prop, operator, handler, gizmos classes = ( - operator.AddDrawing, - operator.CreateDrawing, - operator.AddAnnotation, - operator.AddSheet, - operator.OpenSheet, - operator.AddDrawingToSheet, - operator.CreateSheets, - operator.OpenView, - operator.OpenViewCamera, - operator.ActivateView, - operator.SelectDocIfcFile, - operator.GenerateReferences, - operator.ResizeText, - operator.AddVariable, - operator.RemoveVariable, - operator.RemoveDrawing, - operator.AddDrawingStyle, - operator.RemoveDrawingStyle, - operator.SaveDrawingStyle, operator.ActivateDrawingStyle, - operator.EditVectorStyle, - operator.RemoveSheet, - operator.AddSchedule, - operator.RemoveSchedule, - operator.SelectScheduleFile, - operator.BuildSchedule, - operator.AddScheduleToSheet, + operator.ActivateView, + operator.AddAnnotation, + operator.AddDrawing, + operator.AddDrawingStyle, operator.AddDrawingStyleAttribute, - operator.RemoveDrawingStyleAttribute, - operator.RefreshDrawingList, + operator.AddDrawingToSheet, + operator.AddSchedule, + operator.AddScheduleToSheet, + operator.AddSectionsAnnotations, + operator.AddSheet, + operator.BuildSchedule, operator.CleanWireframes, operator.CopyGrid, - operator.AddSectionsAnnotations, - operator.EditText, + operator.CreateDrawing, + operator.CreateSheets, operator.DisableEditingText, + operator.DisableEditingTextProduct, + operator.EditText, + operator.EditTextProduct, + operator.EditVectorStyle, operator.EnableEditingText, + operator.EnableEditingTextProduct, + operator.GenerateReferences, + operator.OpenSheet, + operator.OpenView, + operator.OpenViewCamera, + operator.RefreshDrawingList, + operator.RemoveDrawing, + operator.RemoveDrawingStyle, + operator.RemoveDrawingStyleAttribute, + operator.RemoveSchedule, + operator.RemoveSheet, + operator.ResizeText, + operator.SaveDrawingStyle, + operator.SelectDocIfcFile, + operator.SelectScheduleFile, prop.Variable, prop.Drawing, prop.Schedule, diff --git a/src/blenderbim/blenderbim/bim/module/drawing/data.py b/src/blenderbim/blenderbim/bim/module/drawing/data.py index 7da44213ce..91b1f73473 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/data.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/data.py @@ -31,7 +31,7 @@ class TextData: @classmethod def load(cls): - cls.data = {"attributes": cls.attributes()} + cls.data = {"attributes": cls.attributes(), "relating_product": cls.relating_product()} cls.is_loaded = True @classmethod @@ -45,3 +45,12 @@ class TextData: {"name": "Literal", "value": text_literal.Literal}, {"name": "BoxAlignment", "value": text_literal.BoxAlignment}, ] + + @classmethod + def relating_product(cls): + element = tool.Ifc.get_entity(bpy.context.active_object) + if not element or not element.is_a("IfcAnnotation") or element.ObjectType not in ["TEXT", "TEXT_LEADER"]: + return + for rel in element.HasAssignments: + if rel.is_a("IfcRelAssignsToProduct"): + return rel.RelatingProduct.Name or "Unnamed" diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index f9cf880779..629dd653cd 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -894,27 +894,6 @@ class ResizeText(bpy.types.Operator): return {"FINISHED"} -class AddVariable(bpy.types.Operator): - bl_idname = "bim.add_variable" - bl_label = "Add Variable" - bl_options = {"REGISTER", "UNDO"} - - def execute(self, context): - context.active_object.data.BIMTextProperties.variables.add() - return {"FINISHED"} - - -class RemoveVariable(bpy.types.Operator): - bl_idname = "bim.remove_variable" - bl_label = "Remove Variable" - bl_options = {"REGISTER", "UNDO"} - index: bpy.props.IntProperty() - - def execute(self, context): - context.active_object.data.BIMTextProperties.variables.remove(self.index) - return {"FINISHED"} - - class RemoveDrawing(bpy.types.Operator): bl_idname = "bim.remove_drawing" bl_label = "Remove Drawing" @@ -1411,3 +1390,33 @@ class DisableEditingText(bpy.types.Operator, Operator): def _execute(self, context): core.disable_editing_text(tool.Drawing, obj=context.active_object) + + +class EditTextProduct(bpy.types.Operator, Operator): + bl_idname = "bim.edit_text_product" + bl_label = "Edit Text Product" + bl_options = {"REGISTER", "UNDO"} + + def _execute(self, context): + product = None + if context.active_object.BIMTextProperties.relating_product: + product = tool.Ifc.get_entity(context.active_object.BIMTextProperties.relating_product) + core.edit_text_product(tool.Ifc, tool.Drawing, obj=context.active_object, product=product) + + +class EnableEditingTextProduct(bpy.types.Operator, Operator): + bl_idname = "bim.enable_editing_text_product" + bl_label = "Enable Editing Text Product" + bl_options = {"REGISTER", "UNDO"} + + def _execute(self, context): + core.enable_editing_text_product(tool.Drawing, obj=context.active_object) + + +class DisableEditingTextProduct(bpy.types.Operator, Operator): + bl_idname = "bim.disable_editing_text_product" + bl_label = "Disable Editing Text Product" + bl_options = {"REGISTER", "UNDO"} + + def _execute(self, context): + core.disable_editing_text_product(tool.Drawing, obj=context.active_object) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/prop.py b/src/blenderbim/blenderbim/bim/module/drawing/prop.py index dd0ae2ee73..db6ec2a2dc 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/prop.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/prop.py @@ -358,6 +358,7 @@ class BIMCameraProperties(PropertyGroup): class BIMTextProperties(PropertyGroup): is_editing: BoolProperty(name="Is Editing", default=False) + is_editing_product: BoolProperty(name="Is Editing Product", default=False) attributes: CollectionProperty(name="Attributes", type=Attribute) value: StringProperty(name="Value", default="TEXT") font_size: EnumProperty( @@ -381,5 +382,4 @@ class BIMTextProperties(PropertyGroup): update=refreshFontSize, name="Symbol", ) - related_element: PointerProperty(name="Related Element", type=bpy.types.Object) - variables: CollectionProperty(name="Variables", type=Variable) + relating_product: PointerProperty(name="Relating Product", type=bpy.types.Object) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/ui.py b/src/blenderbim/blenderbim/bim/module/drawing/ui.py index d0b3485270..45c92e553b 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/ui.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/ui.py @@ -255,7 +255,6 @@ class BIM_PT_text(Panel): if not TextData.is_loaded: TextData.load() - self.layout.use_property_split = True props = context.active_object.BIMTextProperties if props.is_editing: @@ -272,22 +271,20 @@ class BIM_PT_text(Panel): row.label(text=attribute["name"]) row.label(text=attribute["value"]) + if props.is_editing_product: + row = self.layout.row(align=True) + row.prop(props, "relating_product", text="") + row.operator("bim.edit_text_product", icon="CHECKMARK", text="") + row.operator("bim.disable_editing_text_product", icon="CANCEL", text="") + else: + row = self.layout.row(align=True) + row.label(text=TextData.data["relating_product"] or "No Relating Product", icon="OBJECT_DATA") + row.operator("bim.enable_editing_text_product", icon="GREASEPENCIL", text="") + row = self.layout.row() row.prop(props, "font_size") row = self.layout.row() row.prop(props, "symbol") - row = self.layout.row() - row.prop(props, "related_element") - - row = self.layout.row() - row.operator("bim.add_variable") - - for index, variable in enumerate(props.variables): - row = self.layout.row(align=True) - row.prop(variable, "name") - row.operator("bim.remove_variable", icon="X", text="").index = index - row = self.layout.row() - row.prop(variable, "prop_key") class BIM_PT_annotation_utilities(Panel): diff --git a/src/blenderbim/blenderbim/core/drawing.py b/src/blenderbim/blenderbim/core/drawing.py index 56fa27d524..15b3b7be61 100644 --- a/src/blenderbim/blenderbim/core/drawing.py +++ b/src/blenderbim/blenderbim/core/drawing.py @@ -34,3 +34,25 @@ def edit_text(ifc, drawing, obj=None): ) drawing.update_text_value(obj) drawing.disable_editing_text(obj) + + +def enable_editing_text_product(drawing, obj=None): + drawing.enable_editing_text_product(obj) + drawing.import_text_product(obj) + + +def disable_editing_text_product(drawing, obj=None): + drawing.disable_editing_text_product(obj) + + +def edit_text_product(ifc, drawing, obj=None, product=None): + element = ifc.get_entity(obj) + existing_product = drawing.get_text_product(element) + if existing_product == product: + return + if existing_product: + ifc.run("drawing.unassign_product", relating_product=existing_product, related_object=element) + if product: + ifc.run("drawing.assign_product", relating_product=product, related_object=element) + drawing.update_text_value(obj) + drawing.disable_editing_text_product(obj) diff --git a/src/blenderbim/blenderbim/core/geometry.py b/src/blenderbim/blenderbim/core/geometry.py index ed7bc4efe9..c7bb5e9291 100644 --- a/src/blenderbim/blenderbim/core/geometry.py +++ b/src/blenderbim/blenderbim/core/geometry.py @@ -38,7 +38,7 @@ def add_representation( edit_object_placement(ifc, geometry, surveyor, obj=obj) data = geometry.get_object_data(obj) - if not data: + if not data and ifc_representation_class != "IfcTextLiteral": return representation = ifc.run( @@ -66,11 +66,12 @@ def add_representation( ifc.run("geometry.assign_representation", product=element, representation=representation) - data = geometry.duplicate_object_data(obj) - geometry.change_object_data(obj, data, is_global=True) - name = geometry.get_representation_name(representation) - geometry.rename_object(data, name) - geometry.link(representation, data) + if data: + data = geometry.duplicate_object_data(obj) + geometry.change_object_data(obj, data, is_global=True) + name = geometry.get_representation_name(representation) + geometry.rename_object(data, name) + geometry.link(representation, data) return representation diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index b2b5c73a3c..8c603d78fc 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -99,10 +99,14 @@ class Debug: @interface class Drawing: def disable_editing_text(cls, obj): pass + def disable_editing_text_product(cls, obj): pass def enable_editing_text(cls, obj): pass + def enable_editing_text_product(cls, obj): pass def export_text_literal_attributes(cls, obj): pass def get_text_literal(cls, obj): pass + def get_text_product(cls, element): pass def import_text_attributes(cls, obj): pass + def import_text_product(cls, obj): pass def update_text_value(cls, obj): pass diff --git a/src/blenderbim/blenderbim/tool/drawing.py b/src/blenderbim/blenderbim/tool/drawing.py index bf885b633b..2256b0b49a 100644 --- a/src/blenderbim/blenderbim/tool/drawing.py +++ b/src/blenderbim/blenderbim/tool/drawing.py @@ -27,10 +27,18 @@ class Drawing(blenderbim.core.tool.Drawing): def disable_editing_text(cls, obj): obj.BIMTextProperties.is_editing = False + @classmethod + def disable_editing_text_product(cls, obj): + obj.BIMTextProperties.is_editing_product = False + @classmethod def enable_editing_text(cls, obj): obj.BIMTextProperties.is_editing = True + @classmethod + def enable_editing_text_product(cls, obj): + obj.BIMTextProperties.is_editing_product = True + @classmethod def export_text_literal_attributes(cls, obj): return blenderbim.bim.helper.export_attributes(obj.BIMTextProperties.attributes) @@ -47,6 +55,12 @@ class Drawing(blenderbim.core.tool.Drawing): if items: return items[0] + @classmethod + def get_text_product(cls, element): + for rel in element.HasAssignments: + if rel.is_a("IfcRelAssignsToProduct"): + return rel.RelatingProduct + @classmethod def import_text_attributes(cls, obj): props = obj.BIMTextProperties @@ -54,6 +68,15 @@ class Drawing(blenderbim.core.tool.Drawing): text = cls.get_text_literal(obj) blenderbim.bim.helper.import_attributes2(text, props.attributes) + @classmethod + def import_text_product(cls, obj): + element = tool.Ifc.get_entity(obj) + product = cls.get_text_product(element) + if product: + obj.BIMTextProperties.relating_product = tool.Ifc.get_object(product) + else: + obj.BIMTextProperties.relating_product = None + @classmethod def update_text_value(cls, obj): element = cls.get_text_literal(obj) diff --git a/src/blenderbim/test/core/test_drawing.py b/src/blenderbim/test/core/test_drawing.py index b16f72e53c..19c06a0df4 100644 --- a/src/blenderbim/test/core/test_drawing.py +++ b/src/blenderbim/test/core/test_drawing.py @@ -41,3 +41,27 @@ class TestEditText: drawing.update_text_value("obj").should_be_called() drawing.disable_editing_text("obj").should_be_called() subject.edit_text(ifc, drawing, obj="obj") + + +class TestEnableEditingTextProduct: + def test_run(self, drawing): + drawing.enable_editing_text_product("obj").should_be_called() + drawing.import_text_product("obj").should_be_called() + subject.enable_editing_text_product(drawing, obj="obj") + + +class TestDisableEditingTextProduct: + def test_run(self, drawing): + drawing.disable_editing_text_product("obj").should_be_called() + subject.disable_editing_text_product(drawing, obj="obj") + + +class TestEditTextProduct: + def test_run(self, ifc, drawing): + ifc.get_entity("obj").should_be_called().will_return("element") + drawing.get_text_product("element").should_be_called().will_return("existing_product") + ifc.run("drawing.unassign_product", relating_product="existing_product", related_object="element").should_be_called() + ifc.run("drawing.assign_product", relating_product="product", related_object="element").should_be_called() + drawing.update_text_value("obj").should_be_called() + drawing.disable_editing_text_product("obj").should_be_called() + subject.edit_text_product(ifc, drawing, obj="obj", product="product") diff --git a/src/blenderbim/test/tool/test_drawing.py b/src/blenderbim/test/tool/test_drawing.py index 4ef3e4c2d8..876872b762 100644 --- a/src/blenderbim/test/tool/test_drawing.py +++ b/src/blenderbim/test/tool/test_drawing.py @@ -37,6 +37,14 @@ class TestDisableEditingText(NewFile): assert obj.BIMTextProperties.is_editing == False +class TestDisableEditingTextProduct(NewFile): + def test_run(self): + obj = bpy.data.objects.new("Object", None) + obj.BIMTextProperties.is_editing_product = True + subject.disable_editing_text_product(obj) + assert obj.BIMTextProperties.is_editing_product == False + + class TestEnableEditingText(NewFile): def test_run(self): obj = bpy.data.objects.new("Object", None) @@ -44,6 +52,13 @@ class TestEnableEditingText(NewFile): assert obj.BIMTextProperties.is_editing == True +class TestEnableEditingTextProduct(NewFile): + def test_run(self): + obj = bpy.data.objects.new("Object", None) + subject.enable_editing_text_product(obj) + assert obj.BIMTextProperties.is_editing_product == True + + class TestExportTextLiteralAttributes(NewFile): def test_run(self): TestImportTextAttributes().test_run() @@ -69,6 +84,16 @@ class TestGetTextLiteral(NewFile): assert subject.get_text_literal(obj) == item +class TestGetTextProduct(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + wall = ifc.createIfcWall() + label = ifc.createIfcAnnotation() + ifcopenshell.api.run("drawing.assign_product", ifc, relating_product=wall, related_object=label) + assert subject.get_text_product(label) == wall + + class TestImportTextAttributes(NewFile): def test_run(self): ifc = ifcopenshell.file() @@ -88,6 +113,30 @@ class TestImportTextAttributes(NewFile): assert props.attributes.get("BoxAlignment").string_value == "BoxAlignment" +class TestImportTextProduct(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + wall = ifc.createIfcWall() + label = ifc.createIfcAnnotation() + ifcopenshell.api.run("drawing.assign_product", ifc, relating_product=wall, related_object=label) + wall_obj = bpy.data.objects.new("Object", None) + label_obj = bpy.data.objects.new("Object", None) + tool.Ifc.link(wall, wall_obj) + tool.Ifc.link(label, label_obj) + subject.import_text_product(label_obj) + assert label_obj.BIMTextProperties.relating_product == wall_obj + + def test_doing_nothing_if_no_product_to_import(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + label = ifc.createIfcAnnotation() + label_obj = bpy.data.objects.new("Object", None) + tool.Ifc.link(label, label_obj) + subject.import_text_product(label_obj) + assert label_obj.BIMTextProperties.relating_product is None + + class TestUpdateTextValue(NewFile): def test_run(self): TestGetTextLiteral().test_run() diff --git a/src/ifcopenshell-python/ifcopenshell/api/drawing/assign_product.py b/src/ifcopenshell-python/ifcopenshell/api/drawing/assign_product.py new file mode 100644 index 0000000000..4437913ffc --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/drawing/assign_product.py @@ -0,0 +1,43 @@ +import ifcopenshell +import ifcopenshell.api + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = { + "relating_product": None, + "related_object": None, + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + if self.settings["related_object"].HasAssignments: + for assignment in self.settings["related_object"].HasAssignments: + if ( + assignment.is_a("IfcRelAssignsToProduct") + and assignment.RelatingProduct == self.settings["relating_product"] + ): + return + + referenced_by = None + if self.settings["relating_product"].ReferencedBy: + referenced_by = self.settings["relating_product"].ReferencedBy[0] + + if referenced_by: + related_objects = list(referenced_by.RelatedObjects) + related_objects.append(self.settings["related_object"]) + referenced_by.RelatedObjects = related_objects + ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": referenced_by}) + else: + referenced_by = self.file.create_entity( + "IfcRelAssignsToProduct", + **{ + "GlobalId": ifcopenshell.guid.new(), + "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), + "RelatedObjects": [self.settings["related_object"]], + "RelatingProduct": self.settings["relating_product"], + } + ) + return referenced_by diff --git a/src/ifcopenshell-python/ifcopenshell/api/drawing/unassign_product.py b/src/ifcopenshell-python/ifcopenshell/api/drawing/unassign_product.py new file mode 100644 index 0000000000..5d28bc43d0 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/drawing/unassign_product.py @@ -0,0 +1,25 @@ +import ifcopenshell +import ifcopenshell.api + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = { + "relating_product": None, + "related_object": None, + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + for rel in self.settings["related_object"].HasAssignments or []: + if not rel.is_a("IfcRelAssignsToProduct") or rel.RelatingProduct != self.settings["relating_product"]: + continue + if len(rel.RelatedObjects) == 1: + return self.file.remove(rel) + related_objects = list(rel.RelatedObjects) + related_objects.remove(self.settings["related_object"]) + rel.RelatedObjects = related_objects + ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel}) + return rel diff --git a/src/ifcopenshell-python/test/api/drawing/test_assign_product.py b/src/ifcopenshell-python/test/api/drawing/test_assign_product.py new file mode 100644 index 0000000000..bad7ffb205 --- /dev/null +++ b/src/ifcopenshell-python/test/api/drawing/test_assign_product.py @@ -0,0 +1,20 @@ +import test.bootstrap +import ifcopenshell.api + + +class TestAssignProduct(test.bootstrap.IFC4): + def test_assigning_a_product(self): + wall = self.file.createIfcWall() + label = self.file.createIfcAnnotation() + label2 = self.file.createIfcAnnotation() + ifcopenshell.api.run("drawing.assign_product", self.file, relating_product=wall, related_object=label) + assert wall.ReferencedBy[0].RelatedObjects == (label,) + ifcopenshell.api.run("drawing.assign_product", self.file, relating_product=wall, related_object=label2) + assert wall.ReferencedBy[0].RelatedObjects == (label, label2) + + def test_not_assigning_twice(self): + wall = self.file.createIfcWall() + label = self.file.createIfcAnnotation() + ifcopenshell.api.run("drawing.assign_product", self.file, relating_product=wall, related_object=label) + ifcopenshell.api.run("drawing.assign_product", self.file, relating_product=wall, related_object=label) + assert wall.ReferencedBy[0].RelatedObjects == (label,) diff --git a/src/ifcopenshell-python/test/api/drawing/test_unassign_product.py b/src/ifcopenshell-python/test/api/drawing/test_unassign_product.py new file mode 100644 index 0000000000..d43deb322a --- /dev/null +++ b/src/ifcopenshell-python/test/api/drawing/test_unassign_product.py @@ -0,0 +1,11 @@ +import test.bootstrap +import ifcopenshell.api + + +class TestUnassignProduct(test.bootstrap.IFC4): + def test_unassigning_a_product(self): + wall = self.file.createIfcWall() + label = self.file.createIfcAnnotation() + ifcopenshell.api.run("drawing.assign_product", self.file, relating_product=wall, related_object=label) + ifcopenshell.api.run("drawing.unassign_product", self.file, relating_product=wall, related_object=label) + assert len(self.file.by_type("IfcRelAssignsToProduct")) == 0