diff --git a/src/bonsai/bonsai/bim/export_ifc.py b/src/bonsai/bonsai/bim/export_ifc.py
index deafc5b3ff..84173a9ec5 100644
--- a/src/bonsai/bonsai/bim/export_ifc.py
+++ b/src/bonsai/bonsai/bim/export_ifc.py
@@ -132,7 +132,8 @@ class IfcExporter:
bpy.ops.bim.update_representation(obj=obj.name)
def has_changed_materials(self, obj: bpy.types.Object) -> bool:
- checksum = obj.data.BIMMeshProperties.material_checksum
+ mprops = tool.Geometry.get_mesh_props(obj.data)
+ checksum = mprops.material_checksum
return checksum != tool.Geometry.get_material_checksum(obj)
def sync_object_placement(self, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
diff --git a/src/bonsai/bonsai/bim/handler.py b/src/bonsai/bonsai/bim/handler.py
index d65017243f..572cfaf117 100644
--- a/src/bonsai/bonsai/bim/handler.py
+++ b/src/bonsai/bonsai/bim/handler.py
@@ -221,7 +221,8 @@ def refresh_ui_data():
if isinstance(tool.Ifc.get(), ifcopenshell.sqlite):
tool.Ifc.get().clear_cache()
- bpy.context.scene.DocProperties.should_draw_decorations = bpy.context.scene.DocProperties.should_draw_decorations
+ props = tool.Drawing.get_document_props()
+ props.should_draw_decorations = props.should_draw_decorations
if bpy.context.scene.WebProperties.is_connected:
tool.Web.send_webui_data()
@@ -343,7 +344,7 @@ def load_post(scene):
bpy.context.scene.BIMProperties.has_blend_warning = True
# Bonsai overlays
- georeference_props = bpy.context.scene.BIMGeoreferenceProperties
+ georeference_props = tool.Georeference.get_georeference_props()
aggregate_props = bpy.context.scene.BIMAggregateProperties
nest_props = bpy.context.scene.BIMNestProperties
model_props = tool.Model.get_model_props()
diff --git a/src/bonsai/bonsai/bim/ifc.py b/src/bonsai/bonsai/bim/ifc.py
index 1c5d7800b1..044bf74fd2 100644
--- a/src/bonsai/bonsai/bim/ifc.py
+++ b/src/bonsai/bonsai/bim/ifc.py
@@ -178,6 +178,7 @@ class IfcStore:
if not os.path.isfile(path):
return
extension = path.split(".")[-1]
+ props = tool.Project.get_project_props()
if extension.lower() == "ifczip":
with tempfile.TemporaryDirectory() as unzipped_path:
with zipfile.ZipFile(path, "r") as zip_ref:
@@ -187,7 +188,7 @@ class IfcStore:
return
elif extension.lower() == "ifcxml":
IfcStore.file = ifcopenshell.file(ifcopenshell.ifcopenshell_wrapper.parse_ifcxml(path))
- elif bpy.context.scene.BIMProjectProperties.should_stream:
+ elif props.should_stream:
IfcStore.file = ifcopenshell.open(path, should_stream=True)
else:
IfcStore.file = ifcopenshell.open(path)
@@ -195,9 +196,8 @@ class IfcStore:
@staticmethod
def get_schema() -> ifcopenshell.ifcopenshell_wrapper.schema_definition:
if IfcStore.file is None:
- IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(
- bpy.context.scene.BIMProjectProperties.export_schema
- )
+ props = tool.Project.get_project_props()
+ IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(props.export_schema)
elif IfcStore.schema is None:
IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(IfcStore.file.schema_identifier)
return IfcStore.schema
@@ -247,7 +247,7 @@ class IfcStore:
# refactor this class and deprecate usage of IfcStore in favour of
# tools.
if not isinstance(obj, (bpy.types.Object, bpy.types.Material)):
- obj.BIMMeshProperties.ifc_definition_id = element.id()
+ tool.Geometry.get_mesh_props(obj).ifc_definition_id = element.id()
return
existing_obj = IfcStore.id_map.get(element.id(), None)
diff --git a/src/bonsai/bonsai/bim/import_ifc.py b/src/bonsai/bonsai/bim/import_ifc.py
index d2bdc2ed70..13fce33a93 100644
--- a/src/bonsai/bonsai/bim/import_ifc.py
+++ b/src/bonsai/bonsai/bim/import_ifc.py
@@ -90,7 +90,8 @@ class MaterialCreator:
return # Already has materials assign to the representation itself
# Otherwise, we need to check for material styles on the element, since
# create_shape on types only works on representations.
- context = tool.Ifc.get().by_id(self.mesh.BIMMeshProperties.ifc_definition_id).ContextOfItems
+ mprops = tool.Geometry.get_mesh_props(self.mesh)
+ context = tool.Ifc.get().by_id(mprops.ifc_definition_id).ContextOfItems
for material in ifcopenshell.util.element.get_materials(element):
if style := ifcopenshell.util.representation.get_material_style(material, context):
self.mesh["ios_materials"] = (style.id(),)
@@ -218,7 +219,7 @@ class IfcImporter:
self.progress = 0
self.material_creator = MaterialCreator(ifc_import_settings, self)
- classes_to_wireframe_str = bpy.context.scene.DocProperties.classes_to_wireframe
+ classes_to_wireframe_str = tool.Drawing.get_document_props().classes_to_wireframe
self.classes_to_wireframe_list = [word.strip() for word in classes_to_wireframe_str.split(",")]
def profile_code(self, message: str) -> None:
@@ -434,7 +435,7 @@ class IfcImporter:
return False
def calculate_model_offset(self) -> None:
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
if self.ifc_import_settings.false_origin_mode == "MANUAL":
tool.Loader.set_manual_blender_offset(self.file)
elif self.ifc_import_settings.false_origin_mode == "AUTOMATIC":
diff --git a/src/bonsai/bonsai/bim/module/bcf/operator.py b/src/bonsai/bonsai/bim/module/bcf/operator.py
index bfcc40b8a9..aed65de52c 100644
--- a/src/bonsai/bonsai/bim/module/bcf/operator.py
+++ b/src/bonsai/bonsai/bim/module/bcf/operator.py
@@ -1316,7 +1316,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
[0, 0, 0, 1],
)
)
- props = context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
if props.has_blender_offset:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
matrix = ifcopenshell.util.geolocation.global2local(
diff --git a/src/bonsai/bonsai/bim/module/boundary/operator.py b/src/bonsai/bonsai/bim/module/boundary/operator.py
index 5d83a88597..75cf32cb66 100644
--- a/src/bonsai/bonsai/bim/module/boundary/operator.py
+++ b/src/bonsai/bonsai/bim/module/boundary/operator.py
@@ -114,7 +114,7 @@ class Loader:
bm.edges.new((verts[-1], verts[0]))
bm.to_mesh(mesh)
bm.free()
- mesh.BIMMeshProperties.ifc_definition_id = surface.id()
+ tool.Ifc.link(surface, mesh)
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
matrix = mathutils.Matrix(
ifcopenshell.util.placement.get_axis2placement(surface.BasisSurface.Position).tolist()
diff --git a/src/bonsai/bonsai/bim/module/cad/workspace.py b/src/bonsai/bonsai/bim/module/cad/workspace.py
index 854b34bb7d..4b70d04df1 100644
--- a/src/bonsai/bonsai/bim/module/cad/workspace.py
+++ b/src/bonsai/bonsai/bim/module/cad/workspace.py
@@ -69,10 +69,12 @@ class CadTool(WorkSpaceTool):
("bim.cad_hotkey", {"type": "X", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_X")]}),
)
- def draw_settings(context, layout, workspace_tool):
+ def draw_settings(
+ context: bpy.types.Context, layout: bpy.types.UILayout, workspace_tool: bpy.types.WorkSpaceTool
+ ) -> None:
ui_context = str(context.region.type)
obj = context.active_object
- if not obj or not obj.data:
+ if not obj or not (data := obj.data):
return
is_profile = tool.Geometry.is_profile_object_active()
if is_profile:
@@ -122,7 +124,10 @@ class CadTool(WorkSpaceTool):
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
add_layout_hotkey_operator(row, "Reset Vertex", "S_X", bpy.ops.bim.reset_vertex.__doc__, ui_context)
- elif hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.subshape_type == "AXIS":
+ elif (
+ isinstance(data, tool.Geometry.TYPES_WITH_MESH_PROPERTIES)
+ and tool.Geometry.get_mesh_props(data).subshape_type == "AXIS"
+ ):
add_header_apply_button(
layout, "Edit Axis", "bim.edit_extrusion_axis", "bim.disable_editing_extrusion_axis", ui_context
)
@@ -141,7 +146,7 @@ class CadTool(WorkSpaceTool):
if (
(RailingData.is_loaded or not RailingData.load())
and RailingData.data["pset_data"]
- and context.active_object.BIMRailingProperties.is_editing_path
+ and obj.BIMRailingProperties.is_editing_path
):
add_header_apply_button(
layout,
@@ -154,7 +159,7 @@ class CadTool(WorkSpaceTool):
elif (
(RoofData.is_loaded or not RoofData.load())
and RoofData.data["pset_data"]
- and context.active_object.BIMRoofProperties.is_editing_path
+ and obj.BIMRoofProperties.is_editing_path
):
add_header_apply_button(
layout, "Edit Roof Path", "bim.finish_editing_roof_path", "bim.cancel_editing_roof_path", ui_context
@@ -252,15 +257,27 @@ class CadHotkey(bpy.types.Operator):
bpy.ops.bim.cad_offset(distance=self.props.distance / si_conversion)
def hotkey_S_Q(self):
- element = tool.Ifc.get_entity(bpy.context.active_object)
- if bpy.context.active_object.data.BIMMeshProperties.subshape_type == "PROFILE":
+ obj = bpy.context.active_object
+
+ if not obj:
+ return
+
+ if not tool.Geometry.has_mesh_properties(data := obj.data):
+ return
+
+ element = tool.Ifc.get_entity(obj)
+ if not element:
+ return
+
+ mprops = tool.Geometry.get_mesh_props(data)
+ if mprops.subshape_type == "PROFILE":
if element.is_a("IfcProfileDef"):
bpy.ops.bim.edit_arbitrary_profile()
elif element.is_a("IfcRelSpaceBoundary"):
bpy.ops.bim.edit_boundary_geometry()
else:
bpy.ops.bim.edit_extrusion_profile()
- elif bpy.context.active_object.data.BIMMeshProperties.subshape_type == "AXIS":
+ elif mprops.subshape_type == "AXIS":
bpy.ops.bim.edit_extrusion_axis()
def hotkey_S_R(self):
diff --git a/src/bonsai/bonsai/bim/module/classification/operator.py b/src/bonsai/bonsai/bim/module/classification/operator.py
index 199fa2d61b..acdffae946 100644
--- a/src/bonsai/bonsai/bim/module/classification/operator.py
+++ b/src/bonsai/bonsai/bim/module/classification/operator.py
@@ -201,7 +201,8 @@ class EnableEditingClassification(bpy.types.Operator):
def execute(self, context):
def callback(name, prop, data):
if name == "ReferenceTokens":
- new = bpy.context.scene.BIMGeoreferenceProperties.projected_crs.add()
+ geo_props = tool.Georeference.get_georeference_props()
+ new = geo_props.projected_crs.add()
new.name = name
new.data_type = "string"
new.is_null = data[name] is None
diff --git a/src/bonsai/bonsai/bim/module/debug/operator.py b/src/bonsai/bonsai/bim/module/debug/operator.py
index 33100956ef..bb949b8fd0 100644
--- a/src/bonsai/bonsai/bim/module/debug/operator.py
+++ b/src/bonsai/bonsai/bim/module/debug/operator.py
@@ -101,13 +101,13 @@ class ConvertToBlender(bpy.types.Operator):
if tool.Geometry.has_mesh_properties(data):
if data.library:
continue
- data.BIMMeshProperties.ifc_definition_id = 0
+ tool.Geometry.get_mesh_props(data).ifc_definition_id = 0
for material in bpy.data.materials:
if material.library:
continue
tool.Ifc.unlink(obj=material)
context.scene.BIMProperties.ifc_file = ""
- context.scene.BIMDebugProperties.attributes.clear()
+ tool.Debug.get_debug_props().attributes.clear()
IfcStore.purge()
bonsai.bim.handler.refresh_ui_data()
return {"FINISHED"}
@@ -256,7 +256,7 @@ class CreateShapeFromStepId(bpy.types.Operator):
logger = logging.getLogger("ImportIFC")
self.ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
self.file = tool.Ifc.get()
- element = self.file.by_id(self.step_id or int(context.scene.BIMDebugProperties.step_id))
+ element = self.file.by_id(self.step_id or int(tool.Debug.get_debug_props().step_id))
settings = ifcopenshell.geom.settings()
settings.set("keep-bounding-boxes", True)
if self.should_include_curves:
@@ -309,7 +309,7 @@ class RewindInspector(bpy.types.Operator):
bl_description = "Rewind the Inspector to the previously inspected element"
def execute(self, context):
- props = context.scene.BIMDebugProperties
+ props = tool.Debug.get_debug_props()
total_breadcrumbs = len(props.step_id_breadcrumb)
if total_breadcrumbs < 2:
return {"FINISHED"}
@@ -332,9 +332,9 @@ class InspectFromStepId(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
- debug_props = context.scene.BIMDebugProperties
+ debug_props = tool.Debug.get_debug_props()
debug_props.active_step_id = self.step_id
- crumb = context.scene.BIMDebugProperties.step_id_breadcrumb.add()
+ crumb = debug_props.step_id_breadcrumb.add()
crumb.name = str(self.step_id)
element = self.file.by_id(self.step_id)
debug_props.attributes.clear()
@@ -385,7 +385,7 @@ class InspectFromObject(bpy.types.Operator):
if (
(data := obj.data)
and tool.Geometry.has_mesh_properties(data)
- and (ifc_id := data.BIMMeshProperties.ifc_definition_id)
+ and (ifc_id := tool.Geometry.get_mesh_props(data).ifc_definition_id)
):
return ifc_id
@@ -438,7 +438,8 @@ class ParseExpress(bpy.types.Operator):
bl_label = "Parse Express"
def execute(self, context):
- core.parse_express(tool.Debug, context.scene.BIMDebugProperties.express_file)
+ props = tool.Debug.get_debug_props()
+ core.parse_express(tool.Debug, props.express_file)
bonsai.bim.handler.refresh_ui_data()
return {"FINISHED"}
@@ -452,8 +453,9 @@ class SelectExpressFile(bpy.types.Operator):
filter_glob: bpy.props.StringProperty(default="*.exp", options={"HIDDEN"})
def execute(self, context):
+ props = tool.Debug.get_debug_props()
if os.path.exists(self.filepath) and "exp" in os.path.splitext(self.filepath)[1]:
- context.scene.BIMDebugProperties.express_file = self.filepath
+ props.express_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -497,7 +499,7 @@ class PrintUnusedElementStats(bpy.types.Operator):
ignore_styled_items: bpy.props.BoolProperty(name="Ignore Styled Items", default=True)
def execute(self, context):
- props = context.scene.BIMDebugProperties
+ props = tool.Debug.get_debug_props()
# ignore some classes that could have zero 0 inverse references by their nature
ignore_classes = []
if self.ignore_contexts:
@@ -543,7 +545,7 @@ class PurgeUnusedElementsByClass(bpy.types.Operator, tool.Ifc.Operator):
return True
def _execute(self, context):
- props = context.scene.BIMDebugProperties
+ props = tool.Debug.get_debug_props()
if props.ifc_class_purge:
purged_elements = core.purge_unused_elements(tool.Ifc, tool.Debug, props.ifc_class_purge)
self.report({"INFO"}, f"{purged_elements} unused elements found and removed.")
@@ -803,7 +805,7 @@ class DebugActiveDrawing(bpy.types.Operator):
)
def execute(self, context: bpy.types.Context):
- props = context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
drawing_item = props.drawings[props.active_drawing_index]
drawing = tool.Ifc.get().by_id(drawing_item.ifc_definition_id)
diff --git a/src/bonsai/bonsai/bim/module/debug/prop.py b/src/bonsai/bonsai/bim/module/debug/prop.py
index 01076ccde3..5a28b22f79 100644
--- a/src/bonsai/bonsai/bim/module/debug/prop.py
+++ b/src/bonsai/bonsai/bim/module/debug/prop.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
from bonsai.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
@@ -28,6 +29,9 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
+from typing import TYPE_CHECKING, Literal, get_args
+
+DisplayType = Literal["BOUNDS", "WIRE", "SOLID", "TEXTURED"]
class BIMDebugProperties(PropertyGroup):
@@ -41,14 +45,23 @@ class BIMDebugProperties(PropertyGroup):
inverse_references: CollectionProperty(name="Inverse References", type=Attribute)
express_file: StringProperty(name="Express File")
display_type: EnumProperty(
- items=[
- ("BOUNDS", "Bounds", ""),
- ("WIRE", "Wire", ""),
- ("SOLID", "Solid", ""),
- ("TEXTURED", "Textured", ""),
- ],
+ items=[(display_type, display_type.capitalize(), "") for display_type in get_args(DisplayType)],
name="Display Type",
default="BOUNDS",
)
ifc_class_purge: StringProperty(name="Unused Elements IFC Class", default="")
package_name: StringProperty(name="Package Name", default="")
+
+ if TYPE_CHECKING:
+ step_id: int
+ number_of_polygons: int
+ percentile_of_polygons: int
+ active_step_id: int
+ step_id_breadcrumb: bpy.types.bpy_prop_collection_idprop[StrProperty]
+ attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
+ inverse_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
+ inverse_references: bpy.types.bpy_prop_collection_idprop[Attribute]
+ express_file: str
+ display_type: str
+ ifc_class_purge: str
+ package_name: str
diff --git a/src/bonsai/bonsai/bim/module/debug/ui.py b/src/bonsai/bonsai/bim/module/debug/ui.py
index 3611920095..4fbd5c8ea6 100644
--- a/src/bonsai/bonsai/bim/module/debug/ui.py
+++ b/src/bonsai/bonsai/bim/module/debug/ui.py
@@ -17,6 +17,7 @@
# along with Bonsai. If not, see .
import bpy
+import bonsai.tool as tool
from bpy.types import Panel
@@ -32,7 +33,7 @@ class BIM_PT_debug(Panel):
def draw(self, context):
layout = self.layout
- props = context.scene.BIMDebugProperties
+ props = tool.Debug.get_debug_props()
row = self.layout.row(align=True)
row.prop(context.scene.BIMProperties, "ifc_file", text="")
@@ -87,27 +88,25 @@ class BIM_PT_debug(Panel):
row.prop(props, "step_id", text="")
row = layout.split(factor=0.7, align=True)
- row.operator("bim.select_high_polygon_meshes").threshold = context.scene.BIMDebugProperties.number_of_polygons
+ row.operator("bim.select_high_polygon_meshes").threshold = props.number_of_polygons
row.prop(props, "number_of_polygons", text="")
row = layout.split(factor=0.7, align=True)
- row.operator("bim.select_highest_polygon_meshes").percentile = (
- context.scene.BIMDebugProperties.percentile_of_polygons
- )
+ row.operator("bim.select_highest_polygon_meshes").percentile = props.percentile_of_polygons
row.prop(props, "percentile_of_polygons", text="")
row = layout.split(factor=0.5, align=True)
row.prop(props, "display_type", text="")
- row.operator("bim.override_display_type").display = context.scene.BIMDebugProperties.display_type
+ row.operator("bim.override_display_type").display = props.display_type
layout.operator("bim.purge_unused_representations")
row = layout.row(align=True)
- row.prop(context.scene.BIMDebugProperties, "ifc_class_purge", text="")
+ row.prop(props, "ifc_class_purge", text="")
row.operator("bim.purge_unused_elements_by_class", text="Purge Orphaned", icon="TRASH")
row.operator("bim.print_unused_elements_stats", text="", icon="INFO")
- if context.active_object and context.active_object.data:
- mprops = context.active_object.data.BIMMeshProperties
+ if context.active_object and (data := context.active_object.data):
+ mprops = tool.Geometry.get_mesh_props(data)
row = layout.row()
row.operator("bim.get_representation_ifc_parameters")
for index, ifc_parameter in enumerate(mprops.ifc_parameters):
@@ -123,7 +122,7 @@ class BIM_PT_debug(Panel):
row.operator("bim.rewind_inspector", icon="FRAME_PREV", text="")
row.prop(props, "active_step_id", text="")
row = layout.row(align=True)
- row.operator("bim.inspect_from_step_id").step_id = context.scene.BIMDebugProperties.active_step_id
+ row.operator("bim.inspect_from_step_id").step_id = props.active_step_id
row.operator("bim.inspect_from_object")
if props.attributes:
diff --git a/src/bonsai/bonsai/bim/module/drawing/data.py b/src/bonsai/bonsai/bim/module/drawing/data.py
index be252a29c9..d781b8bd30 100644
--- a/src/bonsai/bonsai/bim/module/drawing/data.py
+++ b/src/bonsai/bonsai/bim/module/drawing/data.py
@@ -88,7 +88,8 @@ class SheetsData:
project = tool.Ifc.get().by_type("IfcProject")[0]
titleblocks_dir = ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "TitleblocksDir")
if not titleblocks_dir:
- titleblocks_dir = bpy.context.scene.DocProperties.titleblocks_dir
+ props = tool.Drawing.get_document_props()
+ titleblocks_dir = props.titleblocks_dir
titleblocks_dir = tool.Ifc.resolve_uri(titleblocks_dir)
if os.path.exists(titleblocks_dir):
files.extend([str(f.stem) for f in Path(titleblocks_dir).glob("*.svg")])
@@ -120,23 +121,25 @@ class DrawingsData:
@classmethod
def location_hint(cls):
- if bpy.context.scene.DocProperties.target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]:
+ props = tool.Drawing.get_document_props()
+ if props.target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]:
results = [("0", "Origin", "")]
results.extend(
[(str(s.id()), s.Name or "Unnamed", "") for s in tool.Ifc.get().by_type("IfcBuildingStorey")]
)
return results
- elif bpy.context.scene.DocProperties.target_view in ["MODEL_VIEW"]:
+ elif props.target_view in ["MODEL_VIEW"]:
return [(h.upper(), h, "") for h in ["Orthographic", "Perspective"]]
return [(h.upper(), h, "") for h in ["North", "South", "East", "West"]]
@classmethod
def active_drawing_pset_data(cls):
ifc_file = tool.Ifc.get()
- drawing_id = bpy.context.scene.DocProperties.active_drawing_id
+ props = tool.Drawing.get_document_props()
+ drawing_id = props.active_drawing_id
if drawing_id == 0:
return {}
- drawing = ifc_file.by_id(bpy.context.scene.DocProperties.active_drawing_id)
+ drawing = ifc_file.by_id(drawing_id)
return ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing")
diff --git a/src/bonsai/bonsai/bim/module/drawing/decoration.py b/src/bonsai/bonsai/bim/module/drawing/decoration.py
index e8fba1914d..0bf8ab00b8 100644
--- a/src/bonsai/bonsai/bim/module/drawing/decoration.py
+++ b/src/bonsai/bonsai/bim/module/drawing/decoration.py
@@ -423,7 +423,8 @@ class BaseDecorator:
# font_size = 16 <-- this is a good default
# TODO: need to synchronize it better with svg
- magic_font_scale = bpy.context.scene.DocProperties.magic_font_scale
+ props = tool.Drawing.get_document_props()
+ magic_font_scale = props.magic_font_scale
font_size_px = int(magic_font_scale * mm_to_px) * font_size_mm / 2.5
pos = pos - line_no * font_size_px * rotation_matrix[1]
@@ -2022,7 +2023,8 @@ class DecorationsHandler:
for object_type in ("SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"):
self.decorators[object_type] = self.decorators["FALL"]
self.decorators["MULTI_SYMBOL"] = self.decorators["SYMBOL"]
- if drawing_font := bpy.context.scene.DocProperties.drawing_font:
+ props = tool.Drawing.get_document_props()
+ if drawing_font := props.drawing_font:
drawing_font_path = tool.Blender.get_data_dir_path(Path("fonts") / drawing_font)
if drawing_font_path.is_file():
font_id = blf.load(drawing_font_path.__str__())
diff --git a/src/bonsai/bonsai/bim/module/drawing/gizmos.py b/src/bonsai/bonsai/bim/module/drawing/gizmos.py
index 8e4f9bb97e..952ea0cb96 100644
--- a/src/bonsai/bonsai/bim/module/drawing/gizmos.py
+++ b/src/bonsai/bonsai/bim/module/drawing/gizmos.py
@@ -19,6 +19,7 @@
import bpy
import blf
import gpu
+import bonsai.tool as tool
from bpy import types
from mathutils import Vector
from mathutils import geometry
@@ -478,21 +479,25 @@ class ExtrusionWidget(types.GizmoGroup):
bl_options = {"3D", "PERSISTENT", "SHOW_MODAL_ALL"}
@classmethod
- def poll(cls, ctx):
- obj = ctx.object
+ def poll(cls, context):
+ obj = context.active_object
return (
obj
- and obj.type == "MESH"
- and obj.data.BIMMeshProperties.ifc_parameters.get("IfcExtrudedAreaSolid/Depth") is not None
+ and (data := obj.data)
+ and isinstance(data, bpy.types.Mesh)
+ and tool.Geometry.get_mesh_props(data).ifc_parameters.get("IfcExtrudedAreaSolid/Depth") is not None
)
- def setup(self, ctx):
- target = ctx.object
- prop = target.data.BIMMeshProperties.ifc_parameters.get("IfcExtrudedAreaSolid/Depth")
+ def setup(self, context: bpy.types.Context) -> None:
+ target = context.object
+ assert target
+ mesh = target.data
+ assert isinstance(mesh, bpy.types.Mesh)
+ prop = tool.Geometry.get_mesh_props(mesh).ifc_parameters.get("IfcExtrudedAreaSolid/Depth")
basis = target.matrix_world.normalized()
- theme = ctx.preferences.themes[0].user_interface
- scale_value = self.get_scale_value(ctx.scene.unit_settings.system, ctx.scene.unit_settings.length_unit)
+ theme = context.preferences.themes[0].user_interface
+ scale_value = self.get_scale_value(context.scene.unit_settings.system, context.scene.unit_settings.length_unit)
# setup handle
gz = self.handle = self.gizmos.new("BIM_GT_uglydot_3d")
@@ -521,23 +526,26 @@ class ExtrusionWidget(types.GizmoGroup):
# gz.use_draw_modal = True
# gz.target_set_prop('value', target.demo, 'depth')
- def refresh(self, ctx):
+ def refresh(self, context: bpy.types.Context) -> None:
"""updating gizmos"""
- target = ctx.object
+ target = context.active_object
basis = target.matrix_world.normalized()
self.handle.matrix_basis = basis
self.guides.matrix_basis = basis
- def update(self, ctx):
+ def update(self, context: bpy.types.Context) -> None:
"""updating object"""
bpy.ops.bim.update_parametric_representation()
- target = ctx.object
- prop = target.data.BIMMeshProperties.ifc_parameters.get("IfcExtrudedAreaSolid/Depth")
+ target = context.active_object
+ assert target
+ mesh = target.data
+ assert isinstance(mesh, bpy.types.Mesh)
+ prop = tool.Geometry.get_mesh_props(mesh).ifc_parameters.get("IfcExtrudedAreaSolid/Depth")
self.handle.target_set_prop("offset", prop, "value")
self.guides.target_set_prop("depth", prop, "value")
@staticmethod
- def get_scale_value(system, length_unit):
+ def get_scale_value(system: str, length_unit: str) -> float:
scale_value = 1
if system == "METRIC":
if length_unit == "KILOMETERS":
diff --git a/src/bonsai/bonsai/bim/module/drawing/handler.py b/src/bonsai/bonsai/bim/module/drawing/handler.py
index abfc8bb261..d1622d1908 100644
--- a/src/bonsai/bonsai/bim/module/drawing/handler.py
+++ b/src/bonsai/bonsai/bim/module/drawing/handler.py
@@ -24,7 +24,8 @@ from bpy.app.handlers import persistent
@persistent
def load_post(*args):
- if bpy.context.scene.DocProperties.should_draw_decorations:
+ props = tool.Drawing.get_document_props()
+ if props.should_draw_decorations:
decoration.DecorationsHandler.install(bpy.context)
else:
decoration.DecorationsHandler.uninstall()
@@ -35,9 +36,11 @@ def depsgraph_update_pre_handler(scene):
set_active_camera_resolution(scene)
-def set_active_camera_resolution(scene):
- if not scene.camera or "/" not in scene.camera.name or not scene.DocProperties.drawings:
+def set_active_camera_resolution(scene: bpy.types.Scene) -> None:
+ props = tool.Drawing.get_document_props()
+ if not scene.camera or "/" not in scene.camera.name or not props.drawings:
return
+ assert isinstance(scene.camera.data, bpy.types.Camera)
props = scene.camera.data.BIMCameraProperties
ortho_scale = max((props.width, props.height))
aspect_ratio = props.width / props.height
@@ -60,5 +63,3 @@ def set_active_camera_resolution(scene):
scene.render.resolution_x = scene.camera.data.BIMCameraProperties.raster_x = int(raster_x)
scene.render.resolution_y = scene.camera.data.BIMCameraProperties.raster_y = int(raster_y)
-
- current_drawing = scene.DocProperties.drawings[scene.DocProperties.current_drawing_index]
diff --git a/src/bonsai/bonsai/bim/module/drawing/helper.py b/src/bonsai/bonsai/bim/module/drawing/helper.py
index 720459f85b..c63fd3d910 100644
--- a/src/bonsai/bonsai/bim/module/drawing/helper.py
+++ b/src/bonsai/bonsai/bim/module/drawing/helper.py
@@ -22,6 +22,7 @@ import mathutils.geometry
import ifcopenshell
import bonsai.tool as tool
from mathutils import Vector
+from typing import Union
# Code taken and updated from https://blenderartists.org/t/detecting-intersection-of-bounding-boxes/457520/2
@@ -136,7 +137,9 @@ def format_distance(
scaleFactor = bpy.context.scene.unit_settings.scale_length
unit_system = bpy.context.scene.unit_settings.system
unit_length = bpy.context.scene.unit_settings.length_unit
- area_unit_symbol = " " + ifcopenshell.util.unit.get_unit_symbol(ifcopenshell.util.unit.get_project_unit(tool.Ifc.get(), "AREAUNIT"))
+ area_unit_symbol = " " + ifcopenshell.util.unit.get_unit_symbol(
+ ifcopenshell.util.unit.get_project_unit(tool.Ifc.get(), "AREAUNIT")
+ )
value *= scaleFactor
@@ -326,16 +329,18 @@ def format_distance(
fmt += area_unit_symbol
d_cm = value * (1000000)
tx_dist = fmt % d_cm
-
+
else:
tx_dist = fmt % value
return tx_dist
-def get_active_drawing(scene):
+def get_active_drawing(
+ scene: bpy.types.Scene,
+) -> Union[tuple[bpy.types.Collection, bpy.types.Camera], tuple[None, None]]:
"""Get active drawing collection and camera"""
- props = scene.DocProperties
+ props = tool.Drawing.get_document_props()
try:
camera = tool.Ifc.get_object(tool.Ifc.get().by_id(props.active_drawing_id))
return camera.BIMObjectProperties.collection, camera
diff --git a/src/bonsai/bonsai/bim/module/drawing/operator.py b/src/bonsai/bonsai/bim/module/drawing/operator.py
index 950d701e4a..6bac483f21 100644
--- a/src/bonsai/bonsai/bim/module/drawing/operator.py
+++ b/src/bonsai/bonsai/bim/module/drawing/operator.py
@@ -134,19 +134,19 @@ class AddDrawing(bpy.types.Operator, tool.Ifc.Operator):
bl_description = "Add a drawing view to the IFC project"
def _execute(self, context):
- self.props = context.scene.DocProperties
- hint = self.props.location_hint
- if self.props.target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]:
+ props = tool.Drawing.get_document_props()
+ hint = props.location_hint
+ if props.target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]:
hint = int(hint)
core.add_drawing(
tool.Ifc,
tool.Collector,
tool.Drawing,
- target_view=self.props.target_view,
+ target_view=props.target_view,
location_hint=hint,
)
try:
- drawing = tool.Ifc.get().by_id(self.props.active_drawing_id)
+ drawing = tool.Ifc.get().by_id(props.active_drawing_id)
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing)
except:
pass
@@ -162,7 +162,7 @@ class DuplicateDrawing(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
- props = context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.")
return False
@@ -176,7 +176,7 @@ class DuplicateDrawing(bpy.types.Operator, tool.Ifc.Operator):
row.prop(self, "should_duplicate_annotations")
def _execute(self, context):
- self.props = context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
core.duplicate_drawing(
tool.Ifc,
tool.Drawing,
@@ -184,7 +184,7 @@ class DuplicateDrawing(bpy.types.Operator, tool.Ifc.Operator):
should_duplicate_annotations=self.should_duplicate_annotations,
)
try:
- drawing = tool.Ifc.get().by_id(self.props.active_drawing_id)
+ drawing = tool.Ifc.get().by_id(props.active_drawing_id)
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing)
except:
pass
@@ -244,7 +244,7 @@ class CreateDrawing(bpy.types.Operator):
return self.execute(context)
def execute(self, context):
- self.props = context.scene.DocProperties
+ self.props = tool.Drawing.get_document_props()
active_drawing_id = context.scene.camera.BIMObjectProperties.ifc_definition_id
if self.print_all:
@@ -385,7 +385,7 @@ class CreateDrawing(bpy.types.Operator):
obj.hide_render = obj.name not in visible_object_names
context.scene.render.filepath = str(Path(svg_path).with_suffix(".png"))
- drawing_style = context.scene.DocProperties.drawing_styles[self.cprops.active_drawing_style_index]
+ drawing_style = self.props.drawing_styles[self.cprops.active_drawing_style_index]
if drawing_style.render_type == "DEFAULT":
bpy.ops.render.render(write_still=True)
@@ -714,7 +714,8 @@ class CreateDrawing(bpy.types.Operator):
files = {context.scene.BIMProperties.ifc_file: tool.Ifc.get()}
- for link in context.scene.BIMProjectProperties.links:
+ props = tool.Project.get_project_props()
+ for link in props.links:
if link.name not in IfcStore.session_files:
IfcStore.session_files[link.name] = ifcopenshell.open(link.name)
files[link.name] = IfcStore.session_files[link.name]
@@ -1141,7 +1142,8 @@ class CreateDrawing(bpy.types.Operator):
try:
return tool.Ifc.get().by_guid(guid)
except:
- for link in bpy.context.scene.BIMProjectProperties.links:
+ props = tool.Project.get_project_props()
+ for link in props.links:
if link.name not in IfcStore.session_files:
IfcStore.session_files[link.name] = ifcopenshell.open(link.name)
try:
@@ -1458,7 +1460,8 @@ class AddSheet(bpy.types.Operator, tool.Ifc.Operator):
bl_description = "Add a sheet to the project"
def _execute(self, context):
- core.add_sheet(tool.Ifc, tool.Drawing, titleblock=context.scene.DocProperties.titleblock)
+ props = tool.Drawing.get_document_props()
+ core.add_sheet(tool.Ifc, tool.Drawing, titleblock=props.titleblock)
class DuplicateSheet(bpy.types.Operator, tool.Ifc.Operator):
@@ -1474,7 +1477,7 @@ class DuplicateSheet(bpy.types.Operator, tool.Ifc.Operator):
cls.poll_message_set("Not implemented yet.")
return False
- props = context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.")
return False
@@ -1483,7 +1486,7 @@ class DuplicateSheet(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
pass
"""
- self.props = context.scene.DocProperties
+ self.props = tool.Drawing.get_document_props()
core.duplicate_sheet(
tool.Ifc,
tool.Drawing,
@@ -1508,7 +1511,7 @@ class OpenLayout(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
- self.props = context.scene.DocProperties
+ self.props = tool.Drawing.get_document_props()
sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id)
sheet_builder = sheeter.SheetBuilder()
sheet_builder.update_sheet_drawing_sizes(sheet)
@@ -1530,7 +1533,8 @@ class SelectAllSheets(bpy.types.Operator):
return self.execute(context)
def execute(self, context):
- for sheet in context.scene.DocProperties.sheets:
+ props = tool.Drawing.get_document_props()
+ for sheet in props.sheets:
if sheet.is_selected != self.select_all:
sheet.is_selected = self.select_all
return {"FINISHED"}
@@ -1550,7 +1554,7 @@ class OpenSheet(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
- props = context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_sheet_item(is_sheet=True):
cls.poll_message_set("No sheet selected.")
return False
@@ -1564,7 +1568,7 @@ class OpenSheet(bpy.types.Operator, tool.Ifc.Operator):
return self.execute(context)
def execute(self, context):
- self.props = context.scene.DocProperties
+ self.props = tool.Drawing.get_document_props()
svg2pdf_command = tool.Blender.get_addon_preferences().svg2pdf_command
if self.open_all:
@@ -1612,7 +1616,7 @@ class AddDrawingToSheet(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
- props = context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
# Won't be visible in UI anyway.
if not props.sheets or not context.scene.BIMProperties.data_dir:
return False
@@ -1622,8 +1626,8 @@ class AddDrawingToSheet(bpy.types.Operator, tool.Ifc.Operator):
return True
def _execute(self, context):
- props = context.scene.DocProperties
- active_drawing = tool.Drawing.get_active_drawing_item()
+ props = tool.Drawing.get_document_props()
+ active_drawing = props.drawings[props.active_drawing_index]
assert active_drawing
active_sheet = tool.Drawing.get_active_sheet(context)
@@ -1680,7 +1684,7 @@ class RemoveDrawingFromSheet(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
- props = context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
active_item = tool.Drawing.get_active_sheet_item()
if active_item is None:
return False
@@ -1714,7 +1718,7 @@ class CreateSheets(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
- props = context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_sheet_item(is_sheet=True):
cls.poll_message_set("No sheet selected.")
return False
@@ -1731,7 +1735,7 @@ class CreateSheets(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
scene = context.scene
- props = scene.DocProperties
+ props = tool.Drawing.get_document_props()
svg2pdf_command = tool.Blender.get_addon_preferences().svg2pdf_command
svg2dxf_command = tool.Blender.get_addon_preferences().svg2dxf_command
@@ -1838,7 +1842,8 @@ class SelectAllDrawings(bpy.types.Operator):
return self.execute(context)
def execute(self, context):
- for drawing in context.scene.DocProperties.drawings:
+ props = tool.Drawing.get_document_props()
+ for drawing in props.drawings:
if drawing.is_selected != self.select_all:
drawing.is_selected = self.select_all
return {"FINISHED"}
@@ -1857,7 +1862,7 @@ class OpenDrawing(bpy.types.Operator):
@classmethod
def poll(cls, context):
- props = context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.")
return False
@@ -1871,7 +1876,7 @@ class OpenDrawing(bpy.types.Operator):
return self.execute(context)
def execute(self, context):
- self.props = context.scene.DocProperties
+ self.props = tool.Drawing.get_document_props()
if self.open_all:
drawings = [
tool.Ifc.get().by_id(d.ifc_definition_id) for d in self.props.drawings if d.is_drawing and d.is_selected
@@ -1907,7 +1912,7 @@ class ActivateModel(bpy.types.Operator):
bl_description = "Activate the model view, hide all annotations"
def execute(self, context):
- dprops = bpy.context.scene.DocProperties
+ dprops = tool.Drawing.get_document_props()
dprops.active_drawing_id = 0
CutDecorator.uninstall()
@@ -1971,11 +1976,12 @@ class ActivateDrawingBase:
return self.execute(context)
def execute(self, context):
- if bpy.context.scene.DocProperties.is_editing_drawings == False:
+ props = tool.Drawing.get_document_props()
+ if props.is_editing_drawings == False:
bpy.ops.bim.load_drawings()
drawing = tool.Ifc.get().by_id(self.drawing)
- dprops = bpy.context.scene.DocProperties
+ dprops = tool.Drawing.get_document_props()
if self.use_quick_preview:
tool.Blender.activate_camera(tool.Drawing.import_temporary_drawing_camera(drawing))
@@ -2027,7 +2033,7 @@ class ActivateDrawing(bpy.types.Operator, ActivateDrawingBase):
@classmethod
def poll(cls, context):
- props = context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.")
return False
@@ -2050,7 +2056,7 @@ class ActivateDrawingFromSheet(bpy.types.Operator, ActivateDrawingBase):
@classmethod
def poll(cls, context):
- props = context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_sheet_item(reference_type="DRAWING"):
cls.poll_message_set("No drawing selected.")
return False
@@ -2066,7 +2072,8 @@ class SelectDocIfcFile(bpy.types.Operator):
index: bpy.props.IntProperty()
def execute(self, context):
- context.scene.DocProperties.ifc_files[self.index].name = self.filepath
+ props = tool.Drawing.get_document_props()
+ props.ifc_files[self.index].name = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -2098,7 +2105,7 @@ class RemoveDrawing(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
- props = context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.")
return False
@@ -2112,12 +2119,10 @@ class RemoveDrawing(bpy.types.Operator, tool.Ifc.Operator):
return self.execute(context)
def _execute(self, context):
- props = context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
if self.remove_all:
drawings = [
- tool.Ifc.get().by_id(d.ifc_definition_id)
- for d in context.scene.DocProperties.drawings
- if d.is_drawing and d.is_selected
+ tool.Ifc.get().by_id(d.ifc_definition_id) for d in props.drawings if d.is_drawing and d.is_selected
]
else:
if not self.drawing:
@@ -2187,7 +2192,8 @@ class ReloadDrawingStyles(bpy.types.Operator):
with open(json_path, "r") as fi:
shading_styles_json = json.load(fi)
- drawing_styles = context.scene.DocProperties.drawing_styles
+ props = tool.Drawing.get_document_props()
+ drawing_styles = props.drawing_styles
drawing_styles.clear()
styles = [style for style in shading_styles_json]
for style_name in styles:
@@ -2212,7 +2218,8 @@ class AddDrawingStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- drawing_styles = context.scene.DocProperties.drawing_styles
+ props = tool.Drawing.get_document_props()
+ drawing_styles = props.drawing_styles
new = drawing_styles.add()
# drawing style is saved to ifc on rename
new.name = tool.Blender.ensure_unique_name("New Drawing Style", drawing_styles)
@@ -2227,7 +2234,8 @@ class RemoveDrawingStyle(bpy.types.Operator, tool.Ifc.Operator):
index: bpy.props.IntProperty()
def execute(self, context):
- context.scene.DocProperties.drawing_styles.remove(self.index)
+ props = tool.Drawing.get_document_props()
+ props.drawing_styles.remove(self.index)
context.scene.camera.data.BIMCameraProperties.active_drawing_style_index = max(self.index - 1, 0)
bpy.ops.bim.save_drawing_styles_data()
return {"FINISHED"}
@@ -2283,7 +2291,8 @@ class SaveDrawingStyle(bpy.types.Operator, tool.Ifc.Operator):
index = int(self.index)
else:
index = context.scene.camera.data.BIMCameraProperties.active_drawing_style_index
- scene.DocProperties.drawing_styles[index].raster_style = json.dumps(style)
+ props = tool.Drawing.get_document_props()
+ props.drawing_styles[index].raster_style = json.dumps(style)
bpy.ops.bim.save_drawing_styles_data()
return {"FINISHED"}
@@ -2309,7 +2318,8 @@ class SaveDrawingStylesData(bpy.types.Operator, tool.Ifc.Operator):
if not DrawingsData.is_loaded:
DrawingsData.load()
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
- drawing_styles = context.scene.DocProperties.drawing_styles
+ props = tool.Drawing.get_document_props()
+ drawing_styles = props.drawing_styles
rel_path = drawing_pset_data["ShadingStyles"]
current_style = drawing_pset_data.get("CurrentShadingStyle", None)
@@ -2338,7 +2348,7 @@ class SaveDrawingStylesData(bpy.types.Operator, tool.Ifc.Operator):
new_style_name = None
ifc_file = tool.Ifc.get()
- drawing = ifc_file.by_id(context.scene.DocProperties.active_drawing_id)
+ drawing = ifc_file.by_id(props.active_drawing_id)
pset = tool.Pset.get_element_pset(drawing, "EPset_Drawing")
ifcopenshell.api.run(
"pset.edit_pset", ifc_file, pset=pset, properties={"CurrentShadingStyle": new_style_name}
@@ -2357,17 +2367,18 @@ class ActivateDrawingStyle(bpy.types.Operator, tool.Ifc.Operator):
scene = context.scene
ifc_file = tool.Ifc.get()
active_drawing_style_index = scene.camera.data.BIMCameraProperties.active_drawing_style_index
+ props = tool.Drawing.get_document_props()
- if active_drawing_style_index >= len(scene.DocProperties.drawing_styles):
+ if active_drawing_style_index >= len(props.drawing_styles):
self.report({"ERROR"}, "Could not find active drawing style")
return {"CANCELLED"}
- self.drawing_style = scene.DocProperties.drawing_styles[active_drawing_style_index]
+ self.drawing_style = props.drawing_styles[active_drawing_style_index]
self.set_raster_style(context)
self.set_query(context)
- drawing = ifc_file.by_id(scene.DocProperties.active_drawing_id)
+ drawing = ifc_file.by_id(props.active_drawing_id)
pset = tool.Pset.get_element_pset(drawing, "EPset_Drawing")
ifcopenshell.api.run(
"pset.edit_pset", ifc_file, pset=pset, properties={"CurrentShadingStyle": self.drawing_style.name}
@@ -2392,7 +2403,8 @@ class ActivateDrawingStyle(bpy.types.Operator, tool.Ifc.Operator):
def set_query(self, context: bpy.types.Context) -> None:
self.include_global_ids = []
self.exclude_global_ids = []
- for ifc_file in context.scene.DocProperties.ifc_files:
+ props = tool.Drawing.get_document_props()
+ for ifc_file in props.ifc_files:
try:
ifc = ifcopenshell.open(ifc_file.name)
except:
@@ -2507,14 +2519,14 @@ class AddScheduleToSheet(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
- props = context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
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
def _execute(self, context):
- props = context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
active_schedule = props.schedules[props.active_schedule_index]
active_sheet = tool.Drawing.get_active_sheet(context)
schedule = tool.Ifc.get().by_id(active_schedule.ifc_definition_id)
@@ -2573,14 +2585,14 @@ class AddReferenceToSheet(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
- props = context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
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
def _execute(self, context):
- props = context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
active_reference = props.references[props.active_reference_index]
active_sheet = tool.Drawing.get_active_sheet(context)
extref = tool.Ifc.get().by_id(active_reference.ifc_definition_id)
@@ -2682,7 +2694,8 @@ class AddDrawingStyleAttribute(bpy.types.Operator):
def execute(self, context):
props = context.scene.camera.data.BIMCameraProperties
- context.scene.DocProperties.drawing_styles[props.active_drawing_style_index].attributes.add()
+ dprops = tool.Drawing.get_document_props()
+ dprops.drawing_styles[props.active_drawing_style_index].attributes.add()
return {"FINISHED"}
@@ -2695,7 +2708,8 @@ class RemoveDrawingStyleAttribute(bpy.types.Operator):
def execute(self, context):
props = context.scene.camera.data.BIMCameraProperties
- context.scene.DocProperties.drawing_styles[props.active_drawing_style_index].attributes.remove(self.index)
+ dprops = tool.Drawing.get_document_props()
+ dprops.drawing_styles[props.active_drawing_style_index].attributes.remove(self.index)
return {"FINISHED"}
@@ -2979,7 +2993,7 @@ class LoadSheets(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
core.load_sheets(tool.Drawing)
- props = context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
sheets_not_found = []
for sheet_prop in props.sheets:
if not sheet_prop.is_sheet:
@@ -3009,7 +3023,7 @@ class EditSheet(bpy.types.Operator, tool.Ifc.Operator):
document_type: Literal["SHEET", "TITLEBLOCK", "EMBEDDED"]
def invoke(self, context, event):
- self.props = context.scene.DocProperties
+ self.props = tool.Drawing.get_document_props()
sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id)
if sheet.is_a("IfcDocumentInformation"):
self.document_type = "SHEET"
@@ -3023,6 +3037,7 @@ class EditSheet(bpy.types.Operator, tool.Ifc.Operator):
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
+ props = tool.Drawing.get_document_props()
if self.document_type == "SHEET":
row = self.layout.row()
row.prop(self, "identification", text="Identification")
@@ -3030,13 +3045,13 @@ class EditSheet(bpy.types.Operator, tool.Ifc.Operator):
row.prop(self, "name", text="Name")
elif self.document_type == "TITLEBLOCK":
row = self.layout.row()
- row.prop(context.scene.DocProperties, "titleblock", text="Titleblock")
+ row.prop(props, "titleblock", text="Titleblock")
elif self.document_type == "EMBEDDED":
row = self.layout.row()
row.prop(self, "identification", text="Identification")
def _execute(self, context):
- self.props = context.scene.DocProperties
+ self.props = tool.Drawing.get_document_props()
sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id)
if self.document_type == "SHEET":
core.rename_sheet(tool.Ifc, tool.Drawing, sheet=sheet, identification=self.identification, name=self.name)
@@ -3134,7 +3149,7 @@ class ExpandTargetView(bpy.types.Operator):
target_view: bpy.props.StringProperty()
def execute(self, context):
- props = context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
for drawing in [d for d in props.drawings if d.target_view == self.target_view]:
drawing.is_expanded = True
core.load_drawings(tool.Drawing)
@@ -3150,7 +3165,7 @@ class ContractTargetView(bpy.types.Operator):
target_view: bpy.props.StringProperty()
def execute(self, context):
- props = context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
for drawing in [d for d in props.drawings if d.target_view == self.target_view]:
drawing.is_expanded = False
core.load_drawings(tool.Drawing)
@@ -3166,7 +3181,7 @@ class ExpandSheet(bpy.types.Operator):
sheet: bpy.props.IntProperty()
def execute(self, context):
- props = context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
for sheet in [s for s in props.sheets if s.ifc_definition_id == self.sheet]:
sheet.is_expanded = True
core.load_sheets(tool.Drawing)
@@ -3182,7 +3197,7 @@ class ContractSheet(bpy.types.Operator):
sheet: bpy.props.IntProperty()
def execute(self, context):
- props = context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
for sheet in [s for s in props.sheets if s.ifc_definition_id == self.sheet]:
sheet.is_expanded = False
core.load_sheets(tool.Drawing)
@@ -3373,7 +3388,7 @@ class ConvertSVGToDXF(bpy.types.Operator):
@classmethod
def poll(cls, context):
- props = context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.")
return False
@@ -3387,14 +3402,13 @@ class ConvertSVGToDXF(bpy.types.Operator):
return self.execute(context)
def execute(self, context):
+ props = tool.Drawing.get_document_props()
if self.convert_all:
drawings = [
- tool.Ifc.get().by_id(d.ifc_definition_id)
- for d in context.scene.DocProperties.drawings
- if d.is_drawing and d.is_selected
+ tool.Ifc.get().by_id(d.ifc_definition_id) for d in props.drawings if d.is_drawing and d.is_selected
]
else:
- drawings = [tool.Ifc.get().by_id(context.scene.DocProperties.drawings.get(self.view).ifc_definition_id)]
+ drawings = [tool.Ifc.get().by_id(props.drawings.get(self.view).ifc_definition_id)]
drawing_uris: list[Path] = []
drawings_not_found: list[str] = []
diff --git a/src/bonsai/bonsai/bim/module/drawing/prop.py b/src/bonsai/bonsai/bim/module/drawing/prop.py
index 02ce195100..6c875d1cfe 100644
--- a/src/bonsai/bonsai/bim/module/drawing/prop.py
+++ b/src/bonsai/bonsai/bim/module/drawing/prop.py
@@ -76,7 +76,7 @@ def update_diagram_scale(self, context):
try:
element = (
tool.Ifc.get()
- .by_id(self.id_data.BIMMeshProperties.ifc_definition_id)
+ .by_id(tool.Geometry.get_mesh_props(self.id_data).ifc_definition_id)
.OfProductRepresentation[0]
.ShapeOfProduct[0]
)
@@ -93,7 +93,7 @@ def update_diagram_scale(self, context):
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties=diagram_scale)
-def update_is_nts(self, context):
+def update_is_nts(self: "BIMCameraProperties", context: bpy.types.Context) -> None:
if not self.update_props:
return
if not context.scene.camera or context.scene.camera.data != self.id_data:
@@ -104,7 +104,7 @@ def update_is_nts(self, context):
try:
element = (
tool.Ifc.get()
- .by_id(self.id_data.BIMMeshProperties.ifc_definition_id)
+ .by_id(tool.Geometry.get_mesh_props(self.id_data).ifc_definition_id)
.OfProductRepresentation[0]
.ShapeOfProduct[0]
)
@@ -192,8 +192,8 @@ def get_drawing_style_name(self: "DrawingStyle"):
def set_drawing_style_name(self: "DrawingStyle", new_value: str) -> None:
"""ensure the name is unique"""
- scene = bpy.context.scene
- drawing_styles = [s.name for s in scene.DocProperties.drawing_styles if s.name != self.name]
+ props = tool.Drawing.get_document_props()
+ drawing_styles = [s.name for s in props.drawing_styles if s.name != self.name]
new_value = tool.Blender.ensure_unique_name(new_value, drawing_styles)
old_value = self.name
self["name"] = new_value
diff --git a/src/bonsai/bonsai/bim/module/drawing/scheduler.py b/src/bonsai/bonsai/bim/module/drawing/scheduler.py
index d00fd1d94f..00ce2c1612 100644
--- a/src/bonsai/bonsai/bim/module/drawing/scheduler.py
+++ b/src/bonsai/bonsai/bim/module/drawing/scheduler.py
@@ -56,7 +56,7 @@ def a1_to_rc(cell):
class Scheduler:
- def schedule(self, infile, outfile):
+ def schedule(self, infile: str, outfile: str) -> None:
self.svg = svgwrite.Drawing(
outfile,
debug=False,
@@ -71,11 +71,12 @@ class Scheduler:
elif infile.endswith("xlsx"):
self.schedule_xlsx(infile, outfile)
- def parse_css(self, infile):
+ def parse_css(self, infile: str) -> None:
+ props = tool.Drawing.get_document_props()
stylesheet_path = os.path.splitext(infile)[0] + ".css"
if not os.path.exists(stylesheet_path):
- stylesheet_rel_path = getattr(bpy.context.scene.DocProperties, "schedules_stylesheet_path")
- ifc_file_path = os.path.dirname(IfcStore.path)
+ stylesheet_rel_path = props.schedules_stylesheet_path
+ ifc_file_path = os.path.dirname(tool.Ifc.get_path())
stylesheet_path = ifc_file_path + "\\" + stylesheet_rel_path
if not os.path.exists(stylesheet_path):
stylesheet_path = tool.Blender.get_data_dir_path(Path("assets") / "schedule.css")
@@ -91,7 +92,7 @@ class Scheduler:
self.svg.defs.add(self.svg.style(css))
- def schedule_xlsx(self, infile, outfile):
+ def schedule_xlsx(self, infile: str, outfile: str) -> None:
workbook = openpyxl.open(infile, data_only=True)
sheet = workbook.active
@@ -236,7 +237,7 @@ class Scheduler:
self.svg["viewBox"] = "0 0 {} {}".format(total_width, total_height)
self.svg.save(pretty=True)
- def schedule_ods(self, infile, outfile):
+ def schedule_ods(self, infile: str, outfile: str) -> None:
doc = load_ods(infile)
# useful for debugging ods
@@ -495,11 +496,11 @@ class Scheduler:
self.svg["viewBox"] = "0 0 {} {}".format(total_width, total_height)
self.svg.save(pretty=True)
- def get_style(self, style_name, styles):
+ def get_style(self, style_name: str, styles: dict) -> dict:
style = styles[style_name] if style_name else {}
return style
- def get_box_alignment(self, style):
+ def get_box_alignment(self, style: dict) -> str:
if style and "vertical-align" in style and style["vertical-align"] != "automatic":
vertical_align = style["vertical-align"]
else:
diff --git a/src/bonsai/bonsai/bim/module/drawing/ui.py b/src/bonsai/bonsai/bim/module/drawing/ui.py
index f35fc55325..6bf62ddec2 100644
--- a/src/bonsai/bonsai/bim/module/drawing/ui.py
+++ b/src/bonsai/bonsai/bim/module/drawing/ui.py
@@ -49,7 +49,7 @@ class BIM_PT_camera(Panel):
return
self.layout.use_property_split = True
- dprops = context.scene.DocProperties
+ dprops = tool.Drawing.get_document_props()
props = context.scene.camera.data.BIMCameraProperties
col = self.layout.column(align=True)
@@ -161,7 +161,7 @@ class BIM_PT_drawing_underlay(Panel):
layout.use_property_split = True
camera = context.scene.camera
assert camera
- dprops = context.scene.DocProperties
+ dprops = tool.Drawing.get_document_props()
props = camera.data.BIMCameraProperties
drawing_index_is_valid = props.active_drawing_style_index < len(dprops.drawing_styles)
@@ -229,7 +229,7 @@ class BIM_PT_drawings(Panel):
draw_project_not_saved_ui(self)
return
- self.props = context.scene.DocProperties
+ self.props = tool.Drawing.get_document_props()
if not self.props.is_editing_drawings:
row = self.layout.row(align=True)
@@ -302,7 +302,7 @@ class BIM_PT_schedules(Panel):
draw_project_not_saved_ui(self)
return
- self.props = context.scene.DocProperties
+ self.props = tool.Drawing.get_document_props()
if not self.props.is_editing_schedules:
row = self.layout.row(align=True)
@@ -352,7 +352,7 @@ class BIM_PT_references(Panel):
draw_project_not_saved_ui(self)
return
- self.props = context.scene.DocProperties
+ self.props = tool.Drawing.get_document_props()
if not self.props.is_editing_references:
row = self.layout.row(align=True)
@@ -394,7 +394,7 @@ class BIM_PT_sheets(Panel):
draw_project_not_saved_ui(self)
return
- self.props = context.scene.DocProperties
+ self.props = tool.Drawing.get_document_props()
if not self.props.is_editing_sheets:
row = self.layout.row(align=True)
@@ -601,7 +601,7 @@ class BIM_UL_drawinglist(bpy.types.UIList):
selected_icon = "CHECKBOX_HLT" if item.is_selected else "CHECKBOX_DEHLT"
row.prop(item, "is_selected", text="", icon=selected_icon, emboss=False)
row.prop(item, "name", text="", emboss=False)
- self.props = context.scene.DocProperties
+ self.props = tool.Drawing.get_document_props()
if (
self.props.drawings
and self.props.active_drawing_id
diff --git a/src/bonsai/bonsai/bim/module/drawing/workspace.py b/src/bonsai/bonsai/bim/module/drawing/workspace.py
index 30d57bf3db..bd679cb065 100644
--- a/src/bonsai/bonsai/bim/module/drawing/workspace.py
+++ b/src/bonsai/bonsai/bim/module/drawing/workspace.py
@@ -197,6 +197,8 @@ def create_annotation_occurrence(context):
class AnnotationToolUI:
+ layout: bpy.types.UILayout
+
@classmethod
def draw(cls, context, layout):
cls.layout = layout
@@ -224,7 +226,8 @@ class AnnotationToolUI:
@classmethod
def draw_create_object_interface(cls):
row = cls.layout.row(align=True)
- row.prop(bpy.context.scene.DocProperties, "should_draw_decorations", text="Viewport Annotations")
+ props = tool.Drawing.get_document_props()
+ row.prop(props, "should_draw_decorations", text="Viewport Annotations")
@classmethod
def draw_edit_object_interface(cls, context):
diff --git a/src/bonsai/bonsai/bim/module/geometry/__init__.py b/src/bonsai/bonsai/bim/module/geometry/__init__.py
index 533cdcd6bf..4e9eb9cf6f 100644
--- a/src/bonsai/bonsai/bim/module/geometry/__init__.py
+++ b/src/bonsai/bonsai/bim/module/geometry/__init__.py
@@ -98,12 +98,14 @@ addon_keymaps = []
@persistent
-def block_scale(scene):
+def block_scale(scene: bpy.types.Scene) -> None:
+ import bonsai.tool as tool
+
if obj := (getattr(bpy.context, "active_object", None) or bpy.context.view_layer.objects.active):
if isinstance(obj, bpy.types.Object) and obj.BIMObjectProperties.ifc_definition_id:
if obj.scale != (1, 1, 1):
obj.scale = (1, 1, 1)
- elif isinstance(obj, bpy.types.Mesh) and obj.BIMMeshProperties.ifc_definition_id:
+ elif isinstance(obj, bpy.types.Mesh) and tool.Geometry.get_mesh_props(obj).ifc_definition_id:
if obj.scale != (1, 1, 1):
obj.scale = (1, 1, 1)
diff --git a/src/bonsai/bonsai/bim/module/geometry/data.py b/src/bonsai/bonsai/bim/module/geometry/data.py
index a719d27893..2ba25df702 100644
--- a/src/bonsai/bonsai/bim/module/geometry/data.py
+++ b/src/bonsai/bonsai/bim/module/geometry/data.py
@@ -53,15 +53,16 @@ class ViewportData:
obj = bpy.context.active_object
element = tool.Ifc.get_entity(obj)
- modes = [obj_mode]
-
- if bpy.context.scene.BIMGeometryProperties.representation_obj:
+ modes: list[tuple[str, str, str, str, int]] = [obj_mode]
+ gprops = tool.Geometry.get_geometry_props()
+ if gprops.representation_obj:
modes.append(item_mode)
if not obj:
return modes
- if obj in bpy.context.scene.BIMProjectProperties.clipping_planes_objs:
+ pprops = tool.Project.get_project_props()
+ if obj in pprops.clipping_planes_objs:
pass
elif element:
if tool.Geometry.is_locked(element):
@@ -107,8 +108,9 @@ class RepresentationsData:
element = tool.Ifc.get_entity(obj)
active_representation_id = None
- if obj.data and hasattr(obj.data, "BIMMeshProperties"):
- active_representation_id = obj.data.BIMMeshProperties.ifc_definition_id
+ active_representation = tool.Geometry.get_active_representation(obj)
+ if active_representation:
+ active_representation_id = active_representation.id()
for representation in tool.Geometry.get_representations_iter(element):
representation_type = representation.RepresentationType
@@ -158,9 +160,9 @@ class RepresentationsData:
if not obj.data:
return []
element = tool.Ifc.get_entity(obj)
- if not (active_representation_id := obj.data.BIMMeshProperties.ifc_definition_id):
+ base_representation = tool.Geometry.get_active_representation(obj)
+ if not base_representation:
return [] # Maybe in profile editing mode
- base_representation = tool.Ifc.get().by_id(active_representation_id)
# shape aspects matching context of the active representation
matching_shape_aspects = []
@@ -390,7 +392,7 @@ class PlacementData:
def load(cls):
cls.data = {"has_placement": cls.has_placement()}
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
obj = bpy.context.active_object
if obj and props.has_blender_offset:
xyz = cls.original_xyz(obj)
@@ -413,7 +415,7 @@ class PlacementData:
@classmethod
def original_xyz(cls, obj):
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
xyz = ifcopenshell.util.geolocation.xyz2enh(
obj.matrix_world[0][3],
obj.matrix_world[1][3],
diff --git a/src/bonsai/bonsai/bim/module/geometry/decorator.py b/src/bonsai/bonsai/bim/module/geometry/decorator.py
index 2be10a27f0..5993cc4469 100644
--- a/src/bonsai/bonsai/bim/module/geometry/decorator.py
+++ b/src/bonsai/bonsai/bim/module/geometry/decorator.py
@@ -46,12 +46,14 @@ class ItemDecorator:
obj_is_boolean: dict[str, list[ifcopenshell.entity_instance]] = {}
objs: dict[str, dict[str, list]] = {}
obj_matrix: dict[str, Matrix] = {}
- for item_obj in context.scene.BIMGeometryProperties.item_objs:
+ props = tool.Geometry.get_geometry_props()
+ for item_obj in props.item_objs:
if obj := item_obj.obj:
obj: bpy.types.Object
objs[obj.name] = cls.get_obj_data(obj)
obj_is_selected[obj.name] = obj.select_get()
- item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
+ item = tool.Geometry.get_active_representation(obj)
+ assert item
obj_is_boolean[obj.name] = [i for i in tool.Ifc.get().get_inverse(item) if i.is_a("IfcBooleanResult")]
obj_matrix[obj.name] = obj.matrix_world.copy()
@@ -143,7 +145,8 @@ class ItemDecorator:
color = selected_elements_color
blf.color(font_id, *color)
- for item in context.scene.BIMGeometryProperties.item_objs:
+ props = tool.Geometry.get_geometry_props()
+ for item in props.item_objs:
if (obj := item.obj) and obj.hide_get() == False:
if obj.select_get():
centroid = obj.matrix_world @ Vector(obj.bound_box[0]).lerp(Vector(obj.bound_box[6]), 0.5)
diff --git a/src/bonsai/bonsai/bim/module/geometry/operator.py b/src/bonsai/bonsai/bim/module/geometry/operator.py
index 6a234800bf..1d43e584d7 100644
--- a/src/bonsai/bonsai/bim/module/geometry/operator.py
+++ b/src/bonsai/bonsai/bim/module/geometry/operator.py
@@ -23,6 +23,7 @@ import numpy as np
import numpy.typing as npt
import ifcopenshell
import ifcopenshell.api.layer
+import ifcopenshell.api.style
import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.representation
@@ -78,7 +79,8 @@ class OverrideMeshSeparate(bpy.types.Operator, tool.Ifc.Operator):
self.separate_element(element)
def separate_item(self, context, obj):
- item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
+ item = tool.Geometry.get_active_representation(obj)
+ assert item
if tool.Geometry.is_meshlike_item(item):
previous_selected_objects = context.selected_objects
bpy.ops.mesh.separate(type=self.type)
@@ -86,7 +88,7 @@ class OverrideMeshSeparate(bpy.types.Operator, tool.Ifc.Operator):
if obj in previous_selected_objects:
continue
self.add_meshlike_item(obj)
- tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj)
+ tool.Geometry.reload_representation(tool.Geometry.get_geometry_props().representation_obj)
else:
self.report({"INFO"}, f"Separating an {item.is_a()} is not supported")
@@ -121,7 +123,7 @@ class OverrideMeshSeparate(bpy.types.Operator, tool.Ifc.Operator):
representation.Items = list(representation.Items) + [item]
obj.name = obj.data.name = f"Item/{item.is_a()}/{item.id()}"
- obj.data.BIMMeshProperties.ifc_definition_id = item.id()
+ tool.Ifc.link(item, obj)
props.add_item_object(obj, item)
def separate_element(self, element):
@@ -302,11 +304,12 @@ class AddRepresentation(bpy.types.Operator, tool.Ifc.Operator):
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
+ props = tool.Geometry.get_geometry_props()
row = self.layout.row()
row.prop(self, "representation_conversion_method", text="")
if self.representation_conversion_method == "OBJECT":
row = self.layout.row()
- row.prop(context.scene.BIMGeometryProperties, "representation_from_object", text="")
+ row.prop(props, "representation_from_object", text="")
class SelectConnection(bpy.types.Operator, tool.Ifc.Operator):
@@ -457,13 +460,17 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"}
def update_obj_mesh_representation(self, context: bpy.types.Context, obj: bpy.types.Object) -> None:
+ data = obj.data
+ assert tool.Geometry.is_data_supported_for_adding_representation(data)
+ mprops = tool.Geometry.get_mesh_props(data)
+
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
material = ifcopenshell.util.element.get_material(product, should_skip_usage=True)
# NOTE: Currently iterator doesn't detect whether opening is actually affected the representation
# or it's just present on the element. In theory, we can also allow editing representations
# if we know that representation wasn't affected by existing openings.
- has_openings = tool.Geometry.has_openings(product) and obj.data.BIMMeshProperties.has_openings_applied
+ has_openings = tool.Geometry.has_openings(product) and tool.Geometry.get_mesh_props(data).has_openings_applied
if has_openings and not self.apply_openings:
# Meshlike things with openings can only be updated without openings applied.
if self.from_ui:
@@ -486,7 +493,8 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
if tool.Ifc.is_moved(obj) or tool.Geometry.is_scaled(obj):
core.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
- old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
+ old_representation = tool.Geometry.get_active_representation(obj)
+ assert old_representation
if material and material.is_a() in ["IfcMaterialProfileSet", "IfcMaterialLayerSet"]:
if self.ifc_representation_class == "IfcTessellatedFaceSet":
# We are explicitly casting to a tessellation, so remove all parametric materials.
@@ -545,12 +553,12 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
tool.Geometry.run_style_add_style(obj=mat)
for mat in tool.Geometry.get_object_materials_without_styles(obj)
]
- ifcopenshell.api.run(
- "style.assign_representation_styles",
+ props = tool.Geometry.get_geometry_props()
+ ifcopenshell.api.style.assign_representation_styles(
self.file,
shape_representation=new_representation,
styles=tool.Geometry.get_styles(obj, only_assigned_to_faces=True),
- should_use_presentation_style_assignment=context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
+ should_use_presentation_style_assignment=props.should_use_presentation_style_assignment,
)
tool.Geometry.record_object_materials(obj)
@@ -569,8 +577,8 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
continue
representation.RepresentationIdentifier = "Reference"
- obj.data.BIMMeshProperties.ifc_definition_id = int(new_representation.id())
- obj.data.name = f"{old_representation.ContextOfItems.id()}/{new_representation.id()}"
+ tool.Ifc.link(new_representation, data)
+ data.name = tool.Loader.get_mesh_name(new_representation)
# TODO: In simple scenarios, a type has a ShapeRepresentation of ID
# 123. This is then mapped through mapped representations by
@@ -586,7 +594,7 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
# transformations.
core.remove_representation(tool.Ifc, tool.Geometry, obj=obj, representation=old_representation)
- if obj.data.BIMMeshProperties.ifc_parameters:
+ if mprops.ifc_parameters:
core.get_representation_ifc_parameters(tool.Geometry, obj=obj)
@@ -598,12 +606,13 @@ class UpdateParametricRepresentation(bpy.types.Operator):
@classmethod
def poll(cls, context):
- return context.active_object and context.active_object.mode == "OBJECT"
+ return (obj := context.active_object) and obj.mode == "OBJECT" and tool.Geometry.has_mesh_properties(obj.data)
def execute(self, context):
self.file = IfcStore.get_file()
obj = context.active_object
- props = obj.data.BIMMeshProperties
+ assert obj and tool.Geometry.has_mesh_properties(obj.data)
+ props = tool.Geometry.get_mesh_props(obj.data)
parameter = props.ifc_parameters[self.index]
self.file.by_id(parameter.step_id)[parameter.index] = parameter.value
show_representation_parameters = bool(props.ifc_parameters)
@@ -627,8 +636,10 @@ class GetRepresentationIfcParameters(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
- core.get_representation_ifc_parameters(tool.Geometry, obj=context.active_object)
- parameters = context.active_object.data.BIMMeshProperties.ifc_parameters
+ obj = context.active_object
+ assert obj and tool.Geometry.has_mesh_properties((data := obj.data))
+ core.get_representation_ifc_parameters(tool.Geometry, obj=obj)
+ parameters = tool.Geometry.get_mesh_props(data).ifc_parameters
self.report({"INFO"}, f"{len(parameters)} parameters found.")
@@ -721,7 +732,7 @@ class OverrideDelete(bpy.types.Operator):
row = self.layout.row()
row.label(text="Warning: Faster deletion will use more memory.", icon="ERROR")
- def _execute(self, context):
+ def _execute(self, context: bpy.types.Context):
start_time = time()
if self.is_batch:
@@ -730,9 +741,7 @@ class OverrideDelete(bpy.types.Operator):
self.process_arrays(context)
clear_active_object = True
for obj in context.selected_objects:
- try:
- obj.name
- except:
+ if not tool.Blender.is_valid_data_block(obj):
continue
element = tool.Ifc.get_entity(obj)
if element:
@@ -782,7 +791,7 @@ class OverrideDelete(bpy.types.Operator):
data["old_file"].redo()
tool.Ifc.set(data["new_file"])
- def process_arrays(self, context):
+ def process_arrays(self, context: bpy.types.Context) -> None:
selected_objects = set(context.selected_objects)
array_parents = set()
for obj in context.selected_objects:
@@ -1030,7 +1039,8 @@ class OverrideDuplicateMove(bpy.types.Operator):
# Unlink from previous boolean element
# and keep object tracked for decorations.
if is_tracked_opening:
- new_obj.data.BIMMeshProperties.ifc_boolean_id = 0
+ mprops = tool.Geometry.get_mesh_props(new_obj.data)
+ mprops.ifc_boolean_id = 0
tool.Root.add_tracked_opening(new_obj, tracked_opening_type)
if obj == context.active_object:
@@ -1054,7 +1064,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
if new.is_a("IfcRelSpaceBoundary"):
surface = new.ConnectionGeometry.SurfaceOnRelatingElement
temp_data.name = f"0/{surface.id()}"
- temp_data.BIMMeshProperties.ifc_definition_id = surface.id()
+ tool.Ifc.link(surface, temp_data)
else:
tool.Blender.remove_data_block(temp_data)
@@ -1090,12 +1100,14 @@ class OverrideDuplicateMove(bpy.types.Operator):
@staticmethod
def duplicate_item(obj: bpy.types.Object) -> None:
props = tool.Geometry.get_geometry_props()
- item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
+ item = tool.Geometry.get_active_representation(obj)
+ assert item
new_item = ifcopenshell.util.element.copy_deep(tool.Ifc.get(), item)
new_obj = obj.copy()
+ assert tool.Geometry.has_mesh_properties(obj.data)
temp_data = obj.data.copy()
new_obj.data = temp_data
- new_obj.data.BIMMeshProperties.ifc_definition_id = new_item.id()
+ tool.Ifc.link(new_item, temp_data)
new_obj.name = obj.data.name = f"Item/{new_item.is_a()}/{new_item.id()}"
props.add_item_object(new_obj, new_item)
@@ -1671,7 +1683,8 @@ class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator):
def join_item(self) -> None:
props = tool.Geometry.get_geometry_props()
ifc_file = tool.Ifc.get()
- item = tool.Ifc.get().by_id(self.target.data.BIMMeshProperties.ifc_definition_id)
+ item = tool.Geometry.get_active_representation(self.target)
+ assert item
if tool.Geometry.is_meshlike_item(item):
tool.Geometry.dissolve_triangulated_edges(self.target)
item_objs = [i.obj for i in props.item_objs if i.obj]
@@ -1694,7 +1707,7 @@ class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator):
for item_data in items_data:
props.add_item_object(item_data["obj"], ifc_file.by_id(item_data["ifc_definition_id"]))
- tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj)
+ tool.Geometry.reload_representation(props.representation_obj)
bpy.context.view_layer.update()
tool.Root.reload_item_decorator()
@@ -1702,7 +1715,8 @@ class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator):
ifc_file = tool.Ifc.get()
builder = ShapeBuilder(ifc_file)
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
- representation = ifc_file.by_id(self.target.data.BIMMeshProperties.ifc_definition_id)
+ representation = tool.Geometry.get_active_representation(self.target)
+ assert representation
representation_type = representation.RepresentationType
if representation_type in ("Tessellation", "Brep"):
for obj in bpy.context.selected_objects:
@@ -1741,7 +1755,8 @@ class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator):
continue
# Only objects of the same representation type can be joined
- obj_rep = ifc_file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
+ obj_rep = tool.Geometry.get_active_representation(obj)
+ assert obj_rep
if obj_rep.RepresentationType != representation_type:
obj.select_set(False)
self.report(
@@ -1892,9 +1907,10 @@ class OverrideEscape(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- if context.scene.BIMGeometryProperties.mode == "ITEM":
+ props = tool.Geometry.get_geometry_props()
+ if props.mode == "ITEM":
tool.Geometry.disable_item_mode()
- elif context.scene.BIMGeometryProperties.mode == "EDIT":
+ elif props.mode == "EDIT":
bpy.ops.bim.override_mode_set_object("INVOKE_DEFAULT", should_save=False)
tool.Geometry.disable_item_mode()
elif tool.Model.get_model_props().openings:
@@ -1950,6 +1966,7 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
def handle_single_object(self, context: bpy.types.Context, obj: bpy.types.Object) -> None:
element = tool.Ifc.get_entity(obj)
props = tool.Geometry.get_geometry_props()
+ pprops = tool.Project.get_project_props()
if obj == props.representation_obj:
self.report({"ERROR"}, f"Element '{obj.name}' is in item mode and cannot be edited directly")
elif obj in [o.obj for o in context.scene.BIMAggregateProperties.not_editing_objects]:
@@ -1957,7 +1974,7 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
self.report(
{"ERROR"}, f"Element '{obj.name}' does not belong to this aggregate and cannot be edited directly"
)
- elif obj in bpy.context.scene.BIMProjectProperties.clipping_planes_objs:
+ elif obj in pprops.clipping_planes_objs:
self.report({"ERROR"}, "Clipping planes cannot be edited")
elif element:
if not obj.data:
@@ -2005,12 +2022,15 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
self.handle_single_object(context, obj)
def enable_editing_representation_item(self, context: bpy.types.Context, obj: bpy.types.Object) -> None:
- item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
- element = tool.Ifc.get_entity(context.scene.BIMGeometryProperties.representation_obj)
+ item = tool.Geometry.get_active_representation(obj)
+ assert item
+ element = tool.Ifc.get_entity(tool.Geometry.get_geometry_props().representation_obj)
if tool.Geometry.is_meshlike_item(item):
tool.Geometry.dissolve_triangulated_edges(obj)
tool.Blender.select_and_activate_single_object(context, obj)
- obj.data.BIMMeshProperties.mesh_checksum = tool.Geometry.get_mesh_checksum(obj.data)
+ assert isinstance(mesh := obj.data, bpy.types.Mesh)
+ props = tool.Geometry.get_mesh_props(mesh)
+ props.mesh_checksum = tool.Geometry.get_mesh_checksum(mesh)
self.enable_edit_mode(context)
elif (
item.is_a("IfcSweptAreaSolid")
@@ -2027,21 +2047,21 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
f"Couldn't import profile, editing it directly is not yet supported. Failing profile: {profile}.",
)
return
- obj.data.BIMMeshProperties.ifc_definition_id = item.id()
+ tool.Ifc.link(item, obj.data)
self.enable_edit_mode(context)
ProfileDecorator.install(context)
if not bpy.app.background:
tool.Blender.set_viewport_tool("bim.cad_tool")
elif item.is_a("IfcAnnotationFillArea"):
tool.Model.import_annotation_fill_area(item, obj=obj)
- obj.data.BIMMeshProperties.ifc_definition_id = item.id()
+ tool.Ifc.link(item, obj.data)
self.enable_edit_mode(context)
ProfileDecorator.install(context)
if not bpy.app.background:
tool.Blender.set_viewport_tool("bim.cad_tool")
elif tool.Geometry.is_curvelike_item(item):
tool.Model.import_curve(item, obj=obj)
- obj.data.BIMMeshProperties.ifc_definition_id = item.id()
+ tool.Ifc.link(item, obj.data)
self.enable_edit_mode(context)
ProfileDecorator.install(context)
if not bpy.app.background:
@@ -2052,10 +2072,11 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
def enable_edit_mode(self, context: bpy.types.Context) -> Union[None, set[str]]:
if tool.Blender.toggle_edit_mode(context) == {"CANCELLED"}:
return {"CANCELLED"}
- context.scene.BIMGeometryProperties.is_changing_mode = True
- if context.scene.BIMGeometryProperties.mode != "EDIT":
- context.scene.BIMGeometryProperties.mode = "EDIT"
- context.scene.BIMGeometryProperties.is_changing_mode = False
+ props = tool.Geometry.get_geometry_props()
+ props.is_changing_mode = True
+ if props.mode != "EDIT":
+ props.mode = "EDIT"
+ props.is_changing_mode = False
def has_aggregates(self, objs):
for obj in objs:
@@ -2102,14 +2123,15 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
tool.Blender.toggle_edit_mode(context)
- context.scene.BIMGeometryProperties.is_changing_mode = True
- if context.scene.BIMGeometryProperties.representation_obj:
- if context.scene.BIMGeometryProperties.mode != "ITEM":
- context.scene.BIMGeometryProperties.mode = "ITEM"
+ props = tool.Geometry.get_geometry_props()
+ props.is_changing_mode = True
+ if props.representation_obj:
+ if props.mode != "ITEM":
+ props.mode = "ITEM"
else:
- if context.scene.BIMGeometryProperties.mode != "OBJECT":
- context.scene.BIMGeometryProperties.mode = "OBJECT"
- context.scene.BIMGeometryProperties.is_changing_mode = False
+ if props.mode != "OBJECT":
+ props.mode = "OBJECT"
+ props.is_changing_mode = False
if context.active_object and self.should_save:
element = tool.Ifc.get_entity(context.active_object)
@@ -2155,24 +2177,27 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
else:
bpy.ops.bim.edit_extrusion_profile()
return self.execute(context)
- elif obj.data.BIMMeshProperties.ifc_definition_id:
- if not tool.Geometry.has_geometric_data(obj):
+ elif representation := tool.Geometry.get_active_representation(obj):
+ if not tool.Geometry.is_geometric_data(obj.data):
self.is_valid = False
self.should_save = False
- representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
+ assert tool.Geometry.has_mesh_properties(obj.data)
+ mesh_props = tool.Geometry.get_mesh_props(obj.data)
if tool.Geometry.is_meshlike(
representation
- ) and obj.data.BIMMeshProperties.mesh_checksum != tool.Geometry.get_mesh_checksum(obj.data):
+ ) and mesh_props.mesh_checksum != tool.Geometry.get_mesh_checksum(obj.data):
self.edited_objs.append(obj)
elif getattr(element, "HasOpenings", None):
self.unchanged_objs_with_openings.append(obj)
else:
tool.Ifc.finish_edit(obj)
elif element.is_a("IfcGridAxis"):
- if not tool.Geometry.has_geometric_data(obj):
+ if not tool.Geometry.is_geometric_data(obj.data):
self.is_valid = False
self.should_save = False
- if obj.data.BIMMeshProperties.mesh_checksum != tool.Geometry.get_mesh_checksum(obj.data):
+ assert tool.Geometry.has_mesh_properties(obj.data)
+ mesh_props = tool.Geometry.get_mesh_props(obj.data)
+ if mesh_props.mesh_checksum != tool.Geometry.get_mesh_checksum(obj.data):
self.edited_objs.append(obj)
else:
tool.Ifc.finish_edit(obj)
@@ -2197,9 +2222,11 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"}
def edit_representation_item(self, obj: bpy.types.Object) -> None:
- item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
+ props = tool.Geometry.get_geometry_props()
+ item = tool.Geometry.get_active_representation(obj)
+ assert item
if tool.Geometry.is_meshlike_item(item):
- if tool.Geometry.has_geometric_data(obj) and obj.data.polygons:
+ if tool.Geometry.is_geometric_data(obj.data) and obj.data.polygons:
tool.Geometry.edit_meshlike_item(obj)
else:
tool.Geometry.import_item(obj)
@@ -2220,11 +2247,11 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
ifcopenshell.util.element.replace_attribute(inverse, old_profile, profile)
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_profile)
- tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj)
+ tool.Geometry.reload_representation(props.representation_obj)
tool.Geometry.import_item(obj)
tool.Geometry.import_item_attributes(obj)
- element = tool.Ifc.get_entity(bpy.context.scene.BIMGeometryProperties.representation_obj)
+ element = tool.Ifc.get_entity(props.representation_obj)
# Only certain classes should have a footprint
if element.is_a() in ("IfcSlab", "IfcRamp"):
footprint_context = ifcopenshell.util.representation.get_context(
@@ -2277,9 +2304,9 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
for inverse in tool.Ifc.get().get_inverse(item):
ifcopenshell.util.element.replace_attribute(inverse, item, profile)
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), item)
- obj.data.BIMMeshProperties.ifc_definition_id = profile.id()
+ tool.Ifc.link(profile, obj.data)
- tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj)
+ tool.Geometry.reload_representation(props.representation_obj)
tool.Geometry.import_item(obj)
tool.Geometry.import_item_attributes(obj)
elif tool.Geometry.is_curvelike_item(item):
@@ -2305,10 +2332,9 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
ifcopenshell.util.element.replace_attribute(inverse, item, new)
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), item)
- obj.data.BIMMeshProperties.ifc_definition_id = new.id()
+ tool.Ifc.link(new, obj.data)
tool.Geometry.import_item(obj)
- props = tool.Geometry.get_geometry_props()
for item in additional_curves:
representation = tool.Geometry.get_active_representation(props.representation_obj)
representation = ifcopenshell.util.representation.resolve_representation(representation)
@@ -2317,7 +2343,7 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
name = f"Item/{item.is_a()}/{item.id()}"
mesh = bpy.data.meshes.new(name)
new_obj = bpy.data.objects.new(name, mesh)
- new_obj.data.BIMMeshProperties.ifc_definition_id = item.id()
+ tool.Ifc.link(item, new_obj.data)
bpy.context.collection.objects.link(new_obj)
props.add_item_object(new_obj, item)
new_obj.matrix_world = obj.matrix_world
@@ -2330,10 +2356,11 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
def enable_edit_mode(self, context):
if tool.Blender.toggle_edit_mode(context) == {"CANCELLED"}:
return {"CANCELLED"}
- context.scene.BIMGeometryProperties.is_changing_mode = True
- if context.scene.BIMGeometryProperties.mode != "EDIT":
- context.scene.BIMGeometryProperties.mode = "EDIT"
- context.scene.BIMGeometryProperties.is_changing_mode = False
+ props = tool.Geometry.get_geometry_props()
+ props.is_changing_mode = True
+ if props.mode != "EDIT":
+ props.mode = "EDIT"
+ props.is_changing_mode = False
class FlipObject(bpy.types.Operator):
@@ -2370,12 +2397,13 @@ class EnableEditingRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
item.tags += ","
item.tags += tag
- if obj.data and hasattr(obj.data, "BIMMeshProperties"):
- active_representation_id = obj.data.BIMMeshProperties.ifc_definition_id
- representation = tool.Ifc.get().by_id(active_representation_id)
+ if tool.Geometry.has_mesh_properties((data := obj.data)):
+ representation = tool.Geometry.get_data_representation(data)
+ assert representation
# Shape aspects must be considered from the PartOfProductDefinitionShape level
element = tool.Ifc.get_entity(obj)
+ assert element
product_reps = []
if element.is_a("IfcProduct"):
product_reps = [element.Representation]
@@ -2440,7 +2468,8 @@ class DisableEditingRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj()
- obj.BIMGeometryProperties.is_editing = False
+ assert obj
+ tool.Geometry.get_object_geometry_props(obj).is_editing = False
class RemoveRepresentationItem(bpy.types.Operator, tool.Ifc.Operator):
@@ -2451,9 +2480,12 @@ class RemoveRepresentationItem(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
- if context.scene.BIMGeometryProperties.representation_obj:
+ if tool.Geometry.get_geometry_props().representation_obj:
return False # Artificial restriction for now to prevent removing when in item mode
- if not (obj := tool.Geometry.get_active_or_representation_obj()) or len(obj.BIMGeometryProperties.items) <= 1:
+ if (
+ not (obj := tool.Geometry.get_active_or_representation_obj())
+ or len(tool.Geometry.get_object_geometry_props(obj).items) <= 1
+ ):
cls.poll_message_set(
"Active object need to have more than 1 representation items to keep representation valid"
)
@@ -2488,13 +2520,17 @@ class SelectRepresentationItem(bpy.types.Operator):
def execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj()
- item = tool.Ifc.get().by_id(obj.BIMGeometryProperties.active_item.ifc_definition_id)
+ obj_props = tool.Geometry.get_object_geometry_props(obj)
+ assert obj_props.active_item
+ item = tool.Ifc.get().by_id(obj_props.active_item.ifc_definition_id)
item_ids = self.get_nested_item_ids(item)
props = tool.Geometry.get_geometry_props()
for item_obj in props.item_objs:
- if item_obj.obj.data.BIMMeshProperties.ifc_definition_id in item_ids:
- tool.Blender.select_object(item_obj.obj)
+ obj_ = item_obj.obj
+ props = tool.Geometry.get_mesh_props(obj_.data)
+ if props.ifc_definition_id in item_ids:
+ tool.Blender.select_object(obj_)
return {"FINISHED"}
def get_nested_item_ids(self, item):
@@ -2512,7 +2548,7 @@ class SelectRepresentationItem(bpy.types.Operator):
def poll_editing_representation_item_style(cls, context):
if not (obj := tool.Geometry.get_active_or_representation_obj()):
return False
- props = obj.BIMGeometryProperties
+ props = tool.Geometry.get_object_geometry_props(obj)
if not props.is_editing:
return False
if not (item := props.active_item):
@@ -2547,7 +2583,8 @@ class EnableEditingRepresentationItemStyle(bpy.types.Operator, tool.Ifc.Operator
def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj()
- props = obj.BIMGeometryProperties
+ assert obj
+ props = tool.Geometry.get_object_geometry_props(obj)
props.is_editing_item_style = True
ifc_file = tool.Ifc.get()
@@ -2566,7 +2603,8 @@ class EditRepresentationItemStyle(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj()
- props = obj.BIMGeometryProperties
+ assert obj
+ props = tool.Geometry.get_object_geometry_props(obj)
props.is_editing_item_style = False
ifc_file = tool.Ifc.get()
@@ -2588,7 +2626,7 @@ class DisableEditingRepresentationItemStyle(bpy.types.Operator, tool.Ifc.Operato
def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj()
- props = obj.BIMGeometryProperties
+ props = tool.Geometry.get_object_geometry_props(obj)
props.is_editing_item_style = False
@@ -2604,7 +2642,8 @@ class UnassignRepresentationItemStyle(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
active_obj = tool.Geometry.get_active_or_representation_obj()
- active_props = active_obj.BIMGeometryProperties
+ assert active_obj
+ active_props = tool.Geometry.get_object_geometry_props(active_obj)
active_props.is_editing_item_style = False
# Get active representation item
@@ -2671,7 +2710,8 @@ class EnableEditingRepresentationItemShapeAspect(bpy.types.Operator, tool.Ifc.Op
def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj()
- props = obj.BIMGeometryProperties
+ assert obj
+ props = tool.Geometry.get_object_geometry_props(obj)
props.is_editing_item_shape_aspect = True
# set dropdown to currently active shape aspect
@@ -2687,11 +2727,13 @@ class EditRepresentationItemShapeAspect(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj()
+ assert obj
element = tool.Ifc.get_entity(obj)
- props = obj.BIMGeometryProperties
+ props = tool.Geometry.get_object_geometry_props(obj)
props.is_editing_item_shape_aspect = False
ifc_file = tool.Ifc.get()
+ assert props.active_item
representation_item_id = props.active_item.ifc_definition_id
representation_item = ifc_file.by_id(representation_item_id)
@@ -2744,7 +2786,8 @@ class DisableEditingRepresentationItemShapeAspect(bpy.types.Operator, tool.Ifc.O
def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj()
- props = obj.BIMGeometryProperties
+ assert obj
+ props = tool.Geometry.get_object_geometry_props(obj)
props.is_editing_item_shape_aspect = False
@@ -2755,10 +2798,12 @@ class RemoveRepresentationItemFromShapeAspect(bpy.types.Operator, tool.Ifc.Opera
def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj()
+ assert obj
element = tool.Ifc.get_entity(obj)
- props = obj.BIMGeometryProperties
+ props = tool.Geometry.get_object_geometry_props(obj)
ifc_file = tool.Ifc.get()
+ assert props.active_item
representation_item_id = props.active_item.ifc_definition_id
representation_item = ifc_file.by_id(representation_item_id)
shape_aspect = ifc_file.by_id(props.active_item.shape_aspect_id)
@@ -2841,7 +2886,7 @@ class ImportRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
boolean_ids.add(item.SecondOperand.id())
continue
item_mesh = bpy.data.meshes.new(f"Item/{item.is_a()}/{item_id}")
- item_mesh.BIMMeshProperties.ifc_definition_id = item_id
+ tool.Ifc.link(item, item_mesh)
item_obj = bpy.data.objects.new(f"Item/{item.is_a()}/{item_id}", item_mesh)
item_obj.matrix_world = obj.matrix_world
@@ -2871,7 +2916,7 @@ class UpdateItemAttributes(bpy.types.Operator, tool.Ifc.Operator):
obj = context.active_object
tool.Geometry.sync_item_positions()
tool.Geometry.update_item_attributes(obj)
- tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj)
+ tool.Geometry.reload_representation(tool.Geometry.get_geometry_props().representation_obj)
tool.Geometry.import_item(obj)
tool.Root.reload_item_decorator()
@@ -2888,6 +2933,10 @@ class NameProfile(bpy.types.Operator, tool.Ifc.Operator):
options={"SKIP_SAVE"},
)
+ if TYPE_CHECKING:
+ extrusion_item_obj: str
+ profile_name: str
+
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
@@ -2902,7 +2951,7 @@ class NameProfile(bpy.types.Operator, tool.Ifc.Operator):
ifc_file = tool.Ifc.get()
extrusion_item_obj = bpy.data.objects[self.extrusion_item_obj]
- mesh_props = extrusion_item_obj.data.BIMMeshProperties
+ mesh_props = tool.Geometry.get_mesh_props(extrusion_item_obj.data)
extrusion = ifc_file.by_id(mesh_props.ifc_definition_id)
assert extrusion.is_a("IfcSweptAreaSolid")
profile = extrusion.SweptArea
@@ -2972,9 +3021,9 @@ class AddMeshlikeItem(bpy.types.Operator, tool.Ifc.Operator):
props.add_item_object(obj, item)
representation.Items = list(representation.Items) + [item]
- tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj)
+ tool.Geometry.reload_representation(props.representation_obj)
obj.name = obj.data.name = f"Item/{item.is_a()}/{item.id()}"
- obj.data.BIMMeshProperties.ifc_definition_id = item.id()
+ tool.Geometry.get_mesh_props(obj.data).ifc_definition_id = item.id()
tool.Root.reload_item_decorator()
@@ -3020,10 +3069,10 @@ class AddSweptAreaSolidItem(bpy.types.Operator, tool.Ifc.Operator):
props.add_item_object(obj, item)
representation.Items = list(representation.Items) + [item]
- tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj)
+ tool.Geometry.reload_representation(props.representation_obj)
obj.name = obj.data.name = f"Item/{item.is_a()}/{item.id()}"
- obj.data.BIMMeshProperties.ifc_definition_id = item.id()
+ tool.Geometry.get_mesh_props(obj.data).ifc_definition_id = item.id()
tool.Geometry.import_item(obj)
tool.Geometry.import_item_attributes(obj)
tool.Root.reload_item_decorator()
@@ -3091,10 +3140,10 @@ class AddCurvelikeItem(bpy.types.Operator, tool.Ifc.Operator):
props.add_item_object(obj, item)
representation.Items = list(representation.Items) + [item]
- tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj)
+ tool.Geometry.reload_representation(props.representation_obj)
obj.name = obj.data.name = f"Item/{item.is_a()}/{item.id()}"
- obj.data.BIMMeshProperties.ifc_definition_id = item.id()
+ tool.Geometry.get_mesh_props(obj.data).ifc_definition_id = item.id()
tool.Geometry.import_item(obj)
tool.Geometry.import_item_attributes(obj)
@@ -3140,10 +3189,10 @@ class AddHalfSpaceSolidItem(bpy.types.Operator, tool.Ifc.Operator):
representation = ifcopenshell.util.representation.resolve_representation(representation)
representation.Items = list(representation.Items) + [item]
- tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj)
+ tool.Geometry.reload_representation(props.representation_obj)
obj.name = obj.data.name = f"Item/{item.is_a()}/{item.id()}"
- obj.data.BIMMeshProperties.ifc_definition_id = item.id()
+ tool.Geometry.get_mesh_props(obj.data).ifc_definition_id = item.id()
tool.Geometry.import_item(obj)
# TODO refactor to core and not rely on selection
diff --git a/src/bonsai/bonsai/bim/module/geometry/prop.py b/src/bonsai/bonsai/bim/module/geometry/prop.py
index 2ab4ec5efe..7b80e9b040 100644
--- a/src/bonsai/bonsai/bim/module/geometry/prop.py
+++ b/src/bonsai/bonsai/bim/module/geometry/prop.py
@@ -32,7 +32,7 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
-from typing import Optional, TYPE_CHECKING, Union
+from typing import Optional, TYPE_CHECKING, Union, Literal
def get_contexts(self, context):
@@ -270,6 +270,9 @@ class BIMObjectGeometryProperties(PropertyGroup):
representation_item_layer: str
+GeometryMode = Literal["OBJECT", "ITEM", "EDIT"]
+
+
class BIMGeometryProperties(PropertyGroup):
# Revit workaround
should_use_presentation_style_assignment: BoolProperty(name="Force Presentation Style Assignment", default=False)
@@ -308,7 +311,7 @@ class BIMGeometryProperties(PropertyGroup):
should_force_faceted_brep: bool
should_force_triangulation: bool
is_changing_mode: bool
- mode: str
+ mode: GeometryMode
representation_obj: Union[bpy.types.Object, None]
item_objs: bpy.types.bpy_prop_collection_idprop[RepresentationItemObject]
representation_from_object: Union[bpy.types.Object, None]
diff --git a/src/bonsai/bonsai/bim/module/geometry/ui.py b/src/bonsai/bonsai/bim/module/geometry/ui.py
index 01d0a8ae0e..b0ad2ea47d 100644
--- a/src/bonsai/bonsai/bim/module/geometry/ui.py
+++ b/src/bonsai/bonsai/bim/module/geometry/ui.py
@@ -53,9 +53,10 @@ def mode_menu(self, context):
UIData.load()
ifc_icon = f"{UIData.data['menu_icon_color_mode']}_ifc"
row = self.layout.row(align=True)
- if context.scene.BIMGeometryProperties.mode == "EDIT":
+ props = tool.Geometry.get_geometry_props()
+ if props.mode == "EDIT":
row.operator("bim.override_mode_set_object", icon="CANCEL", text="Discard Changes").should_save = False
- row.prop(context.scene.BIMGeometryProperties, "mode", text="", icon_value=bonsai.bim.icons[ifc_icon].icon_id)
+ row.prop(props, "mode", text="", icon_value=bonsai.bim.icons[ifc_icon].icon_id)
def object_menu(self, context):
@@ -411,15 +412,18 @@ class BIM_PT_mesh(Panel):
@classmethod
def poll(cls, context):
return (
- context.active_object is not None
- and context.active_object.type == "MESH"
- and hasattr(context.active_object.data, "BIMMeshProperties")
- and context.active_object.data.BIMMeshProperties.ifc_definition_id
+ (obj := context.active_object) is not None
+ and (mesh := obj.data)
+ and isinstance(mesh, bpy.types.Mesh)
+ and tool.Geometry.get_mesh_props(mesh).ifc_definition_id
)
def draw(self, context):
- if not context.active_object.data:
- return
+ obj = context.active_object
+ assert obj
+ mesh = obj.data
+ assert isinstance(mesh, bpy.types.Mesh)
+
row = self.layout.row()
row.label(text="Advanced Users Only", icon="ERROR")
@@ -427,7 +431,7 @@ class BIM_PT_mesh(Panel):
row = layout.row()
text = "Manually Save Representation"
- if tool.Ifc.is_edited(context.active_object):
+ if tool.Ifc.is_edited(obj):
text += "*"
row.operator("bim.update_representation", text=text)
@@ -454,8 +458,8 @@ class BIM_PT_mesh(Panel):
op = row.operator("bim.update_representation", text="Convert To Arbitrary Extrusion With Voids")
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids"
- if context.active_object and context.active_object.data:
- mprops = context.active_object.data.BIMMeshProperties
+ if True:
+ mprops = tool.Geometry.get_mesh_props(mesh)
row = layout.row()
row.operator("bim.get_representation_ifc_parameters")
for index, ifc_parameter in enumerate(mprops.ifc_parameters):
@@ -477,7 +481,7 @@ class BIM_PT_placement(Panel):
@classmethod
def poll(cls, context):
- return context.active_object and context.active_object.BIMObjectProperties.ifc_definition_id
+ return (obj := context.active_object) and obj.BIMObjectProperties.ifc_definition_id
def draw(self, context):
if not PlacementData.is_loaded:
@@ -571,10 +575,10 @@ class BIM_PT_workarounds(Panel):
@classmethod
def poll(cls, context):
return (
- context.active_object is not None
- and context.active_object.type == "MESH"
- and hasattr(context.active_object.data, "BIMMeshProperties")
- and context.active_object.data.BIMMeshProperties.ifc_definition_id
+ (obj := context.active_object) is not None
+ and (mesh := obj.data)
+ and isinstance(mesh, bpy.types.Mesh)
+ and tool.Geometry.get_mesh_props(mesh).ifc_definition_id
)
def draw(self, context):
@@ -588,7 +592,7 @@ class BIM_PT_workarounds(Panel):
class BIM_UL_representation_items(UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
+ def draw_item(self, context, layout: bpy.types.UILayout, data, item, icon, active_data, active_propname):
if item:
icon = "MATERIAL" if item.surface_style else "MESH_UVSPHERE"
row = layout.row(align=True)
diff --git a/src/bonsai/bonsai/bim/module/georeference/data.py b/src/bonsai/bonsai/bim/module/georeference/data.py
index 314de226ff..b64ef55c39 100644
--- a/src/bonsai/bonsai/bim/module/georeference/data.py
+++ b/src/bonsai/bonsai/bim/module/georeference/data.py
@@ -167,7 +167,7 @@ class GeoreferenceData:
result["rotation"] = str(round(ifcopenshell.util.geolocation.yaxis2angle(*wcs[:, 1][:2]), 3))
result["x"], result["y"], result["z"] = wcs[:, 3][:3]
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
if props.has_blender_offset:
blender_xyz = ifcopenshell.util.geolocation.enh2xyz(
result["x"],
@@ -193,7 +193,7 @@ class GeoreferenceData:
@classmethod
def local_origin(cls):
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
if not props.has_blender_offset:
return
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
diff --git a/src/bonsai/bonsai/bim/module/georeference/decorator.py b/src/bonsai/bonsai/bim/module/georeference/decorator.py
index bd5a9b97e8..afd9dfba27 100644
--- a/src/bonsai/bonsai/bim/module/georeference/decorator.py
+++ b/src/bonsai/bonsai/bim/module/georeference/decorator.py
@@ -21,6 +21,7 @@ import blf
import gpu
import bmesh
import ifcopenshell
+import ifcopenshell.util.geolocation
import bonsai.tool as tool
from math import radians
from bpy.types import SpaceView3D
@@ -53,7 +54,8 @@ class GeoreferenceDecorator:
cls.is_installed = False
def draw_batch(self, shader_type, content_pos, color, indices=None):
- self.scale = bpy.context.scene.BIMGeoreferenceProperties.visualization_scale
+ props = tool.Georeference.get_georeference_props()
+ self.scale = props.visualization_scale
content_pos = [v * self.scale for v in content_pos]
shader = self.line_shader if shader_type == "LINES" else self.shader
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
@@ -64,7 +66,7 @@ class GeoreferenceDecorator:
if not GeoreferenceData.is_loaded:
GeoreferenceData.load()
- props = context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
if not props.model_origin: # If this is empty, no georeferencing data has been loaded.
return
@@ -177,7 +179,7 @@ class GeoreferenceDecorator:
if not GeoreferenceData.is_loaded:
GeoreferenceData.load()
- props = context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
if not props.model_origin: # If this is empty, no georeferencing data has been loaded.
return
@@ -350,7 +352,7 @@ class GeoreferenceDecorator:
self.gn_angle = float(GeoreferenceData.data["map_derived_angle"] or 0)
self.tn_angle = float(GeoreferenceData.data["true_derived_angle"] or 0)
- props = context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
if props.has_blender_offset:
blender_angle = ifcopenshell.util.geolocation.xaxis2angle(
float(props.blender_x_axis_abscissa), float(props.blender_x_axis_ordinate)
diff --git a/src/bonsai/bonsai/bim/module/georeference/prop.py b/src/bonsai/bonsai/bim/module/georeference/prop.py
index f0458b2045..58a3088b86 100644
--- a/src/bonsai/bonsai/bim/module/georeference/prop.py
+++ b/src/bonsai/bonsai/bim/module/georeference/prop.py
@@ -33,15 +33,18 @@ from bpy.props import (
)
from bonsai.bim.module.georeference.data import GeoreferenceData
from bonsai.bim.module.georeference.decorator import GeoreferenceDecorator
+from typing import TYPE_CHECKING
-def get_coordinate_operation_class(self, context):
+def get_coordinate_operation_class(
+ self: "BIMGeoreferenceProperties", context: bpy.types.Context
+) -> list[tuple[str, str, str]]:
if not GeoreferenceData.is_loaded:
GeoreferenceData.load()
return GeoreferenceData.data["coordinate_operation_class"]
-def update_true_north_angle(self, context):
+def update_true_north_angle(self: "BIMGeoreferenceProperties", context: bpy.types.Context) -> None:
if self.is_changing_angle:
return
self.is_changing_angle = True
@@ -54,7 +57,7 @@ def update_true_north_angle(self, context):
self.is_changing_angle = False
-def update_true_north_vector(self, context):
+def update_true_north_vector(self: "BIMGeoreferenceProperties", context: bpy.types.Context) -> None:
if self.is_changing_angle:
return
self.is_changing_angle = True
@@ -67,7 +70,7 @@ def update_true_north_vector(self, context):
self.is_changing_angle = False
-def update_grid_north_angle(self, context):
+def update_grid_north_angle(self: "BIMGeoreferenceProperties", context: bpy.types.Context) -> None:
if self.is_changing_angle:
return
self.is_changing_angle = True
@@ -81,7 +84,7 @@ def update_grid_north_angle(self, context):
self.is_changing_angle = False
-def update_grid_north_vector(self, context):
+def update_grid_north_vector(self: "BIMGeoreferenceProperties", context: bpy.types.Context) -> None:
if self.is_changing_angle:
return
self.is_changing_angle = True
@@ -95,15 +98,15 @@ def update_grid_north_vector(self, context):
self.is_changing_angle = False
-def update_should_visualise(self, context):
+def update_should_visualise(self: "BIMGeoreferenceProperties", context: bpy.types.Context) -> None:
if self.should_visualise:
GeoreferenceDecorator.install(bpy.context)
else:
GeoreferenceDecorator.uninstall()
-def update_blender_coordinates(self, context):
- props = bpy.context.scene.BIMGeoreferenceProperties
+def update_blender_coordinates(self: "BIMGeoreferenceProperties", context: bpy.types.Context) -> None:
+ props = self
if props.is_updating_coordinates:
return
props.is_updating_coordinates = True
@@ -123,8 +126,8 @@ def update_blender_coordinates(self, context):
props.is_updating_coordinates = False
-def update_local_coordinates(self, context):
- props = bpy.context.scene.BIMGeoreferenceProperties
+def update_local_coordinates(self: "BIMGeoreferenceProperties", context: bpy.types.Context) -> None:
+ props = self
if props.is_updating_coordinates:
return
props.is_updating_coordinates = True
@@ -147,8 +150,8 @@ def update_local_coordinates(self, context):
props.is_updating_coordinates = False
-def update_map_coordinates(self, context):
- props = bpy.context.scene.BIMGeoreferenceProperties
+def update_map_coordinates(self: "BIMGeoreferenceProperties", context: bpy.types.Context) -> None:
+ props = self
if props.is_updating_coordinates:
return
props.is_updating_coordinates = True
@@ -248,3 +251,45 @@ class BIMGeoreferenceProperties(PropertyGroup):
wcs_y: StringProperty(name="WCS Y", default="0")
wcs_z: StringProperty(name="WCS Z", default="0")
wcs_rotation: StringProperty(name="WCS Rotation", default="0")
+
+ if TYPE_CHECKING:
+ coordinate_operation_class: str
+ is_changing_angle: bool
+ is_editing: bool
+ is_editing_wcs: bool
+ is_editing_true_north: bool
+ coordinate_operation: bpy.types.bpy_prop_collection_idprop[Attribute]
+ projected_crs: bpy.types.bpy_prop_collection_idprop[Attribute]
+ is_updating_coordinates: bool
+ blender_coordinates: str
+ local_coordinates: str
+ map_coordinates: str
+ should_visualise: bool
+ visualization_scale: float
+ grid_north_angle: str
+ x_axis_abscissa: str
+ x_axis_ordinate: str
+ x_axis_is_null: bool
+
+ host_model_origin: str
+ host_model_origin_si: str
+ host_model_project_north: str
+
+ model_origin: str
+ model_origin_si: str
+ model_project_north: str
+
+ has_blender_offset: bool
+ blender_offset_x: str
+ blender_offset_y: str
+ blender_offset_z: str
+ blender_x_axis_abscissa: str
+ blender_x_axis_ordinate: str
+
+ true_north_angle: str
+ true_north_abscissa: str
+ true_north_ordinate: str
+ wcs_x: str
+ wcs_y: str
+ wcs_z: str
+ wcs_rotation: str
diff --git a/src/bonsai/bonsai/bim/module/georeference/ui.py b/src/bonsai/bonsai/bim/module/georeference/ui.py
index 8b2dfd43e0..3dfec3a9f5 100644
--- a/src/bonsai/bonsai/bim/module/georeference/ui.py
+++ b/src/bonsai/bonsai/bim/module/georeference/ui.py
@@ -32,7 +32,7 @@ class BIM_PT_gis(Panel):
bl_parent_id = "BIM_PT_tab_geometry"
def draw_header(self, context):
- props = context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
row = self.layout.row(align=True)
icon = "HIDE_OFF" if props.should_visualise else "HIDE_ON"
row.label(text="") # empty text occupies the left of the row
@@ -43,7 +43,7 @@ class BIM_PT_gis(Panel):
def draw(self, context):
self.layout.use_property_split = True
self.layout.use_property_decorate = False
- props = context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
if not GeoreferenceData.is_loaded:
GeoreferenceData.load()
@@ -54,7 +54,7 @@ class BIM_PT_gis(Panel):
self.draw_ui(context)
def draw_editable_ui(self, context):
- props = context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
row = self.layout.row(align=True)
row.label(text="Projected CRS", icon="WORLD")
row.operator("bim.edit_georeferencing", icon="CHECKMARK", text="")
@@ -87,7 +87,7 @@ class BIM_PT_gis(Panel):
draw_attribute(attribute, self.layout.row())
def draw_ui(self, context):
- props = context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
if tool.Ifc.get_schema() == "IFC2X3":
row = self.layout.row()
@@ -149,7 +149,7 @@ class BIM_PT_gis_true_north(Panel):
if not GeoreferenceData.is_loaded:
GeoreferenceData.load()
- self.props = context.scene.BIMGeoreferenceProperties
+ self.props = tool.Georeference.get_georeference_props()
if self.props.is_editing_true_north:
self.draw_editable_ui(context)
@@ -200,7 +200,7 @@ class BIM_PT_gis_blender(Panel):
if not GeoreferenceData.is_loaded:
GeoreferenceData.load()
- props = context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
if props.has_blender_offset:
row = self.layout.row()
@@ -233,7 +233,7 @@ class BIM_PT_gis_wcs(Panel):
if not GeoreferenceData.is_loaded:
GeoreferenceData.load()
- props = context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
if props.is_editing_wcs:
self.draw_editable_ui(context)
@@ -263,7 +263,7 @@ class BIM_PT_gis_wcs(Panel):
row.operator("bim.enable_editing_wcs", icon="GREASEPENCIL", text="")
def draw_editable_ui(self, context):
- props = context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
row = self.layout.row(align=True)
row.label(text="World Coordinate System", icon="EMPTY_ARROWS")
@@ -293,7 +293,7 @@ class BIM_PT_gis_calculator(Panel):
if not GeoreferenceData.is_loaded:
GeoreferenceData.load()
- props = context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
if props.has_blender_offset:
row = self.layout.row(align=True)
diff --git a/src/bonsai/bonsai/bim/module/layer/operator.py b/src/bonsai/bonsai/bim/module/layer/operator.py
index 9c2f4a2249..13b5d79a56 100644
--- a/src/bonsai/bonsai/bim/module/layer/operator.py
+++ b/src/bonsai/bonsai/bim/module/layer/operator.py
@@ -147,7 +147,7 @@ class AssignPresentationLayer(bpy.types.Operator, tool.Ifc.Operator):
"layer.assign_layer",
self.file,
**{
- "items": [self.file.by_id(item.BIMMeshProperties.ifc_definition_id)],
+ "items": [self.file.by_id(tool.Geometry.get_mesh_props(item).ifc_definition_id)],
"layer": self.file.by_id(self.layer),
},
)
@@ -164,15 +164,10 @@ class UnassignPresentationLayer(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
item = bpy.data.meshes.get(self.item) if self.item else context.active_object.data
- self.file = IfcStore.get_file()
- ifcopenshell.api.run(
- "layer.unassign_layer",
- self.file,
- **{
- "items": [self.file.by_id(item.BIMMeshProperties.ifc_definition_id)],
- "layer": self.file.by_id(self.layer),
- },
- )
+ ifc_file = tool.Ifc.get()
+ representation = tool.Geometry.get_data_representation(item)
+ assert representation
+ ifcopenshell.api.layer.unassign_layer(ifc_file, items=[representation], layer=ifc_file.by_id(self.layer))
return {"FINISHED"}
diff --git a/src/bonsai/bonsai/bim/module/layer/ui.py b/src/bonsai/bonsai/bim/module/layer/ui.py
index 8c3b77d30a..5fb8db827d 100644
--- a/src/bonsai/bonsai/bim/module/layer/ui.py
+++ b/src/bonsai/bonsai/bim/module/layer/ui.py
@@ -77,7 +77,6 @@ class BIM_UL_layers(UIList):
row.label(text=item.name)
if context.active_object and isinstance(context.active_object.data, Mesh):
- mprops = context.active_object.data.BIMMeshProperties
if item.ifc_definition_id in LayersData.data["active_layers"]:
op = row.operator("bim.unassign_presentation_layer", text="", icon="KEYFRAME_HLT", emboss=False)
op.layer = item.ifc_definition_id
diff --git a/src/bonsai/bonsai/bim/module/material/data.py b/src/bonsai/bonsai/bim/module/material/data.py
index 50a03603df..a332a89804 100644
--- a/src/bonsai/bonsai/bim/module/material/data.py
+++ b/src/bonsai/bonsai/bim/module/material/data.py
@@ -282,8 +282,9 @@ class ObjectMaterialData:
if item.is_a("IfcMaterialLayer"):
total_thickness = item.LayerThickness
unit_system = bpy.context.scene.unit_settings.system
+ props = tool.Drawing.get_document_props()
if unit_system == "IMPERIAL":
- precision = bpy.context.scene.DocProperties.imperial_precision
+ precision = props.imperial_precision
else:
precision = None
formatted_thickness = format_distance(
diff --git a/src/bonsai/bonsai/bim/module/material/ui.py b/src/bonsai/bonsai/bim/module/material/ui.py
index 19865be4e8..9ddd5ccd9d 100644
--- a/src/bonsai/bonsai/bim/module/material/ui.py
+++ b/src/bonsai/bonsai/bim/module/material/ui.py
@@ -351,9 +351,10 @@ class BIM_PT_object_material(Panel):
if ObjectMaterialData.data["total_thickness"]:
total_thickness = ObjectMaterialData.data["total_thickness"]
unit_system = bpy.context.scene.unit_settings.system
+ props = tool.Drawing.get_document_props()
if unit_system == "IMPERIAL":
- precision = bpy.context.scene.DocProperties.imperial_precision
+ precision = props.imperial_precision
else:
precision = None
formatted_thickness = format_distance(
diff --git a/src/bonsai/bonsai/bim/module/misc/operator.py b/src/bonsai/bonsai/bim/module/misc/operator.py
index 198408eeb5..20bd454d35 100644
--- a/src/bonsai/bonsai/bim/module/misc/operator.py
+++ b/src/bonsai/bonsai/bim/module/misc/operator.py
@@ -299,7 +299,7 @@ class DrawSystemArrows(bpy.types.Operator, tool.Ifc.Operator):
tool.Blender.select_and_activate_single_object(context, curve)
def get_absolute_matrix(self, matrix):
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
if props.has_blender_offset:
matrix = np.array(
ifcopenshell.util.geolocation.global2local(
diff --git a/src/bonsai/bonsai/bim/module/model/opening.py b/src/bonsai/bonsai/bim/module/model/opening.py
index 0e73e71a91..d9da756705 100644
--- a/src/bonsai/bonsai/bim/module/model/opening.py
+++ b/src/bonsai/bonsai/bim/module/model/opening.py
@@ -236,9 +236,9 @@ class FilledOpeningGenerator:
voided_elements = ifcopenshell.util.element.get_parts(voided_element) or [voided_element]
for voided_element in voided_elements:
voided_obj = tool.Ifc.get_object(voided_element)
- if not voided_obj.data:
+ representation = tool.Geometry.get_active_representation(voided_obj)
+ if not representation:
continue
- representation = tool.Ifc.get().by_id(voided_obj.data.BIMMeshProperties.ifc_definition_id)
bonsai.core.geometry.switch_representation(
tool.Ifc,
tool.Geometry,
@@ -465,10 +465,13 @@ class AddBoolean(Operator, tool.Ifc.Operator):
self.report({"INFO"}, "At least two representation items must be selected to add a boolean.")
return {"CANCELLED"}
- props = context.scene.BIMBooleanProperties
+ props = tool.Feature.get_boolean_props()
- first_item = tool.Ifc.get().by_id(first_obj.data.BIMMeshProperties.ifc_definition_id)
- second_items = [tool.Ifc.get().by_id(o.data.BIMMeshProperties.ifc_definition_id) for o in second_objs]
+ first_item = tool.Geometry.get_active_representation(first_obj)
+ assert first_item
+ second_items = [
+ representation for o in second_objs if (representation := tool.Geometry.get_active_representation(o))
+ ]
booleans = ifcopenshell.api.geometry.add_boolean(tool.Ifc.get(), first_item, second_items, props.operator)
rep_obj = tool.Geometry.get_geometry_props().representation_obj
@@ -664,6 +667,7 @@ class EditOpenings(Operator, tool.Ifc.Operator):
def edit_openings(
self, building_objs: set[bpy.types.Object], opening_elements: set[ifcopenshell.entity_instance]
) -> None:
+ props = tool.Geometry.get_geometry_props()
objects_to_remove: set[bpy.types.Object] = set()
for opening_element in opening_elements:
opening_obj = tool.Ifc.get_object(opening_element)
@@ -690,8 +694,8 @@ class EditOpenings(Operator, tool.Ifc.Operator):
self.get_all_building_objects_of_similar_openings(opening_element)
) # NB this has nothing to do with clone similar_opening
tool.Ifc.unlink(element=opening_element)
- if bpy.context.scene.BIMGeometryProperties.representation_obj == opening_obj:
- bpy.context.scene.BIMGeometryProperties.representation_obj = None
+ if props.representation_obj == opening_obj:
+ props.representation_obj = None
objects_to_remove.add(opening_obj)
tool.Blender.remove_data_blocks(objects_to_remove, remove_unused_data=True)
@@ -817,11 +821,11 @@ class RemoveBoolean(Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
- props = context.scene.BIMBooleanProperties
+ props = tool.Feature.get_boolean_props()
return props.active_boolean
def _execute(self, context):
- props = context.scene.BIMBooleanProperties
+ props = tool.Feature.get_boolean_props()
ifcopenshell.api.geometry.remove_boolean(
tool.Ifc.get(), tool.Ifc.get().by_id(props.active_boolean.ifc_definition_id)
)
@@ -841,7 +845,7 @@ class SelectBoolean(Operator):
@classmethod
def poll(cls, context):
- props = context.scene.BIMBooleanProperties
+ props = tool.Feature.get_boolean_props()
return props.active_boolean
def invoke(self, context, event):
@@ -850,9 +854,9 @@ class SelectBoolean(Operator):
return self.execute(context)
def execute(self, context):
- props = context.scene.BIMBooleanProperties
+ props = tool.Feature.get_boolean_props()
queue = [tool.Ifc.get().by_id(props.active_boolean.ifc_definition_id)]
- items = {i.ifc_definition_id: i.obj for i in context.scene.BIMGeometryProperties.item_objs}
+ items = {i.ifc_definition_id: i.obj for i in tool.Geometry.get_geometry_props().item_objs}
while queue:
item = queue.pop()
if item.is_a("IfcBooleanResult"):
@@ -911,11 +915,12 @@ class DecorationsHandler:
gpu.state.point_size_set(6)
gpu.state.blend_set("ALPHA")
+ gprops = tool.Geometry.get_geometry_props()
for opening in props.openings:
obj = opening.obj
- if context.scene.BIMGeometryProperties.representation_obj == obj:
+ if gprops.representation_obj == obj:
# We are editing the representation of the opening :
- for item in context.scene.BIMGeometryProperties.item_objs:
+ for item in gprops.item_objs:
if item.obj.mode == "EDIT":
obj = item.obj
break
diff --git a/src/bonsai/bonsai/bim/module/model/profile.py b/src/bonsai/bonsai/bim/module/model/profile.py
index 6286422f99..d96e7d0b51 100644
--- a/src/bonsai/bonsai/bim/module/model/profile.py
+++ b/src/bonsai/bonsai/bim/module/model/profile.py
@@ -22,6 +22,7 @@ import bmesh
import mathutils.geometry
import ifcopenshell
import ifcopenshell.api
+import ifcopenshell.api.geometry
import ifcopenshell.util.type
import ifcopenshell.util.unit
import ifcopenshell.util.element
@@ -265,7 +266,7 @@ class DumbProfileRegenerator:
def _regenerate_from_type(self, related_object: ifcopenshell.entity_instance) -> None:
obj = tool.Ifc.get_object(related_object)
- if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
+ if not obj or not tool.Geometry.get_active_representation(obj):
return
DumbProfileRecalculator().recalculate([obj])
@@ -501,7 +502,9 @@ class DumbProfileJoiner:
"geometry.assign_representation", tool.Ifc.get(), product=element, representation=new_axis
)
- def get_placement_axes(body_representation):
+ def get_placement_axes(
+ body_representation: Union[ifcopenshell.entity_instance, None],
+ ) -> Union[tuple[tuple[float, float, float], tuple[float, float, float]], tuple[None, None]]:
if not body_representation:
return None, None
extrusion = tool.Model.get_extrusion(body_representation)
@@ -513,8 +516,7 @@ class DumbProfileJoiner:
return ((0.0, 0.0, 1.0), (1.0, 0.0, 0.0))
old_body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
- new_body = ifcopenshell.api.run(
- "geometry.add_profile_representation",
+ new_body = ifcopenshell.api.geometry.add_profile_representation(
tool.Ifc.get(),
context=self.body_context,
profile=self.profile,
@@ -527,8 +529,9 @@ class DumbProfileJoiner:
if old_body:
for inverse in tool.Ifc.get().get_inverse(old_body):
ifcopenshell.util.element.replace_attribute(inverse, old_body, new_body)
- obj.data.BIMMeshProperties.ifc_definition_id = int(new_body.id())
- obj.data.name = f"{self.body_context.id()}/{new_body.id()}"
+ assert isinstance(mesh := obj.data, bpy.types.Mesh)
+ tool.Ifc.link(new_body, mesh)
+ mesh.name = tool.Loader.get_mesh_name(new_body)
bonsai.core.geometry.remove_representation(tool.Ifc, tool.Geometry, obj=obj, representation=old_body)
else:
ifcopenshell.api.run(
diff --git a/src/bonsai/bonsai/bim/module/model/slab.py b/src/bonsai/bonsai/bim/module/model/slab.py
index de3078819d..328eb34090 100644
--- a/src/bonsai/bonsai/bim/module/model/slab.py
+++ b/src/bonsai/bonsai/bim/module/model/slab.py
@@ -259,7 +259,7 @@ class DumbSlabPlaner:
self, related_object: ifcopenshell.entity_instance, layer_set_direction: Optional[str], new_thickness: float
) -> None:
obj = tool.Ifc.get_object(related_object)
- if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
+ if not obj or not tool.Geometry.get_active_representation(obj):
return
material = ifcopenshell.util.element.get_material(related_object)
diff --git a/src/bonsai/bonsai/bim/module/model/wall.py b/src/bonsai/bonsai/bim/module/model/wall.py
index 5a94be7528..d6e6ec7a31 100644
--- a/src/bonsai/bonsai/bim/module/model/wall.py
+++ b/src/bonsai/bonsai/bim/module/model/wall.py
@@ -908,7 +908,7 @@ class DumbWallPlaner:
self, related_object: ifcopenshell.entity_instance, layer_set_direction: Optional[str]
) -> None:
obj = tool.Ifc.get_object(related_object)
- if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
+ if not obj or not tool.Geometry.get_active_representation(obj):
return
material = ifcopenshell.util.element.get_material(related_object)
@@ -1331,7 +1331,9 @@ class DumbWallJoiner:
axis = body = tool.Model.get_wall_axis(obj)["reference"]
self.axis = copy.deepcopy(axis)
self.body = copy.deepcopy(body)
- extrusion_data = self.get_extrusion_data(tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id))
+ representation = tool.Geometry.get_active_representation(obj)
+ assert representation
+ extrusion_data = self.get_extrusion_data(representation)
height = extrusion_data["height"]
x_angle = extrusion_data["x_angle"]
self.clippings = []
@@ -1410,8 +1412,9 @@ class DumbWallJoiner:
if old_body:
for inverse in tool.Ifc.get().get_inverse(old_body):
ifcopenshell.util.element.replace_attribute(inverse, old_body, new_body)
- obj.data.BIMMeshProperties.ifc_definition_id = int(new_body.id())
- obj.data.name = f"{self.body_context.id()}/{new_body.id()}"
+ assert isinstance(mesh := obj.data, bpy.types.Mesh)
+ tool.Ifc.link(new_body, mesh)
+ mesh.name = tool.Loader.get_mesh_name(new_body)
bonsai.core.geometry.remove_representation(tool.Ifc, tool.Geometry, obj=obj, representation=old_body)
else:
ifcopenshell.api.run(
@@ -1708,10 +1711,11 @@ class DumbWallJoiner:
return True
- def clip(self, wall1, slab2):
+ def clip(self, wall1: bpy.types.Object, slab2: bpy.types.Object) -> float:
"""returns height of the clipped wall, adds clipping plane to `clippings`"""
element1 = tool.Ifc.get_entity(wall1)
element2 = tool.Ifc.get_entity(slab2)
+ assert element1 and element2
layers1 = tool.Model.get_material_layer_parameters(element1)
axis1 = tool.Model.get_wall_axis(wall1, layers1)
@@ -1719,7 +1723,9 @@ class DumbWallJoiner:
bases = [axis1["base"][0].to_3d(), axis1["base"][1].to_3d(), axis1["side"][0].to_3d(), axis1["side"][1].to_3d()]
bases = [Vector((v[0], v[1], wall1.matrix_world.translation.z)) for v in bases] # add wall Z location
- extrusion = self.get_extrusion_data(tool.Ifc.get().by_id(wall1.data.BIMMeshProperties.ifc_definition_id))
+ representation = tool.Geometry.get_active_representation(wall1)
+ assert representation
+ extrusion = self.get_extrusion_data(representation)
wall_dir = wall1.matrix_world.to_quaternion() @ extrusion["direction"]
slab_pt = slab2.matrix_world @ Vector((0, 0, 0))
diff --git a/src/bonsai/bonsai/bim/module/model/workspace.py b/src/bonsai/bonsai/bim/module/model/workspace.py
index ad4446b9b6..11f4fe73c5 100644
--- a/src/bonsai/bonsai/bim/module/model/workspace.py
+++ b/src/bonsai/bonsai/bim/module/model/workspace.py
@@ -96,7 +96,8 @@ class BimTool(WorkSpaceTool):
def draw_settings(
cls, context: bpy.types.Context, layout: bpy.types.UILayout, ws_tool: bpy.types.WorkSpaceTool
) -> None:
- if context.scene.BIMGeometryProperties.mode == "ITEM":
+ props = tool.Geometry.get_geometry_props()
+ if props.mode == "ITEM":
EditItemUI.draw(context, layout)
elif (
active_ifc_object := (context.active_object and tool.Ifc.get_entity(context.active_object))
@@ -430,7 +431,7 @@ class EditItemUI:
obj = context.active_object
assert obj
- mesh_props = obj.data.BIMMeshProperties
+ mesh_props = tool.Geometry.get_mesh_props(obj.data)
if AuthoringData.data["is_representation_item_swept_solid"]:
# TODO: support EndSweptArea for IfcRevolvedAreaSolidTapered,
# will need to add second attribute for this.
@@ -440,10 +441,10 @@ class EditItemUI:
op = row.operator("bim.name_profile", text="", icon="TAG")
op.extrusion_item_obj = obj.name
- for item_attribute in obj.data.BIMMeshProperties.item_attributes:
+ for item_attribute in mesh_props.item_attributes:
row = cls.layout.row()
draw_attribute(item_attribute, cls.layout)
- if len(obj.data.BIMMeshProperties.item_attributes) or AuthoringData.data["is_representation_item_swept_solid"]:
+ if len(mesh_props.item_attributes) or AuthoringData.data["is_representation_item_swept_solid"]:
row = cls.layout.row()
row.operator("bim.update_item_attributes", icon="FILE_REFRESH", text="")
@@ -1136,7 +1137,8 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
row.prop(self, "z")
def hotkey_S_A(self):
- if bpy.context.scene.BIMGeometryProperties.mode == "ITEM":
+ gprops = tool.Geometry.get_geometry_props()
+ if gprops.mode == "ITEM":
bpy.ops.wm.call_menu(name="BIM_MT_add_representation_item")
return
diff --git a/src/bonsai/bonsai/bim/module/profile/data.py b/src/bonsai/bonsai/bim/module/profile/data.py
index 88bd85197c..38a32d34f7 100644
--- a/src/bonsai/bonsai/bim/module/profile/data.py
+++ b/src/bonsai/bonsai/bim/module/profile/data.py
@@ -94,7 +94,7 @@ class ProfileData:
obj = bpy.context.active_object
return (
obj
- and obj.data
- and hasattr(obj.data, "BIMMeshProperties")
- and obj.data.BIMMeshProperties.subshape_type == "PROFILE"
+ and (data := obj.data)
+ and isinstance(data, bpy.types.Mesh)
+ and tool.Geometry.get_mesh_props(data).subshape_type == "PROFILE"
)
diff --git a/src/bonsai/bonsai/bim/module/profile/operator.py b/src/bonsai/bonsai/bim/module/profile/operator.py
index 1a330917db..eab0db8ef2 100644
--- a/src/bonsai/bonsai/bim/module/profile/operator.py
+++ b/src/bonsai/bonsai/bim/module/profile/operator.py
@@ -248,7 +248,12 @@ class EnableEditingArbitraryProfile(bpy.types.Operator):
def disable_editing_arbitrary_profile(context):
obj = context.active_object
- if obj and obj.type == "MESH" and obj.data and obj.data.BIMMeshProperties.subshape_type == "PROFILE":
+ if (
+ obj
+ and (mesh := obj.data)
+ and isinstance(mesh, bpy.types.Mesh)
+ and tool.Geometry.get_mesh_props(mesh).subshape_type == "PROFILE"
+ ):
ProfileDecorator.uninstall()
bpy.ops.object.mode_set(mode="OBJECT")
profile_mesh = obj.data
diff --git a/src/bonsai/bonsai/bim/module/project/decorator.py b/src/bonsai/bonsai/bim/module/project/decorator.py
index cd00264224..1f738dbc90 100644
--- a/src/bonsai/bonsai/bim/module/project/decorator.py
+++ b/src/bonsai/bonsai/bim/module/project/decorator.py
@@ -31,7 +31,8 @@ from typing import Union
@persistent
def toggle_decorations_on_load(*args):
- if bpy.context.scene.BIMProjectProperties.clipping_planes:
+ props = tool.Project.get_project_props()
+ if props.clipping_planes:
ClippingPlaneDecorator.install(bpy.context)
else:
ClippingPlaneDecorator.uninstall()
@@ -99,7 +100,7 @@ class ProjectDecorator:
selected_edges = []
selected_tris = []
- props = context.scene.BIMProjectProperties
+ props = tool.Project.get_project_props()
try:
obj = props.queried_obj
selected_vertices = obj["selected_vertices"]
@@ -171,7 +172,8 @@ class ClippingPlaneDecorator:
unselected_edges = []
unselected_tris = []
- for clipping_plane in context.scene.BIMProjectProperties.clipping_planes:
+ props = tool.Project.get_project_props()
+ for clipping_plane in props.clipping_planes:
obj = clipping_plane.obj
if not obj or not obj.data:
continue
diff --git a/src/bonsai/bonsai/bim/module/project/gizmo.py b/src/bonsai/bonsai/bim/module/project/gizmo.py
index e5da7e3d52..bb372b9cb7 100644
--- a/src/bonsai/bonsai/bim/module/project/gizmo.py
+++ b/src/bonsai/bonsai/bim/module/project/gizmo.py
@@ -18,6 +18,7 @@
import bpy
+import bonsai.tool as tool
from bpy.types import GizmoGroup
from mathutils import Matrix
@@ -32,11 +33,12 @@ class ClippingPlane(GizmoGroup):
@classmethod
def poll(cls, context):
obj = context.object
+ props = tool.Project.get_project_props()
return (
context.selected_objects
and obj
and obj.name.startswith("ClippingPlane")
- and obj in [sp.obj for sp in context.scene.BIMProjectProperties.clipping_planes]
+ and obj in [sp.obj for sp in props.clipping_planes]
)
def setup(self, context):
diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py
index 8510a7d125..0aea8d00a3 100644
--- a/src/bonsai/bonsai/bim/module/project/operator.py
+++ b/src/bonsai/bonsai/bim/module/project/operator.py
@@ -75,35 +75,36 @@ class NewProject(bpy.types.Operator):
def execute(self, context):
bpy.ops.wm.read_homefile()
+ pprops = tool.Project.get_project_props()
if self.preset == "metric_m":
- bpy.context.scene.BIMProjectProperties.export_schema = "IFC4"
+ 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"
- bpy.context.scene.BIMProjectProperties.template_file = "0"
+ pprops.template_file = "0"
elif self.preset == "metric_mm":
- bpy.context.scene.BIMProjectProperties.export_schema = "IFC4"
+ 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"
- bpy.context.scene.BIMProjectProperties.template_file = "0"
+ pprops.template_file = "0"
elif self.preset == "imperial_ft":
- bpy.context.scene.BIMProjectProperties.export_schema = "IFC4"
+ 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"
- bpy.context.scene.BIMProjectProperties.template_file = "0"
+ pprops.template_file = "0"
elif self.preset == "demo":
- bpy.context.scene.BIMProjectProperties.export_schema = "IFC4"
+ 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"
- bpy.context.scene.BIMProjectProperties.template_file = "IFC4 Demo Template.ifc"
+ pprops.template_file = "IFC4 Demo Template.ifc"
if self.preset != "wizard":
bpy.ops.bim.create_project()
@@ -126,7 +127,7 @@ class CreateProject(bpy.types.Operator):
return {"FINISHED"}
def _execute(self, context):
- props = context.scene.BIMProjectProperties
+ props = tool.Project.get_project_props()
template = None if props.template_file == "0" else props.template_file
if tool.Blender.is_default_scene():
for obj in bpy.data.objects:
@@ -592,7 +593,8 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator):
self.import_presentation_style_from_ifc(element, context)
try:
- context.scene.BIMProjectProperties.library_elements[self.prop_index].is_appended = True
+ props = tool.Project.get_project_props()
+ props.library_elements[self.prop_index].is_appended = True
except:
# TODO Remove this terrible code when I refactor this into the core
pass
@@ -771,8 +773,8 @@ class EnableEditingHeader(bpy.types.Operator):
return IfcStore.get_file()
def execute(self, context):
- self.file = IfcStore.get_file()
- props = context.scene.BIMProjectProperties
+ self.file = tool.Ifc.get()
+ props = tool.Project.get_project_props()
props.is_editing = True
mvd = "".join(IfcStore.get_file().wrapped_data.header.file_description.description)
@@ -818,8 +820,8 @@ class EditHeader(bpy.types.Operator):
return result
def _execute(self, context):
- self.file = IfcStore.get_file()
- props = context.scene.BIMProjectProperties
+ self.file = tool.Ifc.get()
+ props = tool.Project.get_project_props()
props.is_editing = True
self.file.wrapped_data.header.file_description.description = (f"ViewDefinition[{props.mvd}]",)
@@ -860,7 +862,8 @@ class DisableEditingHeader(bpy.types.Operator):
bl_description = "Cancel unsaved header information"
def execute(self, context):
- context.scene.BIMProjectProperties.is_editing = False
+ props = tool.Project.get_project_props()
+ props.is_editing = False
return {"FINISHED"}
@@ -983,9 +986,10 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
f"Error loading IFC file from filepath '{filepath}'. See logs above in the system console for the details.",
)
return {"CANCELLED"}
- context.scene.BIMProjectProperties.is_loading = True
- context.scene.BIMProjectProperties.total_elements = len(tool.Ifc.get().by_type("IfcElement"))
- context.scene.BIMProjectProperties.use_relative_project_path = self.use_relative_path
+ props = tool.Project.get_project_props()
+ props.is_loading = True
+ props.total_elements = len(tool.Ifc.get().by_type("IfcElement"))
+ props.use_relative_project_path = self.use_relative_path
tool.Blender.register_toolbar()
tool.Project.add_recent_ifc_project(self.get_filepath_abs())
@@ -1051,8 +1055,8 @@ class LoadProjectElements(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- self.props = context.scene.BIMProjectProperties
- self.file = IfcStore.get_file()
+ self.props = tool.Project.get_project_props()
+ self.file = tool.Ifc.get()
bonsai.bim.schema.reload(self.file.schema_identifier)
start = time.time()
logger = logging.getLogger("ImportIFC")
@@ -1084,7 +1088,8 @@ class LoadProjectElements(bpy.types.Operator):
ifc_importer.execute()
settings.logger.info("Import finished in {:.2f} seconds".format(time.time() - start))
print("Import finished in {:.2f} seconds".format(time.time() - start))
- context.scene.BIMProjectProperties.is_loading = False
+ props = tool.Project.get_project_props()
+ props.is_loading = False
tool.Project.load_pset_templates()
tool.Project.load_default_thumbnails()
@@ -1156,7 +1161,8 @@ class ToggleFilterCategories(bpy.types.Operator):
should_select: bpy.props.BoolProperty(name="Should Select", default=True)
def execute(self, context):
- for filter_category in context.scene.BIMProjectProperties.filter_categories:
+ props = tool.Project.get_project_props()
+ for filter_category in props.filter_categories:
filter_category.is_selected = self.should_select
return {"FINISHED"}
@@ -1183,7 +1189,7 @@ class LinkIfc(bpy.types.Operator):
directory: str
def draw(self, context):
- pprops = context.scene.BIMProjectProperties
+ pprops = tool.Project.get_project_props()
row = self.layout.row()
row.prop(self, "use_relative_path")
row = self.layout.row()
@@ -1204,7 +1210,8 @@ class LinkIfc(bpy.types.Operator):
if bpy.data.filepath and filepath.samefile(bpy.data.filepath):
self.report({"INFO"}, "Can't link the current .blend file")
continue
- new = context.scene.BIMProjectProperties.links.add()
+ props = tool.Project.get_project_props()
+ new = props.links.add()
filepath = tool.Ifc.get_uri(filepath, use_relative_path=self.use_relative_path)
new.name = filepath
status = bpy.ops.bim.load_link(filepath=filepath, use_cache=self.use_cache)
@@ -1235,9 +1242,10 @@ class UnlinkIfc(bpy.types.Operator):
def execute(self, context):
filepath = Path(self.filepath).as_posix()
bpy.ops.bim.unload_link(filepath=filepath)
- index = context.scene.BIMProjectProperties.links.find(filepath)
+ props = tool.Project.get_project_props()
+ index = props.links.find(filepath)
if index != -1:
- context.scene.BIMProjectProperties.links.remove(index)
+ props.links.remove(index)
return {"FINISHED"}
@@ -1257,8 +1265,9 @@ class UnloadLink(bpy.types.Operator):
if tool.Blender.ensure_blender_path_is_abs(Path(library.filepath)) == filepath:
bpy.data.libraries.remove(library)
- links = context.scene.BIMProjectProperties.links
- link = links.get(self.filepath)
+ props = tool.Project.get_project_props()
+ links = props.links
+ link = links[self.filepath]
# Let's assume that user might delete it.
if empty_handle := link.empty_handle:
bpy.data.objects.remove(empty_handle)
@@ -1267,7 +1276,7 @@ class UnloadLink(bpy.types.Operator):
if not any([l.is_loaded for l in links]):
ProjectDecorator.uninstall()
# we make sure we don't draw queried object from the file that was just unlinked
- elif queried_obj := context.scene.BIMProjectProperties.queried_obj:
+ elif queried_obj := props.queried_obj:
queried_filepath = Path(queried_obj["ifc_filepath"])
if queried_filepath == filepath:
ProjectDecorator.uninstall()
@@ -1299,7 +1308,7 @@ class LoadLink(bpy.types.Operator):
def link_blend(self, filepath: Path) -> None:
with bpy.data.libraries.load(str(filepath), link=True) as (data_from, data_to):
data_to.scenes = data_from.scenes
- link = bpy.context.scene.BIMProjectProperties.links[self.filepath]
+ link = tool.Project.get_project_props().links[self.filepath]
for scene in bpy.data.scenes:
if not scene.library or Path(scene.library.filepath) != filepath:
continue
@@ -1325,13 +1334,13 @@ class LoadLink(bpy.types.Operator):
if not blend_filepath.exists():
pprops = tool.Project.get_project_props()
- gprops = bpy.context.scene.BIMGeoreferenceProperties
+ gprops = tool.Georeference.get_georeference_props()
code = f"""
import bpy
def run():
- gprops = bpy.context.scene.BIMGeoreferenceProperties
+ gprops = tool.Georeference.get_georeference_props()
# Our model origin becomes their host model origin
gprops.host_model_origin = "{gprops.model_origin}"
gprops.host_model_origin_si = "{gprops.model_origin_si}"
@@ -1342,7 +1351,7 @@ def run():
gprops.blender_offset_z = "{gprops.blender_offset_z}"
gprops.blender_x_axis_abscissa = "{gprops.blender_x_axis_abscissa}"
gprops.blender_x_axis_ordinate = "{gprops.blender_x_axis_ordinate}"
- pprops = bpy.context.scene.BIMProjectProperties
+ pprops = tool.Project.get_project_props()
pprops.distance_limit = {pprops.distance_limit}
pprops.false_origin_mode = "{pprops.false_origin_mode}"
pprops.false_origin = "{pprops.false_origin}"
@@ -1397,7 +1406,7 @@ except Exception as e:
with open(json_filepath, "r") as f:
data = json.load(f)
- gprops = bpy.context.scene.BIMGeoreferenceProperties
+ gprops = tool.Georeference.get_georeference_props()
for prop in ("model_origin", "model_origin_si", "model_project_north"):
if (value := data.get(prop, None)) is not None:
setattr(gprops, prop, value)
@@ -1433,8 +1442,8 @@ class ToggleLinkSelectability(bpy.types.Operator):
link: bpy.props.StringProperty(name="Linked IFC Filepath")
def execute(self, context):
- props = context.scene.BIMProjectProperties
- link = props.links.get(self.link)
+ props = tool.Project.get_project_props()
+ link = props.links[self.link]
self.library_filepath = tool.Blender.ensure_blender_path_is_abs(Path(self.link).with_suffix(".ifc.cache.blend"))
link.is_selectable = (is_selectable := not link.is_selectable)
for collection in self.get_linked_collections():
@@ -1460,8 +1469,8 @@ class ToggleLinkVisibility(bpy.types.Operator):
mode: bpy.props.EnumProperty(name="Visibility Mode", items=((i, i, "") for i in ("WIREFRAME", "VISIBLE")))
def execute(self, context):
- props = context.scene.BIMProjectProperties
- link = props.links.get(self.link)
+ props = tool.Project.get_project_props()
+ link = props.links[self.link]
self.library_filepath = tool.Blender.ensure_blender_path_is_abs(Path(self.link).with_suffix(".ifc.cache.blend"))
if self.mode == "WIREFRAME":
self.toggle_wireframe(link)
@@ -1507,7 +1516,7 @@ class SelectLinkHandle(bpy.types.Operator):
index: bpy.props.IntProperty(name="Link Index")
def execute(self, context):
- props = context.scene.BIMProjectProperties
+ props = tool.Project.get_project_props()
link = props.links[self.index]
handle = link.empty_handle
if not handle:
@@ -1549,7 +1558,7 @@ class ExportIFC(bpy.types.Operator):
bpy.ops.wm.save_mainfile("INVOKE_DEFAULT")
return {"FINISHED"}
- self.use_relative_path = context.scene.BIMProjectProperties.use_relative_project_path
+ 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:
self.filepath = str(tool.Blender.ensure_blender_path_is_abs(Path(filepath)))
return self.execute(context)
@@ -1564,7 +1573,7 @@ class ExportIFC(bpy.types.Operator):
return {"RUNNING_MODAL"}
def execute(self, context):
- project_props = context.scene.BIMProjectProperties
+ project_props = tool.Project.get_project_props()
project_props.use_relative_project_path = self.use_relative_path
if project_props.should_disable_undo_on_save:
old_history_size = tool.Ifc.get().history_size
@@ -1612,10 +1621,12 @@ class ExportIFC(bpy.types.Operator):
# New project created in Bonsai should be in recent projects too.
tool.Project.add_recent_ifc_project(Path(output_file))
scene = context.scene
- if not scene.DocProperties.ifc_files:
- new = scene.DocProperties.ifc_files.add()
+ props = tool.Drawing.get_document_props()
+ if not props.ifc_files:
+ new = props.ifc_files.add()
new.name = output_file
- if context.scene.BIMProjectProperties.use_relative_project_path and bpy.data.is_saved:
+ 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
@@ -1660,8 +1671,8 @@ class LoadLinkedProject(bpy.types.Operator):
start = time.time()
- pprops = bpy.context.scene.BIMProjectProperties
- gprops = bpy.context.scene.BIMGeoreferenceProperties
+ pprops = tool.Project.get_project_props()
+ gprops = tool.Georeference.get_georeference_props()
self.filepath = Path(self.filepath).as_posix()
print("Processing", self.filepath)
@@ -1875,7 +1886,7 @@ class LoadLinkedProject(bpy.types.Operator):
mesh = bpy.data.meshes.new("Mesh")
geometry = shape.geometry
- gprops = bpy.context.scene.BIMGeoreferenceProperties
+ gprops = tool.Georeference.get_georeference_props()
if (
gprops.has_blender_offset
and geometry.verts
@@ -1981,9 +1992,8 @@ class QueryLinkedElement(bpy.types.Operator):
from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_origin_3d
LinksData.linked_data = {}
- props = context.scene.BIMProjectProperties
+ props = tool.Project.get_project_props()
props.queried_obj = None
- props.quried_obj_root = None
for area in bpy.context.screen.areas:
if area.type == "PROPERTIES":
@@ -2119,6 +2129,7 @@ class AppendInspectedLinkedElement(AppendLibraryElement):
def _execute(self, context):
from bonsai.bim.module.project.data import LinksData
+ props = tool.Project.get_project_props()
if not LinksData.linked_data:
self.report({"INFO"}, "No linked element found.")
return {"CANCELLED"}
@@ -2128,7 +2139,7 @@ class AppendInspectedLinkedElement(AppendLibraryElement):
self.report({"INFO"}, "Cannot find Global Id for element.")
return {"CANCELLED"}
- queried_obj = context.scene.BIMProjectProperties.queried_obj
+ queried_obj = props.queried_obj
ifc_file = tool.Ifc.get()
linked_ifc_file: ifcopenshell.file
@@ -2275,34 +2286,36 @@ class RefreshClippingPlanes(bpy.types.Operator):
def modal(self, context, event):
should_refresh = False
+ props = tool.Project.get_project_props()
self.clean_deleted_planes(context)
- for clipping_plane in context.scene.BIMProjectProperties.clipping_planes:
+ for clipping_plane in props.clipping_planes:
if clipping_plane.obj and self.is_moved(clipping_plane.obj):
should_refresh = True
break
- total_planes = len(context.scene.BIMProjectProperties.clipping_planes)
+ total_planes = len(props.clipping_planes)
if should_refresh or total_planes != self.total_planes:
self.refresh_clipping_planes(context)
- for clipping_plane in context.scene.BIMProjectProperties.clipping_planes:
+ for clipping_plane in props.clipping_planes:
if clipping_plane.obj:
tool.Geometry.record_object_position(clipping_plane.obj)
self.total_planes = total_planes
return {"PASS_THROUGH"}
- def clean_deleted_planes(self, context):
+ def clean_deleted_planes(self, context: bpy.types.Context) -> None:
+ props = tool.Project.get_project_props()
while True:
- for i, clipping_plane in enumerate(context.scene.BIMProjectProperties.clipping_planes):
+ for i, clipping_plane in enumerate(props.clipping_planes):
if clipping_plane.obj:
try:
clipping_plane.obj.name
except:
- context.scene.BIMProjectProperties.clipping_planes.remove(i)
+ props.clipping_planes.remove(i)
break
else:
- context.scene.BIMProjectProperties.clipping_planes.remove(i)
+ props.clipping_planes.remove(i)
break
else:
break
@@ -2326,14 +2339,15 @@ class RefreshClippingPlanes(bpy.types.Operator):
region = next(r for r in area.regions if r.type == "WINDOW")
data = region.data
- if not len(context.scene.BIMProjectProperties.clipping_planes):
+ props = tool.Project.get_project_props()
+ if not len(props.clipping_planes):
data.use_clip_planes = False
else:
with bpy.context.temp_override(area=area, region=region):
bpy.ops.view3d.clip_border()
clip_planes = []
- for clipping_plane in bpy.context.scene.BIMProjectProperties.clipping_planes:
+ for clipping_plane in tool.Project.get_project_props().clipping_planes:
obj = clipping_plane.obj
if not obj:
continue
@@ -2372,8 +2386,8 @@ class CreateClippingPlane(bpy.types.Operator):
from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_origin_3d
# Clean up deleted planes
-
- if len(context.scene.BIMProjectProperties.clipping_planes) > 5:
+ props = tool.Project.get_project_props()
+ if len(props.clipping_planes) > 5:
self.report({"INFO"}, "Maximum of six clipping planes allowed.")
return {"FINISHED"}
@@ -2413,7 +2427,7 @@ class CreateClippingPlane(bpy.types.Operator):
context.scene.cursor.location = location
- new = context.scene.BIMProjectProperties.clipping_planes.add()
+ new = tool.Project.get_project_props().clipping_planes.add()
new.obj = plane_obj
tool.Blender.set_active_object(plane_obj)
@@ -2444,7 +2458,7 @@ class FlipClippingPlane(bpy.types.Operator):
def execute(self, context):
obj = context.active_object
- if obj in context.scene.BIMProjectProperties.clipping_planes_objs:
+ if obj in tool.Project.get_project_props().clipping_planes_objs:
obj.rotation_euler[0] += radians(180)
context.view_layer.update()
return {"FINISHED"}
@@ -2462,14 +2476,15 @@ class BIM_OT_save_clipping_planes(bpy.types.Operator):
@classmethod
def poll(cls, context):
if IfcStore.path:
- return context.scene.BIMProjectProperties.clipping_planes
+ return tool.Project.get_project_props().clipping_planes
cls.poll_message_set("Please Save The IFC File")
def execute(self, context):
clipping_planes_to_serialize = defaultdict(dict)
- clipping_planes = context.scene.BIMProjectProperties.clipping_planes
+ clipping_planes = tool.Project.get_project_props().clipping_planes
for clipping_plane in clipping_planes:
obj = clipping_plane.obj
+ assert obj
name = obj.name
clipping_planes_to_serialize[name]["location"] = obj.location[0:3]
clipping_planes_to_serialize[name]["rotation"] = obj.rotation_euler[0:3]
@@ -2495,13 +2510,14 @@ class BIM_OT_load_clipping_planes(bpy.types.Operator):
cls.poll_message_set("Please Save The IFC File")
def execute(self, context):
- bpy.data.batch_remove(context.scene.BIMProjectProperties.clipping_planes_objs)
- context.scene.BIMProjectProperties.clipping_planes.clear()
+ props = tool.Project.get_project_props()
+ bpy.data.batch_remove(props.clipping_planes_objs)
+ props.clipping_planes.clear()
with open(Path(IfcStore.path).with_name(CLIPPING_PLANES_FILE_NAME), "r") as file:
clipping_planes_dict = json.load(file)
for name, values in clipping_planes_dict.items():
bpy.ops.bim.create_clipping_plane()
- obj = context.scene.BIMProjectProperties.clipping_planes_objs[-1]
+ obj = props.clipping_planes_objs[-1]
obj.name = name
obj.location = values["location"]
obj.rotation_euler = values["rotation"]
diff --git a/src/bonsai/bonsai/bim/module/qto/calculator.py b/src/bonsai/bonsai/bim/module/qto/calculator.py
index e4ba0580a7..871b128db6 100644
--- a/src/bonsai/bonsai/bim/module/qto/calculator.py
+++ b/src/bonsai/bonsai/bim/module/qto/calculator.py
@@ -451,7 +451,8 @@ def get_side_area(o: bpy.types.Object) -> float:
def get_cross_section_area(obj: bpy.types.Object) -> float:
- representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
+ representation = tool.Geometry.get_active_representation(obj)
+ assert representation
item = representation.Items[0]
while True:
if item.is_a("IfcExtrudedAreaSolid"):
diff --git a/src/bonsai/bonsai/bim/module/search/operator.py b/src/bonsai/bonsai/bim/module/search/operator.py
index 5083fb2445..4022c1d5ff 100644
--- a/src/bonsai/bonsai/bim/module/search/operator.py
+++ b/src/bonsai/bonsai/bim/module/search/operator.py
@@ -711,14 +711,14 @@ class SelectSimilar(Operator, tool.Ifc.Operator):
return self.execute(context)
def _execute(self, context):
- props = context.scene.BIMSearchProperties
obj = context.active_object
element = tool.Ifc.get_entity(obj)
key = self.key
if key == "PredefinedType":
key = "predefined_type"
value = ifcopenshell.util.selector.get_element_value(element, key)
- tolerance = bpy.context.scene.DocProperties.tolerance
+ dprops = tool.Drawing.get_document_props()
+ tolerance = dprops.tolerance
# Determine the number of decimal places based on the magnitude of the rounding value
if tolerance < 1:
diff --git a/src/bonsai/bonsai/bim/module/style/operator.py b/src/bonsai/bonsai/bim/module/style/operator.py
index ef7914c69e..5f2d6d2a52 100644
--- a/src/bonsai/bonsai/bim/module/style/operator.py
+++ b/src/bonsai/bonsai/bim/module/style/operator.py
@@ -155,10 +155,10 @@ class UnlinkStyle(bpy.types.Operator, tool.Ifc.Operator):
# for unlinked blender material.
updated_meshes = set()
for obj in bpy.data.objects:
- mesh = obj.data
- if not isinstance(mesh, bpy.types.Mesh):
+ if not (mesh := obj.data) or not isinstance(mesh, bpy.types.Mesh):
continue
- if not mesh.BIMMeshProperties.ifc_definition_id:
+ representation = tool.Geometry.get_data_representation(mesh)
+ if not representation:
continue
if mesh in updated_meshes:
continue
@@ -1137,13 +1137,16 @@ class AssignStyleToSelected(bpy.types.Operator, tool.Ifc.Operator):
ifc_file = tool.Ifc.get()
style = ifc_file.by_id(self.style_id)
material = tool.Ifc.get_object(style)
+ assert isinstance(material, bpy.types.Material)
has_items = False
representations: dict[ifcopenshell.entity_instance, bpy.types.Object] = {}
for obj in context.selected_objects:
if tool.Geometry.is_representation_item(obj):
has_items = True
- item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
+ assert isinstance(obj.data, bpy.types.Mesh)
+ item = tool.Geometry.get_active_representation(obj)
+ assert item
tool.Style.assign_style_to_representation_item(item, style)
obj.data.materials.clear()
obj.data.materials.append(material)
@@ -1156,7 +1159,8 @@ class AssignStyleToSelected(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"}
if has_items:
- tool.Geometry.reload_representation(context.scene.BIMGeometryProperties.representation_obj)
+ gprops = tool.Geometry.get_geometry_props()
+ tool.Geometry.reload_representation(gprops.representation_obj)
bpy.ops.bim.disable_editing_representation_items()
bpy.ops.bim.enable_editing_representation_items()
diff --git a/src/bonsai/bonsai/bim/module/void/data.py b/src/bonsai/bonsai/bim/module/void/data.py
index fe387b6f81..133d3e15f5 100644
--- a/src/bonsai/bonsai/bim/module/void/data.py
+++ b/src/bonsai/bonsai/bim/module/void/data.py
@@ -127,26 +127,14 @@ class BooleansData:
def booleans(cls):
props = tool.Geometry.get_geometry_props()
obj = props.representation_obj or bpy.context.active_object
- if (
- not obj.data
- or not hasattr(obj.data, "BIMMeshProperties")
- or not obj.data.BIMMeshProperties.ifc_definition_id
- ):
+ if not (representation := tool.Geometry.get_active_representation(obj)):
return []
-
- representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
return tool.Model.get_booleans(representation=representation)
@classmethod
def manual_booleans(cls):
props = tool.Geometry.get_geometry_props()
obj = props.representation_obj or bpy.context.active_object
- if (
- not obj.data
- or not hasattr(obj.data, "BIMMeshProperties")
- or not obj.data.BIMMeshProperties.ifc_definition_id
- ):
+ if not (representation := tool.Geometry.get_active_representation(obj)):
return []
-
- representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
return tool.Model.get_manual_booleans(tool.Ifc.get_entity(obj), representation=representation)
diff --git a/src/bonsai/bonsai/bim/module/void/operator.py b/src/bonsai/bonsai/bim/module/void/operator.py
index 5867ed9950..a1493f2711 100644
--- a/src/bonsai/bonsai/bim/module/void/operator.py
+++ b/src/bonsai/bonsai/bim/module/void/operator.py
@@ -139,7 +139,8 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
tool.Ifc, tool.Geometry, tool.Surveyor, obj=voided_obj
)
- representation = tool.Ifc.get().by_id(voided_obj.data.BIMMeshProperties.ifc_definition_id)
+ representation = tool.Geometry.get_active_representation(voided_obj)
+ assert representation
bonsai.core.geometry.switch_representation(
tool.Ifc,
tool.Geometry,
@@ -269,20 +270,18 @@ class BooleansMarkAsManual(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
obj = context.active_object
- if (
- obj
- and tool.Ifc.get_entity(obj)
- and hasattr(obj.data, "BIMMeshProperties")
- and obj.data.BIMMeshProperties.ifc_definition_id
- ):
+ if obj and tool.Ifc.get_entity(obj) and tool.Geometry.get_active_representation(obj):
return True
cls.poll_message_set("Need to select IFC element with representation")
return False
def _execute(self, context):
obj = context.active_object
+ assert obj
element = tool.Ifc.get_entity(obj)
- representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
+ assert element
+ representation = tool.Geometry.get_active_representation(obj)
+ assert representation
booleans = tool.Model.get_booleans(representation=representation)
if self.mark_as_manual:
@@ -304,13 +303,13 @@ class EnableEditingBooleans(bpy.types.Operator):
@classmethod
def poll(cls, context):
- if not bpy.context.scene.BIMGeometryProperties.representation_obj:
+ if not tool.Geometry.get_geometry_props().representation_obj:
cls.poll_message_set("To enable editing booleans object should be in item mode.")
return False
return True
def execute(self, context):
- props = context.scene.BIMBooleanProperties
+ props = tool.Feature.get_boolean_props()
gprops = tool.Geometry.get_geometry_props()
rep_obj = gprops.representation_obj
assert rep_obj
@@ -344,6 +343,6 @@ class DisableEditingBooleans(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- props = context.scene.BIMBooleanProperties
+ props = tool.Feature.get_boolean_props()
props.is_editing = False
return {"FINISHED"}
diff --git a/src/bonsai/bonsai/bim/module/void/prop.py b/src/bonsai/bonsai/bim/module/void/prop.py
index 8c6bbaf4e6..0170dbd606 100644
--- a/src/bonsai/bonsai/bim/module/void/prop.py
+++ b/src/bonsai/bonsai/bim/module/void/prop.py
@@ -19,34 +19,51 @@
import bpy
from bpy.types import PropertyGroup
from bpy.props import PointerProperty, StringProperty, IntProperty, BoolProperty, CollectionProperty, EnumProperty
-from typing import Union
+from typing import Union, TYPE_CHECKING, Literal, get_args
+
+OperatorType = Literal["DIFFERENCE", "INTERSECTION", "UNION"]
class Boolean(PropertyGroup):
name: StringProperty(name="Name")
- operator: StringProperty(name="Operator")
+ operator: EnumProperty(
+ items=[(i, i, "") for i in get_args(OperatorType)],
+ name="Operator",
+ default="DIFFERENCE",
+ )
ifc_definition_id: IntProperty(name="IFC Definition ID")
level: IntProperty(name="Level")
+ if TYPE_CHECKING:
+ operator: OperatorType
+ name: str
+ ifc_definition_id: int
+ level: int
+
class VoidProperties(PropertyGroup):
desired_opening: PointerProperty(name="Desired Opening To Fill", type=bpy.types.Object)
+ if TYPE_CHECKING:
+ desired_opening: Union[bpy.types.Object, None]
+
class BIMBooleanProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing", default=False)
booleans: CollectionProperty(name="Booleans", type=Boolean)
active_boolean_index: IntProperty(name="Active Boolean Index")
operator: EnumProperty(
- items=[
- ("DIFFERENCE", "DIFFERENCE", ""),
- ("INTERSECTION", "INTERSECTION", ""),
- ("UNION", "UNION", ""),
- ],
+ items=[(i, i, "") for i in get_args(OperatorType)],
name="Operator",
default="DIFFERENCE",
)
+ if TYPE_CHECKING:
+ is_editing: bool
+ booleans: bpy.types.bpy_prop_collection_idprop[Boolean]
+ active_boolean_index: int
+ operator: OperatorType
+
@property
def active_boolean(self) -> Union[Boolean, None]:
if self.booleans and 0 <= self.active_boolean_index < len(self.booleans):
diff --git a/src/bonsai/bonsai/bim/module/void/ui.py b/src/bonsai/bonsai/bim/module/void/ui.py
index d07266a56f..69b34a40e1 100644
--- a/src/bonsai/bonsai/bim/module/void/ui.py
+++ b/src/bonsai/bonsai/bim/module/void/ui.py
@@ -126,13 +126,10 @@ class BIM_PT_booleans(Panel):
@classmethod
def poll(cls, context):
return (
- context.active_object is not None
- and context.active_object.type == "MESH"
- and hasattr(context.active_object.data, "BIMMeshProperties")
- and (
- context.active_object.data.BIMMeshProperties.ifc_definition_id
- or context.active_object.data.BIMMeshProperties.ifc_boolean_id
- )
+ (obj := context.active_object) is not None
+ and isinstance(data := obj.data, bpy.types.Mesh)
+ and (mesh_props := tool.Geometry.get_mesh_props(data))
+ and (mesh_props.ifc_definition_id or mesh_props.ifc_boolean_id)
)
def draw(self, context):
@@ -141,13 +138,13 @@ class BIM_PT_booleans(Panel):
obj = context.active_object
assert obj
+ mesh = obj.data
+ assert isinstance(mesh, bpy.types.Mesh)
- if not context.active_object.data:
- return
layout = self.layout
- props = context.scene.BIMBooleanProperties
+ props = tool.Feature.get_boolean_props()
- if context.active_object.data.BIMMeshProperties.ifc_definition_id:
+ if tool.Geometry.get_mesh_props(mesh).ifc_definition_id:
row = layout.row(align=True)
total_booleans = BooleansData.data["total_booleans"]
manual_booleans = BooleansData.data["manual_booleans"]
diff --git a/src/bonsai/bonsai/bim/operator.py b/src/bonsai/bonsai/bim/operator.py
index be0d07102d..2bd6f445f4 100644
--- a/src/bonsai/bonsai/bim/operator.py
+++ b/src/bonsai/bonsai/bim/operator.py
@@ -828,7 +828,8 @@ class AddIfcFile(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- context.scene.DocProperties.ifc_files.add()
+ props = tool.Drawing.get_document_props()
+ props.ifc_files.add()
return {"FINISHED"}
@@ -839,7 +840,8 @@ class RemoveIfcFile(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- context.scene.DocProperties.ifc_files.remove(self.index)
+ props = tool.Drawing.get_document_props()
+ props.ifc_files.remove(self.index)
return {"FINISHED"}
@@ -1060,7 +1062,8 @@ class ClippingPlaneCutWithCappings(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- cutting_planes = [p.obj for p in context.scene.BIMProjectProperties.clipping_planes]
+ props = tool.Project.get_project_props()
+ cutting_planes = [obj for p in props.clipping_planes if (obj := p.obj)]
if not cutting_planes:
self.report({"INFO"}, "No cutting planes found.")
return {"FINISHED"}
@@ -1070,7 +1073,7 @@ class ClippingPlaneCutWithCappings(bpy.types.Operator):
objects_processed, t0 = 0, time.time()
wm.progress_begin(0, len(context.selected_objects))
for obj_i, obj in enumerate(context.selected_objects):
- if obj.type != "MESH":
+ if not isinstance((mesh := obj.data), bpy.types.Mesh):
continue
if obj in cutting_planes:
@@ -1082,7 +1085,6 @@ class ClippingPlaneCutWithCappings(bpy.types.Operator):
ws_to_ls = obj.matrix_world.inverted()
rotation = ws_to_ls.to_quaternion()
- mesh = obj.data
bm = tool.Blender.get_bmesh_for_mesh(mesh)
object_changed = False
@@ -1106,7 +1108,7 @@ class ClippingPlaneCutWithCappings(bpy.types.Operator):
# don't swap mesh if it wasn't affected by any of the cutting planes
if object_changed:
temp_mesh = bpy.data.meshes.new("temp_cut")
- temp_mesh.BIMMeshProperties.replaced_mesh = mesh
+ tool.Geometry.get_mesh_props(temp_mesh).replaced_mesh = mesh
for material in mesh.materials:
temp_mesh.materials.append(material)
obj.data = temp_mesh
@@ -1163,9 +1165,10 @@ class RevertClippingPlaneCut(bpy.types.Operator):
self.report({"INFO"}, f"{objects_processed} processed - {time.time()-t0:.3f} sec")
return {"FINISHED"}
- def revert_object_mesh(self, obj):
+ def revert_object_mesh(self, obj: bpy.types.Object) -> None:
mesh = obj.data
- replaced_mesh = mesh.BIMMeshProperties.replaced_mesh
+ assert isinstance(mesh, bpy.types.Mesh)
+ replaced_mesh = tool.Geometry.get_mesh_props(mesh).replaced_mesh
if replaced_mesh:
obj.data = replaced_mesh
tool.Blender.remove_data_block(mesh, do_unlink=False)
diff --git a/src/bonsai/bonsai/bim/prop.py b/src/bonsai/bonsai/bim/prop.py
index 2f095cbb2c..c9b8c759d8 100644
--- a/src/bonsai/bonsai/bim/prop.py
+++ b/src/bonsai/bonsai/bim/prop.py
@@ -525,6 +525,13 @@ class IfcParameter(PropertyGroup):
value: FloatProperty(name="Value") # For now, only floats
type: StringProperty(name="Type")
+ if TYPE_CHECKING:
+ name: str
+ step_id: int
+ index: int
+ value: float
+ type: str
+
class PsetQto(PropertyGroup):
name: StringProperty(name="Name")
@@ -532,6 +539,11 @@ class PsetQto(PropertyGroup):
is_expanded: BoolProperty(name="Is Expanded", default=True)
is_editable: BoolProperty(name="Is Editable")
+ if TYPE_CHECKING:
+ properties: bpy.types.bpy_prop_collection_idprop[Attribute]
+ is_expanded: bool
+ is_editable: bool
+
class GlobalId(PropertyGroup):
name: StringProperty(name="Name")
@@ -540,6 +552,9 @@ class GlobalId(PropertyGroup):
class BIMCollectionProperties(PropertyGroup):
obj: PointerProperty(type=bpy.types.Object)
+ if TYPE_CHECKING:
+ obj: Union[bpy.types.Object, None]
+
class BIMObjectProperties(PropertyGroup):
collection: PointerProperty(type=bpy.types.Collection)
@@ -564,6 +579,9 @@ def get_profiles(self: "BIMMeshProperties", context: bpy.types.Context):
return ItemData.data["profiles_enum"]
+SubshapeType = Literal["-", "PROFILE", "AXIS"]
+
+
class BIMMeshProperties(PropertyGroup):
ifc_definition_id: IntProperty(name="IFC Definition ID")
ifc_boolean_id: IntProperty(name="IFC Boolean ID")
@@ -572,7 +590,7 @@ class BIMMeshProperties(PropertyGroup):
is_native: BoolProperty(name="Is Native", default=False)
is_swept_solid: BoolProperty(name="Is Swept Solid")
is_parametric: BoolProperty(name="Is Parametric", default=False)
- subshape_type: EnumProperty(name="Subshape Type", items=[(i, i, "") for i in ("-", "PROFILE", "AXIS")])
+ subshape_type: EnumProperty(name="Subshape Type", items=[(i, i, "") for i in get_args(SubshapeType)])
ifc_parameters: CollectionProperty(name="IFC Parameters", type=IfcParameter)
item_attributes: CollectionProperty(name="Item Attributes", type=Attribute)
item_profile: EnumProperty(name="Item Profile", items=get_profiles)
@@ -580,6 +598,22 @@ class BIMMeshProperties(PropertyGroup):
mesh_checksum: StringProperty(name="Mesh Checksum", default="")
replaced_mesh: PointerProperty(type=bpy.types.Mesh, description="Original mesh to revert section cutaway")
+ if TYPE_CHECKING:
+ ifc_definition_id: int
+ ifc_boolean_id: int
+ obj: Union[bpy.types.Object, None]
+ has_openings_applied: bool
+ is_native: bool
+ is_swept_solid: bool
+ is_parametric: bool
+ subshape_type: SubshapeType
+ ifc_parameters: bpy.types.bpy_prop_collection_idprop[IfcParameter]
+ item_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
+ item_profile: str
+ material_checksum: str
+ mesh_checksum: str
+ replaced_mesh: Union[bpy.types.Mesh, None]
+
class BIMFacet(PropertyGroup):
name: StringProperty(name="Name")
@@ -599,16 +633,30 @@ class BIMFacet(PropertyGroup):
],
)
+ if TYPE_CHECKING:
+ pset: str
+ value: str
+ type: str
+ comparison: Literal["=", "!=", ">=", "<=", ">", "<", "*=", "!*="]
+
class BIMFilterGroup(PropertyGroup):
filters: CollectionProperty(type=BIMFacet, name="filters")
+ if TYPE_CHECKING:
+ filters: bpy.types.bpy_prop_collection_idprop[BIMFacet]
+
class BIMSnapGroups(PropertyGroup):
object: BoolProperty(name="Object", default=True)
polyline: BoolProperty(name="Polyline", default=True)
measure: BoolProperty(name="Measure", default=True)
+ if TYPE_CHECKING:
+ object: bool
+ polyline: bool
+ measure: bool
+
class BIMSnapProperties(PropertyGroup):
vertex: BoolProperty(name="Vertex", default=True)
@@ -616,3 +664,10 @@ class BIMSnapProperties(PropertyGroup):
edge_center: BoolProperty(name="Edge Center", default=True)
edge_intersection: BoolProperty(name="Edge Intersection", default=True)
face: BoolProperty(name="Face", default=True)
+
+ if TYPE_CHECKING:
+ vertex: bool
+ edge: bool
+ edge_center: bool
+ edge_intersection: bool
+ face: bool
diff --git a/src/bonsai/bonsai/bim/ui.py b/src/bonsai/bonsai/bim/ui.py
index fe300719af..a77650a72b 100644
--- a/src/bonsai/bonsai/bim/ui.py
+++ b/src/bonsai/bonsai/bim/ui.py
@@ -19,6 +19,7 @@
import os
import bpy
import platform
+import bonsai.bim.helper
from pathlib import Path
from bpy.types import Panel
from bpy.props import StringProperty, IntProperty, BoolProperty
@@ -34,7 +35,7 @@ import bonsai.bim
import bonsai.tool as tool
from ifcopenshell.util.file import IfcHeaderExtractor
from bonsai.bim.prop import Attribute
-from typing import Optional
+from typing import Optional, TYPE_CHECKING
class IFCFileSelector:
@@ -147,7 +148,7 @@ class BIM_PT_section_with_cappings(Panel):
row.operator("bim.clipping_plane_cut_with_cappings", icon="XRAY", text="Cut")
row.operator("bim.revert_clipping_plane_cut", icon="FILE_REFRESH", text="Revert Cut")
- props = context.scene.BIMProjectProperties
+ props = tool.Project.get_project_props()
box = layout.box()
header = box.row(align=True)
header.label(text="Clipping Planes")
@@ -247,7 +248,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
should_play_chaching_sound: BoolProperty(name="Play A Cha-Ching Sound When Project Costs Updates", default=False)
tmp_dir: StringProperty(
name="Temporary Directory",
- description='Path to create and store temporary files. If left blank, a system default will be used.',
+ description="Path to create and store temporary files. If left blank, a system default will be used.",
)
spatial_elements_unselectable: BoolProperty(
name="Make Spatial Elements Unselectable By Default",
@@ -302,7 +303,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
size=4,
description="Color of background overlays",
)
-
opening_focus_opacity: bpy.props.IntProperty(
default=100,
min=0,
@@ -312,7 +312,29 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
description="When modifying openings, other elements of the model will display with some transparency.\n0 is fully transparent and 100 is fully opaque",
)
- def draw(self, context):
+ if TYPE_CHECKING:
+ svg2pdf_command: str
+ svg2dxf_command: str
+ svg_command: str
+ layout_svg_command: str
+ pdf_command: str
+ spreadsheet_command: str
+ should_hide_empty_props: bool
+ should_setup_workspace: bool
+ activate_workspace: bool
+ should_setup_toolbar: bool
+ should_play_chaching_sound: bool
+ spatial_elements_unselectable: bool
+ tmp_dir: str
+ decorations_colour: tuple[float, float, float, float]
+ decorator_color_selected: tuple[float, float, float, float]
+ decorator_color_unselected: tuple[float, float, float, float]
+ decorator_color_special: tuple[float, float, float, float]
+ decorator_color_error: tuple[float, float, float, float]
+ decorator_color_background: tuple[float, float, float, float]
+ opening_focus_opacity: int
+
+ def draw(self, context: bpy.types.Context) -> None:
layout = self.layout
row = layout.row()
@@ -333,7 +355,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
bonsai.bim.helper.draw_expandable_panel(self.layout, context, "Drawing", self.draw_drawing_settings)
bonsai.bim.helper.draw_expandable_panel(self.layout, context, "Openings", self.draw_openings_settings)
- def draw_commands(self, layout, context):
+ def draw_commands(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
layout.prop(self, "svg2pdf_command")
layout.prop(self, "svg2dxf_command")
layout.prop(self, "svg_command")
@@ -341,15 +363,16 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
layout.prop(self, "pdf_command")
layout.prop(self, "spreadsheet_command")
- def draw_misc_settings(self, layout, context):
+ def draw_misc_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
layout.prop(self, "should_hide_empty_props")
layout.prop(self, "should_setup_workspace")
layout.prop(self, "activate_workspace")
layout.prop(self, "should_setup_toolbar")
layout.prop(self, "should_play_chaching_sound")
layout.prop(self, "spatial_elements_unselectable")
- layout.prop(context.scene.BIMProjectProperties, "should_disable_undo_on_save")
- layout.prop(context.scene.BIMProjectProperties, "should_stream")
+ props = tool.Project.get_project_props()
+ layout.prop(props, "should_disable_undo_on_save")
+ layout.prop(props, "should_stream")
def draw_model_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
props = tool.Model.get_model_props()
@@ -357,7 +380,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
if props.occurrence_name_style == "CUSTOM":
layout.prop(props, "occurrence_name_function")
- def draw_directories(self, layout, context):
+ def draw_directories(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
row = layout.row(align=True)
row.prop(context.scene.BIMProperties, "data_dir")
row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "scene.BIMProperties.data_dir"
@@ -370,25 +393,26 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
row.prop(self, "tmp_dir")
row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "preferences.tmp_dir"
- def draw_drawing_settings(self, layout, context):
+ def draw_drawing_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
layout.prop(context.scene.BIMProperties, "pset_dir")
- layout.prop(context.scene.DocProperties, "sheets_dir")
- layout.prop(context.scene.DocProperties, "layouts_dir")
- layout.prop(context.scene.DocProperties, "titleblocks_dir")
- layout.prop(context.scene.DocProperties, "drawings_dir")
- layout.prop(context.scene.DocProperties, "stylesheet_path")
- layout.prop(context.scene.DocProperties, "schedules_stylesheet_path")
- layout.prop(context.scene.DocProperties, "markers_path")
- layout.prop(context.scene.DocProperties, "symbols_path")
- layout.prop(context.scene.DocProperties, "patterns_path")
- layout.prop(context.scene.DocProperties, "shadingstyles_path")
- layout.prop(context.scene.DocProperties, "shadingstyle_default")
+ dprops = tool.Drawing.get_document_props()
+ layout.prop(dprops, "sheets_dir")
+ layout.prop(dprops, "layouts_dir")
+ layout.prop(dprops, "titleblocks_dir")
+ layout.prop(dprops, "drawings_dir")
+ layout.prop(dprops, "stylesheet_path")
+ layout.prop(dprops, "schedules_stylesheet_path")
+ layout.prop(dprops, "markers_path")
+ layout.prop(dprops, "symbols_path")
+ layout.prop(dprops, "patterns_path")
+ layout.prop(dprops, "shadingstyles_path")
+ layout.prop(dprops, "shadingstyle_default")
row = layout.row()
- row.prop(context.scene.DocProperties, "drawing_font")
- row.prop(context.scene.DocProperties, "magic_font_scale")
- layout.prop(context.scene.DocProperties, "imperial_precision")
- layout.prop(context.scene.DocProperties, "tolerance")
- layout.prop(context.scene.DocProperties, "classes_to_wireframe")
+ row.prop(dprops, "drawing_font")
+ row.prop(dprops, "magic_font_scale")
+ layout.prop(dprops, "imperial_precision")
+ layout.prop(dprops, "tolerance")
+ layout.prop(dprops, "classes_to_wireframe")
def draw_decorator_colors(self, layout, context):
layout.row().prop(self, "decorations_colour")
@@ -496,9 +520,10 @@ class BIM_PT_tabs(Panel):
op.uri = "https://docs.bonsaibim.org/guides/troubleshooting.html#saving-and-loading-blend-files"
row.operator("bim.close_blend_warning", text="", icon="CANCEL")
- if context.mode == "OBJECT" and context.scene.BIMGeometryProperties.mode in ("OBJECT", "ITEM"):
+ gprops = tool.Geometry.get_geometry_props()
+ if context.mode == "OBJECT" and gprops.mode in ("OBJECT", "ITEM"):
pass
- elif context.mode.startswith("EDIT") and context.scene.BIMGeometryProperties.mode == "EDIT":
+ elif context.mode.startswith("EDIT") and gprops.mode == "EDIT":
pass
else:
box = self.layout.box()
@@ -533,7 +558,7 @@ class BIM_PT_tab_new_project_wizard(Panel):
if not tool.Blender.is_tab(context, "PROJECT"):
return False
props = context.scene.BIMProperties
- pprops = context.scene.BIMProjectProperties
+ pprops = tool.Project.get_project_props()
if pprops.is_loading:
return False
elif tool.Ifc.get() or props.ifc_file:
@@ -555,7 +580,7 @@ class BIM_PT_tab_project_info(Panel):
if not tool.Blender.is_tab(context, "PROJECT"):
return False
props = context.scene.BIMProperties
- pprops = context.scene.BIMProjectProperties
+ pprops = tool.Project.get_project_props()
if pprops.is_loading:
return True
elif tool.Ifc.get() or props.ifc_file:
@@ -854,6 +879,7 @@ class BIM_PT_tab_object_metadata(Panel):
@classmethod
def poll(cls, context):
+ props = tool.Project.get_project_props()
return (
tool.Blender.is_tab(context, "OBJECT")
and tool.Ifc.get()
@@ -862,7 +888,7 @@ class BIM_PT_tab_object_metadata(Panel):
and (
obj.type != "EMPTY"
or not obj.instance_collection
- or not any(l.empty_handle == obj for l in context.scene.BIMProjectProperties.links)
+ or not any(l.empty_handle == obj for l in props.links)
)
)
@@ -1231,7 +1257,7 @@ class BIM_PT_decorators_overlay(Panel):
view = context.space_data
overlay = view.overlay
- georeference_props = bpy.context.scene.BIMGeoreferenceProperties
+ georeference_props = tool.Georeference.get_georeference_props()
aggregate_props = bpy.context.scene.BIMAggregateProperties
nest_props = bpy.context.scene.BIMNestProperties
model_props = tool.Model.get_model_props()
diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py
index ad62b1fabc..b9699de172 100644
--- a/src/bonsai/bonsai/tool/blender.py
+++ b/src/bonsai/bonsai/tool/blender.py
@@ -913,7 +913,7 @@ class Blender(bonsai.core.tool.Blender):
return False
if not (element := tool.Ifc.get_entity(obj)):
return True
- if obj in bpy.context.scene.BIMProjectProperties.clipping_planes_objs:
+ if obj in tool.Project.get_project_props().clipping_planes_objs:
return False
usage_type = tool.Model.get_usage_type(element)
if usage_type in ("LAYER1", "LAYER2"):
diff --git a/src/bonsai/bonsai/tool/debug.py b/src/bonsai/bonsai/tool/debug.py
index 0dd9b6c855..ec4490b2c3 100644
--- a/src/bonsai/bonsai/tool/debug.py
+++ b/src/bonsai/bonsai/tool/debug.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 os
import json
import bpy
@@ -30,10 +31,17 @@ import bonsai.tool as tool
from bonsai.bim.ifc import IfcStore
from mathutils import Vector
from collections import defaultdict
-from typing import Iterable, Literal
+from typing import Iterable, Literal, TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from bonsai.bim.module.debug.prop import BIMDebugProperties
class Debug(bonsai.core.tool.Debug):
+ @classmethod
+ def get_debug_props(cls) -> BIMDebugProperties:
+ return bpy.context.scene.BIMDebugProperties
+
@classmethod
def add_schema_identifier(cls, schema: W.schema_definition) -> None:
IfcStore.schema_identifiers.append(schema.name())
diff --git a/src/bonsai/bonsai/tool/drawing.py b/src/bonsai/bonsai/tool/drawing.py
index 134f3bcf5f..088e5b7025 100644
--- a/src/bonsai/bonsai/tool/drawing.py
+++ b/src/bonsai/bonsai/tool/drawing.py
@@ -346,19 +346,23 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod
def disable_editing_drawings(cls) -> None:
- bpy.context.scene.DocProperties.is_editing_drawings = False
+ props = tool.Drawing.get_document_props()
+ props.is_editing_drawings = False
@classmethod
def disable_editing_schedules(cls) -> None:
- bpy.context.scene.DocProperties.is_editing_schedules = False
+ props = tool.Drawing.get_document_props()
+ props.is_editing_schedules = False
@classmethod
def disable_editing_references(cls) -> None:
- bpy.context.scene.DocProperties.is_editing_references = False
+ props = tool.Drawing.get_document_props()
+ props.is_editing_references = False
@classmethod
def disable_editing_sheets(cls) -> None:
- bpy.context.scene.DocProperties.is_editing_sheets = False
+ props = tool.Drawing.get_document_props()
+ props.is_editing_sheets = False
@classmethod
def disable_editing_text(cls, obj: bpy.types.Object) -> None:
@@ -383,19 +387,23 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod
def enable_editing_drawings(cls) -> None:
- bpy.context.scene.DocProperties.is_editing_drawings = True
+ props = tool.Drawing.get_document_props()
+ props.is_editing_drawings = True
@classmethod
def enable_editing_schedules(cls) -> None:
- bpy.context.scene.DocProperties.is_editing_schedules = True
+ props = tool.Drawing.get_document_props()
+ props.is_editing_schedules = True
@classmethod
def enable_editing_references(cls) -> None:
- bpy.context.scene.DocProperties.is_editing_references = True
+ props = tool.Drawing.get_document_props()
+ props.is_editing_references = True
@classmethod
def enable_editing_sheets(cls) -> None:
- bpy.context.scene.DocProperties.is_editing_sheets = True
+ props = tool.Drawing.get_document_props()
+ props.is_editing_sheets = True
@classmethod
def enable_editing_text(cls, obj: bpy.types.Object) -> None:
@@ -616,7 +624,8 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod
def is_editing_sheets(cls) -> bool:
- return bpy.context.scene.DocProperties.is_editing_sheets
+ props = tool.Drawing.get_document_props()
+ return props.is_editing_sheets
@classmethod
def remove_literal_from_annotation(cls, obj: bpy.types.Object, literal: ifcopenshell.entity_instance) -> None:
@@ -810,7 +819,7 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod
def import_drawings(cls) -> None:
- props = bpy.context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
expanded_target_views = {d.target_view for d in props.drawings if d.is_expanded}
if not hasattr(cls, "drawing_selected_states"):
cls.drawing_selected_states = {}
@@ -1048,7 +1057,8 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod
def show_decorations(cls) -> None:
- bpy.context.scene.DocProperties.should_draw_decorations = True
+ props = tool.Drawing.get_document_props()
+ props.should_draw_decorations = True
@classmethod
def update_text_value(cls, obj: bpy.types.Object) -> None:
@@ -1147,36 +1157,33 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod
def get_default_layout_path(cls, identification: str, name: str) -> str:
project = tool.Ifc.get().by_type("IfcProject")[0]
+ props = tool.Drawing.get_document_props()
layouts_dir = (
- ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "LayoutsDir")
- or bpy.context.scene.DocProperties.layouts_dir
+ ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "LayoutsDir") or props.layouts_dir
)
return os.path.join(layouts_dir, cls.sanitise_filename(f"{identification} - {name}.svg")).replace("\\", "/")
@classmethod
def get_default_sheet_path(cls, identification: str, name: str) -> str:
project = tool.Ifc.get().by_type("IfcProject")[0]
- sheets_dir = (
- ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "SheetsDir")
- or bpy.context.scene.DocProperties.sheets_dir
- )
+ props = tool.Drawing.get_document_props()
+ sheets_dir = ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "SheetsDir") or props.sheets_dir
return os.path.join(sheets_dir, cls.sanitise_filename(f"{identification} - {name}.svg")).replace("\\", "/")
@classmethod
def get_default_titleblock_path(cls, name: str) -> str:
project = tool.Ifc.get().by_type("IfcProject")[0]
titleblocks_dir = (
- ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "TitleblocksDir")
- or bpy.context.scene.DocProperties.titleblocks_dir
+ ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "TitleblocksDir") or props.titleblocks_dir
)
return os.path.join(titleblocks_dir, cls.sanitise_filename(f"{name}.svg")).replace("\\", "/")
@classmethod
def get_default_drawing_path(cls, name: str) -> str:
project = tool.Ifc.get().by_type("IfcProject")[0]
+ props = tool.Drawing.get_document_props()
drawings_dir = (
- ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "DrawingsDir")
- or bpy.context.scene.DocProperties.drawings_dir
+ ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "DrawingsDir") or props.drawings_dir
)
return os.path.join(drawings_dir, cls.sanitise_filename(f"{name}.svg")).replace("\\", "/")
@@ -1187,15 +1194,16 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod
def get_default_drawing_resource_path(cls, resource: str) -> Union[str, None]:
project = tool.Ifc.get().by_type("IfcProject")[0]
- resource_path = ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", f"{resource}Path") or getattr(
- bpy.context.scene.DocProperties, f"{resource.lower()}_path"
+ props = tool.Drawing.get_document_props()
+ resource_path = (
+ ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", f"{resource}Path") or props.resource_path
)
if resource_path:
return resource_path.replace("\\", "/")
@classmethod
def get_default_shading_style(cls) -> str:
- dprops = bpy.context.scene.DocProperties
+ dprops = tool.Drawing.get_document_props()
return dprops.shadingstyle_default
@classmethod
@@ -1501,7 +1509,7 @@ class Drawing(bonsai.core.tool.Drawing):
dst.data = dst.data.copy()
dst.name = dst.name.replace("IfcGridAxis/", "")
dst.BIMObjectProperties.ifc_definition_id = 0
- dst.data.BIMMeshProperties.ifc_definition_id = 0
+ tool.Geometry.get_geometry_props(dst).ifc_definition_id = 0
return dst
def disassemble(obj: bpy.types.Object) -> tuple[bpy.types.Object, bmesh.types.BMesh]:
@@ -1883,7 +1891,8 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod
def is_active_drawing(cls, drawing: ifcopenshell.entity_instance) -> bool:
- return drawing.id() == bpy.context.scene.DocProperties.active_drawing_id
+ props = tool.Drawing.get_document_props()
+ return drawing.id() == props.active_drawing_id
@classmethod
def run_drawing_activate_model(cls) -> None:
diff --git a/src/bonsai/bonsai/tool/feature.py b/src/bonsai/bonsai/tool/feature.py
index d8fd8921eb..0990aef008 100644
--- a/src/bonsai/bonsai/tool/feature.py
+++ b/src/bonsai/bonsai/tool/feature.py
@@ -16,17 +16,25 @@
# 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 bonsai.core.tool
import bonsai.tool as tool
import bonsai.bim.helper
import ifcopenshell
-from typing import Iterable
+from typing import Iterable, TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from bonsai.bim.module.void.prop import BIMBooleanProperties
class Feature(bonsai.core.tool.Feature):
# TODO: consolidate module/model/opening and module/void into new module/feature
+ @classmethod
+ def get_boolean_props(cls) -> BIMBooleanProperties:
+ return bpy.context.scene.BIMBooleanProperties
+
@classmethod
def add_feature(cls, featured_obj: bpy.types.Object, feature_objs: Iterable[bpy.types.Object]) -> None:
featured_element = tool.Ifc.get_entity(featured_obj)
diff --git a/src/bonsai/bonsai/tool/geometry.py b/src/bonsai/bonsai/tool/geometry.py
index fca6541088..6fb121766c 100644
--- a/src/bonsai/bonsai/tool/geometry.py
+++ b/src/bonsai/bonsai/tool/geometry.py
@@ -27,6 +27,7 @@ import numpy.typing as npt
import multiprocessing
import ifcopenshell
import ifcopenshell.api
+import ifcopenshell.api.geometry
import ifcopenshell.api.grid
import ifcopenshell.api.profile
import ifcopenshell.api.style
@@ -53,11 +54,23 @@ from math import radians, pi
from mathutils import Vector, Matrix
from mathutils.bvhtree import BVHTree
from bonsai.bim.ifc import IfcStore
-from typing import Union, Iterable, Optional, Literal, Iterator, List, TYPE_CHECKING, get_args, Generator, cast
+from typing import (
+ Union,
+ Iterable,
+ Optional,
+ Literal,
+ Iterator,
+ List,
+ TYPE_CHECKING,
+ get_args,
+ Generator,
+ cast,
+ TypeGuard,
+)
from typing_extensions import TypeIs
if TYPE_CHECKING:
- from bonsai.bim.prop import Attribute
+ from bonsai.bim.prop import Attribute, BIMMeshProperties
from bonsai.bim.module.geometry.prop import BIMObjectGeometryProperties, BIMGeometryProperties
@@ -70,6 +83,10 @@ class Geometry(bonsai.core.tool.Geometry):
def get_object_geometry_props(cls, object: bpy.types.Object) -> BIMObjectGeometryProperties:
return object.BIMGeometryProperties
+ @classmethod
+ def get_mesh_props(cls, mesh: TYPES_WITH_MESH_PROPERTIES) -> BIMMeshProperties:
+ return mesh.BIMMeshProperties
+
@classmethod
def change_object_data(cls, obj: bpy.types.Object, data: bpy.types.ID, is_global: bool = False) -> None:
if is_global:
@@ -182,7 +199,9 @@ class Geometry(bonsai.core.tool.Geometry):
if item_obj.obj == obj:
props.item_objs.remove(i)
break
- item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
+ mesh = obj.data
+ assert isinstance(mesh, bpy.types.Mesh)
+ item = tool.Ifc.get().by_id(tool.Geometry.get_mesh_props(mesh).ifc_definition_id)
cls.remove_representation_item(item)
cls.reload_representation(props.representation_obj)
bpy.data.objects.remove(obj)
@@ -249,17 +268,18 @@ class Geometry(bonsai.core.tool.Geometry):
bonsai.core.system.remove_port(tool.Ifc, tool.System, port=port)
ifcopenshell.api.run("root.remove_product", tool.Ifc.get(), product=element)
- if isinstance(obj.data, bpy.types.Mesh) and not tool.Ifc.get_entity_by_id(
- obj.data.BIMMeshProperties.ifc_definition_id
- ):
- tool.Blender.remove_data_block(obj.data)
+ mesh = obj.data
+ assert isinstance(mesh, bpy.types.Mesh)
+ if not tool.Ifc.get_entity_by_id(tool.Geometry.get_mesh_props(mesh).ifc_definition_id):
+ tool.Blender.remove_data_block(mesh)
if is_spatial:
bonsai.core.spatial.import_spatial_decomposition(tool.Spatial)
try:
obj.name
- if bpy.context.scene.BIMGeometryProperties.representation_obj == obj:
- bpy.context.scene.BIMGeometryProperties.representation_obj = None
+ props = tool.Geometry.get_geometry_props()
+ if props.representation_obj == obj:
+ props.representation_obj = None
bpy.data.objects.remove(obj)
except:
pass
@@ -268,7 +288,9 @@ class Geometry(bonsai.core.tool.Geometry):
def dissolve_triangulated_edges(cls, obj: bpy.types.Object) -> None:
# AdvancedBreps may contain non-faceted, curved faces (e.g. as part of
# a cylinder) so dissolving edges should not be allowed.
- mesh_element = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
+ mesh = obj.data
+ assert isinstance(mesh, Geometry.TYPES_WITH_MESH_PROPERTIES)
+ mesh_element = tool.Ifc.get().by_id(tool.Geometry.get_mesh_props(mesh).ifc_definition_id)
if (
(
mesh_element.is_a("IfcShapeRepresentation")
@@ -279,26 +301,30 @@ class Geometry(bonsai.core.tool.Geometry):
or not obj.data
):
return
- if hasattr(obj.data, "attributes") and (ios_edges_attribute := obj.data.attributes.get("ios_edges")):
+
+ if not isinstance(mesh, bpy.types.Mesh):
+ return
+
+ if hasattr(mesh, "attributes") and (ios_edges_attribute := mesh.attributes.get("ios_edges")):
# Edges from a forced triangulation are stored as True in a boolean attribute on the mesh
bm = bmesh.new()
- bm.from_mesh(obj.data)
+ bm.from_mesh(mesh)
edges_to_dissolve = [e for i, e in enumerate(bm.edges) if not ios_edges_attribute.data[i].value]
bmesh.ops.dissolve_edges(bm, edges=edges_to_dissolve)
- bm.to_mesh(obj.data)
+ bm.to_mesh(mesh)
bm.free()
- elif "ios_edges" in obj.data:
+ elif "ios_edges" in mesh:
bm = bmesh.new()
- bm.from_mesh(obj.data)
- edges_to_keep = set(map(frozenset, obj.data["ios_edges"]))
+ bm.from_mesh(mesh)
+ edges_to_keep = set(map(frozenset, mesh["ios_edges"]))
edges_to_dissolve = []
for edge in bm.edges:
if frozenset([vert.index for vert in edge.verts]) not in edges_to_keep:
edges_to_dissolve.append(edge)
bmesh.ops.dissolve_edges(bm, edges=edges_to_dissolve)
- bm.to_mesh(obj.data)
+ bm.to_mesh(mesh)
bm.free()
- del obj.data["ios_edges"]
+ del mesh["ios_edges"]
@classmethod
def apply_item_ids_as_vertex_groups(cls, obj: bpy.types.Object) -> None:
@@ -486,13 +512,19 @@ class Geometry(bonsai.core.tool.Geometry):
@classmethod
def get_active_representation(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
""":return: IfcRepresentation/IfcRepresentationItem or None"""
- if obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.ifc_definition_id:
- return tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
+ if (
+ (data := obj.data)
+ and isinstance(data, Geometry.TYPES_WITH_MESH_PROPERTIES)
+ and (ifc_id := tool.Geometry.get_mesh_props(data).ifc_definition_id)
+ ):
+ return tool.Ifc.get().by_id(ifc_id)
@classmethod
- def get_data_representation(cls, data: bpy.types.Mesh) -> ifcopenshell.entity_instance | None:
- if hasattr(data, "BIMMeshProperties") and data.BIMMeshProperties.ifc_definition_id:
- return tool.Ifc.get().by_id(data.BIMMeshProperties.ifc_definition_id)
+ def get_data_representation(cls, data: bpy.types.ID) -> ifcopenshell.entity_instance | None:
+ if isinstance(data, Geometry.TYPES_WITH_MESH_PROPERTIES) and (
+ ifc_id := tool.Geometry.get_mesh_props(data).ifc_definition_id
+ ):
+ return tool.Ifc.get().by_id(ifc_id)
@classmethod
def get_active_representation_context(cls, obj: bpy.types.Object) -> ifcopenshell.entity_instance:
@@ -670,13 +702,13 @@ class Geometry(bonsai.core.tool.Geometry):
return data.users != 0
@classmethod
- def has_geometric_data(cls, obj: bpy.types.Object) -> bool:
- if not obj.data:
+ def is_geometric_data(cls, data: Union[bpy.types.ID, None]) -> TypeGuard[Union[bpy.types.Mesh, bpy.types.Curve]]:
+ if not data:
return False
- if isinstance(obj.data, bpy.types.Mesh):
- return bool(obj.data.vertices)
- elif isinstance(obj.data, bpy.types.Curve):
- return bool(obj.data.splines)
+ if isinstance(data, bpy.types.Mesh):
+ return bool(data.vertices)
+ elif isinstance(data, bpy.types.Curve):
+ return bool(data.splines)
return False
@classmethod
@@ -826,7 +858,8 @@ class Geometry(bonsai.core.tool.Geometry):
ifc_importer.material_creator.load_existing_materials()
shape_has_openings = cls.does_shape_has_openings(shape)
ifc_importer.material_creator.create(element, obj, mesh, shape_has_openings)
- mesh.BIMMeshProperties.has_openings_applied = apply_openings
+ mprops = tool.Geometry.get_mesh_props(mesh)
+ mprops.has_openings_applied = apply_openings
if not shape_has_openings:
tool.Loader.load_indexed_colour_map(representation, mesh)
tool.Loader.link_mesh(shape, mesh)
@@ -852,7 +885,8 @@ class Geometry(bonsai.core.tool.Geometry):
ifc_importer.material_creator.load_existing_materials()
shape_has_openings = False
ifc_importer.material_creator.create(element, obj, mesh, shape_has_openings)
- mesh.BIMMeshProperties.has_openings_applied = apply_openings
+ mprops = tool.Geometry.get_mesh_props(mesh)
+ mprops.has_openings_applied = apply_openings
if not shape_has_openings:
tool.Loader.load_indexed_colour_map(representation, mesh)
meshes[mesh_name] = mesh
@@ -867,7 +901,7 @@ class Geometry(bonsai.core.tool.Geometry):
@classmethod
def import_representation_parameters(cls, data: bpy.types.Mesh) -> None:
- props = data.BIMMeshProperties
+ props = tool.Geometry.get_mesh_props(data)
elements = tool.Ifc.get().traverse(tool.Ifc.get().by_id(props.ifc_definition_id))
props.ifc_parameters.clear()
for element in elements:
@@ -974,7 +1008,8 @@ class Geometry(bonsai.core.tool.Geometry):
@classmethod
def is_profile_based(cls, data: bpy.types.Mesh) -> bool:
- return data.BIMMeshProperties.subshape_type == "PROFILE"
+ props = tool.Geometry.get_mesh_props(data)
+ return props.subshape_type == "PROFILE"
@classmethod
def is_profile_object_active(cls) -> bool:
@@ -992,7 +1027,7 @@ class Geometry(bonsai.core.tool.Geometry):
data = obj.data
if (
isinstance(data, Geometry.TYPES_WITH_MESH_PROPERTIES)
- and (ifc_id := data.BIMMeshProperties.ifc_definition_id)
+ and (ifc_id := tool.Geometry.get_mesh_props(data).ifc_definition_id)
and ((item := tool.Ifc.get().by_id(ifc_id)).is_a("IfcRepresentationItem"))
):
return item
@@ -1008,14 +1043,14 @@ class Geometry(bonsai.core.tool.Geometry):
if tool.Ifc.get_entity(obj):
return obj
elif tool.Geometry.is_representation_item(obj):
- return bpy.context.scene.BIMGeometryProperties.representation_obj
+ return tool.Geometry.get_geometry_props().representation_obj
@classmethod
def is_boolean_operand(cls, obj: bpy.types.Object) -> bool:
return bool(
(data := obj.data)
and isinstance(data, Geometry.TYPES_WITH_MESH_PROPERTIES)
- and (ifc_id := data.BIMMeshProperties.ifc_definition_id)
+ and (ifc_id := tool.Geometry.get_mesh_props(data).ifc_definition_id)
and (item := tool.Ifc.get().by_id(ifc_id))
and (
item.is_a("IfcBooleanResult")
@@ -1041,7 +1076,8 @@ class Geometry(bonsai.core.tool.Geometry):
@classmethod
def record_object_materials(cls, obj: bpy.types.Object) -> None:
- obj.data.BIMMeshProperties.material_checksum = cls.get_material_checksum(obj)
+ props = tool.Geometry.get_mesh_props(obj.data)
+ props.material_checksum = cls.get_material_checksum(obj)
@classmethod
def record_object_position(cls, obj: bpy.types.Object) -> None:
@@ -1146,11 +1182,13 @@ class Geometry(bonsai.core.tool.Geometry):
@classmethod
def should_force_faceted_brep(cls) -> bool:
- return bpy.context.scene.BIMGeometryProperties.should_force_faceted_brep
+ props = tool.Geometry.get_geometry_props()
+ return props.should_force_faceted_brep
@classmethod
def should_force_triangulation(cls) -> bool:
- return bpy.context.scene.BIMGeometryProperties.should_force_triangulation
+ props = tool.Geometry.get_geometry_props()
+ return props.should_force_triangulation
@classmethod
def should_generate_uvs(cls, obj: bpy.types.Object) -> bool:
@@ -1167,7 +1205,8 @@ class Geometry(bonsai.core.tool.Geometry):
@classmethod
def should_use_presentation_style_assignment(cls) -> bool:
- return bpy.context.scene.BIMGeometryProperties.should_use_presentation_style_assignment
+ props = tool.Geometry.get_geometry_props()
+ return props.should_use_presentation_style_assignment
@classmethod
def get_model_representations(cls) -> list[ifcopenshell.entity_instance]:
@@ -1238,7 +1277,8 @@ class Geometry(bonsai.core.tool.Geometry):
In the most cases just use reload_representation
as it will handle those complications by itself.
"""
- representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
+ representation = cls.get_active_representation(obj)
+ assert representation
bonsai.core.geometry.switch_representation(
tool.Ifc,
tool.Geometry,
@@ -1526,7 +1566,7 @@ class Geometry(bonsai.core.tool.Geometry):
@classmethod
def get_blender_offset_type(cls, obj: bpy.types.Object) -> Optional[str]:
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
if props.has_blender_offset:
if (result := obj.BIMObjectProperties.blender_offset_type) == "NONE":
result = obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT"
@@ -1641,7 +1681,8 @@ class Geometry(bonsai.core.tool.Geometry):
for item_obj in props.item_objs:
if not (obj := item_obj.obj) or not tool.Ifc.is_moved(obj):
continue
- item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
+ item = cls.get_active_representation(obj)
+ assert item
if item.is_a("IfcSweptAreaSolid"):
has_changed = True
old_position = item.Position
@@ -1683,7 +1724,7 @@ class Geometry(bonsai.core.tool.Geometry):
@classmethod
def import_item_attributes(cls, obj: bpy.types.Object) -> None:
- props = obj.data.BIMMeshProperties
+ props = tool.Geometry.get_mesh_props(obj.data)
props.item_attributes.clear()
item = tool.Ifc.get().by_id(props.ifc_definition_id)
allowed_attributes = [
@@ -1710,10 +1751,10 @@ class Geometry(bonsai.core.tool.Geometry):
@classmethod
def update_item_attributes(cls, obj: bpy.types.Object) -> None:
- props = obj.data.BIMMeshProperties
+ props = tool.Geometry.get_mesh_props(obj.data)
ifc_file = tool.Ifc.get()
- item = tool.Ifc.get().by_id(props.ifc_definition_id)
+ item = ifc_file.by_id(props.ifc_definition_id)
for attribute in props.item_attributes:
setattr(item, attribute.name, attribute.get_value())
@@ -1738,7 +1779,9 @@ class Geometry(bonsai.core.tool.Geometry):
tool.Loader.settings.contexts = ifcopenshell.util.representation.get_prioritised_contexts(tool.Ifc.get())
tool.Loader.settings.context_settings = tool.Loader.create_settings()
tool.Loader.settings.gross_context_settings = tool.Loader.create_settings(is_gross=True)
- item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
+ assert isinstance(obj.data, bpy.types.Mesh)
+ item = tool.Geometry.get_active_representation(obj)
+ assert item
obj.data.clear_geometry()
if item.is_a("IfcHalfSpaceSolid"):
@@ -1802,12 +1845,14 @@ class Geometry(bonsai.core.tool.Geometry):
props.mode = "OBJECT"
props.is_changing_mode = False
props.representation_obj = None
- bpy.context.scene.BIMBooleanProperties.is_editing = False
+ tool.Feature.get_boolean_props().is_editing = False
@classmethod
def edit_meshlike_item(cls, obj: bpy.types.Object) -> None:
- item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
- if obj.data.BIMMeshProperties.mesh_checksum == cls.get_mesh_checksum(obj.data):
+ item = tool.Geometry.get_active_representation(obj)
+ assert item
+ mprops = tool.Geometry.get_mesh_props(obj.data)
+ if mprops.mesh_checksum == cls.get_mesh_checksum(obj.data):
return
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
@@ -1830,8 +1875,8 @@ class Geometry(bonsai.core.tool.Geometry):
for inverse in tool.Ifc.get().get_inverse(item):
ifcopenshell.util.element.replace_attribute(inverse, item, new_item)
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), item)
- obj.data.BIMMeshProperties.ifc_definition_id = new_item.id()
- cls.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj)
+ tool.Ifc.link(new_item, obj.data)
+ cls.reload_representation(props.representation_obj)
@classmethod
def split_by_loose_parts(cls, obj: bpy.types.Object) -> List[bpy.types.Mesh]:
diff --git a/src/bonsai/bonsai/tool/georeference.py b/src/bonsai/bonsai/tool/georeference.py
index f2a0a4d866..7b8ee46d24 100644
--- a/src/bonsai/bonsai/tool/georeference.py
+++ b/src/bonsai/bonsai/tool/georeference.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 numpy as np
@@ -27,24 +28,34 @@ import ifcopenshell.util.unit
import bonsai.core.tool
import bonsai.tool as tool
import bonsai.bim.helper
-from typing import Any, Union, Literal
+from typing import Any, Union, Literal, TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from bonsai.bim.module.georeference.prop import BIMGeoreferenceProperties
class Georeference(bonsai.core.tool.Georeference):
COORDINATE_TYPE = Literal["blender", "local", "map"]
+ @classmethod
+ def get_georeference_props(cls) -> BIMGeoreferenceProperties:
+ return bpy.context.scene.BIMGeoreferenceProperties
+
@classmethod
def add_georeferencing(cls) -> None:
+ props = cls.get_georeference_props()
tool.Ifc.run(
"georeference.add_georeferencing",
- ifc_class=bpy.context.scene.BIMGeoreferenceProperties.coordinate_operation_class,
+ ifc_class=props.coordinate_operation_class,
)
@classmethod
def import_projected_crs(cls) -> None:
+ props = tool.Georeference.get_georeference_props()
+
def callback(name, prop, data):
if name == "MapUnit":
- new = bpy.context.scene.BIMGeoreferenceProperties.projected_crs.add()
+ new = props.projected_crs.add()
new.name = name
new.data_type = "enum"
new.is_null = data[name] is None
@@ -61,7 +72,6 @@ class Georeference(bonsai.core.tool.Georeference):
new.update = "tool.Georeference.update_map_unit"
return True
- props = bpy.context.scene.BIMGeoreferenceProperties
props.projected_crs.clear()
if tool.Ifc.get_schema() == "IFC2X3":
@@ -84,7 +94,8 @@ class Georeference(bonsai.core.tool.Georeference):
result = 1.0
else:
result = 1.0
- for attribute in bpy.context.scene.BIMGeoreferenceProperties.coordinate_operation:
+ props = cls.get_georeference_props()
+ for attribute in props.coordinate_operation:
if attribute.name == "Scale":
attribute.set_value(str(result))
@@ -92,7 +103,7 @@ class Georeference(bonsai.core.tool.Georeference):
def import_coordinate_operation(cls) -> None:
def callback(name, prop, data):
if name in ("FirstCoordinate", "SecondCoordinate"):
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = cls.get_georeference_props()
if name == "FirstCoordinate":
new = props.coordinate_operation.add()
new.name = "Measure Type"
@@ -110,7 +121,7 @@ class Georeference(bonsai.core.tool.Georeference):
prop.string_value = "" if prop.is_null else str(data[name].wrappedValue)
return True
elif name == "XAxisAbscissa":
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = cls.get_georeference_props()
props.is_changing_angle = True
if data["XAxisAbscissa"] is None or data["XAxisOrdinate"] is None:
props.x_axis_is_null = True
@@ -132,7 +143,7 @@ class Georeference(bonsai.core.tool.Georeference):
prop.string_value = "" if prop.is_null else str(data[name])
return True
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = cls.get_georeference_props()
props.coordinate_operation.clear()
if tool.Ifc.get_schema() == "IFC2X3":
@@ -151,7 +162,7 @@ class Georeference(bonsai.core.tool.Georeference):
if tool.Ifc.get_schema() == "IFC2X3":
return
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = cls.get_georeference_props()
props.is_changing_angle = True
props.true_north_abscissa = "0"
props.true_north_ordinate = "1"
@@ -175,7 +186,7 @@ class Georeference(bonsai.core.tool.Georeference):
attributes[prop.name] = tool.Ifc.get().by_id(int(prop.enum_value))
return True
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = cls.get_georeference_props()
return bonsai.bim.helper.export_attributes(props.projected_crs, callback=callback)
@classmethod
@@ -191,7 +202,7 @@ class Georeference(bonsai.core.tool.Georeference):
attributes[prop.name] = tool.Ifc.get().create_entity(measure_type, float(prop.string_value))
return True
elif prop.name == "XAxisAbscissa":
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = cls.get_georeference_props()
if props.x_axis_is_null:
attributes["XAxisAbscissa"] = None
attributes["XAxisOrdinate"] = None
@@ -206,12 +217,12 @@ class Georeference(bonsai.core.tool.Georeference):
attributes[prop.name] = float(prop.string_value)
return True
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = cls.get_georeference_props()
return bonsai.bim.helper.export_attributes(props.coordinate_operation, callback=callback)
@classmethod
def get_true_north_attributes(cls) -> Union[list[float], None]:
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = cls.get_georeference_props()
try:
return [float(props.true_north_abscissa), float(props.true_north_ordinate)]
except ValueError:
@@ -219,36 +230,42 @@ class Georeference(bonsai.core.tool.Georeference):
@classmethod
def enable_editing(cls) -> None:
- bpy.context.scene.BIMGeoreferenceProperties.is_editing = True
+ props = cls.get_georeference_props()
+ props.is_editing = True
@classmethod
def disable_editing(cls) -> None:
- bpy.context.scene.BIMGeoreferenceProperties.is_editing = False
+ props = cls.get_georeference_props()
+ props.is_editing = False
@classmethod
def enable_editing_wcs(cls) -> None:
- bpy.context.scene.BIMGeoreferenceProperties.is_editing_wcs = True
+ props = cls.get_georeference_props()
+ props.is_editing_wcs = True
@classmethod
def disable_editing_wcs(cls) -> None:
- bpy.context.scene.BIMGeoreferenceProperties.is_editing_wcs = False
+ props = cls.get_georeference_props()
+ props.is_editing_wcs = False
@classmethod
def enable_editing_true_north(cls) -> None:
- bpy.context.scene.BIMGeoreferenceProperties.is_editing_true_north = True
+ props = cls.get_georeference_props()
+ props.is_editing_true_north = True
@classmethod
def disable_editing_true_north(cls) -> None:
- bpy.context.scene.BIMGeoreferenceProperties.is_editing_true_north = False
+ props = cls.get_georeference_props()
+ props.is_editing_true_north = False
@classmethod
def set_coordinates(cls, io: COORDINATE_TYPE, coordinates: list[float]) -> None:
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = cls.get_georeference_props()
setattr(props, f"{io}_coordinates", ",".join([str(o) for o in coordinates]))
@classmethod
def get_coordinates(cls, io: COORDINATE_TYPE) -> list[float]:
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = cls.get_georeference_props()
return [float(co) for co in getattr(props, f"{io}_coordinates").split(",")]
@classmethod
@@ -260,7 +277,7 @@ class Georeference(bonsai.core.tool.Georeference):
def xyz2enh(
cls, coordinates: tuple[float, float, float], should_return_in_map_units: bool = True
) -> tuple[float, float, float]:
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = cls.get_georeference_props()
if props.has_blender_offset:
coordinates = ifcopenshell.util.geolocation.xyz2enh(
coordinates[0],
@@ -279,7 +296,7 @@ class Georeference(bonsai.core.tool.Georeference):
@classmethod
def enh2xyz(cls, coordinates: tuple[float, float, float]) -> tuple[float, float, float]:
coordinates = ifcopenshell.util.geolocation.auto_enh2xyz(tool.Ifc.get(), *coordinates)
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = cls.get_georeference_props()
if props.has_blender_offset:
coordinates = ifcopenshell.util.geolocation.enh2xyz(
coordinates[0],
@@ -329,7 +346,7 @@ class Georeference(bonsai.core.tool.Georeference):
@classmethod
def import_wcs(cls) -> None:
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = cls.get_georeference_props()
wcs = None
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
wcs = context.WorldCoordinateSystem
@@ -346,7 +363,7 @@ class Georeference(bonsai.core.tool.Georeference):
@classmethod
def export_wcs(cls) -> dict[str, float]:
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = cls.get_georeference_props()
return {
"x": float(props.wcs_x),
"y": float(props.wcs_y),
@@ -361,7 +378,7 @@ class Georeference(bonsai.core.tool.Georeference):
@classmethod
def set_model_origin(cls) -> None:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
- gprops = bpy.context.scene.BIMGeoreferenceProperties
+ gprops = tool.Georeference.get_georeference_props()
e, n, h = cls.xyz2enh((0, 0, 0), should_return_in_map_units=False)
gprops.model_origin = f"{e},{n},{h}"
gprops.model_origin_si = f"{e * unit_scale},{n * unit_scale},{h * unit_scale}"
@@ -375,4 +392,4 @@ class Georeference(bonsai.core.tool.Georeference):
@classmethod
def has_blender_offset(cls) -> bool:
- return bpy.context.scene.BIMGeoreferenceProperties.has_blender_offset
+ return tool.Georeference.get_georeference_props().has_blender_offset
diff --git a/src/bonsai/bonsai/tool/ifc.py b/src/bonsai/bonsai/tool/ifc.py
index 5b3f8bb48a..c82ffa80a5 100644
--- a/src/bonsai/bonsai/tool/ifc.py
+++ b/src/bonsai/bonsai/tool/ifc.py
@@ -111,7 +111,7 @@ class Ifc(bonsai.core.tool.Ifc):
elif isinstance(obj, bpy.types.Material):
props = obj.BIMStyleProperties
else:
- props = obj.BIMMeshProperties
+ props = tool.Geometry.get_mesh_props(obj)
if props and (ifc_definition_id := props.ifc_definition_id):
try:
@@ -180,7 +180,7 @@ class Ifc(bonsai.core.tool.Ifc):
cls.setup_listeners(obj)
IfcStore.edited_objs = set()
- edited_objs = bpy.context.scene.BIMProjectProperties.edited_objs
+ edited_objs = tool.Project.get_project_props().edited_objs
for i in range(len(edited_objs))[::-1]:
obj = edited_objs[i].obj
if obj:
@@ -220,7 +220,7 @@ class Ifc(bonsai.core.tool.Ifc):
"""
if obj in IfcStore.edited_objs:
return
- edited_objs = bpy.context.scene.BIMProjectProperties.edited_objs
+ edited_objs = tool.Project.get_project_props().edited_objs
edited_objs.add().obj = obj
IfcStore.edited_objs.add(obj)
IfcStore.history_edit_object(obj, finish_editing=False)
@@ -233,7 +233,7 @@ class Ifc(bonsai.core.tool.Ifc):
"""
if obj not in IfcStore.edited_objs:
return
- edited_objs = bpy.context.scene.BIMProjectProperties.edited_objs
+ edited_objs = tool.Project.get_project_props().edited_objs
edited_objs.remove(next(i for i, o in enumerate(edited_objs) if o.obj == obj))
IfcStore.edited_objs.discard(obj)
IfcStore.history_edit_object(obj, finish_editing=True)
diff --git a/src/bonsai/bonsai/tool/loader.py b/src/bonsai/bonsai/tool/loader.py
index f79f4b4509..17e8764fbf 100644
--- a/src/bonsai/bonsai/tool/loader.py
+++ b/src/bonsai/bonsai/tool/loader.py
@@ -89,7 +89,7 @@ class Loader(bonsai.core.tool.Loader):
@classmethod
def get_mesh_name(cls, representation: ifcopenshell.entity_instance) -> str:
- context_id = representation.ContextOfItems.id() if hasattr(representation, "ContextOfItems") else 0
+ context_id = context.id() if (context := getattr(representation, "ContextOfItems", None)) else 0
return "{}/{}".format(context_id, representation.id())
@classmethod
@@ -105,7 +105,7 @@ class Loader(bonsai.core.tool.Loader):
mesh: tool.Geometry.TYPES_WITH_MESH_PROPERTIES,
) -> None:
geometry = shape.geometry if hasattr(shape, "geometry") else shape
- mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id.split("-")[0])
+ tool.Geometry.get_mesh_props(mesh).ifc_definition_id = int(geometry.id.split("-")[0])
@classmethod
def create_surface_style_shading(
@@ -698,7 +698,7 @@ class Loader(bonsai.core.tool.Loader):
project_north = 0
if has_offset or has_rotation:
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
props.blender_offset_x = str(model_offset[0])
props.blender_offset_y = str(model_offset[1])
props.blender_offset_z = str(model_offset[2])
@@ -747,7 +747,7 @@ class Loader(bonsai.core.tool.Loader):
cls, element: ifcopenshell.entity_instance, is_gross: bool = False
) -> Union[ifcopenshell.geom.ShapeElementType, None]:
context_settings = cls.settings.gross_context_settings if is_gross else cls.settings.context_settings
- geometry_library = bpy.context.scene.BIMProjectProperties.geometry_library
+ geometry_library = tool.Project.get_project_props().geometry_library
for settings in context_settings:
try:
result = ifcopenshell.geom.create_shape(settings, element, geometry_library=geometry_library)
@@ -952,7 +952,7 @@ class Loader(bonsai.core.tool.Loader):
matrix[1][3] = offset_xyz[1]
matrix[2][3] = offset_xyz[2]
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
if props.has_blender_offset:
if obj.BIMObjectProperties.blender_offset_type == "NONE":
obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT"
diff --git a/src/bonsai/bonsai/tool/misc.py b/src/bonsai/bonsai/tool/misc.py
index 87d367895b..db43a14c5d 100644
--- a/src/bonsai/bonsai/tool/misc.py
+++ b/src/bonsai/bonsai/tool/misc.py
@@ -113,10 +113,11 @@ class Misc(bonsai.core.tool.Misc):
new_objs = []
for obj in objs:
- if obj.type != "MESH" or obj == cutter:
+ mesh = obj.data
+ if not isinstance(mesh, bpy.types.Mesh) or obj == cutter:
continue
new_obj = obj.copy()
- new_obj.data = obj.data.copy()
+ new_obj.data = mesh.copy()
for collection in obj.users_collection:
collection.objects.link(new_obj)
diff --git a/src/bonsai/bonsai/tool/model.py b/src/bonsai/bonsai/tool/model.py
index 6db9718d4d..e55798602f 100644
--- a/src/bonsai/bonsai/tool/model.py
+++ b/src/bonsai/bonsai/tool/model.py
@@ -277,7 +277,7 @@ class Model(bonsai.core.tool.Model):
mesh = bpy.data.meshes.new("Axis")
mesh.from_pydata(cls.vertices, cls.edges, [])
- mesh.BIMMeshProperties.subshape_type = "AXIS"
+ tool.Geometry.get_mesh_props(mesh).subshape_type = "AXIS"
if obj is None:
obj = bpy.data.objects.new("Axis", mesh)
@@ -334,7 +334,7 @@ class Model(bonsai.core.tool.Model):
mesh = bpy.data.meshes.new("Profile")
mesh.from_pydata(cls.vertices, cls.edges, [])
- mesh.BIMMeshProperties.subshape_type = "PROFILE"
+ tool.Geometry.get_mesh_props(mesh).subshape_type = "PROFILE"
if obj is None:
obj = bpy.data.objects.new("Profile", mesh)
@@ -376,7 +376,7 @@ class Model(bonsai.core.tool.Model):
mesh = bpy.data.meshes.new("Curve")
mesh.from_pydata(cls.vertices, cls.edges, [])
- mesh.BIMMeshProperties.subshape_type = "PROFILE"
+ tool.Geometry.get_mesh_props(mesh).subshape_type = "PROFILE"
if obj is None:
obj = bpy.data.objects.new("Curve", mesh)
@@ -417,7 +417,7 @@ class Model(bonsai.core.tool.Model):
mesh = bpy.data.meshes.new("Surface")
mesh.from_pydata(cls.vertices, cls.edges, [])
- mesh.BIMMeshProperties.subshape_type = "PROFILE"
+ tool.Geometry.get_mesh_props(mesh).subshape_type = "PROFILE"
if obj is None:
obj = bpy.data.objects.new("Surface", mesh)
@@ -569,7 +569,10 @@ class Model(bonsai.core.tool.Model):
element: Optional[ifcopenshell.entity_instance] = None,
representation: Optional[ifcopenshell.entity_instance] = None,
) -> list[ifcopenshell.entity_instance]:
+ """Either element or representation must be provided."""
+ assert element or representation, "Either element or representation must be provided."
if representation is None:
+ assert element
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if not representation:
return []
@@ -1461,9 +1464,9 @@ class Model(bonsai.core.tool.Model):
after material assignment or material unassignment.
"""
for element in elements:
- if not (obj := tool.Ifc.get_object(element)) or not obj.data:
+ if not (obj := tool.Ifc.get_object(element)) or not (data := obj.data):
continue
- representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
+ representation = tool.Ifc.get().by_id(tool.Geometry.get_mesh_props(data).ifc_definition_id)
bonsai.core.geometry.switch_representation(
tool.Ifc,
tool.Geometry,
@@ -1931,7 +1934,9 @@ class Model(bonsai.core.tool.Model):
or it's not referring to an object (e.g. potential boolean object)."""
if obj.type != "MESH":
return
- return obj.data.BIMMeshProperties.obj
+ mesh = obj.data
+ assert isinstance(mesh, bpy.types.Mesh)
+ return tool.Geometry.get_mesh_props(mesh).obj
@classmethod
def get_tracked_opening_type(cls, obj: bpy.types.Object) -> Union[Literal["OPENING", "BOOLEAN"], None]:
diff --git a/src/bonsai/bonsai/tool/polyline.py b/src/bonsai/bonsai/tool/polyline.py
index 8554e182f1..db8f4e7922 100644
--- a/src/bonsai/bonsai/tool/polyline.py
+++ b/src/bonsai/bonsai/tool/polyline.py
@@ -20,6 +20,7 @@ import bpy
import bmesh
import math
import ifcopenshell
+import ifcopenshell.util.unit
import bonsai.core.tool
import bonsai.tool as tool
from bonsai.bim.module.drawing.helper import format_distance
@@ -452,7 +453,8 @@ class Polyline(bonsai.core.tool.Polyline):
def format_input_ui_units(cls, value: float, is_area: bool = False) -> str:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
if bpy.context.scene.unit_settings.system == "IMPERIAL":
- precision = bpy.context.scene.DocProperties.imperial_precision
+ dprops = tool.Drawing.get_document_props()
+ precision = dprops.imperial_precision
if is_area:
area_unit = bpy.context.scene.BIMProperties.area_unit
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get(), unit_type=area_unit)
diff --git a/src/bonsai/bonsai/tool/project.py b/src/bonsai/bonsai/tool/project.py
index 7b529cdea9..98f4f76799 100644
--- a/src/bonsai/bonsai/tool/project.py
+++ b/src/bonsai/bonsai/tool/project.py
@@ -235,7 +235,7 @@ class Project(bonsai.core.tool.Project):
@classmethod
def load_linked_models_from_ifc(cls) -> None:
- links = bpy.context.scene.BIMProjectProperties.links
+ links = tool.Project.get_project_props().links
links.clear()
links_document = cls.get_linked_models_document()
if not links_document:
@@ -252,7 +252,7 @@ class Project(bonsai.core.tool.Project):
@classmethod
def save_linked_models_to_ifc(cls) -> None:
ifc_file = tool.Ifc.get()
- links = bpy.context.scene.BIMProjectProperties.links
+ links = tool.Project.get_project_props().links
filepaths: set[Path] = set()
for link in links:
filepaths.add(Path(link.name))
diff --git a/src/bonsai/bonsai/tool/root.py b/src/bonsai/bonsai/tool/root.py
index b18e649338..ac763cdbf4 100644
--- a/src/bonsai/bonsai/tool/root.py
+++ b/src/bonsai/bonsai/tool/root.py
@@ -19,6 +19,7 @@
import bpy
import ifcopenshell
import ifcopenshell.api
+import ifcopenshell.api.style
import ifcopenshell.util.representation
import ifcopenshell.util.element
import ifcopenshell.util.placement
@@ -49,12 +50,12 @@ class Root(bonsai.core.tool.Root):
tool.Geometry.run_style_add_style(obj=mat)
for mat in tool.Geometry.get_object_materials_without_styles(obj)
]
- ifcopenshell.api.run(
- "style.assign_representation_styles",
+ props = tool.Geometry.get_geometry_props()
+ ifcopenshell.api.style.assign_representation_styles(
tool.Ifc.get(),
shape_representation=body,
styles=tool.Geometry.get_styles(obj),
- should_use_presentation_style_assignment=bpy.context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
+ should_use_presentation_style_assignment=props.should_use_presentation_style_assignment,
)
@classmethod
@@ -169,8 +170,8 @@ class Root(bonsai.core.tool.Root):
@classmethod
def get_object_representation(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
- if obj.data and obj.data.BIMMeshProperties.ifc_definition_id:
- return tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
+ if obj.data and (mesh_props := tool.Geometry.get_mesh_props(obj.data)).ifc_definition_id:
+ return tool.Ifc.get().by_id(mesh_props.ifc_definition_id)
element = tool.Ifc.get_entity(obj)
if element.is_a("IfcTypeProduct"):
if element.RepresentationMaps:
@@ -302,8 +303,8 @@ class Root(bonsai.core.tool.Root):
voided_objs.append(subobj)
for voided_obj in voided_objs:
- if voided_obj.data:
- representation = tool.Ifc.get().by_id(voided_obj.data.BIMMeshProperties.ifc_definition_id)
+ if data := voided_obj.data:
+ representation = tool.Ifc.get().by_id(tool.Geometry.get_mesh_props(data).ifc_definition_id)
bonsai.core.geometry.switch_representation(
tool.Ifc,
tool.Geometry,
@@ -421,8 +422,8 @@ class Root(bonsai.core.tool.Root):
to unlink them.
"""
tool.Ifc.unlink(obj=obj)
- if hasattr(obj.data, "BIMMeshProperties"):
- obj.data.BIMMeshProperties.ifc_definition_id = 0
+ if tool.Geometry.has_mesh_properties((data := obj.data)):
+ tool.Geometry.get_mesh_props(mesh).ifc_definition_id = 0
for material_slot in obj.material_slots:
if material := material_slot.material:
tool.Ifc.unlink(obj=material)
diff --git a/src/bonsai/bonsai/tool/snap.py b/src/bonsai/bonsai/tool/snap.py
index b06d9a65bb..10636f031a 100644
--- a/src/bonsai/bonsai/tool/snap.py
+++ b/src/bonsai/bonsai/tool/snap.py
@@ -25,6 +25,7 @@ import math
import mathutils
from mathutils import Matrix, Vector
from lark import Lark, Transformer
+from typing import Union
class Snap(bonsai.core.tool.Snap):
@@ -313,7 +314,9 @@ class Snap(bonsai.core.tool.Snap):
plane_normal = tool.Polyline.use_transform_orientations(plane_normal)
return plane_origin, plane_normal
- def cast_rays_to_single_object(obj, mouse_pos):
+ def cast_rays_to_single_object(
+ obj: bpy.types.Object, mouse_pos: tuple[int, int]
+ ) -> Union[tuple[bpy.types.Object, Vector, int], tuple[None, None, None]]:
if obj.type != "MESH":
return None, None, None
hit, normal, face_index = tool.Raycast.obj_ray_cast(context, event, obj)
@@ -332,7 +335,9 @@ class Snap(bonsai.core.tool.Snap):
else:
return None, None, None
- def cast_rays_and_get_best_object(objs_to_raycast, mouse_pos):
+ def cast_rays_and_get_best_object(
+ objs_to_raycast: list[bpy.types.Object], mouse_pos: tuple[int, int]
+ ) -> Union[tuple[bpy.types.Object, Vector, int], tuple[None, None, None]]:
best_length_squared = 1.0
best_obj = None
best_hit = None
diff --git a/src/bonsai/bonsai/tool/spatial.py b/src/bonsai/bonsai/tool/spatial.py
index f2c784c464..2f59e0b3da 100644
--- a/src/bonsai/bonsai/tool/spatial.py
+++ b/src/bonsai/bonsai/tool/spatial.py
@@ -699,9 +699,10 @@ class Spatial(bonsai.core.tool.Spatial):
for obj in bpy.context.visible_objects:
visible_element = tool.Ifc.get_entity(obj)
+ old_mesh = obj.data
if (
not visible_element
- or obj.type != "MESH"
+ or not isinstance(old_mesh, bpy.types.Mesh)
or not cls.is_bounding_class(visible_element)
or not tool.Drawing.is_intersecting_plane(obj, cut_point, cut_normal)
):
@@ -959,7 +960,7 @@ class Spatial(bonsai.core.tool.Spatial):
old_mesh = active_obj.data
old_mesh_name = old_mesh.name
assert active_obj and isinstance(old_mesh, bpy.types.Mesh)
- mesh.BIMMeshProperties.ifc_definition_id = old_mesh.BIMMeshProperties.ifc_definition_id
+ tool.Geometry.get_mesh_props(mesh).ifc_definition_id = tool.Geometry.get_mesh_props(old_mesh).ifc_definition_id
tool.Geometry.change_object_data(active_obj, mesh, is_global=True)
tool.Ifc.edit(active_obj)
tool.Blender.remove_data_block(old_mesh)
diff --git a/src/bonsai/bonsai/tool/surveyor.py b/src/bonsai/bonsai/tool/surveyor.py
index 80aa96a341..28856a19f8 100644
--- a/src/bonsai/bonsai/tool/surveyor.py
+++ b/src/bonsai/bonsai/tool/surveyor.py
@@ -32,7 +32,7 @@ class Surveyor(bonsai.core.tool.Surveyor):
def get_absolute_matrix(cls, obj: bpy.types.Object) -> npt.NDArray[np.float64]:
M_TRANSLATION = (slice(0, 3), 3)
matrix = np.array(obj.matrix_world)
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
if props.has_blender_offset and obj.BIMObjectProperties.blender_offset_type != "NOT_APPLICABLE":
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
coordinate_offset = tool.Geometry.get_cartesian_point_offset(obj)
diff --git a/src/bonsai/scripts/headless_import.py b/src/bonsai/scripts/headless_import.py
index cda13e3878..6ce5f1f1ad 100644
--- a/src/bonsai/scripts/headless_import.py
+++ b/src/bonsai/scripts/headless_import.py
@@ -1,12 +1,13 @@
# This can be run using `blender -b -P headless_import.py`
import bpy
+import bonsai.tool as tool
from bonsai.bim.ifc import IfcStore
# When federating, you may wish to manually specify the origin to ensure models
# with different or arbitrary origin conventions will turn up in the right spot.
-props = bpy.context.scene.BIMGeoreferenceProperties
+props = tool.Georeference.get_georeference_props()
# A good idea it to test import a portion of the model (or grids only) and check
# georeferencing coordinates in the IFC Georeferencing panel before filling out
@@ -19,7 +20,7 @@ props = bpy.context.scene.BIMGeoreferenceProperties
# props.blender_x_axis_ordinate = '0.989063862448262'
# props.has_blender_offset = True
-props = bpy.context.scene.BIMProjectProperties
+props = tool.Project.get_project_props()
# Generally recommended to disable caching for stability right now
props.should_cache = False
diff --git a/src/bonsai/test/bim/bootstrap.py b/src/bonsai/test/bim/bootstrap.py
index bea49df413..a973c8a7dc 100644
--- a/src/bonsai/test/bim/bootstrap.py
+++ b/src/bonsai/test/bim/bootstrap.py
@@ -23,6 +23,7 @@ import bpy
import pytest
import webbrowser
import bonsai.bim.handler
+import bonsai.tool as tool
import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.util.representation
@@ -66,7 +67,8 @@ class NewIfc4X3:
bpy.data.batch_remove(bpy.data.objects)
bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
bonsai.bim.handler.load_post(None)
- bpy.context.scene.BIMProjectProperties.export_schema = "IFC4X3_ADD2"
+ props = tool.Project.get_project_props()
+ props.export_schema = "IFC4X3_ADD2"
bpy.ops.bim.create_project()
diff --git a/src/bonsai/test/bim/test_feature.py b/src/bonsai/test/bim/test_feature.py
index 95945ced81..50598d7be1 100644
--- a/src/bonsai/test/bim/test_feature.py
+++ b/src/bonsai/test/bim/test_feature.py
@@ -39,7 +39,7 @@ scenarios("feature")
variables = {
"cwd": Path.cwd().as_posix(),
- "ifc": "IfcStore.get_file()",
+ "ifc": "tool.Ifc.get()",
"pset_ifc": "IfcStore.pset_template_file",
"classification_ifc": "IfcStore.classification_file",
}
@@ -190,7 +190,8 @@ def an_empty_blender_session():
# default project settings
bpy.context.scene.unit_settings.system = "METRIC"
bpy.context.scene.unit_settings.length_unit = "MILLIMETERS"
- bpy.context.scene.BIMProjectProperties.template_file = "0"
+ props = tool.Project.get_project_props()
+ props.template_file = "0"
tool.Blender.get_addon_preferences().should_play_chaching_sound = False
@@ -203,7 +204,8 @@ def an_empty_ifc_project():
@given("an empty IFC2X3 project")
def an_empty_ifc_2x3_project():
an_empty_blender_session()
- bpy.context.scene.BIMProjectProperties.export_schema = "IFC2X3"
+ props = tool.Project.get_project_props()
+ props.export_schema = "IFC2X3"
bpy.ops.bim.create_project()
@@ -742,7 +744,7 @@ def the_object_name_has_a_representation_type_of_context(name, type, context):
def the_object_name_data_is_a_type_representation_of_context(name, type, context):
ifc = an_ifc_file_exists()
context, subcontext, target_view = context.split("/")
- rep = ifc.by_id(the_object_name_exists(name).data.BIMMeshProperties.ifc_definition_id)
+ rep = ifc.by_id(tool.Geometry.get_mesh_props(the_object_name_exists(name).data).ifc_definition_id)
assert rep
assert rep.RepresentationType == type, f"The object {name} is not a {type} representation"
assert rep.ContextOfItems.ContextType == context
@@ -888,7 +890,7 @@ def the_object_name_has_no_data(name):
@then(parsers.parse('the object "{name}" has data which is an IFC representation'))
def the_object_name_has_ifc_representation_data(name):
- id = the_object_name_exists(name).data.BIMMeshProperties.ifc_definition_id
+ id = tool.Geometry.get_mesh_props(the_object_name_exists(name).data).ifc_definition_id
assert id != 0, f"The ID is {id}"
diff --git a/src/bonsai/test/tool/test_drawing.py b/src/bonsai/test/tool/test_drawing.py
index 5e3c239e03..902e51e75d 100644
--- a/src/bonsai/test/tool/test_drawing.py
+++ b/src/bonsai/test/tool/test_drawing.py
@@ -113,30 +113,34 @@ class TestDeleteDrawingElements(NewFile):
class TestDisableEditingDrawings(NewFile):
def test_run(self):
- bpy.context.scene.DocProperties.is_editing_drawings = True
+ props = tool.Drawing.get_document_props()
+ props.is_editing_drawings = True
subject.disable_editing_drawings()
- assert bpy.context.scene.DocProperties.is_editing_drawings == False
+ assert props.is_editing_drawings == False
class TestDisableEditingSchedules(NewFile):
def test_run(self):
- bpy.context.scene.DocProperties.is_editing_schedules = True
+ props = tool.Drawing.get_document_props()
+ props.is_editing_schedules = True
subject.disable_editing_schedules()
- assert bpy.context.scene.DocProperties.is_editing_schedules == False
+ assert props.is_editing_schedules == False
class TestDisableEditingReferences(NewFile):
def test_run(self):
- bpy.context.scene.DocProperties.is_editing_references = True
+ props = tool.Drawing.get_document_props()
+ props.is_editing_references = True
subject.disable_editing_references()
- assert bpy.context.scene.DocProperties.is_editing_references == False
+ assert props.is_editing_references == False
class TestDisableEditingSheets(NewFile):
def test_run(self):
- bpy.context.scene.DocProperties.is_editing_sheets = True
+ props = tool.Drawing.get_document_props()
+ props.is_editing_sheets = True
subject.disable_editing_sheets()
- assert bpy.context.scene.DocProperties.is_editing_sheets == False
+ assert props.is_editing_sheets == False
class TestDisableEditingText(NewFile):
@@ -166,30 +170,34 @@ class TestEnableEditing(NewFile):
class TestEnableEditingDrawings(NewFile):
def test_run(self):
- bpy.context.scene.DocProperties.is_editing_drawings = False
+ props = tool.Drawing.get_document_props()
+ props.is_editing_drawings = False
subject.enable_editing_drawings()
- assert bpy.context.scene.DocProperties.is_editing_drawings == True
+ assert props.is_editing_drawings == True
class TestEnableEditingSchedules(NewFile):
def test_run(self):
- bpy.context.scene.DocProperties.is_editing_schedules = False
+ props = tool.Drawing.get_document_props()
+ props.is_editing_schedules = False
subject.enable_editing_schedules()
- assert bpy.context.scene.DocProperties.is_editing_schedules == True
+ assert props.is_editing_schedules == True
class TestEnableEditingReferences(NewFile):
def test_run(self):
- bpy.context.scene.DocProperties.is_editing_references = False
+ props = tool.Drawing.get_document_props()
+ props.is_editing_references = False
subject.enable_editing_references()
- assert bpy.context.scene.DocProperties.is_editing_references == True
+ assert props.is_editing_references == True
class TestEnableEditingSheets(NewFile):
def test_run(self):
- bpy.context.scene.DocProperties.is_editing_sheets = False
+ props = tool.Drawing.get_document_props()
+ props.is_editing_sheets = False
subject.enable_editing_sheets()
- assert bpy.context.scene.DocProperties.is_editing_sheets == True
+ assert props.is_editing_sheets == True
class TestEnableEditingText(NewFile):
@@ -492,7 +500,7 @@ class TestImportDrawings(NewFile):
pset = ifcopenshell.api.run("pset.add_pset", ifc, product=drawing, name="EPset_Drawing")
ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"TargetView": "PLAN_VIEW"})
subject.import_drawings()
- props = bpy.context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
for d in props.drawings:
d.is_expanded = True
subject.import_drawings()
@@ -508,7 +516,7 @@ class TestImportSchedules(NewFile):
ifc.createIfcDocumentInformation(Identification="Y", Name="FOOBAZ")
document = ifc.createIfcDocumentInformation(Identification="X", Name="FOOBAR", Scope="SCHEDULE")
subject.import_documents("SCHEDULE")
- props = bpy.context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
assert props.schedules[0].ifc_definition_id == document.id()
assert props.schedules[0].identification == "X"
assert props.schedules[0].name == "FOOBAR"
@@ -519,7 +527,7 @@ class TestImportSchedules(NewFile):
ifc.createIfcDocumentInformation(DocumentId="Y", Name="FOOBAZ")
document = ifc.createIfcDocumentInformation(DocumentId="X", Name="FOOBAR", Scope="SCHEDULE")
subject.import_documents("SCHEDULE")
- props = bpy.context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
assert props.schedules[0].ifc_definition_id == document.id()
assert props.schedules[0].identification == "X"
assert props.schedules[0].name == "FOOBAR"
@@ -532,7 +540,7 @@ class TestImportReferences(NewFile):
ifc.createIfcDocumentInformation(Identification="Y", Name="FOOBAZ")
document = ifc.createIfcDocumentInformation(Identification="X", Name="FOOBAR", Scope="REFERENCE")
subject.import_documents("REFERENCE")
- props = bpy.context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
assert props.references[0].ifc_definition_id == document.id()
assert props.references[0].identification == "X"
assert props.references[0].name == "FOOBAR"
@@ -543,7 +551,7 @@ class TestImportReferences(NewFile):
ifc.createIfcDocumentInformation(DocumentId="Y", Name="FOOBAZ")
document = ifc.createIfcDocumentInformation(DocumentId="X", Name="FOOBAR", Scope="REFERENCE")
subject.import_documents("REFERENCE")
- props = bpy.context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
assert props.references[0].ifc_definition_id == document.id()
assert props.references[0].identification == "X"
assert props.references[0].name == "FOOBAR"
@@ -556,7 +564,7 @@ class TestImportSheets(NewFile):
ifc.createIfcDocumentInformation(Identification="Y", Name="FOOBAZ")
document = ifc.createIfcDocumentInformation(Identification="X", Name="FOOBAR", Scope="SHEET")
subject.import_sheets()
- props = bpy.context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
assert props.sheets[0].ifc_definition_id == document.id()
assert props.sheets[0].identification == "X"
assert props.sheets[0].name == "FOOBAR"
@@ -567,7 +575,7 @@ class TestImportSheets(NewFile):
ifc.createIfcDocumentInformation(DocumentId="Y", Name="FOOBAZ")
document = ifc.createIfcDocumentInformation(DocumentId="X", Name="FOOBAR", Scope="SHEET")
subject.import_sheets()
- props = bpy.context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
assert props.sheets[0].ifc_definition_id == document.id()
assert props.sheets[0].identification == "X"
assert props.sheets[0].name == "FOOBAR"
@@ -657,9 +665,10 @@ class TestSetName(NewFile):
class TestShowDecorations(NewFile):
def test_run(self):
- bpy.context.scene.DocProperties.should_draw_decorations = False
+ props = tool.Drawing.get_document_props()
+ props.should_draw_decorations = False
subject.show_decorations()
- assert bpy.context.scene.DocProperties.should_draw_decorations is True
+ assert props.should_draw_decorations is True
class TestDrawingMaintainingSheetPosition(NewFile):
@@ -680,7 +689,7 @@ class TestDrawingMaintainingSheetPosition(NewFile):
return drawing_data
def test_run(self):
- props = bpy.context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
bpy.ops.bim.create_project()
ifc = tool.Ifc.get()
sheet_path = Path.cwd() / "layouts" / "A00 - UNTITLED.svg"
@@ -845,10 +854,11 @@ class TestDrawingStyles(NewFile):
ifc = tool.Ifc.get()
drawing = ifc.by_type("IfcAnnotation")[0]
bpy.ops.bim.expand_target_view(target_view="PLAN_VIEW")
- props = bpy.context.scene.DocProperties
+ props = tool.Drawing.get_document_props()
props.active_drawing_index = 2
bpy.ops.bim.activate_drawing(drawing=drawing.id())
- self.drawing_styles = bpy.context.scene.DocProperties.drawing_styles
+ props = tool.Drawing.get_document_props()
+ self.drawing_styles = props.drawing_styles
def test_drawing_styles_not_loaded_if_underlay_is_inactive(self):
self.setup_project_with_drawing()
@@ -867,7 +877,8 @@ class TestDrawingStyles(NewFile):
class TestAddReferenceImage(NewFile):
def test_run(self):
- bpy.context.scene.BIMProjectProperties.template_file = "0"
+ props = tool.Project.get_project_props()
+ props.template_file = "0"
bpy.ops.bim.create_project()
ifc_path = Path("test/files/temp/test.ifc").absolute()
bpy.ops.bim.save_project(filepath=str(ifc_path), should_save_as=True)
diff --git a/src/bonsai/test/tool/test_geometry.py b/src/bonsai/test/tool/test_geometry.py
index f1ca935f30..63721bcdb7 100644
--- a/src/bonsai/test/tool/test_geometry.py
+++ b/src/bonsai/test/tool/test_geometry.py
@@ -171,14 +171,14 @@ class TestGetCartesianPointCoordinateOffset(NewFile):
def test_run(self):
obj = bpy.data.objects.new("Object", None)
obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT"
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
props.has_blender_offset = True
obj.BIMObjectProperties.cartesian_point_offset = "1,2,3"
assert np.allclose(subject.get_cartesian_point_offset(obj), np.array((1.0, 2.0, 3.0)))
def test_get_null_if_not_a_cartesian_point_offset_type(self):
obj = bpy.data.objects.new("Object", None)
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
props.has_blender_offset = True
obj.BIMObjectProperties.cartesian_point_offset = "1,2,3"
assert subject.get_cartesian_point_offset(obj) is None
@@ -186,7 +186,7 @@ class TestGetCartesianPointCoordinateOffset(NewFile):
def test_get_null_if_no_blender_offset(self):
obj = bpy.data.objects.new("Object", None)
obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT"
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
props.has_blender_offset = False
assert subject.get_cartesian_point_offset(obj) is None
@@ -237,14 +237,15 @@ class TestImportRepresentationParameters(NewFile):
item = ifc.createIfcExtrudedAreaSolid(SweptArea=swept_area, Depth=2)
representation = ifc.createIfcShapeRepresentation(Items=[item])
data = bpy.data.meshes.new("Mesh")
- data.BIMMeshProperties.ifc_definition_id = representation.id()
+ mprops = tool.Geometry.get_mesh_props(data)
+ mprops.ifc_definition_id = representation.id()
subject.import_representation_parameters(data)
- assert data.BIMMeshProperties.ifc_parameters[0].name == "IfcExtrudedAreaSolid/Depth"
- assert data.BIMMeshProperties.ifc_parameters[0].step_id == item.id()
- assert data.BIMMeshProperties.ifc_parameters[0].index == 3
- assert data.BIMMeshProperties.ifc_parameters[1].name == "IfcCircleProfileDef/Radius"
- assert data.BIMMeshProperties.ifc_parameters[1].step_id == swept_area.id()
- assert data.BIMMeshProperties.ifc_parameters[1].index == 3
+ assert mprops.ifc_parameters[0].name == "IfcExtrudedAreaSolid/Depth"
+ assert mprops.ifc_parameters[0].step_id == item.id()
+ assert mprops.ifc_parameters[0].index == 3
+ assert mprops.ifc_parameters[1].name == "IfcCircleProfileDef/Radius"
+ assert mprops.ifc_parameters[1].step_id == swept_area.id()
+ assert mprops.ifc_parameters[1].index == 3
class TestIsBodyRepresentation(NewFile):
@@ -293,7 +294,7 @@ class TestLink(NewFile):
element = ifc.createIfcShapeRepresentation()
obj = bpy.data.meshes.new("Mesh")
subject.link(element, obj)
- assert obj.BIMMeshProperties.ifc_definition_id == element.id()
+ assert tool.Geometry.get_mesh_props(obj).ifc_definition_id == element.id()
class TestRecordObjectMaterials(NewFile):
@@ -306,7 +307,7 @@ class TestRecordObjectMaterials(NewFile):
material.BIMStyleProperties.ifc_definition_id = style.id()
obj.data.materials.append(material)
subject.record_object_materials(obj)
- assert obj.data.BIMMeshProperties.material_checksum == str([style.id()])
+ assert tool.Geometry.get_mesh_props(obj).material_checksum == str([style.id()])
class TestRecordObjectPosition(NewFile):
diff --git a/src/bonsai/test/tool/test_georeference.py b/src/bonsai/test/tool/test_georeference.py
index 4b22a84781..96e498482c 100644
--- a/src/bonsai/test/tool/test_georeference.py
+++ b/src/bonsai/test/tool/test_georeference.py
@@ -39,7 +39,7 @@ class TestImportProjectedCRS(NewFile):
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
subject.import_projected_crs()
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
assert len(props.projected_crs) == 0
def test_importing_projected_crs(self):
@@ -58,7 +58,7 @@ class TestImportProjectedCRS(NewFile):
unit = ifcopenshell.api.run("unit.add_si_unit", ifc, unit_type="LENGTHUNIT")
projected_crs.MapUnit = unit
subject.import_projected_crs()
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
assert props.projected_crs.get("Name").string_value == "Name"
assert props.projected_crs.get("Description").string_value == "Description"
assert props.projected_crs.get("GeodeticDatum").string_value == "GeodeticDatum"
@@ -71,7 +71,7 @@ class TestImportProjectedCRS(NewFile):
ifc = ifcopenshell.file(schema="IFC2X3")
tool.Ifc.set(ifc)
subject.import_projected_crs()
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
assert len(props.projected_crs) == 0
@@ -82,7 +82,7 @@ class TestImportCoordinateOperation(NewFile):
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
subject.import_coordinate_operation()
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
assert len(props.coordinate_operation) == 0
def test_importing_coordinate_operation(self):
@@ -99,7 +99,7 @@ class TestImportCoordinateOperation(NewFile):
map_conversion.XAxisOrdinate = 5
map_conversion.Scale = 6
subject.import_coordinate_operation()
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
assert props.coordinate_operation.get("Eastings").string_value == "1.0"
assert props.coordinate_operation.get("Northings").string_value == "2.0"
assert props.coordinate_operation.get("OrthogonalHeight").string_value == "3.0"
@@ -112,7 +112,7 @@ class TestImportCoordinateOperation(NewFile):
ifc = ifcopenshell.file(schema="IFC2X3")
tool.Ifc.set(ifc)
subject.import_coordinate_operation()
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
assert len(props.coordinate_operation) == 0
@@ -123,7 +123,7 @@ class TestImportTrueNorth(NewFile):
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
subject.import_true_north()
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
assert props.true_north_abscissa == "0"
assert props.true_north_ordinate == "1"
assert props.true_north_angle == "0"
@@ -135,7 +135,7 @@ class TestImportTrueNorth(NewFile):
context = ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
context.TrueNorth = ifc.createIfcDirection((1.0, 2.0, 0.0))
subject.import_true_north()
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
assert props.true_north_abscissa == "1.0"
assert props.true_north_ordinate == "2.0"
assert props.true_north_angle == "-26.5650512"
@@ -176,35 +176,39 @@ class TestGetTrueNorthAttributes(NewFile):
class TestEnableEditing(NewFile):
def test_run(self):
- bpy.context.scene.BIMGeoreferenceProperties.is_editing = False
+ props = tool.Georeference.get_georeference_props()
+ props.is_editing = False
subject.enable_editing()
- assert bpy.context.scene.BIMGeoreferenceProperties.is_editing is True
+ assert props.is_editing is True
class TestDisableEditing(NewFile):
def test_run(self):
- bpy.context.scene.BIMGeoreferenceProperties.is_editing = True
+ props = tool.Georeference.get_georeference_props()
+ props.is_editing = True
subject.disable_editing()
- assert bpy.context.scene.BIMGeoreferenceProperties.is_editing is False
+ assert props.is_editing is False
class TestSetCoordinates(NewFile):
def test_run(self):
+ props = tool.Georeference.get_georeference_props()
subject.set_coordinates("local", [1.0, 2.0, 3.0])
- assert bpy.context.scene.BIMGeoreferenceProperties.local_coordinates == "1.0,2.0,3.0"
+ assert props.local_coordinates == "1.0,2.0,3.0"
subject.set_coordinates("blender", [4.0, 5.0, 6.0])
- assert bpy.context.scene.BIMGeoreferenceProperties.blender_coordinates == "4.0,5.0,6.0"
+ assert props.blender_coordinates == "4.0,5.0,6.0"
subject.set_coordinates("map", [7.0, 8.0, 9.0])
- assert bpy.context.scene.BIMGeoreferenceProperties.map_coordinates == "7.0,8.0,9.0"
+ assert props.map_coordinates == "7.0,8.0,9.0"
class TestGetCoordinates(NewFile):
def test_run(self):
- bpy.context.scene.BIMGeoreferenceProperties.local_coordinates = "1.0,2.0,3.0"
+ props = tool.Georeference.get_georeference_props()
+ props.local_coordinates = "1.0,2.0,3.0"
assert subject.get_coordinates("local") == [1.0, 2.0, 3.0]
- bpy.context.scene.BIMGeoreferenceProperties.blender_coordinates = "4.0,5.0,6.0"
+ props.blender_coordinates = "4.0,5.0,6.0"
assert subject.get_coordinates("blender") == [4.0, 5.0, 6.0]
- bpy.context.scene.BIMGeoreferenceProperties.map_coordinates = "7.0,8.0,9.0"
+ props.map_coordinates = "7.0,8.0,9.0"
assert subject.get_coordinates("map") == [7.0, 8.0, 9.0]
@@ -231,7 +235,7 @@ class TestXyz2Enh(NewFile):
ifc = ifcopenshell.file()
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
tool.Ifc.set(ifc)
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
props.has_blender_offset = True
props.blender_offset_x = "1.0"
assert subject.xyz2enh([0.0, 0.0, 0.0]) == (1.0, 0.0, 0.0)
@@ -247,7 +251,7 @@ class TestXyz2Enh(NewFile):
assert subject.xyz2enh([0.0, 0.0, 0.0]) == (1.0, 0.0, 0.0)
def test_applying_both_blender_offset_and_map_conversion(self):
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
props.has_blender_offset = True
props.blender_offset_x = "1.0"
ifc = ifcopenshell.file()
@@ -271,7 +275,7 @@ class TestEnh2Xyz(NewFile):
ifc = ifcopenshell.file()
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
tool.Ifc.set(ifc)
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
props.has_blender_offset = True
props.blender_offset_x = "1.0"
assert subject.enh2xyz([0.0, 0.0, 0.0]) == (-1.0, 0.0, 0.0)
@@ -287,7 +291,7 @@ class TestEnh2Xyz(NewFile):
assert subject.enh2xyz([0.0, 0.0, 0.0]) == (-1.0, 0.0, 0.0)
def test_applying_both_blender_offset_and_map_conversion(self):
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
props.has_blender_offset = True
props.blender_offset_x = "1.0"
ifc = ifcopenshell.file()
diff --git a/src/bonsai/test/tool/test_ifc.py b/src/bonsai/test/tool/test_ifc.py
index 429dd76212..641a8adfa0 100644
--- a/src/bonsai/test/tool/test_ifc.py
+++ b/src/bonsai/test/tool/test_ifc.py
@@ -189,7 +189,7 @@ class TestLink(test.bim.bootstrap.NewFile):
element = ifc.create_entity("IfcShapeRepresentation")
obj = bpy.data.meshes.new("Material")
subject.link(element, obj)
- assert obj.BIMMeshProperties.ifc_definition_id == element.id()
+ assert tool.Geometry.get_mesh_props(obj).ifc_definition_id == element.id()
class TestUnlink(test.bim.bootstrap.NewFile):
diff --git a/src/bonsai/test/tool/test_loader.py b/src/bonsai/test/tool/test_loader.py
index c8235ed52b..30ef9e22b9 100644
--- a/src/bonsai/test/tool/test_loader.py
+++ b/src/bonsai/test/tool/test_loader.py
@@ -520,7 +520,8 @@ class TestLoadingIndexedMap(NewFile):
class TestSetupActiveBsddClassification(NewFile):
def run_test(self, schema: ifcopenshell.util.schema.IFC_SCHEMA) -> None:
schema_ = "IFC4X3_ADD2" if schema == "IFC4X3" else schema
- bpy.context.scene.BIMProjectProperties.export_schema = schema_
+ props = tool.Project.get_project_props()
+ props.export_schema = schema_
bpy.ops.bim.create_project()
ifc_file = tool.Ifc.get()
name = "CCI Construction"
diff --git a/src/bonsai/test/tool/test_model.py b/src/bonsai/test/tool/test_model.py
index 13a847f94a..a77d90ab92 100644
--- a/src/bonsai/test/tool/test_model.py
+++ b/src/bonsai/test/tool/test_model.py
@@ -378,7 +378,7 @@ class TestGenerateStair2DProfile(NewFile):
class TestUsingArrays(NewFile):
def setup_array(self, add_second_layer=False, sync_children=False):
- bpy.context.scene.BIMProjectProperties.template_file = "0"
+ tool.Project.get_project_props().template_file = "0"
bpy.ops.bim.create_project()
bpy.ops.mesh.primitive_cube_add()
@@ -467,7 +467,8 @@ class TestApplyIfcMaterialChanges(NewFile):
return mesh
def setup_test(self, and_elements: bool = True) -> None:
- bpy.context.scene.BIMProjectProperties.template_file = "0"
+ props = tool.Project.get_project_props()
+ props.template_file = "0"
bpy.context.scene.unit_settings.length_unit = "MILLIMETERS"
bpy.ops.bim.create_project()
ifc_file = tool.Ifc.get()
diff --git a/src/bonsai/test/tool/test_project.py b/src/bonsai/test/tool/test_project.py
index 4af7494a85..a5f94dbe4b 100644
--- a/src/bonsai/test/tool/test_project.py
+++ b/src/bonsai/test/tool/test_project.py
@@ -244,25 +244,25 @@ class TestLoadProject(NewFile):
class TestLoadLinkedModels(NewFile):
def test_load_linked_models_no_document(self):
- links = bpy.context.scene.BIMProjectProperties.links
+ props = tool.Project.get_project_props()
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
subject.load_linked_models_from_ifc()
- assert len(links) == 0
+ assert len(props.links) == 0
def test_load_linked_models_document_no_references(self):
ifc = ifcopenshell.file()
- links = bpy.context.scene.BIMProjectProperties.links
+ props = tool.Project.get_project_props()
ifcopenshell.api.root.create_entity(ifc, "IfcProject")
document = ifcopenshell.api.document.add_information(ifc)
document.Name = "BBIM_Linked_Models"
tool.Ifc.set(ifc)
subject.load_linked_models_from_ifc()
- assert len(links) == 0
+ assert len(props.links) == 0
def test_load_linked_models_document_with_references(self):
ifc = ifcopenshell.file()
- links = bpy.context.scene.BIMProjectProperties.links
+ props = tool.Project.get_project_props()
ifcopenshell.api.root.create_entity(ifc, "IfcProject")
document = ifcopenshell.api.document.add_information(ifc)
document.Name = "BBIM_Linked_Models"
@@ -271,8 +271,8 @@ class TestLoadLinkedModels(NewFile):
reference.Location = linked_model_path
tool.Ifc.set(ifc)
subject.load_linked_models_from_ifc()
- assert len(links) == 1
- assert links[0].name == linked_model_path
+ assert len(props.links) == 1
+ assert props.links[0].name == linked_model_path
class TestSaveLinkedModelsToIfc(NewFile):
@@ -286,8 +286,8 @@ class TestSaveLinkedModelsToIfc(NewFile):
def test_save_linked_models_to_ifc_paths_to_add(self):
ifc = ifcopenshell.file()
ifcopenshell.api.root.create_entity(ifc, "IfcProject")
- links = bpy.context.scene.BIMProjectProperties.links
- link = links.add()
+ props = tool.Project.get_project_props()
+ link = props.links.add()
linked_model_path = "test.ifc"
link.name = linked_model_path
tool.Ifc.set(ifc)
@@ -299,7 +299,7 @@ class TestSaveLinkedModelsToIfc(NewFile):
def test_save_linked_models_to_ifc_already_created_references(self):
ifc = ifcopenshell.file()
- links = bpy.context.scene.BIMProjectProperties.links
+ links = tool.Project.get_project_props().links
ifcopenshell.api.root.create_entity(ifc, "IfcProject")
document = ifcopenshell.api.document.add_information(ifc)
@@ -326,7 +326,7 @@ class TestSaveLinkedModelsToIfc(NewFile):
def test_save_linked_models_to_ifc_references_to_remove(self):
ifc = ifcopenshell.file()
- links = bpy.context.scene.BIMProjectProperties.links
+ links = tool.Project.get_project_props().links
ifcopenshell.api.root.create_entity(ifc, "IfcProject")
document = ifcopenshell.api.document.add_information(ifc)
diff --git a/src/bonsai/test/tool/test_root.py b/src/bonsai/test/tool/test_root.py
index 19cba02029..e87eb312a7 100644
--- a/src/bonsai/test/tool/test_root.py
+++ b/src/bonsai/test/tool/test_root.py
@@ -120,8 +120,8 @@ class TestGetObjectRepresentation(NewFile):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
representation = ifc.createIfcShapeRepresentation()
- obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh"))
- obj.data.BIMMeshProperties.ifc_definition_id = representation.id()
+ obj = bpy.data.objects.new("Object", (mesh := bpy.data.meshes.new("Mesh")))
+ tool.Geometry.get_mesh_props(mesh).ifc_definition_id = representation.id()
assert subject.get_object_representation(obj) == representation
@@ -175,7 +175,7 @@ class TestSetObjectName(NewFile):
class TestReassignClass(NewFile):
def test_reassigning_multiple_occurrences_of_the_same_type(self):
- bpy.context.scene.BIMProjectProperties.template_file = "IFC4 Demo Template.ifc"
+ tool.Project.get_project_props().template_file = "IFC4 Demo Template.ifc"
bpy.ops.bim.create_project()
ifc_file = tool.Ifc.get()
context = bpy.context
diff --git a/src/bonsai/test/tool/test_surveyor.py b/src/bonsai/test/tool/test_surveyor.py
index 0a12bd3903..bfba0cb480 100644
--- a/src/bonsai/test/tool/test_surveyor.py
+++ b/src/bonsai/test/tool/test_surveyor.py
@@ -20,6 +20,7 @@ import bpy
import numpy as np
import ifcopenshell
import ifcopenshell.api
+import ifcopenshell.util.geolocation
import test.bim.bootstrap
import bonsai.core.tool
@@ -34,7 +35,7 @@ class TestImplementsTool(test.bim.bootstrap.NewFile):
class TestGetGlobalMatrix(test.bim.bootstrap.NewFile):
def test_getting_an_absolute_matrix_if_no_blender_offset(self):
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
props.has_blender_offset = False
obj = bpy.data.objects.new("Object", None)
assert (subject.get_absolute_matrix(obj) == np.array(obj.matrix_world)).all()
@@ -45,7 +46,7 @@ class TestGetGlobalMatrix(test.bim.bootstrap.NewFile):
unit = ifcopenshell.api.run("unit.add_si_unit", ifc, unit_type="LENGTHUNIT", prefix="MILLI")
ifcopenshell.api.run("unit.assign_unit", ifc, units=[unit])
tool.Ifc.set(ifc)
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = tool.Georeference.get_georeference_props()
props.has_blender_offset = True
props.blender_offset_x = "1000"
props.blender_offset_y = "2000"
diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/copy_cost_item_values.py b/src/ifcopenshell-python/ifcopenshell/api/cost/copy_cost_item_values.py
index 6903f41bfe..e62b105c50 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/cost/copy_cost_item_values.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/cost/copy_cost_item_values.py
@@ -30,11 +30,8 @@ def copy_cost_item_values(
parametrically linked, so if one value changes, the other will not.
:param source: The IfcCostItem to copy cost values from
- :type source: ifcopenshell.entity_instance
:param destination: The IfcCostItem to copy cost values from
- :type destination: ifcopenshell.entity_instance
:return: None
- :rtype: None
Example:
@@ -53,11 +50,9 @@ def copy_cost_item_values(
# Let's copy the value from one item to another
ifcopenshell.api.cost.copy_cost_item_values(model, source=item1, destination=item2)
"""
- settings = {"source": source, "destination": destination}
-
- for cost_value in settings["destination"].CostValues or []:
+ for cost_value in destination.CostValues or []:
ifcopenshell.api.cost.remove_cost_item_value(file, cost_value=cost_value)
copied_cost_values = []
- for cost_value in settings["source"].CostValues or []:
+ for cost_value in source.CostValues or []:
copied_cost_values.append(ifcopenshell.util.element.copy_deep(file, cost_value))
- settings["destination"].CostValues = copied_cost_values
+ destination.CostValues = copied_cost_values
diff --git a/src/ifcopenshell-python/ifcopenshell/api/layer/assign_layer.py b/src/ifcopenshell-python/ifcopenshell/api/layer/assign_layer.py
index fa57e526cc..57117b6625 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/layer/assign_layer.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/layer/assign_layer.py
@@ -62,15 +62,10 @@ def assign_layer(
# only one item) to the layer.
ifcopenshell.api.layer.assign_layer(model, items=[representation.Items[0]], layer=layer)
"""
- settings = {
- "items": items,
- "layer": layer,
- }
-
# support AssignedItems == None since layer might just got created
- layer = settings["layer"]
+ assigned_items: set[ifcopenshell.entity_instance]
assigned_items = set(layer.AssignedItems or [])
- items = set(settings["items"])
- if items.issubset(assigned_items):
+ items_set = set(items)
+ if items_set.issubset(assigned_items):
return
- layer.AssignedItems = list(assigned_items | items)
+ layer.AssignedItems = list(assigned_items | items_set)
diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/edit_profile_usage.py b/src/ifcopenshell-python/ifcopenshell/api/material/edit_profile_usage.py
index 4a659f251c..8a583d64b4 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/material/edit_profile_usage.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/material/edit_profile_usage.py
@@ -91,8 +91,7 @@ def edit_profile_usage(
usecase = Usecase()
usecase.file = file
- usecase.settings = {"usage": usage, "attributes": attributes}
- return usecase.execute()
+ return usecase.execute(usage, attributes)
class Usecase:
diff --git a/src/ifcopenshell-python/ifcopenshell/api/owner/settings.py b/src/ifcopenshell-python/ifcopenshell/api/owner/settings.py
index 493f4077f4..b5dedfff49 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/owner/settings.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/owner/settings.py
@@ -30,17 +30,15 @@ def get_application(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instanc
IfcApplication. See ifcopenshell.api.owner.create_owner_history for details.
:param ifc: The IFC file object that is being edited.
- :type ifc: ifcopenshell.file
:return: The IfcApplication with metadata of the authoring software.
- :rtype: ifcopenshell.entity_instance
"""
- app = ifc.by_type("IfcApplication")
+ app = next(iter(ifc.by_type("IfcApplication")), None)
if not app and ifc.schema == "IFC2X3":
raise Exception(
"Please create an application to continue. See the owner.create_owner_history docs for more info."
"https://docs.ifcopenshell.org/autoapi/ifcopenshell/api/owner/create_owner_history/index.html"
)
- return (app or [None])[0]
+ return app
def get_user(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None]:
@@ -50,17 +48,15 @@ def get_user(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None
IfcApplication. See ifcopenshell.api.owner.create_owner_history for details.
:param ifc: The IFC file object that is being edited.
- :type ifc: ifcopenshell.file
:return: The IfcPersonAndOrganization with metadata of the authoring user.
- :rtype: ifcopenshell.entity_instance
"""
- pao = ifc.by_type("IfcPersonAndOrganization")
+ pao = next(iter(ifc.by_type("IfcPersonAndOrganization")), None)
if not pao and ifc.schema == "IFC2X3":
raise Exception(
"Please create a user to continue. See the owner.create_owner_history docs for more info."
"https://docs.ifcopenshell.org/autoapi/ifcopenshell/api/owner/create_owner_history/index.html"
)
- return (pao or [None])[0]
+ return pao
get_application_factory = get_application
diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/cascade_schedule.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/cascade_schedule.py
index 8a989ecb55..65cbd5a58a 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/sequence/cascade_schedule.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/cascade_schedule.py
@@ -19,6 +19,8 @@
import datetime
import ifcopenshell.util.date
import ifcopenshell.util.sequence
+from ifcopenshell.util.sequence import DURATION_TYPE
+from typing import Union, Optional
def cascade_schedule(file: ifcopenshell.file, task: ifcopenshell.entity_instance) -> None:
@@ -41,9 +43,7 @@ def cascade_schedule(file: ifcopenshell.file, task: ifcopenshell.entity_instance
be equivalent to be Tuesday 8am, for instance.
:param task: The start task to begin cascading from.
- :type task: ifcopenshell.entity_instance
:return: None
- :rtype: None
Example:
@@ -103,16 +103,22 @@ def cascade_schedule(file: ifcopenshell.file, task: ifcopenshell.entity_instance
"""
usecase = Usecase()
usecase.file = file
- usecase.settings = {"task": task}
- return usecase.execute()
+ return usecase.execute(task)
class Usecase:
- def execute(self):
- self.calendar_cache = {}
- self.cascade_task(self.settings["task"], is_first_task=True)
+ file: ifcopenshell.file
- def cascade_task(self, task, is_first_task=False, task_sequence=None):
+ def execute(self, task: ifcopenshell.entity_instance):
+ self.calendar_cache = {}
+ self.cascade_task(task, is_first_task=True)
+
+ def cascade_task(
+ self,
+ task: ifcopenshell.entity_instance,
+ is_first_task: bool = False,
+ task_sequence: Optional[list[ifcopenshell.entity_instance]] = None,
+ ) -> None:
if task_sequence is None:
task_sequence = []
@@ -316,18 +322,22 @@ class Usecase:
for nested_task in rel.RelatedObjects or []
]
- def get_lag_time_days(self, lag_time):
+ def get_lag_time_days(self, lag_time: ifcopenshell.entity_instance) -> int:
return ifcopenshell.util.date.ifc2datetime(lag_time.LagValue.wrappedValue).days
- def get_calendar(self, task):
+ def get_calendar(self, task: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
if task.id() not in self.calendar_cache:
self.calendar_cache[task.id()] = ifcopenshell.util.sequence.derive_calendar(task)
return self.calendar_cache[task.id()]
- def offset_date(self, date, days, duration_type, calendar):
+ def offset_date(
+ self, date: datetime.datetime, days: int, duration_type: DURATION_TYPE, calendar: ifcopenshell.entity_instance
+ ) -> datetime.datetime:
return ifcopenshell.util.sequence.offset_date(date, datetime.timedelta(days=days), duration_type, calendar)
- def get_task_time_attribute(self, task, attribute):
+ def get_task_time_attribute(
+ self, task: ifcopenshell.entity_instance, attribute: str
+ ) -> Union[datetime.datetime, None]:
if task.TaskTime:
value = getattr(task.TaskTime, attribute)
if value:
diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/create_baseline.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/create_baseline.py
index a6637c7a88..ab1035bbab 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/sequence/create_baseline.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/create_baseline.py
@@ -75,7 +75,9 @@ class Usecase:
baseline_work_schedule.Name = name
self.create_baseline_reference(work_schedule, baseline_work_schedule)
for summary_task in ifcopenshell.util.sequence.get_root_tasks(work_schedule):
- current, duplicate = ifcopenshell.api.sequence.duplicate_task(self.file, task=summary_task)
+ res = ifcopenshell.api.sequence.duplicate_task(self.file, task=summary_task)
+ assert isinstance(res, list)
+ current, duplicate = res
ifcopenshell.api.control.assign_control(
self.file, relating_control=baseline_work_schedule, related_object=duplicate[0]
)
diff --git a/src/ifcopenshell-python/ifcopenshell/api/structural/edit_structural_connection_cs.py b/src/ifcopenshell-python/ifcopenshell/api/structural/edit_structural_connection_cs.py
index 0d569c5f56..e9fcc0d660 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/structural/edit_structural_connection_cs.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/structural/edit_structural_connection_cs.py
@@ -16,42 +16,33 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see .
import ifcopenshell
+from ifcopenshell.util.shape_builder import VectorType, ifc_safe_vector_type
def edit_structural_connection_cs(
file: ifcopenshell.file,
structural_item: ifcopenshell.entity_instance,
- axis: tuple[float, float, float] = (0.0, 0.0, 1.0),
- ref_direction: tuple[float, float, float] = (1.0, 0.0, 0.0),
+ axis: VectorType = (0.0, 0.0, 1.0),
+ ref_direction: VectorType = (1.0, 0.0, 0.0),
) -> None:
"""Edits the coordinate system of a structural connection
:param structural_item: The IfcStructuralItem you want to modify.
- :type structural_item: ifcopenshell.entity_instance
:param axis: The unit Z axis vector defined as a list of 3 floats.
Defaults to (0., 0., 1.).
- :type axis: tuple[float, float, float]
:param ref_direction: The unit X axis vector defined as a list of 3
floats. Defaults to (1., 0., 0.).
- :type ref_direction: tuple[float, float, float]
:return: None
- :rtype: None
"""
- settings = {
- "structural_item": structural_item,
- "axis": axis,
- "ref_direction": ref_direction,
- }
-
- if settings["structural_item"].ConditionCoordinateSystem is None:
+ if structural_item.ConditionCoordinateSystem is None:
point = file.createIfcCartesianPoint((0.0, 0.0, 0.0))
ccs = file.createIfcAxis2Placement3D(point, None, None)
- settings["structural_item"].ConditionCoordinateSystem = ccs
+ structural_item.ConditionCoordinateSystem = ccs
- ccs = settings["structural_item"].ConditionCoordinateSystem
+ ccs = structural_item.ConditionCoordinateSystem
if ccs.Axis and len(file.get_inverse(ccs.Axis)) == 1:
file.remove(ccs.Axis)
- ccs.Axis = file.createIfcDirection(settings["axis"])
- if ccs.RefDirection and len(file.get_inverse(ccs.RefDirection)) == 1:
- file.remove(ccs.RefDirection)
- ccs.RefDirection = file.createIfcDirection(settings["ref_direction"])
+ ccs.Axis = file.create_entity("IfcDirection", ifc_safe_vector_type(axis))
+ if (prev_ref_direction := ccs.RefDirection) and len(file.get_inverse(prev_ref_direction)) == 1:
+ file.remove(prev_ref_direction)
+ ccs.RefDirection = file.create_entity("IfcDirection", ifc_safe_vector_type(ref_direction))
diff --git a/src/ifcpatch/ifcpatch/recipes/FixRevitTINs.py b/src/ifcpatch/ifcpatch/recipes/FixRevitTINs.py
index 07184d1cb2..6a52abd742 100644
--- a/src/ifcpatch/ifcpatch/recipes/FixRevitTINs.py
+++ b/src/ifcpatch/ifcpatch/recipes/FixRevitTINs.py
@@ -84,7 +84,8 @@ class Patcher:
import bonsai.tool as tool
from math import degrees
- bpy.context.scene.BIMProjectProperties.should_use_native_meshes = True
+ props = tool.Project.get_project_props()
+ props.should_use_native_meshes = True
bpy.ops.bim.load_project(filepath=self.filepath)
old_history_size = tool.Ifc.get().history_size