diff --git a/src/bonsai/bonsai/bim/module/brick/data.py b/src/bonsai/bonsai/bim/module/brick/data.py index 737896ad3c..b7ac6f61e1 100644 --- a/src/bonsai/bonsai/bim/module/brick/data.py +++ b/src/bonsai/bonsai/bim/module/brick/data.py @@ -52,7 +52,7 @@ class BrickschemaData: def active_relations(cls): if BrickStore.graph is None: return [] - props = bpy.context.scene.BIMBrickProperties + props = tool.Brick.get_brick_props() try: brick = props.bricks[props.active_brick_index] except: diff --git a/src/bonsai/bonsai/bim/module/brick/operator.py b/src/bonsai/bonsai/bim/module/brick/operator.py index e76da467fa..522241a88e 100644 --- a/src/bonsai/bonsai/bim/module/brick/operator.py +++ b/src/bonsai/bonsai/bim/module/brick/operator.py @@ -36,7 +36,7 @@ class LoadBrickProject(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): if os.path.exists(self.filepath) and "ttl" in os.path.splitext(self.filepath)[1].lower(): - root = context.scene.BIMBrickProperties.brick_list_root + root = tool.Brick.get_brick_props().brick_list_root core.load_brick_project(tool.Brick, filepath=self.filepath, brick_root=root) else: self.report({"ERROR"}, f"Failed to load {self.filepath}") @@ -111,10 +111,10 @@ class AssignBrickReference(bpy.types.Operator, tool.Ifc.Operator): bl_description = "Assign the selected Ifc entity to the selected Brick entity" def _execute(self, context): - if not context.active_object: + if not (obj := context.active_object) or not (element := tool.Ifc.get_entity(obj)): self.report({"ERROR"}, f"No Ifc selected") return - props = context.scene.BIMBrickProperties + props = tool.Brick.get_brick_props() try: props.bricks[props.active_brick_index] except: @@ -123,7 +123,7 @@ class AssignBrickReference(bpy.types.Operator, tool.Ifc.Operator): core.assign_brick_reference( tool.Ifc, tool.Brick, - element=tool.Ifc.get_entity(context.active_object), + element=element, library=tool.Ifc.get().by_id(int(props.libraries)), brick_uri=props.bricks[props.active_brick_index].uri, ) @@ -136,7 +136,7 @@ class AddBrick(bpy.types.Operator, tool.Ifc.Operator): bl_description = "Create the Brick entity" def _execute(self, context): - props = context.scene.BIMBrickProperties + props = tool.Brick.get_brick_props() core.add_brick( tool.Ifc, tool.Brick, @@ -155,7 +155,7 @@ class AddBrickRelation(bpy.types.Operator, tool.Ifc.Operator): bl_description = "Create the Brick relationship" def _execute(self, context): - props = context.scene.BIMBrickProperties + props = tool.Brick.get_brick_props() brick = props.bricks[props.active_brick_index] if props.new_brick_relation_type == "http://www.w3.org/2000/01/rdf-schema#label": object = props.new_brick_relation_object @@ -173,7 +173,7 @@ class ConvertIfcToBrick(bpy.types.Operator, tool.Ifc.Operator): bl_description = "Convert Ifc entities and relations to Brick entities and relations" def _execute(self, context): - props = context.scene.BIMBrickProperties + props = tool.Brick.get_brick_props() library = None if props.libraries: library = tool.Ifc.get().by_id(int(props.libraries)) @@ -201,7 +201,8 @@ class NewBrickFile(bpy.types.Operator): return {"FINISHED"} def _execute(self, context): - root = context.scene.BIMBrickProperties.brick_list_root + props = tool.Brick.get_brick_props() + root = props.brick_list_root core.new_brick_file(tool.Brick, brick_root=root) def rollback(self, data): @@ -230,7 +231,7 @@ class RemoveBrick(bpy.types.Operator, tool.Ifc.Operator): bl_description = "Delete this entity" def _execute(self, context): - props = context.scene.BIMBrickProperties + props = tool.Brick.get_brick_props() core.remove_brick( tool.Ifc, tool.Brick, @@ -273,7 +274,7 @@ class AddBrickNamespace(bpy.types.Operator, tool.Ifc.Operator): bl_description = "Bind a new namespace to the Brick project" def _execute(self, context): - props = context.scene.BIMBrickProperties + props = tool.Brick.get_brick_props() alias = props.new_brick_namespace_alias uri = props.new_brick_namespace_uri core.add_brick_namespace(tool.Brick, alias=alias, uri=uri) @@ -288,6 +289,6 @@ class RemoveBrickRelation(bpy.types.Operator, tool.Ifc.Operator): object: bpy.props.StringProperty(name="Object") def _execute(self, context): - props = context.scene.BIMBrickProperties + props = tool.Brick.get_brick_props() brick = props.bricks[props.active_brick_index] core.remove_brick_relation(tool.Brick, brick_uri=brick.uri, predicate=self.predicate, object=self.object) diff --git a/src/bonsai/bonsai/bim/module/brick/prop.py b/src/bonsai/bonsai/bim/module/brick/prop.py index 1a226fd1a0..4b738a0c77 100644 --- a/src/bonsai/bonsai/bim/module/brick/prop.py +++ b/src/bonsai/bonsai/bim/module/brick/prop.py @@ -33,6 +33,7 @@ from bpy.props import ( import bonsai.core.brick as core import bonsai.tool.brick as tool from bonsai.tool.brick import BrickStore +from typing import TYPE_CHECKING def update_active_brick_index(self, context): @@ -74,13 +75,13 @@ def get_brick_relations(self, context): return BRICK_RELATIONS_ENUM_ITEMS -def update_view(self, context): - root = context.scene.BIMBrickProperties.brick_list_root +def update_view(self: "BIMBrickProperties", context: bpy.types.Context) -> None: + root = self.brick_list_root core.set_brick_list_root(tool.Brick, brick_root=root, split_screen=False) -def split_screen_update_view(self, context): - root = context.scene.BIMBrickProperties.split_screen_brick_list_root +def split_screen_update_view(self: "BIMBrickProperties", context: bpy.types.Context) -> None: + root = self.split_screen_brick_list_root core.set_brick_list_root(tool.Brick, brick_root=root, split_screen=True) @@ -90,6 +91,11 @@ class Brick(PropertyGroup): uri: StringProperty(name="URI") total_items: IntProperty(name="Total Items") + if TYPE_CHECKING: + label: str + uri: str + total_items: int + class BIMBrickProperties(PropertyGroup): active_brick_class: StringProperty(name="Active Brick Class") @@ -124,3 +130,28 @@ class BIMBrickProperties(PropertyGroup): split_screen_brick_list_root: EnumProperty( name="Split Screen Brick List Root", items=get_brick_roots, update=split_screen_update_view ) + + if TYPE_CHECKING: + active_brick_class: str + brick_breadcrumbs: bpy.types.bpy_prop_collection_idprop[StrProperty] + bricks: bpy.types.bpy_prop_collection_idprop[Brick] + active_brick_index: int + libraries: str + set_list_root_toggled: bool + brick_list_root: str + namespace: str + new_brick_namespace_alias: str + new_brick_namespace_uri: str + new_brick_label: str + brick_entity_create_type: str + brick_entity_class: str + brick_create_relations_toggled: bool + brick_edit_relations_toggled: bool + new_brick_relation_type: str + new_brick_relation_object: str + split_screen_toggled: bool + split_screen_bricks: bpy.types.bpy_prop_collection_idprop[Brick] + split_screen_active_brick_index: int + split_screen_active_brick_class: str + split_screen_brick_breadcrumbs: bpy.types.bpy_prop_collection_idprop[StrProperty] + split_screen_brick_list_root: str diff --git a/src/bonsai/bonsai/bim/module/brick/ui.py b/src/bonsai/bonsai/bim/module/brick/ui.py index 141dba5559..e7d0a1be69 100644 --- a/src/bonsai/bonsai/bim/module/brick/ui.py +++ b/src/bonsai/bonsai/bim/module/brick/ui.py @@ -45,7 +45,7 @@ class BIM_PT_brickschema_project_info(Panel): bl_parent_id = "BIM_PT_brickschema" def draw(self, context): - self.props = context.scene.BIMBrickProperties + self.props = tool.Brick.get_brick_props() if not BrickschemaData.data["is_loaded"]: row = self.layout.row(align=True) @@ -87,7 +87,7 @@ class BIM_PT_brickschema_namespaces(Panel): return BrickStore.graph != None def draw(self, context): - self.props = context.scene.BIMBrickProperties + self.props = tool.Brick.get_brick_props() row = self.layout.row(align=True) row.label(text="Active Namespace:") @@ -122,7 +122,7 @@ class BIM_PT_brickschema_create_entity(Panel): return BrickStore.graph != None def draw(self, context): - self.props = context.scene.BIMBrickProperties + self.props = tool.Brick.get_brick_props() # TO DO: hide this if selected entity already has a reference, or something similar row = self.layout.row(align=True) @@ -156,7 +156,7 @@ class BIM_PT_brickschema_viewport(Panel): return BrickStore.graph != None def draw(self, context): - self.props = context.scene.BIMBrickProperties + self.props = tool.Brick.get_brick_props() row = self.layout.row(align=True) row.column().alignment = "RIGHT" @@ -292,7 +292,7 @@ class BIM_PT_ifc_brickschema_references(Panel): def draw(self, context): if not BrickschemaReferencesData.is_loaded: BrickschemaReferencesData.load() - self.props = context.scene.BIMBrickProperties + self.props = tool.Brick.get_brick_props() if not BrickschemaReferencesData.data["is_loaded"]: row = self.layout.row() diff --git a/src/bonsai/bonsai/bim/module/root/operator.py b/src/bonsai/bonsai/bim/module/root/operator.py index d141e7075e..4accda58d9 100644 --- a/src/bonsai/bonsai/bim/module/root/operator.py +++ b/src/bonsai/bonsai/bim/module/root/operator.py @@ -21,6 +21,7 @@ import bmesh import ifcopenshell import ifcopenshell.api import ifcopenshell.api.geometry +import ifcopenshell.api.root import ifcopenshell.util.schema import ifcopenshell.util.element import ifcopenshell.util.shape_builder @@ -149,10 +150,9 @@ class ReassignClass(bpy.types.Operator, tool.Ifc.Operator): elements_to_update = elements_to_update | set(elements_to_reassign) objects_to_update = set(o for e in elements_to_update if (o := tool.Ifc.get_object(e))) - reassigned_elements = set() + reassigned_elements: set[ifcopenshell.entity_instance] = set() for element, ifc_class_ in elements_to_reassign.items(): - element = ifcopenshell.api.run( - "root.reassign_class", + element = ifcopenshell.api.root.reassign_class( self.file, product=element, ifc_class=ifc_class_, diff --git a/src/bonsai/bonsai/core/brick.py b/src/bonsai/bonsai/core/brick.py index 4d537a6751..27b82d43f8 100644 --- a/src/bonsai/bonsai/core/brick.py +++ b/src/bonsai/bonsai/core/brick.py @@ -16,8 +16,16 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . +from __future__ import annotations +from typing import TYPE_CHECKING, Optional, Union -def load_brick_project(brick, filepath=None, brick_root=None): +if TYPE_CHECKING: + import bpy + import ifcopenshell + import bonsai.tool as tool + + +def load_brick_project(brick: tool.Brick, filepath: str, brick_root: str) -> None: brick.load_brick_file(filepath) brick.import_brick_classes(brick_root) brick.import_brick_classes(brick_root, split_screen=True) @@ -25,7 +33,7 @@ def load_brick_project(brick, filepath=None, brick_root=None): brick.set_active_brick_class(brick_root, split_screen=True) -def new_brick_file(brick, brick_root=None): +def new_brick_file(brick: tool.Brick, brick_root: str) -> None: brick.new_brick_file() brick.import_brick_classes(brick_root) brick.import_brick_classes(brick_root, split_screen=True) @@ -33,7 +41,7 @@ def new_brick_file(brick, brick_root=None): brick.set_active_brick_class(brick_root, split_screen=True) -def view_brick_class(brick, brick_class=None, split_screen=False): +def view_brick_class(brick: tool.Brick, brick_class: str, split_screen: bool = False) -> None: brick.add_brick_breadcrumb(split_screen=split_screen) brick.clear_brick_browser(split_screen=split_screen) brick.import_brick_classes(brick_class, split_screen=split_screen) @@ -41,13 +49,13 @@ def view_brick_class(brick, brick_class=None, split_screen=False): brick.set_active_brick_class(brick_class, split_screen=split_screen) -def view_brick_item(brick, item=None, split_screen=False): +def view_brick_item(brick: tool.Brick, item: str, split_screen: bool = False) -> None: brick_class = brick.get_item_class(item) brick.run_view_brick_class(brick_class=brick_class, split_screen=split_screen) brick.select_browser_item(item, split_screen=split_screen) -def rewind_brick_class(brick, split_screen=False): +def rewind_brick_class(brick: tool.Brick, split_screen: bool = False) -> None: previous_class = brick.pop_brick_breadcrumb(split_screen=split_screen) brick.clear_brick_browser(split_screen=split_screen) brick.import_brick_classes(previous_class, split_screen=split_screen) @@ -55,7 +63,7 @@ def rewind_brick_class(brick, split_screen=False): brick.set_active_brick_class(previous_class, split_screen=split_screen) -def close_brick_project(brick): +def close_brick_project(brick: tool.Brick) -> None: brick.clear_project() brick.clear_brick_browser() brick.clear_brick_browser(split_screen=True) @@ -63,13 +71,19 @@ def close_brick_project(brick): brick.clear_breadcrumbs(split_screen=True) -def convert_brick_project(ifc, brick): +def convert_brick_project(ifc: tool.Ifc, brick: tool.Brick) -> None: library = ifc.run("library.add_library", name=brick.get_brick_path_name()) if ifc.get_schema() != "IFC2X3": ifc.run("library.edit_library", library=library, attributes={"Location": brick.get_brick_path()}) -def assign_brick_reference(ifc, brick, element=None, library=None, brick_uri=None): +def assign_brick_reference( + ifc: tool.Ifc, + brick: tool.Brick, + element: ifcopenshell.entity_instance, + library: ifcopenshell.entity_instance, + brick_uri: str, +) -> None: reference = brick.get_library_brick_reference(library, brick_uri) if not reference: reference = ifc.run("library.add_reference", library=library) @@ -81,7 +95,15 @@ def assign_brick_reference(ifc, brick, element=None, library=None, brick_uri=Non brick.add_brickifc_reference(brick_uri, element, project) -def add_brick(ifc, brick, element=None, namespace=None, brick_class=None, library=None, label="Unnamed"): +def add_brick( + ifc: tool.Ifc, + brick: tool.Brick, + element: Union[ifcopenshell.entity_instance, None], + namespace: str, + brick_class: str, + library: Union[str, None], + label: str = "Unnamed", +) -> None: if element: brick_uri = brick.add_brick_from_element(element, namespace, brick_class) if library: @@ -91,12 +113,12 @@ def add_brick(ifc, brick, element=None, namespace=None, brick_class=None, librar brick.run_refresh_brick_viewer() -def add_brick_relation(brick, brick_uri=None, predicate=None, object=None): +def add_brick_relation(brick: tool.Brick, brick_uri: str, predicate: str, object: str) -> None: brick.add_relation(brick_uri, predicate, object) brick.run_refresh_brick_viewer() -def convert_ifc_to_brick(brick, namespace=None, library=None): +def convert_ifc_to_brick(brick: tool.Brick, namespace: str, library: Union[ifcopenshell.entity_instance, None]) -> None: # convert spaces to brick spaces = brick.get_convertable_brick_spaces() space_uris = {} @@ -141,14 +163,14 @@ def convert_ifc_to_brick(brick, namespace=None, library=None): brick.run_refresh_brick_viewer() -def refresh_brick_viewer(brick): +def refresh_brick_viewer(brick: tool.Brick) -> None: brick.run_view_brick_class(brick_class=brick.get_active_brick_class()) brick.pop_brick_breadcrumb() brick.run_view_brick_class(brick_class=brick.get_active_brick_class(split_screen=True), split_screen=True) brick.pop_brick_breadcrumb(split_screen=True) -def remove_brick(ifc, brick, library=None, brick_uri=None): +def remove_brick(ifc: tool.Ifc, brick: tool.Brick, library: ifcopenshell.entity_instance, brick_uri: str) -> None: if library: reference = brick.get_library_brick_reference(library, brick_uri) if reference: @@ -157,18 +179,18 @@ def remove_brick(ifc, brick, library=None, brick_uri=None): brick.run_refresh_brick_viewer() -def serialize_brick(brick): +def serialize_brick(brick: tool.Brick) -> None: brick.serialize_brick() -def add_brick_namespace(brick, alias=None, uri=None): +def add_brick_namespace(brick: tool.Brick, alias: str, uri: str) -> None: brick.add_namespace(alias, uri) -def set_brick_list_root(brick, brick_root=None, split_screen=False): +def set_brick_list_root(brick: tool.Brick, brick_root: str, split_screen: bool = False) -> None: brick.run_view_brick_class(brick_class=brick_root, split_screen=split_screen) brick.clear_breadcrumbs(split_screen=split_screen) -def remove_brick_relation(brick, brick_uri=None, predicate=None, object=None): +def remove_brick_relation(brick: tool.Brick, brick_uri: str, predicate: str, object: str) -> None: brick.remove_relation(brick_uri, predicate, object) diff --git a/src/bonsai/bonsai/tool/brick.py b/src/bonsai/bonsai/tool/brick.py index acd8d50b01..ce34cb65fb 100644 --- a/src/bonsai/bonsai/tool/brick.py +++ b/src/bonsai/bonsai/tool/brick.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . +from __future__ import annotations import os import bpy import datetime @@ -29,6 +30,7 @@ import bonsai.core.tool import bonsai.tool as tool from pathlib import Path from contextlib import contextmanager +from typing import Generator, Any, Union, TYPE_CHECKING try: import brickschema @@ -41,6 +43,11 @@ except: # See #1860 print("Warning: brickschema not available.") +if TYPE_CHECKING: + import brickschema + from rdflib import Literal, URIRef, Namespace, BNode + from bonsai.bim.module.brick.prop import BIMBrickProperties + # silence known rdflib_sqlalchemy TypeError warning # see https://github.com/BrickSchema/Brick/issues/513#issuecomment-1558493675 import logging @@ -51,7 +58,11 @@ logger.setLevel(logging.ERROR) class Brick(bonsai.core.tool.Brick): @classmethod - def add_brick(cls, namespace, brick_class, label): + def get_brick_props(cls) -> BIMBrickProperties: + return bpy.context.scene.BIMBrickProperties + + @classmethod + def add_brick(cls, namespace: str, brick_class: str, label: str) -> str: ns = Namespace(namespace) brick = ns[ifcopenshell.guid.expand(ifcopenshell.guid.new())] with BrickStore.new_changeset() as cs: @@ -60,8 +71,8 @@ class Brick(bonsai.core.tool.Brick): return str(brick) @classmethod - def add_brick_breadcrumb(cls, split_screen=False): - props = bpy.context.scene.BIMBrickProperties + def add_brick_breadcrumb(cls, split_screen: bool = False) -> None: + props = tool.Brick.get_brick_props() if split_screen: new = props.split_screen_brick_breadcrumbs.add() new.name = props.split_screen_active_brick_class @@ -70,7 +81,7 @@ class Brick(bonsai.core.tool.Brick): new.name = props.active_brick_class @classmethod - def add_brick_from_element(cls, element, namespace, brick_class): + def add_brick_from_element(cls, element: ifcopenshell.entity_instance, namespace: str, brick_class: str) -> str: ns = Namespace(namespace) brick = ns[element.GlobalId] with BrickStore.new_changeset() as cs: @@ -82,7 +93,7 @@ class Brick(bonsai.core.tool.Brick): return str(brick) @classmethod - def add_brickifc_project(cls, namespace): + def add_brickifc_project(cls, namespace: str) -> str: project = tool.Ifc.get().by_type("IfcProject")[0] ns = Namespace(namespace) brick_project = ns[project.GlobalId] @@ -96,7 +107,7 @@ class Brick(bonsai.core.tool.Brick): return str(brick_project) @classmethod - def add_brickifc_reference(cls, brick, element, project): + def add_brickifc_reference(cls, brick: str, element: ifcopenshell.entity_instance, project: str) -> None: with BrickStore.new_changeset() as cs: bnode = BNode() cs.add((URIRef(brick), REF.hasExternalReference, bnode)) @@ -107,23 +118,24 @@ class Brick(bonsai.core.tool.Brick): cs.add((bnode, REF.ifcName, Literal(element.Name))) @classmethod - def add_relation(cls, brick_uri, predicate, object): + def add_relation(cls, brick_uri: str, predicate: str, object: str) -> None: + props = tool.Brick.get_brick_props() if predicate == "http://www.w3.org/2000/01/rdf-schema#label": with BrickStore.new_changeset() as cs: cs.add((URIRef(brick_uri), URIRef(predicate), Literal(object))) - bpy.context.scene.BIMBrickProperties.new_brick_relation_type = BrickStore.relationships[0] - bpy.context.scene.BIMBrickProperties.add_brick_relation_failed = False + props.new_brick_relation_type = BrickStore.relationships[0] + props.add_brick_relation_failed = False return 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))) - bpy.context.scene.BIMBrickProperties.add_brick_relation_failed = False + props.add_brick_relation_failed = False else: - bpy.context.scene.BIMBrickProperties.add_brick_relation_failed = True + props.add_brick_relation_failed = True @classmethod - def remove_relation(cls, brick_uri, predicate, object): + def remove_relation(cls, brick_uri: str, predicate: str, object: str) -> None: with BrickStore.new_changeset() as cs: for s, p, o in BrickStore.graph.triples((brick_uri, predicate, object)): cs.remove((s, p, o)) @@ -132,21 +144,22 @@ class Brick(bonsai.core.tool.Brick): cs.remove(triple) @classmethod - def clear_brick_browser(cls, split_screen=False): - props = bpy.context.scene.BIMBrickProperties + def clear_brick_browser(cls, split_screen: bool = False) -> None: + props = cls.get_brick_props() if split_screen: props.split_screen_bricks.clear() else: props.bricks.clear() @classmethod - def clear_project(cls): + def clear_project(cls) -> None: BrickStore.purge() - bpy.context.scene.BIMBrickProperties.active_brick_class == "" - bpy.context.scene.BIMBrickProperties.split_screen_active_brick_class == "" + props = cls.get_brick_props() + props.active_brick_class = "" + props.split_screen_active_brick_class = "" @classmethod - def export_brick_attributes(cls, brick_uri): + def export_brick_attributes(cls, brick_uri: str) -> dict[str, Any]: query = BrickStore.graph.query( """ PREFIX rdfs: @@ -169,13 +182,14 @@ class Brick(bonsai.core.tool.Brick): return {"Identification": brick_uri, "Name": name} @classmethod - def get_active_brick_class(cls, split_screen=False): + def get_active_brick_class(cls, split_screen: bool = False) -> str: + props = cls.get_brick_props() if split_screen: - return bpy.context.scene.BIMBrickProperties.split_screen_active_brick_class - return bpy.context.scene.BIMBrickProperties.active_brick_class + return props.split_screen_active_brick_class + return props.active_brick_class @classmethod - def get_brick(cls, element): + def get_brick(cls, element: ifcopenshell.entity_instance) -> Union[str, None]: for rel in element.HasAssociations: if rel.is_a("IfcRelAssociatesLibrary"): if tool.Ifc.get_schema() == "IFC2X3" and "#" in rel.RelatingLibrary.ItemReference: @@ -184,21 +198,21 @@ class Brick(bonsai.core.tool.Brick): return rel.RelatingLibrary.Identification @classmethod - def get_brick_class(cls, element): + def get_brick_class(cls, element: ifcopenshell.entity_instance) -> Union[str, None]: return ifcopenshell.util.brick.get_brick_type(element) @classmethod - def get_brick_path(cls): + def get_brick_path(cls) -> Union[str, None]: return BrickStore.path @classmethod - def get_brick_path_name(cls): + def get_brick_path_name(cls) -> str: if BrickStore.path: return os.path.basename(BrickStore.path) return "Unnamed" @classmethod - def get_brickifc_project(cls): + def get_brickifc_project(cls) -> Union[str, None]: project = tool.Ifc.get().by_type("IfcProject")[0] query = BrickStore.graph.query( """ @@ -217,45 +231,46 @@ class Brick(bonsai.core.tool.Brick): return results[0][0].toPython() @classmethod - def get_convertable_brick_elements(cls): + def get_convertable_brick_elements(cls) -> set[ifcopenshell.entity_instance]: 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): + def get_convertable_brick_spaces(cls) -> set[ifcopenshell.entity_instance]: if tool.Ifc.get_schema() == "IFC2X3": return set(tool.Ifc.get().by_type("IfcSpatialStructureElement")) return set(tool.Ifc.get().by_type("IfcSpatialElement")) @classmethod - def get_convertable_brick_systems(cls): + def get_convertable_brick_systems(cls) -> set[ifcopenshell.entity_instance]: 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): + def get_parent_space(cls, space: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]: element = ifcopenshell.util.element.get_aggregate(space) + assert element if not element.is_a("IfcProject"): return element @classmethod - def get_element_container(cls, element): + def get_element_container(cls, element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]: return ifcopenshell.util.element.get_container(element) @classmethod - def get_element_systems(cls, element): + def get_element_systems(cls, element: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]: return ifcopenshell.util.system.get_element_systems(element) @classmethod - def get_element_feeds(cls, element): + def get_element_feeds(cls, element: ifcopenshell.entity_instance) -> set[ifcopenshell.entity_instance]: return ifcopenshell.util.brick.get_element_feeds(element) @classmethod - def get_item_class(cls, item): + def get_item_class(cls, item: str) -> Union[str, None]: query = BrickStore.graph.query( """ PREFIX brick: @@ -271,7 +286,9 @@ class Brick(bonsai.core.tool.Brick): return row.get("class").toPython().split("#")[-1] @classmethod - def get_library_brick_reference(cls, library, brick_uri): + def get_library_brick_reference( + cls, library: ifcopenshell.entity_instance, brick_uri: str + ) -> Union[ifcopenshell.entity_instance, None]: if tool.Ifc.get_schema() == "IFC2X3": for reference in library.LibraryReference or []: if reference.ItemReference == brick_uri: @@ -282,11 +299,11 @@ class Brick(bonsai.core.tool.Brick): return reference @classmethod - def get_namespace(cls, uri): + def get_namespace(cls, uri: str) -> str: return uri.split("#")[0] + "#" @classmethod - def import_brick_classes(cls, brick_class, split_screen=False): + def import_brick_classes(cls, brick_class: str, split_screen: bool = False) -> None: query = BrickStore.graph.query( """ PREFIX brick: @@ -305,10 +322,11 @@ class Brick(bonsai.core.tool.Brick): "{brick_class}", brick_class ) ) + props = tool.Brick.get_brick_props() if split_screen: - bricks = bpy.context.scene.BIMBrickProperties.split_screen_bricks + bricks = props.split_screen_bricks else: - bricks = bpy.context.scene.BIMBrickProperties.bricks + bricks = props.bricks for row in query: new = bricks.add() label = row.get("label") @@ -319,7 +337,7 @@ class Brick(bonsai.core.tool.Brick): new.total_items = row.get("total_items").toPython() @classmethod - def import_brick_items(cls, brick_class, split_screen=False): + def import_brick_items(cls, brick_class: str, split_screen: bool = False) -> None: query = BrickStore.graph.query( """ PREFIX brick: @@ -336,10 +354,11 @@ class Brick(bonsai.core.tool.Brick): "{brick_class}", brick_class ) ) + props = tool.Brick.get_brick_props() if split_screen: - bricks = bpy.context.scene.BIMBrickProperties.split_screen_bricks + bricks = props.split_screen_bricks else: - bricks = bpy.context.scene.BIMBrickProperties.bricks + bricks = props.bricks for row in query: new = bricks.add() label = row.get("label") @@ -349,7 +368,7 @@ class Brick(bonsai.core.tool.Brick): new.uri = row.get("item").toPython() @classmethod - def load_brick_file(cls, filepath): + def load_brick_file(cls, filepath: str) -> None: if not BrickStore.schema: # important check for running under test cases BrickStore.schema = tool.Blender.get_data_dir_path(Path("brick", "Brick.ttl")) BrickStore.graph = brickschema.persistent.VersionedGraphCollection("sqlite://") @@ -365,7 +384,7 @@ class Brick(bonsai.core.tool.Brick): BrickStore.load_relationships() @classmethod - def new_brick_file(cls): + def new_brick_file(cls) -> None: if not BrickStore.schema: # important check for running under test cases BrickStore.schema = tool.Blender.get_data_dir_path(Path("brick", "Brick.ttl")) BrickStore.graph = brickschema.persistent.VersionedGraphCollection("sqlite://") @@ -378,8 +397,8 @@ class Brick(bonsai.core.tool.Brick): BrickStore.load_relationships() @classmethod - def pop_brick_breadcrumb(cls, split_screen=False): - props = bpy.context.scene.BIMBrickProperties + def pop_brick_breadcrumb(cls, split_screen: bool = False) -> str: + props = cls.get_brick_props() if split_screen: breadcrumbs = props.split_screen_brick_breadcrumbs else: @@ -391,7 +410,7 @@ class Brick(bonsai.core.tool.Brick): return name @classmethod - def remove_brick(cls, brick_uri): + def remove_brick(cls, brick_uri: str) -> None: with BrickStore.new_changeset() as cs: for s, p, o in BrickStore.graph.triples((URIRef(brick_uri), None, None)): cs.remove((s, p, o)) @@ -406,51 +425,55 @@ class Brick(bonsai.core.tool.Brick): ) @classmethod - def run_refresh_brick_viewer(cls): + def run_refresh_brick_viewer(cls) -> None: return bonsai.core.brick.refresh_brick_viewer(tool.Brick) @classmethod - def run_view_brick_class(cls, brick_class=None, split_screen=False): + def run_view_brick_class(cls, brick_class: Union[str, None] = None, split_screen: bool = False) -> None: return bonsai.core.brick.view_brick_class(tool.Brick, brick_class=brick_class, split_screen=split_screen) @classmethod - def select_browser_item(cls, item, split_screen=False): + def select_browser_item(cls, item: str, split_screen: bool = False) -> None: name = item.split("#")[-1] - props = bpy.context.scene.BIMBrickProperties + props = cls.get_brick_props() if split_screen: props.split_screen_active_brick_index = props.split_screen_bricks.find(name) else: props.active_brick_index = props.bricks.find(name) @classmethod - def set_active_brick_class(cls, brick_class, split_screen=False): - props = bpy.context.scene.BIMBrickProperties + def set_active_brick_class(cls, brick_class: str, split_screen: bool = False) -> None: + props = cls.get_brick_props() if split_screen: props.split_screen_active_brick_class = brick_class else: props.active_brick_class = brick_class @classmethod - def serialize_brick(cls): + def serialize_brick(cls) -> None: BrickStore.get_project().serialize(destination=BrickStore.path, format="turtle") BrickStore.set_last_saved() @classmethod - def add_namespace(cls, alias, uri): + def add_namespace(cls, alias: str, uri: str) -> None: + assert BrickStore.graph BrickStore.graph.bind(alias, Namespace(uri)) BrickStore.load_namespaces() @classmethod - def clear_breadcrumbs(cls, split_screen=False): + def clear_breadcrumbs(cls, split_screen: bool = False) -> None: + props = cls.get_brick_props() if split_screen: - bpy.context.scene.BIMBrickProperties.split_screen_brick_breadcrumbs.clear() + props.split_screen_brick_breadcrumbs.clear() else: - bpy.context.scene.BIMBrickProperties.brick_breadcrumbs.clear() + props.brick_breadcrumbs.clear() class BrickStore: schema = None # this is now a os path + path: Union[str, None] path = None # file path if the project was loaded in + graph: Union[brickschema.persistent.VersionedGraphCollection, None] graph = None # this is the VersionedGraphCollection with 2 arbitrarily named graphs: "schema" and "project" # "SCHEMA" holds the Brick.ttl metadata; "PROJECT" holds all the authored entities last_saved = None @@ -475,11 +498,11 @@ class BrickStore: BrickStore.relationships = [] @classmethod - def get_project(cls): + def get_project(cls) -> brickschema.graph.Graph: return BrickStore.graph.graph_at(graph="PROJECT") @classmethod - def load_sub_roots(cls): + def load_sub_roots(cls) -> None: query = BrickStore.graph.query( """ PREFIX brick: @@ -505,7 +528,7 @@ class BrickStore: BrickStore.root_classes.append(sub_root) @classmethod - def load_namespaces(cls): + def load_namespaces(cls) -> None: BrickStore.namespaces = [] keyword_filter = [ "brickschema.org", @@ -529,7 +552,7 @@ class BrickStore: BrickStore.namespaces.append((alias, str(uri))) @classmethod - def load_entity_classes(cls): + def load_entity_classes(cls) -> None: for root_class in BrickStore.root_classes: query = BrickStore.graph.query( """ @@ -551,7 +574,7 @@ class BrickStore: BrickStore.entity_classes[root_class].append(uri) @classmethod - def load_relationships(cls): + def load_relationships(cls) -> None: query = BrickStore.graph.query( """ PREFIX brick: @@ -565,30 +588,30 @@ class BrickStore: BrickStore.relationships.append(uri) @classmethod - def set_history_size(cls, size): + def set_history_size(cls, size: int) -> None: cls.history_size = size while len(cls.history) > cls.history_size: cls.history.pop(0) @classmethod - def begin_transaction(cls): + def begin_transaction(cls) -> None: cls.current_changesets = 0 @classmethod - def end_transaction(cls): + def end_transaction(cls) -> None: cls.history.append(cls.current_changesets) if len(cls.history) > cls.history_size: cls.history.pop(0) @classmethod @contextmanager - def new_changeset(cls): + def new_changeset(cls) -> Generator[Any, None, None]: cls.current_changesets += 1 with BrickStore.graph.new_changeset("PROJECT") as cs: yield cs @classmethod - def undo(cls): + def undo(cls) -> None: if not BrickStore.graph or not BrickStore.history: return total_changesets = BrickStore.history.pop() @@ -597,7 +620,7 @@ class BrickStore: BrickStore.future.append(total_changesets) @classmethod - def redo(cls): + def redo(cls) -> None: if not BrickStore.graph or not BrickStore.future: return total_changesets = BrickStore.future.pop() @@ -606,7 +629,7 @@ class BrickStore: BrickStore.history.append(total_changesets) @classmethod - def set_last_saved(cls): + def set_last_saved(cls) -> None: save = os.path.getmtime(BrickStore.path) save = datetime.datetime.fromtimestamp(save) BrickStore.last_saved = f"{save.year}-{save.month}-{save.day} {save.hour}:{save.minute}" diff --git a/src/bonsai/test/tool/test_brick.py b/src/bonsai/test/tool/test_brick.py index b26a2acd43..0220cb2962 100644 --- a/src/bonsai/test/tool/test_brick.py +++ b/src/bonsai/test/tool/test_brick.py @@ -59,16 +59,18 @@ class TestAddBrickBreadcrumb(NewFile): def test_run(self): subject.set_active_brick_class("brick_class") subject.add_brick_breadcrumb() - assert bpy.context.scene.BIMBrickProperties.brick_breadcrumbs[0].name == "brick_class" + props = tool.Brick.get_brick_props() + assert props.brick_breadcrumbs[0].name == "brick_class" subject.add_brick_breadcrumb() - assert bpy.context.scene.BIMBrickProperties.brick_breadcrumbs[1].name == "brick_class" + assert props.brick_breadcrumbs[1].name == "brick_class" def test_run_split_screen(self): subject.set_active_brick_class("brick_class", split_screen=True) subject.add_brick_breadcrumb(split_screen=True) - assert bpy.context.scene.BIMBrickProperties.split_screen_brick_breadcrumbs[0].name == "brick_class" + props = tool.Brick.get_brick_props() + assert props.split_screen_brick_breadcrumbs[0].name == "brick_class" subject.add_brick_breadcrumb(split_screen=True) - assert bpy.context.scene.BIMBrickProperties.split_screen_brick_breadcrumbs[1].name == "brick_class" + assert props.split_screen_brick_breadcrumbs[1].name == "brick_class" class TestAddBrickFromElement(NewFile): @@ -188,25 +190,28 @@ class TestRemoveRelation(NewFile): class TestClearBrickBrowser(NewFile): def test_run(self): - bpy.context.scene.BIMBrickProperties.bricks.add() + props = tool.Brick.get_brick_props() + props.bricks.add() subject.clear_brick_browser() - assert len(bpy.context.scene.BIMBrickProperties.bricks) == 0 + assert len(props.bricks) == 0 def test_run_split_screen(self): - bpy.context.scene.BIMBrickProperties.split_screen_bricks.add() + props = tool.Brick.get_brick_props() + props.split_screen_bricks.add() subject.clear_brick_browser(split_screen=True) - assert len(bpy.context.scene.BIMBrickProperties.split_screen_bricks) == 0 + assert len(props.split_screen_bricks) == 0 class TestClearProject(NewFile): def test_run(self): BrickStore.graph = "graph" - bpy.context.scene.BIMBrickProperties.active_brick_class == "brick_class" - bpy.context.scene.BIMBrickProperties.split_screen_active_brick_class == "brick_class2" + props = tool.Brick.get_brick_props() + props.active_brick_class = "brick_class" + props.split_screen_active_brick_class = "brick_class2" subject.clear_project() assert BrickStore.graph is None - assert bpy.context.scene.BIMBrickProperties.active_brick_class == "" - assert bpy.context.scene.BIMBrickProperties.split_screen_active_brick_class == "" + assert props.active_brick_class == "" + assert props.split_screen_active_brick_class == "" class TestExportBrickAttributes(NewFile): @@ -374,13 +379,14 @@ class TestImportBrickClasses(NewFile): def test_run(self): TestLoadBrickFile().test_run() subject.import_brick_classes("Class") - assert len(bpy.context.scene.BIMBrickProperties.bricks) == 2 - brick = bpy.context.scene.BIMBrickProperties.bricks[0] + props = tool.Brick.get_brick_props() + assert len(props.bricks) == 2 + brick = props.bricks[0] assert brick.name == "Building" assert brick.uri == "https://brickschema.org/schema/Brick#Building" assert brick.total_items == 1 assert not brick.label - brick = bpy.context.scene.BIMBrickProperties.bricks[1] + brick = props.bricks[1] assert brick.name == "Location" assert brick.uri == "https://brickschema.org/schema/Brick#Location" assert brick.total_items == 1 @@ -389,13 +395,14 @@ class TestImportBrickClasses(NewFile): def test_run_split_sccreen(self): TestLoadBrickFile().test_run() subject.import_brick_classes("Class", split_screen=True) - assert len(bpy.context.scene.BIMBrickProperties.split_screen_bricks) == 2 - brick = bpy.context.scene.BIMBrickProperties.split_screen_bricks[0] + props = tool.Brick.get_brick_props() + assert len(props.split_screen_bricks) == 2 + brick = props.split_screen_bricks[0] assert brick.name == "Building" assert brick.uri == "https://brickschema.org/schema/Brick#Building" assert brick.total_items == 1 assert not brick.label - brick = bpy.context.scene.BIMBrickProperties.split_screen_bricks[1] + brick = props.split_screen_bricks[1] assert brick.name == "Location" assert brick.uri == "https://brickschema.org/schema/Brick#Location" assert brick.total_items == 1 @@ -406,8 +413,9 @@ class TestImportBrickItems(NewFile): def test_run(self): TestLoadBrickFile().test_run() subject.import_brick_items("Building") - assert len(bpy.context.scene.BIMBrickProperties.bricks) == 1 - brick = bpy.context.scene.BIMBrickProperties.bricks[0] + props = tool.Brick.get_brick_props() + assert len(props.bricks) == 1 + brick = props.bricks[0] assert brick.name == "bldg" assert brick.label == "My Building" assert brick.uri == "https://example.org/digitaltwin#bldg" @@ -416,8 +424,9 @@ class TestImportBrickItems(NewFile): def test_run_split_screen(self): TestLoadBrickFile().test_run() subject.import_brick_items("Building", split_screen=True) - assert len(bpy.context.scene.BIMBrickProperties.split_screen_bricks) == 1 - brick = bpy.context.scene.BIMBrickProperties.split_screen_bricks[0] + props = tool.Brick.get_brick_props() + assert len(props.split_screen_bricks) == 1 + brick = props.split_screen_bricks[0] assert brick.name == "bldg" assert brick.label == "My Building" assert brick.uri == "https://example.org/digitaltwin#bldg" @@ -456,18 +465,20 @@ class TestNewBrickFile(NewFile): class TestPopBrickBreadcrumb(NewFile): def test_run(self): - bpy.context.scene.BIMBrickProperties.brick_breadcrumbs.add().name = "foo" - bpy.context.scene.BIMBrickProperties.brick_breadcrumbs.add().name = "bar" + props = tool.Brick.get_brick_props() + props.brick_breadcrumbs.add().name = "foo" + props.brick_breadcrumbs.add().name = "bar" assert subject.pop_brick_breadcrumb() == "bar" - assert len(bpy.context.scene.BIMBrickProperties.brick_breadcrumbs) == 1 - assert bpy.context.scene.BIMBrickProperties.brick_breadcrumbs[0].name == "foo" + assert len(props.brick_breadcrumbs) == 1 + assert props.brick_breadcrumbs[0].name == "foo" def test_run_split_screen(self): - bpy.context.scene.BIMBrickProperties.split_screen_brick_breadcrumbs.add().name = "foo" - bpy.context.scene.BIMBrickProperties.split_screen_brick_breadcrumbs.add().name = "bar" + props = tool.Brick.get_brick_props() + props.split_screen_brick_breadcrumbs.add().name = "foo" + props.split_screen_brick_breadcrumbs.add().name = "bar" assert subject.pop_brick_breadcrumb(split_screen=True) == "bar" - assert len(bpy.context.scene.BIMBrickProperties.split_screen_brick_breadcrumbs) == 1 - assert bpy.context.scene.BIMBrickProperties.split_screen_brick_breadcrumbs[0].name == "foo" + assert len(props.split_screen_brick_breadcrumbs) == 1 + assert props.split_screen_brick_breadcrumbs[0].name == "foo" class TestRemoveBrick(NewFile): @@ -512,19 +523,22 @@ class TestRunViewBrickClass(NewFile): class TestSelectBrowserItem(NewFile): def test_run(self): subject.set_active_brick_class("brick_class") - assert bpy.context.scene.BIMBrickProperties.active_brick_class == "brick_class" + props = tool.Brick.get_brick_props() + assert props.active_brick_class == "brick_class" def test_run(self): subject.set_active_brick_class("brick_class", split_screen=True) - assert bpy.context.scene.BIMBrickProperties.split_screen_active_brick_class == "brick_class" + props = tool.Brick.get_brick_props() + assert props.split_screen_active_brick_class == "brick_class" class TestSetActiveBrickClass(NewFile): def test_run(self): - bpy.context.scene.BIMBrickProperties.bricks.add().name = "foo" - bpy.context.scene.BIMBrickProperties.bricks.add().name = "bar" + props = tool.Brick.get_brick_props() + props.bricks.add().name = "foo" + props.bricks.add().name = "bar" subject.select_browser_item("namespace#bar") - assert bpy.context.scene.BIMBrickProperties.active_brick_index == 1 + assert props.active_brick_index == 1 class TestSerializeBrick(NewFile): diff --git a/src/ifcopenshell-python/ifcopenshell/api/root/reassign_class.py b/src/ifcopenshell-python/ifcopenshell/api/root/reassign_class.py index 7682856864..a7588962dd 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/root/reassign_class.py +++ b/src/ifcopenshell-python/ifcopenshell/api/root/reassign_class.py @@ -27,7 +27,7 @@ import ifcopenshell.util.representation import ifcopenshell.util.type import ifcopenshell.util.schema import ifcopenshell.util.element -from typing import Optional, Union, Literal, Any +from typing import Optional, Union, Literal def reassign_class( @@ -56,7 +56,6 @@ def reassign_class( Reassigning type class to occurrence (and vice versa) is supported. :param product: The IfcProduct that you want to change the class of. - :type product: ifcopenshell.entity_instance :param ifc_class: The new IFC class you want to change it to. :param predefined_type: In case you want to change the predefined type too. User defined types are also allowed, just type what you want. @@ -77,25 +76,18 @@ def reassign_class( """ usecase = Usecase() usecase.file = file - usecase.settings = { - "product": product, - "ifc_class": ifc_class, - "predefined_type": predefined_type, - } - return usecase.execute() + return usecase.execute(product, ifc_class, predefined_type) class Usecase: file: ifcopenshell.file - settings: dict[str, Any] - - def execute(self): - ifc_class: str = self.settings["ifc_class"] - product: ifcopenshell.entity_instance = self.settings["product"] - predefined_type: Union[str, None] = self.settings["predefined_type"] + def execute( + self, product: ifcopenshell.entity_instance, ifc_class: str, predefined_type: Union[str, None] + ) -> ifcopenshell.entity_instance: was_type_product_before = product.is_a("IfcTypeProduct") schema = ifcopenshell.schema_by_name(self.file.schema) + is_type_product_after: bool is_type_product_after = schema.declaration_by_name(ifc_class)._is("IfcTypeProduct") if was_type_product_before == is_type_product_after: diff --git a/src/ifcopenshell-python/ifcopenshell/api/type/unassign_type.py b/src/ifcopenshell-python/ifcopenshell/api/type/unassign_type.py index 2723dd760f..572327afb7 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/type/unassign_type.py +++ b/src/ifcopenshell-python/ifcopenshell/api/type/unassign_type.py @@ -28,9 +28,7 @@ def unassign_type(file: ifcopenshell.file, related_objects: list[ifcopenshell.en and material usages associated with the previously assigned type. :param related_objects: List of IfcElement occurrences. - :type related_objects: list[ifcopenshell.entity_instance] :return: None - :rtype: None Example: @@ -50,23 +48,21 @@ def unassign_type(file: ifcopenshell.file, related_objects: list[ifcopenshell.en # Change our mind. Maybe it's a different type? ifcopenshell.api.type.unassign_type(model, related_objects=[furniture]) """ - settings = {"related_objects": related_objects} - - related_objects = set(settings["related_objects"]) + related_objects_set = set(related_objects) if file.schema == "IFC2X3": rels = set( rel - for object in related_objects + for object in related_objects_set if (rel := next((rel for rel in object.IsDefinedBy if rel.is_a("IfcRelDefinesByType")), None)) ) else: - rels = set(rel for object in related_objects if (rel := next((rel for rel in object.IsTypedBy), None))) + rels = set(rel for object in related_objects_set if (rel := next((rel for rel in object.IsTypedBy), None))) for rel in rels: - related_objects = set(rel.RelatedObjects) - related_objects - if related_objects: - rel.RelatedObjects = list(related_objects) + related_objects_set = set(rel.RelatedObjects) - related_objects_set + if related_objects_set: + rel.RelatedObjects = list(related_objects_set) ifcopenshell.api.owner.update_owner_history(file, **{"element": rel}) else: history = rel.OwnerHistory diff --git a/src/ifcopenshell-python/ifcopenshell/util/brick.py b/src/ifcopenshell-python/ifcopenshell/util/brick.py index 9070a0054b..a50caeaa05 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/brick.py +++ b/src/ifcopenshell-python/ifcopenshell/util/brick.py @@ -21,6 +21,7 @@ import json import ifcopenshell import ifcopenshell.util.element import ifcopenshell.util.classification +from typing import Union cwd = os.path.dirname(os.path.realpath(__file__)) @@ -31,7 +32,7 @@ with open(os.path.join(cwd, "ifc4_to_brick.json")) as f: ifc4_to_brick_map = json.load(f) -def get_brick_type(element): +def get_brick_type(element: ifcopenshell.entity_instance) -> Union[str, None]: references = ifcopenshell.util.classification.get_references(element) for reference in references: system = ifcopenshell.util.classification.get_classification(reference) @@ -61,10 +62,10 @@ def get_brick_type(element): return f"https://brickschema.org/schema/Brick#System" -def get_element_feeds(element): +def get_element_feeds(element: ifcopenshell.entity_instance) -> set[ifcopenshell.entity_instance]: current_element = element processed_elements = set() - downstream_equipment = set() + downstream_equipment: set[ifcopenshell.entity_instance] = 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