diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index d829c6f184..a38bd04498 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -601,7 +601,7 @@ class IfcImporter: ) ) checkpoint = time.time() - #self.update_progress(((total / approx_total_products) * progress_range) + start_progress) + # self.update_progress(((total / approx_total_products) * progress_range) + start_progress) shape = iterator.get() if shape: product = self.file.by_id(shape.guid) @@ -1149,7 +1149,7 @@ class IfcImporter: container = ifcopenshell.util.element.get_container(element) if container: - if element.is_a("IfcGrid"): # TODO: refactor into a more holistic collection mode feature + if element.is_a("IfcGrid"): # TODO: refactor into a more holistic collection mode feature grid_collection = bpy.data.collections.get(obj.name) if grid_collection: # Just in case we run into invalid grids from Revit self.collections[container.GlobalId].children.link(grid_collection) diff --git a/src/blenderbim/blenderbim/bim/module/context/operator.py b/src/blenderbim/blenderbim/bim/module/context/operator.py index 9417865eb8..a3ffa13a1e 100644 --- a/src/blenderbim/blenderbim/bim/module/context/operator.py +++ b/src/blenderbim/blenderbim/bim/module/context/operator.py @@ -18,6 +18,8 @@ import bpy import ifcopenshell.api +import blenderbim.bim.tool +import blenderbim.core.context from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.context.data import Data @@ -34,16 +36,12 @@ class AddSubcontext(bpy.types.Operator): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "context.add_context", - self.file, - **{ - "context": self.context or context.scene.BIMProperties.available_contexts, - "subcontext": self.subcontext or context.scene.BIMProperties.available_subcontexts, - "target_view": self.target_view or context.scene.BIMProperties.available_target_views, - }, - ) + blenderbim.core.context.AddContext( + blenderbim.bim.tool.Ifc, + context=self.context or context.scene.BIMProperties.available_contexts, + subcontext=self.subcontext or context.scene.BIMProperties.available_subcontexts, + target_view=self.target_view or context.scene.BIMProperties.available_target_views, + ).execute() Data.load(IfcStore.get_file()) return {"FINISHED"} @@ -59,8 +57,8 @@ class RemoveSubcontext(bpy.types.Operator): def _execute(self, context): self.file = IfcStore.get_file() - ifcopenshell.api.run( - "context.remove_context", self.file, **{"context": self.file.by_id(self.ifc_definition_id)} - ) - Data.load(IfcStore.get_file()) + blenderbim.core.context.RemoveContext( + blenderbim.bim.tool.Ifc, context=self.file.by_id(self.ifc_definition_id) + ).execute() + Data.load(self.file) return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/tool.py b/src/blenderbim/blenderbim/bim/tool.py new file mode 100644 index 0000000000..477f5b4a78 --- /dev/null +++ b/src/blenderbim/blenderbim/bim/tool.py @@ -0,0 +1,32 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +import ifcopenshell.api +from blenderbim.bim.ifc import IfcStore + + +class Ifc: + @classmethod + def run(cls, command, **kwargs): + return ifcopenshell.api.run(command, IfcStore.get_file(), **kwargs) + + +class Blender: + @classmethod + def get_ifc(cls): + return "ifc" diff --git a/src/blenderbim/blenderbim/core/__init__.py b/src/blenderbim/blenderbim/core/__init__.py new file mode 100644 index 0000000000..fb33323db9 --- /dev/null +++ b/src/blenderbim/blenderbim/core/__init__.py @@ -0,0 +1,17 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . diff --git a/src/blenderbim/blenderbim/core/context.py b/src/blenderbim/blenderbim/core/context.py new file mode 100644 index 0000000000..6f258c3356 --- /dev/null +++ b/src/blenderbim/blenderbim/core/context.py @@ -0,0 +1,42 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + + +class AddContext: + def __init__(self, ifc, context=None, subcontext=None, target_view=None): + self.ifc = ifc + self.context = context + self.subcontext = subcontext + self.target_view = target_view + + def execute(self): + return self.ifc.run( + "context.add_context", + context=self.context, + subcontext=self.subcontext, + target_view=self.target_view, + ) + + +class RemoveContext: + def __init__(self, ifc, context=None): + self.ifc = ifc + self.context = context + + def execute(self): + self.ifc.run("context.remove_context", context=self.context) diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py new file mode 100644 index 0000000000..c5bca78893 --- /dev/null +++ b/src/blenderbim/blenderbim/core/tool.py @@ -0,0 +1,30 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + + +class Ifc: + @classmethod + def run(cls, command, **kwargs): + """Runs an operation on the active IFC dataset""" + pass + + +class Blender: + @classmethod + def get_ifc(cls): + pass diff --git a/src/blenderbim/setup_pytest.py b/src/blenderbim/setup_pytest.py index 6ac6b6080c..0dba1f5264 100644 --- a/src/blenderbim/setup_pytest.py +++ b/src/blenderbim/setup_pytest.py @@ -21,9 +21,9 @@ import sys py_exec = str(sys.executable) # Ensure pip is installed -subprocess.call([py_exec, "-m", "ensurepip", "--user" ]) +subprocess.call([py_exec, "-m", "ensurepip", "--user"]) # Update pip -subprocess.call([py_exec, "-m", "pip", "install", "--upgrade", "pip" ]) +subprocess.call([py_exec, "-m", "pip", "install", "--upgrade", "pip"]) # Install packages -subprocess.call([py_exec,"-m", "pip", "install", f"--target={py_exec[:-14]}" + "lib", "pytest"]) -subprocess.call([py_exec,"-m", "pip", "install", f"--target={py_exec[:-14]}" + "lib", "pytest-blender"]) +subprocess.call([py_exec, "-m", "pip", "install", f"--target={py_exec[:-14]}" + "lib", "pytest"]) +subprocess.call([py_exec, "-m", "pip", "install", f"--target={py_exec[:-14]}" + "lib", "pytest-blender"]) diff --git a/src/blenderbim/test/bim/bootstrap.py b/src/blenderbim/test/bim/bootstrap.py index 888e5873a9..f2559ceac8 100644 --- a/src/blenderbim/test/bim/bootstrap.py +++ b/src/blenderbim/test/bim/bootstrap.py @@ -325,6 +325,7 @@ definitions = { 'the object "(.*)" should display as "(.*)"': the_object_name_should_display_as_mode, 'the object "(.*)" has "([0-9]+)" vertices': the_object_name_has_number_vertices, 'the object "(.*)" is at "(.*)"': the_object_name_is_at_location, + "nothing interesting happens": lambda: None, } diff --git a/src/blenderbim/test/bim/module/context/test_operator.py b/src/blenderbim/test/bim/module/context/test_operator.py new file mode 100644 index 0000000000..ab2c97f4cd --- /dev/null +++ b/src/blenderbim/test/bim/module/context/test_operator.py @@ -0,0 +1,40 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +import test.bim.bootstrap + + +class TestAddSubcontext(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_adding_a_subcontext(self): + return """ + Given an empty IFC project + When I press "bim.add_subcontext(context='Model')" + Then nothing interesting happens + """ + + +class TestRemoveSubcontext(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_removing_a_subcontext(self): + return """ + Given an empty IFC project + And the variable "context_id" is "IfcStore.get_file().by_type('IfcGeometricRepresentationContext')[0].id()" + When I press "bim.remove_subcontext(ifc_definition_id={context_id})" + Then nothing interesting happens + """ diff --git a/src/blenderbim/test/core/__init__.py b/src/blenderbim/test/core/__init__.py new file mode 100644 index 0000000000..fb33323db9 --- /dev/null +++ b/src/blenderbim/test/core/__init__.py @@ -0,0 +1,17 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . diff --git a/src/blenderbim/test/core/bootstrap.py b/src/blenderbim/test/core/bootstrap.py new file mode 100644 index 0000000000..839dd2ee46 --- /dev/null +++ b/src/blenderbim/test/core/bootstrap.py @@ -0,0 +1,109 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +import json +import pytest +import blenderbim.core.tool + + +@pytest.fixture +def ifc(): + prophet = Prophecy(blenderbim.core.tool.Ifc) + yield prophet + prophet.verify() + + +@pytest.fixture +def blender(): + prophet = Prophecy(blenderbim.core.tool.Blender) + yield prophet + prophet.verify() + + +def subject(sus): + def decorate(cls): + cls.sus = sus + return cls + + return decorate + + +class Spec: + @pytest.fixture(autouse=True) + def setup(self): + self.subject = None + + def construct_with(self, *args, **kwargs): + self.subject = self.sus(*args, **kwargs) + return self.subject + + def predict(self, cls): + return Prophecy(cls) + + +class Prophecy: + def __init__(self, cls): + self.subject = cls + self.predictions = [] + self.calls = [] + self.return_values = {} + self.should_call = None + + def __getattr__(self, attr): + if not hasattr(self.subject, attr): + raise AttributeError(f"Prophecy has no attribute {attr}") + + def decorate(*args, **kwargs): + call = {"name": attr, "args": args, "kwargs": kwargs} + try: + key = json.dumps(call, sort_keys=True) + self.calls.append(call) + if key in self.return_values: + return self.return_values[key] + except: + pass + return self + + return decorate + + def should(self): + self.should_call = self.calls.pop() + return self + + def be_called(self, number=None): + self.predictions.append({"type": "SHOULD_BE_CALLED", "number": number, "call": self.should_call}) + return self + + def return_with(self, value): + key = json.dumps(self.should_call, sort_keys=True) + self.return_values[key] = value + return self + + def verify(self): + for prediction in self.predictions: + if prediction["type"] == "SHOULD_BE_CALLED": + self.verify_should_be_called(prediction) + + def verify_should_be_called(self, prediction): + if prediction["number"]: + count = self.calls.count(prediction["call"]) + if count != prediction["number"]: + raise Exception(f"Called {count}: {prediction}") + else: + if prediction["call"] not in self.calls: + raise Exception("Not called", prediction) diff --git a/src/blenderbim/test/core/test_context.py b/src/blenderbim/test/core/test_context.py new file mode 100644 index 0000000000..dc7d645032 --- /dev/null +++ b/src/blenderbim/test/core/test_context.py @@ -0,0 +1,38 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +import blenderbim.core.context +from test.core.bootstrap import Spec, subject, ifc, blender + + +@subject(blenderbim.core.context.AddContext) +class TestAddContext(Spec): + def test_adding_a_context(self, ifc): + self.construct_with(ifc, context="Model", subcontext="Body", target_view="MODEL_VIEW") + ifc.run( + "context.add_context", context="Model", subcontext="Body", target_view="MODEL_VIEW" + ).should().be_called().return_with("context") + assert self.subject.execute() == "context" + + +@subject(blenderbim.core.context.RemoveContext) +class TestRemoveContext(Spec): + def test_removing_a_context(self, ifc): + self.construct_with(ifc, context="context") + ifc.run("context.remove_context", context="context").should().be_called() + self.subject.execute()