diff --git a/src/bonsai/bonsai/bim/module/type/data.py b/src/bonsai/bonsai/bim/module/type/data.py
index 37586df924..d93b86261d 100644
--- a/src/bonsai/bonsai/bim/module/type/data.py
+++ b/src/bonsai/bonsai/bim/module/type/data.py
@@ -67,7 +67,8 @@ class TypeData:
if not relating_type_classes:
return []
results = []
- relating_type_class = bpy.context.active_object.BIMTypeProperties.relating_type_class
+ assert (obj := bpy.context.active_object)
+ relating_type_class = tool.Type.get_object_type_props(obj).relating_type_class
if not relating_type_class and relating_type_classes:
relating_type_class = relating_type_classes[0][0]
elements = tool.Ifc.get().by_type(relating_type_class)
diff --git a/src/bonsai/bonsai/bim/module/type/operator.py b/src/bonsai/bonsai/bim/module/type/operator.py
index fac2f93546..f8c5c1ae6b 100644
--- a/src/bonsai/bonsai/bim/module/type/operator.py
+++ b/src/bonsai/bonsai/bim/module/type/operator.py
@@ -46,9 +46,13 @@ class AssignType(bpy.types.Operator, tool.Ifc.Operator):
related_object: str
def _execute(self, context):
- relating_type = tool.Ifc.get().by_id(
- self.relating_type or int(context.active_object.BIMTypeProperties.relating_type)
- )
+ if self.relating_type:
+ relating_type = self.relating_type
+ else:
+ assert (obj := context.active_object)
+ props = tool.Type.get_object_type_props(obj)
+ relating_type = int(props.relating_type)
+ relating_type = tool.Ifc.get().by_id(relating_type)
if self.related_object:
related_objects = [bpy.data.objects[self.related_object]]
else:
@@ -130,8 +134,10 @@ class EnableEditingType(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- context.active_object.BIMTypeProperties.is_editing_type = True
- context.active_object.BIMTypeProperties.relating_type_object = None
+ assert (obj := context.active_object)
+ props = tool.Type.get_object_type_props(obj)
+ props.is_editing_type = True
+ props.relating_type_object = None
return {"FINISHED"}
@@ -142,8 +148,10 @@ class DisableEditingType(bpy.types.Operator):
obj: bpy.props.StringProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
- obj.BIMTypeProperties.is_editing_type = False
+ obj = bpy.data.objects[self.obj] if self.obj else context.active_object
+ assert obj
+ props = tool.Type.get_object_type_props(obj)
+ props.is_editing_type = False
return {"FINISHED"}
diff --git a/src/bonsai/bonsai/bim/module/type/prop.py b/src/bonsai/bonsai/bim/module/type/prop.py
index 974e4b5d53..814d678390 100644
--- a/src/bonsai/bonsai/bim/module/type/prop.py
+++ b/src/bonsai/bonsai/bim/module/type/prop.py
@@ -21,6 +21,7 @@ import ifcopenshell.util.element
import ifcopenshell.util.type
from bonsai.bim.module.type.data import TypeData
import bonsai.tool as tool
+from typing import TYPE_CHECKING, Union
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -34,23 +35,23 @@ from bpy.props import (
)
-def get_relating_type_class(self, context):
+def get_relating_type_class(self: "BIMTypeProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
if not TypeData.is_loaded:
TypeData.load()
return TypeData.data["relating_type_classes"]
-def get_relating_type(self, context):
+def get_relating_type(self: "BIMTypeProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
if not TypeData.is_loaded:
TypeData.load()
return TypeData.data["relating_types"]
-def update_relating_type_class(self, context):
+def update_relating_type_class(self: "BIMTypeProperties", context: bpy.types.Context) -> None:
TypeData.is_loaded = False
-def update_relating_type_from_object(self, context):
+def update_relating_type_from_object(self: "BIMTypeProperties", context: bpy.types.Context) -> None:
if self.relating_type_object is None:
return
element = tool.Ifc.get_entity(self.relating_type_object)
@@ -63,12 +64,12 @@ def update_relating_type_from_object(self, context):
bpy.ops.bim.assign_type()
-def is_object_class_applicable(self, obj):
+def is_object_class_applicable(self: "BIMTypeProperties", obj: bpy.types.Object) -> bool:
if not TypeData.is_loaded:
TypeData.load()
element = tool.Ifc.get_entity(obj)
if not element:
- return
+ return False
element_type = ifcopenshell.util.element.get_type(element)
if element_type is None:
return False
@@ -89,3 +90,9 @@ class BIMTypeProperties(PropertyGroup):
update=update_relating_type_from_object,
poll=is_object_class_applicable,
)
+
+ if TYPE_CHECKING:
+ is_editing_type: bool
+ relating_type_class: str
+ relating_type: str
+ relating_type_object: Union[bpy.types.Object, None]
diff --git a/src/bonsai/bonsai/bim/module/type/ui.py b/src/bonsai/bonsai/bim/module/type/ui.py
index 291c37f1ba..701dff465d 100644
--- a/src/bonsai/bonsai/bim/module/type/ui.py
+++ b/src/bonsai/bonsai/bim/module/type/ui.py
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see .
+import bpy
import bonsai.tool as tool
import bonsai.bim.module.type.prop as type_prop
from bpy.types import Panel
@@ -55,8 +56,9 @@ class BIM_PT_type(Panel):
else:
self.draw_type_ui(context)
- def draw_type_ui(self, context):
- oprops = tool.Blender.get_object_bim_props(context.active_object)
+ def draw_type_ui(self, context: bpy.types.Context) -> None:
+ assert (obj := context.active_object)
+ oprops = tool.Blender.get_object_bim_props(obj)
row = self.layout.row(align=True)
row.label(text=f"{TypeData.data['total_instances']} Typed Objects")
select_type_objects_row = row.row(align=True)
@@ -66,9 +68,10 @@ class BIM_PT_type(Panel):
op.element = oprops.ifc_definition_id
row.operator("bim.auto_rename_occurrences", icon="ITALIC", text="")
- def draw_product_ui(self, context):
+ def draw_product_ui(self, context: bpy.types.Context) -> None:
layout = self.layout
- props = context.active_object.BIMTypeProperties
+ assert (obj := context.active_object)
+ props = tool.Type.get_object_type_props(obj)
if props.is_editing_type:
row = layout.row(align=True)
diff --git a/src/bonsai/bonsai/core/geometry.py b/src/bonsai/bonsai/core/geometry.py
index f4eefcbcc0..b5fcd61285 100644
--- a/src/bonsai/bonsai/core/geometry.py
+++ b/src/bonsai/bonsai/core/geometry.py
@@ -26,9 +26,9 @@ if TYPE_CHECKING:
def edit_object_placement(
- ifc: tool.Ifc,
- geometry: tool.Geometry,
- surveyor: tool.Surveyor,
+ ifc: type[tool.Ifc],
+ geometry: type[tool.Geometry],
+ surveyor: type[tool.Surveyor],
obj: Optional[bpy.types.Object] = None,
apply_scale: bool = True,
) -> None:
@@ -50,10 +50,10 @@ def edit_object_placement(
def add_representation(
- ifc: tool.Ifc,
- geometry: tool.Geometry,
- style: tool.Style,
- surveyor: tool.Surveyor,
+ ifc: type[tool.Ifc],
+ geometry: type[tool.Geometry],
+ style: type[tool.Style],
+ surveyor: type[tool.Surveyor],
obj: bpy.types.Object,
context: ifcopenshell.entity_instance,
ifc_representation_class: Optional[str] = None,
@@ -110,8 +110,8 @@ def add_representation(
def switch_representation(
- ifc: tool.Ifc,
- geometry: tool.Geometry,
+ ifc: type[tool.Ifc],
+ geometry: type[tool.Geometry],
obj: bpy.types.Object,
representation: ifcopenshell.entity_instance,
should_reload: bool = True,
@@ -139,13 +139,16 @@ def switch_representation(
def get_representation_ifc_parameters(
- geometry: tool.Geometry, obj: bpy.types.Object, should_sync_changes_first: bool = False
+ geometry: type[tool.Geometry], obj: bpy.types.Object, should_sync_changes_first: bool = False
) -> None:
geometry.import_representation_parameters(geometry.get_object_data(obj))
def remove_representation(
- ifc: tool.Ifc, geometry: tool.Geometry, obj: bpy.types.Object, representation: ifcopenshell.entity_instance
+ ifc: type[tool.Ifc],
+ geometry: type[tool.Geometry],
+ obj: bpy.types.Object,
+ representation: ifcopenshell.entity_instance,
) -> None:
"""Remove IFC representation from an object.
@@ -183,7 +186,7 @@ def remove_representation(
geometry.delete_data(data)
-def purge_unused_representations(ifc: tool.Ifc, geometry: tool.Geometry) -> int:
+def purge_unused_representations(ifc: type[tool.Ifc], geometry: type[tool.Geometry]) -> int:
"""Purge representations without inverses.
:return: A number of purged representations.
@@ -196,15 +199,17 @@ def purge_unused_representations(ifc: tool.Ifc, geometry: tool.Geometry) -> int:
return purged_representations
-def select_connection(geometry: tool.Geometry, connection: ifcopenshell.entity_instance) -> None:
+def select_connection(geometry: type[tool.Geometry], connection: ifcopenshell.entity_instance) -> None:
geometry.select_connection(connection)
-def remove_connection(geometry: tool.Geometry, connection: ifcopenshell.entity_instance) -> None:
+def remove_connection(geometry: type[tool.Geometry], connection: ifcopenshell.entity_instance) -> None:
geometry.remove_connection(connection)
-def get_similar_openings(ifc: tool.Ifc, opening: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
+def get_similar_openings(
+ ifc: type[tool.Ifc], opening: ifcopenshell.entity_instance
+) -> list[ifcopenshell.entity_instance]:
model = ifc.get()
all_openings = model.by_type("IfcOpeningElement")
similar_openings = [o for o in all_openings if o.ObjectPlacement == opening.ObjectPlacement and o != opening]
@@ -212,7 +217,7 @@ def get_similar_openings(ifc: tool.Ifc, opening: ifcopenshell.entity_instance) -
def get_similar_openings_building_objs(
- ifc: tool.Ifc, similar_openings: list[ifcopenshell.entity_instance]
+ ifc: type[tool.Ifc], similar_openings: list[ifcopenshell.entity_instance]
) -> list[bpy.types.Object]:
building_objs = []
for similar_opening in similar_openings:
@@ -221,7 +226,7 @@ def get_similar_openings_building_objs(
def edit_similar_opening_placement(
- geometry: tool.Geometry,
+ geometry: type[tool.Geometry],
opening: Optional[ifcopenshell.entity_instance] = None,
similar_openings: Sequence[ifcopenshell.entity_instance] = (),
) -> None:
diff --git a/src/bonsai/bonsai/tool/type.py b/src/bonsai/bonsai/tool/type.py
index d05e3c6dc7..bda2ed4502 100644
--- a/src/bonsai/bonsai/tool/type.py
+++ b/src/bonsai/bonsai/tool/type.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 ifcopenshell
import ifcopenshell.util.element
@@ -23,18 +24,25 @@ import ifcopenshell.util.representation
import bonsai.core.tool
import bonsai.core.geometry
import bonsai.tool as tool
-import bonsai.bim.helper
-from typing import Union
+from typing import Union, TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from bonsai.bim.module.type.prop import BIMTypeProperties
class Type(bonsai.core.tool.Type):
+ @classmethod
+ def get_object_type_props(cls, obj: bpy.types.Object) -> BIMTypeProperties:
+ return obj.BIMTypeProperties
+
@classmethod
def change_object_data(cls, obj: bpy.types.Object, data: bpy.types.ID, is_global: bool = False) -> None:
tool.Geometry.change_object_data(obj, data, is_global)
@classmethod
def disable_editing(cls, obj: bpy.types.Object) -> None:
- obj.BIMTypeProperties.is_editing_type = False
+ props = cls.get_object_type_props(obj)
+ props.is_editing_type = False
@classmethod
def get_body_context(cls) -> ifcopenshell.entity_instance:
diff --git a/src/bonsai/test/tool/test_type.py b/src/bonsai/test/tool/test_type.py
index 4bb9c4b694..ed434e9cc5 100644
--- a/src/bonsai/test/tool/test_type.py
+++ b/src/bonsai/test/tool/test_type.py
@@ -52,9 +52,10 @@ class TestChangeObjectData(NewFile):
class TestDisableEditing(NewFile):
def test_run(self):
obj = bpy.data.objects.new("Object", None)
- obj.BIMTypeProperties.is_editing_type = True
+ props = tool.Type.get_object_type_props(obj)
+ props.is_editing_type = True
subject.disable_editing(obj)
- assert obj.BIMTypeProperties.is_editing_type is False
+ assert props.is_editing_type is False
class TestGetBodyContext(NewFile):