diff --git a/src/blenderbim/blenderbim/bim/module/owner/data.py b/src/blenderbim/blenderbim/bim/module/owner/data.py index c74762c856..34b2bdd782 100644 --- a/src/blenderbim/blenderbim/bim/module/owner/data.py +++ b/src/blenderbim/blenderbim/bim/module/owner/data.py @@ -188,9 +188,11 @@ class OwnerData: def get_users(cls): results = [] for user in tool.Ifc.get().by_type("IfcPersonAndOrganization"): - results.append({ - "id": user.id(), - "label": "{} ({})".format(user.ThePerson[0] or "Unnamed", user.TheOrganization[0] or "Unnamed"), - "is_active": bpy.context.scene.BIMOwnerProperties.active_user_id == user.id(), - }) + results.append( + { + "id": user.id(), + "label": "{} ({})".format(user.ThePerson[0] or "Unnamed", user.TheOrganization[0] or "Unnamed"), + "is_active": bpy.context.scene.BIMOwnerProperties.active_user_id == user.id(), + } + ) return results diff --git a/src/blenderbim/blenderbim/bim/module/project/operator.py b/src/blenderbim/blenderbim/bim/module/project/operator.py index ca7ed6250c..ac13623bc6 100644 --- a/src/blenderbim/blenderbim/bim/module/project/operator.py +++ b/src/blenderbim/blenderbim/bim/module/project/operator.py @@ -27,8 +27,8 @@ import ifcopenshell.util.selector import ifcopenshell.util.representation import blenderbim.bim.handler import blenderbim.tool as tool -import blenderbim.core.context as core_context -import blenderbim.core.owner as core_owner +import blenderbim.core.context +import blenderbim.core.owner from blenderbim.bim.ifc import IfcStore from blenderbim.bim import import_ifc @@ -58,10 +58,10 @@ class CreateProject(bpy.types.Operator): ) self.file = IfcStore.get_file() - person = core_owner.add_person(tool.Ifc) - organisation = core_owner.add_organisation(tool.Ifc) - user = core_owner.add_person_and_organisation(tool.Ifc, person=person, organisation=organisation) - core_owner.set_user(tool.Owner, user=user) + person = blenderbim.core.owner.add_person(tool.Ifc) + organisation = blenderbim.core.owner.add_organisation(tool.Ifc) + user = blenderbim.core.owner.add_person_and_organisation(tool.Ifc, person=person, organisation=organisation) + blenderbim.core.owner.set_user(tool.Owner, user=user) project = bpy.data.objects.new(self.get_name("IfcProject", "My Project"), None) site = bpy.data.objects.new(self.get_name("IfcSite", "My Site"), None) @@ -71,17 +71,19 @@ class CreateProject(bpy.types.Operator): bpy.ops.bim.assign_class(obj=project.name, ifc_class="IfcProject") bpy.ops.bim.assign_unit() - model = core_context.add_context( + model = blenderbim.core.context.add_context( tool.Ifc, context_type="Model", context_identifier="", target_view="", parent=0 ) - core_context.add_context( + blenderbim.core.context.add_context( tool.Ifc, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model ) - core_context.add_context( + blenderbim.core.context.add_context( tool.Ifc, context_type="Model", context_identifier="Box", target_view="MODEL_VIEW", parent=model ) - plan = core_context.add_context(tool.Ifc, context_type="Plan", context_identifier="", target_view="", parent=0) - core_context.add_context( + plan = blenderbim.core.context.add_context( + tool.Ifc, context_type="Plan", context_identifier="", target_view="", parent=0 + ) + blenderbim.core.context.add_context( tool.Ifc, context_type="Plan", context_identifier="Annotation", target_view="PLAN_VIEW", parent=plan ) diff --git a/src/blenderbim/blenderbim/bim/module/root/operator.py b/src/blenderbim/blenderbim/bim/module/root/operator.py index ce375218c8..dfb368dc55 100644 --- a/src/blenderbim/blenderbim/bim/module/root/operator.py +++ b/src/blenderbim/blenderbim/bim/module/root/operator.py @@ -22,6 +22,8 @@ import ifcopenshell.api import ifcopenshell.util.schema import ifcopenshell.util.element import blenderbim.bim.handler +import blenderbim.core.spatial +import blenderbim.tool as tool from ifcopenshell.api.void.data import Data as VoidData from blenderbim.bim.ifc import IfcStore @@ -236,8 +238,13 @@ class AssignClass(bpy.types.Operator): continue elif self.file.schema == "IFC2X3" and not element.is_a("IfcSpatialStructureElement"): continue - bpy.ops.bim.assign_container( - relating_structure=spatial_obj.BIMObjectProperties.ifc_definition_id, related_element=obj.name + blenderbim.core.spatial.assign_container( + tool.Ifc, + tool.Collector, + tool.Container, + tool.Surveyor, + structure_obj=spatial_obj, + element_obj=obj, ) break diff --git a/src/blenderbim/blenderbim/bim/module/spatial/__init__.py b/src/blenderbim/blenderbim/bim/module/spatial/__init__.py index 1d1b95cc90..ba0f1b3426 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/__init__.py @@ -30,7 +30,7 @@ classes = ( prop.BIMSpatialProperties, prop.BIMObjectSpatialProperties, ui.BIM_PT_spatial, - ui.BIM_UL_spatial_elements, + ui.BIM_UL_containers, ) diff --git a/src/blenderbim/blenderbim/bim/module/spatial/data.py b/src/blenderbim/blenderbim/bim/module/spatial/data.py new file mode 100644 index 0000000000..0b284445c1 --- /dev/null +++ b/src/blenderbim/blenderbim/bim/module/spatial/data.py @@ -0,0 +1,71 @@ +# 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 +import ifcopenshell.util.element + + +class SpatialData: + data = {} + is_loaded = False + + @classmethod + def load(cls): + cls.data = { + "is_editing": bpy.context.active_object.BIMObjectSpatialProperties.is_editing, + "parent_container_id": cls.get_parent_container_id(), + "container_id": cls.get_container_id(), + "container_list_settings": cls.get_container_list_settings(), + "is_contained": cls.is_contained(), + "label": cls.get_label(), + } + cls.is_loaded = True + + @classmethod + def get_parent_container_id(cls): + container_id = bpy.context.scene.BIMSpatialProperties.active_container_id + if not container_id: + return + container = tool.Ifc.get().by_id(container_id) + if container.is_a("IfcProject"): + return + return container.Decomposes[0].RelatingObject.id() + + @classmethod + def get_container_id(cls): + props = bpy.context.scene.BIMSpatialProperties + try: + return props.containers[props.active_container_index].ifc_definition_id + except: + return 0 + + @classmethod + def get_container_list_settings(cls): + props = bpy.context.scene.BIMSpatialProperties + return ["", props, "containers", props, "active_container_id"] + + @classmethod + def is_contained(cls): + return ifcopenshell.util.element.get_container(tool.Ifc.get_entity(bpy.context.active_object)) + + @classmethod + def get_label(cls): + container = ifcopenshell.util.element.get_container(tool.Ifc.get_entity(bpy.context.active_object)) + if container: + return f"{container.is_a()}/{container.Name or ''}" diff --git a/src/blenderbim/blenderbim/bim/module/spatial/operator.py b/src/blenderbim/blenderbim/bim/module/spatial/operator.py index 3b02e5651a..f19fc2d937 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/operator.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/operator.py @@ -19,150 +19,74 @@ import bpy import ifcopenshell.api import ifcopenshell.util.element +import blenderbim.tool as tool +import blenderbim.core.spatial as core from blenderbim.bim.ifc import IfcStore -from ifcopenshell.api.spatial.data import Data -from blenderbim.bim.module.spatial.prop import getSpatialContainers +from blenderbim.bim.module.spatial.data import SpatialData -class AssignContainer(bpy.types.Operator): +class Operator: + def execute(self, context): + IfcStore.execute_ifc_operator(self, context) + SpatialData.is_loaded = False + return {"FINISHED"} + + +class AssignContainer(bpy.types.Operator, Operator): bl_idname = "bim.assign_container" bl_label = "Assign Container" bl_options = {"REGISTER", "UNDO"} - relating_structure: bpy.props.IntProperty() - related_element: bpy.props.StringProperty() - - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) + structure: bpy.props.IntProperty() def _execute(self, context): - # The active object pointer is lost when the object is unlinked from all collections in the view layer - # We need to store it first, then restore it at the end - active_object = context.active_object - self.file = IfcStore.get_file() - related_elements = ( - [bpy.data.objects.get(self.related_element)] if self.related_element else context.selected_objects - ) - sprops = context.scene.BIMSpatialProperties - relating_structure = ( - self.relating_structure or sprops.spatial_elements[sprops.active_spatial_element_index].ifc_definition_id - ) - for related_element in related_elements: - oprops = related_element.BIMObjectProperties - if not oprops.ifc_definition_id: - continue - props = related_element.BIMObjectSpatialProperties - - ifcopenshell.api.run( - "spatial.assign_container", - self.file, - **{ - "product": self.file.by_id(oprops.ifc_definition_id), - "relating_structure": self.file.by_id(relating_structure), - }, + structure_obj = tool.Ifc.get_object(tool.Ifc.get().by_id(self.structure)) + for element_obj in context.selected_objects: + core.assign_container( + tool.Ifc, + tool.Collector, + tool.Container, + tool.Surveyor, + structure_obj=structure_obj, + element_obj=element_obj, ) - bpy.ops.bim.edit_object_placement(obj=related_element.name) - Data.load(IfcStore.get_file(), oprops.ifc_definition_id) - bpy.ops.bim.disable_editing_container(obj=related_element.name) - - aggregate_collection = bpy.data.collections.get(related_element.name) - - relating_structure_obj = IfcStore.get_element(relating_structure) - relating_collection = None - if relating_structure_obj: - relating_collection = bpy.data.collections.get(relating_structure_obj.name) - - if aggregate_collection: - self.remove_collection(context.scene.collection, aggregate_collection) - for collection in bpy.data.collections: - self.remove_collection(collection, aggregate_collection) - relating_collection.children.link(aggregate_collection) - elif relating_collection: - for collection in related_element.users_collection: - collection.objects.unlink(related_element) - relating_collection.objects.link(related_element) - # Restore the active object : - context.view_layer.objects.active = active_object - return {"FINISHED"} - - def remove_collection(self, parent, child): - try: - parent.children.unlink(child) - except: - pass -class EnableEditingContainer(bpy.types.Operator): +class EnableEditingContainer(bpy.types.Operator, Operator): bl_idname = "bim.enable_editing_container" bl_label = "Enable Editing Container" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): - context.active_object.BIMObjectSpatialProperties.is_editing = True - getSpatialContainers(self, context) - return {"FINISHED"} + def _execute(self, context): + core.enable_editing_container(tool.Container, obj=context.active_object) -class ChangeSpatialLevel(bpy.types.Operator): +class ChangeSpatialLevel(bpy.types.Operator, Operator): bl_idname = "bim.change_spatial_level" bl_label = "Change Spatial Level" bl_options = {"REGISTER", "UNDO"} - parent_id: bpy.props.IntProperty() + parent: bpy.props.IntProperty() - def execute(self, context): - getSpatialContainers(self, context, parent_id=self.parent_id) - return {"FINISHED"} + def _execute(self, context): + core.change_spatial_level(tool.Container, parent=tool.Ifc.get().by_id(self.parent)) -class DisableEditingContainer(bpy.types.Operator): +class DisableEditingContainer(bpy.types.Operator, Operator): bl_idname = "bim.disable_editing_container" bl_label = "Disable Editing Container" bl_options = {"REGISTER", "UNDO"} - obj: bpy.props.StringProperty() - def execute(self, context): - obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object - obj.BIMObjectSpatialProperties.is_editing = False - return {"FINISHED"} + def _execute(self, context): + core.disable_editing_container(tool.Container, obj=context.active_object) -class RemoveContainer(bpy.types.Operator): +class RemoveContainer(bpy.types.Operator, Operator): bl_idname = "bim.remove_container" bl_label = "Remove Container" bl_options = {"REGISTER", "UNDO"} - obj: bpy.props.StringProperty() - - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - active_object = context.active_object - self.file = IfcStore.get_file() - objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects - for obj in objs: - obj_id = obj.BIMObjectProperties.ifc_definition_id - if not obj_id: - continue - ifcopenshell.api.run("spatial.remove_container", self.file, **{"product": self.file.by_id(obj_id)}) - Data.load(self.file, obj_id) - - aggregate_collection = bpy.data.collections.get(obj.name) - if aggregate_collection: - self.remove_collection(context.scene.collection, aggregate_collection) - for collection in bpy.data.collections: - self.remove_collection(collection, spatial_collection) - context.scene.collection.children.link(aggregate_collection) - else: - for collection in obj.users_collection: - collection.objects.unlink(obj) - context.scene.collection.objects.link(obj) - context.view_layer.objects.active = active_object - return {"FINISHED"} - - def remove_collection(self, parent, child): - try: - parent.children.unlink(child) - except: - pass + for obj in context.selected_objects: + core.remove_container(tool.Ifc, tool.Collector, obj=obj) class CopyToContainer(bpy.types.Operator): diff --git a/src/blenderbim/blenderbim/bim/module/spatial/prop.py b/src/blenderbim/blenderbim/bim/module/spatial/prop.py index 769479f3e8..4e15dff8ab 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/prop.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/prop.py @@ -18,8 +18,7 @@ import bpy from blenderbim.bim.prop import StrProperty, Attribute -from ifcopenshell.api.spatial.data import Data -from blenderbim.bim.ifc import IfcStore +from blenderbim.bim.module.spatial.data import SpatialData from bpy.types import PropertyGroup from bpy.props import ( PointerProperty, @@ -33,37 +32,8 @@ from bpy.props import ( ) -def getSpatialContainers(self, context, parent_id=None): - Data.load(IfcStore.get_file()) - self.file = IfcStore.get_file() - props = context.scene.BIMSpatialProperties - props.spatial_elements.clear() - - if parent_id: - props.active_decomposes_id = parent_id - - try: - self.file.by_id(props.active_decomposes_id) - except: - props.active_decomposes_id = 0 - - project_id = self.file.by_type("IfcProject")[0].id() - - if not props.active_decomposes_id: - props.active_decomposes_id = project_id - - for ifc_definition_id, spatial_element in Data.spatial_elements.items(): - if spatial_element["Decomposes"] == props.active_decomposes_id: - new = props.spatial_elements.add() - new.name = spatial_element["Name"] or "Unnamed" - new.long_name = spatial_element["LongName"] or "" - new.has_decomposition = bool(spatial_element["IsDecomposedBy"]) - new.ifc_definition_id = ifc_definition_id - - if props.active_decomposes_id == project_id: - props.active_decomposes_parent_id = 0 - else: - props.active_decomposes_parent_id = Data.spatial_elements[props.active_decomposes_id]["Decomposes"] +def update_active_container_index(self, context): + SpatialData.is_loaded = False class SpatialElement(PropertyGroup): @@ -75,10 +45,9 @@ class SpatialElement(PropertyGroup): class BIMSpatialProperties(PropertyGroup): - spatial_elements: CollectionProperty(name="Spatial Elements", type=SpatialElement) - active_spatial_element_index: IntProperty(name="Active Spatial Element Index") - active_decomposes_id: IntProperty(name="Active Decomposes Id") - active_decomposes_parent_id: IntProperty(name="Active Decomposes Parent Id") + containers: CollectionProperty(name="Containers", type=SpatialElement) + active_container_index: IntProperty(name="Active Container Index", update=update_active_container_index) + active_container_id: IntProperty(name="Active Container Id") class BIMObjectSpatialProperties(PropertyGroup): diff --git a/src/blenderbim/blenderbim/bim/module/spatial/ui.py b/src/blenderbim/blenderbim/bim/module/spatial/ui.py index 00a4e8541d..dc1afbbc8c 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/ui.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/ui.py @@ -19,6 +19,7 @@ from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.spatial.data import Data +from blenderbim.bim.module.spatial.data import SpatialData class BIM_PT_spatial(Panel): @@ -44,44 +45,36 @@ class BIM_PT_spatial(Panel): return True def draw(self, context): - oprops = context.active_object.BIMObjectProperties - sprops = context.scene.BIMSpatialProperties - props = context.active_object.BIMObjectSpatialProperties - if not oprops.ifc_definition_id: - return - if oprops.ifc_definition_id not in Data.products: - Data.load(IfcStore.get_file(), oprops.ifc_definition_id) + if not SpatialData.is_loaded: + SpatialData.load() - if props.is_editing: + if SpatialData.data["is_editing"]: row = self.layout.row(align=True) - if sprops.active_decomposes_parent_id: + if SpatialData.data["parent_container_id"]: op = row.operator("bim.change_spatial_level", text="", icon="FRAME_PREV") - op.parent_id = sprops.active_decomposes_parent_id - row.operator("bim.assign_container", icon="CHECKMARK") + op.parent = SpatialData.data["parent_container_id"] + row.operator("bim.assign_container", icon="CHECKMARK").structure = SpatialData.data["container_id"] row.operator("bim.copy_to_container", icon="COPYDOWN", text="") row.operator("bim.disable_editing_container", icon="CANCEL", text="") - self.layout.template_list( - "BIM_UL_spatial_elements", "", sprops, "spatial_elements", sprops, "active_spatial_element_index" - ) + self.layout.template_list("BIM_UL_containers", *SpatialData.data["container_list_settings"]) else: row = self.layout.row(align=True) - name = "{}/{}".format( - Data.products[oprops.ifc_definition_id]["type"], Data.products[oprops.ifc_definition_id]["Name"] - ) - if name == "None/None": - name = "This object is not spatially contained" - row.label(text=name) - row.operator("bim.enable_editing_container", icon="GREASEPENCIL", text="") - row.operator("bim.remove_container", icon="X", text="") + if SpatialData.data["is_contained"]: + row.label(text=SpatialData.data["label"]) + row.operator("bim.enable_editing_container", icon="GREASEPENCIL", text="") + row.operator("bim.remove_container", icon="X", text="") + else: + row.label(text="This object is not spatially contained") + row.operator("bim.enable_editing_container", icon="GREASEPENCIL", text="") -class BIM_UL_spatial_elements(UIList): +class BIM_UL_containers(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): if item: if item.has_decomposition: op = layout.operator("bim.change_spatial_level", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False) - op.parent_id = item.ifc_definition_id + op.parent = item.ifc_definition_id layout.label(text=item.name) layout.label(text=item.long_name) layout.prop( diff --git a/src/blenderbim/blenderbim/core/geometry.py b/src/blenderbim/blenderbim/core/geometry.py index 223f32c69a..3df8ff5273 100644 --- a/src/blenderbim/blenderbim/core/geometry.py +++ b/src/blenderbim/blenderbim/core/geometry.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 . + + def edit_object_placement(ifc, surveyor, obj=None): element = ifc.get_entity(obj) if element: diff --git a/src/blenderbim/blenderbim/core/spatial.py b/src/blenderbim/blenderbim/core/spatial.py new file mode 100644 index 0000000000..bb51abdae3 --- /dev/null +++ b/src/blenderbim/blenderbim/core/spatial.py @@ -0,0 +1,51 @@ +# 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 + + +def assign_container(ifc, collector, container, surveyor, structure_obj=None, element_obj=None): + if not container.can_contain(structure_obj, element_obj): + return + rel = ifc.run( + "spatial.assign_container", + product=ifc.get_entity(element_obj), + relating_structure=ifc.get_entity(structure_obj), + ) + blenderbim.core.geometry.edit_object_placement(ifc, surveyor, obj=element_obj) + container.disable_editing(element_obj) + collector.assign(element_obj) + return rel + + +def enable_editing_container(container, obj=None): + container.enable_editing(obj) + container.import_containers() + + +def disable_editing_container(container, obj=None): + container.disable_editing(obj) + + +def change_spatial_level(container, parent=None): + container.import_containers(parent=parent) + + +def remove_container(ifc, collector, obj=None): + ifc.run("spatial.remove_container", product=ifc.get_entity(obj)) + collector.assign(obj) diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index fdbb7ceffa..9c5182e648 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -83,6 +83,11 @@ class Container(abc.ABC): def disable_editing(cls, obj): pass + @classmethod + @abc.abstractmethod + def import_containers(cls, parent=None): + pass + class ContextEditor(abc.ABC): @classmethod diff --git a/src/blenderbim/blenderbim/tool/__init__.py b/src/blenderbim/blenderbim/tool/__init__.py index 71ef3e3b0f..9d04e812d1 100644 --- a/src/blenderbim/blenderbim/tool/__init__.py +++ b/src/blenderbim/blenderbim/tool/__init__.py @@ -18,6 +18,8 @@ from blenderbim.tool.address_editor import AddressEditor from blenderbim.tool.blender import Blender +from blenderbim.tool.collector import Collector +from blenderbim.tool.container import Container from blenderbim.tool.context_editor import ContextEditor from blenderbim.tool.ifc import Ifc from blenderbim.tool.organisation_editor import OrganisationEditor @@ -25,4 +27,3 @@ from blenderbim.tool.owner import Owner from blenderbim.tool.person_editor import PersonEditor from blenderbim.tool.role_editor import RoleEditor from blenderbim.tool.surveyor import Surveyor -from blenderbim.tool.container import Container diff --git a/src/blenderbim/blenderbim/tool/collector.py b/src/blenderbim/blenderbim/tool/collector.py new file mode 100644 index 0000000000..cdbf6fdfd8 --- /dev/null +++ b/src/blenderbim/blenderbim/tool/collector.py @@ -0,0 +1,54 @@ +# 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.util.element + +import blenderbim.core.tool +import blenderbim.tool as tool + + +class Collector(blenderbim.core.tool.Collector): + @classmethod + def assign(cls, obj): + for collection in obj.users_collection: + collection.objects.unlink(obj) + + element = tool.Ifc.get_entity(obj) + aggregate = ifcopenshell.util.element.get_aggregate(element) + if aggregate: + aggregate_obj = tool.Ifc.get_object(aggregate) + collection = bpy.data.collections.get(aggregate_obj.name) + if collection: + collection.objects.link(obj) + return + + container = ifcopenshell.util.element.get_container(element) + if container: + container_obj = tool.Ifc.get_object(container) + collection = bpy.data.collections.get(container_obj.name) + if collection: + collection.objects.link(obj) + return + + project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0]) + if project_obj: + collection = bpy.data.collections.get(project_obj.name) + if collection: + collection.objects.link(obj) + return diff --git a/src/blenderbim/blenderbim/tool/container.py b/src/blenderbim/blenderbim/tool/container.py index b270c98e89..78f67791c4 100644 --- a/src/blenderbim/blenderbim/tool/container.py +++ b/src/blenderbim/blenderbim/tool/container.py @@ -1,7 +1,26 @@ +# 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.core.tool import blenderbim.tool as tool + class Container(blenderbim.core.tool.Container): @classmethod def can_contain(cls, structure_obj, element_obj): diff --git a/src/blenderbim/blenderbim/tool/owner.py b/src/blenderbim/blenderbim/tool/owner.py index 4dce3f92fa..65ba74bf87 100644 --- a/src/blenderbim/blenderbim/tool/owner.py +++ b/src/blenderbim/blenderbim/tool/owner.py @@ -1,7 +1,26 @@ +# 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.core.tool import blenderbim.tool as tool + class Owner(blenderbim.core.tool.Owner): @classmethod def set_user(cls, user): diff --git a/src/blenderbim/blenderbim/tool/surveyor.py b/src/blenderbim/blenderbim/tool/surveyor.py index a80ce4e2cb..e1ced18793 100644 --- a/src/blenderbim/blenderbim/tool/surveyor.py +++ b/src/blenderbim/blenderbim/tool/surveyor.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 import ifcopenshell.api import blenderbim.core.tool diff --git a/src/blenderbim/pytest.ini b/src/blenderbim/pytest.ini index bf15c4038d..5d7b6426f3 100644 --- a/src/blenderbim/pytest.ini +++ b/src/blenderbim/pytest.ini @@ -4,3 +4,4 @@ markers = geometry owner void + spatial diff --git a/src/blenderbim/test/bim/feature/spatial.feature b/src/blenderbim/test/bim/feature/spatial.feature new file mode 100644 index 0000000000..18e2b354e1 --- /dev/null +++ b/src/blenderbim/test/bim/feature/spatial.feature @@ -0,0 +1,30 @@ +@spatial +Feature: Spatial + Covers spatial containment management. + +Scenario: Enable editing container + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + When I press "bim.enable_editing_container" + Then nothing happens + +Scenario: Disable editing container + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I press "bim.enable_editing_container" + When I press "bim.disable_editing_container" + Then nothing happens + +Scenario: Assign container + 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 object "IfcWall/Cube" is selected + And I press "bim.enable_editing_container" + And the variable "site" is "tool.Ifc.get().by_type('IfcSite')[0].id()" + When I press "bim.assign_container(structure={site})" + Then the object "IfcWall/Cube" is in the collection "IfcSite/My Site" diff --git a/src/blenderbim/test/bim/test_feature.py b/src/blenderbim/test/bim/test_feature.py index e0679dc6c0..dd6424646a 100644 --- a/src/blenderbim/test/bim/test_feature.py +++ b/src/blenderbim/test/bim/test_feature.py @@ -18,6 +18,7 @@ import os import bpy +import blenderbim.tool as tool from blenderbim.bim.ifc import IfcStore from pytest_bdd import scenarios, given, when, then, parsers @@ -103,8 +104,9 @@ def i_delete_the_selected_objects(): bpy.ops.object.delete() -@then(parsers.parse('the variable "{key}" is "{value}"')) +@given(parsers.parse('the variable "{key}" is "{value}"')) @when(parsers.parse('the variable "{key}" is "{value}"')) +@then(parsers.parse('the variable "{key}" is "{value}"')) def the_variable_key_is_value(key, value): variables[key] = eval(replace_variables(value)) @@ -122,7 +124,7 @@ def the_object_name_exists(name) -> bpy.types.Object: return obj -@then('an IFC file exists') +@then("an IFC file exists") def an_ifc_file_exists(): ifc = IfcStore.get_file() if not ifc: @@ -255,3 +257,8 @@ def prop_is_value(prop, value): if not is_value: actual_value = eval(f"bpy.context.{prop}") assert False, f"Value is {actual_value}" + + +@then(parsers.parse('the object "{name}" is in the collection "{collection}"')) +def the_object_name_is_in_the_collection_collection(name, collection): + assert collection in [c.name for c in the_object_name_exists(name).users_collection] diff --git a/src/blenderbim/test/core/test_geometry.py b/src/blenderbim/test/core/test_geometry.py index bc24457b19..2de9749a52 100644 --- a/src/blenderbim/test/core/test_geometry.py +++ b/src/blenderbim/test/core/test_geometry.py @@ -1,6 +1,25 @@ +# 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.geometry as subject from test.core.bootstrap import ifc, surveyor + class TestEditObjectPlacement: def test_run(self, ifc, surveyor): ifc.get_entity("obj").should_be_called().will_return("element") diff --git a/src/blenderbim/test/core/test_spatial.py b/src/blenderbim/test/core/test_spatial.py new file mode 100644 index 0000000000..6dffb3a85a --- /dev/null +++ b/src/blenderbim/test/core/test_spatial.py @@ -0,0 +1,67 @@ +# 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.spatial as subject +from test.core.bootstrap import ifc, collector, container, surveyor + + +class TestAssignContainer: + def test_run(self, ifc, collector, container, surveyor): + container.can_contain("structure_obj", "element_obj").should_be_called().will_return(True) + ifc.get_entity("structure_obj").should_be_called().will_return("structure") + ifc.get_entity("element_obj").should_be_called().will_return("element") + ifc.run( + "spatial.assign_container", product="element", relating_structure="structure" + ).should_be_called().will_return("rel") + surveyor.get_absolute_matrix("element_obj").should_be_called().will_return("matrix") + ifc.run("geometry.edit_object_placement", product="element", matrix="matrix").should_be_called() + container.disable_editing("element_obj").should_be_called() + collector.assign("element_obj").should_be_called() + assert ( + subject.assign_container( + ifc, collector, container, surveyor, structure_obj="structure_obj", element_obj="element_obj" + ) + == "rel" + ) + + +class TestEnableEditingContainer: + def test_run(self, container): + container.enable_editing("obj").should_be_called() + container.import_containers().should_be_called() + subject.enable_editing_container(container, obj="obj") + + +class TestDisableEditingContainer: + def test_run(self, container): + container.disable_editing("obj").should_be_called() + subject.disable_editing_container(container, obj="obj") + + +class TestChangeSpatialLevel: + def test_run(self, container): + container.import_containers(parent="parent").should_be_called() + subject.change_spatial_level(container, parent="parent") + + +class TestRemoveContainer: + def test_run(self, ifc, collector): + ifc.get_entity("obj").should_be_called().will_return("element") + ifc.run("spatial.remove_container", product="element").should_be_called() + collector.assign("obj").should_be_called() + subject.remove_container(ifc, collector, obj="obj") diff --git a/src/blenderbim/test/tool/test_collector.py b/src/blenderbim/test/tool/test_collector.py new file mode 100644 index 0000000000..cc565c0818 --- /dev/null +++ b/src/blenderbim/test/tool/test_collector.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 ifcopenshell +import blenderbim.core.tool +import blenderbim.tool as tool +from blenderbim.tool.collector import Collector as subject +from test.bim.bootstrap import NewFile + + +class TestImplementsTool(NewFile): + def test_run(self): + assert isinstance(subject(), blenderbim.core.tool.Collector) + + +class TestAssign(NewFile): + def test_in_decomposition_mode_walls_are_placed_in_its_spatial_collection(self): + bpy.ops.bim.create_project() + wall_obj = bpy.data.objects.new("Object", None) + wall_element = tool.Ifc.get().createIfcWall() + tool.Ifc.link(wall_element, wall_obj) + bpy.context.scene.collection.objects.link(wall_obj) + ifcopenshell.api.run( + "spatial.assign_container", + tool.Ifc.get(), + product=wall_element, + relating_structure=tool.Ifc.get().by_type("IfcSite")[0], + ) + subject.assign(wall_obj) + assert len(wall_obj.users_collection) == 1 + assert "IfcSite" in wall_obj.users_collection[0].name + + def test_in_decomposition_mode_walls_are_placed_in_the_project_if_not_decomposes(self): + bpy.ops.bim.create_project() + wall_obj = bpy.data.objects.new("Object", None) + wall_element = tool.Ifc.get().createIfcWall() + tool.Ifc.link(wall_element, wall_obj) + bpy.context.scene.collection.objects.link(wall_obj) + subject.assign(wall_obj) + assert len(wall_obj.users_collection) == 1 + assert "IfcProject" in wall_obj.users_collection[0].name diff --git a/src/blenderbim/test/tool/test_container.py b/src/blenderbim/test/tool/test_container.py index f4b883114a..0e2e6736a0 100644 --- a/src/blenderbim/test/tool/test_container.py +++ b/src/blenderbim/test/tool/test_container.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 import ifcopenshell import blenderbim.core.tool diff --git a/src/blenderbim/test/tool/test_owner.py b/src/blenderbim/test/tool/test_owner.py index fdcffbd8e6..8231d6b296 100644 --- a/src/blenderbim/test/tool/test_owner.py +++ b/src/blenderbim/test/tool/test_owner.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 import ifcopenshell import test.bim.bootstrap diff --git a/src/blenderbim/test/tool/test_surveyor.py b/src/blenderbim/test/tool/test_surveyor.py index 18bf47eef5..7bba457df9 100644 --- a/src/blenderbim/test/tool/test_surveyor.py +++ b/src/blenderbim/test/tool/test_surveyor.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 import numpy as np import ifcopenshell diff --git a/src/ifcopenshell-python/test/api/unit/__init__.py b/src/ifcopenshell-python/test/api/unit/__init__.py new file mode 100644 index 0000000000..e69de29bb2