diff --git a/src/blenderbim/blenderbim/core/brick.py b/src/blenderbim/blenderbim/core/brick.py index 6e86fed613..167b8ae4d8 100644 --- a/src/blenderbim/blenderbim/core/brick.py +++ b/src/blenderbim/blenderbim/core/brick.py @@ -91,11 +91,46 @@ def add_brick_relation(brick, brick_uri=None, predicate=None, object=None): def convert_ifc_to_brick(brick, namespace=None, library=None): + spaces = brick.get_convertable_brick_spaces() + space_uris = {} + for space in spaces: + brick_uri = brick.add_brick_from_element(space, namespace, brick.get_brick_class(space)) + space_uris[space] = brick_uri + if library: + brick.run_assign_brick_reference(element=space, library=library, brick_uri=brick_uri) + + for space in spaces: + parent = brick.get_parent_space(space) + if parent: + brick.add_relation(space_uris[parent], "hasPart", space_uris[space]) + + systems = brick.get_convertable_brick_systems() + system_uris = {} + for system in systems: + brick_uri = brick.add_brick_from_element(system, namespace, brick.get_brick_class(system)) + space_uris[space] = brick_uri + if library: + brick.run_assign_brick_reference(element=system, library=library, brick_uri=brick_uri) + distribution_elements = brick.get_convertable_brick_elements() + equipment_uris = {} for element in distribution_elements: brick_uri = brick.add_brick_from_element(element, namespace, brick.get_brick_class(element)) + equipment_uris[element] = brick_uri + space = brick.get_element_container(element) + brick.add_relation(brick_uri, "hasLocation", space_uris[space]) + systems = brick.get_element_systems(element) + for system in systems: + brick.add_relation(system_uris[system], "hasPart", brick_uri) if library: brick.run_assign_brick_reference(element=element, library=library, brick_uri=brick_uri) + + distribution_elements = brick.get_convertable_brick_elements() + for element in distribution_elements: + feeds = brick.get_element_feeds(element) + for downstream_equipment in feeds: + brick.add_relation(equipment_uris[element], "feeds", equipment_uris[downstream_equipment]) + brick.run_refresh_brick_viewer() brick.run_refresh_brick_viewer(split_screen=True) @@ -141,4 +176,4 @@ def set_brick_list_root(brick, brick_root=None, split_screen=False): def remove_brick_relation(brick, brick_uri=None, predicate=None, object=None): brick.remove_relation(brick_uri, predicate, object) - brick.run_refresh_brick_viewer() \ No newline at end of file + brick.run_refresh_brick_viewer() diff --git a/src/blenderbim/blenderbim/tool/brick.py b/src/blenderbim/blenderbim/tool/brick.py index 91f732dafc..45b79b04e6 100644 --- a/src/blenderbim/blenderbim/tool/brick.py +++ b/src/blenderbim/blenderbim/tool/brick.py @@ -96,7 +96,7 @@ class Brick(blenderbim.core.tool.Brick): cs.add((URIRef(brick), REF.hasExternalReference, bnode)) cs.add((bnode, A, REF.IFCReference)) cs.add((bnode, REF.hasIfcProjectReference, URIRef(project))) - cs.add((bnode, REF.ifcGlobalID , Literal(element.GlobalId))) + cs.add((bnode, REF.ifcGlobalID, Literal(element.GlobalId))) if element.Name: cs.add((bnode, REF.ifcName, Literal(element.Name))) @@ -108,11 +108,7 @@ class Brick(blenderbim.core.tool.Brick): bpy.context.scene.BIMBrickProperties.new_brick_relation_type = BrickStore.relationships[0][0] bpy.context.scene.BIMBrickProperties.add_relation_failed = False return - query = BrickStore.graph.query( - "ASK { <{object_uri}> a ?o . }".replace( - "{object_uri}", object - ) - ) + query = BrickStore.graph.query("ASK { <{object_uri}> a ?o . }".replace("{object_uri}", object)) if query: with BrickStore.new_changeset() as cs: cs.add((URIRef(brick_uri), URIRef(predicate), URIRef(object))) @@ -200,7 +196,41 @@ class Brick(blenderbim.core.tool.Brick): @classmethod def get_convertable_brick_elements(cls): - return ifcopenshell.util.brick.get_brick_elements(tool.Ifc.get()) + equipment = set(tool.Ifc.get().by_type("IfcDistributionElement")) + equipment -= set(tool.Ifc.get().by_type("IfcFlowSegment")) + equipment -= set(tool.Ifc.get().by_type("IfcFlowFitting")) + return equipment + + @classmethod + def get_convertable_brick_spaces(cls): + if tool.Ifc.get_schema() == "IFC2X3": + return tool.Ifc.get().by_type("IfcSpatialStructureElement") + return tool.Ifc.get().by_type("IfcSpatialElement") + + @classmethod + def get_convertable_brick_systems(cls): + systems = set(tool.Ifc.get().by_type("IfcSystem")) + systems -= set(tool.Ifc.get().by_type("IfcStructuralAnalysisModel")) + systems -= set(tool.Ifc.get().by_type("IfcZone")) + return systems + + @classmethod + def get_parent_space(cls, space): + element = ifcopenshell.util.element.get_aggregate(space) + if not element.is_a("IfcProject"): + return element + + @classmethod + def get_element_container(cls, element): + return ifcopenshell.util.element.get_container(element) + + @classmethod + def get_element_systems(cls, element): + return ifcopenshell.util.system.get_element_systems(element) + + @classmethod + def get_element_feeds(cls, element): + return ifcopenshell.util.brick.get_element_feeds(element) @classmethod def get_item_class(cls, item): @@ -389,7 +419,7 @@ class Brick(blenderbim.core.tool.Brick): def add_namespace(cls, alias, uri): BrickStore.graph.bind(alias, Namespace(uri)) BrickStore.load_namespaces() - + @classmethod def clear_breadcrumbs(cls, split_screen=False): if split_screen: @@ -403,6 +433,7 @@ class Brick(blenderbim.core.tool.Brick): save = datetime.datetime.fromtimestamp(save) BrickStore.last_saved = f"{save.year}-{save.month}-{save.day} {save.hour}:{save.minute}" + class BrickStore: schema = None # this is now a os path path = None # file path if the project was loaded in @@ -432,7 +463,7 @@ class BrickStore: @classmethod def get_project(cls): return BrickStore.graph.graph_at(graph="PROJECT") - + @classmethod def load_sub_roots(cls): query = BrickStore.graph.query( @@ -462,7 +493,18 @@ class BrickStore: @classmethod def load_namespaces(cls): BrickStore.namespaces = [] - keyword_filter = ["brickschema.org", "schema.org", "w3.org", "purl.org", "rdfs.org", "qudt.org", "ashrae.org", "usefulinc.com", "xmlns.com", "opengis.net"] + keyword_filter = [ + "brickschema.org", + "schema.org", + "w3.org", + "purl.org", + "rdfs.org", + "qudt.org", + "ashrae.org", + "usefulinc.com", + "xmlns.com", + "opengis.net", + ] for alias, uri in BrickStore.graph.namespaces(): ignore_namespace = False for keyword in keyword_filter: @@ -484,7 +526,7 @@ class BrickStore: ?class rdfs:subClassOf* brick:{root_class} . FILTER NOT EXISTS { ?class owl:deprecated true . - } + } } """.replace( "{root_class}", root_class diff --git a/src/ifcopenshell-python/ifcopenshell/util/brick.py b/src/ifcopenshell-python/ifcopenshell/util/brick.py index 80ff1a8215..9070a0054b 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/brick.py +++ b/src/ifcopenshell-python/ifcopenshell/util/brick.py @@ -20,6 +20,7 @@ import os import json import ifcopenshell import ifcopenshell.util.element +import ifcopenshell.util.classification cwd = os.path.dirname(os.path.realpath(__file__)) @@ -31,6 +32,11 @@ with open(os.path.join(cwd, "ifc4_to_brick.json")) as f: def get_brick_type(element): + references = ifcopenshell.util.classification.get_references(element) + for reference in references: + system = ifcopenshell.util.classification.get_classification(reference) + if system.Name == "Brick": + return reference.Location result = None predefined_type = ifcopenshell.util.element.get_predefined_type(element) if predefined_type: @@ -46,12 +52,50 @@ def get_brick_type(element): result = ifc4_to_brick_map.get(ifc_type_class, None) if result: return f"https://brickschema.org/schema/Brick#{result}" - # We choose equipment as a generic fallback - return f"https://brickschema.org/schema/Brick#Equipment" + # Generic fallback + if element.is_a("IfcDistributionElement"): + return f"https://brickschema.org/schema/Brick#Equipment" + elif element.is_a("IfcSpatialElement") or element.is_a("IfcSpatialStructureElement"): + return f"https://brickschema.org/schema/Brick#Location" + elif element.is_a("IfcSystem"): + return f"https://brickschema.org/schema/Brick#System" -def get_brick_elements(ifc_file): - elements = set(ifc_file.by_type("IfcDistributionElement")) - elements = elements.difference(ifc_file.by_type("IfcFlowSegment")) - elements = elements.difference(ifc_file.by_type("IfcFlowFitting")) - return elements +def get_element_feeds(element): + current_element = element + processed_elements = set() + downstream_equipment = set() + + # A queue is a list of branches. A branch is a list of elements in + # sequence, each one connecting to another element. An element in a + # branch may have a child queue. The queue and child queues are + # acyclic. + + def extend_branch(element, branch, predecessor=None): + processed_elements.add(element) + branch_element = {"element": element, "children": [], "predecessor": predecessor} + branch.append(branch_element) + + connected = { + e + for e in ifcopenshell.util.system.get_connected_to(element, flow_direction="SOURCE") + if e not in processed_elements + } + connected.update( + [ + e + for e in ifcopenshell.util.system.get_connected_from(element, flow_direction="SOURCE") + if e not in processed_elements + ] + ) + + for connected_element in connected: + if connected_element.is_a("IfcFlowFitting") or connected_element.is_a("IfcFlowSegment"): + branch_element["children"].append(extend_branch(connected_element, [], element)) + else: + downstream_equipment.add(connected_element) + + return branch + + extended_branch = extend_branch(current_element, []) + return downstream_equipment diff --git a/src/ifcopenshell-python/ifcopenshell/util/system.py b/src/ifcopenshell-python/ifcopenshell/util/system.py index 5f75b342db..51be9062f5 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/system.py +++ b/src/ifcopenshell-python/ifcopenshell/util/system.py @@ -67,13 +67,21 @@ def get_element_systems(element): return results -def get_ports(element): +def get_ports(element, flow_direction=None): results = [] for rel in getattr(element, "IsNestedBy", []) or []: - results.extend([o for o in rel.RelatedObjects if o.is_a("IfcDistributionPort")]) + for port in rel.RelatedObjects: + if not port.is_a("IfcDistributionPort"): + continue + if flow_direction and port.FlowDirection != flow_direction: + continue + results.append(port) # IFC2X3 only, deprecated in IFC4 for rel in getattr(element, "HasPorts", []) or []: - results.append(rel.RelatingPort) + port = rel.RelatingPort + if flow_direction and port.FlowDirection != flow_direction: + continue + results.append(port) return results @@ -84,9 +92,9 @@ def get_connected_port(port): return rel.RelatingPort -def get_connected_to(element): +def get_connected_to(element, flow_direction=None): results = [] - for port in ifcopenshell.util.system.get_ports(element): + for port in ifcopenshell.util.system.get_ports(element, flow_direction=flow_direction): for relConnectsPort in port.ConnectedTo: for disPort in [relConnectsPort.RelatedPort, relConnectsPort.RelatingPort]: if hasattr(disPort, "Nests"): @@ -101,9 +109,9 @@ def get_connected_to(element): return results -def get_connected_from(element): +def get_connected_from(element, flow_direction=None): results = [] - for port in ifcopenshell.util.system.get_ports(element): + for port in ifcopenshell.util.system.get_ports(element, flow_direction=flow_direction): for relConnectsPort in port.ConnectedFrom: for disPort in [relConnectsPort.RelatedPort, relConnectsPort.RelatingPort]: if hasattr(disPort, "Nests"):