From 78302d15835f010c44c9aece9cb2e5206f83b1d0 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 27 Sep 2024 11:44:10 +0500 Subject: [PATCH] typing --- src/bonsai/bonsai/bim/helper.py | 76 ++++++++++++++++++++++------- src/bonsai/bonsai/bim/import_ifc.py | 4 +- src/bonsai/bonsai/core/context.py | 24 +++++++-- src/bonsai/bonsai/tool/context.py | 14 +++--- 4 files changed, 88 insertions(+), 30 deletions(-) diff --git a/src/bonsai/bonsai/bim/helper.py b/src/bonsai/bonsai/bim/helper.py index a492752f8d..788e398cde 100644 --- a/src/bonsai/bonsai/bim/helper.py +++ b/src/bonsai/bonsai/bim/helper.py @@ -16,11 +16,12 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . -# from datetime import date +from __future__ import annotations import bpy import json import math import ifcopenshell +import ifcopenshell.ifcopenshell_wrapper as W import ifcopenshell.util.attribute import ifcopenshell.util.element import ifcopenshell.util.unit @@ -29,14 +30,27 @@ from mathutils import geometry from mathutils import Vector import bonsai.tool as tool from bonsai.bim.ifc import IfcStore -from typing import Optional, Callable, Any, Union +from typing import Optional, Callable, Any, Union, Iterable, TYPE_CHECKING + +if TYPE_CHECKING: + import bonsai.bim.prop + + # ImportCallback return values: + # - None - property should be imported by default workflow + # - True - setting value for imported attribute should be skipped + # - False - property should be skipped entirely from import + ImportCallback = Callable[[str, Optional[bonsai.bim.prop.Attribute], dict[str, Any], Union[bool, None]]] + # ExportCallback return values: + # - True - property should be skipped entirely from export + # - False - property should be exproted by default workflow + ExportCallback = Callable[[dict[str, Any], bonsai.bim.prop.Attribute], bool] def draw_attributes( - props: list[bpy.types.PropertyGroup], + props: list[bonsai.bim.prop.Attribute], layout: bpy.types.UILayout, copy_operator: Optional[str] = None, - popup_active_attribute: Optional[bpy.types.PropertyGroup] = None, + popup_active_attribute: Optional[bonsai.bim.prop.Attribute] = None, ) -> None: """you can set attribute active in popup with `active_attribute` meaning you will be able to type into attribute's field without having to click @@ -50,7 +64,7 @@ def draw_attributes( def draw_attribute( - attribute: bpy.types.PropertyGroup, layout: bpy.types.UILayout, copy_operator: Optional[str] = None + attribute: bonsai.bim.prop.Attribute, layout: bpy.types.UILayout, copy_operator: Optional[str] = None ) -> None: value_name = attribute.get_value_name() if not value_name: @@ -94,7 +108,12 @@ def draw_attribute( op.name = attribute.name -def import_attributes(ifc_class, props, data, callback=None): +def import_attributes( + ifc_class: str, + props: bpy.types.bpy_prop_collection, + data: dict[str, Any], + callback: Optional[ImportCallback] = None, +) -> None: for attribute in IfcStore.get_schema().declaration_by_name(ifc_class).all_attributes(): import_attribute(attribute, props, data, callback=callback) @@ -102,8 +121,8 @@ def import_attributes(ifc_class, props, data, callback=None): # A more elegant attribute importer signature, intended to supersede import_attributes def import_attributes2( element: Union[str, ifcopenshell.entity_instance], - props: bpy.types.PropertyGroup, - callback: Optional[Callable] = None, + props: bpy.types.bpy_prop_collection, + callback: Optional[ImportCallback] = None, ) -> None: if isinstance(element, str): attributes = tool.Ifc.schema().declaration_by_name(element).as_entity().all_attributes() @@ -116,8 +135,14 @@ def import_attributes2( import_attribute(attribute, props, info, callback=callback) -def import_attribute(attribute, props, data, callback=None): +def import_attribute( + attribute: W.attribute, + props: bpy.types.bpy_prop_collection, + data: dict[str, Any], + callback: Optional[ImportCallback] = None, +) -> None: data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) + # Complex data types (aggregates and entities) are handled only by callback. if isinstance(data_type, tuple) or data_type == "entity": callback(attribute.name(), None, data) if callback else None return @@ -156,7 +181,7 @@ def import_attribute(attribute, props, data, callback=None): ATTRIBUTE_MIN_MAX_CONSTRAINTS = {"IfcMaterialLayer": {"Priority": {"value_min": 0, "value_max": 100}}} -def add_attribute_min_max(attribute_blender): +def add_attribute_min_max(attribute_blender: bonsai.bim.prop.Attribute) -> None: if attribute_blender.ifc_class in ATTRIBUTE_MIN_MAX_CONSTRAINTS: constraints = ATTRIBUTE_MIN_MAX_CONSTRAINTS[attribute_blender.ifc_class].get(attribute_blender.name, {}) for constraint, value in constraints.items(): @@ -164,7 +189,9 @@ def add_attribute_min_max(attribute_blender): setattr(attribute_blender, constraint + "_constraint", True) -def add_attribute_enum_items_descriptions(attribute_blender, enum_items): +def add_attribute_enum_items_descriptions( + attribute_blender: bonsai.bim.prop.Attribute, enum_items: Iterable[str] +) -> None: attribute_blender.enum_descriptions.clear() if isinstance(enum_items, dict): enum_items = enum_items.keys() @@ -178,7 +205,7 @@ def add_attribute_enum_items_descriptions(attribute_blender, enum_items): new_enum_description.name = description -def add_attribute_description(attribute_blender, attribute_ifc=None): +def add_attribute_description(attribute_blender: bonsai.bim.prop.Attribute, attribute_ifc=None): if not attribute_blender.name: return version = tool.Ifc.get_schema() @@ -196,8 +223,11 @@ def add_attribute_description(attribute_blender, attribute_ifc=None): attribute_blender.description = description -def export_attributes(props, callback: Optional[Callable] = None) -> dict[str, Any]: - attributes = {} +def export_attributes( + props: bpy.types.bpy_prop_collection, + callback: Optional[ExportCallback] = None, +) -> dict[str, Any]: + attributes: dict[str, Any] = {} for prop in props: is_handled_by_callback = callback(attributes, prop) if callback else False if is_handled_by_callback: @@ -206,7 +236,13 @@ def export_attributes(props, callback: Optional[Callable] = None) -> dict[str, A return attributes -def prop_with_search(layout, data, prop_name, should_click_ok_to_validate=False, **kwargs): +def prop_with_search( + layout: bpy.types.UILayout, + data: Union[bpy.types.PropertyGroup, bpy.types.ID], + prop_name: str, + should_click_ok_to_validate: bool = False, + **kwargs: Any, +): # kwargs are layout.prop arguments (text, icon, etc.) row = layout.row(align=True) row.prop(data, prop_name, **kwargs) @@ -221,7 +257,11 @@ def prop_with_search(layout, data, prop_name, should_click_ok_to_validate=False, pass -def get_enum_items(data, prop_name, context=None): +def get_enum_items( + data: Union[bpy.types.PropertyGroup, bpy.types.ID], prop_name: str, context: Optional[bpy.types.Context] = None +) -> Union[ + Iterable[Union[tuple[str, str, str], tuple[str, str, str, int], tuple[str, str, str, str, int], None]], None +]: # Retrieve items from a dynamic EnumProperty, which is otherwise not supported # Or throws an error in the console when the items callback returns an empty list # See https://blender.stackexchange.com/q/215781/86891 @@ -235,7 +275,7 @@ def get_enum_items(data, prop_name, context=None): return items -def convert_property_group_from_si(property_group, skip_props=()): +def convert_property_group_from_si(property_group: bpy.types.PropertyGroup, skip_props: tuple[str, ...] = ()) -> None: """Method converts property group values from si to current ifc project units based on default values of the properties. @@ -254,7 +294,7 @@ def convert_property_group_from_si(property_group, skip_props=()): setattr(property_group, prop_name, prop_value) -def draw_filter(layout, filter_groups, data, module): +def draw_filter(layout: bpy.types.UILayout, filter_groups, data, module: str) -> None: if not data.is_loaded: data.load() diff --git a/src/bonsai/bonsai/bim/import_ifc.py b/src/bonsai/bonsai/bim/import_ifc.py index ecf59304c8..8681615fd3 100644 --- a/src/bonsai/bonsai/bim/import_ifc.py +++ b/src/bonsai/bonsai/bim/import_ifc.py @@ -1095,7 +1095,9 @@ class IfcImporter: self.mesh_data["loop_start"].extend(loop_start) # list(di1.keys()) - def create_native_swept_disk_solid(self, element, mesh_name, native_data): + def create_native_swept_disk_solid( + self, element: ifcopenshell.entity_instance, mesh_name: str, native_data: dict[str, Any] + ) -> bpy.types.Curve: # TODO: georeferencing? curve = bpy.data.curves.new(mesh_name, type="CURVE") curve.dimensions = "3D" diff --git a/src/bonsai/bonsai/core/context.py b/src/bonsai/bonsai/core/context.py index 5ef631bae2..c251a263c4 100644 --- a/src/bonsai/bonsai/core/context.py +++ b/src/bonsai/bonsai/core/context.py @@ -16,8 +16,22 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . +from __future__ import annotations +from typing import TYPE_CHECKING, Optional -def add_context(ifc, context_type=None, context_identifier=None, target_view=None, parent=None): +if TYPE_CHECKING: + import bpy + import ifcopenshell + import bonsai.tool as tool + + +def add_context( + ifc: tool.Ifc, + context_type: Optional[str] = None, + context_identifier: Optional[str] = None, + target_view: Optional[str] = None, + parent: Optional[str] = None, +) -> ifcopenshell.entity_instance: return ifc.run( "context.add_context", context_type=context_type, @@ -27,19 +41,19 @@ def add_context(ifc, context_type=None, context_identifier=None, target_view=Non ) -def remove_context(ifc, context=None): +def remove_context(ifc: tool.Ifc, context: ifcopenshell.entity_instance) -> None: ifc.run("context.remove_context", context=context) -def enable_editing_context(context_tool, context=None): +def enable_editing_context(context_tool: tool.Context, context: ifcopenshell.entity_instance) -> None: context_tool.set_context(context) context_tool.import_attributes() -def disable_editing_context(context): +def disable_editing_context(context: tool.Context) -> None: context.clear_context() -def edit_context(ifc, context): +def edit_context(ifc: tool.Ifc, context: tool.Context) -> None: ifc.run("context.edit_context", context=context.get_context(), attributes=context.export_attributes()) disable_editing_context(context) diff --git a/src/bonsai/bonsai/tool/context.py b/src/bonsai/bonsai/tool/context.py index dfb3590e59..ec38f2a1e4 100644 --- a/src/bonsai/bonsai/tool/context.py +++ b/src/bonsai/bonsai/tool/context.py @@ -20,20 +20,22 @@ import bpy import bonsai.bim.helper import bonsai.tool as tool import bonsai.core.tool +import ifcopenshell +from typing import Any, Union class Context(bonsai.core.tool.Context): @classmethod - def set_context(cls, context): + def set_context(cls, context: ifcopenshell.entity_instance) -> None: bpy.context.scene.BIMContextProperties.active_context_id = context.id() @classmethod - def import_attributes(cls): + def import_attributes(cls) -> None: props = bpy.context.scene.BIMContextProperties props.context_attributes.clear() context = cls.get_context() - def callback(name, prop, data): + def callback(name: str, prop, data) -> Union[bool, None]: if context.is_a("IfcGeometricRepresentationSubContext"): if name == "Precision": props.context_attributes.remove(props.context_attributes.find("Precision")) @@ -45,13 +47,13 @@ class Context(bonsai.core.tool.Context): bonsai.bim.helper.import_attributes(context.is_a(), props.context_attributes, context.get_info(), callback) @classmethod - def clear_context(cls): + def clear_context(cls) -> None: bpy.context.scene.BIMContextProperties.active_context_id = 0 @classmethod - def get_context(cls): + def get_context(cls) -> ifcopenshell.entity_instance: return tool.Ifc.get().by_id(bpy.context.scene.BIMContextProperties.active_context_id) @classmethod - def export_attributes(cls): + def export_attributes(cls) -> dict[str, Any]: return bonsai.bim.helper.export_attributes(bpy.context.scene.BIMContextProperties.context_attributes)