[BlenderBIM] Tools to load IfcRelSpaceBoundary (#1864)

* Tools to load IfcRelSpaceBoundary

At scene level to load all.
At space level to load ones from a specific space or a specific one.

* Add a tool to colour boundaries

Colour is based on its related building element.
This commit is contained in:
Cyril Waechter
2021-11-08 23:28:44 +01:00
committed by GitHub
parent 8a4bc34fad
commit 849dfbed28
3 changed files with 244 additions and 6 deletions
@@ -17,9 +17,17 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy 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(): def register():
@@ -0,0 +1,204 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 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 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"
@@ -20,12 +20,32 @@ import bpy
import blenderbim.bim.helper import blenderbim.bim.helper
from bpy.types import Panel, UIList from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
import blenderbim.tool as tool
from ifcopenshell.api.boundary.data import Data from ifcopenshell.api.boundary.data import Data
class BIM_PT_boundary(Panel): class BIM_PT_SceneBoundaries(Panel):
bl_label = "IFC Space Boundaries" 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_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES" bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW" bl_region_type = "WINDOW"
@@ -46,9 +66,15 @@ class BIM_PT_boundary(Panel):
def draw(self, context): def draw(self, context):
self.oprops = context.active_object.BIMObjectProperties 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: 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, []): for boundary_id in Data.spaces.get(self.oprops.ifc_definition_id, []):
boundary = Data.boundaries[boundary_id] boundary = Data.boundaries[boundary_id]
building_element = ifc_file.by_id(boundary["RelatedBuildingElement"])
row = self.layout.row() 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