diff --git a/src/bonsai/bonsai/bim/handler.py b/src/bonsai/bonsai/bim/handler.py
index 21cfd08adb..2a90ecdcbc 100644
--- a/src/bonsai/bonsai/bim/handler.py
+++ b/src/bonsai/bonsai/bim/handler.py
@@ -277,10 +277,10 @@ def get_user(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None
return pao
elif ifc.schema == "IFC2X3":
if (person := next(iter(ifc.by_type("IfcPerson")), None)) is None:
- person = tool.Ifc.run("owner.add_person")
+ person = ifcopenshell.api.owner.add_person(ifc)
if (organization := next(iter(ifc.by_type("IfcOrganization")), None)) is None:
- organization = tool.Ifc.run("owner.add_organisation")
- pao = tool.Ifc.run("owner.add_person_and_organisation", person=person, organisation=organization)
+ organization = ifcopenshell.api.owner.add_organisation(ifc)
+ pao = ifcopenshell.api.owner.add_person_and_organisation(ifc, person=person, organisation=organization)
return pao
diff --git a/src/bonsai/bonsai/bim/helper.py b/src/bonsai/bonsai/bim/helper.py
index 0a7cb1e325..0fbbb94901 100644
--- a/src/bonsai/bonsai/bim/helper.py
+++ b/src/bonsai/bonsai/bim/helper.py
@@ -121,7 +121,8 @@ def import_attributes(
callback: Optional[ImportCallback] = None,
) -> None:
schema = tool.Ifc.schema()
- for attribute in schema.declaration_by_name(ifc_class).all_attributes():
+ assert (entity := schema.declaration_by_name(ifc_class).as_entity())
+ for attribute in entity.all_attributes():
import_attribute(attribute, props, data, callback=callback)
@@ -132,11 +133,13 @@ def import_attributes2(
callback: Optional[ImportCallback] = None,
) -> None:
if isinstance(element, str):
- attributes = tool.Ifc.schema().declaration_by_name(element).as_entity().all_attributes()
+ assert (entity := tool.Ifc.schema().declaration_by_name(element).as_entity())
+ attributes = entity.all_attributes()
info = {a.name(): None for a in attributes}
info["type"] = element
else:
- attributes = element.wrapped_data.declaration().as_entity().all_attributes()
+ assert (entity := element.wrapped_data.declaration().as_entity())
+ attributes = entity.all_attributes()
info = element.get_info()
for attribute in attributes:
import_attribute(attribute, props, info, callback=callback)
diff --git a/src/bonsai/bonsai/bim/module/debug/operator.py b/src/bonsai/bonsai/bim/module/debug/operator.py
index 8f39bc95ac..af5c841761 100644
--- a/src/bonsai/bonsai/bim/module/debug/operator.py
+++ b/src/bonsai/bonsai/bim/module/debug/operator.py
@@ -42,7 +42,10 @@ from bpy_extras.io_utils import ImportHelper, ExportHelper
from pathlib import Path
from bonsai import get_debug_info, format_debug_info
from bonsai.bim.ifc import IfcStore
-from typing import get_args, Union
+from typing import get_args, Union, Any, TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from bonsai.bim.prop import Attribute
class CopyDebugInformation(bpy.types.Operator):
@@ -70,6 +73,7 @@ class CopyDebugInformation(bpy.types.Operator):
print(text_with_backticks)
print("-" * 80)
+ assert context.window_manager
context.window_manager.clipboard = text_with_backticks
return {"FINISHED"}
@@ -347,8 +351,9 @@ class SelectHighPolygonMeshes(bpy.types.Operator):
threshold: bpy.props.IntProperty()
def execute(self, context):
+ assert context.view_layer
for obj in context.view_layer.objects:
- if obj.type == "MESH" and len(obj.data.polygons) > self.threshold:
+ if isinstance(obj.data, bpy.types.Mesh) and len(obj.data.polygons) > self.threshold:
obj.select_set(True)
return {"FINISHED"}
@@ -361,6 +366,7 @@ class SelectHighestPolygonMeshes(bpy.types.Operator):
percentile: bpy.props.IntProperty()
def execute(self, context):
+ assert context.view_layer
objects = [obj for obj in context.view_layer.objects if obj.type == "MESH"]
if objects:
percentile = len(max(objects, key=lambda o: len(o.data.polygons)).data.polygons) * self.percentile / 100
@@ -423,7 +429,7 @@ class InspectFromStepId(bpy.types.Operator):
new.int_value = inverse.id()
return {"FINISHED"}
- def add_attribute(self, prop, key, value):
+ def add_attribute(self, prop: "bpy.types.bpy_prop_collection_idprop[Attribute]", key: str, value: Any) -> None:
if isinstance(value, tuple) and len(value) < 10:
for i, item in enumerate(value):
self.add_attribute(prop, key + f"[{i}]", item)
@@ -459,8 +465,10 @@ class InspectFromObject(bpy.types.Operator):
def poll(cls, context):
if not context.active_object:
cls.poll_message_set("No Active Object")
+ return False
elif not cls.get_active_object_ifc_definition(context):
cls.poll_message_set("Active Object doesn't have an IFC definition")
+ return False
else:
return True
@@ -492,6 +500,7 @@ class PrintObjectPlacement(bpy.types.Operator):
if self.create_empty_object:
bpy.ops.object.empty_add(type="ARROWS")
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
+ assert context.active_object
context.active_object.matrix_world = placement.transpose()
context.active_object.matrix_world.translation *= si_conversion
context.active_object.empty_display_size = self.arrow_size
@@ -1017,6 +1026,7 @@ class RestartBlender(bpy.types.Operator):
def execute(self, context):
# Save preferences manually since we're restarting Blender using .execv
# and it doens't have a chance to save them on exit.
+ assert context.preferences
if context.preferences.use_preferences_save:
bpy.ops.wm.save_userpref()
diff --git a/src/bonsai/bonsai/bim/module/debug/ui.py b/src/bonsai/bonsai/bim/module/debug/ui.py
index 12667d5e1a..14e257c40a 100644
--- a/src/bonsai/bonsai/bim/module/debug/ui.py
+++ b/src/bonsai/bonsai/bim/module/debug/ui.py
@@ -31,6 +31,7 @@ class BIM_PT_debug(Panel):
bl_parent_id = "BIM_PT_tab_quality_control"
def draw(self, context):
+ assert self.layout
layout = self.layout
props = tool.Debug.get_debug_props()
diff --git a/src/bonsai/bonsai/core/debug.py b/src/bonsai/bonsai/core/debug.py
index ccf569798c..7975bdfae3 100644
--- a/src/bonsai/bonsai/core/debug.py
+++ b/src/bonsai/bonsai/core/debug.py
@@ -26,15 +26,15 @@ if TYPE_CHECKING:
import bonsai.tool as tool
-def parse_express(debug: tool.Debug, filename: str) -> None:
+def parse_express(debug: type[tool.Debug], filename: str) -> None:
debug.add_schema_identifier(debug.load_express(filename))
-def purge_hdf5_cache(debug: tool.Debug) -> None:
+def purge_hdf5_cache(debug: type[tool.Debug]) -> None:
debug.purge_hdf5_cache()
-def purge_unused_elements(ifc, debug: tool.Debug, ifc_class: str) -> int:
+def purge_unused_elements(ifc: type[tool.Ifc], debug: type[tool.Debug], ifc_class: str) -> int:
ifc_file = ifc.get()
unused_elements = [i for i in ifc_file.by_type(ifc_class) if ifc_file.get_total_inverses(i) == 0]
unused_elements_amount = len(unused_elements)
diff --git a/src/bonsai/bonsai/core/profile.py b/src/bonsai/bonsai/core/profile.py
index a0ee00b6de..46e179229c 100644
--- a/src/bonsai/bonsai/core/profile.py
+++ b/src/bonsai/bonsai/core/profile.py
@@ -25,7 +25,7 @@ if TYPE_CHECKING:
import bonsai.tool as tool
-def purge_unused_profiles(ifc: tool.Ifc, profile: tool.Profile) -> int:
+def purge_unused_profiles(ifc: type[tool.Ifc], profile: type[tool.Profile]) -> int:
"""Purge profiles that have no inverses.
:return: Number of removed profiles.
diff --git a/src/bonsai/bonsai/core/style.py b/src/bonsai/bonsai/core/style.py
index 0c68c4373b..85bb4200aa 100644
--- a/src/bonsai/bonsai/core/style.py
+++ b/src/bonsai/bonsai/core/style.py
@@ -25,7 +25,7 @@ if TYPE_CHECKING:
import bonsai.tool as tool
-def add_style(ifc: tool.Ifc, style: tool.Style, obj: bpy.types.Material) -> ifcopenshell.entity_instance:
+def add_style(ifc: type[tool.Ifc], style: type[tool.Style], obj: bpy.types.Material) -> ifcopenshell.entity_instance:
element = ifc.run("style.add_style", name=style.get_name(obj))
ifc.link(element, obj)
if style.can_support_rendering_style(obj):
@@ -38,7 +38,9 @@ def add_style(ifc: tool.Ifc, style: tool.Style, obj: bpy.types.Material) -> ifco
# TODO: outdated.
-def add_external_style(ifc: tool.Ifc, style: tool.Style, obj: bpy.types.Material, attributes: dict[str, Any]) -> None:
+def add_external_style(
+ ifc: type[tool.Ifc], style: type[tool.Style], obj: bpy.types.Material, attributes: dict[str, Any]
+) -> None:
element = ifc.get_entity(obj)
ifc.run(
"style.add_surface_style", style=element, ifc_class="IfcExternallyDefinedSurfaceStyle", attributes=attributes
@@ -47,7 +49,7 @@ def add_external_style(ifc: tool.Ifc, style: tool.Style, obj: bpy.types.Material
# TODO: unused `style` argument?
def update_external_style(
- ifc: tool.Ifc,
+ ifc: type[tool.Ifc],
style: ifcopenshell.entity_instance,
external_style: ifcopenshell.entity_instance,
attributes: dict[str, Any],
@@ -56,7 +58,10 @@ def update_external_style(
def remove_style(
- ifc: tool.Ifc, style_tool: tool.Style, style: ifcopenshell.entity_instance, reload_styles_ui: bool = False
+ ifc: type[tool.Ifc],
+ style_tool: type[tool.Style],
+ style: ifcopenshell.entity_instance,
+ reload_styles_ui: bool = False,
) -> None:
"""Remove IfcPresentationStyle and associated Blender material.
@@ -78,7 +83,9 @@ def remove_style(
style_tool.import_presentation_styles(style_type)
-def update_style_colours(ifc: tool.Ifc, style: tool.Style, obj: bpy.types.Material, verbose: bool = False) -> None:
+def update_style_colours(
+ ifc: type[tool.Ifc], style: type[tool.Style], obj: bpy.types.Material, verbose: bool = False
+) -> None:
element = ifc.get_entity(obj)
if style.can_support_rendering_style(obj):
@@ -114,7 +121,10 @@ def update_style_colours(ifc: tool.Ifc, style: tool.Style, obj: bpy.types.Materi
def update_style_textures(
- ifc: tool.Ifc, style: tool.Style, obj: ifcopenshell.entity_instance, representation: ifcopenshell.entity_instance
+ ifc: type[tool.Ifc],
+ style: type[tool.Style],
+ obj: ifcopenshell.entity_instance,
+ representation: ifcopenshell.entity_instance,
) -> None:
element = ifc.get_entity(obj)
@@ -136,22 +146,22 @@ def update_style_textures(
# TODO: outdated.
-def unlink_style(ifc: tool.Ifc, style: ifcopenshell.entity_instance) -> None:
+def unlink_style(ifc: type[tool.Ifc], style: ifcopenshell.entity_instance) -> None:
ifc.unlink(element=style)
-def enable_editing_style(style_tool: tool.Style, style: ifcopenshell.entity_instance) -> None:
+def enable_editing_style(style_tool: type[tool.Style], style: ifcopenshell.entity_instance) -> None:
style_tool.enable_editing(style)
style_tool.import_surface_attributes(style)
-def disable_editing_style(style: tool.Style) -> None:
+def disable_editing_style(style: type[tool.Style]) -> None:
obj = style.get_currently_edited_material()
style.disable_editing()
style.reload_material_from_ifc(obj)
-def edit_style(ifc: tool.Ifc, style: tool.Style) -> None:
+def edit_style(ifc: type[tool.Ifc], style: type[tool.Style]) -> None:
obj = style.get_currently_edited_material()
style_element = ifc.get_entity(obj)
assert style_element
@@ -164,14 +174,16 @@ def edit_style(ifc: tool.Ifc, style: tool.Style) -> None:
style.reload_representations(style_element)
-def load_styles(style: tool.Style, style_type: str) -> None:
+def load_styles(style: type[tool.Style], style_type: str) -> None:
style.import_presentation_styles(style_type)
style.enable_editing_styles()
-def disable_editing_styles(style: tool.Style) -> None:
+def disable_editing_styles(style: type[tool.Style]) -> None:
style.disable_editing_styles()
-def select_by_style(style_tool: tool.Style, spatial: tool.Spatial, style: ifcopenshell.entity_instance) -> None:
+def select_by_style(
+ style_tool: type[tool.Style], spatial: type[tool.Spatial], style: ifcopenshell.entity_instance
+) -> None:
spatial.select_products(style_tool.get_elements_by_style(style))
diff --git a/src/bonsai/bonsai/core/type.py b/src/bonsai/bonsai/core/type.py
index a7c334dfd5..226f00c461 100644
--- a/src/bonsai/bonsai/core/type.py
+++ b/src/bonsai/bonsai/core/type.py
@@ -27,7 +27,10 @@ if TYPE_CHECKING:
def assign_type(
- ifc: tool.Ifc, type_tool: tool.Type, element: ifcopenshell.entity_instance, type: ifcopenshell.entity_instance
+ ifc: type[tool.Ifc],
+ type_tool: type[tool.Type],
+ element: ifcopenshell.entity_instance,
+ type: ifcopenshell.entity_instance,
) -> None:
ifc.run("type.assign_type", related_objects=[element], relating_type=type)
obj = ifc.get_object(element)
@@ -40,7 +43,7 @@ def assign_type(
type_tool.disable_editing(obj)
-def purge_unused_types(ifc: tool.Ifc, type: tool.Type, geometry: tool.Geometry) -> int:
+def purge_unused_types(ifc: type[tool.Ifc], type: type[tool.Type], geometry: type[tool.Geometry]) -> int:
"""Remove all types without occurrences, return an amount of the removed types."""
purged_types = 0
for element_type in type.get_model_types():
diff --git a/src/ifcopenshell-python/ifcopenshell/entity_instance.py b/src/ifcopenshell-python/ifcopenshell/entity_instance.py
index 53ef66cbd0..0029b75041 100644
--- a/src/ifcopenshell-python/ifcopenshell/entity_instance.py
+++ b/src/ifcopenshell-python/ifcopenshell/entity_instance.py
@@ -26,7 +26,7 @@ import operator
import subprocess
import sys
import time
-from typing import Union, Any, TypeVar, overload, TYPE_CHECKING
+from typing import Union, Any, TypeVar, overload, TYPE_CHECKING, cast, NoReturn
from collections.abc import Callable, Sequence
from . import ifcopenshell_wrapper
@@ -64,17 +64,20 @@ def set_unsupported_attribute(*args):
# done for each invocation of __setitem__. Now this
# mapping is built once during initialization of the
# module.
-_method_dict = {}
+MethodList = list[Callable[[ifcopenshell_wrapper.entity_instance, int, Any], Union[None, NoReturn]]]
+"""List of setter methods for class attributes."""
+_method_dict: dict[str, MethodList] = {}
+"""Mapping of entity classes (e.g. 'IFC4.IfcWall') to MethodLists."""
def register_schema_attributes(schema: ifcopenshell_wrapper.schema_definition) -> None:
for decl in schema.declarations():
- decl: ifcopenshell_wrapper.declaration
if hasattr(decl, "argument_types"):
fq_name = ".".join((schema.name(), decl.name()))
# get type strings as reported by IfcOpenShell C++
type_strs = decl.argument_types()
+ type_strs = cast(Sequence[str], type_strs)
# convert case for setter function
type_strs = [x.title().replace(" ", "") for x in type_strs]
diff --git a/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi b/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi
index d0e5c07f4f..eeb3812f78 100644
--- a/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi
+++ b/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see .
+import ifcopenshell
from typing import Any, Union, Literal
# `std::vector` usually translated to `tuple[xxx, ...]`.
@@ -775,6 +776,9 @@ class entity(declaration):
def supertype(self) -> Union[entity, None]: ...
class entity_instance:
+ file: ifcopenshell.file
+ """Reference to IFC file to prevent it's garbage collection, if entity is still used."""
+
file_: Any
id_: Any
def data(self, *args): ...
diff --git a/src/ifcopenshell-python/ifcopenshell/util/cost.py b/src/ifcopenshell-python/ifcopenshell/util/cost.py
index 3027897520..71af763d48 100644
--- a/src/ifcopenshell-python/ifcopenshell/util/cost.py
+++ b/src/ifcopenshell-python/ifcopenshell/util/cost.py
@@ -282,9 +282,10 @@ def get_cost_values(cost_item: ifcopenshell.entity_instance) -> list[dict[str, s
def get_cost_schedule_types(file: ifcopenshell.file) -> list[dict[str, str]]:
- schema: ifcopenshell_wrapper.schema_definition = ifcopenshell_wrapper.schema_by_name(file.schema_identifier)
+ schema = ifcopenshell_wrapper.schema_by_name(file.schema_identifier)
results = []
- declaration = schema.declaration_by_name("IfcCostSchedule")
+ declaration = schema.declaration_by_name("IfcCostSchedule").as_entity()
+ assert declaration
version = file.schema_identifier
for attribute in declaration.attributes():
if attribute.name() == "PredefinedType":
diff --git a/src/ifcopenshell-python/ifcopenshell/util/schema.py b/src/ifcopenshell-python/ifcopenshell/util/schema.py
index b634edab31..698a3a9987 100644
--- a/src/ifcopenshell-python/ifcopenshell/util/schema.py
+++ b/src/ifcopenshell-python/ifcopenshell/util/schema.py
@@ -136,8 +136,8 @@ def get_subtypes(
[, , ..., ]
"""
- def get_classes(decl):
- results = []
+ def get_classes(decl: ifcopenshell_wrapper.entity) -> list[ifcopenshell_wrapper.entity]:
+ results: list[ifcopenshell_wrapper.entity] = []
if not decl.is_abstract():
results.append(decl)
for subtype in decl.subtypes():
@@ -172,7 +172,7 @@ def reassign_class(
if not ifc_file:
ifc_file = element.file
- schema: ifcopenshell_wrapper.schema_definition = ifcopenshell_wrapper.schema_by_name(ifc_file.schema_identifier)
+ schema = ifcopenshell_wrapper.schema_by_name(ifc_file.schema_identifier)
try:
declaration = schema.declaration_by_name(new_class)
except RuntimeError:
diff --git a/src/ifcopenshell-python/ifcopenshell/util/unit.py b/src/ifcopenshell-python/ifcopenshell/util/unit.py
index fbebb799d0..b7e91c798b 100644
--- a/src/ifcopenshell-python/ifcopenshell/util/unit.py
+++ b/src/ifcopenshell-python/ifcopenshell/util/unit.py
@@ -815,12 +815,13 @@ def iter_element_and_attributes_per_type(ifc_file: ifcopenshell.file, attr_type_
None,
None,
]:
- schema: ifcopenshell_wrapper.schema_definition = ifcopenshell_wrapper.schema_by_name(ifc_file.schema_identifier)
+ schema = ifcopenshell_wrapper.schema_by_name(ifc_file.schema_identifier)
for element in ifc_file:
- entity = schema.declaration_by_name(element.is_a())
+ entity = schema.declaration_by_name(element.is_a()).as_entity()
+ assert entity
attrs = entity.all_attributes()
- attrs_derived: tuple[bool, ...] = entity.derived()
+ attrs_derived = entity.derived()
for attr, val, is_derived in zip(attrs, list(element), attrs_derived):
if is_derived:
continue