You can now associate IFC elements with Brick elements and navigate to them

This commit is contained in:
Dion Moult
2021-11-17 13:27:07 +11:00
parent da6945b352
commit f9221717ff
12 changed files with 248 additions and 3 deletions
@@ -20,7 +20,9 @@ import bpy
from . import ui, prop, operator from . import ui, prop, operator
classes = ( classes = (
operator.AssignBrickReference,
operator.CloseBrickProject, operator.CloseBrickProject,
operator.ConvertBrickProject,
operator.LoadBrickProject, operator.LoadBrickProject,
operator.RewindBrickClass, operator.RewindBrickClass,
operator.ViewBrickClass, operator.ViewBrickClass,
@@ -28,6 +30,7 @@ classes = (
prop.Brick, prop.Brick,
prop.BIMBrickProperties, prop.BIMBrickProperties,
ui.BIM_PT_brickschema, ui.BIM_PT_brickschema,
ui.BIM_PT_ifc_brickschema_references,
ui.BIM_UL_bricks, ui.BIM_UL_bricks,
) )
@@ -23,6 +23,7 @@ from blenderbim.tool.brick import BrickStore
def refresh(): def refresh():
BrickschemaData.is_loaded = False BrickschemaData.is_loaded = False
BrickschemaReferencesData.is_loaded = False
class BrickschemaData: class BrickschemaData:
@@ -76,3 +77,45 @@ class BrickschemaData:
} }
) )
return results return results
class BrickschemaReferencesData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data = {"is_loaded": cls.get_is_loaded(), "libraries": cls.libraries(), "references": cls.references()}
@classmethod
def get_is_loaded(cls):
return BrickStore.graph is not None
@classmethod
def libraries(cls):
results = []
for library in tool.Ifc.get().by_type("IfcLibraryInformation"):
results.append((str(library.id()), library.Name or "Unnamed", ""))
return results
@classmethod
def references(cls):
results = []
for rel in getattr(tool.Ifc.get_entity(bpy.context.active_object), "HasAssociations", []):
if rel.is_a("IfcRelAssociatesLibrary"):
reference = rel.RelatingLibrary
if tool.Ifc.get_schema() == "IFC2X3" and "#" not in reference.ItemReference:
continue
if tool.Ifc.get_schema() != "IFC2X3" and "#" not in reference.Identification:
continue
results.append(
{
"id": reference.id(),
"identification": reference.ItemReference
if tool.Ifc.get_schema() == "IFC2X3"
else reference.Identification,
"name": reference.Name or "Unnamed",
}
)
return results
@@ -82,3 +82,28 @@ class CloseBrickProject(bpy.types.Operator, Operator):
def _execute(self, context): def _execute(self, context):
core.close_brick_project(tool.Brick) core.close_brick_project(tool.Brick)
class ConvertBrickProject(bpy.types.Operator, Operator):
bl_idname = "bim.convert_brick_project"
bl_label = "Convert Brick Project"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.convert_brick_project(tool.Ifc, tool.Brick)
class AssignBrickReference(bpy.types.Operator, Operator):
bl_idname = "bim.assign_brick_reference"
bl_label = "Assign Brick Reference"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMBrickProperties
core.assign_brick_reference(
tool.Ifc,
tool.Brick,
obj=context.active_object,
library=tool.Ifc.get().by_id(int(props.libraries)),
brick_uri=props.bricks[props.active_brick_index].uri,
)
@@ -17,7 +17,7 @@
# 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 blenderbim.bim.module.brick.data import BrickschemaData from blenderbim.bim.module.brick.data import BrickschemaData, BrickschemaReferencesData
from blenderbim.bim.prop import StrProperty, Attribute from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
from bpy.props import ( from bpy.props import (
@@ -36,6 +36,12 @@ def update_active_brick_index(self, context):
BrickschemaData.is_loaded = False BrickschemaData.is_loaded = False
def get_libraries(self, context):
if not BrickschemaReferencesData.is_loaded:
BrickschemaReferencesData.load()
return BrickschemaReferencesData.data["libraries"]
class Brick(PropertyGroup): class Brick(PropertyGroup):
name: StringProperty(name="Name") name: StringProperty(name="Name")
uri: StringProperty(name="URI") uri: StringProperty(name="URI")
@@ -47,3 +53,4 @@ class BIMBrickProperties(PropertyGroup):
brick_breadcrumbs: CollectionProperty(name="Brick Breadcrumbs", type=StrProperty) brick_breadcrumbs: CollectionProperty(name="Brick Breadcrumbs", type=StrProperty)
bricks: CollectionProperty(name="Bricks", type=Brick) bricks: CollectionProperty(name="Bricks", type=Brick)
active_brick_index: IntProperty(name="Active Brick Index", update=update_active_brick_index) active_brick_index: IntProperty(name="Active Brick Index", update=update_active_brick_index)
libraries: EnumProperty(name="Libraries", items=get_libraries)
@@ -16,8 +16,9 @@
# You should have received a copy of the GNU General Public License # 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/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.tool as tool
from bpy.types import Panel, UIList from bpy.types import Panel, UIList
from blenderbim.bim.module.brick.data import BrickschemaData from blenderbim.bim.module.brick.data import BrickschemaData, BrickschemaReferencesData
class BIM_PT_brickschema(Panel): class BIM_PT_brickschema(Panel):
@@ -55,6 +56,54 @@ class BIM_PT_brickschema(Panel):
op.item = attribute["value_uri"] op.item = attribute["value_uri"]
class BIM_PT_ifc_brickschema_references(Panel):
bl_label = "IFC Brickschema References"
bl_idname = "BIM_PT_ifc_brickschema_references"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_brickschema"
@classmethod
def poll(cls, context):
return tool.Ifc.get()
def draw(self, context):
if not BrickschemaReferencesData.is_loaded:
BrickschemaReferencesData.load()
self.props = context.scene.BIMBrickProperties
if not BrickschemaReferencesData.data["is_loaded"]:
row = self.layout.row()
row.label(text="No Brickschema Project Loaded")
return
if not BrickschemaReferencesData.data["libraries"]:
row = self.layout.row(align=True)
row.label(text="No IFC Libraries")
row.operator("bim.convert_brick_project", text="", icon="ADD")
return
row = self.layout.row(align=True)
row.prop(self.props, "libraries")
row.operator("bim.convert_brick_project", text="", icon="ADD")
row = self.layout.row()
row.operator("bim.assign_brick_reference", icon="ADD")
if not BrickschemaReferencesData.data["references"]:
row = self.layout.row()
row.label(text="No References")
for reference in BrickschemaReferencesData.data["references"]:
row = self.layout.row(align=True)
row.label(text=reference["identification"], icon="ASSET_MANAGER")
row.label(text=reference["name"])
row.operator("bim.unassign_library_reference", text="", icon="X").reference = reference["id"]
row.operator("bim.view_brick_item", text="", icon="DISCLOSURE_TRI_RIGHT").item = reference["identification"]
class BIM_UL_bricks(UIList): class BIM_UL_bricks(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:
+13
View File
@@ -47,3 +47,16 @@ def rewind_brick_class(brick):
def close_brick_project(brick): def close_brick_project(brick):
brick.clear_project() brick.clear_project()
def convert_brick_project(ifc, brick):
library = ifc.run("library.add_library", name=brick.get_brick_path_name())
ifc.run("library.edit_library", library=library, attributes={"Location": brick.get_brick_path()})
def assign_brick_reference(ifc, brick, obj=None, library=None, brick_uri=None):
reference = brick.get_library_brick_reference(library, brick_uri)
if not reference:
reference = ifc.run("library.add_reference", library=library)
ifc.run("library.edit_reference", reference=reference, attributes=brick.export_brick_attributes(brick_uri))
ifc.run("library.assign_reference", product=ifc.get_entity(obj), reference=reference)
+4
View File
@@ -46,7 +46,11 @@ class Brick:
def add_brick_breadcrumb(cls): pass def add_brick_breadcrumb(cls): pass
def clear_brick_browser(cls): pass def clear_brick_browser(cls): pass
def clear_project(cls): pass def clear_project(cls): pass
def export_brick_attributes(cls, brick_uri): pass
def get_brick_path(cls): pass
def get_brick_path_name(cls): pass
def get_item_class(cls, item): pass def get_item_class(cls, item): pass
def get_library_brick_reference(cls, library, brick_uri): pass
def import_brick_classes(cls, brick_class): pass def import_brick_classes(cls, brick_class): pass
def import_brick_items(cls, brick_class): pass def import_brick_items(cls, brick_class): pass
def load_brick_file(cls, filepath): pass def load_brick_file(cls, filepath): pass
+26
View File
@@ -48,6 +48,18 @@ class Brick(blenderbim.core.tool.Brick):
bpy.context.scene.BIMBrickProperties.active_brick_class == "" bpy.context.scene.BIMBrickProperties.active_brick_class == ""
bpy.context.scene.BIMBrickProperties.brick_breadcrumbs.clear() bpy.context.scene.BIMBrickProperties.brick_breadcrumbs.clear()
@classmethod
def export_brick_attributes(cls, brick_uri):
return {"Identification": brick_uri, "Name": brick_uri.split("#")[-1]}
@classmethod
def get_brick_path(cls):
return BrickStore.path
@classmethod
def get_brick_path_name(cls):
return os.path.basename(BrickStore.path)
@classmethod @classmethod
def get_item_class(cls, item): def get_item_class(cls, item):
query = BrickStore.graph.query( query = BrickStore.graph.query(
@@ -64,6 +76,17 @@ class Brick(blenderbim.core.tool.Brick):
for row in query: for row in query:
return row.get("class").toPython().split("#")[-1] return row.get("class").toPython().split("#")[-1]
@classmethod
def get_library_brick_reference(cls, library, brick_uri):
if tool.Ifc.get_schema() == "IFC2X3":
for reference in library.LibraryReference:
if reference.ItemReference == brick_uri:
return reference
else:
for reference in library.HasLibraryReferences:
if reference.Identification == brick_uri:
return reference
@classmethod @classmethod
def import_brick_classes(cls, brick_class): def import_brick_classes(cls, brick_class):
query = BrickStore.graph.query( query = BrickStore.graph.query(
@@ -115,6 +138,7 @@ class Brick(blenderbim.core.tool.Brick):
schema_path = os.path.join(cwd, "..", "bim", "schema", "Brick.ttl") schema_path = os.path.join(cwd, "..", "bim", "schema", "Brick.ttl")
BrickStore.schema.load_file(schema_path) BrickStore.schema.load_file(schema_path)
BrickStore.graph = brickschema.Graph().load_file(filepath) + BrickStore.schema BrickStore.graph = brickschema.Graph().load_file(filepath) + BrickStore.schema
BrickStore.path = filepath
@classmethod @classmethod
def pop_brick_breadcrumb(cls): def pop_brick_breadcrumb(cls):
@@ -138,8 +162,10 @@ class Brick(blenderbim.core.tool.Brick):
class BrickStore: class BrickStore:
schema = None schema = None
graph = None graph = None
path = None
@staticmethod @staticmethod
def purge(): def purge():
BrickStore.schema = None BrickStore.schema = None
BrickStore.graph = None BrickStore.graph = None
BrickStore.path = None
+29 -1
View File
@@ -17,7 +17,7 @@
# 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 blenderbim.core.brick as subject import blenderbim.core.brick as subject
from test.core.bootstrap import brick from test.core.bootstrap import ifc, brick
class TestLoadBrickProject: class TestLoadBrickProject:
@@ -64,3 +64,31 @@ class TestCloseBrickProject:
def test_run(self, brick): def test_run(self, brick):
brick.clear_project().should_be_called() brick.clear_project().should_be_called()
subject.close_brick_project(brick) subject.close_brick_project(brick)
class TestConvertBrickProject:
def test_run(self, ifc, brick):
brick.get_brick_path_name().should_be_called().will_return("foo.ttl")
ifc.run("library.add_library", name="foo.ttl").should_be_called().will_return("library")
brick.get_brick_path().should_be_called().will_return("/path/to/foo.ttl")
ifc.run(
"library.edit_library", library="library", attributes={"Location": "/path/to/foo.ttl"}
).should_be_called()
subject.convert_brick_project(ifc, brick)
class TestAssignBrickReference:
def test_assigning_to_a_new_reference(self, ifc, brick):
brick.get_library_brick_reference("library", "brick").should_be_called().will_return(None)
ifc.run("library.add_reference", library="library").should_be_called().will_return("reference")
brick.export_brick_attributes("brick").should_be_called().will_return("attributes")
ifc.run("library.edit_reference", reference="reference", attributes="attributes").should_be_called()
ifc.get_entity("obj").should_be_called().will_return("product")
ifc.run("library.assign_reference", product="product", reference="reference").should_be_called()
subject.assign_brick_reference(ifc, brick, obj="obj", library="library", brick_uri="brick")
def test_assigning_to_an_existing_reference(self, ifc, brick):
brick.get_library_brick_reference("library", "brick").should_be_called().will_return("reference")
ifc.get_entity("obj").should_be_called().will_return("product")
ifc.run("library.assign_reference", product="product", reference="reference").should_be_called()
subject.assign_brick_reference(ifc, brick, obj="obj", library="library", brick_uri="brick")
+33
View File
@@ -59,12 +59,45 @@ class TestClearProject(NewFile):
assert len(bpy.context.scene.BIMBrickProperties.brick_breadcrumbs) == 0 assert len(bpy.context.scene.BIMBrickProperties.brick_breadcrumbs) == 0
class TestExportBrickAttributes(NewFile):
def test_run(self):
assert subject.export_brick_attributes("ex:#floor") == {"Identification": "ex:#floor", "Name": "floor"}
class TestGetBrickPath(NewFile):
def test_run(self):
TestLoadBrickFile().test_run()
cwd = os.path.dirname(os.path.realpath(__file__))
assert subject.get_brick_path() == os.path.join(cwd, "..", "files", "spaces.ttl")
class TestGetBrickPathName(NewFile):
def test_run(self):
TestLoadBrickFile().test_run()
assert subject.get_brick_path_name() == "spaces.ttl"
class TestGetItemClass(NewFile): class TestGetItemClass(NewFile):
def test_run(self): def test_run(self):
TestLoadBrickFile().test_run() TestLoadBrickFile().test_run()
assert subject.get_item_class("ex:#floor") == "Floor" assert subject.get_item_class("ex:#floor") == "Floor"
class TestGetLibraryBrickReference(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
library = ifc.createIfcLibraryInformation()
reference = ifc.createIfcLibraryReference(Identification="ex:#floor", ReferencedLibrary=library)
assert subject.get_library_brick_reference(library, "ex:#floor") == reference
def test_run_ifc2x3(self):
ifc = ifcopenshell.file(schema="IFC2X3")
tool.Ifc.set(ifc)
reference = ifc.createIfcLibraryReference(ItemReference="ex:#floor")
library = ifc.createIfcLibraryInformation(LibraryReference=[reference])
assert subject.get_library_brick_reference(library, "ex:#floor") == reference
class TestImportBrickClasses(NewFile): class TestImportBrickClasses(NewFile):
def test_run(self): def test_run(self):
TestLoadBrickFile().test_run() TestLoadBrickFile().test_run()
@@ -11,4 +11,10 @@ class Usecase:
self.settings[key] = value self.settings[key] = value
def execute(self): def execute(self):
if self.file.schema == "IFC2X3":
reference = self.file.createIfcLibraryReference()
references = list(self.settings["library"].LibraryReference or [])
references.append(reference)
self.settings["library"].LibraryReference = references
return reference
return self.file.createIfcLibraryReference(ReferencedLibrary=self.settings["library"]) return self.file.createIfcLibraryReference(ReferencedLibrary=self.settings["library"])
@@ -8,3 +8,11 @@ class TestAddReference(test.bootstrap.IFC4):
reference = ifcopenshell.api.run("library.add_reference", self.file, library=library) reference = ifcopenshell.api.run("library.add_reference", self.file, library=library)
assert reference.is_a("IfcLibraryReference") assert reference.is_a("IfcLibraryReference")
assert reference.ReferencedLibrary == library assert reference.ReferencedLibrary == library
class TestAddReferenceIFC2X3(test.bootstrap.IFC2X3):
def test_adding_a_reference(self):
library = ifcopenshell.api.run("library.add_library", self.file, name="Name")
reference = ifcopenshell.api.run("library.add_reference", self.file, library=library)
assert reference.is_a("IfcLibraryReference")
assert library.LibraryReference == (reference,)