Assigning a brick reference now adds a 2-way IFC reference in the Brick model

This commit is contained in:
Dion Moult
2021-12-09 17:45:08 +11:00
parent 78a36ab429
commit 2ef1f4d812
7 changed files with 203 additions and 6 deletions
@@ -20,6 +20,12 @@ import bpy
import blenderbim.tool as tool import blenderbim.tool as tool
from blenderbim.tool.brick import BrickStore from blenderbim.tool.brick import BrickStore
try:
from rdflib import URIRef, BNode
except:
# See #1860
print("Warning: brickschema not available.")
def refresh(): def refresh():
BrickschemaData.is_loaded = False BrickschemaData.is_loaded = False
@@ -67,15 +73,30 @@ class BrickschemaData:
"{uri}", uri "{uri}", uri
) )
) )
for row in query: for row in query:
name = row.get("name").toPython().split("#")[-1]
value = row.get("value")
results.append( results.append(
{ {
"name": row.get("name").toPython().split("#")[-1], "name": name,
"value": row.get("value").toPython().split("#")[-1], "value": value.toPython().split("#")[-1],
"is_uri": "#" in row.get("value").toPython(), "is_uri": isinstance(value, URIRef),
"value_uri": row.get("value").toPython(), "value_uri": value.toPython(),
"is_globalid": name == "globalID",
} }
) )
if isinstance(row.get("value"), BNode):
for s, p, o in BrickStore.graph.triples((value, None, None)):
results.append(
{
"name": name + ":" + p.toPython().split("#")[-1],
"value": o.toPython().split("#")[-1],
"is_uri": isinstance(o, URIRef),
"value_uri": o.toPython(),
"is_globalid": p.toPython().split("#")[-1] == "globalID",
}
)
return results return results
@@ -96,7 +117,10 @@ class BrickschemaReferencesData:
def libraries(cls): def libraries(cls):
results = [] results = []
for library in tool.Ifc.get().by_type("IfcLibraryInformation"): for library in tool.Ifc.get().by_type("IfcLibraryInformation"):
results.append((str(library.id()), library.Name or "Unnamed", "")) if tool.Ifc.get_schema() == "IFC2X3":
results.append((str(library.id()), library.Name or "Unnamed", ""))
elif ".ttl" in library.Location:
results.append((str(library.id()), library.Name or "Unnamed", ""))
return results return results
@classmethod @classmethod
+6 -1
View File
@@ -60,4 +60,9 @@ def assign_brick_reference(ifc, brick, obj=None, library=None, brick_uri=None):
if not reference: if not reference:
reference = ifc.run("library.add_reference", library=library) 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.edit_reference", reference=reference, attributes=brick.export_brick_attributes(brick_uri))
ifc.run("library.assign_reference", product=ifc.get_entity(obj), reference=reference) product = ifc.get_entity(obj)
ifc.run("library.assign_reference", product=product, reference=reference)
project = brick.get_brickifc_project()
if not project:
project = brick.add_brickifc_project(brick.get_namespace(brick_uri))
brick.add_brickifc_reference(brick_uri, product, project)
+4
View File
@@ -44,13 +44,17 @@ class Blender: pass
@interface @interface
class Brick: class Brick:
def add_brick_breadcrumb(cls): pass def add_brick_breadcrumb(cls): pass
def add_brickifc_project(cls, namespace): pass
def add_brickifc_reference(cls, brick, element, project): 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 export_brick_attributes(cls, brick_uri): pass
def get_brick_path(cls): pass def get_brick_path(cls): pass
def get_brick_path_name(cls): pass def get_brick_path_name(cls): pass
def get_brickifc_project(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 get_library_brick_reference(cls, library, brick_uri): pass
def get_namespace(cls, 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
+59
View File
@@ -23,6 +23,9 @@ import blenderbim.tool as tool
try: try:
import brickschema import brickschema
import urllib.parse
from rdflib import Literal, URIRef, Namespace
from rdflib.namespace import RDF
except: except:
# See #1860 # See #1860
print("Warning: brickschema not available.") print("Warning: brickschema not available.")
@@ -38,6 +41,39 @@ class Brick(blenderbim.core.tool.Brick):
new = bpy.context.scene.BIMBrickProperties.brick_breadcrumbs.add() new = bpy.context.scene.BIMBrickProperties.brick_breadcrumbs.add()
new.name = bpy.context.scene.BIMBrickProperties.active_brick_class new.name = bpy.context.scene.BIMBrickProperties.active_brick_class
@classmethod
def add_brickifc_project(cls, namespace):
project = tool.Ifc.get().by_type("IfcProject")[0]
ns = Namespace(namespace)
ns_brickifc = Namespace("https://brickschema.org/extension/ifc#")
BrickStore.graph.bind("brickifc", ns_brickifc)
brick_project = ns[project.GlobalId]
BrickStore.graph.add((brick_project, RDF.type, ns_brickifc["Project"]))
if project.Name:
BrickStore.graph.add(
(brick_project, URIRef("http://www.w3.org/2000/01/rdf-schema#label"), Literal(project.Name))
)
BrickStore.graph.add((brick_project, ns_brickifc["projectID"], Literal(project.GlobalId)))
BrickStore.graph.add(
(brick_project, ns_brickifc["fileLocation"], Literal(bpy.context.scene.BIMProperties.ifc_file))
)
return str(brick_project)
@classmethod
def add_brickifc_reference(cls, brick, element, project):
ns_brickifc = Namespace("https://brickschema.org/extension/ifc#")
BrickStore.graph.bind("brickifc", ns_brickifc)
BrickStore.graph.add(
(
URIRef(brick),
ns_brickifc["hasIFCReference"],
[
(ns_brickifc["hasProjectReference"], URIRef(project)),
(ns_brickifc["globalID"], Literal(element.GlobalId)),
],
)
)
@classmethod @classmethod
def clear_brick_browser(cls): def clear_brick_browser(cls):
bpy.context.scene.BIMBrickProperties.bricks.clear() bpy.context.scene.BIMBrickProperties.bricks.clear()
@@ -63,6 +99,25 @@ class Brick(blenderbim.core.tool.Brick):
def get_brick_path_name(cls): def get_brick_path_name(cls):
return os.path.basename(BrickStore.path) return os.path.basename(BrickStore.path)
@classmethod
def get_brickifc_project(cls):
project = tool.Ifc.get().by_type("IfcProject")[0]
query = BrickStore.graph.query(
"""
PREFIX brickifc: <https://brickschema.org/extension/ifc#>
SELECT ?proj WHERE {
?proj a brickifc:Project .
?proj brickifc:projectID "{project_globalid}"
}
LIMIT 1
""".replace(
"{project_globalid}", project.GlobalId
)
)
results = list(query)
if results:
return results[0][0].toPython()
@classmethod @classmethod
def get_item_class(cls, item): def get_item_class(cls, item):
query = BrickStore.graph.query( query = BrickStore.graph.query(
@@ -90,6 +145,10 @@ class Brick(blenderbim.core.tool.Brick):
if reference.Identification == brick_uri: if reference.Identification == brick_uri:
return reference return reference
@classmethod
def get_namespace(cls, uri):
return uri.split("#")[0] + "#"
@classmethod @classmethod
def import_brick_classes(cls, brick_class): def import_brick_classes(cls, brick_class):
query = BrickStore.graph.query( query = BrickStore.graph.query(
@@ -30,3 +30,22 @@ Scenario: Close Brick project
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')" And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
When I press "bim.close_brick_project" When I press "bim.close_brick_project"
Then nothing happens Then nothing happens
Scenario: Convert brick project
Given an empty IFC project
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
When I press "bim.convert_brick_project"
Then nothing happens
Scenario: Assign brick reference
Given an empty IFC project
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
And I press "bim.view_brick_item(item='https://brickschema.org/schema/Brick#Chiller')"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcChiller"
And I press "bim.assign_class"
And the object "IfcChiller/Cube" is selected
And I press "bim.convert_brick_project"
When I press "bim.assign_brick_reference"
Then nothing happens
+14
View File
@@ -92,10 +92,24 @@ class TestAssignBrickReference:
ifc.run("library.edit_reference", reference="reference", attributes="attributes").should_be_called() ifc.run("library.edit_reference", reference="reference", attributes="attributes").should_be_called()
ifc.get_entity("obj").should_be_called().will_return("product") ifc.get_entity("obj").should_be_called().will_return("product")
ifc.run("library.assign_reference", product="product", reference="reference").should_be_called() ifc.run("library.assign_reference", product="product", reference="reference").should_be_called()
brick.get_brickifc_project().should_be_called().will_return("project")
brick.add_brickifc_reference("brick", "product", "project").should_be_called()
subject.assign_brick_reference(ifc, brick, obj="obj", library="library", brick_uri="brick") subject.assign_brick_reference(ifc, brick, obj="obj", library="library", brick_uri="brick")
def test_assigning_to_an_existing_reference(self, ifc, brick): def test_assigning_to_an_existing_reference(self, ifc, brick):
brick.get_library_brick_reference("library", "brick").should_be_called().will_return("reference") brick.get_library_brick_reference("library", "brick").should_be_called().will_return("reference")
ifc.get_entity("obj").should_be_called().will_return("product") ifc.get_entity("obj").should_be_called().will_return("product")
ifc.run("library.assign_reference", product="product", reference="reference").should_be_called() ifc.run("library.assign_reference", product="product", reference="reference").should_be_called()
brick.get_brickifc_project().should_be_called().will_return("project")
brick.add_brickifc_reference("brick", "product", "project").should_be_called()
subject.assign_brick_reference(ifc, brick, obj="obj", library="library", brick_uri="brick")
def test_adding_a_brickifc_project_if_it_doesnt_exist(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()
brick.get_brickifc_project().should_be_called().will_return(None)
brick.get_namespace("brick").should_be_called().will_return("namespace")
brick.add_brickifc_project("namespace").should_be_called().will_return("project")
brick.add_brickifc_reference("brick", "product", "project").should_be_called()
subject.assign_brick_reference(ifc, brick, obj="obj", library="library", brick_uri="brick") subject.assign_brick_reference(ifc, brick, obj="obj", library="library", brick_uri="brick")
+72
View File
@@ -22,6 +22,8 @@ import brickschema
import ifcopenshell import ifcopenshell
import blenderbim.core.tool import blenderbim.core.tool
import blenderbim.tool as tool import blenderbim.tool as tool
from rdflib.namespace import RDF
from rdflib import Literal, URIRef, Namespace
from test.bim.bootstrap import NewFile from test.bim.bootstrap import NewFile
from blenderbim.tool.brick import Brick as subject from blenderbim.tool.brick import Brick as subject
from blenderbim.tool.brick import BrickStore from blenderbim.tool.brick import BrickStore
@@ -41,6 +43,62 @@ class TestAddBrickBreadcrumb(NewFile):
assert bpy.context.scene.BIMBrickProperties.brick_breadcrumbs[1].name == "brick_class" assert bpy.context.scene.BIMBrickProperties.brick_breadcrumbs[1].name == "brick_class"
class TestAddBrickifcProject(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
project = ifc.createIfcProject(ifcopenshell.guid.new())
project.Name = "My Project"
BrickStore.graph = brickschema.Graph()
result = subject.add_brickifc_project("http://example.org/digitaltwin#")
assert result == f"http://example.org/digitaltwin#{project.GlobalId}"
brick = URIRef(result)
assert list(
BrickStore.graph.triples((brick, RDF.type, URIRef("https://brickschema.org/extension/ifc#Project")))
)
assert list(
BrickStore.graph.triples(
(brick, URIRef("http://www.w3.org/2000/01/rdf-schema#label"), Literal("My Project"))
)
)
assert list(
BrickStore.graph.triples(
(brick, URIRef("https://brickschema.org/extension/ifc#projectID"), Literal(project.GlobalId))
)
)
assert list(
BrickStore.graph.triples(
(
brick,
URIRef("https://brickschema.org/extension/ifc#fileLocation"),
Literal(bpy.context.scene.BIMProperties.ifc_file),
)
)
)
class TestAddBrickifcReference(NewFile):
def test_run(self):
TestAddBrickifcProject().test_run()
element = tool.Ifc.get().createIfcChiller(ifcopenshell.guid.new())
project = URIRef(f"http://example.org/digitaltwin#{tool.Ifc.get().by_type('IfcProject')[0].GlobalId}")
subject.add_brickifc_reference("http://example.org/digitaltwin#foo", element, project)
brick = URIRef("http://example.org/digitaltwin#foo")
bnode = list(
BrickStore.graph.triples((brick, URIRef("https://brickschema.org/extension/ifc#hasIFCReference"), None))
)[0][2]
assert list(
BrickStore.graph.triples(
(bnode, URIRef("https://brickschema.org/extension/ifc#hasProjectReference"), project)
)
)
assert list(
BrickStore.graph.triples(
(bnode, URIRef("https://brickschema.org/extension/ifc#globalID"), Literal(element.GlobalId))
)
)
class TestClearBrickBrowser(NewFile): class TestClearBrickBrowser(NewFile):
def test_run(self): def test_run(self):
bpy.context.scene.BIMBrickProperties.bricks.add() bpy.context.scene.BIMBrickProperties.bricks.add()
@@ -81,6 +139,15 @@ class TestGetBrickPathName(NewFile):
assert subject.get_brick_path_name() == "spaces.ttl" assert subject.get_brick_path_name() == "spaces.ttl"
class TestGetBrickifcProject(NewFile):
def test_run(self):
TestAddBrickifcProject().test_run()
assert (
subject.get_brickifc_project()
== f"http://example.org/digitaltwin#{tool.Ifc.get().by_type('IfcProject')[0].GlobalId}"
)
class TestGetItemClass(NewFile): class TestGetItemClass(NewFile):
def test_run(self): def test_run(self):
TestLoadBrickFile().test_run() TestLoadBrickFile().test_run()
@@ -102,6 +169,11 @@ class TestGetLibraryBrickReference(NewFile):
assert subject.get_library_brick_reference(library, "ex:#floor") == reference assert subject.get_library_brick_reference(library, "ex:#floor") == reference
class TestGetNamespace(NewFile):
def test_run(self):
assert subject.get_namespace("http://example.org/digitaltwin#globalid") == "http://example.org/digitaltwin#"
class TestImportBrickClasses(NewFile): class TestImportBrickClasses(NewFile):
def test_run(self): def test_run(self):
TestLoadBrickFile().test_run() TestLoadBrickFile().test_run()