See #1848. Refactor boundary data class.

This commit is contained in:
Dion Moult
2023-01-30 11:27:38 +11:00
parent b939370f2b
commit 044cd6e97b
6 changed files with 71 additions and 42 deletions
@@ -0,0 +1,46 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2023 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 os
import bpy
import ifcopenshell
import ifcopenshell.util.schema
import blenderbim.tool as tool
def refresh():
SpaceBoundariesData.is_loaded = False
class SpaceBoundariesData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"boundaries": cls.boundaries()}
cls.is_loaded = True
@classmethod
def boundaries(cls):
results = []
element = tool.Ifc.get_entity(bpy.context.active_object)
for rel in element.BoundedBy or []:
description = f"{rel.id()} > {rel.RelatedBuildingElement.is_a()}/{rel.RelatedBuildingElement.Name}"
results.append({"id": rel.id(), "description": description})
return results
@@ -21,7 +21,6 @@ import logging
import ifcopenshell.api import ifcopenshell.api
import blenderbim.tool as tool import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.boundary.data import Data
import blenderbim.bim.import_ifc as import_ifc import blenderbim.bim.import_ifc as import_ifc
@@ -49,7 +48,7 @@ class Loader:
if not boundary.ConnectionGeometry: if not boundary.ConnectionGeometry:
return None return None
surface = boundary.ConnectionGeometry.SurfaceOnRelatingElement surface = boundary.ConnectionGeometry.SurfaceOnRelatingElement
# workaround for invalid geometry provided by Revit. See https://github.com/IfcOpenShell/IfcOpenShell/issues/635#issuecomment-770366838 # Workaround for invalid geometry provided by Revit. See https://github.com/Autodesk/revit-ifc/issues/270
if surface.is_a("IfcCurveBoundedPlane") and not getattr(surface, "InnerBoundaries", None): if surface.is_a("IfcCurveBoundedPlane") and not getattr(surface, "InnerBoundaries", None):
surface.InnerBoundaries = () surface.InnerBoundaries = ()
shape = ifcopenshell.geom.create_shape(self.settings, surface) shape = ifcopenshell.geom.create_shape(self.settings, surface)
@@ -69,8 +68,7 @@ class Loader:
self.ifc_importer = import_ifc.IfcImporter(ifc_import_settings) self.ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
self.ifc_importer.file = self.ifc_file self.ifc_importer.file = self.ifc_file
def load_boundary(self, boundary_id, blender_space): def load_boundary(self, boundary, blender_space):
boundary = self.ifc_file.by_id(boundary_id)
obj = tool.Ifc.get_object(boundary) obj = tool.Ifc.get_object(boundary)
if obj: if obj:
return obj return obj
@@ -90,12 +88,8 @@ class LoadProjectSpaceBoundaries(bpy.types.Operator):
def execute(self, context): def execute(self, context):
loader = Loader() loader = Loader()
if not Data.is_loaded: for rel in tool.Ifc.get().by_type("IfcRelSpaceBoundary"):
Data.load(tool.Ifc.get()) loader.load_boundary(rel, tool.Ifc.get_object(rel.RelatingSpace))
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"} return {"FINISHED"}
@@ -107,10 +101,9 @@ class LoadBoundary(bpy.types.Operator):
def execute(self, context): def execute(self, context):
loader = Loader() loader = Loader()
blender_space = context.active_object
for obj in context.visible_objects: for obj in context.visible_objects:
obj.select_set(False) obj.select_set(False)
obj = loader.load_boundary(self.boundary_id, blender_space) obj = loader.load_boundary(tool.Ifc.get().by_id(self.boundary_id), context.active_object)
obj.select_set(True) obj.select_set(True)
bpy.context.view_layer.objects.active = obj bpy.context.view_layer.objects.active = obj
return {"FINISHED"} return {"FINISHED"}
@@ -122,13 +115,10 @@ class LoadSpaceBoundaries(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def execute(self, context): def execute(self, context):
blender_space = context.active_object
oprops = context.active_object.BIMObjectProperties
loader = Loader() loader = Loader()
if not Data.is_loaded: element = tool.Ifc.get_entity(context.active_object)
Data.load(tool.Ifc.get()) for rel in element.BoundedBy or []:
for boundary_id in Data.spaces.get(oprops.ifc_definition_id, []): loader.load_boundary(rel, context.active_object)
loader.load_boundary(boundary_id, blender_space)
return {"FINISHED"} return {"FINISHED"}
@@ -140,10 +130,8 @@ class SelectProjectBoundaries(bpy.types.Operator):
def execute(self, context): def execute(self, context):
for obj in context.visible_objects: for obj in context.visible_objects:
obj.select_set(False) obj.select_set(False)
ifc_file = tool.Ifc.get() for rel in tool.Ifc.get().by_type("IfcRelSpaceBoundary"):
for boundary_id in Data.boundaries: obj = tool.Ifc.get_object(rel)
boundary = ifc_file.by_id(boundary_id)
obj = tool.Ifc.get_object(boundary)
if obj: if obj:
obj.select_set(True) obj.select_set(True)
return {"FINISHED"} return {"FINISHED"}
@@ -21,7 +21,7 @@ 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 import blenderbim.tool as tool
from ifcopenshell.api.boundary.data import Data from blenderbim.bim.module.boundary.data import SpaceBoundariesData
class BIM_PT_SceneBoundaries(Panel): class BIM_PT_SceneBoundaries(Panel):
@@ -129,28 +129,22 @@ class BIM_PT_SpaceBoundaries(Panel):
return False return False
if not IfcStore.get_element(props.ifc_definition_id): if not IfcStore.get_element(props.ifc_definition_id):
return False return False
valid_classes = ("IfcSpace", "IfcExternalSpatialElement") element = tool.Ifc.get_entity(context.active_object)
entity = IfcStore.get_file().by_id(props.ifc_definition_id) for ifc_class in ("IfcSpace", "IfcExternalSpatialElement"):
for ifc_class in valid_classes: if element.is_a(ifc_class):
if entity.is_a(ifc_class):
return True return True
return False return False
def draw(self, context): def draw(self, context):
if not SpaceBoundariesData.is_loaded:
SpaceBoundariesData.load()
self.props = context.active_object.BIMObjectProperties self.props = context.active_object.BIMObjectProperties
self.ifc_file = tool.Ifc.get() self.ifc_file = tool.Ifc.get()
row = self.layout.row() row = self.layout.row()
row.operator("bim.load_space_boundaries") row.operator("bim.load_space_boundaries")
if not Data.is_loaded: for boundary in SpaceBoundariesData.data["boundaries"]:
Data.load(self.ifc_file)
for boundary_id in Data.spaces.get(self.props.ifc_definition_id, []):
boundary_data = Data.boundaries[boundary_id]
building_element = self.ifc_file.by_id(boundary_data["RelatedBuildingElement"])
row = self.layout.row() row = self.layout.row()
if building_element: row.label(text=boundary["description"], icon="GHOST_ENABLED")
bld_el_description = f"{building_element.is_a()}/{building_element.Name}"
else:
bld_el_description = None
row.label(text=f"{boundary_id} > {bld_el_description}", icon="GHOST_ENABLED")
op = row.operator("bim.load_boundary", text="", icon="RESTRICT_SELECT_OFF") op = row.operator("bim.load_boundary", text="", icon="RESTRICT_SELECT_OFF")
op.boundary_id = boundary_id op.boundary_id = boundary["id"]
+1 -1
View File
@@ -261,4 +261,4 @@ def sync_references(ifc, collector, drawing_tool, drawing=None):
drawing_tool.sync_object_representation(reference_obj) drawing_tool.sync_object_representation(reference_obj)
def select_assigned_product(drawing): def select_assigned_product(drawing):
drawing.select_assigned_product() drawing.select_assigned_product()
@@ -7,7 +7,7 @@ Scenario: Execute IFC Patch
And I set "scene.BIMPatchProperties.ifc_patch_input" to "{cwd}/test/files/basic.ifc" And I set "scene.BIMPatchProperties.ifc_patch_input" to "{cwd}/test/files/basic.ifc"
And I set "scene.BIMPatchProperties.ifc_patch_output" to "{cwd}/test/files/basic-patched.ifc" And I set "scene.BIMPatchProperties.ifc_patch_output" to "{cwd}/test/files/basic-patched.ifc"
And I set "scene.BIMPatchProperties.ifc_patch_args" to "[123454321,0,0,0]" And I set "scene.BIMPatchProperties.ifc_patch_args" to "[123454321,0,0,0]"
When I press "bim.execute_ifc_patch" When I press "bim.execute_ifc_patch(use_json_for_args=True)"
Then the file "{cwd}/test/files/basic-patched.ifc" should contain "123454321" Then the file "{cwd}/test/files/basic-patched.ifc" should contain "123454321"
Scenario: Run migrate patch Scenario: Run migrate patch
+3 -2
View File
@@ -33,7 +33,9 @@ class TestImplementsTool(NewFile):
class TestAddSchemaIdentifier(NewFile): class TestAddSchemaIdentifier(NewFile):
def test_run(self): def test_run(self):
subject.add_schema_identifier(TestLoadExpress().test_run()) cwd = os.path.dirname(os.path.realpath(__file__))
schema = subject.load_express(os.path.join(cwd, "..", "files", "test.exp"))
subject.add_schema_identifier(schema)
assert IfcStore.schema_identifiers[-1] == "IFCROGUE" assert IfcStore.schema_identifiers[-1] == "IFCROGUE"
@@ -42,7 +44,6 @@ class TestLoadExpress(NewFile):
cwd = os.path.dirname(os.path.realpath(__file__)) cwd = os.path.dirname(os.path.realpath(__file__))
schema = subject.load_express(os.path.join(cwd, "..", "files", "test.exp")) schema = subject.load_express(os.path.join(cwd, "..", "files", "test.exp"))
assert schema.schema_name == "IFCROGUE" assert schema.schema_name == "IFCROGUE"
return schema
class TestPurgeHdf5Cache(NewFile): class TestPurgeHdf5Cache(NewFile):