Removing or changing spatial containers now supports updating more types of collection organisations

This commit is contained in:
Dion Moult
2021-10-05 14:52:41 +11:00
parent f5913baaca
commit 7c94d4275a
26 changed files with 582 additions and 193 deletions
@@ -188,9 +188,11 @@ class OwnerData:
def get_users(cls): def get_users(cls):
results = [] results = []
for user in tool.Ifc.get().by_type("IfcPersonAndOrganization"): for user in tool.Ifc.get().by_type("IfcPersonAndOrganization"):
results.append({ results.append(
"id": user.id(), {
"label": "{} ({})".format(user.ThePerson[0] or "Unnamed", user.TheOrganization[0] or "Unnamed"), "id": user.id(),
"is_active": bpy.context.scene.BIMOwnerProperties.active_user_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 return results
@@ -27,8 +27,8 @@ import ifcopenshell.util.selector
import ifcopenshell.util.representation import ifcopenshell.util.representation
import blenderbim.bim.handler import blenderbim.bim.handler
import blenderbim.tool as tool import blenderbim.tool as tool
import blenderbim.core.context as core_context import blenderbim.core.context
import blenderbim.core.owner as core_owner import blenderbim.core.owner
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim import import_ifc from blenderbim.bim import import_ifc
@@ -58,10 +58,10 @@ class CreateProject(bpy.types.Operator):
) )
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
person = core_owner.add_person(tool.Ifc) person = blenderbim.core.owner.add_person(tool.Ifc)
organisation = core_owner.add_organisation(tool.Ifc) organisation = blenderbim.core.owner.add_organisation(tool.Ifc)
user = core_owner.add_person_and_organisation(tool.Ifc, person=person, organisation=organisation) user = blenderbim.core.owner.add_person_and_organisation(tool.Ifc, person=person, organisation=organisation)
core_owner.set_user(tool.Owner, user=user) blenderbim.core.owner.set_user(tool.Owner, user=user)
project = bpy.data.objects.new(self.get_name("IfcProject", "My Project"), None) project = bpy.data.objects.new(self.get_name("IfcProject", "My Project"), None)
site = bpy.data.objects.new(self.get_name("IfcSite", "My Site"), 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_class(obj=project.name, ifc_class="IfcProject")
bpy.ops.bim.assign_unit() 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 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 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 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) plan = blenderbim.core.context.add_context(
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 tool.Ifc, context_type="Plan", context_identifier="Annotation", target_view="PLAN_VIEW", parent=plan
) )
@@ -22,6 +22,8 @@ import ifcopenshell.api
import ifcopenshell.util.schema import ifcopenshell.util.schema
import ifcopenshell.util.element import ifcopenshell.util.element
import blenderbim.bim.handler import blenderbim.bim.handler
import blenderbim.core.spatial
import blenderbim.tool as tool
from ifcopenshell.api.void.data import Data as VoidData from ifcopenshell.api.void.data import Data as VoidData
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
@@ -236,8 +238,13 @@ class AssignClass(bpy.types.Operator):
continue continue
elif self.file.schema == "IFC2X3" and not element.is_a("IfcSpatialStructureElement"): elif self.file.schema == "IFC2X3" and not element.is_a("IfcSpatialStructureElement"):
continue continue
bpy.ops.bim.assign_container( blenderbim.core.spatial.assign_container(
relating_structure=spatial_obj.BIMObjectProperties.ifc_definition_id, related_element=obj.name tool.Ifc,
tool.Collector,
tool.Container,
tool.Surveyor,
structure_obj=spatial_obj,
element_obj=obj,
) )
break break
@@ -30,7 +30,7 @@ classes = (
prop.BIMSpatialProperties, prop.BIMSpatialProperties,
prop.BIMObjectSpatialProperties, prop.BIMObjectSpatialProperties,
ui.BIM_PT_spatial, ui.BIM_PT_spatial,
ui.BIM_UL_spatial_elements, ui.BIM_UL_containers,
) )
@@ -0,0 +1,71 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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 ''}"
@@ -19,150 +19,74 @@
import bpy import bpy
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.element import ifcopenshell.util.element
import blenderbim.tool as tool
import blenderbim.core.spatial as core
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.spatial.data import Data from blenderbim.bim.module.spatial.data import SpatialData
from blenderbim.bim.module.spatial.prop import getSpatialContainers
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_idname = "bim.assign_container"
bl_label = "Assign Container" bl_label = "Assign Container"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
relating_structure: bpy.props.IntProperty() structure: bpy.props.IntProperty()
related_element: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context): def _execute(self, context):
# The active object pointer is lost when the object is unlinked from all collections in the view layer structure_obj = tool.Ifc.get_object(tool.Ifc.get().by_id(self.structure))
# We need to store it first, then restore it at the end for element_obj in context.selected_objects:
active_object = context.active_object core.assign_container(
self.file = IfcStore.get_file() tool.Ifc,
related_elements = ( tool.Collector,
[bpy.data.objects.get(self.related_element)] if self.related_element else context.selected_objects tool.Container,
) tool.Surveyor,
sprops = context.scene.BIMSpatialProperties structure_obj=structure_obj,
relating_structure = ( element_obj=element_obj,
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),
},
) )
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_idname = "bim.enable_editing_container"
bl_label = "Enable Editing Container" bl_label = "Enable Editing Container"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def execute(self, context): def _execute(self, context):
context.active_object.BIMObjectSpatialProperties.is_editing = True core.enable_editing_container(tool.Container, obj=context.active_object)
getSpatialContainers(self, context)
return {"FINISHED"}
class ChangeSpatialLevel(bpy.types.Operator): class ChangeSpatialLevel(bpy.types.Operator, Operator):
bl_idname = "bim.change_spatial_level" bl_idname = "bim.change_spatial_level"
bl_label = "Change Spatial Level" bl_label = "Change Spatial Level"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
parent_id: bpy.props.IntProperty() parent: bpy.props.IntProperty()
def execute(self, context): def _execute(self, context):
getSpatialContainers(self, context, parent_id=self.parent_id) core.change_spatial_level(tool.Container, parent=tool.Ifc.get().by_id(self.parent))
return {"FINISHED"}
class DisableEditingContainer(bpy.types.Operator): class DisableEditingContainer(bpy.types.Operator, Operator):
bl_idname = "bim.disable_editing_container" bl_idname = "bim.disable_editing_container"
bl_label = "Disable Editing Container" bl_label = "Disable Editing Container"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context): def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object core.disable_editing_container(tool.Container, obj=context.active_object)
obj.BIMObjectSpatialProperties.is_editing = False
return {"FINISHED"}
class RemoveContainer(bpy.types.Operator): class RemoveContainer(bpy.types.Operator, Operator):
bl_idname = "bim.remove_container" bl_idname = "bim.remove_container"
bl_label = "Remove Container" bl_label = "Remove Container"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context): def _execute(self, context):
active_object = context.active_object for obj in context.selected_objects:
self.file = IfcStore.get_file() core.remove_container(tool.Ifc, tool.Collector, obj=obj)
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
class CopyToContainer(bpy.types.Operator): class CopyToContainer(bpy.types.Operator):
@@ -18,8 +18,7 @@
import bpy import bpy
from blenderbim.bim.prop import StrProperty, Attribute from blenderbim.bim.prop import StrProperty, Attribute
from ifcopenshell.api.spatial.data import Data from blenderbim.bim.module.spatial.data import SpatialData
from blenderbim.bim.ifc import IfcStore
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
from bpy.props import ( from bpy.props import (
PointerProperty, PointerProperty,
@@ -33,37 +32,8 @@ from bpy.props import (
) )
def getSpatialContainers(self, context, parent_id=None): def update_active_container_index(self, context):
Data.load(IfcStore.get_file()) SpatialData.is_loaded = False
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"]
class SpatialElement(PropertyGroup): class SpatialElement(PropertyGroup):
@@ -75,10 +45,9 @@ class SpatialElement(PropertyGroup):
class BIMSpatialProperties(PropertyGroup): class BIMSpatialProperties(PropertyGroup):
spatial_elements: CollectionProperty(name="Spatial Elements", type=SpatialElement) containers: CollectionProperty(name="Containers", type=SpatialElement)
active_spatial_element_index: IntProperty(name="Active Spatial Element Index") active_container_index: IntProperty(name="Active Container Index", update=update_active_container_index)
active_decomposes_id: IntProperty(name="Active Decomposes Id") active_container_id: IntProperty(name="Active Container Id")
active_decomposes_parent_id: IntProperty(name="Active Decomposes Parent Id")
class BIMObjectSpatialProperties(PropertyGroup): class BIMObjectSpatialProperties(PropertyGroup):
@@ -19,6 +19,7 @@
from bpy.types import Panel, UIList from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.spatial.data import Data from ifcopenshell.api.spatial.data import Data
from blenderbim.bim.module.spatial.data import SpatialData
class BIM_PT_spatial(Panel): class BIM_PT_spatial(Panel):
@@ -44,44 +45,36 @@ class BIM_PT_spatial(Panel):
return True return True
def draw(self, context): def draw(self, context):
oprops = context.active_object.BIMObjectProperties if not SpatialData.is_loaded:
sprops = context.scene.BIMSpatialProperties SpatialData.load()
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 props.is_editing: if SpatialData.data["is_editing"]:
row = self.layout.row(align=True) 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 = row.operator("bim.change_spatial_level", text="", icon="FRAME_PREV")
op.parent_id = sprops.active_decomposes_parent_id op.parent = SpatialData.data["parent_container_id"]
row.operator("bim.assign_container", icon="CHECKMARK") row.operator("bim.assign_container", icon="CHECKMARK").structure = SpatialData.data["container_id"]
row.operator("bim.copy_to_container", icon="COPYDOWN", text="") row.operator("bim.copy_to_container", icon="COPYDOWN", text="")
row.operator("bim.disable_editing_container", icon="CANCEL", text="") row.operator("bim.disable_editing_container", icon="CANCEL", text="")
self.layout.template_list( self.layout.template_list("BIM_UL_containers", *SpatialData.data["container_list_settings"])
"BIM_UL_spatial_elements", "", sprops, "spatial_elements", sprops, "active_spatial_element_index"
)
else: else:
row = self.layout.row(align=True) row = self.layout.row(align=True)
name = "{}/{}".format( if SpatialData.data["is_contained"]:
Data.products[oprops.ifc_definition_id]["type"], Data.products[oprops.ifc_definition_id]["Name"] row.label(text=SpatialData.data["label"])
) row.operator("bim.enable_editing_container", icon="GREASEPENCIL", text="")
if name == "None/None": row.operator("bim.remove_container", icon="X", text="")
name = "This object is not spatially contained" else:
row.label(text=name) row.label(text="This object is not spatially contained")
row.operator("bim.enable_editing_container", icon="GREASEPENCIL", text="") row.operator("bim.enable_editing_container", icon="GREASEPENCIL", text="")
row.operator("bim.remove_container", icon="X", 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): def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item: if item:
if item.has_decomposition: if item.has_decomposition:
op = layout.operator("bim.change_spatial_level", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False) 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.name)
layout.label(text=item.long_name) layout.label(text=item.long_name)
layout.prop( layout.prop(
@@ -1,3 +1,22 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
def edit_object_placement(ifc, surveyor, obj=None): def edit_object_placement(ifc, surveyor, obj=None):
element = ifc.get_entity(obj) element = ifc.get_entity(obj)
if element: if element:
+51
View File
@@ -0,0 +1,51 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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)
+5
View File
@@ -83,6 +83,11 @@ class Container(abc.ABC):
def disable_editing(cls, obj): def disable_editing(cls, obj):
pass pass
@classmethod
@abc.abstractmethod
def import_containers(cls, parent=None):
pass
class ContextEditor(abc.ABC): class ContextEditor(abc.ABC):
@classmethod @classmethod
+2 -1
View File
@@ -18,6 +18,8 @@
from blenderbim.tool.address_editor import AddressEditor from blenderbim.tool.address_editor import AddressEditor
from blenderbim.tool.blender import Blender 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.context_editor import ContextEditor
from blenderbim.tool.ifc import Ifc from blenderbim.tool.ifc import Ifc
from blenderbim.tool.organisation_editor import OrganisationEditor 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.person_editor import PersonEditor
from blenderbim.tool.role_editor import RoleEditor from blenderbim.tool.role_editor import RoleEditor
from blenderbim.tool.surveyor import Surveyor from blenderbim.tool.surveyor import Surveyor
from blenderbim.tool.container import Container
@@ -0,0 +1,54 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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
@@ -1,7 +1,26 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
import bpy import bpy
import blenderbim.core.tool import blenderbim.core.tool
import blenderbim.tool as tool import blenderbim.tool as tool
class Container(blenderbim.core.tool.Container): class Container(blenderbim.core.tool.Container):
@classmethod @classmethod
def can_contain(cls, structure_obj, element_obj): def can_contain(cls, structure_obj, element_obj):
+19
View File
@@ -1,7 +1,26 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
import bpy import bpy
import blenderbim.core.tool import blenderbim.core.tool
import blenderbim.tool as tool import blenderbim.tool as tool
class Owner(blenderbim.core.tool.Owner): class Owner(blenderbim.core.tool.Owner):
@classmethod @classmethod
def set_user(cls, user): def set_user(cls, user):
@@ -1,3 +1,21 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
import bpy import bpy
import ifcopenshell.api import ifcopenshell.api
import blenderbim.core.tool import blenderbim.core.tool
+1
View File
@@ -4,3 +4,4 @@ markers =
geometry geometry
owner owner
void void
spatial
@@ -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"
+9 -2
View File
@@ -18,6 +18,7 @@
import os import os
import bpy import bpy
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from pytest_bdd import scenarios, given, when, then, parsers from pytest_bdd import scenarios, given, when, then, parsers
@@ -103,8 +104,9 @@ def i_delete_the_selected_objects():
bpy.ops.object.delete() 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}"')) @when(parsers.parse('the variable "{key}" is "{value}"'))
@then(parsers.parse('the variable "{key}" is "{value}"'))
def the_variable_key_is_value(key, value): def the_variable_key_is_value(key, value):
variables[key] = eval(replace_variables(value)) variables[key] = eval(replace_variables(value))
@@ -122,7 +124,7 @@ def the_object_name_exists(name) -> bpy.types.Object:
return obj return obj
@then('an IFC file exists') @then("an IFC file exists")
def an_ifc_file_exists(): def an_ifc_file_exists():
ifc = IfcStore.get_file() ifc = IfcStore.get_file()
if not ifc: if not ifc:
@@ -255,3 +257,8 @@ def prop_is_value(prop, value):
if not is_value: if not is_value:
actual_value = eval(f"bpy.context.{prop}") actual_value = eval(f"bpy.context.{prop}")
assert False, f"Value is {actual_value}" 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]
+19
View File
@@ -1,6 +1,25 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
import blenderbim.core.geometry as subject import blenderbim.core.geometry as subject
from test.core.bootstrap import ifc, surveyor from test.core.bootstrap import ifc, surveyor
class TestEditObjectPlacement: class TestEditObjectPlacement:
def test_run(self, ifc, surveyor): def test_run(self, ifc, surveyor):
ifc.get_entity("obj").should_be_called().will_return("element") ifc.get_entity("obj").should_be_called().will_return("element")
+67
View File
@@ -0,0 +1,67 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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")
@@ -0,0 +1,57 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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
@@ -1,3 +1,21 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
import bpy import bpy
import ifcopenshell import ifcopenshell
import blenderbim.core.tool import blenderbim.core.tool
+18
View File
@@ -1,3 +1,21 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
import bpy import bpy
import ifcopenshell import ifcopenshell
import test.bim.bootstrap import test.bim.bootstrap
+18
View File
@@ -1,3 +1,21 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
import bpy import bpy
import numpy as np import numpy as np
import ifcopenshell import ifcopenshell