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
from blenderbim.tool.brick import BrickStore
try:
from rdflib import URIRef, BNode
except:
# See #1860
print("Warning: brickschema not available.")
def refresh():
BrickschemaData.is_loaded = False
@@ -67,15 +73,30 @@ class BrickschemaData:
"{uri}", uri
)
)
for row in query:
name = row.get("name").toPython().split("#")[-1]
value = row.get("value")
results.append(
{
"name": row.get("name").toPython().split("#")[-1],
"value": row.get("value").toPython().split("#")[-1],
"is_uri": "#" in row.get("value").toPython(),
"value_uri": row.get("value").toPython(),
"name": name,
"value": value.toPython().split("#")[-1],
"is_uri": isinstance(value, URIRef),
"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
@@ -96,7 +117,10 @@ class BrickschemaReferencesData:
def libraries(cls):
results = []
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
@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:
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)
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
class Brick:
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_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_brickifc_project(cls): pass
def get_item_class(cls, item): 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_items(cls, brick_class): pass
def load_brick_file(cls, filepath): pass
+59
View File
@@ -23,6 +23,9 @@ import blenderbim.tool as tool
try:
import brickschema
import urllib.parse
from rdflib import Literal, URIRef, Namespace
from rdflib.namespace import RDF
except:
# See #1860
print("Warning: brickschema not available.")
@@ -38,6 +41,39 @@ class Brick(blenderbim.core.tool.Brick):
new = bpy.context.scene.BIMBrickProperties.brick_breadcrumbs.add()
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
def clear_brick_browser(cls):
bpy.context.scene.BIMBrickProperties.bricks.clear()
@@ -63,6 +99,25 @@ class Brick(blenderbim.core.tool.Brick):
def get_brick_path_name(cls):
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
def get_item_class(cls, item):
query = BrickStore.graph.query(
@@ -90,6 +145,10 @@ class Brick(blenderbim.core.tool.Brick):
if reference.Identification == brick_uri:
return reference
@classmethod
def get_namespace(cls, uri):
return uri.split("#")[0] + "#"
@classmethod
def import_brick_classes(cls, brick_class):
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')"
When I press "bim.close_brick_project"
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.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("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_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()
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")
+72
View File
@@ -22,6 +22,8 @@ import brickschema
import ifcopenshell
import blenderbim.core.tool
import blenderbim.tool as tool
from rdflib.namespace import RDF
from rdflib import Literal, URIRef, Namespace
from test.bim.bootstrap import NewFile
from blenderbim.tool.brick import Brick as subject
from blenderbim.tool.brick import BrickStore
@@ -41,6 +43,62 @@ class TestAddBrickBreadcrumb(NewFile):
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):
def test_run(self):
bpy.context.scene.BIMBrickProperties.bricks.add()
@@ -81,6 +139,15 @@ class TestGetBrickPathName(NewFile):
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):
def test_run(self):
TestLoadBrickFile().test_run()
@@ -102,6 +169,11 @@ class TestGetLibraryBrickReference(NewFile):
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):
def test_run(self):
TestLoadBrickFile().test_run()