diff --git a/src/bonsai/bonsai/bim/module/project/data.py b/src/bonsai/bonsai/bim/module/project/data.py
index 56a77d1880..8ecf30eef5 100644
--- a/src/bonsai/bonsai/bim/module/project/data.py
+++ b/src/bonsai/bonsai/bim/module/project/data.py
@@ -170,6 +170,6 @@ class ProjectLibraryData:
class LinksData:
- linked_data = {}
+ linked_data: dict[str, Any] = {}
enable_culling = False
is_loaded = False
diff --git a/src/bonsai/bonsai/bim/module/project/decorator.py b/src/bonsai/bonsai/bim/module/project/decorator.py
index 8c2ec0e08b..cb1ee4b4bd 100644
--- a/src/bonsai/bonsai/bim/module/project/decorator.py
+++ b/src/bonsai/bonsai/bim/module/project/decorator.py
@@ -16,8 +16,6 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see .
-from typing import Union
-
import bmesh
import bpy
import gpu
@@ -54,7 +52,7 @@ class ProjectDecorator:
installed = None
@classmethod
- def install(cls, context):
+ def install(cls, context: bpy.types.Context) -> None:
if cls.installed:
cls.uninstall()
handler = cls()
@@ -99,9 +97,9 @@ class ProjectDecorator:
# general shader
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
- selected_vertices = []
- selected_edges = []
- selected_tris = []
+ selected_vertices: list[tuple[float, float, float]] = []
+ selected_edges: list[tuple[int, int]] = []
+ selected_tris: list[tuple[int, int, int]] = []
props = tool.Project.get_project_props()
try:
@@ -112,7 +110,7 @@ class ProjectDecorator:
except:
return
- root_obj: Union[bpy.types.Object, None] = props.queried_obj_root
+ root_obj = props.queried_obj_root
if root_obj and not (m := root_obj.matrix_world).is_identity:
selected_vertices = [m @ Vector(v) for v in selected_vertices]
diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py
index da3771ba9c..ea074a2e96 100644
--- a/src/bonsai/bonsai/bim/module/project/operator.py
+++ b/src/bonsai/bonsai/bim/module/project/operator.py
@@ -27,11 +27,12 @@ import traceback
from collections import defaultdict
from math import radians
from pathlib import Path
-from typing import TYPE_CHECKING, Literal, Union, get_args
+from typing import TYPE_CHECKING, Any, Literal, Union, get_args
import bpy
import ifcopenshell
import ifcopenshell.api.attribute
+import ifcopenshell.api.document
import ifcopenshell.api.nest
import ifcopenshell.api.project
import ifcopenshell.api.root
@@ -1438,8 +1439,13 @@ class LoadLink(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Load Link"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Load the selected file"
- link_index: bpy.props.IntProperty(name="Link Index")
- use_cache: bpy.props.BoolProperty(name="Use Cache", default=True)
+
+ link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration]
+ use_cache: bpy.props.BoolProperty(name="Use Cache", default=True) # pyright: ignore[reportRedeclaration]
+
+ if TYPE_CHECKING:
+ link_index: int
+ use_cache: bool
def _execute(self, context):
self.link = tool.Project.get_project_props().links[self.link_index]
@@ -1464,9 +1470,10 @@ class LoadLink(bpy.types.Operator, tool.Ifc.Operator):
empty = bpy.data.objects.new(empty_name, None)
empty.instance_type = "COLLECTION"
empty.instance_collection = collection
- empty.matrix_world = Matrix(tool.Project.calculate_link_matrix(self.link))
+ empty.matrix_world = tool.Project.calculate_link_matrix(self.link)
tool.Project.set_link_empty_handle(self.link, empty)
+ assert bpy.context.scene
bpy.context.scene.collection.objects.link(empty)
self.link.is_loaded = True
if tool.Ifc.get(): # For non-IFC projects, locking has no meaning
@@ -1635,8 +1642,16 @@ class ToggleLinkVisibility(bpy.types.Operator):
bl_label = "Toggle Link Visibility"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Toggle visibility between SOLID and WIREFRAME"
- link_index: bpy.props.IntProperty(name="Link Index")
- mode: bpy.props.EnumProperty(name="Visibility Mode", items=((i, i, "") for i in ("WIREFRAME", "VISIBLE")))
+
+ link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration]
+ mode: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
+ name="Visibility Mode",
+ items=((i, i, "") for i in ("WIREFRAME", "VISIBLE")),
+ )
+
+ if TYPE_CHECKING:
+ link_index: int
+ mode: Literal["WIREFRAME", "VISIBLE"]
def execute(self, context):
props = tool.Project.get_project_props()
@@ -1688,8 +1703,11 @@ class EnableEditingLink(bpy.types.Operator):
def execute(self, context):
link = tool.Project.get_project_props().active_link
+ assert link
link.is_editing = True
- tool.Geometry.unlock_object(tool.Project.get_link_empty_handle(link))
+ obj = tool.Project.get_link_empty_handle(link)
+ assert obj
+ tool.Geometry.unlock_object(obj)
return {"FINISHED"}
@@ -1701,9 +1719,11 @@ class DisableEditingLink(bpy.types.Operator):
def execute(self, context):
link = tool.Project.get_project_props().active_link
+ assert link
link.is_editing = False
obj = tool.Project.get_link_empty_handle(link)
- obj.matrix_world = Matrix(tool.Project.calculate_link_matrix(link))
+ assert obj
+ obj.matrix_world = tool.Project.calculate_link_matrix(link)
tool.Geometry.lock_object(obj)
return {"FINISHED"}
@@ -1716,8 +1736,10 @@ class EditLink(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
link = tool.Project.get_project_props().active_link
+ assert link
link.is_editing = False
obj = tool.Project.get_link_empty_handle(link)
+ assert obj
new_obj_matrix = obj.matrix_world
filepath = Path(tool.Ifc.resolve_uri(link.filepath))
@@ -1753,7 +1775,7 @@ class EditLink(bpy.types.Operator, tool.Ifc.Operator):
else:
link.transformation = transformation
- obj.matrix_world = Matrix(tool.Project.calculate_link_matrix(link))
+ obj.matrix_world = tool.Project.calculate_link_matrix(link)
tool.Geometry.lock_object(obj)
@@ -2270,7 +2292,8 @@ class QueryLinkedElement(bpy.types.Operator):
props = tool.Project.get_project_props()
props.queried_obj = None
- for area in bpy.context.screen.areas:
+ assert context.screen
+ for area in context.screen.areas:
if area.type == "PROPERTIES":
for region in area.regions:
if region.type == "WINDOW":
@@ -2278,6 +2301,7 @@ class QueryLinkedElement(bpy.types.Operator):
elif area.type == "VIEW_3D":
area.tag_redraw()
+ assert context.region and context.region_data
region = context.region
rv3d = context.region_data
coord = (self.mouse_x, self.mouse_y)
@@ -2294,18 +2318,20 @@ class QueryLinkedElement(bpy.types.Operator):
guid = None
guid_start_index = 0
- for i, guid_end_index in enumerate(obj["guid_ids"]):
+ guid_ids: list[int] = obj["guid_ids"]
+ for i, guid_end_index in enumerate(guid_ids):
if face_index < guid_end_index:
guid = obj["guids"][i]
props.queried_obj = obj
props.queried_obj_root = self.find_obj_root(obj, instance_matrix)
- selected_tris = []
- selected_edges = []
- vert_indices = set()
+ selected_tris: list[tuple[int, ...]] = []
+ selected_edges: list[tuple[int, ...]] = []
+ vert_indices_set: set[int] = set()
+ assert isinstance(obj.data, bpy.types.Mesh)
for polygon in obj.data.polygons[guid_start_index:guid_end_index]:
- vert_indices.update(polygon.vertices)
- vert_indices = list(vert_indices)
+ vert_indices_set.update(polygon.vertices)
+ vert_indices = list(vert_indices_set)
vert_map = {k: v for v, k in enumerate(vert_indices)}
selected_vertices = [tuple(obj.matrix_world @ obj.data.vertices[vi].co) for vi in vert_indices]
for polygon in obj.data.polygons[guid_start_index:guid_end_index]:
@@ -2319,13 +2345,14 @@ class QueryLinkedElement(bpy.types.Operator):
break
guid_start_index = guid_end_index
+ assert guid is not None
self.db = sqlite3.connect(obj["db"])
self.c = self.db.cursor()
self.c.execute(f"SELECT * FROM elements WHERE global_id = '{guid}' LIMIT 1")
element = self.c.fetchone()
- attributes = {}
+ attributes: dict[str, Any] = {}
for i, attr in enumerate(["GlobalId", "IFC Class", "Predefined Type", "Name", "Description"]):
if element[i + 1] is not None:
attributes[attr] = element[i + 1]
@@ -2415,6 +2442,7 @@ class AppendInspectedLinkedElement(AppendLibraryElement):
return {"CANCELLED"}
queried_obj = props.queried_obj
+ assert queried_obj
ifc_file = tool.Ifc.get()
linked_ifc_file: ifcopenshell.file
@@ -2683,10 +2711,12 @@ class CreateClippingPlane(bpy.types.Operator):
self.report({"INFO"}, "Maximum of six clipping planes allowed.")
return {"FINISHED"}
+ assert context.screen
for area in context.screen.areas:
if area.type == "VIEW_3D":
area.tag_redraw()
+ assert context.region and context.region_data
region = context.region
rv3d = context.region_data
if rv3d: # Called from a 3D viewport
diff --git a/src/bonsai/bonsai/bim/ui.py b/src/bonsai/bonsai/bim/ui.py
index 7bc27a20e0..1d0e8742dd 100644
--- a/src/bonsai/bonsai/bim/ui.py
+++ b/src/bonsai/bonsai/bim/ui.py
@@ -774,6 +774,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
bsdd_load_preview_dictionaries: bool
bsdd_load_inactive_dictionaries: bool
bsdd_load_test_dictionaries: bool
+ bsdd_baseurl: str
should_disable_undo_on_save: bool
should_stream: bool
should_always_cache: bool
@@ -789,6 +790,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
mass_time_units_in_wizard: bool
chain_filter_with_set_operations: bool
save_metadata_blend_file: bool
+ metadata_blend_file_suffix: str
decorator_font_scale: float
def draw(self, context: bpy.types.Context) -> None:
diff --git a/src/bonsai/bonsai/tool/project.py b/src/bonsai/bonsai/tool/project.py
index dc47b977f8..5e53223158 100644
--- a/src/bonsai/bonsai/tool/project.py
+++ b/src/bonsai/bonsai/tool/project.py
@@ -31,6 +31,7 @@ import ifcopenshell
import ifcopenshell.api.document
import ifcopenshell.util.element
import ifcopenshell.util.representation
+import ifcopenshell.util.shape_builder
import numpy as np
from ifcopenshell.api.project.append_asset import APPENDABLE_ASSET_TYPES
@@ -45,7 +46,7 @@ import bonsai.tool as tool
from bonsai.bim.ifc import IfcStore
if TYPE_CHECKING:
- from bonsai.bim.module.project.prop import BIMProjectProperties, MeasureToolSettings
+ from bonsai.bim.module.project.prop import BIMProjectProperties, MeasureToolSettings, Link
HiearchyDict = dict[ifcopenshell.entity_instance, "HiearchyDict"]
@@ -61,20 +62,20 @@ class Project(bonsai.core.tool.Project):
return scene.MeasureToolSettings # pyright: ignore[reportAttributeAccessIssue]
@classmethod
- def get_link_empty_handle(cls, link) -> bpy.types.Object | None:
+ def get_link_empty_handle(cls, link: Link) -> bpy.types.Object | None:
if tool.Ifc.get():
return tool.Ifc.get_object(tool.Ifc.get().by_id(link.ifc_definition_id))
return link.empty_handle
@classmethod
- def set_link_empty_handle(cls, link, empty: bpy.types.Object) -> None:
+ def set_link_empty_handle(cls, link: Link, empty: bpy.types.Object) -> None:
if tool.Ifc.get():
tool.Ifc.link(tool.Ifc.get().by_id(link.ifc_definition_id), empty)
else:
link.empty_handle = empty
@classmethod
- def calculate_link_matrix(cls, link) -> None:
+ def calculate_link_matrix(cls, link: Link) -> Matrix:
filepath = Path(tool.Ifc.resolve_uri(link.filepath))
with open(filepath.with_suffix(".ifc.cache.json"), "r") as f:
metadata = json.load(f)
@@ -99,7 +100,7 @@ class Project(bonsai.core.tool.Project):
rot = ifcopenshell.util.shape_builder.np_rotation_matrix(radians(-float(gprops.model_project_north)), 4, "Z")
local_matrix = rot @ np.eye(4)
local_matrix[:, 3][:3] = [float(o) for o in gprops.model_origin_si.split(",")]
- return np.linalg.inv(local_matrix) @ global_matrix
+ return Matrix(np.linalg.inv(local_matrix) @ global_matrix)
@classmethod
def append_all_types_from_template(cls, template: str) -> None:
diff --git a/src/ifcpatch/ifcpatch/recipes/ExtractPropertiesToSQLite.py b/src/ifcpatch/ifcpatch/recipes/ExtractPropertiesToSQLite.py
index 0f9dc8544d..c7ca60610b 100644
--- a/src/ifcpatch/ifcpatch/recipes/ExtractPropertiesToSQLite.py
+++ b/src/ifcpatch/ifcpatch/recipes/ExtractPropertiesToSQLite.py
@@ -17,21 +17,24 @@
# along with IfcPatch. If not, see .
+import logging
import tempfile
import ifcopenshell.util.element
+import ifcpatch
+
try:
import sqlite3
except:
print("No SQLite support")
-class Patcher:
+class Patcher(ifcpatch.BasePatcher):
def __init__(
self,
- file,
- logger,
+ file: ifcopenshell.file,
+ logger: logging.Logger | None = None,
):
"""Extracts properties and relationships from a IFC-SPF model to SQLite.
@@ -45,10 +48,11 @@ class Patcher:
result = ifcpatch.execute({"input": fn, "file": model, "recipe": "ExtractPropertiesToSQLite"})
ifcpatch.write(result, "output.sqlite")
"""
- self.file = file
- self.logger = logger
+ super().__init__(file, logger)
def patch(self):
+ import sqlite3
+
tmp = tempfile.NamedTemporaryFile(delete=False)
db_file = tmp.name
self.db = sqlite3.connect(db_file)
diff --git a/src/ifcpatch/ifcpatch/recipes/Ifc2Sql.py b/src/ifcpatch/ifcpatch/recipes/Ifc2Sql.py
index 256ffa99ce..d1405e9a33 100644
--- a/src/ifcpatch/ifcpatch/recipes/Ifc2Sql.py
+++ b/src/ifcpatch/ifcpatch/recipes/Ifc2Sql.py
@@ -125,7 +125,6 @@ class Patcher(ifcpatch.BasePatcher):
)
"""
super().__init__(file, logger)
- self.logger = logger
self.sql_type: Literal["sqlite", "mysql"] = sql_type.lower()
self.host = host
self.username = username