From 53b26a9b66f51097b5a504d08f9d18d76f86b5c4 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 11 Apr 2025 10:57:06 +0500 Subject: [PATCH] typing --- .../bonsai/bim/module/attribute/operator.py | 56 +++++++++++-------- .../bonsai/bim/module/attribute/prop.py | 5 ++ src/bonsai/bonsai/bim/module/attribute/ui.py | 6 +- src/bonsai/bonsai/tool/blender.py | 5 ++ src/ifcpatch/ifcpatch/__init__.py | 2 +- .../ifcpatch/recipes/ExtractElements.py | 5 +- .../ifcpatch/recipes/MergeDuplicateTypes.py | 8 +-- 7 files changed, 54 insertions(+), 33 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/attribute/operator.py b/src/bonsai/bonsai/bim/module/attribute/operator.py index 2b279895e3..5f5bf9f8c2 100644 --- a/src/bonsai/bonsai/bim/module/attribute/operator.py +++ b/src/bonsai/bonsai/bim/module/attribute/operator.py @@ -26,30 +26,42 @@ import bonsai.bim.helper import bonsai.tool as tool import bonsai.core.attribute as core import bonsai.core.spatial +from typing import TYPE_CHECKING -def get_objs_for_operation(operator_properties, context): +def get_objs_for_operation( + operator_properties: "AttributesOperator", context: bpy.types.Context +) -> list[bpy.types.Object]: if operator_properties.obj: return [bpy.data.objects[operator_properties.obj]] if operator_properties.mass_operation: return context.selected_objects[:] - return [context.active_object] + obj = context.active_object + assert obj + return [obj] -class EnableEditingAttributes(bpy.types.Operator): - bl_idname = "bim.enable_editing_attributes" - bl_label = "Enable Editing Attributes" - bl_description = "ALT + Left Click to enable editing attributes on all selected objects" - bl_options = {"REGISTER", "UNDO"} +class AttributesOperator: obj: bpy.props.StringProperty(options={"SKIP_SAVE"}) mass_operation: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) + if TYPE_CHECKING: + obj: str + mass_operation: bool + def invoke(self, context, event): self.mass_operation = event.alt return self.execute(context) - def enable_editing_attribute_on_obj(self, obj): - props = obj.BIMAttributeProperties + +class EnableEditingAttributes(bpy.types.Operator, AttributesOperator): + bl_idname = "bim.enable_editing_attributes" + bl_label = "Enable Editing Attributes" + bl_description = "ALT + Left Click to enable editing attributes on all selected objects" + bl_options = {"REGISTER", "UNDO"} + + def enable_editing_attribute_on_obj(self, obj: bpy.types.Object) -> None: + props = tool.Blender.get_object_attribute_props(obj) props.attributes.clear() element = tool.Ifc.get_entity(obj) @@ -87,20 +99,14 @@ class EnableEditingAttributes(bpy.types.Operator): return {"FINISHED"} -class DisableEditingAttributes(bpy.types.Operator): +class DisableEditingAttributes(bpy.types.Operator, AttributesOperator): bl_idname = "bim.disable_editing_attributes" bl_label = "Disable Editing Attributes" bl_description = "ALT + Left Click to disable editing attributes on all selected objects" bl_options = {"REGISTER", "UNDO"} - obj: bpy.props.StringProperty(options={"SKIP_SAVE"}) - mass_operation: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) - def invoke(self, context, event): - self.mass_operation = event.alt - return self.execute(context) - - def disable_editing_attributes_on_obj(self, obj): - props = obj.BIMAttributeProperties + def disable_editing_attributes_on_obj(self, obj: bpy.types.Object) -> None: + props = tool.Blender.get_object_attribute_props(obj) props.is_editing_attributes = False def execute(self, context): @@ -118,7 +124,7 @@ class EditAttributes(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): self.file = tool.Ifc.get() obj = tool.Blender.get_active_object(is_selected=False) - if not (element := tool.Ifc.get_entity(obj)): + if not obj or not (element := tool.Ifc.get_entity(obj)): return def callback(attributes, prop): @@ -130,7 +136,7 @@ class EditAttributes(bpy.types.Operator, tool.Ifc.Operator): attributes[prop.name] = None return True - props = obj.BIMAttributeProperties + props = tool.Blender.get_object_attribute_props(obj) attributes = bonsai.bim.helper.export_attributes(props.attributes, callback=callback) ifcopenshell.api.attribute.edit_attributes(self.file, product=element, attributes=attributes) @@ -165,13 +171,12 @@ class GenerateGlobalId(bpy.types.Operator, tool.Ifc.Operator): element.GlobalId = ifcopenshell.guid.new() obj = context.active_object - if not obj or not obj.BIMAttributeProperties.is_editing_attributes: + if not obj or not (props := tool.Blender.get_object_attribute_props(obj)).is_editing_attributes: return {"FINISHED"} - props = obj.BIMAttributeProperties element = tool.Ifc.get_entity(obj) - if not element.is_a("IfcRoot"): + if not element or not element.is_a("IfcRoot"): return {"FINISHED"} if self.use_selected and obj in context.selected_objects: @@ -191,7 +196,10 @@ class CopyAttributeToSelection(bpy.types.Operator, tool.Ifc.Operator): name: bpy.props.StringProperty() def _execute(self, context): - value = tool.Blender.get_active_object().BIMAttributeProperties.attributes.get(self.name).get_value() + obj = tool.Blender.get_active_object() + assert obj + props = tool.Blender.get_object_attribute_props(obj) + value = props.attributes[self.name].get_value() total = core.copy_attribute_to_selection( tool.Ifc, tool.Blender, tool.Root, tool.Spatial, name=self.name, value=value ) diff --git a/src/bonsai/bonsai/bim/module/attribute/prop.py b/src/bonsai/bonsai/bim/module/attribute/prop.py index fd2dab7cd2..b42846a3d6 100644 --- a/src/bonsai/bonsai/bim/module/attribute/prop.py +++ b/src/bonsai/bonsai/bim/module/attribute/prop.py @@ -29,8 +29,13 @@ from bpy.props import ( FloatVectorProperty, CollectionProperty, ) +from typing import TYPE_CHECKING class BIMAttributeProperties(PropertyGroup): attributes: CollectionProperty(name="Attributes", type=Attribute) is_editing_attributes: BoolProperty(name="Is Editing Attributes") + + if TYPE_CHECKING: + attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + is_editing_attributes: bool diff --git a/src/bonsai/bonsai/bim/module/attribute/ui.py b/src/bonsai/bonsai/bim/module/attribute/ui.py index 0fce2b07d4..065aaab880 100644 --- a/src/bonsai/bonsai/bim/module/attribute/ui.py +++ b/src/bonsai/bonsai/bim/module/attribute/ui.py @@ -17,14 +17,16 @@ # along with Bonsai. If not, see . import bonsai.bim.helper +import bpy.types from bpy.types import Panel from bonsai.bim.module.attribute.data import AttributesData import bonsai.tool as tool -def draw_ui(context, layout, attributes): +def draw_ui(context: bpy.types.Context, layout: bpy.types.UILayout, attributes) -> None: obj = context.active_object - props = obj.BIMAttributeProperties + assert obj + props = tool.Blender.get_object_attribute_props(obj) if props.is_editing_attributes: row = layout.row(align=True) diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index 7c2ad314c4..49ea438e41 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -43,6 +43,7 @@ from typing_extensions import assert_never if TYPE_CHECKING: from bonsai.bim.prop import BIMProperties, BIMObjectProperties + from bonsai.bim.module.attribute.prop import BIMAttributeProperties from bonsai.bim.module.csv.prop import CsvProperties from bonsai.bim.module.diff.prop import DiffProperties @@ -1635,6 +1636,10 @@ class Blender(bonsai.core.tool.Blender): def get_object_bim_props(cls, obj: bpy.types.Object) -> BIMObjectProperties: return obj.BIMObjectProperties + @classmethod + def get_object_attribute_props(cls, obj: bpy.types.Object) -> BIMAttributeProperties: + return obj.BIMAttributeProperties + @classmethod def get_ifc_definition_id(cls, obj: IFC_CONNECTED_TYPE) -> int: if isinstance(obj, bpy.types.Object): diff --git a/src/ifcpatch/ifcpatch/__init__.py b/src/ifcpatch/ifcpatch/__init__.py index 3fb6573b2b..c2c46b47c3 100644 --- a/src/ifcpatch/ifcpatch/__init__.py +++ b/src/ifcpatch/ifcpatch/__init__.py @@ -53,7 +53,7 @@ def execute(args: ArgumentsDict) -> Union[ifcopenshell.file, str]: :param args: A dictionary of arguments, corresponding to the parameters listed subsequent to this in this docstring. - :type args: dict + :type args: ArgumentsDict :param file: An IFC model to apply the patch recipe to. Required for most recipes except the ones that require `input`. :type file: ifcopenshell.file, optional diff --git a/src/ifcpatch/ifcpatch/recipes/ExtractElements.py b/src/ifcpatch/ifcpatch/recipes/ExtractElements.py index 0218d68a30..63bb8a298c 100644 --- a/src/ifcpatch/ifcpatch/recipes/ExtractElements.py +++ b/src/ifcpatch/ifcpatch/recipes/ExtractElements.py @@ -18,6 +18,7 @@ import ifcopenshell import ifcopenshell.api +import ifcopenshell.api.project import ifcopenshell.guid import ifcopenshell.util.selector from typing import Union @@ -81,8 +82,8 @@ class Patcher: pass if element.is_a("IfcProject"): return self.new.add(element) - return ifcopenshell.api.run( - "project.append_asset", self.new, library=self.file, element=element, reuse_identities=self.reuse_identities + return ifcopenshell.api.project.append_asset( + self.new, library=self.file, element=element, reuse_identities=self.reuse_identities ) def add_spatial_structures( diff --git a/src/ifcpatch/ifcpatch/recipes/MergeDuplicateTypes.py b/src/ifcpatch/ifcpatch/recipes/MergeDuplicateTypes.py index 6b40b26fbc..83cdae5235 100644 --- a/src/ifcpatch/ifcpatch/recipes/MergeDuplicateTypes.py +++ b/src/ifcpatch/ifcpatch/recipes/MergeDuplicateTypes.py @@ -18,8 +18,10 @@ import ifcopenshell import ifcopenshell.api +import ifcopenshell.api.type import ifcopenshell.util.element from logging import Logger +from typing import Any class Patcher: @@ -48,7 +50,6 @@ class Patcher: :param attribute: The name of the attribute to merge element types based on. Typically this will be "Tag" as it stores the unique ID from the proprietary BIM software. - :type attribute: str Example: @@ -66,7 +67,7 @@ class Patcher: def patch(self): key = self.attribute - keys = {} + keys: dict[Any, ifcopenshell.entity_instance] = {} for element_type in self.file.by_type("IfcTypeObject"): original_type = keys.get(getattr(element_type, key), None) if original_type: @@ -86,8 +87,7 @@ class Patcher: # since that would do other things like # map type representations or recalculate material set usages which is # risky when we're patching an existing dataset. - ifcopenshell.api.run( - "type.assign_type", + ifcopenshell.api.type.assign_type( self.file, relating_type=relating_type, related_objects=related_objects,