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):
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:
+12 -11
View File
@@ -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)
+35 -4
View File
@@ -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
+5 -5
View File
@@ -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()
@@ -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_,
+39 -17
View File
@@ -16,8 +16,16 @@
# You should have received a copy of the GNU General Public License
# 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.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)
+93 -70
View File
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
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: <http://www.w3.org/2000/01/rdf-schema#>
@@ -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: <https://brickschema.org/schema/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: <https://brickschema.org/schema/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: <https://brickschema.org/schema/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: <https://brickschema.org/schema/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: <https://brickschema.org/schema/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}"
+49 -35
View File
@@ -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):
@@ -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:
@@ -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
@@ -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