diff --git a/src/bonsai/bonsai/bim/handler.py b/src/bonsai/bonsai/bim/handler.py
index 287e0bb09d..4ed12d74f0 100644
--- a/src/bonsai/bonsai/bim/handler.py
+++ b/src/bonsai/bonsai/bim/handler.py
@@ -228,7 +228,7 @@ def refresh_ui_data():
@persistent
-def loadIfcStore(scene):
+def loadIfcStore(scene: bpy.types.Scene) -> None:
IfcStore.purge()
refresh_ui_data()
if not tool.Ifc.get():
@@ -238,19 +238,21 @@ def loadIfcStore(scene):
@persistent
-def undo_post(scene):
- if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction:
- IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
- IfcStore.undo(until_key=bpy.context.scene.BIMProperties.last_transaction)
+def undo_post(scene: bpy.types.Scene) -> None:
+ props = tool.Blender.get_bim_props()
+ if IfcStore.last_transaction != props.last_transaction:
+ IfcStore.last_transaction = props.last_transaction
+ IfcStore.undo(until_key=props.last_transaction)
refresh_ui_data()
tool.Ifc.rebuild_element_maps()
@persistent
-def redo_post(scene):
- if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction:
- IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
- IfcStore.redo(until_key=bpy.context.scene.BIMProperties.last_transaction)
+def redo_post(scene: bpy.types.Scene) -> None:
+ props = tool.Blender.get_bim_props()
+ if IfcStore.last_transaction != props.last_transaction:
+ IfcStore.last_transaction = props.last_transaction
+ IfcStore.redo(until_key=props.last_transaction)
refresh_ui_data()
tool.Ifc.rebuild_element_maps()
@@ -283,7 +285,7 @@ def get_user(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None
return pao
-def viewport_shading_changed_callback(area):
+def viewport_shading_changed_callback(area: bpy.types.Area) -> None:
shading = area.spaces.active.shading.type
if shading == "RENDERED":
bpy.context.scene.BIMStylesProperties.active_style_type = "External"
@@ -341,7 +343,8 @@ def load_post(scene):
tool.Blender.setup_tabs()
if tool.Ifc.get() and bpy.data.is_saved:
- bpy.context.scene.BIMProperties.has_blend_warning = True
+ props = tool.Blender.get_bim_props()
+ props.has_blend_warning = True
# Bonsai overlays
georeference_props = tool.Georeference.get_georeference_props()
diff --git a/src/bonsai/bonsai/bim/helper.py b/src/bonsai/bonsai/bim/helper.py
index 2d19fa6e1f..3ac994739a 100644
--- a/src/bonsai/bonsai/bim/helper.py
+++ b/src/bonsai/bonsai/bim/helper.py
@@ -32,6 +32,7 @@ from typing import Optional, Callable, Any, Union, Iterable, TYPE_CHECKING
if TYPE_CHECKING:
import bonsai.bim.prop
from bonsai.bim.prop import Attribute
+ from bonsai.bim.module.search.prop import BIMFilterGroup
# ImportCallback return values:
# - None - property should be imported by default workflow
@@ -372,11 +373,16 @@ def convert_property_group_from_si(property_group: bpy.types.PropertyGroup, skip
setattr(property_group, prop_name, prop_value)
-def draw_filter(layout: bpy.types.UILayout, filter_groups, data, module: str) -> None:
+def draw_filter(
+ layout: bpy.types.UILayout,
+ filter_groups: bpy.types.bpy_prop_collection_idprop[BIMFilterGroup],
+ data,
+ module: str,
+) -> None:
if not data.is_loaded:
data.load()
- sprops = bpy.context.scene.BIMSearchProperties
+ sprops = tool.Search.get_search_props()
if tool.Ifc.get():
row = layout.row(align=True)
diff --git a/src/bonsai/bonsai/bim/ifc.py b/src/bonsai/bonsai/bim/ifc.py
index 044bf74fd2..35b40b7b3c 100644
--- a/src/bonsai/bonsai/bim/ifc.py
+++ b/src/bonsai/bonsai/bim/ifc.py
@@ -105,7 +105,8 @@ class IfcStore:
@staticmethod
def get_file():
if IfcStore.file is None:
- IfcStore.path = cast(str, bpy.context.scene.BIMProperties.ifc_file)
+ props = tool.Blender.get_bim_props()
+ IfcStore.path = props.ifc_file
# Interpret relative paths as relative to .blend file.
if IfcStore.path and not os.path.isabs(IfcStore.path):
IfcStore.path = os.path.abspath(os.path.join(bpy.path.abspath("//"), IfcStore.path))
@@ -119,10 +120,11 @@ class IfcStore:
@staticmethod
def get_cache():
if IfcStore.cache is None and IfcStore.path:
+ props = tool.Blender.get_bim_props()
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
- os.makedirs(bpy.context.scene.BIMProperties.cache_dir, exist_ok=True)
- IfcStore.cache_path = os.path.join(bpy.context.scene.BIMProperties.cache_dir, f"{ifc_hash}.h5")
+ os.makedirs(props.cache_dir, exist_ok=True)
+ IfcStore.cache_path = os.path.join(props.cache_dir, f"{ifc_hash}.h5")
cache_path = Path(IfcStore.cache_path)
cache_settings = ifcopenshell.geom.settings()
serializer_settings = ifcopenshell.geom.serializer_settings()
@@ -162,7 +164,8 @@ class IfcStore:
assert IfcStore.file
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
- new_cache_path = os.path.join(bpy.context.scene.BIMProperties.cache_dir, f"{ifc_hash}.h5")
+ props = tool.Blender.get_bim_props()
+ new_cache_path = os.path.join(props.cache_dir, f"{ifc_hash}.h5")
IfcStore.cache = None
try:
shutil.move(IfcStore.cache_path, new_cache_path)
@@ -414,7 +417,8 @@ class IfcStore:
method: Literal["EXECUTE", "INVOKE", "MODAL"] = "EXECUTE",
) -> set[str]:
bonsai.last_actions.append({"type": "operator", "name": operator.bl_idname})
- bpy.context.scene.BIMProperties.is_dirty = True
+ props = tool.Blender.get_bim_props()
+ props.is_dirty = True
# Modals don't nest, and Blender handles the loop that continuously calls modal()
is_top_level_operator = not bool(IfcStore.current_transaction) or (method == "MODAL")
@@ -493,7 +497,8 @@ class IfcStore:
) -> None:
key = getattr(operator, "transaction_key", None)
data = getattr(operator, "transaction_data", None)
- bpy.context.scene.BIMProperties.last_transaction = key
+ props = tool.Blender.get_bim_props()
+ props.last_transaction = key
IfcStore.last_transaction = key
rollback = rollback or getattr(operator, "rollback", lambda data: True)
commit = commit or getattr(operator, "commit", lambda data: True)
diff --git a/src/bonsai/bonsai/bim/import_ifc.py b/src/bonsai/bonsai/bim/import_ifc.py
index 5dd519de28..21c8d16e3e 100644
--- a/src/bonsai/bonsai/bim/import_ifc.py
+++ b/src/bonsai/bonsai/bim/import_ifc.py
@@ -879,17 +879,19 @@ class IfcImporter:
def load_file(self):
self.ifc_import_settings.logger.info("loading file %s", self.ifc_import_settings.input_file)
- if not bpy.context.scene.BIMProperties.ifc_file:
- bpy.context.scene.BIMProperties.ifc_file = self.ifc_import_settings.input_file
+ props = tool.Blender.get_bim_props()
+ if not props.ifc_file:
+ props.ifc_file = self.ifc_import_settings.input_file
self.file = tool.Ifc.get()
def calculate_unit_scale(self):
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
tool.Loader.set_unit_scale(self.unit_scale)
- def set_units(self):
+ def set_units(self) -> None:
if not (assignment := self.file.by_type("IfcProject")[0].UnitsInContext):
return # Geometry is optional in IFC
+ props = tool.Blender.get_bim_props()
for unit in assignment.Units:
if unit.is_a("IfcNamedUnit") and unit.UnitType == "LENGTHUNIT":
if unit.is_a("IfcSIUnit"):
@@ -909,19 +911,19 @@ class IfcImporter:
elif unit.is_a("IfcNamedUnit") and unit.UnitType == "AREAUNIT":
name = unit.Name if unit.is_a("IfcSIUnit") else unit.Name.lower()
try:
- bpy.context.scene.BIMProperties.area_unit = "{}{}".format(
+ props.area_unit = "{}{}".format(
unit.Prefix + "/" if hasattr(unit, "Prefix") and unit.Prefix else "", name
)
except: # Probably an invalid unit.
- bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE"
+ props.area_unit = "SQUARE_METRE"
elif unit.is_a("IfcNamedUnit") and unit.UnitType == "VOLUMEUNIT":
name = unit.Name if unit.is_a("IfcSIUnit") else unit.Name.lower()
try:
- bpy.context.scene.BIMProperties.volume_unit = "{}{}".format(
+ props.volume_unit = "{}{}".format(
unit.Prefix + "/" if hasattr(unit, "Prefix") and unit.Prefix else "", name
)
except: # Probably an invalid unit.
- bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE"
+ props.volume_unit = "CUBIC_METRE"
def create_project(self):
project = self.file.by_type("IfcProject")[0]
diff --git a/src/bonsai/bonsai/bim/module/augin/operator.py b/src/bonsai/bonsai/bim/module/augin/operator.py
index c4999ab08f..b578beb554 100644
--- a/src/bonsai/bonsai/bim/module/augin/operator.py
+++ b/src/bonsai/bonsai/bim/module/augin/operator.py
@@ -122,15 +122,16 @@ class AuginCreateNewModel(bpy.types.Operator):
context.scene.render.image_settings.file_format = old_file_format
context.scene.render.filepath = old_filepath
- client.upload_file(context.scene.BIMProperties.ifc_file, result["s3_bucket"], result["model_path"])
+ bim_props = tool.Blender.get_bim_props()
+ client.upload_file(bim_props.ifc_file, result["s3_bucket"], result["model_path"])
client.upload_file(thumb_path, result["s3_bucket"], result["thumb_path"])
# Notify done
url = "https://server.auge.pro.br/API/v3/augin_rest.php/files_uploaded"
payload = {
"user_token": props.token,
- "ifc_filesize": os.path.getsize(context.scene.BIMProperties.ifc_file),
- "model_filesize": os.path.getsize(context.scene.BIMProperties.ifc_file),
+ "ifc_filesize": os.path.getsize(bim_props.ifc_file),
+ "model_filesize": os.path.getsize(bim_props.ifc_file),
"thumb_filesize": os.path.getsize(thumb_path),
"model_upload_path": result["model_path"],
"thumb_upload_path": result["thumb_path"],
diff --git a/src/bonsai/bonsai/bim/module/augin/ui.py b/src/bonsai/bonsai/bim/module/augin/ui.py
index 0da722f7a6..65ca3ac7f9 100644
--- a/src/bonsai/bonsai/bim/module/augin/ui.py
+++ b/src/bonsai/bonsai/bim/module/augin/ui.py
@@ -17,6 +17,7 @@
# along with Bonsai. If not, see .
import bpy.types
+import bonsai.tool as tool
class BIM_PT_augin(bpy.types.Panel):
@@ -47,7 +48,8 @@ class BIM_PT_augin(bpy.types.Panel):
row = layout.row()
row.label(text="Logged in as " + props.username)
- if not context.scene.BIMProperties.ifc_file:
+ bim_props = tool.Blender.get_bim_props()
+ if not bim_props.ifc_file:
row = layout.row()
row.label(text="No IFC Found")
return
diff --git a/src/bonsai/bonsai/bim/module/clash/operator.py b/src/bonsai/bonsai/bim/module/clash/operator.py
index d8eb640551..c3d87e4759 100644
--- a/src/bonsai/bonsai/bim/module/clash/operator.py
+++ b/src/bonsai/bonsai/bim/module/clash/operator.py
@@ -321,8 +321,9 @@ class SelectIfcClashResults(bpy.types.Operator):
ifc_file = ""
for scene in obj.users_scene:
- if scene.BIMProperties.ifc_file:
- ifc_file = scene.BIMProperties.ifc_file
+ bim_props = tool.Blender.get_bim_props(scene)
+ if bim_props.ifc_file:
+ ifc_file = bim_props.ifc_file
if scene.library:
break
diff --git a/src/bonsai/bonsai/bim/module/debug/operator.py b/src/bonsai/bonsai/bim/module/debug/operator.py
index 68e3525a87..4b16c3fb12 100644
--- a/src/bonsai/bonsai/bim/module/debug/operator.py
+++ b/src/bonsai/bonsai/bim/module/debug/operator.py
@@ -106,7 +106,8 @@ class ConvertToBlender(bpy.types.Operator):
if material.library:
continue
tool.Ifc.unlink(obj=material)
- context.scene.BIMProperties.ifc_file = ""
+ bim_props = tool.Blender.get_bim_props()
+ bim_props.ifc_file = ""
tool.Debug.get_debug_props().attributes.clear()
IfcStore.purge()
bonsai.bim.handler.refresh_ui_data()
@@ -160,7 +161,8 @@ class ProfileImportIFC(bpy.types.Operator):
if not tool.Ifc.get():
cls.poll_message_set("No IFC file loaded.")
return False
- if not context.scene.BIMProperties.ifc_file:
+ bim_props = tool.Blender.get_bim_props()
+ if not bim_props.ifc_file:
cls.poll_message_set("Current IFC file is not saved.")
return False
return True
diff --git a/src/bonsai/bonsai/bim/module/debug/ui.py b/src/bonsai/bonsai/bim/module/debug/ui.py
index 4fbd5c8ea6..c2c43bd4bb 100644
--- a/src/bonsai/bonsai/bim/module/debug/ui.py
+++ b/src/bonsai/bonsai/bim/module/debug/ui.py
@@ -34,9 +34,10 @@ class BIM_PT_debug(Panel):
layout = self.layout
props = tool.Debug.get_debug_props()
+ bim_props = tool.Blender.get_bim_props()
row = self.layout.row(align=True)
- row.prop(context.scene.BIMProperties, "ifc_file", text="")
+ row.prop(bim_props, "ifc_file", text="")
row.operator("bim.validate_ifc_file", icon="CHECKMARK", text="")
row.operator("bim.select_ifc_file", icon="FILE_FOLDER", text="")
diff --git a/src/bonsai/bonsai/bim/module/diff/operator.py b/src/bonsai/bonsai/bim/module/diff/operator.py
index ead5dd6e04..dc9e981b19 100644
--- a/src/bonsai/bonsai/bim/module/diff/operator.py
+++ b/src/bonsai/bonsai/bim/module/diff/operator.py
@@ -70,8 +70,9 @@ class VisualiseDiff(bpy.types.Operator):
ifc_file = ""
for scene in obj.users_scene:
- if scene.BIMProperties.ifc_file:
- ifc_file = scene.BIMProperties.ifc_file
+ bim_props = tool.Blender.get_bim_props(scene)
+ if bim_props.ifc_file:
+ ifc_file = bim_props.ifc_file
if scene.library:
break
@@ -257,8 +258,9 @@ class SelectDiffObjects(bpy.types.Operator):
ifc_file = ""
for scene in obj.users_scene:
- if scene.BIMProperties.ifc_file:
- ifc_file = scene.BIMProperties.ifc_file
+ bim_props = tool.Blender.get_bim_props(scene)
+ if bim_props.ifc_file:
+ ifc_file = bim_props.ifc_file
if scene.library:
break
diff --git a/src/bonsai/bonsai/bim/module/document/data.py b/src/bonsai/bonsai/bim/module/document/data.py
index 0a75605b5c..5cde82e499 100644
--- a/src/bonsai/bonsai/bim/module/document/data.py
+++ b/src/bonsai/bonsai/bim/module/document/data.py
@@ -52,7 +52,7 @@ class DocumentData:
@classmethod
def parent_document(cls):
- props = bpy.context.scene.BIMDocumentProperties
+ props = tool.Document.get_document_props()
if len(props.breadcrumbs):
parent = tool.Ifc.get().by_id(int(props.breadcrumbs[-1].name))
if tool.Ifc.get_schema() == "IFC2X3":
diff --git a/src/bonsai/bonsai/bim/module/document/operator.py b/src/bonsai/bonsai/bim/module/document/operator.py
index 13c494d092..2ed1a0a8d5 100644
--- a/src/bonsai/bonsai/bim/module/document/operator.py
+++ b/src/bonsai/bonsai/bim/module/document/operator.py
@@ -115,7 +115,7 @@ class EditDocument(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
- props = context.scene.BIMDocumentProperties
+ props = tool.Document.get_document_props()
core.edit_document(tool.Ifc, tool.Document, document=tool.Ifc.get().by_id(props.active_document_id))
diff --git a/src/bonsai/bonsai/bim/module/document/prop.py b/src/bonsai/bonsai/bim/module/document/prop.py
index 2f8d32408f..ef4fb29ab9 100644
--- a/src/bonsai/bonsai/bim/module/document/prop.py
+++ b/src/bonsai/bonsai/bim/module/document/prop.py
@@ -30,6 +30,7 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
+from typing import TYPE_CHECKING
def update_document_name(self: "Document", context: bpy.types.Context) -> None:
@@ -57,6 +58,11 @@ class Document(PropertyGroup):
)
ifc_definition_id: IntProperty(name="IFC Definition ID")
+ if TYPE_CHECKING:
+ identification: str
+ is_information: bool
+ ifc_definition_id: int
+
class BIMDocumentProperties(PropertyGroup):
document_attributes: CollectionProperty(name="Document Attributes", type=Attribute)
@@ -65,3 +71,11 @@ class BIMDocumentProperties(PropertyGroup):
breadcrumbs: CollectionProperty(name="Breadcrumbs", type=StrProperty)
active_document_index: IntProperty(name="Active Document Index")
is_editing: BoolProperty(name="Is Editing", default=False)
+
+ if TYPE_CHECKING:
+ document_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
+ active_document_id: int
+ documents: bpy.types.bpy_prop_collection_idprop[Document]
+ breadcrumbs: bpy.types.bpy_prop_collection_idprop[StrProperty]
+ active_document_index: int
+ is_editing: bool
diff --git a/src/bonsai/bonsai/bim/module/document/ui.py b/src/bonsai/bonsai/bim/module/document/ui.py
index c2aff57ed4..4bd935c8f6 100644
--- a/src/bonsai/bonsai/bim/module/document/ui.py
+++ b/src/bonsai/bonsai/bim/module/document/ui.py
@@ -39,7 +39,7 @@ class BIM_PT_documents(Panel):
if not DocumentData.is_loaded:
DocumentData.load()
- self.props = context.scene.BIMDocumentProperties
+ self.props = tool.Document.get_document_props()
row = self.layout.row(align=True)
row.label(text="{} Documents Found".format(DocumentData.data["total_information"]), icon="FILE")
@@ -102,7 +102,7 @@ class BIM_PT_object_documents(Panel):
obj = context.active_object
self.oprops = obj.BIMObjectProperties
- self.props = context.scene.BIMDocumentProperties
+ self.props = tool.Document.get_document_props()
self.file = tool.Ifc.get()
self.draw_add_ui()
diff --git a/src/bonsai/bonsai/bim/module/drawing/operator.py b/src/bonsai/bonsai/bim/module/drawing/operator.py
index 3d1c3f46ec..6014015995 100644
--- a/src/bonsai/bonsai/bim/module/drawing/operator.py
+++ b/src/bonsai/bonsai/bim/module/drawing/operator.py
@@ -712,7 +712,8 @@ class CreateDrawing(bpy.types.Operator):
}
cached_linework -= edited_guids
- files = {context.scene.BIMProperties.ifc_file: tool.Ifc.get()}
+ bim_props = tool.Blender.get_bim_props()
+ files = {bim_props.ifc_file: tool.Ifc.get()}
props = tool.Project.get_project_props()
for link in props.links:
@@ -730,7 +731,7 @@ class CreateDrawing(bpy.types.Operator):
# Don't use draw.main() just whilst we're prototyping and experimenting
# TODO: hash paths are never used
ifc_hash = hashlib.md5(ifc_path.encode("utf-8")).hexdigest()
- ifc_cache_path = os.path.join(context.scene.BIMProperties.cache_dir, f"{ifc_hash}.h5")
+ ifc_cache_path = os.path.join(bim_props.cache_dir, f"{ifc_hash}.h5")
self.serialiser.setFile(ifc)
drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element, ifc_file=ifc)
@@ -1618,7 +1619,8 @@ class AddDrawingToSheet(bpy.types.Operator, tool.Ifc.Operator):
def poll(cls, context):
props = tool.Drawing.get_document_props()
# Won't be visible in UI anyway.
- if not props.sheets or not context.scene.BIMProperties.data_dir:
+ bim_props = tool.Blender.get_bim_props()
+ if not props.sheets or not bim_props.data_dir:
return False
if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.")
@@ -1722,7 +1724,8 @@ class CreateSheets(bpy.types.Operator, tool.Ifc.Operator):
if not tool.Drawing.get_active_sheet_item(is_sheet=True):
cls.poll_message_set("No sheet selected.")
return False
- return props.sheets and context.scene.BIMProperties.data_dir
+ bim_props = tool.Blender.get_bim_props()
+ return props.sheets and bim_props.data_dir
def invoke(self, context, event):
# opening all sheets on shift+click
@@ -2523,7 +2526,8 @@ class AddScheduleToSheet(bpy.types.Operator, tool.Ifc.Operator):
if not props.schedules:
cls.poll_message_set("No schedule selected.")
return False
- return props.schedules and props.sheets and context.scene.BIMProperties.data_dir
+ bim_props = tool.Blender.get_bim_props()
+ return props.schedules and props.sheets and bim_props.data_dir
def _execute(self, context):
props = tool.Drawing.get_document_props()
@@ -2589,7 +2593,8 @@ class AddReferenceToSheet(bpy.types.Operator, tool.Ifc.Operator):
if not props.references:
cls.poll_message_set("No reference selected.")
return False
- return props.references and props.sheets and context.scene.BIMProperties.data_dir
+ bim_props = tool.Blender.get_bim_props()
+ return props.references and props.sheets and bim_props.data_dir
def _execute(self, context):
props = tool.Drawing.get_document_props()
diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py
index 3408bd8baa..b91e0d8158 100644
--- a/src/bonsai/bonsai/bim/module/project/operator.py
+++ b/src/bonsai/bonsai/bim/module/project/operator.py
@@ -76,34 +76,35 @@ class NewProject(bpy.types.Operator):
def execute(self, context):
bpy.ops.wm.read_homefile()
pprops = tool.Project.get_project_props()
+ bim_props = tool.Blender.get_bim_props()
if self.preset == "metric_m":
pprops.export_schema = "IFC4"
bpy.context.scene.unit_settings.system = "METRIC"
bpy.context.scene.unit_settings.length_unit = "METERS"
- bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE"
- bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE"
+ bim_props.area_unit = "SQUARE_METRE"
+ bim_props.volume_unit = "CUBIC_METRE"
pprops.template_file = "0"
elif self.preset == "metric_mm":
pprops.export_schema = "IFC4"
bpy.context.scene.unit_settings.system = "METRIC"
bpy.context.scene.unit_settings.length_unit = "MILLIMETERS"
- bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE"
- bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE"
+ bim_props.area_unit = "SQUARE_METRE"
+ bim_props.volume_unit = "CUBIC_METRE"
pprops.template_file = "0"
elif self.preset == "imperial_ft":
pprops.export_schema = "IFC4"
bpy.context.scene.unit_settings.system = "IMPERIAL"
bpy.context.scene.unit_settings.length_unit = "FEET"
- bpy.context.scene.BIMProperties.area_unit = "square foot"
- bpy.context.scene.BIMProperties.volume_unit = "cubic foot"
+ bim_props.area_unit = "square foot"
+ bim_props.volume_unit = "cubic foot"
pprops.template_file = "0"
elif self.preset == "demo":
pprops.export_schema = "IFC4"
bpy.context.scene.unit_settings.system = "METRIC"
bpy.context.scene.unit_settings.length_unit = "MILLIMETERS"
- bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE"
- bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE"
+ bim_props.area_unit = "SQUARE_METRE"
+ bim_props.volume_unit = "CUBIC_METRE"
pprops.template_file = "IFC4 Demo Template.ifc"
if self.preset != "wizard":
@@ -979,7 +980,8 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
if not self.is_advanced and not self.should_start_fresh_session:
bpy.ops.bim.convert_to_blender()
- context.scene.BIMProperties.ifc_file = filepath
+ bim_props = tool.Blender.get_bim_props()
+ bim_props.ifc_file = filepath
if not tool.Ifc.get():
self.report(
{"ERROR"},
@@ -1039,13 +1041,15 @@ class RevertProject(bpy.types.Operator, IFCFileSelector):
@classmethod
def poll(cls, context):
- if not context.scene.BIMProperties.ifc_file:
+ props = tool.Blender.get_bim_props()
+ if not props.ifc_file:
cls.poll_message_set("IFC project need to be loaded and saved on the disk.")
return False
return True
def execute(self, context):
- bpy.ops.bim.load_project(should_start_fresh_session=True, filepath=context.scene.BIMProperties.ifc_file)
+ props = tool.Blender.get_bim_props()
+ bpy.ops.bim.load_project(should_start_fresh_session=True, filepath=props.ifc_file)
return {"FINISHED"}
@@ -1070,7 +1074,8 @@ class LoadProjectElements(bpy.types.Operator):
filemode="a",
level=logging.DEBUG,
)
- settings = import_ifc.IfcImportSettings.factory(context, context.scene.BIMProperties.ifc_file, logger)
+ props = tool.Blender.get_bim_props()
+ settings = import_ifc.IfcImportSettings.factory(context, props.ifc_file, logger)
settings.has_filter = self.props.filter_mode != "NONE"
settings.should_filter_spatial_elements = self.props.should_filter_spatial_elements
if self.props.filter_mode == "DECOMPOSITION":
@@ -1560,7 +1565,8 @@ class ExportIFC(bpy.types.Operator):
return {"FINISHED"}
self.use_relative_path = tool.Project.get_project_props().use_relative_project_path
- if (filepath := context.scene.BIMProperties.ifc_file) and not self.should_save_as:
+ props = tool.Blender.get_bim_props()
+ if (filepath := props.ifc_file) and not self.should_save_as:
self.filepath = str(tool.Blender.ensure_blender_path_is_abs(Path(filepath)))
return self.execute(context)
if not self.filepath:
@@ -1621,7 +1627,6 @@ class ExportIFC(bpy.types.Operator):
print("Export finished in {:.2f} seconds".format(time.time() - start))
# New project created in Bonsai should be in recent projects too.
tool.Project.add_recent_ifc_project(Path(output_file))
- scene = context.scene
props = tool.Drawing.get_document_props()
if not props.ifc_files:
new = props.ifc_files.add()
@@ -1629,12 +1634,13 @@ class ExportIFC(bpy.types.Operator):
props = tool.Project.get_project_props()
if props.use_relative_project_path and bpy.data.is_saved:
output_file = os.path.relpath(output_file, bpy.path.abspath("//"))
- if scene.BIMProperties.ifc_file != output_file and extension not in ("ifczip", "ifcjson"):
- scene.BIMProperties.ifc_file = output_file
+ bim_props = tool.Blender.get_bim_props()
+ if bim_props.ifc_file != output_file and extension not in ("ifczip", "ifcjson"):
+ bim_props.ifc_file = output_file
save_blend_file = bool(bpy.data.is_saved and bpy.data.is_dirty and bpy.data.filepath)
if save_blend_file:
bpy.ops.wm.save_mainfile(filepath=bpy.data.filepath)
- bpy.context.scene.BIMProperties.is_dirty = False
+ bim_props.is_dirty = False
bonsai.bim.handler.refresh_ui_data()
self.report(
{"INFO"},
diff --git a/src/bonsai/bonsai/bim/module/project/ui.py b/src/bonsai/bonsai/bim/module/project/ui.py
index e49cdb8910..c5d390c780 100644
--- a/src/bonsai/bonsai/bim/module/project/ui.py
+++ b/src/bonsai/bonsai/bim/module/project/ui.py
@@ -28,7 +28,7 @@ from bonsai.bim.module.project.data import ProjectData, LinksData
from typing import TYPE_CHECKING
if TYPE_CHECKING:
- from bonsai.bim.module.project.prop import LibraryElement, BIMProjectProperties
+ from bonsai.bim.module.project.prop import LibraryElement, BIMProjectProperties, FilterCategory, Link
def file_import_menu(self, context):
@@ -151,7 +151,7 @@ class BIM_PT_project(Panel):
self.layout.use_property_decorate = False
self.layout.use_property_split = True
- props = context.scene.BIMProperties
+ props = tool.Blender.get_bim_props()
pprops = self.props = tool.Project.get_project_props()
self.file = tool.Ifc.get()
if pprops.is_loading:
@@ -305,7 +305,7 @@ class BIM_PT_project(Panel):
def draw_loaded_project_ui(self, context):
# file name row
- props = context.scene.BIMProperties
+ props = tool.Blender.get_bim_props()
file_name_row = self.layout.row(align=True)
file_name_row.label(text=os.path.basename(props.ifc_file), icon="FILE")
self.draw_editing_buttons(context, file_name_row)
@@ -315,7 +315,7 @@ class BIM_PT_project(Panel):
# file path row and actions section
row = self.layout.row(align=True)
- if context.scene.BIMProperties.is_dirty:
+ if props.is_dirty:
row.label(text="Saved*", icon="EXPORT")
else:
row.label(text="Saved", icon="EXPORT")
@@ -339,7 +339,7 @@ class BIM_PT_new_project_wizard(Panel):
self.layout.use_property_decorate = False
self.layout.use_property_split = True
- props = context.scene.BIMProperties
+ props = tool.Blender.get_bim_props()
pprops = tool.Project.get_project_props()
prop_with_search(self.layout, pprops, "export_schema")
row = self.layout.row()
@@ -542,7 +542,16 @@ class BIM_UL_library(UIList):
class BIM_UL_filter_categories(UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
+ def draw_item(
+ self,
+ context,
+ layout: bpy.types.UILayout,
+ data: BIMProjectProperties,
+ item: FilterCategory,
+ icon,
+ active_data,
+ active_propname,
+ ):
if item:
row = layout.row(align=True)
row.label(text=f"{item.name} ({item.total_elements})")
@@ -556,7 +565,17 @@ class BIM_UL_filter_categories(UIList):
class BIM_UL_links(UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
+ def draw_item(
+ self,
+ context,
+ layout: bpy.types.UILayout,
+ data: BIMProjectProperties,
+ item: Link,
+ icon,
+ active_data,
+ active_propname,
+ index,
+ ):
if item:
row = layout.row(align=True)
if item.is_loaded:
diff --git a/src/bonsai/bonsai/bim/module/search/operator.py b/src/bonsai/bonsai/bim/module/search/operator.py
index 4022c1d5ff..32d3729d58 100644
--- a/src/bonsai/bonsai/bim/module/search/operator.py
+++ b/src/bonsai/bonsai/bim/module/search/operator.py
@@ -39,6 +39,7 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
+from typing import TYPE_CHECKING, Literal, get_args
class AddFilterGroup(Operator):
@@ -144,13 +145,19 @@ class Search(Operator):
bl_idname = "bim.search"
bl_label = "Search"
- property_group: bpy.props.StringProperty(name="Property Group", default="")
+ PropertyGroupType = Literal["CsvProperties", "BIMSearchProperties"]
+ property_group: bpy.props.EnumProperty(
+ name="Property Group", items=[(i, i, "") for i in get_args(PropertyGroupType)]
+ )
+
+ if TYPE_CHECKING:
+ property_group: PropertyGroupType
def execute(self, context):
if self.property_group == "CsvProperties":
props = context.scene.CsvProperties
elif self.property_group == "BIMSearchProperties":
- props = context.scene.BIMSearchProperties
+ props = tool.Search.get_search_props()
else:
raise Exception(f"bim.search - unexpected property group name '{self.property_group}'.")
@@ -213,11 +220,12 @@ class LoadSearch(Operator, tool.Ifc.Operator):
def _execute(self, context):
filter_groups = tool.Search.get_filter_groups(self.module)
- group = tool.Ifc.get().by_id(int(context.scene.BIMSearchProperties.saved_searches))
+ props = tool.Search.get_search_props()
+ group = tool.Ifc.get().by_id(int(props.saved_searches))
tool.Search.import_filter_query(tool.Search.get_group_query(group), filter_groups)
def draw(self, context):
- props = context.scene.BIMSearchProperties
+ props = tool.Search.get_search_props()
row = self.layout.row()
row.prop(props, "saved_searches", text="")
@@ -239,7 +247,7 @@ class ColourByProperty(Operator):
return result
def _execute(self, context):
- props = context.scene.BIMSearchProperties
+ props = tool.Search.get_search_props()
query = props.colourscheme_query if props.colourscheme_key == "QUERY" else props.colourscheme_key
if not query:
@@ -358,11 +366,11 @@ class SelectByProperty(Operator):
@classmethod
def poll(cls, context):
- props = context.scene.BIMSearchProperties
+ props = tool.Search.get_search_props()
return props.active_colourscheme_index < len(props.colourscheme)
def execute(self, context):
- props = context.scene.BIMSearchProperties
+ props = tool.Search.get_search_props()
query = props.colourscheme_query if props.colourscheme_key == "QUERY" else props.colourscheme_key
if not query:
@@ -420,7 +428,7 @@ class SaveColourscheme(Operator, tool.Ifc.Operator):
if not self.name:
return
- props = context.scene.BIMSearchProperties
+ props = tool.Search.get_search_props()
query = props.colourscheme_query
group = [g for g in tool.Ifc.get().by_type("IfcGroup") if g.Name == self.name]
@@ -446,7 +454,7 @@ class LoadColourscheme(Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
- props = context.scene.BIMSearchProperties
+ props = tool.Search.get_search_props()
group = tool.Ifc.get().by_id(int(props.saved_colourschemes))
description = json.loads(group.Description)
props.colourscheme_query = description.get("colourscheme_query")
@@ -458,7 +466,7 @@ class LoadColourscheme(Operator, tool.Ifc.Operator):
new.colour = data["colour"]
def draw(self, context):
- props = context.scene.BIMSearchProperties
+ props = tool.Search.get_search_props()
row = self.layout.row()
row.prop(props, "saved_colourschemes", text="")
@@ -549,7 +557,7 @@ class ResetObjectColours(Operator):
def execute(self, context):
for obj in context.visible_objects:
obj.color = (1, 1, 1, 1)
- props = context.scene.BIMSearchProperties
+ props = tool.Search.get_search_props()
props.colourscheme.clear()
return {"FINISHED"}
@@ -562,7 +570,7 @@ class ToggleFilterSelection(Operator):
action: EnumProperty(items=(("SELECT", "Select", ""), ("DESELECT", "Deselect", "")))
def execute(self, context):
- props = bpy.context.scene.BIMSearchProperties
+ props = tool.Search.get_search_props()
self.selecting_actionbool = self.action == "SELECT"
if props.filter_type == "CLASSES":
for ifc_class in props.filter_classes:
@@ -587,7 +595,7 @@ class ActivateIfcClassFilter(Operator):
return True
def invoke(self, context, event):
- props = bpy.context.scene.BIMSearchProperties
+ props = tool.Search.get_search_props()
props.filter_classes.clear()
ifc_types = {}
for obj in context.selected_objects:
@@ -606,18 +614,20 @@ class ActivateIfcClassFilter(Operator):
return context.window_manager.invoke_props_dialog(self, width=250)
def execute(self, context):
- bpy.context.scene.BIMSearchProperties.filter_classes.clear()
+ props = tool.Search.get_search_props()
+ props.filter_classes.clear()
return {"FINISHED"}
def draw(self, context):
+ props = tool.Search.get_search_props()
self.layout.template_list(
"BIM_UL_ifc_class_filter",
"",
- context.scene.BIMSearchProperties,
+ props,
"filter_classes",
- context.scene.BIMSearchProperties,
+ props,
"filter_classes_index",
- rows=min(len(bpy.context.scene.BIMSearchProperties.filter_classes), 20),
+ rows=min(len(props.filter_classes), 20),
)
row = self.layout.row(align=True)
row.operator("bim.toggle_filter_selection", text="Select All").action = "SELECT"
@@ -638,7 +648,7 @@ class ActivateContainerFilter(Operator):
return True
def invoke(self, context, event):
- props = bpy.context.scene.BIMSearchProperties
+ props = tool.Search.get_search_props()
props.filter_container.clear()
containers = {}
@@ -661,18 +671,20 @@ class ActivateContainerFilter(Operator):
return context.window_manager.invoke_props_dialog(self, width=250)
def execute(self, context):
- bpy.context.scene.BIMSearchProperties.filter_container.clear()
+ props = tool.Search.get_search_props()
+ props.filter_container.clear()
return {"FINISHED"}
def draw(self, context):
+ props = tool.Search.get_search_props()
self.layout.template_list(
"BIM_UL_ifc_building_storey_filter",
"",
- context.scene.BIMSearchProperties,
+ props,
"filter_container",
- context.scene.BIMSearchProperties,
+ props,
"filter_container_index",
- rows=min(len(bpy.context.scene.BIMSearchProperties.filter_container), 20),
+ rows=min(len(props.filter_container), 20),
)
row = self.layout.row(align=True)
row.operator("bim.toggle_filter_selection", text="Select All").action = "SELECT"
diff --git a/src/bonsai/bonsai/bim/module/search/prop.py b/src/bonsai/bonsai/bim/module/search/prop.py
index 08cfb46d8f..61cff222d3 100644
--- a/src/bonsai/bonsai/bim/module/search/prop.py
+++ b/src/bonsai/bonsai/bim/module/search/prop.py
@@ -33,33 +33,34 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
+from typing import TYPE_CHECKING
-def get_element_key(self, context):
+def get_element_key(self: "BIMSearchProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
if not SelectSimilarData.is_loaded:
SelectSimilarData.load()
return SelectSimilarData.data["element_key"]
-def get_colourscheme_key(self, context):
+def get_colourscheme_key(self: "BIMSearchProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
if not ColourByPropertyData.is_loaded:
ColourByPropertyData.load()
return ColourByPropertyData.data["colourscheme_key"]
-def get_saved_searches(self, context):
+def get_saved_searches(self: "BIMSearchProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
if not SearchData.is_loaded:
SearchData.load()
return SearchData.data["saved_searches"]
-def get_saved_colourschemes(self, context):
+def get_saved_colourschemes(self: "BIMSearchProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
if not ColourByPropertyData.is_loaded:
ColourByPropertyData.load()
return ColourByPropertyData.data["saved_colourschemes"]
-def update_is_class_selected(self, context):
+def update_is_class_selected(self: "BIMFilterClasses", context: bpy.types.Context) -> None:
if self.is_selected:
for obj in self.unselected_objects:
obj.obj.select_set(True)
@@ -73,7 +74,7 @@ def update_is_class_selected(self, context):
new.obj = obj
-def update_is_container_selected(self, context):
+def update_is_container_selected(self: "BIMFilterBuildingStoreys", context: bpy.types.Context) -> None:
if self.is_selected:
for obj in self.unselected_objects:
obj.obj.select_set(True)
@@ -87,15 +88,15 @@ def update_is_container_selected(self, context):
new.obj = obj
-def update_show_flat_colours(self, context):
+def update_show_flat_colours(self: "BIMSearchProperties", context: bpy.types.Context) -> None:
+ space = tool.Blender.get_view3d_space()
+ assert space
if self.show_flat_colours:
- space = tool.Blender.get_view3d_space()
space.shading.light = "FLAT"
space.shading.color_type = "OBJECT"
space.shading.show_object_outline = True
space.shading.show_cavity = True
else:
- space = tool.Blender.get_view3d_space()
space.shading.type = "SOLID"
space.shading.light = "STUDIO"
space.shading.show_object_outline = True
@@ -108,6 +109,11 @@ class BIMFilterClasses(PropertyGroup):
total: IntProperty(name="Total")
unselected_objects: CollectionProperty(type=ObjProperty, name="Unfiltered Objects")
+ if TYPE_CHECKING:
+ is_selected: bool
+ total: int
+ unselected_objects: bpy.types.bpy_prop_collection_idprop[ObjProperty]
+
class BIMFilterBuildingStoreys(PropertyGroup):
name: StringProperty(name="Name")
@@ -115,12 +121,21 @@ class BIMFilterBuildingStoreys(PropertyGroup):
total: IntProperty(name="Total")
unselected_objects: CollectionProperty(type=ObjProperty, name="Unfiltered Objects")
+ if TYPE_CHECKING:
+ is_selected: bool
+ total: int
+ unselected_objects: bpy.types.bpy_prop_collection_idprop[ObjProperty]
+
class BIMColour(PropertyGroup):
name: StringProperty(name="Name")
total: IntProperty(name="Total")
colour: FloatVectorProperty(name="Colour", subtype="COLOR", default=(1, 0, 0), min=0.0, max=1.0)
+ if TYPE_CHECKING:
+ total: int
+ colour: tuple[float, float, float]
+
class BIMSearchProperties(PropertyGroup):
element_key: EnumProperty(items=get_element_key, name="Element Key")
@@ -189,6 +204,29 @@ class BIMSearchProperties(PropertyGroup):
filter_container_index: IntProperty(name="Filter Level Index")
show_flat_colours: BoolProperty(name="Flat Colours", default=False, update=update_show_flat_colours)
+ if TYPE_CHECKING:
+ element_key: str
+ filter_query: str
+ filter_groups: bpy.types.bpy_prop_collection_idprop[BIMFilterGroup]
+ facet: str
+ saved_searches: str
+ saved_colourschemes: str
+ colourscheme_key: str
+ colourscheme_query: str
+ palette: str
+ min_mode: Literal["AUTO", "MANUAL"]
+ max_mode: Literal["AUTO", "MANUAL"]
+ min_value: float
+ max_value: float
+ colourscheme: bpy.types.bpy_prop_collection_idprop[BIMColour]
+ active_colourscheme_index: int
+ filter_type: str
+ filter_classes: bpy.types.bpy_prop_collection_idprop[BIMFilterClasses]
+ filter_classes_index: int
+ filter_container: bpy.types.bpy_prop_collection_idprop[BIMFilterBuildingStoreys]
+ filter_container_index: int
+ show_flat_colours: bool
+
def get_classes(self, ifc_product):
declaration = tool.Ifc.schema().declaration_by_name(ifc_product)
diff --git a/src/bonsai/bonsai/bim/module/search/ui.py b/src/bonsai/bonsai/bim/module/search/ui.py
index ce0b346027..a9254415e7 100644
--- a/src/bonsai/bonsai/bim/module/search/ui.py
+++ b/src/bonsai/bonsai/bim/module/search/ui.py
@@ -17,6 +17,7 @@
# along with Bonsai. If not, see .
import bpy
+import bonsai.tool as tool
import bonsai.bim.helper
from bpy.types import Panel
from bonsai.bim.module.search.data import SearchData, ColourByPropertyData, SelectSimilarData
@@ -34,7 +35,7 @@ class BIM_PT_search(Panel):
if not SearchData.is_loaded:
SearchData.load()
- props = context.scene.BIMSearchProperties
+ props = tool.Search.get_search_props()
bonsai.bim.helper.draw_filter(self.layout, props.filter_groups, SearchData, "search")
@@ -73,7 +74,7 @@ class BIM_PT_colour_by_property(Panel):
if not ColourByPropertyData.is_loaded:
ColourByPropertyData.load()
- props = context.scene.BIMSearchProperties
+ props = tool.Search.get_search_props()
row = self.layout.row(align=True)
row.label(text=f"{len(ColourByPropertyData.data['saved_colourschemes'])} Saved Colourschemes")
@@ -127,7 +128,7 @@ class BIM_PT_select_similar(Panel):
if not SelectSimilarData.is_loaded:
SelectSimilarData.load()
- props = context.scene.BIMSearchProperties
+ props = tool.Search.get_search_props()
if SelectSimilarData.data["element_key"]:
row = self.layout.row(align=True)
diff --git a/src/bonsai/bonsai/bim/module/unit/prop.py b/src/bonsai/bonsai/bim/module/unit/prop.py
index b01fab4ddf..f0f04e16ae 100644
--- a/src/bonsai/bonsai/bim/module/unit/prop.py
+++ b/src/bonsai/bonsai/bim/module/unit/prop.py
@@ -30,21 +30,22 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
+from typing import TYPE_CHECKING
-def get_unit_classes(self, context):
+def get_unit_classes(self: "BIMUnitProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
if not UnitsData.is_loaded:
UnitsData.load()
return UnitsData.data["unit_classes"]
-def get_conversion_unit_types(self, context):
+def get_conversion_unit_types(self: "BIMUnitProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
if not UnitsData.is_loaded:
UnitsData.load()
return UnitsData.data["conversion_unit_types"]
-def get_named_unit_types(self, context):
+def get_named_unit_types(self: "BIMUnitProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
if not UnitsData.is_loaded:
UnitsData.load()
return UnitsData.data["named_unit_types"]
@@ -57,6 +58,12 @@ class Unit(PropertyGroup):
ifc_class: StringProperty(name="IFC Class")
ifc_definition_id: IntProperty(name="IFC Definition ID")
+ if TYPE_CHECKING:
+ unit_type: str
+ is_assigned: bool
+ ifc_class: str
+ ifc_definition_id: int
+
class BIMUnitProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing")
@@ -67,3 +74,13 @@ class BIMUnitProperties(PropertyGroup):
conversion_unit_types: EnumProperty(items=get_conversion_unit_types, name="Conversion Unit Types")
named_unit_types: EnumProperty(items=get_named_unit_types, name="Named Unit Types")
unit_attributes: CollectionProperty(name="Unit Attributes", type=Attribute)
+
+ if TYPE_CHECKING:
+ is_editing: bool
+ units: bpy.types.bpy_prop_collection_idprop[Unit]
+ active_unit_index: int
+ active_unit_id: int
+ unit_classes: str
+ conversion_unit_types: str
+ named_unit_types: str
+ unit_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
diff --git a/src/bonsai/bonsai/bim/module/unit/ui.py b/src/bonsai/bonsai/bim/module/unit/ui.py
index 83637845f7..f7b55a96e3 100644
--- a/src/bonsai/bonsai/bim/module/unit/ui.py
+++ b/src/bonsai/bonsai/bim/module/unit/ui.py
@@ -41,7 +41,7 @@ class BIM_PT_units(Panel):
if not UnitsData.is_loaded:
UnitsData.load()
- self.props = context.scene.BIMUnitProperties
+ self.props = tool.Unit.get_unit_props()
row = self.layout.row(align=True)
row.label(text="{} Units Found".format(UnitsData.data["total_units"]), icon="SNAP_GRID")
@@ -103,7 +103,7 @@ class BIM_PT_units(Panel):
class BIM_UL_units(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
- props = context.scene.BIMUnitProperties
+ props = tool.Unit.get_unit_props()
if item:
icon = "MOD_MESHDEFORM"
if item.ifc_class == "IfcSIUnit":
diff --git a/src/bonsai/bonsai/bim/module/web/data.py b/src/bonsai/bonsai/bim/module/web/data.py
index fa94ad26d8..db939cadfa 100644
--- a/src/bonsai/bonsai/bim/module/web/data.py
+++ b/src/bonsai/bonsai/bim/module/web/data.py
@@ -39,9 +39,11 @@ class WebData:
@classmethod
def get_ifc_file_name(cls):
- filename = os.path.basename(bpy.context.scene.BIMProperties.ifc_file)
+ props = tool.Blender.get_bim_props()
+ filename = os.path.basename(props.ifc_file)
return filename
@classmethod
def get_is_dirty(cls):
- return bpy.context.scene.BIMProperties.is_dirty
+ props = tool.Blender.get_bim_props()
+ return props.is_dirty
diff --git a/src/bonsai/bonsai/bim/operator.py b/src/bonsai/bonsai/bim/operator.py
index 2bd6f445f4..f32cd2ef65 100644
--- a/src/bonsai/bonsai/bim/operator.py
+++ b/src/bonsai/bonsai/bim/operator.py
@@ -130,7 +130,8 @@ class CloseBlendWarning(bpy.types.Operator):
bl_label = "Close Blend Warning"
def execute(self, context):
- bpy.context.scene.BIMProperties.has_blend_warning = False
+ props = tool.Blender.get_bim_props()
+ props.has_blend_warning = False
return {"FINISHED"}
def draw(self, context):
@@ -213,11 +214,13 @@ class SelectIfcFile(bpy.types.Operator, IFCFileSelector):
def execute(self, context):
if self.is_existing_ifc_file():
- context.scene.BIMProperties.ifc_file = self.get_filepath()
+ props = tool.Blender.get_bim_props()
+ props.ifc_file = self.get_filepath()
return {"FINISHED"}
def invoke(self, context, event):
- filepath = Path(context.scene.BIMProperties.ifc_file)
+ props = tool.Blender.get_bim_props()
+ filepath = Path(props.ifc_file)
res = tool.Blender.operator_invoke_filepath_hotkeys(self, context, event, filepath)
if res is not None:
return res
@@ -575,7 +578,8 @@ class BIM_OT_add_section_plane(bpy.types.Operator):
backfacing.location = mix_backfacing.location + Vector((-200, 200))
emission = nodes.new(type="ShaderNodeEmission")
- emission.inputs[0].default_value = list(context.scene.BIMProperties.section_plane_colour) + [1]
+ props = tool.Blender.get_bim_props()
+ emission.inputs[0].default_value = list(props.section_plane_colour) + [1]
emission.location = mix_backfacing.location - Vector((200, 150))
cut_obj = nodes.new(type="ShaderNodeTexCoord")
@@ -639,7 +643,8 @@ class BIM_OT_add_section_plane(bpy.types.Operator):
material = bpy.data.materials.new("Section Override")
material.use_nodes = True
- if context.scene.BIMProperties.should_section_selected_objects:
+ props = tool.Blender.get_bim_props()
+ if props.should_section_selected_objects:
objects = list(context.selected_objects)
else:
objects = list(context.visible_objects)
@@ -814,7 +819,8 @@ class ReloadIfcFile(bpy.types.Operator, tool.Ifc.Operator):
settings.logger.info("Import finished in {:.2f} seconds".format(time.time() - start))
print("Import finished in {:.2f} seconds".format(time.time() - start))
- context.scene.BIMProperties.ifc_file = self.filepath
+ bim_props = tool.Blender.get_bim_props()
+ bim_props.ifc_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -852,7 +858,8 @@ class FetchObjectPassport(bpy.types.Operator):
def execute(self, context):
# TODO: this is dead code, awaiting reimplementation. See #1222.
for reference in context.active_object.BIMObjectProperties.document_references:
- reference = context.scene.BIMProperties.document_references[reference.name]
+ bim_props = tool.Blender.get_bim_props()
+ reference = bim_props.document_references[reference.name]
if reference.location[-6:] == ".blend":
self.fetch_blender(reference, context)
return {"FINISHED"}
diff --git a/src/bonsai/bonsai/bim/prop.py b/src/bonsai/bonsai/bim/prop.py
index c9b8c759d8..e335af9a0e 100644
--- a/src/bonsai/bonsai/bim/prop.py
+++ b/src/bonsai/bonsai/bim/prop.py
@@ -119,23 +119,27 @@ def get_attribute_enum_values(prop: "Attribute", context: bpy.types.Context) ->
def update_schema_dir(self: "BIMProperties", context: bpy.types.Context) -> None:
import bonsai.bim.schema
- bonsai.bim.schema.ifc.schema_dir = context.scene.BIMProperties.schema_dir
+ bim_props = tool.Blender.get_bim_props()
+ bonsai.bim.schema.ifc.schema_dir = bim_props.schema_dir
def update_data_dir(self: "BIMProperties", context: bpy.types.Context) -> None:
import bonsai.bim.schema
- bonsai.bim.schema.ifc.data_dir = context.scene.BIMProperties.data_dir
+ bim_props = tool.Blender.get_bim_props()
+ bonsai.bim.schema.ifc.data_dir = bim_props.data_dir
def update_cache_dir(self: "BIMProperties", context: bpy.types.Context) -> None:
import bonsai.bim.schema
- bonsai.bim.schema.ifc.cache_dir = context.scene.BIMProperties.cache_dir
+ bim_props = tool.Blender.get_bim_props()
+ bonsai.bim.schema.ifc.cache_dir = bim_props.cache_dir
def update_ifc_file(self: "BIMProperties", context: bpy.types.Context) -> None:
- if context.scene.BIMProperties.ifc_file:
+ bim_props = tool.Blender.get_bim_props()
+ if bim_props.ifc_file:
bonsai.bim.handler.loadIfcStore(context.scene)
@@ -331,10 +335,31 @@ class Attribute(PropertyGroup):
if TYPE_CHECKING:
name: str
+ display_name: str
description: str
ifc_class: str
data_type: AttributeDataType
special_type: AttributeSpecialType
+ string_value: str
+ bool_value: bool
+ int_value: int
+ float_value: float
+ length_value: float
+ enum_items: str
+ enum_descriptions: bpy.types.bpy_prop_collection_idprop[StrProperty]
+ enum_value: str
+ filepath_value: MultipleFileSelect
+ filter_glob: str
+ is_null: bool
+ is_optional: bool
+ is_uri: bool
+ is_selected: bool
+ value_min: float
+ value_min_constraint: bool
+ value_max: float
+ value_max_constraint: bool
+ metadata: str
+ update: str
def get_value(self) -> Union[str, float, int, bool, None]:
if self.is_optional and self.is_null:
@@ -439,6 +464,13 @@ class BIMAreaProperties(PropertyGroup):
active_tab: BoolProperty(default=True, name="Active Tab")
inactive_tab: BoolProperty(default=False, name="Inactive Tab")
+ if TYPE_CHECKING:
+ tab: str
+ previous_tab: str
+ alt_tab: str
+ active_tab: bool
+ inactive_tab: bool
+
# BIMAreaProperties exists per area and is setup on load post. However, for new
# or temporary screens, they may not be setup yet, so this global tab
@@ -449,6 +481,11 @@ class BIMTabProperties(PropertyGroup):
active_tab: BoolProperty(default=True, name="Active Tab")
inactive_tab: BoolProperty(default=False, name="Inactive Tab")
+ if TYPE_CHECKING:
+ tab: str
+ active_tab: bool
+ inactive_tab: bool
+
class BIMProperties(PropertyGroup):
is_dirty: BoolProperty(name="Is Dirty", default=False)
@@ -517,6 +554,21 @@ class BIMProperties(PropertyGroup):
name="IFC Volume Unit",
)
+ if TYPE_CHECKING:
+ is_dirty: bool
+ schema_dir: str
+ data_dir: str
+ cache_dir: str
+ has_blend_warning: bool
+ pset_dir: str
+ ifc_file: str
+ last_transaction: str
+ should_section_selected_objects: bool
+ section_plane_colour: tuple[float, float, float]
+ section_line_decorator_width: float
+ area_unit: str
+ volume_unit: str
+
class IfcParameter(PropertyGroup):
name: StringProperty(name="Name")
@@ -547,6 +599,7 @@ class PsetQto(PropertyGroup):
class GlobalId(PropertyGroup):
name: StringProperty(name="Name")
+ ifc_definition_id: IntProperty(name="IFC Definition ID")
class BIMCollectionProperties(PropertyGroup):
@@ -556,11 +609,14 @@ class BIMCollectionProperties(PropertyGroup):
obj: Union[bpy.types.Object, None]
+BlenderOffsetType = Literal["NONE", "OBJECT_PLACEMENT", "CARTESIAN_POINT", "NOT_APPLICABLE"]
+
+
class BIMObjectProperties(PropertyGroup):
collection: PointerProperty(type=bpy.types.Collection)
ifc_definition_id: IntProperty(name="IFC Definition ID")
blender_offset_type: EnumProperty(
- items=[(o, o, "") for o in ["NONE", "OBJECT_PLACEMENT", "CARTESIAN_POINT", "NOT_APPLICABLE"]],
+ items=[(o, o, "") for o in get_args(BlenderOffsetType)],
name="Blender Offset",
default="NONE",
)
@@ -570,6 +626,16 @@ class BIMObjectProperties(PropertyGroup):
location_checksum: StringProperty(name="Location Checksum")
rotation_checksum: StringProperty(name="Rotation Checksum")
+ if TYPE_CHECKING:
+ collection: Union[bpy.types.Collection, None]
+ ifc_definition_id: int
+ blender_offset_type: BlenderOffsetType
+ cartesian_point_offset: str
+ is_reassigning_class: bool
+ is_renaming: bool
+ location_checksum: str
+ rotation_checksum: str
+
def get_profiles(self: "BIMMeshProperties", context: bpy.types.Context):
from bonsai.bim.module.model.data import ItemData
diff --git a/src/bonsai/bonsai/bim/schema.py b/src/bonsai/bonsai/bim/schema.py
index 6d67d2e20a..cae1e32146 100644
--- a/src/bonsai/bonsai/bim/schema.py
+++ b/src/bonsai/bonsai/bim/schema.py
@@ -19,6 +19,8 @@
import ifcopenshell
import ifcopenshell.util.pset
import bonsai.tool as tool
+import bpy
+import bpy_restrict_state
class IfcSchema:
@@ -47,10 +49,16 @@ class IfcSchema:
self.psetqto.get_applicable.cache_clear()
self.psetqto.get_applicable_names.cache_clear()
self.psetqto.get_by_name.cache_clear()
+
+ # During register we cannot access the context either way.
+ if isinstance(bpy.context, bpy_restrict_state._RestrictContext):
+ return
for path in tool.Blender.get_data_dir_paths("pset", "*.ifc"):
self.psetqto.templates.append(ifcopenshell.open(path))
+# TODO: do we really need to load it on module import?
+# Loading it on IFC load should be enough.
ifc = IfcSchema()
diff --git a/src/bonsai/bonsai/bim/ui.py b/src/bonsai/bonsai/bim/ui.py
index a77650a72b..4223706b8a 100644
--- a/src/bonsai/bonsai/bim/ui.py
+++ b/src/bonsai/bonsai/bim/ui.py
@@ -121,7 +121,7 @@ class BIM_PT_section_plane(Panel):
def draw(self, context):
layout = self.layout
layout.use_property_split = True
- props = context.scene.BIMProperties
+ props = tool.Blender.get_bim_props()
layout.prop(props, "should_section_selected_objects")
layout.prop(props, "section_plane_colour")
@@ -381,12 +381,13 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
layout.prop(props, "occurrence_name_function")
def draw_directories(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
+ props = tool.Blender.get_bim_props()
row = layout.row(align=True)
- row.prop(context.scene.BIMProperties, "data_dir")
+ row.prop(props, "data_dir")
row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "scene.BIMProperties.data_dir"
row = layout.row(align=True)
- row.prop(context.scene.BIMProperties, "cache_dir")
+ row.prop(props, "cache_dir")
row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "scene.BIMProperties.cache_dir"
row = layout.row(align=True)
@@ -394,7 +395,8 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "preferences.tmp_dir"
def draw_drawing_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
- layout.prop(context.scene.BIMProperties, "pset_dir")
+ props = tool.Blender.get_bim_props()
+ layout.prop(props, "pset_dir")
dprops = tool.Drawing.get_document_props()
layout.prop(dprops, "sheets_dir")
layout.prop(dprops, "layouts_dir")
@@ -510,8 +512,8 @@ class BIM_PT_tabs(Panel):
if not tool.Ifc.get():
return
- props = context.scene.BIMProperties
- if props.has_blend_warning:
+ bim_props = tool.Blender.get_bim_props()
+ if bim_props.has_blend_warning:
box = self.layout.box()
box.alert = True
row = box.row(align=True)
@@ -557,11 +559,11 @@ class BIM_PT_tab_new_project_wizard(Panel):
def poll(cls, context):
if not tool.Blender.is_tab(context, "PROJECT"):
return False
- props = context.scene.BIMProperties
+ bim_props = tool.Blender.get_bim_props()
pprops = tool.Project.get_project_props()
if pprops.is_loading:
return False
- elif tool.Ifc.get() or props.ifc_file:
+ elif tool.Ifc.get() or bim_props.ifc_file:
return False
return True
@@ -579,11 +581,11 @@ class BIM_PT_tab_project_info(Panel):
def poll(cls, context):
if not tool.Blender.is_tab(context, "PROJECT"):
return False
- props = context.scene.BIMProperties
+ bim_props = tool.Blender.get_bim_props()
pprops = tool.Project.get_project_props()
if pprops.is_loading:
return True
- elif tool.Ifc.get() or props.ifc_file:
+ elif tool.Ifc.get() or bim_props.ifc_file:
return True
return False
diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py
index b9699de172..a4a2ed4558 100644
--- a/src/bonsai/bonsai/tool/blender.py
+++ b/src/bonsai/bonsai/tool/blender.py
@@ -37,9 +37,12 @@ from mathutils import Vector
from pathlib import Path
from functools import lru_cache
from bonsai.bim.ifc import IFC_CONNECTED_TYPE
-from typing import Any, Optional, Union, Literal, Iterable, Callable, TypeVar, Generator
+from typing import Any, Optional, Union, Literal, Iterable, Callable, TypeVar, Generator, TYPE_CHECKING
from typing_extensions import assert_never
+if TYPE_CHECKING:
+ from bonsai.bim.prop import BIMProperties
+
VIEWPORT_ATTRIBUTES = [
"view_matrix",
@@ -1477,10 +1480,8 @@ class Blender(bonsai.core.tool.Blender):
@classmethod
def get_user_data_dir(cls) -> Path:
- try:
- return Path(bpy.context.scene.BIMProperties.data_dir)
- except AttributeError:
- return Path()
+ props = tool.Blender.get_bim_props()
+ return Path(props.data_dir)
@classmethod
def get_data_dir_path(cls, relative_path: Union[str, Path]) -> Path:
@@ -1538,3 +1539,9 @@ class Blender(bonsai.core.tool.Blender):
dct = {cls.bl_idname: cls.ifc_element_type for cls in (BimTool.__subclasses__())}
return types.MappingProxyType(dct)
+
+ @classmethod
+ def get_bim_props(cls, scene: Optional[bpy.types.Scene] = None) -> BIMProperties:
+ if scene is None:
+ scene = bpy.context.scene
+ return scene.BIMProperties
diff --git a/src/bonsai/bonsai/tool/brick.py b/src/bonsai/bonsai/tool/brick.py
index 4b034f2160..acd8d50b01 100644
--- a/src/bonsai/bonsai/tool/brick.py
+++ b/src/bonsai/bonsai/tool/brick.py
@@ -86,10 +86,11 @@ class Brick(bonsai.core.tool.Brick):
project = tool.Ifc.get().by_type("IfcProject")[0]
ns = Namespace(namespace)
brick_project = ns[project.GlobalId]
+ props = tool.Blender.get_bim_props()
with BrickStore.new_changeset() as cs:
cs.add((brick_project, A, REF.ifcProject))
cs.add((brick_project, REF.ifcProjectID, Literal(project.GlobalId)))
- cs.add((brick_project, REF.ifcFileLocation, Literal(bpy.context.scene.BIMProperties.ifc_file)))
+ cs.add((brick_project, REF.ifcFileLocation, Literal(props.ifc_file)))
if project.Name:
cs.add((brick_project, URIRef("http://www.w3.org/2000/01/rdf-schema#label"), Literal(project.Name)))
return str(brick_project)
diff --git a/src/bonsai/bonsai/tool/debug.py b/src/bonsai/bonsai/tool/debug.py
index ec4490b2c3..7752423ce4 100644
--- a/src/bonsai/bonsai/tool/debug.py
+++ b/src/bonsai/bonsai/tool/debug.py
@@ -54,7 +54,8 @@ class Debug(bonsai.core.tool.Debug):
@classmethod
def purge_hdf5_cache(cls) -> None:
- cache_dir = bpy.context.scene.BIMProperties.cache_dir
+ props = tool.Blender.get_bim_props()
+ cache_dir = props.cache_dir
filelist = [f for f in os.listdir(cache_dir) if f.endswith(".h5")]
for f in filelist:
try:
diff --git a/src/bonsai/bonsai/tool/document.py b/src/bonsai/bonsai/tool/document.py
index 25799948fc..5d1e5eee54 100644
--- a/src/bonsai/bonsai/tool/document.py
+++ b/src/bonsai/bonsai/tool/document.py
@@ -16,56 +16,68 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see .
+from __future__ import annotations
import bpy
import ifcopenshell.util.system
import bonsai.bim.helper
import bonsai.core.tool
import bonsai.tool as tool
-from typing import Any, Union, Sequence
+from typing import Any, Union, TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from bonsai.bim.module.document.prop import BIMDocumentProperties
class Document(bonsai.core.tool.Document):
+ @classmethod
+ def get_document_props(cls) -> BIMDocumentProperties:
+ return bpy.context.scene.BIMDocumentProperties
+
@classmethod
def add_breadcrumb(cls, document: ifcopenshell.entity_instance) -> None:
- props = bpy.context.scene.BIMDocumentProperties
+ props = cls.get_document_props()
new = props.breadcrumbs.add()
new.name = str(document.id())
@classmethod
def clear_breadcrumbs(cls) -> None:
- props = bpy.context.scene.BIMDocumentProperties
+ props = cls.get_document_props()
props.breadcrumbs.clear()
@classmethod
def clear_document_tree(cls) -> None:
- props = bpy.context.scene.BIMDocumentProperties
+ props = cls.get_document_props()
props.documents.clear()
@classmethod
def disable_editing_document(cls) -> None:
- bpy.context.scene.BIMDocumentProperties.active_document_id = 0
+ props = cls.get_document_props()
+ props.active_document_id = 0
@classmethod
def disable_editing_ui(cls) -> None:
- bpy.context.scene.BIMDocumentProperties.is_editing = False
+ props = cls.get_document_props()
+ props.is_editing = False
@classmethod
def enable_editing_ui(cls) -> None:
- bpy.context.scene.BIMDocumentProperties.is_editing = True
+ props = cls.get_document_props()
+ props.is_editing = True
@classmethod
def export_document_attributes(cls) -> dict[str, Any]:
- return bonsai.bim.helper.export_attributes(bpy.context.scene.BIMDocumentProperties.document_attributes)
+ props = cls.get_document_props()
+ return bonsai.bim.helper.export_attributes(props.document_attributes)
@classmethod
def get_active_breadcrumb(cls) -> Union[ifcopenshell.entity_instance, None]:
- props = bpy.context.scene.BIMDocumentProperties
+ props = cls.get_document_props()
if len(props.breadcrumbs):
return tool.Ifc.get().by_id(int(props.breadcrumbs[-1].name))
@classmethod
def import_document_attributes(cls, document: ifcopenshell.entity_instance) -> None:
- props = bpy.context.scene.BIMDocumentProperties
+ props = cls.get_document_props()
props.document_attributes.clear()
def callback(attr_name: str, _, data: dict[str, Any]) -> Union[bool, None]:
@@ -86,7 +98,7 @@ class Document(bonsai.core.tool.Document):
@classmethod
def import_project_documents(cls) -> None:
- props = bpy.context.scene.BIMDocumentProperties
+ props = cls.get_document_props()
props.documents.clear()
project = tool.Ifc.get().by_type("IfcProject")[0]
for rel in project.HasAssociations or []:
@@ -100,7 +112,7 @@ class Document(bonsai.core.tool.Document):
@classmethod
def import_references(cls, document: ifcopenshell.entity_instance) -> None:
- props = bpy.context.scene.BIMDocumentProperties
+ props = cls.get_document_props()
is_ifc2x3 = tool.Ifc.get_schema() == "IFC2X3"
references = cls.get_document_references(document)
for element in references:
@@ -115,7 +127,7 @@ class Document(bonsai.core.tool.Document):
@classmethod
def import_subdocuments(cls, document: ifcopenshell.entity_instance) -> None:
- props = bpy.context.scene.BIMDocumentProperties
+ props = cls.get_document_props()
if document.IsPointer:
for element in document.IsPointer[0].RelatedDocuments or []:
new = props.documents.add()
@@ -130,13 +142,14 @@ class Document(bonsai.core.tool.Document):
@classmethod
def remove_latest_breadcrumb(cls) -> None:
- props = bpy.context.scene.BIMDocumentProperties
+ props = cls.get_document_props()
if len(props.breadcrumbs):
props.breadcrumbs.remove(len(props.breadcrumbs) - 1)
@classmethod
def set_active_document(cls, document: ifcopenshell.entity_instance) -> None:
- bpy.context.scene.BIMDocumentProperties.active_document_id = document.id()
+ props = cls.get_document_props()
+ props.active_document_id = document.id()
@classmethod
def get_document_information_id(cls, document: ifcopenshell.entity_instance) -> Union[str, None]:
diff --git a/src/bonsai/bonsai/tool/ifcgit.py b/src/bonsai/bonsai/tool/ifcgit.py
index 5b0f9b88e5..93475c090c 100644
--- a/src/bonsai/bonsai/tool/ifcgit.py
+++ b/src/bonsai/bonsai/tool/ifcgit.py
@@ -340,8 +340,8 @@ class IfcGit:
@classmethod
def get_revisions_step_ids(cls) -> Union[STEP_IDS, None]:
-
- path_ifc = bpy.data.scenes["Scene"].BIMProperties.ifc_file
+ props = tool.Blender.get_bim_props()
+ path_ifc = tool.Blender.get_bim_props().ifc_file
props = bpy.context.scene.IfcGitProperties
repo = IfcGitRepo.repo
item = props.ifcgit_commits[props.commit_index]
diff --git a/src/bonsai/bonsai/tool/polyline.py b/src/bonsai/bonsai/tool/polyline.py
index db8f4e7922..56eab4a288 100644
--- a/src/bonsai/bonsai/tool/polyline.py
+++ b/src/bonsai/bonsai/tool/polyline.py
@@ -456,7 +456,8 @@ class Polyline(bonsai.core.tool.Polyline):
dprops = tool.Drawing.get_document_props()
precision = dprops.imperial_precision
if is_area:
- area_unit = bpy.context.scene.BIMProperties.area_unit
+ props = tool.Blender.get_bim_props()
+ area_unit = props.area_unit
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get(), unit_type=area_unit)
else:
precision = None
diff --git a/src/bonsai/bonsai/tool/project.py b/src/bonsai/bonsai/tool/project.py
index 6a2bbb0140..3f4d77f0d7 100644
--- a/src/bonsai/bonsai/tool/project.py
+++ b/src/bonsai/bonsai/tool/project.py
@@ -70,7 +70,8 @@ class Project(bonsai.core.tool.Project):
@classmethod
def load_pset_templates(cls):
- pset_dir = tool.Ifc.resolve_uri(bpy.context.scene.BIMProperties.pset_dir)
+ props = tool.Blender.get_bim_props()
+ pset_dir = tool.Ifc.resolve_uri(props.pset_dir)
if os.path.isdir(pset_dir):
for path in Path(pset_dir).glob("*.ifc"):
bonsai.bim.schema.ifc.psetqto.templates.append(ifcopenshell.open(path))
diff --git a/src/bonsai/bonsai/tool/pset_template.py b/src/bonsai/bonsai/tool/pset_template.py
index e2b69705b6..8977d1e32c 100644
--- a/src/bonsai/bonsai/tool/pset_template.py
+++ b/src/bonsai/bonsai/tool/pset_template.py
@@ -133,7 +133,8 @@ class PsetTemplate(bonsai.core.tool.PsetTemplate):
for f in tool.Blender.get_data_dir_paths("pset", "*.ifc"):
paths.append((f, "Global Pset Template"))
- pset_dir = Path(tool.Ifc.resolve_uri(bpy.context.scene.BIMProperties.pset_dir))
+ props = tool.Blender.get_bim_props()
+ pset_dir = Path(tool.Ifc.resolve_uri(props.pset_dir))
if pset_dir.is_dir():
for path in Path(pset_dir).glob("*.ifc"):
paths.append((path, "Project Pset Template"))
diff --git a/src/bonsai/bonsai/tool/search.py b/src/bonsai/bonsai/tool/search.py
index 3b90961559..a48e1404b6 100644
--- a/src/bonsai/bonsai/tool/search.py
+++ b/src/bonsai/bonsai/tool/search.py
@@ -30,9 +30,14 @@ from typing import Union, Literal, TYPE_CHECKING
if TYPE_CHECKING:
from bonsai.bim.prop import BIMFilterGroup
+ from bonsai.bim.module.search.prop import BIMSearchProperties
class Search(bonsai.core.tool.Search):
+ @classmethod
+ def get_search_props(cls) -> BIMSearchProperties:
+ return bpy.context.scene.BIMSearchProperties
+
@classmethod
def get_group_query(cls, group: ifcopenshell.entity_instance) -> str:
return json.loads(group.Description)["query"]
@@ -40,7 +45,7 @@ class Search(bonsai.core.tool.Search):
@classmethod
def get_filter_groups(cls, module: str) -> bpy.types.bpy_prop_collection_idprop[BIMFilterGroup]:
if module == "search":
- return bpy.context.scene.BIMSearchProperties.filter_groups
+ return cls.get_search_props().filter_groups
elif module == "csv":
return bpy.context.scene.CsvProperties.filter_groups
elif module == "diff":
diff --git a/src/bonsai/bonsai/tool/unit.py b/src/bonsai/bonsai/tool/unit.py
index 37597cf95c..808ea5ead0 100644
--- a/src/bonsai/bonsai/tool/unit.py
+++ b/src/bonsai/bonsai/tool/unit.py
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see .
+from __future__ import annotations
import bpy
import json
import math
@@ -23,24 +24,34 @@ import ifcopenshell
import bonsai.bim.helper
import bonsai.core.tool
import bonsai.tool as tool
-from typing import Union, Literal, Any
+from typing import Union, Literal, Any, TYPE_CHECKING
from typing_extensions import assert_never
+if TYPE_CHECKING:
+ from bonsai.bim.module.unit.prop import BIMUnitProperties
+
class Unit(bonsai.core.tool.Unit):
UNIT_TYPE = Literal["LENGTHUNIT", "AREAUNIT", "VOLUMEUNIT"]
+ @classmethod
+ def get_unit_props(cls) -> BIMUnitProperties:
+ return bpy.context.scene.BIMUnitProperties
+
@classmethod
def clear_active_unit(cls) -> None:
- bpy.context.scene.BIMUnitProperties.active_unit_id = 0
+ props = cls.get_unit_props()
+ props.active_unit_id = 0
@classmethod
def disable_editing_units(cls) -> None:
- bpy.context.scene.BIMUnitProperties.is_editing = False
+ props = cls.get_unit_props()
+ props.is_editing = False
@classmethod
def enable_editing_units(cls) -> None:
- bpy.context.scene.BIMUnitProperties.is_editing = True
+ props = cls.get_unit_props()
+ props.is_editing = True
@classmethod
def export_unit_attributes(cls) -> dict[str, Any]:
@@ -52,11 +63,12 @@ class Unit(bonsai.core.tool.Unit):
attributes[prop.name] = (0, 0, 0, 0, 0, 0, 0)
return True
- props = bpy.context.scene.BIMUnitProperties
+ props = cls.get_unit_props()
return bonsai.bim.helper.export_attributes(props.unit_attributes, callback=callback)
@classmethod
def get_scene_unit_name(cls, unit_type: UNIT_TYPE) -> str:
+ bim_props = tool.Blender.get_bim_props()
if unit_type == "LENGTHUNIT":
props = bpy.context.scene.unit_settings
if props.length_unit == "MILES":
@@ -69,23 +81,24 @@ class Unit(bonsai.core.tool.Unit):
return "thou"
return "foot"
elif unit_type == "AREAUNIT":
- return bpy.context.scene.BIMProperties.area_unit
+ return bim_props.area_unit
elif unit_type == "VOLUMEUNIT":
- return bpy.context.scene.BIMProperties.volume_unit
+ return bim_props.volume_unit
else:
assert_never()
@classmethod
def get_scene_unit_si_prefix(cls, unit_type: UNIT_TYPE) -> Union[str, None]:
+ bim_props = tool.Blender.get_bim_props()
if unit_type == "LENGTHUNIT":
props = bpy.context.scene.unit_settings
if props.length_unit == "ADAPTIVE" or props.length_unit == "METERS":
return
return props.length_unit.replace("METERS", "")
elif unit_type == "AREAUNIT":
- unit = bpy.context.scene.BIMProperties.area_unit
+ unit = bim_props.area_unit
elif unit_type == "VOLUMEUNIT":
- unit = bpy.context.scene.BIMProperties.volume_unit
+ unit = bim_props.volume_unit
else:
assert_never(unit_type)
if "/" in unit:
@@ -93,9 +106,11 @@ class Unit(bonsai.core.tool.Unit):
@classmethod
def import_unit_attributes(cls, unit: ifcopenshell.entity_instance) -> None:
+ props = cls.get_unit_props()
+
def callback(name, prop, data):
if name == "Dimensions" and data["type"] != "IfcSIUnit":
- new = bpy.context.scene.BIMUnitProperties.unit_attributes.add()
+ new = props.unit_attributes.add()
new.name = name
new.is_null = data[name] is None
new.is_optional = False
@@ -103,13 +118,12 @@ class Unit(bonsai.core.tool.Unit):
new.string_value = json.dumps([e for e in tool.Ifc.get().by_id(data["id"]).Dimensions])
return True
- props = bpy.context.scene.BIMUnitProperties
props.unit_attributes.clear()
bonsai.bim.helper.import_attributes2(unit, props.unit_attributes, callback=callback)
@classmethod
def import_units(cls) -> None:
- props = bpy.context.scene.BIMUnitProperties
+ props = tool.Unit.get_unit_props()
props.units.clear()
units = []
@@ -158,7 +172,8 @@ class Unit(bonsai.core.tool.Unit):
@classmethod
def set_active_unit(cls, unit: ifcopenshell.entity_instance) -> None:
- bpy.context.scene.BIMUnitProperties.active_unit_id = unit.id()
+ props = cls.get_unit_props()
+ props.active_unit_id = unit.id()
@classmethod
def get_project_currency_unit(cls) -> Union[ifcopenshell.entity_instance, None]:
diff --git a/src/bonsai/bonsai/tool/web.py b/src/bonsai/bonsai/tool/web.py
index 13f5ff7897..e37911b75e 100644
--- a/src/bonsai/bonsai/tool/web.py
+++ b/src/bonsai/bonsai/tool/web.py
@@ -730,7 +730,8 @@ class Web(bonsai.core.tool.Web):
if operator_data["type"] == "getDrawings":
drawings_data = []
sheets_data = []
- ifc_file_dir = os.path.dirname(bpy.context.scene.BIMProperties.ifc_file)
+ props = tool.Blender.get_bim_props()
+ ifc_file_dir = os.path.dirname(props.ifc_file)
sheets = [d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "SHEET"]
for sheet in sorted(sheets, key=lambda s: getattr(s, "Identification", getattr(s, "DocumentId", None))):
diff --git a/src/bonsai/test/tool/test_brick.py b/src/bonsai/test/tool/test_brick.py
index c2d10d699f..b26a2acd43 100644
--- a/src/bonsai/test/tool/test_brick.py
+++ b/src/bonsai/test/tool/test_brick.py
@@ -122,11 +122,10 @@ class TestAddBrickifcProject(NewFile):
result = subject.add_brickifc_project("http://example.org/digitaltwin#")
assert result == f"http://example.org/digitaltwin#{project.GlobalId}"
brick = URIRef(result)
+ props = tool.Blender.get_bim_props()
assert list(BrickStore.graph.triples((brick, A, REF.ifcProject)))
assert list(BrickStore.graph.triples((brick, REF.ifcProjectID, Literal(project.GlobalId))))
- assert list(
- BrickStore.graph.triples((brick, REF.ifcFileLocation, Literal(bpy.context.scene.BIMProperties.ifc_file)))
- )
+ assert list(BrickStore.graph.triples((brick, REF.ifcFileLocation, Literal(props.ifc_file))))
assert list(
BrickStore.graph.triples(
(brick, URIRef("http://www.w3.org/2000/01/rdf-schema#label"), Literal("My Project"))
diff --git a/src/bonsai/test/tool/test_debug.py b/src/bonsai/test/tool/test_debug.py
index d7d6a95c2c..888a717f4d 100644
--- a/src/bonsai/test/tool/test_debug.py
+++ b/src/bonsai/test/tool/test_debug.py
@@ -55,7 +55,8 @@ class TestLoadExpress(NewFile):
class TestPurgeHdf5Cache(NewFile):
def test_run(self):
- cache_dir = Path(bpy.context.scene.BIMProperties.cache_dir)
+ props = tool.Blender.get_bim_props()
+ cache_dir = Path(props.cache_dir)
test_file = cache_dir / "test.h5"
test_file.parent.mkdir(parents=True, exist_ok=True)
test_file.touch()
diff --git a/src/bonsai/test/tool/test_document.py b/src/bonsai/test/tool/test_document.py
index 30ce8b5721..d60b9fbe31 100644
--- a/src/bonsai/test/tool/test_document.py
+++ b/src/bonsai/test/tool/test_document.py
@@ -36,13 +36,13 @@ class TestAddBreadcrumb(NewFile):
tool.Ifc().set(ifc)
document = ifc.createIfcDocumentInformation()
subject.add_breadcrumb(document)
- props = bpy.context.scene.BIMDocumentProperties
+ props = tool.Document.get_document_props()
assert props.breadcrumbs[0].name == str(document.id())
class TestClearBreadcrumbs(NewFile):
def test_run(self):
- props = bpy.context.scene.BIMDocumentProperties
+ props = tool.Document.get_document_props()
props.breadcrumbs.add()
subject.clear_breadcrumbs()
assert len(props.breadcrumbs) == 0
@@ -50,7 +50,7 @@ class TestClearBreadcrumbs(NewFile):
class TestClearDocumentTree(NewFile):
def test_run(self):
- props = bpy.context.scene.BIMDocumentProperties
+ props = tool.Document.get_document_props()
new = props.documents.add()
subject.clear_document_tree()
assert len(props.documents) == 0
@@ -58,23 +58,26 @@ class TestClearDocumentTree(NewFile):
class TestDisableEditingDocument(NewFile):
def test_run(self):
- bpy.context.scene.BIMDocumentProperties.active_document_id = 1
+ props = tool.Document.get_document_props()
+ props.active_document_id = 1
subject.disable_editing_document()
- assert bpy.context.scene.BIMDocumentProperties.active_document_id == 0
+ assert props.active_document_id == 0
class TestDisableEditingUI(NewFile):
def test_run(self):
- bpy.context.scene.BIMDocumentProperties.is_editing = True
+ props = tool.Document.get_document_props()
+ props.is_editing = True
subject.disable_editing_ui()
- assert bpy.context.scene.BIMDocumentProperties.is_editing == False
+ assert props.is_editing == False
class TestEnableEditingUI(NewFile):
def test_run(self):
- bpy.context.scene.BIMDocumentProperties.is_editing = False
+ props = tool.Document.get_document_props()
+ props.is_editing = False
subject.enable_editing_ui()
- assert bpy.context.scene.BIMDocumentProperties.is_editing == True
+ assert props.is_editing == True
class TestExportDocumentAttributes(NewFile):
@@ -129,22 +132,22 @@ class TestImportDocumentAttributes(NewFile):
document.Confidentiality = "CONFIDENTIAL"
document.Status = "DRAFT"
subject().import_document_attributes(document)
- props = bpy.context.scene.BIMDocumentProperties
- assert props.document_attributes.get("Identification").string_value == "Identification"
- assert props.document_attributes.get("Name").string_value == "Name"
- assert props.document_attributes.get("Description").string_value == "Description"
- assert props.document_attributes.get("Location").string_value == "Location"
- assert props.document_attributes.get("Purpose").string_value == "Purpose"
- assert props.document_attributes.get("IntendedUse").string_value == "IntendedUse"
- assert props.document_attributes.get("Scope").string_value == "Scope"
- assert props.document_attributes.get("Revision").string_value == "Revision"
- assert props.document_attributes.get("CreationTime").string_value == "CreationTime"
- assert props.document_attributes.get("LastRevisionTime").string_value == "LastRevisionTime"
- assert props.document_attributes.get("ElectronicFormat").string_value == "ElectronicFormat"
- assert props.document_attributes.get("ValidFrom").string_value == "ValidFrom"
- assert props.document_attributes.get("ValidUntil").string_value == "ValidUntil"
- assert props.document_attributes.get("Confidentiality").enum_value == "CONFIDENTIAL"
- assert props.document_attributes.get("Status").enum_value == "DRAFT"
+ props = tool.Document.get_document_props()
+ assert props.document_attributes["Identification"].string_value == "Identification"
+ assert props.document_attributes["Name"].string_value == "Name"
+ assert props.document_attributes["Description"].string_value == "Description"
+ assert props.document_attributes["Location"].string_value == "Location"
+ assert props.document_attributes["Purpose"].string_value == "Purpose"
+ assert props.document_attributes["IntendedUse"].string_value == "IntendedUse"
+ assert props.document_attributes["Scope"].string_value == "Scope"
+ assert props.document_attributes["Revision"].string_value == "Revision"
+ assert props.document_attributes["CreationTime"].string_value == "CreationTime"
+ assert props.document_attributes["LastRevisionTime"].string_value == "LastRevisionTime"
+ assert props.document_attributes["ElectronicFormat"].string_value == "ElectronicFormat"
+ assert props.document_attributes["ValidFrom"].string_value == "ValidFrom"
+ assert props.document_attributes["ValidUntil"].string_value == "ValidUntil"
+ assert props.document_attributes["Confidentiality"].enum_value == "CONFIDENTIAL"
+ assert props.document_attributes["Status"].enum_value == "DRAFT"
def test_importing_reference(self):
ifc = ifcopenshell.file()
@@ -155,11 +158,11 @@ class TestImportDocumentAttributes(NewFile):
document.Name = "Name"
document.Description = "Description"
subject().import_document_attributes(document)
- props = bpy.context.scene.BIMDocumentProperties
- assert props.document_attributes.get("Location").string_value == "Location"
- assert props.document_attributes.get("Identification").string_value == "Identification"
- assert props.document_attributes.get("Name").string_value == "Name"
- assert props.document_attributes.get("Description").string_value == "Description"
+ props = tool.Document.get_document_props()
+ assert props.document_attributes["Location"].string_value == "Location"
+ assert props.document_attributes["Identification"].string_value == "Identification"
+ assert props.document_attributes["Name"].string_value == "Name"
+ assert props.document_attributes["Description"].string_value == "Description"
class TestImportProjectDocuments(NewFile):
@@ -169,7 +172,7 @@ class TestImportProjectDocuments(NewFile):
ifc.createIfcProject()
document = ifcopenshell.api.run("document.add_information", ifc)
subject.import_project_documents()
- props = bpy.context.scene.BIMDocumentProperties
+ props = tool.Document.get_document_props()
assert len(props.documents) == 1
assert props.documents[0].ifc_definition_id == document.id()
assert props.documents[0].name == "Unnamed"
@@ -185,7 +188,7 @@ class TestImportReferences(NewFile):
document = ifcopenshell.api.run("document.add_information", ifc)
reference = ifcopenshell.api.run("document.add_reference", ifc, information=document)
subject.import_references(document)
- props = bpy.context.scene.BIMDocumentProperties
+ props = tool.Document.get_document_props()
assert len(props.documents) == 1
assert props.documents[0].ifc_definition_id == reference.id()
assert props.documents[0].name == "Unnamed"
@@ -201,7 +204,7 @@ class TestImportSubdocuments(NewFile):
document = ifcopenshell.api.run("document.add_information", ifc)
subdocument = ifcopenshell.api.run("document.add_information", ifc, parent=document)
subject.import_subdocuments(document)
- props = bpy.context.scene.BIMDocumentProperties
+ props = tool.Document.get_document_props()
assert len(props.documents) == 1
assert props.documents[0].ifc_definition_id == subdocument.id()
assert props.documents[0].name == "Unnamed"
@@ -220,7 +223,7 @@ class TestIsDocumentInformation(NewFile):
class TestRemoveLatestBreadcrumb(NewFile):
def test_run(self):
- props = bpy.context.scene.BIMDocumentProperties
+ props = tool.Document.get_document_props()
props.breadcrumbs.add()
props.breadcrumbs.add()
subject.remove_latest_breadcrumb()
@@ -232,4 +235,5 @@ class TestSetActiveDocument(NewFile):
ifc = ifcopenshell.file()
document = ifc.createIfcDocumentInformation()
subject.set_active_document(document)
- assert bpy.context.scene.BIMDocumentProperties.active_document_id == document.id()
+ props = tool.Document.get_document_props()
+ assert props.active_document_id == document.id()
diff --git a/src/bonsai/test/tool/test_ifc.py b/src/bonsai/test/tool/test_ifc.py
index 641a8adfa0..aedcea3465 100644
--- a/src/bonsai/test/tool/test_ifc.py
+++ b/src/bonsai/test/tool/test_ifc.py
@@ -40,12 +40,14 @@ class TestSet(test.bim.bootstrap.NewFile):
class TestGet(test.bim.bootstrap.NewFile):
def test_getting_an_ifc_dataset_from_a_ifc_spf_filepath(self):
assert subject.get() is None
- bpy.context.scene.BIMProperties.ifc_file = "test/files/basic.ifc"
+ props = tool.Blender.get_bim_props()
+ props.ifc_file = "test/files/basic.ifc"
result = subject.get()
assert isinstance(result, ifcopenshell.file)
def test_getting_the_active_ifc_dataset_regardless_of_ifc_path(self):
- bpy.context.scene.BIMProperties.ifc_file = "test/files/basic.ifc"
+ props = tool.Blender.get_bim_props()
+ props.ifc_file = "test/files/basic.ifc"
ifc = ifcopenshell.file()
subject.set(ifc)
assert subject.get() == ifc
diff --git a/src/bonsai/test/tool/test_unit.py b/src/bonsai/test/tool/test_unit.py
index 4a8512c360..a3bad12ab3 100644
--- a/src/bonsai/test/tool/test_unit.py
+++ b/src/bonsai/test/tool/test_unit.py
@@ -18,6 +18,9 @@
import bpy
import ifcopenshell
+import ifcopenshell.api.project
+import ifcopenshell.api.root
+import ifcopenshell.api.unit
import bonsai.core.tool
import bonsai.tool as tool
from test.bim.bootstrap import NewFile
@@ -31,23 +34,26 @@ class TestImplementsTool(NewFile):
class TestClearActiveUnit(NewFile):
def test_run(self):
- bpy.context.scene.BIMUnitProperties.active_unit_id = 1
+ props = tool.Unit.get_unit_props()
+ props.active_unit_id = 1
subject.clear_active_unit()
- assert bpy.context.scene.BIMUnitProperties.active_unit_id == 0
+ assert props.active_unit_id == 0
class TestDisableEditingUnits(NewFile):
def test_run(self):
- bpy.context.scene.BIMUnitProperties.is_editing = True
+ props = tool.Unit.get_unit_props()
+ props.is_editing = True
subject.disable_editing_units()
- assert bpy.context.scene.BIMUnitProperties.is_editing == False
+ assert props.is_editing == False
class TestEnableEditingUnits(NewFile):
def test_run(self):
- bpy.context.scene.BIMUnitProperties.is_editing = False
+ props = tool.Unit.get_unit_props()
+ props.is_editing = False
subject.enable_editing_units()
- assert bpy.context.scene.BIMUnitProperties.is_editing == True
+ assert props.is_editing == True
class TestExportUnitAttributes(NewFile):
@@ -98,10 +104,11 @@ class TestExportUnitAttributes(NewFile):
class TestGetSceneUnitName(NewFile):
def test_getting_an_imperial_name(self):
+ props = tool.Blender.get_bim_props()
bpy.context.scene.unit_settings.system = "IMPERIAL"
bpy.context.scene.unit_settings.length_unit = "MILES"
- bpy.context.scene.BIMProperties.area_unit = "square foot"
- bpy.context.scene.BIMProperties.volume_unit = "cubic inch"
+ props.area_unit = "square foot"
+ props.volume_unit = "cubic inch"
assert subject.get_scene_unit_name("LENGTHUNIT") == "mile"
assert subject.get_scene_unit_name("AREAUNIT") == "square foot"
assert subject.get_scene_unit_name("VOLUMEUNIT") == "cubic inch"
@@ -142,13 +149,14 @@ class TestGetSceneUnitSIPrefix:
assert subject.get_scene_unit_si_prefix("LENGTHUNIT") == "KILO"
bpy.context.scene.unit_settings.length_unit = "ADAPTIVE"
assert subject.get_scene_unit_si_prefix("LENGTHUNIT") is None
- bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE"
+ props = tool.Blender.get_bim_props()
+ props.area_unit = "SQUARE_METRE"
assert subject.get_scene_unit_si_prefix("AREAUNIT") is None
- bpy.context.scene.BIMProperties.area_unit = "MILLI/SQUARE_METRE"
+ props.area_unit = "MILLI/SQUARE_METRE"
assert subject.get_scene_unit_si_prefix("AREAUNIT") == "MILLI"
- bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE"
+ props.volume_unit = "CUBIC_METRE"
assert subject.get_scene_unit_si_prefix("VOLUMEUNIT") is None
- bpy.context.scene.BIMProperties.volume_unit = "MILLI/CUBIC_METRE"
+ props.volume_unit = "MILLI/CUBIC_METRE"
assert subject.get_scene_unit_si_prefix("VOLUMEUNIT") == "MILLI"
@@ -159,25 +167,25 @@ class TestImportUnitAttributes(NewFile):
unit.UnitType = "ANGULARVELOCITYUNIT"
unit.UserDefinedType = "UserDefinedType"
subject.import_unit_attributes(unit)
- props = bpy.context.scene.BIMUnitProperties
- assert props.unit_attributes.get("UnitType").enum_value == "ANGULARVELOCITYUNIT"
- assert props.unit_attributes.get("UserDefinedType").string_value == "UserDefinedType"
+ props = tool.Unit.get_unit_props()
+ assert props.unit_attributes["UnitType"].enum_value == "ANGULARVELOCITYUNIT"
+ assert props.unit_attributes["UserDefinedType"].string_value == "UserDefinedType"
def test_importing_monetary_units(self):
tool.Ifc.set(ifc := ifcopenshell.file())
unit = ifc.createIfcMonetaryUnit()
unit.Currency = "Currency"
subject.import_unit_attributes(unit)
- props = bpy.context.scene.BIMUnitProperties
- assert props.unit_attributes.get("Currency").string_value == "Currency"
+ props = tool.Unit.get_unit_props()
+ assert props.unit_attributes["Currency"].string_value == "Currency"
def test_importing_monetary_units_ifc2x3(self):
tool.Ifc.set(ifc := ifcopenshell.file(schema="IFC2X3"))
unit = ifc.createIfcMonetaryUnit()
unit.Currency = "USD"
subject.import_unit_attributes(unit)
- props = bpy.context.scene.BIMUnitProperties
- assert props.unit_attributes.get("Currency").enum_value == "USD"
+ props = tool.Unit.get_unit_props()
+ assert props.unit_attributes["Currency"].enum_value == "USD"
def test_importing_context_dependent_units(self):
ifc = ifcopenshell.file()
@@ -187,10 +195,10 @@ class TestImportUnitAttributes(NewFile):
unit.Name = "Name"
unit.Dimensions = ifc.createIfcDimensionalExponents(1, 2, 3, 4, 5, 6, 7)
subject.import_unit_attributes(unit)
- props = bpy.context.scene.BIMUnitProperties
- assert props.unit_attributes.get("UnitType").enum_value == "ABSORBEDDOSEUNIT"
- assert props.unit_attributes.get("Name").string_value == "Name"
- assert props.unit_attributes.get("Dimensions").string_value == "[1, 2, 3, 4, 5, 6, 7]"
+ props = tool.Unit.get_unit_props()
+ assert props.unit_attributes["UnitType"].enum_value == "ABSORBEDDOSEUNIT"
+ assert props.unit_attributes["Name"].string_value == "Name"
+ assert props.unit_attributes["Dimensions"].string_value == "[1, 2, 3, 4, 5, 6, 7]"
def test_importing_conversion_based_units(self):
ifc = ifcopenshell.file()
@@ -200,10 +208,10 @@ class TestImportUnitAttributes(NewFile):
unit.Name = "Name"
unit.Dimensions = ifc.createIfcDimensionalExponents(1, 2, 3, 4, 5, 6, 7)
subject.import_unit_attributes(unit)
- props = bpy.context.scene.BIMUnitProperties
- assert props.unit_attributes.get("UnitType").enum_value == "ABSORBEDDOSEUNIT"
- assert props.unit_attributes.get("Name").string_value == "Name"
- assert props.unit_attributes.get("Dimensions").string_value == "[1, 2, 3, 4, 5, 6, 7]"
+ props = tool.Unit.get_unit_props()
+ assert props.unit_attributes["UnitType"].enum_value == "ABSORBEDDOSEUNIT"
+ assert props.unit_attributes["Name"].string_value == "Name"
+ assert props.unit_attributes["Dimensions"].string_value == "[1, 2, 3, 4, 5, 6, 7]"
def test_importing_conversion_based_with_offset_units(self):
ifc = ifcopenshell.file()
@@ -214,11 +222,11 @@ class TestImportUnitAttributes(NewFile):
unit.Dimensions = ifc.createIfcDimensionalExponents(1, 2, 3, 4, 5, 6, 7)
unit.ConversionOffset = 1
subject.import_unit_attributes(unit)
- props = bpy.context.scene.BIMUnitProperties
- assert props.unit_attributes.get("UnitType").enum_value == "ABSORBEDDOSEUNIT"
- assert props.unit_attributes.get("Name").string_value == "Name"
- assert props.unit_attributes.get("Dimensions").string_value == "[1, 2, 3, 4, 5, 6, 7]"
- assert props.unit_attributes.get("ConversionOffset").float_value == 1
+ props = tool.Unit.get_unit_props()
+ assert props.unit_attributes["UnitType"].enum_value == "ABSORBEDDOSEUNIT"
+ assert props.unit_attributes["Name"].string_value == "Name"
+ assert props.unit_attributes["Dimensions"].string_value == "[1, 2, 3, 4, 5, 6, 7]"
+ assert props.unit_attributes["ConversionOffset"].float_value == 1
def test_importing_si_units(self):
ifc = ifcopenshell.file()
@@ -228,11 +236,11 @@ class TestImportUnitAttributes(NewFile):
unit.Prefix = "EXA"
unit.Name = "AMPERE"
subject.import_unit_attributes(unit)
- props = bpy.context.scene.BIMUnitProperties
- assert props.unit_attributes.get("UnitType").enum_value == "ABSORBEDDOSEUNIT"
- assert props.unit_attributes.get("Prefix").enum_value == "EXA"
- assert props.unit_attributes.get("Name").enum_value == "AMPERE"
- assert props.unit_attributes.get("Dimensions") is None
+ props = tool.Unit.get_unit_props()
+ assert props.unit_attributes["UnitType"].enum_value == "ABSORBEDDOSEUNIT"
+ assert props.unit_attributes["Prefix"].enum_value == "EXA"
+ assert props.unit_attributes["Name"].enum_value == "AMPERE"
+ assert props.unit_attributes["Dimensions"] is None
class TestImportUnits(NewFile):
@@ -248,7 +256,7 @@ class TestImportUnits(NewFile):
ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcProject")
ifcopenshell.api.unit.assign_unit(ifc, units=[unit2])
subject.import_units()
- props = bpy.context.scene.BIMUnitProperties
+ props = tool.Unit.get_unit_props()
assert len(props.units) == 6
assert props.units[0].ifc_definition_id == unit1.id()
@@ -311,4 +319,5 @@ class TestSetActiveUnit(NewFile):
ifc = ifcopenshell.file()
unit = ifc.createIfcSIUnit()
subject.set_active_unit(unit)
- assert bpy.context.scene.BIMUnitProperties.active_unit_id == unit.id()
+ props = tool.Unit.get_unit_props()
+ assert props.active_unit_id == unit.id()
diff --git a/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py b/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py
index 2df1b4eaa5..1ab4bc432e 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py
@@ -62,42 +62,38 @@ def remove_product(file: ifcopenshell.file, product: ifcopenshell.entity_instanc
# No we don't.
ifcopenshell.api.root.remove_product(model, product=wall)
"""
- settings = {"product": product}
-
- representations = []
- if settings["product"].is_a("IfcProduct"):
- if settings["product"].Representation:
- representations = settings["product"].Representation.Representations or []
+ representations: list[ifcopenshell.entity_instance] = []
+ if product.is_a("IfcProduct"):
+ if product.Representation:
+ representations = product.Representation.Representations or []
else:
representations = []
# remove object placements
- object_placement = settings["product"].ObjectPlacement
+ object_placement = product.ObjectPlacement
if object_placement:
if file.get_total_inverses(object_placement) == 1:
- settings["product"].ObjectPlacement = None # remove the inverse for remove_deep2 to work
+ product.ObjectPlacement = None # remove the inverse for remove_deep2 to work
ifcopenshell.util.element.remove_deep2(file, object_placement)
- elif settings["product"].is_a("IfcTypeProduct"):
- representations = [rm.MappedRepresentation for rm in settings["product"].RepresentationMaps or []]
+ elif product.is_a("IfcTypeProduct"):
+ representations = [rm.MappedRepresentation for rm in product.RepresentationMaps or []]
# remove psets
- psets = settings["product"].HasPropertySets or []
+ psets = product.HasPropertySets or []
for pset in psets:
if file.get_total_inverses(pset) != 1:
continue
- ifcopenshell.api.pset.remove_pset(file, product=settings["product"], pset=pset)
+ ifcopenshell.api.pset.remove_pset(file, product=product, pset=pset)
for representation in representations:
- ifcopenshell.api.geometry.unassign_representation(
- file, product=settings["product"], representation=representation
- )
- ifcopenshell.api.geometry.remove_representation(file, **{"representation": representation})
- for opening in getattr(settings["product"], "HasOpenings", []) or []:
+ ifcopenshell.api.geometry.unassign_representation(file, product=product, representation=representation)
+ ifcopenshell.api.geometry.remove_representation(file, representation=representation)
+ for opening in getattr(product, "HasOpenings", []) or []:
ifcopenshell.api.feature.remove_feature(file, feature=opening.RelatedOpeningElement)
- if settings["product"].is_a("IfcGrid"):
- for axis in settings["product"].UAxes + settings["product"].VAxes + (settings["product"].WAxes or ()):
+ if product.is_a("IfcGrid"):
+ for axis in product.UAxes + product.VAxes + (product.WAxes or ()):
ifcopenshell.api.grid.remove_grid_axis(file, axis=axis)
def element_exists(element_id):
@@ -108,22 +104,20 @@ def remove_product(file: ifcopenshell.file, product: ifcopenshell.entity_instanc
return False
# TODO: remove object placement and other relationships
- for inverse_id in [i.id() for i in file.get_inverse(settings["product"])]:
+ for inverse_id in [i.id() for i in file.get_inverse(product)]:
try:
inverse = file.by_id(inverse_id)
except:
continue
if inverse.is_a("IfcRelDefinesByProperties"):
- ifcopenshell.api.pset.remove_pset(
- file, product=settings["product"], pset=inverse.RelatingPropertyDefinition
- )
+ ifcopenshell.api.pset.remove_pset(file, product=product, pset=inverse.RelatingPropertyDefinition)
elif inverse.is_a("IfcRelAssociatesMaterial"):
- ifcopenshell.api.material.unassign_material(file, products=[settings["product"]])
+ ifcopenshell.api.material.unassign_material(file, products=[product])
elif inverse.is_a("IfcRelDefinesByType"):
- if inverse.RelatingType == settings["product"]:
+ if inverse.RelatingType == product:
ifcopenshell.api.type.unassign_type(file, related_objects=inverse.RelatedObjects)
else:
- ifcopenshell.api.type.unassign_type(file, related_objects=[settings["product"]])
+ ifcopenshell.api.type.unassign_type(file, related_objects=[product])
elif inverse.is_a("IfcRelSpaceBoundary"):
ifcopenshell.api.boundary.remove_boundary(file, boundary=inverse)
elif inverse.is_a("IfcRelFillsElement"):
@@ -142,7 +136,7 @@ def remove_product(file: ifcopenshell.file, product: ifcopenshell.entity_instanc
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelNests"):
- if inverse.RelatingObject == settings["product"]:
+ if inverse.RelatingObject == product:
inverse_id = inverse.id()
for subelement in inverse.RelatedObjects:
if subelement.is_a("IfcDistributionPort"):
@@ -153,27 +147,27 @@ def remove_product(file: ifcopenshell.file, product: ifcopenshell.entity_instanc
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
- elif inverse.RelatedObjects == (settings["product"],):
+ elif inverse.RelatedObjects == (product,):
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelAggregates"):
- if inverse.RelatingObject == settings["product"] or len(inverse.RelatedObjects) == 1:
+ if inverse.RelatingObject == product or len(inverse.RelatedObjects) == 1:
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelContainedInSpatialStructure"):
- if inverse.RelatingStructure == settings["product"] or len(inverse.RelatedElements) == 1:
+ if inverse.RelatingStructure == product or len(inverse.RelatedElements) == 1:
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelConnectsElements"):
if inverse.is_a("IfcRelConnectsWithRealizingElements"):
- if settings["product"] not in (inverse.RelatingElement, inverse.RelatedElement) and any(
- el for el in inverse.RealizingElements if el != settings["product"]
+ if product not in (inverse.RelatingElement, inverse.RelatedElement) and any(
+ el for el in inverse.RealizingElements if el != product
):
continue
history = inverse.OwnerHistory
@@ -181,15 +175,15 @@ def remove_product(file: ifcopenshell.file, product: ifcopenshell.entity_instanc
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelConnectsPortToElement"):
- if inverse.RelatedElement == settings["product"]:
+ if inverse.RelatedElement == product:
ifcopenshell.api.root.remove_product(file, product=inverse.RelatingPort)
- elif inverse.RelatingPort == settings["product"]:
+ elif inverse.RelatingPort == product:
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelConnectsPorts"):
- if settings["product"] not in (inverse.RelatingPort, inverse.RelatedPort):
+ if product not in (inverse.RelatingPort, inverse.RelatedPort):
# if it's not RelatingPort/RelatedPort then it's optional RealizingElement
# so we keep the relationship
continue
@@ -204,7 +198,7 @@ def remove_product(file: ifcopenshell.file, product: ifcopenshell.entity_instanc
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelAssignsToProduct"):
- if inverse.RelatingProduct == settings["product"]:
+ if inverse.RelatingProduct == product:
history = inverse.OwnerHistory
file.remove(inverse)
if history:
@@ -215,17 +209,17 @@ def remove_product(file: ifcopenshell.file, product: ifcopenshell.entity_instanc
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelFlowControlElements"):
- if inverse.RelatingFlowElement == settings["product"]:
+ if inverse.RelatingFlowElement == product:
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
- elif inverse.RelatedControlElements == (settings["product"],):
+ elif inverse.RelatedControlElements == (product,):
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
- history = settings["product"].OwnerHistory
- file.remove(settings["product"])
+ history = product.OwnerHistory
+ file.remove(product)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
diff --git a/src/ifcopenshell-python/ifcopenshell/api/style/edit_surface_style.py b/src/ifcopenshell-python/ifcopenshell/api/style/edit_surface_style.py
index 28419b8d86..dba4dd48ea 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/style/edit_surface_style.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/style/edit_surface_style.py
@@ -81,7 +81,7 @@ class Usecase:
def execute(self, style: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
self.style = style
- attribute_types = {}
+ attribute_types: dict[str, str] = {}
for attribute in style.wrapped_data.declaration().as_entity().all_attributes():
attribute_type = attribute.type_of_attribute()
if attribute_type.as_aggregation_type() is None:
diff --git a/src/ifcopenshell-python/ifcopenshell/entity_instance.py b/src/ifcopenshell-python/ifcopenshell/entity_instance.py
index dcd3b74cb0..15e5980d41 100644
--- a/src/ifcopenshell-python/ifcopenshell/entity_instance.py
+++ b/src/ifcopenshell-python/ifcopenshell/entity_instance.py
@@ -248,14 +248,10 @@ class entity_instance:
:param f: A callable that takes a single argument and returns a boolean
value. It represents the condition.
- :type f: Callable
:param g: A callable that takes a single argument and returns a
transformed value. It represents the transformation.
- :type g: Callable
:param value: Any object, the input value to be processed
- :type value: Any
:return: Transformed value
- :rtype: Any
Example:
@@ -304,8 +300,6 @@ class entity_instance:
"""Return the data type of a positional attribute of the element
:param attr: The index or name of the attribute
- :type attr: Union[int, str]
- :rtype: string
"""
attr_idx = attr if isinstance(attr, numbers.Integral) else self.wrapped_data.get_argument_index(attr)
return self.wrapped_data.get_argument_type(attr_idx)
@@ -314,8 +308,6 @@ class entity_instance:
"""Return the name of a positional attribute of the element
:param attr_idx: The index of the attribute
- :type attr_idx: int
- :rtype: string
"""
return self.wrapped_data.get_argument_name(attr_idx)
@@ -405,9 +397,7 @@ class entity_instance:
returned IFC class name should include schema name
(e.g. "IFC4.IfcWall" if `True` and "IfcWall" if `False`).
If omitted will act as `False`.
- :type args: Union[str, bool]
:returns: Either the name of the class, or a boolean if it passes the check
- :rtype: Union[str, bool]
Example:
@@ -423,10 +413,7 @@ class entity_instance:
return self.wrapped_data.is_a(*args)
def id(self) -> int:
- """Return the STEP numerical identifier
-
- :rtype: int
- """
+ """Return the STEP numerical identifier"""
return self.wrapped_data.id()
def __eq__(self, other: "entity_instance") -> bool:
diff --git a/src/ifcopenshell-python/ifcopenshell/file.py b/src/ifcopenshell-python/ifcopenshell/file.py
index d3c94b616d..328d98b27f 100644
--- a/src/ifcopenshell-python/ifcopenshell/file.py
+++ b/src/ifcopenshell-python/ifcopenshell/file.py
@@ -359,7 +359,6 @@ class file:
of those methods.
:param type: Case insensitive name of the IFC class
- :type type: string
:param args: The positional arguments of the IFC class
:param kwargs: The keyword arguments of the IFC class
:returns: An entity instance
@@ -482,12 +481,10 @@ class file:
"""Return an IFC entity instance filtered by IFC ID.
:param id: STEP numerical identifier
- :type id: int
:raises RuntimeError: If `id` is not found.
:returns: An ifcopenshell.entity_instance
- :rtype: ifcopenshell.entity_instance
"""
return self[id]
@@ -495,13 +492,10 @@ class file:
"""Return an IFC entity instance filtered by IFC GUID.
:param guid: GlobalId value in 22-character encoded form
- :type guid: string
:raises RuntimeError: If `guid` is not found.
:returns: An ifcopenshell.entity_instance
- :rtype: ifcopenshell.entity_instance
-
"""
return self[guid]
@@ -510,9 +504,7 @@ class file:
If the entity already exists, it is not re-added. Existence of entity is checked by it's `.identity()`.
:param inst: The entity instance to add
- :type inst: ifcopenshell.entity_instance
:returns: An ifcopenshell.entity_instance
- :rtype: ifcopenshell.entity_instance
"""
if self.transaction:
@@ -530,14 +522,11 @@ class file:
If an IFC type class has subclasses, all entities of those subclasses are also returned.
:param type: The case insensitive type of IFC class to return.
- :type type: string
:param include_subtypes: Whether or not to return subtypes of the IFC class
- :type include_subtypes: bool
:raises RuntimeError: If `type` is not found in IFC schema.
:returns: A list of ifcopenshell.entity_instance objects
- :rtype: list[ifcopenshell.entity_instance]
"""
if include_subtypes:
return [entity_instance(e, self) for e in self.wrapped_data.by_type(type)]
@@ -625,9 +614,7 @@ class file:
significantly faster.
:param inst: The entity instance to get inverse relationships
- :type inst: ifcopenshell.entity_instance
:returns: The total number of references
- :rtype: int
"""
return self.wrapped_data.get_total_inverses(inst.wrapped_data)
@@ -639,8 +626,6 @@ class file:
the reference to the deleted will be removed from the aggregate.
:param inst: The entity instance to delete
- :type inst: ifcopenshell.entity_instance
- :rtype: None
"""
if self.transaction:
self.transaction.store_delete(inst)
@@ -676,9 +661,7 @@ class file:
if None. Supported formats : .ifc, .ifcXML, .ifcZIP (equivalent to
format=".ifc" with zipped=True) For zipped .ifcXML use
format=".ifcXML" with zipped=True
- :type format: str
:param zipped: zip the file after it is written
- :type zipped: bool
Example: