diff --git a/src/blenderbim/blenderbim/bim/module/boundary/__init__.py b/src/blenderbim/blenderbim/bim/module/boundary/__init__.py
index 1f65558621..4a18622060 100644
--- a/src/blenderbim/blenderbim/bim/module/boundary/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/boundary/__init__.py
@@ -17,9 +17,17 @@
# along with BlenderBIM Add-on. If not, see .
import bpy
-from . import ui
+from . import ui, operator
-classes = (ui.BIM_PT_boundary,)
+classes = (
+ operator.LoadProjectSpaceBoundaries,
+ operator.LoadSpaceBoundaries,
+ operator.LoadBoundary,
+ operator.SelectProjectBoundaries,
+ operator.ColourByRelatedBuildingElement,
+ ui.BIM_PT_Boundary,
+ ui.BIM_PT_SceneBoundaries,
+)
def register():
diff --git a/src/blenderbim/blenderbim/bim/module/boundary/operator.py b/src/blenderbim/blenderbim/bim/module/boundary/operator.py
new file mode 100644
index 0000000000..4be2cff3e3
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/module/boundary/operator.py
@@ -0,0 +1,204 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 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 logging
+import bpy
+import ifcopenshell.util.attribute
+import ifcopenshell.api
+import blenderbim.tool as tool
+from blenderbim.bim.ifc import IfcStore
+from ifcopenshell.api.boundary.data import Data
+import blenderbim.bim.import_ifc as import_ifc
+
+
+def get_boundaries_collection(blender_space):
+ space_collection = bpy.data.collections.get(blender_space.name)
+ collection_name = f"Boundaries/{blender_space.BIMObjectProperties.ifc_definition_id}"
+ boundaries_collection = space_collection.children.get(collection_name)
+ if not boundaries_collection:
+ boundaries_collection = bpy.data.collections.new(collection_name)
+ space_collection.children.link(boundaries_collection)
+ return boundaries_collection
+
+
+class Loader:
+ def __init__(self):
+ self.ifc_file = None
+ self.logger = None
+ self.ifc_importer = None
+ self.settings = None
+ self.load_settings()
+ self.load_importer()
+
+ def load_settings(self):
+ self.settings = ifcopenshell.geom.settings()
+ self.settings.set(self.settings.EXCLUDE_SOLIDS_AND_SURFACES, False)
+ self.settings.set(self.settings.USE_BREP_DATA, False)
+
+ def load_importer(self):
+ self.ifc_file = tool.Ifc.get()
+ self.logger = logging.getLogger("ImportIFC")
+ ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, IfcStore.path, self.logger)
+ self.ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
+ self.ifc_importer.file = self.ifc_file
+
+ def load_boundary(self, boundary_id, blender_space):
+ boundary = self.ifc_file.by_id(boundary_id)
+ obj = tool.Ifc.get_object(boundary)
+ if obj:
+ return obj
+ surface = boundary.ConnectionGeometry.SurfaceOnRelatingElement
+ # workaround for unvalid geometry provided by Revit. See https://github.com/IfcOpenShell/IfcOpenShell/issues/635#issuecomment-770366838
+ if surface.is_a("IfcCurveBoundedPlane") and not getattr(surface, "InnerBoundaries", None):
+ surface.InnerBoundaries = ()
+ shape = ifcopenshell.geom.create_shape(self.settings, surface)
+ mesh = self.ifc_importer.create_mesh(None, shape)
+ obj = bpy.data.objects.new(f"{boundary.is_a()}/{boundary_id}", mesh)
+ obj.matrix_world = blender_space.matrix_world
+ boundaries_collection = get_boundaries_collection(blender_space)
+ boundaries_collection.objects.link(obj)
+ tool.Ifc.link(boundary, obj)
+ return obj
+
+
+class LoadProjectSpaceBoundaries(bpy.types.Operator):
+ bl_idname = "bim.load_project_space_boundaries"
+ bl_label = "Load all project space boundaries"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ loader = Loader()
+ if not Data.is_loaded:
+ Data.load(tool.Ifc.get())
+ for space_id, boundaries_id in Data.spaces.items():
+ blender_space = tool.Ifc.get_object(tool.Ifc.get().by_id(space_id))
+ for boundary_id in boundaries_id:
+ loader.load_boundary(boundary_id, blender_space)
+ return {"FINISHED"}
+
+
+class LoadBoundary(bpy.types.Operator):
+ bl_idname = "bim.load_boundary"
+ bl_label = "Load boundary"
+ bl_options = {"REGISTER", "UNDO"}
+ boundary_id: bpy.props.IntProperty()
+
+ def execute(self, context):
+ loader = Loader()
+ blender_space = context.active_object
+ for obj in context.visible_objects:
+ obj.select_set(False)
+ obj = loader.load_boundary(self.boundary_id, blender_space)
+ obj.select_set(True)
+ bpy.context.view_layer.objects.active = obj
+ return {"FINISHED"}
+
+
+class LoadSpaceBoundaries(bpy.types.Operator):
+ bl_idname = "bim.load_space_boundaries"
+ bl_label = "Load selected space boundaries"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ blender_space = context.active_object
+ oprops = context.active_object.BIMObjectProperties
+ loader = Loader()
+ if not Data.is_loaded:
+ Data.load(tool.Ifc.get())
+ for boundary_id in Data.spaces.get(oprops.ifc_definition_id, []):
+ loader.load_boundary(boundary_id, blender_space)
+ return {"FINISHED"}
+
+
+class SelectProjectBoundaries(bpy.types.Operator):
+ bl_idname = "bim.select_project_space_boundaries"
+ bl_label = "Select all project space boundaries"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ for obj in context.visible_objects:
+ obj.select_set(False)
+ ifc_file = tool.Ifc.get()
+ for boundary_id in Data.boundaries:
+ boundary = ifc_file.by_id(boundary_id)
+ obj = tool.Ifc.get_object(boundary)
+ if obj:
+ obj.select_set(True)
+ return {"FINISHED"}
+
+
+def get_colour(ifc_boundary):
+ """Return a color depending on IfcClass given"""
+ product_colors = {
+ "IfcWall": (0.7, 0.3, 0, 1),
+ "IfcWindow": (0, 0.7, 1, 1),
+ "IfcSlab": (0.7, 0.7, 0.5, 1),
+ "IfcRoof": (0, 0.3, 0, 1),
+ "IfcDoor": (1, 1, 1, 1),
+ }
+ if ifc_boundary.PhysicalOrVirtualBoundary == "VIRTUAL":
+ return (1, 0, 1, 1)
+
+ element = ifc_boundary.RelatedBuildingElement
+ if not element:
+ return (1, 0, 0, 1)
+ for product, colour in product_colors.items():
+ if element.is_a(product):
+ return colour
+ return (0, 0, 0, 1)
+
+
+class ColourByRelatedBuildingElement(bpy.types.Operator):
+ bl_idname = "bim.colour_by_related_building_element"
+ bl_label = "Apply colour based on related building elements"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ IfcStore.begin_transaction(self)
+ self.store_state(context)
+ result = self._execute(context)
+ IfcStore.add_transaction_operation(self)
+ IfcStore.end_transaction(self)
+ return result
+
+ def _execute(self, context):
+ self.file = tool.Ifc.get()
+ for obj in context.visible_objects:
+ if not obj.BIMObjectProperties.ifc_definition_id:
+ continue
+ element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
+ if not element.is_a("IfcRelSpaceBoundary"):
+ continue
+ obj.color = get_colour(element)
+ areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
+ if areas:
+ areas[0].spaces[0].shading.color_type = "OBJECT"
+ return {"FINISHED"}
+
+ def store_state(self, context):
+ areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
+ if areas:
+ self.transaction_data = {"area": areas[0], "color_type": areas[0].spaces[0].shading.color_type}
+
+ def rollback(self, data):
+ if data:
+ data["area"].spaces[0].shading.color_type = data["color_type"]
+
+ def commit(self, data):
+ if data:
+ data["area"].spaces[0].shading.color_type = "OBJECT"
diff --git a/src/blenderbim/blenderbim/bim/module/boundary/ui.py b/src/blenderbim/blenderbim/bim/module/boundary/ui.py
index 405f171125..ab8cd0e6bd 100644
--- a/src/blenderbim/blenderbim/bim/module/boundary/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/boundary/ui.py
@@ -20,12 +20,32 @@ import bpy
import blenderbim.bim.helper
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
+import blenderbim.tool as tool
from ifcopenshell.api.boundary.data import Data
-class BIM_PT_boundary(Panel):
+class BIM_PT_SceneBoundaries(Panel):
bl_label = "IFC Space Boundaries"
- bl_idname = "BIM_PT_boundary"
+ bl_id_name = "BIM_PT_scene_boundaries"
+ bl_options = {"DEFAULT_CLOSED"}
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "scene"
+
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file()
+
+ def draw(self, context):
+ row = self.layout.row()
+ row.operator("bim.load_project_space_boundaries")
+ row.operator("bim.select_project_space_boundaries", text="", icon="RESTRICT_SELECT_OFF")
+ row.operator("bim.colour_by_related_building_element", text="", icon="BRUSH_DATA")
+
+
+class BIM_PT_Boundary(Panel):
+ bl_label = "IFC Space Boundaries"
+ bl_idname = "BIM_PT_Boundary"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -46,9 +66,15 @@ class BIM_PT_boundary(Panel):
def draw(self, context):
self.oprops = context.active_object.BIMObjectProperties
+ row = self.layout.row()
+ row.operator("bim.load_space_boundaries")
+ ifc_file = tool.Ifc.get()
if not Data.is_loaded:
- Data.load(IfcStore.get_file())
+ Data.load(ifc_file)
for boundary_id in Data.spaces.get(self.oprops.ifc_definition_id, []):
boundary = Data.boundaries[boundary_id]
+ building_element = ifc_file.by_id(boundary["RelatedBuildingElement"])
row = self.layout.row()
- row.label(text=f"{boundary_id}", icon="GHOST_ENABLED")
+ row.label(text=f"{boundary_id} > {building_element.is_a()}/{building_element.Name}", icon="GHOST_ENABLED")
+ op = row.operator("bim.load_boundary", text="", icon="RESTRICT_SELECT_OFF")
+ op.boundary_id = boundary_id