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