From 7a6df49348b317ab4407a1b8ee717029227f8e48 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 10 Jan 2022 17:30:49 +1100 Subject: [PATCH] Basic implementation to bulk convert relevant IFC classes to a Brick model --- .../blenderbim/bim/module/brick/__init__.py | 1 + .../blenderbim/bim/module/brick/operator.py | 13 +++++++++++ .../blenderbim/bim/module/brick/ui.py | 3 ++- src/blenderbim/blenderbim/core/brick.py | 8 +++++++ src/blenderbim/blenderbim/core/tool.py | 3 +++ src/blenderbim/blenderbim/tool/brick.py | 21 +++++++++++++++++ src/blenderbim/test/bim/feature/brick.feature | 13 +++++++++++ src/blenderbim/test/core/test_brick.py | 15 ++++++++++++ src/blenderbim/test/tool/test_brick.py | 23 +++++++++++++++++++ 9 files changed, 99 insertions(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/bim/module/brick/__init__.py b/src/blenderbim/blenderbim/bim/module/brick/__init__.py index f248848480..63b5a981ae 100644 --- a/src/blenderbim/blenderbim/bim/module/brick/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/brick/__init__.py @@ -25,6 +25,7 @@ classes = ( operator.AssignBrickReference, operator.CloseBrickProject, operator.ConvertBrickProject, + operator.ConvertIfcToBrick, operator.LoadBrickProject, operator.RewindBrickClass, operator.ViewBrickClass, diff --git a/src/blenderbim/blenderbim/bim/module/brick/operator.py b/src/blenderbim/blenderbim/bim/module/brick/operator.py index dd5cf9eef3..ba7f6e21d4 100644 --- a/src/blenderbim/blenderbim/bim/module/brick/operator.py +++ b/src/blenderbim/blenderbim/bim/module/brick/operator.py @@ -141,3 +141,16 @@ class AddBrickFeed(bpy.types.Operator, Operator): source=[o for o in context.selected_objects if o != context.active_object][0], destination=context.active_object, ) + + +class ConvertIfcToBrick(bpy.types.Operator, Operator): + bl_idname = "bim.convert_ifc_to_brick" + bl_label = "Convert IFC To Brick" + bl_options = {"REGISTER", "UNDO"} + + def _execute(self, context): + props = context.scene.BIMBrickProperties + library = None + if props.libraries: + library = tool.Ifc.get().by_id(int(props.libraries)) + core.convert_ifc_to_brick(tool.Brick, namespace=props.namespace, library=library) diff --git a/src/blenderbim/blenderbim/bim/module/brick/ui.py b/src/blenderbim/blenderbim/bim/module/brick/ui.py index 89411f5fae..ace0490417 100644 --- a/src/blenderbim/blenderbim/bim/module/brick/ui.py +++ b/src/blenderbim/blenderbim/bim/module/brick/ui.py @@ -98,8 +98,9 @@ class BIM_PT_ifc_brickschema_references(Panel): row.prop(self.props, "libraries") row.operator("bim.convert_brick_project", text="", icon="ADD") - row = self.layout.row() + row = self.layout.row(align=True) row.operator("bim.assign_brick_reference", icon="ADD") + row.operator("bim.convert_ifc_to_brick", icon="IMPORT") if not BrickschemaReferencesData.data["references"]: row = self.layout.row() diff --git a/src/blenderbim/blenderbim/core/brick.py b/src/blenderbim/blenderbim/core/brick.py index 072763f358..a4c7c801c9 100644 --- a/src/blenderbim/blenderbim/core/brick.py +++ b/src/blenderbim/blenderbim/core/brick.py @@ -79,3 +79,11 @@ def add_brick_feed(ifc, brick, source=None, destination=None): source_element = ifc.get_entity(source) destination_element = ifc.get_entity(destination) brick.add_feed(brick.get_brick(source_element), brick.get_brick(destination_element)) + + +def convert_ifc_to_brick(brick, namespace=None, library=None): + for obj, element in brick.get_convertable_brick_objects_and_elements(): + brick_class = brick.get_brick_class(element) + if not brick_class: + continue + brick.run_add_brick(obj=obj, namespace=namespace, brick_class=brick_class, library=library) diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 29ceb85880..477ea3a095 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -52,9 +52,11 @@ class Brick: def clear_project(cls): pass def export_brick_attributes(cls, brick_uri): pass def get_brick(cls, element): pass + def get_brick_class(cls, element): pass def get_brick_path(cls): pass def get_brick_path_name(cls): pass def get_brickifc_project(cls): pass + def get_convertable_brick_objects_and_elements(cls): pass def get_item_class(cls, item): pass def get_library_brick_reference(cls, library, brick_uri): pass def get_namespace(cls, uri): pass @@ -62,6 +64,7 @@ class Brick: def import_brick_items(cls, brick_class): pass def load_brick_file(cls, filepath): pass def pop_brick_breadcrumb(cls): pass + def run_add_brick(cls, obj=None, namespace=None, brick_class=None, library=None): pass def run_assign_brick_reference(cls, obj=None, library=None, brick_uri=None): pass def select_browser_item(cls, item): pass def set_active_brick_class(cls, brick_class): pass diff --git a/src/blenderbim/blenderbim/tool/brick.py b/src/blenderbim/blenderbim/tool/brick.py index 6386923358..4b3a632a87 100644 --- a/src/blenderbim/blenderbim/tool/brick.py +++ b/src/blenderbim/blenderbim/tool/brick.py @@ -18,6 +18,8 @@ import os import bpy +import ifcopenshell +import ifcopenshell.util.brick import blenderbim.core.tool import blenderbim.tool as tool @@ -110,6 +112,10 @@ class Brick(blenderbim.core.tool.Brick): if tool.Ifc.get_schema() != "IFC2X3" and "#" in rel.RelatingLibrary.Identification: return rel.RelatingLibrary.Identification + @classmethod + def get_brick_class(cls, element): + return ifcopenshell.util.brick.get_brick_type(element) + @classmethod def get_brick_path(cls): return BrickStore.path @@ -137,6 +143,15 @@ class Brick(blenderbim.core.tool.Brick): if results: return results[0][0].toPython() + @classmethod + def get_convertable_brick_objects_and_elements(cls): + results = [] + for element in ifcopenshell.util.brick.get_brick_elements(tool.Ifc.get()): + obj = tool.Ifc.get_object(element) + if obj: + results.append((obj, element)) + return results + @classmethod def get_item_class(cls, item): query = BrickStore.graph.query( @@ -229,6 +244,12 @@ class Brick(blenderbim.core.tool.Brick): bpy.context.scene.BIMBrickProperties.brick_breadcrumbs.remove(last_index) return name + @classmethod + def run_add_brick(cls, obj=None, namespace=None, brick_class=None, library=None): + return blenderbim.core.brick.add_brick( + tool.Ifc, tool.Brick, obj=obj, namespace=namespace, brick_class=brick_class, library=library + ) + @classmethod def run_assign_brick_reference(cls, obj=None, library=None, brick_uri=None): return blenderbim.core.brick.assign_brick_reference( diff --git a/src/blenderbim/test/bim/feature/brick.feature b/src/blenderbim/test/bim/feature/brick.feature index 4913d5bc3f..0758e0af80 100644 --- a/src/blenderbim/test/bim/feature/brick.feature +++ b/src/blenderbim/test/bim/feature/brick.feature @@ -96,3 +96,16 @@ Scenario: Add brick feed And additionally the object "IfcAirTerminalBox/Cube" is selected When I press "bim.add_brick_feed" Then nothing happens + +Scenario: Convert IFC to brick + Given an empty IFC project + And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')" + And I press "bim.convert_brick_project" + And I set "scene.BIMBrickProperties.namespace" to "http://example.org/digitaltwin#" + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcUnitaryEquipment" + And I set "scene.BIMRootProperties.ifc_predefined_type" to "AIRHANDLER" + And I press "bim.assign_class" + When I press "bim.convert_ifc_to_brick" + Then nothing happens diff --git a/src/blenderbim/test/core/test_brick.py b/src/blenderbim/test/core/test_brick.py index f3963dc63b..aefcfa7f4b 100644 --- a/src/blenderbim/test/core/test_brick.py +++ b/src/blenderbim/test/core/test_brick.py @@ -136,3 +136,18 @@ class TestAddBrickFeed: brick.get_brick("destination_element").should_be_called().will_return("destination_brick") brick.add_feed("source_brick", "destination_brick").should_be_called() subject.add_brick_feed(ifc, brick, source="source", destination="destination") + + +class TestConvertIfcToBrick: + def test_run(self, brick): + brick.get_convertable_brick_objects_and_elements().should_be_called().will_return([("obj", "element")]) + brick.get_brick_class("element").should_be_called().will_return("brick_class") + brick.run_add_brick( + obj="obj", namespace="namespace", brick_class="brick_class", library="library" + ).should_be_called() + subject.convert_ifc_to_brick(brick, namespace="namespace", library="library") + + def test_not_converting_an_element_where_we_cannot_find_the_corresponding_brick_class(self, brick): + brick.get_convertable_brick_objects_and_elements().should_be_called().will_return([("obj", "element")]) + brick.get_brick_class("element").should_be_called().will_return(None) + subject.convert_ifc_to_brick(brick, namespace="namespace", library="library") diff --git a/src/blenderbim/test/tool/test_brick.py b/src/blenderbim/test/tool/test_brick.py index 0d0baa4ecc..7f2bee6b7a 100644 --- a/src/blenderbim/test/tool/test_brick.py +++ b/src/blenderbim/test/tool/test_brick.py @@ -186,6 +186,13 @@ class TestGetBrick(NewFile): assert subject.get_brick(element) == "http://example.org/digitaltwin#globalid" +class TestGetBrickClass(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + element = ifc.createIfcAirTerminalBox() + assert subject.get_brick_class(element) == "https://brickschema.org/schema/Brick#TerminalUnit" + + class TestGetBrickPath(NewFile): def test_run(self): TestLoadBrickFile().test_run() @@ -208,6 +215,17 @@ class TestGetBrickifcProject(NewFile): ) +class TestGetConvertableBrickObjectsAndElements(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + element = ifc.createIfcAirTerminalBox() + obj = bpy.data.objects.new("Object", None) + tool.Ifc.link(element, obj) + ifc.createIfcWall() + assert subject.get_convertable_brick_objects_and_elements() == [(obj, element)] + + class TestGetItemClass(NewFile): def test_run(self): TestLoadBrickFile().test_run() @@ -286,6 +304,11 @@ class TestPopBrickBreadcrumb(NewFile): assert bpy.context.scene.BIMBrickProperties.brick_breadcrumbs[0].name == "foo" +class TestRunAddBrick(NewFile): + def test_nothing(self): + pass + + class TestRunAssignBrickReference(NewFile): def test_nothing(self): pass