diff --git a/src/blenderbim/blenderbim/bim/module/pset/ui.py b/src/blenderbim/blenderbim/bim/module/pset/ui.py index 08285b5c40..e96088af94 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/ui.py +++ b/src/blenderbim/blenderbim/bim/module/pset/ui.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . +import bpy import blenderbim.tool as tool from bpy.types import Panel from blenderbim.bim.ifc import IfcStore @@ -34,16 +35,21 @@ from blenderbim.bim.module.pset.data import ( ProfilePsetsData, WorkSchedulePsetsData, ) +from typing import Any, Optional -def draw_property(prop, layout, copy_operator=None): +def draw_property( + prop: bpy.types.PropertyGroup, layout: bpy.types.UILayout, copy_operator: Optional[str] = None +) -> None: if prop.value_type == "IfcPropertySingleValue": draw_single_property(prop, layout, copy_operator) elif prop.value_type == "IfcPropertyEnumeratedValue": draw_enumerated_property(prop, layout, copy_operator) -def draw_single_property(prop, layout, copy_operator=None): +def draw_single_property( + prop: bpy.types.PropertyGroup, layout: bpy.types.UILayout, copy_operator: Optional[str] = None +) -> None: value_name = prop.metadata.get_value_name(display_only=True) if not value_name: layout.label(text=prop["Name"]) @@ -63,7 +69,9 @@ def draw_single_property(prop, layout, copy_operator=None): op.name = prop.metadata.name -def draw_enumerated_property(prop, layout, copy_operator=None): +def draw_enumerated_property( + prop: bpy.types.PropertyGroup, layout: bpy.types.UILayout, copy_operator: Optional[str] = None +) -> None: value_name = prop.metadata.get_value_name() if not value_name: layout.label(text=prop.metadata.name) @@ -78,13 +86,21 @@ def draw_enumerated_property(prop, layout, copy_operator=None): op.name = prop.metadata.name -def get_active_pset_obj_name(context, obj_type): +def get_active_pset_obj_name(context: bpy.types.Context, obj_type: tool.Ifc.OBJECT_TYPE) -> str: if obj_type in ("Object", "MaterialSet", "MaterialSetItem"): return context.active_object.name return "" -def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type, allow_removing=True): +def draw_psetqto_ui( + context: bpy.types.Context, + pset_id: int, + pset: dict[str, Any], + props: bpy.types.PropertyGroup, + layout: bpy.types.UILayout, + obj_type: tool.Ifc.OBJECT_TYPE, + allow_removing: bool = True, +) -> None: box = layout.box() row = box.row(align=True) if "is_expanded" not in pset: @@ -155,7 +171,9 @@ def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type, allow_remov row.label(text="No Properties Set") -def draw_psetqto_editable_ui(box, props, prop): +def draw_psetqto_editable_ui( + box: bpy.types.UILayout, props: bpy.types.PropertyGroup, prop: bpy.types.PropertyGroup +) -> None: row = box.row(align=True) draw_property(prop, row, copy_operator="bim.copy_property_to_selection") diff --git a/src/blenderbim/blenderbim/bim/module/style/data.py b/src/blenderbim/blenderbim/bim/module/style/data.py index 7bcb05482b..0970221caa 100644 --- a/src/blenderbim/blenderbim/bim/module/style/data.py +++ b/src/blenderbim/blenderbim/bim/module/style/data.py @@ -18,6 +18,7 @@ import bpy import ifcopenshell +import ifcopenshell.util.schema import blenderbim.tool as tool from ifcopenshell.util.doc import get_entity_doc from typing import Union diff --git a/src/blenderbim/blenderbim/core/pset.py b/src/blenderbim/blenderbim/core/pset.py index c21b59ccf5..ab2370db38 100644 --- a/src/blenderbim/blenderbim/core/pset.py +++ b/src/blenderbim/blenderbim/core/pset.py @@ -17,7 +17,24 @@ # along with BlenderBIM Add-on. If not, see . -def copy_property_to_selection(ifc, pset, is_pset=True, obj=None, pset_name=None, prop_name=None, prop_value=None): +from __future__ import annotations +from typing import TYPE_CHECKING, Optional, Union, Any + +if TYPE_CHECKING: + import bpy + import ifcopenshell + import blenderbim.tool as tool + + +def copy_property_to_selection( + ifc: tool.Ifc, + pset: tool.Pset, + obj: bpy.types.Object, + pset_name: str, + prop_name: str, + prop_value: Any, + is_pset: bool = True, +) -> None: element = ifc.get_entity(obj) if not element: return @@ -30,7 +47,9 @@ def copy_property_to_selection(ifc, pset, is_pset=True, obj=None, pset_name=None ifc.run("pset.edit_qto", qto=ifc_pset, properties={prop_name: prop_value}) -def add_pset(ifc, pset, blender, obj_name, obj_type): +def add_pset( + ifc: tool.Ifc, pset: tool.Pset, blender: tool.Blender, obj_name: str, obj_type: tool.Ifc.OBJECT_TYPE +) -> None: pset_name = pset.get_pset_name(obj_name, obj_type, pset_type="PSET") if obj_type == "Object": elements = [ifc.get_entity(obj) for obj in blender.get_selected_objects()] @@ -44,7 +63,14 @@ def add_pset(ifc, pset, blender, obj_name, obj_type): pset.enable_pset_editing(pset_id=0, pset_name=pset_name, pset_type="PSET", obj=obj_name, obj_type=obj_type) -def enable_pset_editing(pset_tool, pset, pset_name, pset_type, obj_name, obj_type): +def enable_pset_editing( + pset_tool: tool.Pset, + pset: ifcopenshell.entity_instance, + pset_name: str, + pset_type: tool.Pset.PSET_TYPE, + obj_name: str, + obj_type: tool.Ifc.OBJECT_TYPE, +) -> None: props = pset_tool.get_pset_props(obj_name, obj_type) pset_tool.clear_blender_pset_properties(props) @@ -63,6 +89,6 @@ def enable_pset_editing(pset_tool, pset, pset_name, pset_type, obj_name, obj_typ pset_tool.enable_proposed_pset(props, pset_name, pset_type, has_template) -def add_proposed_prop(pset, obj_name, obj_type, name, value): +def add_proposed_prop(pset: tool.Pset, obj_name: str, obj_type: tool.Ifc.OBJECT_TYPE, name: str, value: Any) -> None: props = pset.get_pset_props(obj_name, obj_type) pset.add_proposed_property(name, pset.cast_string_to_primitive(value), props) diff --git a/src/blenderbim/blenderbim/tool/blender.py b/src/blenderbim/blenderbim/tool/blender.py index 004c4cc34b..17010b1070 100644 --- a/src/blenderbim/blenderbim/tool/blender.py +++ b/src/blenderbim/blenderbim/tool/blender.py @@ -32,6 +32,7 @@ from mathutils import Vector from pathlib import Path from blenderbim.bim.ifc import IFC_CONNECTED_TYPE from typing import Any, Optional, Union, Literal, Iterable, Callable +from typing_extensions import assert_never VIEWPORT_ATTRIBUTES = [ @@ -163,8 +164,14 @@ class Blender(blenderbim.core.tool.Blender): @classmethod def get_obj_ifc_definition_id( - cls, obj: Optional[str] = None, obj_type: Optional[str] = None, context: Optional[bpy.types.Context] = None + cls, + obj: Optional[str] = None, + obj_type: Optional[tool.Ifc.OBJECT_TYPE] = None, + context: Optional[bpy.types.Context] = None, ) -> Union[int, None]: + # TODO: is it ever used as None? + if obj_type is None: + return None if context is None: context = bpy.context if obj_type == "Object": @@ -200,6 +207,7 @@ class Blender(blenderbim.core.tool.Blender): elif obj_type == "Group": prop = context.scene.BIMGroupProperties return prop.groups[prop.active_group_index].ifc_definition_id + assert_never(obj_type) @classmethod def is_ifc_object(cls, obj: bpy.types.Object) -> bool: diff --git a/src/blenderbim/blenderbim/tool/ifc.py b/src/blenderbim/blenderbim/tool/ifc.py index 43b8e9bd46..77be4a34db 100644 --- a/src/blenderbim/blenderbim/tool/ifc.py +++ b/src/blenderbim/blenderbim/tool/ifc.py @@ -26,10 +26,23 @@ import blenderbim.core.tool import blenderbim.bim.handler import blenderbim.tool as tool from blenderbim.bim.ifc import IfcStore, IFC_CONNECTED_TYPE -from typing import Optional, Union, Any, final +from typing import Optional, Union, Any, final, Literal class Ifc(blenderbim.core.tool.Ifc): + OBJECT_TYPE = Literal[ + "Object", + "Material", + "MaterialSet", + "MaterialSetItem", + "Task", + "Cost", + "Resource", + "Profile", + "WorkSchedule", + "Group", + ] + @classmethod def run(cls, command: str, **kwargs) -> Any: return ifcopenshell.api.run(command, IfcStore.get_file(), **kwargs) diff --git a/src/blenderbim/blenderbim/tool/pset.py b/src/blenderbim/blenderbim/tool/pset.py index f26f2c478b..d278e20a7c 100644 --- a/src/blenderbim/blenderbim/tool/pset.py +++ b/src/blenderbim/blenderbim/tool/pset.py @@ -18,14 +18,18 @@ import bpy import ifcopenshell +import ifcopenshell.util.attribute import ifcopenshell.util.element import blenderbim.core.tool import blenderbim.tool as tool import blenderbim.bim.schema -from typing import Union +from typing import Union, Literal, Any +from typing_extensions import assert_never class Pset(blenderbim.core.tool.Pset): + PSET_TYPE = Literal["PSET", "QTO"] + @classmethod def get_element_pset( cls, element: ifcopenshell.entity_instance, pset_name: str @@ -35,7 +39,7 @@ class Pset(blenderbim.core.tool.Pset): return tool.Ifc.get().by_id(pset["id"]) @classmethod - def get_pset_props(cls, obj, obj_type): + def get_pset_props(cls, obj: str, obj_type: tool.Ifc.OBJECT_TYPE) -> bpy.types.PropertyGroup: if obj_type == "Object": return bpy.data.objects.get(obj).PsetProperties elif obj_type == "Material": @@ -54,9 +58,10 @@ class Pset(blenderbim.core.tool.Pset): return bpy.context.scene.WorkSchedulePsetProperties elif obj_type == "Group": return bpy.context.scene.GroupPsetProperties + assert_never(obj_type) @classmethod - def get_pset_name(cls, obj, obj_type, pset_type="PSET"): + def get_pset_name(cls, obj: str, obj_type: tool.Ifc.OBJECT_TYPE, pset_type: PSET_TYPE = "PSET") -> str: props = cls.get_pset_props(obj, obj_type) name = props.pset_name if pset_type == "PSET" else props.qto_name if name == "BBIM_CUSTOM": @@ -73,7 +78,7 @@ class Pset(blenderbim.core.tool.Pset): ) @classmethod - def is_pset_empty(cls, pset): + def is_pset_empty(cls, pset: ifcopenshell.entity_instance) -> bool: pset_dict = ifcopenshell.util.element.get_property_definition(pset) del pset_dict["id"] for value in pset_dict.values(): @@ -82,14 +87,16 @@ class Pset(blenderbim.core.tool.Pset): return True @classmethod - def enable_pset_editing(cls, pset_id=None, pset_name=None, pset_type=None, obj=None, obj_type=None): - # TODO REFACTOR ONCE toll/CORE functions are available + def enable_pset_editing( + cls, pset_id: int, pset_name: str, pset_type: PSET_TYPE, obj: str, obj_type: tool.Ifc.OBJECT_TYPE + ) -> None: + # TODO REFACTOR ONCE tool/CORE functions are available bpy.ops.bim.enable_pset_editing( pset_id=0, pset_name=cls.get_pset_name(obj, obj_type), pset_type="PSET", obj=obj, obj_type=obj_type ) @classmethod - def import_pset_from_existing(cls, pset, props): + def import_pset_from_existing(cls, pset: ifcopenshell.entity_instance, props: bpy.types.PropertyGroup) -> None: pset_props = [] if pset.is_a("IfcElementQuantity"): pset_props = pset.Quantities @@ -134,7 +141,7 @@ class Pset(blenderbim.core.tool.Pset): metadata.set_value(metadata.get_value_default() if metadata.is_null else value) @classmethod - def get_prop_template_primitive_type(cls, prop_template): + def get_prop_template_primitive_type(cls, prop_template: ifcopenshell.entity_instance) -> str: if prop_template.TemplateType in ["Q_LENGTH", "Q_AREA", "Q_VOLUME", "Q_WEIGHT", "Q_TIME"]: return "float" elif prop_template.TemplateType == "Q_COUNT": @@ -144,7 +151,9 @@ class Pset(blenderbim.core.tool.Pset): ) @classmethod - def import_enumerated_value_from_template(cls, prop_template, data, props): + def import_enumerated_value_from_template( + cls, prop_template: ifcopenshell.entity_instance, data: dict[str, Any], props: bpy.types.PropertyGroup + ) -> None: enum_items = [v.wrappedValue for v in prop_template.Enumerators.EnumerationValues] selected_enum_items = data.get(prop_template.Name, []) or [] @@ -167,7 +176,13 @@ class Pset(blenderbim.core.tool.Pset): new.is_selected = enum in selected_enum_items @classmethod - def import_single_value_from_template(cls, pset_template, prop_template, data, props): + def import_single_value_from_template( + cls, + pset_template: ifcopenshell.entity_instance, + prop_template: ifcopenshell.entity_instance, + data: dict[str, Any], + props: bpy.types.PropertyGroup, + ) -> None: prop = props.properties.add() prop.name = prop_template.Name prop.value_type = "IfcPropertySingleValue" @@ -207,7 +222,12 @@ class Pset(blenderbim.core.tool.Pset): blenderbim.bim.helper.add_attribute_description(metadata, prop_template) @classmethod - def import_pset_from_template(cls, pset_template, pset, props): + def import_pset_from_template( + cls, + pset_template: ifcopenshell.entity_instance, + pset: Union[ifcopenshell.entity_instance, None], + props: bpy.types.PropertyGroup, + ) -> None: if pset: data = ifcopenshell.util.element.get_property_definition(pset) del data["id"] @@ -231,28 +251,32 @@ class Pset(blenderbim.core.tool.Pset): pass @classmethod - def clear_blender_pset_properties(cls, props): + def clear_blender_pset_properties(cls, props: bpy.types.PropertyGroup) -> None: props.properties.clear() @classmethod - def set_active_pset(cls, props, pset, has_template): + def set_active_pset( + cls, props: bpy.types.PropertyGroup, pset: ifcopenshell.entity_instance, has_template: bool + ) -> None: props.active_pset_id = pset.id() props.active_pset_name = pset.Name props.active_pset_has_template = has_template @classmethod - def enable_proposed_pset(cls, props, pset_name, pset_type, has_template): + def enable_proposed_pset( + cls, props: bpy.types.PropertyGroup, pset_name: str, pset_type: PSET_TYPE, has_template: bool + ) -> None: props.active_pset_id = 0 props.active_pset_name = pset_name or "My_Data" props.active_pset_type = pset_type props.active_pset_has_template = has_template @classmethod - def get_pset_template(cls, name): + def get_pset_template(cls, name: str) -> Union[ifcopenshell.entity_instance, None]: return blenderbim.bim.schema.ifc.psetqto.get_by_name(name) @classmethod - def add_proposed_property(cls, name, value, props): + def add_proposed_property(cls, name: str, value: Any, props: bpy.types.PropertyGroup) -> None: if props.properties.get(name): return prop = props.properties.add() @@ -265,7 +289,7 @@ class Pset(blenderbim.core.tool.Pset): metadata.set_value(metadata.get_value_default() if metadata.is_null else value) @classmethod - def cast_string_to_primitive(cls, value: str): + def cast_string_to_primitive(cls, value: str) -> Any: value = value.strip() if value.lower() == "true": return True diff --git a/src/ifcopenshell-python/ifcopenshell/geom/main.py b/src/ifcopenshell-python/ifcopenshell/geom/main.py index 29ba26e92b..8fd68a4c62 100644 --- a/src/ifcopenshell-python/ifcopenshell/geom/main.py +++ b/src/ifcopenshell-python/ifcopenshell/geom/main.py @@ -33,6 +33,8 @@ from typing import TypeVar, Union, Optional, Generator, Any, Literal, overload, if TYPE_CHECKING: from OCC.Core import TopoDS + IteratorOutput = Union["ShapeElementType", "utils.shape_tuple"] + T = TypeVar("T") ShapeElementType = Union[ ifcopenshell_wrapper.BRepElement, ifcopenshell_wrapper.TriangulationElement, ifcopenshell_wrapper.SerializedElement @@ -273,7 +275,7 @@ class iterator(ifcopenshell_wrapper.Iterator): def get(self): return wrap_shape_creation(self.settings, ifcopenshell_wrapper.Iterator.get(self)) - def __iter__(self) -> Generator[ShapeElementType, None, None]: + def __iter__(self) -> Generator[IteratorOutput, None, None]: if self.initialize(): while True: yield self.get() @@ -412,7 +414,19 @@ def create_shape( ) -def consume_iterator(it, with_progress=False): +@overload +def consume_iterator(it: iterator, with_progress: Literal[False] = False) -> Generator[IteratorOutput, None, None]: ... +@overload +def consume_iterator( + it: iterator, with_progress: Literal[True] +) -> Generator[tuple[int, IteratorOutput], None, None]: ... +@overload +def consume_iterator( + it: iterator, with_progress: bool +) -> Generator[Union[IteratorOutput, tuple[int, IteratorOutput]], None, None]: ... +def consume_iterator( + it: iterator, with_progress: bool = False +) -> Generator[Union[IteratorOutput, tuple[int, IteratorOutput]], None, None]: if it.initialize(): while True: if with_progress: @@ -423,16 +437,49 @@ def consume_iterator(it, with_progress=False): break +@overload def iterate( - settings, - file_or_filename, - num_threads=1, - include=None, - exclude=None, - with_progress=False, - cache=None, + settings: settings, + file_or_filename: Union[file, str], + num_threads: int = 1, + include: Optional[Union[list[entity_instance], list[str]]] = None, + exclude: Optional[Union[list[entity_instance], list[str]]] = None, + with_progress: Literal[False] = False, + cache: Optional[serializers.hdf5] = None, geometry_library: GEOMETRY_LIBRARY = "opencascade", -): +) -> Generator[IteratorOutput, None, None]: ... +@overload +def iterate( + settings: settings, + file_or_filename: Union[file, str], + num_threads: int = 1, + include: Optional[Union[list[entity_instance], list[str]]] = None, + exclude: Optional[Union[list[entity_instance], list[str]]] = None, + with_progress: Literal[True] = True, + cache: Optional[serializers.hdf5] = None, + geometry_library: GEOMETRY_LIBRARY = "opencascade", +) -> Generator[tuple[int, IteratorOutput], None, None]: ... +@overload +def iterate( + settings: settings, + file_or_filename: Union[file, str], + num_threads: int = 1, + include: Optional[Union[list[entity_instance], list[str]]] = None, + exclude: Optional[Union[list[entity_instance], list[str]]] = None, + with_progress: bool = False, + cache: Optional[serializers.hdf5] = None, + geometry_library: GEOMETRY_LIBRARY = "opencascade", +) -> Generator[Union[IteratorOutput, tuple[int, IteratorOutput]], None, None]: ... +def iterate( + settings: settings, + file_or_filename: Union[file, str], + num_threads: int = 1, + include: Optional[Union[list[entity_instance], list[str]]] = None, + exclude: Optional[Union[list[entity_instance], list[str]]] = None, + with_progress: bool = False, + cache: Optional[serializers.hdf5] = None, + geometry_library: GEOMETRY_LIBRARY = "opencascade", +) -> Generator[Union[IteratorOutput, tuple[int, IteratorOutput]], None, None]: it = iterator(settings, file_or_filename, num_threads, include, exclude, geometry_library) if cache: hdf5_cache = serializers.hdf5(cache, settings) diff --git a/src/ifcopenshell-python/ifcopenshell/geom/occ_utils.py b/src/ifcopenshell-python/ifcopenshell/geom/occ_utils.py index b5e8b1ccae..d8ba66d093 100644 --- a/src/ifcopenshell-python/ifcopenshell/geom/occ_utils.py +++ b/src/ifcopenshell-python/ifcopenshell/geom/occ_utils.py @@ -40,6 +40,7 @@ except ImportError: class shape_tuple(NamedTuple): + """A tuple containing IfcOpenShell serialized element/shape and pythonOCC shape.""" data: Union[ifcopenshell_wrapper.SerializedElement, ifcopenshell_wrapper.Serialization] geometry: TopoDS.TopoDS_Shape styles: tuple[tuple[float, float, float, float], ...]