diff --git a/src/bonsai/bonsai/bim/handler.py b/src/bonsai/bonsai/bim/handler.py
index d0750b36c7..7b10f8c3fd 100644
--- a/src/bonsai/bonsai/bim/handler.py
+++ b/src/bonsai/bonsai/bim/handler.py
@@ -224,7 +224,7 @@ def refresh_ui_data():
props = tool.Drawing.get_document_props()
props.should_draw_decorations = props.should_draw_decorations
- if bpy.context.scene.WebProperties.is_connected:
+ if tool.Web.get_web_props().is_connected:
tool.Web.send_webui_data()
diff --git a/src/bonsai/bonsai/bim/import_ifc.py b/src/bonsai/bonsai/bim/import_ifc.py
index e7dd8ab8b9..9e1170f98c 100644
--- a/src/bonsai/bonsai/bim/import_ifc.py
+++ b/src/bonsai/bonsai/bim/import_ifc.py
@@ -884,7 +884,8 @@ class IfcImporter:
for material in self.material_creator.materials.values():
bpy.data.materials.remove(material)
- def add_project_to_scene(self):
+ def add_project_to_scene(self) -> None:
+ assert bpy.context.scene
try:
bpy.context.scene.collection.children.link(self.project["blender"])
except:
@@ -909,6 +910,7 @@ class IfcImporter:
bpy.ops.mesh.normals_make_consistent()
bpy.ops.object.editmode_toggle()
+ assert bpy.context.view_layer
bpy.context.view_layer.objects.active = last_obj
IfcStore.edited_objs.clear()
@@ -927,6 +929,7 @@ class IfcImporter:
if not (assignment := self.file.by_type("IfcProject")[0].UnitsInContext):
return # Geometry is optional in IFC
props = tool.Blender.get_bim_props()
+ assert bpy.context.scene
for unit in assignment.Units:
if unit.is_a("IfcNamedUnit") and unit.UnitType == "LENGTHUNIT":
if unit.is_a("IfcSIUnit"):
@@ -1065,11 +1068,11 @@ class IfcImporter:
def create_mesh(
self,
element: ifcopenshell.entity_instance,
- shape: Union[ifcopenshell.geom.ShapeElementType, ifcopenshell.geom.ShapeType],
+ shape: Union[W.Triangulation, W.TriangulationElement],
cartesian_point_offset: Union[npt.NDArray[np.float64], Literal[False]] = None,
) -> Union[bpy.types.Mesh, None]:
try:
- if isinstance(shape, ifcopenshell.geom.ShapeElementType):
+ if isinstance(shape, W.TriangulationElement):
# shape is ShapeElementType
geometry = shape.geometry
else:
diff --git a/src/bonsai/bonsai/bim/module/csv/operator.py b/src/bonsai/bonsai/bim/module/csv/operator.py
index 618c022cf4..34e20f6d9c 100644
--- a/src/bonsai/bonsai/bim/module/csv/operator.py
+++ b/src/bonsai/bonsai/bim/module/csv/operator.py
@@ -269,7 +269,7 @@ class ExportIfcCsv(bpy.types.Operator, ExportHelper):
schedule_creator = scheduler.Scheduler()
schedule_creator.schedule(self.filepath, tool.Drawing.get_path_with_ext(self.filepath, "svg"))
if props.format == "web":
- if not context.scene.WebProperties.is_connected:
+ if not tool.Web.get_web_props().is_connected:
bpy.ops.bim.connect_websocket_server()
df = ifc_csv.dataframe
assert df is not None
diff --git a/src/bonsai/bonsai/bim/module/demo/operator.py b/src/bonsai/bonsai/bim/module/demo/operator.py
index 0621f542d2..001c8de692 100644
--- a/src/bonsai/bonsai/bim/module/demo/operator.py
+++ b/src/bonsai/bonsai/bim/module/demo/operator.py
@@ -88,7 +88,7 @@ class SendWebUiDemoMessage(bpy.types.Operator):
# First, we need to make sure that we are connected to the Web UI
# We do that by checking the Web module properties' is_connected prop
# and calling the operator connect_websocket_server if we aren't connected to a Web UI
- if not context.scene.WebProperties.is_connected:
+ if not tool.Web.get_web_props().is_connected:
bpy.ops.bim.connect_websocket_server(page="demo")
props = tool.Demo.get_demo_props()
core.send_webui_demo_message(tool.Web, message=props.webui_message)
diff --git a/src/bonsai/bonsai/bim/module/drawing/helper.py b/src/bonsai/bonsai/bim/module/drawing/helper.py
index ad6bfe2542..6999c8536e 100644
--- a/src/bonsai/bonsai/bim/module/drawing/helper.py
+++ b/src/bonsai/bonsai/bim/module/drawing/helper.py
@@ -134,6 +134,7 @@ def format_distance(
custom_unit=None,
):
# Get Blender Scene Unit Settings
+ assert bpy.context.scene
unit_scale = bpy.context.scene.unit_settings.scale_length
unit_system = bpy.context.scene.unit_settings.system
unit_length = bpy.context.scene.unit_settings.length_unit
diff --git a/src/bonsai/bonsai/bim/module/drawing/operator.py b/src/bonsai/bonsai/bim/module/drawing/operator.py
index 8f8e094e7e..ed4479dddb 100644
--- a/src/bonsai/bonsai/bim/module/drawing/operator.py
+++ b/src/bonsai/bonsai/bim/module/drawing/operator.py
@@ -3735,7 +3735,7 @@ class OpenDocumentationWebUi(bpy.types.Operator):
bl_description = "Open the documentation web UI page"
def execute(self, context):
- if not context.scene.WebProperties.is_connected:
+ if not tool.Web.get_web_props().is_connected:
bpy.ops.bim.connect_websocket_server(page="documentation")
else:
bpy.ops.bim.open_web_browser(page="documentation")
diff --git a/src/bonsai/bonsai/bim/module/web/operator.py b/src/bonsai/bonsai/bim/module/web/operator.py
index b3cc3c8264..1c8bb21e9b 100644
--- a/src/bonsai/bonsai/bim/module/web/operator.py
+++ b/src/bonsai/bonsai/bim/module/web/operator.py
@@ -29,11 +29,12 @@ class ConnectToWebsocketServer(bpy.types.Operator):
page: bpy.props.StringProperty(default="")
def execute(self, context):
- port = context.scene.WebProperties.webserver_port
+ props = tool.Web.get_web_props()
+ port = props.webserver_port
if port == 0:
- context.scene.WebProperties.webserver_port = core.generate_port_number(tool.Web)
+ props.webserver_port = core.generate_port_number(tool.Web)
- port = context.scene.WebProperties.webserver_port
+ port = props.webserver_port
core.connect_websocket_server(tool.Web, port, self.page)
return {"FINISHED"}
@@ -65,6 +66,6 @@ class OpenWebBrowser(bpy.types.Operator):
page: bpy.props.StringProperty(default="")
def execute(self, context):
- port = context.scene.WebProperties.webserver_port
+ port = tool.Web.get_web_props().webserver_port
core.open_web_browser(tool.Web, port, self.page)
return {"FINISHED"}
diff --git a/src/bonsai/bonsai/bim/module/web/prop.py b/src/bonsai/bonsai/bim/module/web/prop.py
index cecd3c9311..66ffdd4995 100644
--- a/src/bonsai/bonsai/bim/module/web/prop.py
+++ b/src/bonsai/bonsai/bim/module/web/prop.py
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see .
+from typing import TYPE_CHECKING
import bpy
from bpy.types import PropertyGroup
from bpy.props import (
@@ -31,6 +32,21 @@ from bpy.props import (
class WebProperties(PropertyGroup):
- webserver_port: IntProperty(name="Webserver Port", min=0, max=65535)
- is_running: BoolProperty(name="Webserver Running Status", default=False)
- is_connected: BoolProperty(name="Connection Status", default=False)
+ webserver_port: IntProperty( # pyright: ignore[reportRedeclaration]
+ name="Webserver Port",
+ min=0,
+ max=65535,
+ )
+ is_running: BoolProperty( # pyright: ignore[reportRedeclaration]
+ name="Webserver Running Status",
+ default=False,
+ )
+ is_connected: BoolProperty( # pyright: ignore[reportRedeclaration]
+ name="Connection Status",
+ default=False,
+ )
+
+ if TYPE_CHECKING:
+ webserver_port: int
+ is_running: bool
+ is_connected: bool
diff --git a/src/bonsai/bonsai/bim/module/web/ui.py b/src/bonsai/bonsai/bim/module/web/ui.py
index 7af5cf84f7..0a6fca55ae 100644
--- a/src/bonsai/bonsai/bim/module/web/ui.py
+++ b/src/bonsai/bonsai/bim/module/web/ui.py
@@ -17,6 +17,7 @@
# along with Bonsai. If not, see .
from bpy.types import Panel
+import bonsai.tool as tool
from bonsai.bim.module.web.data import WebData
@@ -34,10 +35,10 @@ class BIM_PT_webui(Panel):
if not WebData.is_loaded:
WebData.load()
+ assert self.layout
layout = self.layout
- scene = context.scene
- props = scene.WebProperties
+ props = tool.Web.get_web_props()
row = layout.row()
row.prop(props, "webserver_port", text="Websocket server Port")
diff --git a/src/bonsai/bonsai/tool/cost.py b/src/bonsai/bonsai/tool/cost.py
index 7e1a3f7d74..c65b2a29e2 100644
--- a/src/bonsai/bonsai/tool/cost.py
+++ b/src/bonsai/bonsai/tool/cost.py
@@ -1085,8 +1085,8 @@ class Cost(bonsai.core.tool.Cost):
return {"id": unit.id(), "name": unit.Currency}
@classmethod
- def generate_cost_schedule_browser(cls, cost_chedule) -> None:
- if not bpy.context.scene.WebProperties.is_connected:
+ def generate_cost_schedule_browser(cls, cost_chedule: ifcopenshell.entity_instance) -> None:
+ if not tool.Web.get_web_props().is_connected:
bpy.ops.bim.connect_websocket_server(page="costing")
tool.Web.load_cost_schedule_web_ui(cost_chedule)
diff --git a/src/bonsai/bonsai/tool/geometry.py b/src/bonsai/bonsai/tool/geometry.py
index 3b632c28db..92814b4c03 100644
--- a/src/bonsai/bonsai/tool/geometry.py
+++ b/src/bonsai/bonsai/tool/geometry.py
@@ -37,6 +37,7 @@ import ifcopenshell.api.root
import ifcopenshell.api.style
import ifcopenshell.geom
import ifcopenshell.guid
+import ifcopenshell.ifcopenshell_wrapper as W
import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.representation
@@ -883,6 +884,7 @@ class Geometry(bonsai.core.tool.Geometry):
if iterator and iterator.initialize():
while True:
shape = iterator.get()
+ assert isinstance(shape, W.TriangulationElement)
element = tool.Ifc.get().by_id(shape.id)
if obj := tool.Ifc.get_object(element):
# It's possible that there will be multiple shapes for the same context,
diff --git a/src/bonsai/bonsai/tool/loader.py b/src/bonsai/bonsai/tool/loader.py
index bc04415f19..f748c88616 100644
--- a/src/bonsai/bonsai/tool/loader.py
+++ b/src/bonsai/bonsai/tool/loader.py
@@ -23,6 +23,7 @@ import bpy
import bmesh
import logging
import ifcopenshell.geom
+import ifcopenshell.ifcopenshell_wrapper as W
import ifcopenshell.util.element
import ifcopenshell.util.geolocation
import ifcopenshell.util.placement
@@ -814,7 +815,7 @@ class Loader(bonsai.core.tool.Loader):
cls,
element: ifcopenshell.entity_instance,
representation: ifcopenshell.entity_instance,
- shape: ifcopenshell.geom.ShapeElementType,
+ shape: W.TriangulationElement,
) -> bpy.types.Camera:
"""Create camera data.
@@ -968,7 +969,7 @@ class Loader(bonsai.core.tool.Loader):
@classmethod
def convert_geometry_to_mesh(
cls,
- geometry: ifcopenshell.geom.ShapeType,
+ geometry: W.Triangulation,
mesh: bpy.types.Mesh,
verts: Optional[npt.NDArray[np.float64]] = None,
*,
diff --git a/src/bonsai/bonsai/tool/model.py b/src/bonsai/bonsai/tool/model.py
index 96dd8d4f38..fed5d918a2 100644
--- a/src/bonsai/bonsai/tool/model.py
+++ b/src/bonsai/bonsai/tool/model.py
@@ -30,6 +30,7 @@ import ifcopenshell.api.geometry
import ifcopenshell.api.grid
import ifcopenshell.api.pset
import ifcopenshell.geom
+import ifcopenshell.ifcopenshell_wrapper as W
import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.representation
@@ -1864,6 +1865,7 @@ class Model(bonsai.core.tool.Model):
polygons = {}
for curve in curves:
geometry = ifcopenshell.geom.create_shape(settings, curve)
+ assert isinstance(geometry, W.Triangulation)
v = ifcopenshell.util.shape.get_vertices(geometry, is_2d=True)
v = np.round(v, 4) # Round to nearest 0.1mm, otherwise things like circles don't polygonise reliably
edges = ifcopenshell.util.shape.get_edges(geometry)
diff --git a/src/bonsai/bonsai/tool/profile.py b/src/bonsai/bonsai/tool/profile.py
index 8e467dd182..cfb36a06ab 100644
--- a/src/bonsai/bonsai/tool/profile.py
+++ b/src/bonsai/bonsai/tool/profile.py
@@ -21,6 +21,7 @@ import bpy
import ifcopenshell
import ifcopenshell.api.profile
import ifcopenshell.geom
+import ifcopenshell.ifcopenshell_wrapper as W
import ifcopenshell.util.element
import ifcopenshell.util.unit
import ifcopenshell.util.placement
@@ -52,6 +53,7 @@ class Profile(bonsai.core.tool.Profile):
settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
shape = ifcopenshell.geom.create_shape(settings, profile)
+ assert isinstance(shape, W.Triangulation)
verts = ifcopenshell.util.shape.get_vertices(shape)
if verts.size == 0:
raise RuntimeError(f"Profile shape has no vertices, it probably is invalid: '{profile}'.")
diff --git a/src/bonsai/bonsai/tool/sequence.py b/src/bonsai/bonsai/tool/sequence.py
index 1f361cf704..284a919557 100644
--- a/src/bonsai/bonsai/tool/sequence.py
+++ b/src/bonsai/bonsai/tool/sequence.py
@@ -1650,7 +1650,7 @@ class Sequence(bonsai.core.tool.Sequence):
def generate_gantt_browser_chart(
cls, task_json: list[dict[str, Any]], work_schedule: ifcopenshell.entity_instance
) -> None:
- if not bpy.context.scene.WebProperties.is_connected:
+ if not tool.Web.get_web_props().is_connected:
bpy.ops.bim.connect_websocket_server(page="sequencing")
gantt_data = {"tasks": task_json, "work_schedule": work_schedule.get_info(recursive=True)}
tool.Web.send_webui_data(data=gantt_data, data_key="gantt_data", event="gantt_data")
diff --git a/src/bonsai/bonsai/tool/web.py b/src/bonsai/bonsai/tool/web.py
index c89ca76eae..9a1924aa4e 100644
--- a/src/bonsai/bonsai/tool/web.py
+++ b/src/bonsai/bonsai/tool/web.py
@@ -27,7 +27,7 @@ import bonsai.tool as tool
import ifcopenshell.api.sequence
import ifcopenshell.api.classification
import ifcopenshell.api.cost
-from typing import Any, Optional
+from typing import Any, Optional, TYPE_CHECKING
import time
import socket
import sys
@@ -47,6 +47,9 @@ import bonsai.core.cost
from ifc5d.ifc2json import ifc5D2json
from bonsai.bim.ifc import IfcStore
+if TYPE_CHECKING:
+ from bonsai.bim.module.web.prop import WebProperties
+
sio = None
ws_process = None
ws_thread = None
@@ -62,6 +65,11 @@ IFC_TASK_ATTRIBUTE_MAP = {
class Web(bonsai.core.tool.Web):
+ @classmethod
+ def get_web_props(cls) -> WebProperties:
+ assert (scene := bpy.context.scene)
+ return scene.BIMProperties # pyright: ignore[reportAttributeAccessIssue]
+
@classmethod
def generate_port_number(cls) -> int:
"""
@@ -151,7 +159,7 @@ class Web(bonsai.core.tool.Web):
"""
global ws_thread, sio
- if bpy.context.scene.WebProperties.is_connected:
+ if tool.Web.get_web_props().is_connected:
print(f"Already connected to websocket server on port: {port}")
return
@@ -200,7 +208,7 @@ class Web(bonsai.core.tool.Web):
print("No Websocket server running")
return
- if bpy.context.scene.WebProperties.is_connected:
+ if tool.Web.get_web_props().is_connected:
cls.disconnect_websocket_server()
# sleep(0.5)
@@ -282,7 +290,7 @@ class Web(bonsai.core.tool.Web):
if data is not None:
payload[data_key] = data
- if ws_thread is not None and bpy.context.scene.WebProperties.is_connected:
+ if ws_thread is not None and tool.Web.get_web_props().is_connected:
ws_thread.run_coro(cls.sio_send(payload, event, namespace))
@classmethod
@@ -295,7 +303,7 @@ class Web(bonsai.core.tool.Web):
Returns:
(Optional[float]): Returns None if the WebProperties.is_connected is False, otherwise returns 1.0 to continue the timer.
"""
- if not bpy.context.scene.WebProperties.is_connected:
+ if not tool.Web.get_web_props().is_connected:
with web_operator_queue.mutex:
web_operator_queue.queue.clear()
return None # unregister timer if not connected
@@ -879,11 +887,13 @@ class Web(bonsai.core.tool.Web):
@classmethod
def set_is_running(cls, is_running: bool) -> None:
- bpy.context.scene.WebProperties.is_running = is_running
+ props = tool.Web.get_web_props()
+ props.is_running = is_running
@classmethod
def set_is_connected(cls, is_connected: bool) -> None:
- bpy.context.scene.WebProperties.is_connected = is_connected
+ props = tool.Web.get_web_props()
+ props.is_connected = is_connected
class AsyncioThread(threading.Thread):
diff --git a/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi b/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi
index 73d22ce836..659a062cfd 100644
--- a/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi
+++ b/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi
@@ -94,6 +94,7 @@ class FileDescription(HeaderEntity):
class FileName(HeaderEntity):
name: str
+ """Updated automatically only on ``file.write``."""
time_stamp: str
author: tuple[str, ...]
organization: tuple[str, ...]
@@ -358,7 +359,7 @@ class Iterator:
"""
...
- def set_cache(self, cache: GeometrySerializer) -> None: ...
+ def set_cache(self, cache: Union[GeometrySerializer, None]) -> None: ...
def unit_magnitude(self): ...
def unit_name(self): ...
@@ -1351,8 +1352,8 @@ class style(item):
- IFC style id if style assigned to the representation items directly
or through material with a style;
- IFC material id if both true:
- - element has a material without a style;
- - there are parts of the geometry that has no other style assigned to them;
+ - element has a material without a style;
+ - there are parts of the geometry that has no other style assigned to them;
- -1 in case if there is no material;
- 0 in case if there are default materials used.
"""