diff --git a/src/blenderbim/blenderbim/bim/module/context/__init__.py b/src/blenderbim/blenderbim/bim/module/context/__init__.py index 5021ee0da6..4915e3157e 100644 --- a/src/blenderbim/blenderbim/bim/module/context/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/context/__init__.py @@ -20,8 +20,11 @@ import bpy from . import ui, prop, operator classes = ( - operator.AddSubcontext, - operator.RemoveSubcontext, + operator.AddContext, + operator.DisableEditingContext, + operator.EditContext, + operator.EnableEditingContext, + operator.RemoveContext, prop.BIMContextProperties, ui.BIM_PT_context, ) diff --git a/src/blenderbim/blenderbim/bim/module/context/data.py b/src/blenderbim/blenderbim/bim/module/context/data.py index 111da3dae7..48f237a2bc 100644 --- a/src/blenderbim/blenderbim/bim/module/context/data.py +++ b/src/blenderbim/blenderbim/bim/module/context/data.py @@ -1,3 +1,22 @@ +# 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 bpy import blenderbim.tool as tool @@ -15,7 +34,13 @@ class ContextData: results = [] for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False): results.append( - {"id": context.id(), "context_type": context.ContextType, "subcontexts": cls.get_subcontexts(context)} + { + "id": context.id(), + "context_type": context.ContextType, + "subcontexts": cls.get_subcontexts(context), + "is_editing": bpy.context.scene.BIMContextProperties.active_context_id == context.id(), + "props": bpy.context.scene.BIMContextProperties.context_attributes, + } ) return results @@ -29,6 +54,8 @@ class ContextData: "context_type": subcontext.ContextType, "context_identifier": subcontext.ContextIdentifier, "target_view": subcontext.TargetView, + "is_editing": bpy.context.scene.BIMContextProperties.active_context_id == subcontext.id(), + "props": bpy.context.scene.BIMContextProperties.context_attributes, } ) return results diff --git a/src/blenderbim/blenderbim/bim/module/context/operator.py b/src/blenderbim/blenderbim/bim/module/context/operator.py index fb084d03f5..b13c6625ac 100644 --- a/src/blenderbim/blenderbim/bim/module/context/operator.py +++ b/src/blenderbim/blenderbim/bim/module/context/operator.py @@ -31,23 +31,58 @@ class Operator: return {"FINISHED"} -class AddSubcontext(bpy.types.Operator, Operator): - bl_idname = "bim.add_subcontext" +class AddContext(bpy.types.Operator, Operator): + bl_idname = "bim.add_context" bl_label = "Add Subcontext" bl_options = {"REGISTER", "UNDO"} - context: bpy.props.StringProperty() - subcontext: bpy.props.StringProperty() + context_type: bpy.props.StringProperty() + context_identifier: bpy.props.StringProperty() target_view: bpy.props.StringProperty() + parent: bpy.props.IntProperty() def _execute(self, context): - core.add_context(tool.Ifc, context=self.context, subcontext=self.subcontext, target_view=self.target_view) + core.add_context( + tool.Ifc, + context_type=self.context_type, + context_identifier=self.context_identifier, + target_view=self.target_view, + parent=tool.Ifc.get().by_id(self.parent) if self.parent else None, + ) -class RemoveSubcontext(bpy.types.Operator, Operator): - bl_idname = "bim.remove_subcontext" +class RemoveContext(bpy.types.Operator, Operator): + bl_idname = "bim.remove_context" bl_label = "Remove Context" bl_options = {"REGISTER", "UNDO"} context: bpy.props.IntProperty() def _execute(self, context): core.remove_context(tool.Ifc, context=tool.Ifc.get().by_id(self.context)) + + +class EnableEditingContext(bpy.types.Operator, Operator): + bl_idname = "bim.enable_editing_context" + bl_label = "Enable Editing Context" + bl_options = {"REGISTER", "UNDO"} + context: bpy.props.IntProperty() + + def _execute(self, context): + core.enable_editing_context(tool.ContextEditor, context=tool.Ifc.get().by_id(self.context)) + + +class DisableEditingContext(bpy.types.Operator, Operator): + bl_idname = "bim.disable_editing_context" + bl_label = "Disable Editing Context" + bl_options = {"REGISTER", "UNDO"} + + def _execute(self, context): + core.disable_editing_context(tool.ContextEditor) + + +class EditContext(bpy.types.Operator, Operator): + bl_idname = "bim.edit_context" + bl_label = "Edit Context" + bl_options = {"REGISTER", "UNDO"} + + def _execute(self, context): + core.edit_context(tool.Ifc, tool.ContextEditor) diff --git a/src/blenderbim/blenderbim/bim/module/context/prop.py b/src/blenderbim/blenderbim/bim/module/context/prop.py index e22cf7cd41..b37cca9c19 100644 --- a/src/blenderbim/blenderbim/bim/module/context/prop.py +++ b/src/blenderbim/blenderbim/bim/module/context/prop.py @@ -1,3 +1,21 @@ +# 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 bpy from blenderbim.bim.prop import StrProperty, Attribute from blenderbim.bim.module.context.data import ContextData @@ -46,3 +64,5 @@ class BIMContextProperties(PropertyGroup): ], name="Target Views", ) + active_context_id: IntProperty(name="Active Context Id") + context_attributes: CollectionProperty(name="Context Attributes", type=Attribute) diff --git a/src/blenderbim/blenderbim/bim/module/context/ui.py b/src/blenderbim/blenderbim/bim/module/context/ui.py index 9a72a2697a..9f1a6f1e8b 100644 --- a/src/blenderbim/blenderbim/bim/module/context/ui.py +++ b/src/blenderbim/blenderbim/bim/module/context/ui.py @@ -17,6 +17,7 @@ # along with BlenderBIM Add-on. If not, see . import bpy +import blenderbim.bim.helper import blenderbim.tool as tool from blenderbim.bim.module.context.data import ContextData @@ -41,21 +42,45 @@ class BIM_PT_context(bpy.types.Panel): row = self.layout.row(align=True) row.prop(props, "contexts", text="") - row.prop(props, "subcontexts", text="") - row.prop(props, "target_views", text="") - op = row.operator("bim.add_subcontext", icon="ADD", text="") - op.context = props.contexts - op.subcontext = props.subcontexts - op.target_view = props.target_views + op = row.operator("bim.add_context", icon="ADD", text="") + op.context_type = props.contexts + op.context_identifier = "" + op.target_view = "" + op.parent = 0 - for context in ContextData.data["contexts"]: + for ifc_context in ContextData.data["contexts"]: box = self.layout.box() - row = box.row(align=True) - row.label(text=context["context_type"]) - row.operator("bim.remove_subcontext", icon="X", text="").context = context["id"] - for subcontext in context["subcontexts"]: + + if ifc_context["is_editing"]: row = box.row(align=True) - row.label(text=subcontext["context_type"]) - row.label(text=subcontext["context_identifier"]) - row.label(text=subcontext["target_view"]) - row.operator("bim.remove_subcontext", icon="X", text="").context = subcontext["id"] + row.operator("bim.edit_context", icon="CHECKMARK") + row.operator("bim.disable_editing_context", icon="CANCEL", text="") + blenderbim.bim.helper.draw_attributes(ifc_context["props"], box) + else: + row = box.row(align=True) + row.label(text=ifc_context["context_type"]) + row.operator("bim.enable_editing_context", icon="GREASEPENCIL", text="").context = ifc_context["id"] + row.operator("bim.remove_context", icon="X", text="").context = ifc_context["id"] + + row = box.row(align=True) + row.prop(props, "subcontexts", text="") + row.prop(props, "target_views", text="") + op = row.operator("bim.add_context", icon="ADD", text="") + op.context_type = ifc_context["context_type"] + op.context_identifier = props.subcontexts + op.target_view = props.target_views + op.parent = ifc_context["id"] + + for subcontext in ifc_context["subcontexts"]: + if subcontext["is_editing"]: + row = box.row(align=True) + row.operator("bim.edit_context", icon="CHECKMARK") + row.operator("bim.disable_editing_context", icon="CANCEL", text="") + blenderbim.bim.helper.draw_attributes(subcontext["props"], box) + else: + row = box.row(align=True) + row.label(text=subcontext["context_type"]) + row.label(text=subcontext["context_identifier"]) + row.label(text=subcontext["target_view"]) + row.operator("bim.enable_editing_context", icon="GREASEPENCIL", text="").context = subcontext["id"] + row.operator("bim.remove_context", icon="X", text="").context = subcontext["id"] diff --git a/src/blenderbim/blenderbim/bim/module/owner/operator.py b/src/blenderbim/blenderbim/bim/module/owner/operator.py index c180c0ea02..eeaf9be4a1 100644 --- a/src/blenderbim/blenderbim/bim/module/owner/operator.py +++ b/src/blenderbim/blenderbim/bim/module/owner/operator.py @@ -226,9 +226,7 @@ class EnableEditingOrganisation(bpy.types.Operator, Operator): organisation: bpy.props.IntProperty() def _execute(self, context): - core.enable_editing_organisation( - tool.OrganisationEditor, organisation=tool.Ifc.get().by_id(self.organisation) - ) + core.enable_editing_organisation(tool.OrganisationEditor, organisation=tool.Ifc.get().by_id(self.organisation)) class DisableEditingOrganisation(bpy.types.Operator, Operator): diff --git a/src/blenderbim/blenderbim/bim/module/project/operator.py b/src/blenderbim/blenderbim/bim/module/project/operator.py index 9f39040977..604f277871 100644 --- a/src/blenderbim/blenderbim/bim/module/project/operator.py +++ b/src/blenderbim/blenderbim/bim/module/project/operator.py @@ -26,6 +26,8 @@ import ifcopenshell.api import ifcopenshell.util.selector import ifcopenshell.util.representation import blenderbim.bim.handler +import blenderbim.tool as tool +import blenderbim.core.context as context_core from blenderbim.bim.ifc import IfcStore from blenderbim.bim import import_ifc @@ -65,11 +67,21 @@ class CreateProject(bpy.types.Operator): bpy.ops.bim.assign_class(obj=project.name, ifc_class="IfcProject") bpy.ops.bim.assign_unit() - bpy.ops.bim.add_subcontext(context="Model") - bpy.ops.bim.add_subcontext(context="Model", subcontext="Body", target_view="MODEL_VIEW") - bpy.ops.bim.add_subcontext(context="Model", subcontext="Box", target_view="MODEL_VIEW") - bpy.ops.bim.add_subcontext(context="Plan") - bpy.ops.bim.add_subcontext(context="Plan", subcontext="Annotation", target_view="PLAN_VIEW") + + # TODO: refactor + model = context_core.add_context( + tool.Ifc, context_type="Model", context_identifier="", target_view="", parent=0 + ) + context_core.add_context( + tool.Ifc, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model + ) + context_core.add_context( + tool.Ifc, context_type="Model", context_identifier="Box", target_view="MODEL_VIEW", parent=model + ) + plan = context_core.add_context(tool.Ifc, context_type="Plan", context_identifier="", target_view="", parent=0) + context_core.add_context( + tool.Ifc, context_type="Plan", context_identifier="Annotation", target_view="PLAN_VIEW", parent=plan + ) context.scene.BIMProperties.contexts = str( ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW").id() diff --git a/src/blenderbim/blenderbim/core/context.py b/src/blenderbim/blenderbim/core/context.py index acd354fb42..0f30476726 100644 --- a/src/blenderbim/blenderbim/core/context.py +++ b/src/blenderbim/blenderbim/core/context.py @@ -17,9 +17,29 @@ # along with BlenderBIM Add-on. If not, see . -def add_context(ifc, context=None, subcontext=None, target_view=None): - return ifc.run("context.add_context", context=context, subcontext=subcontext, target_view=target_view) +def add_context(ifc, context_type=None, context_identifier=None, target_view=None, parent=None): + return ifc.run( + "context.add_context", + context_type=context_type, + context_identifier=context_identifier, + target_view=target_view, + parent=parent, + ) def remove_context(ifc, context=None): ifc.run("context.remove_context", context=context) + + +def enable_editing_context(context_editor, context=None): + context_editor.set_context(context) + context_editor.import_attributes() + + +def disable_editing_context(context_editor): + context_editor.clear_context() + + +def edit_context(ifc, context_editor): + ifc.run("context.edit_context", context=context_editor.get_context(), attributes=context_editor.export_attributes()) + disable_editing_context(context_editor) diff --git a/src/blenderbim/blenderbim/core/tool/__init__.py b/src/blenderbim/blenderbim/core/tool/__init__.py index 64a7bf69fe..c98548be52 100644 --- a/src/blenderbim/blenderbim/core/tool/__init__.py +++ b/src/blenderbim/blenderbim/core/tool/__init__.py @@ -22,3 +22,4 @@ from blenderbim.core.tool.person_editor import PersonEditor from blenderbim.core.tool.role_editor import RoleEditor from blenderbim.core.tool.address_editor import AddressEditor from blenderbim.core.tool.organisation_editor import OrganisationEditor +from blenderbim.core.tool.context_editor import ContextEditor diff --git a/src/blenderbim/blenderbim/core/tool/context_editor.py b/src/blenderbim/blenderbim/core/tool/context_editor.py new file mode 100644 index 0000000000..4748c4b2dc --- /dev/null +++ b/src/blenderbim/blenderbim/core/tool/context_editor.py @@ -0,0 +1,46 @@ +# 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 abc + + +class ContextEditor(abc.ABC): + @classmethod + @abc.abstractmethod + def set_context(cls, context): + pass + + @classmethod + @abc.abstractmethod + def import_attributes(cls): + pass + + @classmethod + @abc.abstractmethod + def clear_context(cls): + pass + + @classmethod + @abc.abstractmethod + def get_context(cls): + pass + + @classmethod + @abc.abstractmethod + def export_attributes(cls): + pass diff --git a/src/blenderbim/blenderbim/tool/__init__.py b/src/blenderbim/blenderbim/tool/__init__.py index bc746d2f98..6c6127a108 100644 --- a/src/blenderbim/blenderbim/tool/__init__.py +++ b/src/blenderbim/blenderbim/tool/__init__.py @@ -22,3 +22,4 @@ from blenderbim.tool.person_editor import PersonEditor from blenderbim.tool.role_editor import RoleEditor from blenderbim.tool.address_editor import AddressEditor from blenderbim.tool.organisation_editor import OrganisationEditor +from blenderbim.tool.context_editor import ContextEditor diff --git a/src/blenderbim/blenderbim/tool/context_editor.py b/src/blenderbim/blenderbim/tool/context_editor.py new file mode 100644 index 0000000000..fb1edbab72 --- /dev/null +++ b/src/blenderbim/blenderbim/tool/context_editor.py @@ -0,0 +1,57 @@ +# 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 bpy +import blenderbim.bim.helper +import blenderbim.tool as tool +import blenderbim.core.tool.context_editor + + +class ContextEditor(blenderbim.core.tool.context_editor.ContextEditor): + @classmethod + def set_context(cls, context): + bpy.context.scene.BIMContextProperties.active_context_id = context.id() + + @classmethod + def import_attributes(cls): + props = bpy.context.scene.BIMContextProperties + props.context_attributes.clear() + context = cls.get_context() + + def callback(name, prop, data): + if context.is_a("IfcGeometricRepresentationSubContext"): + if name == "Precision": + props.context_attributes.remove(props.context_attributes.find("Precision")) + return True + elif name == "CoordinateSpaceDimension": + props.context_attributes.remove(props.context_attributes.find("CoordinateSpaceDimension")) + return True + + blenderbim.bim.helper.import_attributes(context.is_a(), props.context_attributes, context.get_info(), callback) + + @classmethod + def clear_context(cls): + bpy.context.scene.BIMContextProperties.active_context_id = 0 + + @classmethod + def get_context(cls): + return tool.Ifc.get().by_id(bpy.context.scene.BIMContextProperties.active_context_id) + + @classmethod + def export_attributes(cls): + return blenderbim.bim.helper.export_attributes(bpy.context.scene.BIMContextProperties.context_attributes) diff --git a/src/blenderbim/test/bim/feature/context.feature b/src/blenderbim/test/bim/feature/context.feature index 2a4596f1e9..5679e8dfc5 100644 --- a/src/blenderbim/test/bim/feature/context.feature +++ b/src/blenderbim/test/bim/feature/context.feature @@ -2,13 +2,42 @@ Feature: Context Manages geometric representation contexts and subcontexts -Scenario: Add subcontext +Scenario: Add context Given an empty IFC project - When I press "bim.add_subcontext(context='Model', subcontext='Body', target_view='MODEL_VIEW')" + When I press "bim.add_context(context_type='Model', context_identifier='', target_view='', parent=0)" Then nothing happens -Scenario: Remove subcontext +Scenario: Add a subcontext + Given an empty IFC project + When the variable "parent" is "IfcStore.get_file().by_type('IfcGeometricRepresentationContext', include_subtypes=False)[0].id()" + When I press "bim.add_context(context_type='Model', context_identifier='Body', target_view='MODEL_VIEW', parent={parent})" + Then nothing happens + +Scenario: Remove context Given an empty IFC project When the variable "context" is "IfcStore.get_file().by_type('IfcGeometricRepresentationContext')[0].id()" - And I press "bim.remove_subcontext(context={context})" + And I press "bim.remove_context(context={context})" + Then nothing happens + +Scenario: Enable editing context + Given an empty IFC project + When I press "bim.add_context(context_type='Model', context_identifier='', target_view='', parent=0)" + And the variable "context" is "IfcStore.get_file().by_type('IfcGeometricRepresentationContext')[0].id()" + And I press "bim.enable_editing_context(context={context})" + Then nothing happens + +Scenario: Disable editing context + Given an empty IFC project + When I press "bim.add_context(context_type='Model', context_identifier='', target_view='', parent=0)" + And the variable "context" is "IfcStore.get_file().by_type('IfcGeometricRepresentationContext')[0].id()" + And I press "bim.enable_editing_context(context={context})" + And I press "bim.disable_editing_context()" + Then nothing happens + +Scenario: Edit context + Given an empty IFC project + When I press "bim.add_context(context_type='Model', context_identifier='', target_view='', parent=0)" + And the variable "context" is "IfcStore.get_file().by_type('IfcGeometricRepresentationContext')[0].id()" + And I press "bim.enable_editing_context(context={context})" + And I press "bim.edit_context" Then nothing happens diff --git a/src/blenderbim/test/core/bootstrap.py b/src/blenderbim/test/core/bootstrap.py index 7e0161123a..13f0613b4d 100644 --- a/src/blenderbim/test/core/bootstrap.py +++ b/src/blenderbim/test/core/bootstrap.py @@ -63,6 +63,13 @@ def organisation_editor(): prophet.verify() +@pytest.fixture +def context_editor(): + prophet = Prophecy(blenderbim.core.tool.ContextEditor) + yield prophet + prophet.verify() + + class Prophecy: def __init__(self, cls): self.subject = cls diff --git a/src/blenderbim/test/core/test_context.py b/src/blenderbim/test/core/test_context.py index d35470dc59..bdbe51bf06 100644 --- a/src/blenderbim/test/core/test_context.py +++ b/src/blenderbim/test/core/test_context.py @@ -17,18 +17,58 @@ # along with BlenderBIM Add-on. If not, see . import blenderbim.core.context as subject -from test.core.bootstrap import ifc +from test.core.bootstrap import ifc, context_editor class TestAddContext: def test_adding_a_context(self, ifc): ifc.run( - "context.add_context", context="Model", subcontext="Body", target_view="MODEL_VIEW" + "context.add_context", context_type="Model", context_identifier=None, target_view=None, parent=None ).should_be_called().will_return("context") - assert subject.add_context(ifc, context="Model", subcontext="Body", target_view="MODEL_VIEW") == "context" + assert ( + subject.add_context(ifc, context_type="Model", context_identifier=None, target_view=None, parent=None) + == "context" + ) + + def test_adding_a_subcontext(self, ifc): + ifc.run( + "context.add_context", + context_type="Model", + context_identifier="Body", + target_view="MODEL_VIEW", + parent="parent", + ).should_be_called().will_return("subcontext") + assert ( + subject.add_context( + ifc, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent="parent" + ) + == "subcontext" + ) class TestRemoveContext: def test_removing_a_context(self, ifc): ifc.run("context.remove_context", context="context").should_be_called() subject.remove_context(ifc, context="context") + + +class TestEnableEditingContext: + def test_run(self, context_editor): + context_editor.set_context("context").should_be_called() + context_editor.import_attributes().should_be_called() + subject.enable_editing_context(context_editor, context="context") + + +class TestDisableEditingContext: + def test_run(self, context_editor): + context_editor.clear_context().should_be_called() + subject.disable_editing_context(context_editor) + + +class TestEditContext: + def test_run(self, ifc, context_editor): + context_editor.get_context().should_be_called().will_return("context") + context_editor.export_attributes().should_be_called().will_return("attributes") + ifc.run("context.edit_context", context="context", attributes="attributes").should_be_called() + context_editor.clear_context().should_be_called() + subject.edit_context(ifc, context_editor) diff --git a/src/blenderbim/test/tool/test_context_editor.py b/src/blenderbim/test/tool/test_context_editor.py new file mode 100644 index 0000000000..dcec919fc0 --- /dev/null +++ b/src/blenderbim/test/tool/test_context_editor.py @@ -0,0 +1,120 @@ +# 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 bpy +import ifcopenshell +import test.bim.bootstrap +import blenderbim.core.tool +import blenderbim.tool as tool +from blenderbim.tool.context_editor import ContextEditor as subject + + +class TestImplementsTool(test.bim.bootstrap.NewFile): + def test_run(self): + assert isinstance(subject(), blenderbim.core.tool.context_editor.ContextEditor) + + +class TestSetContext(test.bim.bootstrap.NewFile): + def test_run(self): + ifc = ifcopenshell.file() + context = ifc.createIfcGeometricRepresentationContext() + subject.set_context(context) + assert bpy.context.scene.BIMContextProperties.active_context_id == context.id() + + +class TestImportAttributes(test.bim.bootstrap.NewFile): + def test_importing_a_context(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + context = ifc.createIfcGeometricRepresentationContext() + context.ContextIdentifier = "ContextIdentifier" + context.ContextType = "ContextType" + context.CoordinateSpaceDimension = 1 + context.Precision = 1 + subject.set_context(context) + subject.import_attributes() + props = bpy.context.scene.BIMContextProperties + assert props.context_attributes.get("ContextIdentifier").string_value == "ContextIdentifier" + assert props.context_attributes.get("ContextType").string_value == "ContextType" + assert props.context_attributes.get("CoordinateSpaceDimension").int_value == 1 + assert props.context_attributes.get("Precision").float_value == 1 + + def test_importing_a_subcontext(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + subcontext = ifc.createIfcGeometricRepresentationSubcontext() + subcontext.TargetScale = 0.5 + subcontext.TargetView = "NOTDEFINED" + subcontext.UserDefinedTargetView = "UserDefinedTargetView" + subject.set_context(subcontext) + subject.import_attributes() + props = bpy.context.scene.BIMContextProperties + assert props.context_attributes.get("TargetScale").float_value == 0.5 + assert props.context_attributes.get("TargetView").enum_value == "NOTDEFINED" + assert props.context_attributes.get("UserDefinedTargetView").string_value == "UserDefinedTargetView" + assert not props.context_attributes.get("Precision") + + def test_importing_twice(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + context = ifc.createIfcGeometricRepresentationContext() + context.ContextIdentifier = "ContextIdentifier" + subject.set_context(context) + subject.import_attributes() + context.ContextIdentifier = "ContextIdentifier2" + subject.import_attributes() + props = bpy.context.scene.BIMContextProperties + assert props.context_attributes.get("ContextIdentifier").string_value == "ContextIdentifier2" + + +class TestClearContext(test.bim.bootstrap.NewFile): + def test_run(self): + TestSetContext().test_run() + subject.clear_context() + assert bpy.context.scene.BIMContextProperties.active_context_id == 0 + + +class TestGetContext(test.bim.bootstrap.NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + context = ifc.createIfcGeometricRepresentationContext() + subject.set_context(context) + assert subject.get_context() == context + + +class TestExportAttributes(test.bim.bootstrap.NewFile): + def test_exporting_a_context(self): + TestImportAttributes().test_importing_a_context() + assert subject.export_attributes() == { + "ContextIdentifier": "ContextIdentifier", + "ContextType": "ContextType", + "CoordinateSpaceDimension": 1, + "Precision": 1, + } + + def test_exporting_a_subcontext(self): + TestImportAttributes().test_importing_a_subcontext() + assert subject.export_attributes() == { + "TargetScale": 0.5, + "TargetView": "NOTDEFINED", + "UserDefinedTargetView": "UserDefinedTargetView", + "ContextIdentifier": None, + "ContextType": None, + "CoordinateSpaceDimension": None, + } diff --git a/src/ifcopenshell-python/ifcopenshell/api/context/add_context.py b/src/ifcopenshell-python/ifcopenshell/api/context/add_context.py index cbe9e0b389..bc653b5b2f 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/context/add_context.py +++ b/src/ifcopenshell-python/ifcopenshell/api/context/add_context.py @@ -2,30 +2,26 @@ class Usecase: def __init__(self, file, **settings): self.file = file self.settings = { - "context": None, - "subcontext": None, + "context_type": None, + "parent": None, + "context_identifier": None, "target_view": None, } for key, value in settings.items(): self.settings[key] = value def execute(self): - parent = [ - c - for c in self.file.by_type("IfcGeometricRepresentationContext") - if c.ContextType == self.settings["context"] - ] - if not parent: - self.create_origin() - if self.settings["context"] == "Plan": + if not self.settings["parent"]: + if self.settings["context_type"] == "Plan": + self.create_2d_origin() context = self.file.createIfcGeometricRepresentationContext(None, "Plan", 2, 1.0e-05, self.origin) else: - context = self.file.createIfcGeometricRepresentationContext(None, "Model", 3, 1.0e-05, self.origin) + self.create_3d_origin() + context = self.file.createIfcGeometricRepresentationContext( + None, self.settings["context_type"], 3, 1.0e-05, self.origin + ) - if self.file.schema == "IFC2X3": - project = self.file.by_type("IfcProject")[0] - else: - project = self.file.by_type("IfcContext")[0] + project = self.file.by_type("IfcProject")[0] if project.RepresentationContexts: contexts = list(project.RepresentationContexts) @@ -34,20 +30,25 @@ class Usecase: contexts.append(context) project.RepresentationContexts = contexts return context - parent = parent[0] return self.file.create_entity( "IfcGeometricRepresentationSubContext", **{ - "ContextIdentifier": self.settings["subcontext"], - "ContextType": self.settings["context"], - "ParentContext": parent, + "ContextIdentifier": self.settings["context_identifier"], + "ContextType": self.settings["context_type"], + "ParentContext": self.settings["parent"], "TargetView": self.settings["target_view"], } ) - def create_origin(self): + def create_3d_origin(self): self.origin = self.file.createIfcAxis2Placement3D( self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)), self.file.createIfcDirection((0.0, 0.0, 1.0)), self.file.createIfcDirection((1.0, 0.0, 0.0)), ) + + def create_2d_origin(self): + self.origin = self.file.createIfcAxis2Placement2D( + self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)), + self.file.createIfcDirection((1.0, 0.0, 0.0)), + ) diff --git a/src/ifcopenshell-python/ifcopenshell/api/context/edit_context.py b/src/ifcopenshell-python/ifcopenshell/api/context/edit_context.py new file mode 100644 index 0000000000..7120a9a8b2 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/context/edit_context.py @@ -0,0 +1,10 @@ +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"context": None, "attributes": {}} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + for name, value in self.settings["attributes"].items(): + setattr(self.settings["context"], name, value) diff --git a/src/ifcopenshell-python/test/api/context/test_add_context.py b/src/ifcopenshell-python/test/api/context/test_add_context.py new file mode 100644 index 0000000000..9423194ee9 --- /dev/null +++ b/src/ifcopenshell-python/test/api/context/test_add_context.py @@ -0,0 +1,59 @@ +import test.bootstrap +import ifcopenshell.api + + +class TestAddContext(test.bootstrap.IFC4): + def test_adding_a_3d_context(self): + self.file.createIfcProject() + context = ifcopenshell.api.run("context.add_context", self.file, context_type="Model") + assert context.ContextType == "Model" + assert context.is_a() == "IfcGeometricRepresentationContext" + assert context.WorldCoordinateSystem.is_a() == "IfcAxis2Placement3D" + assert context.WorldCoordinateSystem.Location.Coordinates == (0, 0, 0) + assert context.WorldCoordinateSystem.Axis.DirectionRatios == (0, 0, 1) + assert context.WorldCoordinateSystem.RefDirection.DirectionRatios == (1, 0, 0) + assert context.CoordinateSpaceDimension == 3 + + def test_adding_a_2d_context(self): + self.file.createIfcProject() + context = ifcopenshell.api.run("context.add_context", self.file, context_type="Plan") + assert context.ContextType == "Plan" + assert context.is_a() == "IfcGeometricRepresentationContext" + assert context.WorldCoordinateSystem.is_a() == "IfcAxis2Placement2D" + assert context.WorldCoordinateSystem.Location.Coordinates == (0, 0, 0) + assert context.WorldCoordinateSystem.RefDirection.DirectionRatios == (1, 0, 0) + assert context.CoordinateSpaceDimension == 2 + + def test_defaulting_to_3d_with_an_unknown_context_type(self): + self.file.createIfcProject() + context = ifcopenshell.api.run("context.add_context", self.file) + assert context.ContextType is None + assert context.is_a() == "IfcGeometricRepresentationContext" + assert context.WorldCoordinateSystem.is_a() == "IfcAxis2Placement3D" + assert context.WorldCoordinateSystem.Location.Coordinates == (0, 0, 0) + assert context.WorldCoordinateSystem.Axis.DirectionRatios == (0, 0, 1) + assert context.WorldCoordinateSystem.RefDirection.DirectionRatios == (1, 0, 0) + assert context.CoordinateSpaceDimension == 3 + + def test_adding_a_subcontext(self): + self.test_adding_a_3d_context() + context = self.file.by_type("IfcGeometricRepresentationContext")[0] + subcontext = ifcopenshell.api.run("context.add_context", self.file, parent=context, target_view="NOTDEFINED") + assert subcontext.is_a("IfcGeometricRepresentationSubContext") + assert subcontext.TargetView == "NOTDEFINED" + + def test_adding_a_subcontext_with_an_optional_identifier_specified(self): + self.test_adding_a_3d_context() + context = self.file.by_type("IfcGeometricRepresentationContext")[0] + subcontext = ifcopenshell.api.run( + "context.add_context", self.file, parent=context, target_view="NOTDEFINED", context_identifier="Body" + ) + assert subcontext.is_a("IfcGeometricRepresentationSubContext") + assert subcontext.TargetView == "NOTDEFINED" + assert subcontext.ContextIdentifier == "Body" + + def test_adding_two_contexts(self): + self.test_adding_a_3d_context() + self.test_adding_a_2d_context() + project = self.file.by_type("IfcProject")[0] + assert len(project.RepresentationContexts) == 2