From 0cb6d9de72c064b690d02cf210031aa68c97d8fa Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 28 Jul 2025 11:48:20 +0500 Subject: [PATCH] typing --- nix/build-all.py | 4 +-- src/bonsai/bonsai/bim/module/demo/operator.py | 6 ++-- src/bonsai/bonsai/bim/module/demo/prop.py | 10 ++++++ src/bonsai/bonsai/bim/module/demo/ui.py | 5 +-- .../bonsai/bim/module/model/decorator.py | 8 ++--- .../bonsai/bim/module/model/polyline.py | 2 +- src/bonsai/bonsai/tool/demo.py | 34 ++++++++++++++----- src/bonsai/bonsai/tool/model.py | 6 ++++ src/bonsai/test/tool/test_demo.py | 20 ++++++----- src/ifcpatch/ifcpatch/recipes/AGS2IFC.py | 2 ++ 10 files changed, 69 insertions(+), 28 deletions(-) diff --git a/nix/build-all.py b/nix/build-all.py index ae84c1051f..8e9f9d5258 100644 --- a/nix/build-all.py +++ b/nix/build-all.py @@ -366,7 +366,7 @@ BOOST_LOCATION = f"https://github.com/boostorg/boost/releases/download/boost-{BO # Helper functions -def run_autoconf(arg1, configure_args, cwd): +def run_autoconf(arg1: str, configure_args: "list[str]", cwd: str) -> None: configure_path = os.path.realpath(os.path.join(cwd, "..", "configure")) if not os.path.exists(configure_path): run( @@ -392,7 +392,7 @@ def run_autoconf(arg1, configure_args, cwd): ) -def run_cmake(arg1, cmake_args, cmake_dir=None, cwd=None): +def run_cmake(arg1, cmake_args: "list[str]", cmake_dir: Union[str, None] = None, cwd: Union[str, None] = None): if cmake_dir is None: P = ".." else: diff --git a/src/bonsai/bonsai/bim/module/demo/operator.py b/src/bonsai/bonsai/bim/module/demo/operator.py index d5b34716f1..0621f542d2 100644 --- a/src/bonsai/bonsai/bim/module/demo/operator.py +++ b/src/bonsai/bonsai/bim/module/demo/operator.py @@ -75,7 +75,8 @@ class DemonstrateRenameProject(bpy.types.Operator, tool.Ifc.Operator): # properties. Generally, the inputs to the core function will come from # properties (such as an input field) or data from the scene (like the # actively selected object). - core.demonstrate_rename_project(tool.Ifc, tool.Demo, name=bpy.context.scene.BIMDemoProperties.name) + props = tool.Demo.get_demo_props() + core.demonstrate_rename_project(tool.Ifc, tool.Demo, name=props.name) class SendWebUiDemoMessage(bpy.types.Operator): @@ -89,5 +90,6 @@ class SendWebUiDemoMessage(bpy.types.Operator): # and calling the operator connect_websocket_server if we aren't connected to a Web UI if not context.scene.WebProperties.is_connected: bpy.ops.bim.connect_websocket_server(page="demo") - core.send_webui_demo_message(tool.Web, message=bpy.context.scene.BIMDemoProperties.webui_message) + props = tool.Demo.get_demo_props() + core.send_webui_demo_message(tool.Web, message=props.webui_message) return {"FINISHED"} diff --git a/src/bonsai/bonsai/bim/module/demo/prop.py b/src/bonsai/bonsai/bim/module/demo/prop.py index 6c6141a66c..83df8ccfda 100644 --- a/src/bonsai/bonsai/bim/module/demo/prop.py +++ b/src/bonsai/bonsai/bim/module/demo/prop.py @@ -30,6 +30,8 @@ # property. Properties are stored in the .blend file, so when your user closes # their Blender session, and reopens it, things are how they left it. +from typing import TYPE_CHECKING + import bpy from bpy.types import PropertyGroup @@ -60,3 +62,11 @@ class BIMDemoProperties(PropertyGroup): message: StringProperty(name="Message") show_hints: BoolProperty(name="Show Hints", default=False) webui_message: StringProperty(name="Web UI Message", default="Hello, Web UI!") + + # Type checking block is needed + # as type checker can't understand Blender props types by default. + if TYPE_CHECKING: + name: str + message: str + show_hints: bool + webui_message: str diff --git a/src/bonsai/bonsai/bim/module/demo/ui.py b/src/bonsai/bonsai/bim/module/demo/ui.py index 5d6c62ac7e..6005814cfa 100644 --- a/src/bonsai/bonsai/bim/module/demo/ui.py +++ b/src/bonsai/bonsai/bim/module/demo/ui.py @@ -27,6 +27,7 @@ # panels, buttons, labels, and input fields are laid out. import bpy +import bonsai.tool as tool from bonsai.bim.module.demo.data import DemoData @@ -70,7 +71,7 @@ class BIM_PT_demo(bpy.types.Panel): # Interface panels often show properties. For convenience, define where # the properties are stored for the module. - self.props = context.scene.BIMDemoProperties + self.props = tool.Demo.get_demo_props() # This defines a new "row" in our layout. When a new row is defined, the # things on that row, like buttons, labels, and input fields, show on a @@ -145,7 +146,7 @@ class BIM_PT_webui_demo(bpy.types.Panel): bl_parent_id = "BIM_PT_demo" def draw(self, context): - self.props = context.scene.BIMDemoProperties + self.props = tool.Demo.get_demo_props() row = self.layout.row() # we create an input field for the property webui_message diff --git a/src/bonsai/bonsai/bim/module/model/decorator.py b/src/bonsai/bonsai/bim/module/model/decorator.py index 73b97ba03f..de47c37d1d 100644 --- a/src/bonsai/bonsai/bim/module/model/decorator.py +++ b/src/bonsai/bonsai/bim/module/model/decorator.py @@ -893,7 +893,7 @@ class ProductDecorator: @classmethod def uninstall(cls): - props = bpy.context.scene.BIMProductPreviewProperties # updated by model/polyline.py + props = tool.Model.get_product_preview_props() # updated by model/polyline.py props.verts.clear() props.edges.clear() props.tris.clear() @@ -912,9 +912,9 @@ class ProductDecorator: shader.uniform_float("color", color) batch.draw(shader) - def get_product_preview_data(self, context): - props = context.scene.BIMProductPreviewProperties - data = {} + def get_product_preview_data(self, context) -> dict[str, Any]: + props = tool.Model.get_product_preview_props() + data: dict[str, Any] = {} data["verts"] = [(*v.value_3d,) for v in props.verts] data["edges"] = [(int(e.value_2d[0]), int(e.value_2d[1])) for e in props.edges] data["tris"] = [(int(t.value_3d[0]), int(t.value_3d[1]), int(t.value_3d[2])) for t in props.tris] diff --git a/src/bonsai/bonsai/bim/module/model/polyline.py b/src/bonsai/bonsai/bim/module/model/polyline.py index 11c30ee8c2..0d5314aba9 100644 --- a/src/bonsai/bonsai/bim/module/model/polyline.py +++ b/src/bonsai/bonsai/bim/module/model/polyline.py @@ -962,7 +962,7 @@ class PolylineOperator: data = get_generic_product_preview_data(context, relating_type) # Update properties so it can be used by the decorator - props = context.scene.BIMProductPreviewProperties + props = tool.Model.get_product_preview_props() props.verts.clear() props.edges.clear() props.tris.clear() diff --git a/src/bonsai/bonsai/tool/demo.py b/src/bonsai/bonsai/tool/demo.py index 90b79ef8bd..745fcf914c 100644 --- a/src/bonsai/bonsai/tool/demo.py +++ b/src/bonsai/bonsai/tool/demo.py @@ -29,16 +29,28 @@ # Blender's property systems, IFC's data structures, the filesystem, geometry # processing, and more. +from __future__ import annotations + import bpy +import ifcopenshell import bonsai.core.tool import bonsai.tool as tool +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from bonsai.bim.module.demo.prop import BIMDemoProperties # There is always one class in each tool file, which implements the interface # defined by `core/tool.py`. class Demo(bonsai.core.tool.Demo): @classmethod - def clear_name_field(cls): + def get_demo_props(cls) -> BIMDemoProperties: + assert (scene := bpy.context.scene) + return scene.BIMDemoProperties # pyright: ignore[reportAttributeAccessIssue] + + @classmethod + def clear_name_field(cls) -> None: # In this concrete implementation, we see that "clear name field" # actually translates to "set this Blender string property to empty # string". In this case, it's pretty simple - but even simple scenarios @@ -47,20 +59,24 @@ class Demo(bonsai.core.tool.Demo): # implementations separately from control flow. It also makes it easy to # refactor and share functions, where every tool function is captured by # a function name that describes its intention. - bpy.context.scene.BIMDemoProperties.name = "" + props = cls.get_demo_props() + props.name = "" @classmethod - def get_project(cls): + def get_project(cls) -> ifcopenshell.entity_instance: return tool.Ifc.get().by_type("IfcProject")[0] @classmethod - def hide_user_hints(cls): - bpy.context.scene.BIMDemoProperties.show_hints = False + def hide_user_hints(cls) -> None: + props = cls.get_demo_props() + props.show_hints = False @classmethod - def set_message(cls, message): - bpy.context.scene.BIMDemoProperties.message = message + def set_message(cls, message) -> None: + props = cls.get_demo_props() + props.message = message @classmethod - def show_user_hints(cls): - bpy.context.scene.BIMDemoProperties.show_hints = True + def show_user_hints(cls) -> None: + props = cls.get_demo_props() + props.show_hints = True diff --git a/src/bonsai/bonsai/tool/model.py b/src/bonsai/bonsai/tool/model.py index 9b51b31581..96dd8d4f38 100644 --- a/src/bonsai/bonsai/tool/model.py +++ b/src/bonsai/bonsai/tool/model.py @@ -66,6 +66,7 @@ if TYPE_CHECKING: BIMRailingProperties, BIMExternalParametricGeometryProperties, BIMPolylineProperties, + BIMProductPreviewProperties, ) @@ -107,6 +108,11 @@ class Model(bonsai.core.tool.Model): assert (scene := bpy.context.scene) return scene.BIMPolylineProperties # pyright: ignore[reportAttributeAccessIssue] + @classmethod + def get_product_preview_props(cls) -> BIMProductPreviewProperties: + assert (scene := bpy.context.scene) + return scene.BIMProductPreviewProperties # pyright: ignore[reportAttributeAccessIssue] + @classmethod def convert_si_to_unit(cls, value: T) -> T: if isinstance(value, (tuple, list)): diff --git a/src/bonsai/test/tool/test_demo.py b/src/bonsai/test/tool/test_demo.py index 0f0f9da6e0..ba0c431a90 100644 --- a/src/bonsai/test/tool/test_demo.py +++ b/src/bonsai/test/tool/test_demo.py @@ -55,9 +55,10 @@ class TestImplementsTool(NewFile): # these tests. class TestClearNameField(NewFile): def test_run(self): - bpy.context.scene.BIMDemoProperties.name = "name" + props = tool.Demo.get_demo_props() + props.name = "name" subject.clear_name_field() - assert bpy.context.scene.BIMDemoProperties.name == "" + assert props.name == "" class TestGetProject(NewFile): @@ -73,22 +74,25 @@ class TestGetProject(NewFile): class TestHideUserHints(NewFile): def test_run(self): - bpy.context.scene.BIMDemoProperties.show_hints = True + props = tool.Demo.get_demo_props() + props.show_hints = True subject.hide_user_hints() - assert bpy.context.scene.BIMDemoProperties.show_hints == False + assert props.show_hints == False class TestSetMessage(NewFile): def test_run(self): - bpy.context.scene.BIMDemoProperties.message = "" + props = tool.Demo.get_demo_props() + props.message = "" subject.set_message("message") - assert bpy.context.scene.BIMDemoProperties.message == "message" + assert props.message == "message" class TestShowUserHints(NewFile): def test_run(self): - bpy.context.scene.BIMDemoProperties.show_hints = False + props = tool.Demo.get_demo_props() + props.show_hints = False subject.show_user_hints() - assert bpy.context.scene.BIMDemoProperties.show_hints == True + assert props.show_hints == True """ diff --git a/src/ifcpatch/ifcpatch/recipes/AGS2IFC.py b/src/ifcpatch/ifcpatch/recipes/AGS2IFC.py index 16f5350fac..ef8fc026da 100644 --- a/src/ifcpatch/ifcpatch/recipes/AGS2IFC.py +++ b/src/ifcpatch/ifcpatch/recipes/AGS2IFC.py @@ -21,6 +21,7 @@ import os import csv import numpy as np import ifcopenshell +import ifcopenshell.api.pset import ifcopenshell.api.root import ifcopenshell.api.unit import ifcopenshell.api.style @@ -30,6 +31,7 @@ import ifcopenshell.api.project import ifcopenshell.api.spatial import ifcopenshell.api.geometry import ifcopenshell.api.aggregate +import ifcopenshell.util.shape_builder from itertools import cycle from ifcopenshell.util.shape_builder import V