diff --git a/src/bonsai/bonsai/bim/module/material/operator.py b/src/bonsai/bonsai/bim/module/material/operator.py index 9d768351d1..bd515252ee 100644 --- a/src/bonsai/bonsai/bim/module/material/operator.py +++ b/src/bonsai/bonsai/bim/module/material/operator.py @@ -561,9 +561,12 @@ class EditAssignedMaterial(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): self.file = tool.Ifc.get() active_obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object + assert active_obj props = active_obj.BIMObjectMaterialProperties element = tool.Ifc.get_entity(active_obj) + assert element material = ifcopenshell.util.element.get_material(element) + assert material objects = tool.Blender.get_selected_objects() diff --git a/src/bonsai/bonsai/tool/model.py b/src/bonsai/bonsai/tool/model.py index 62cabec1b5..c63a9ba466 100644 --- a/src/bonsai/bonsai/tool/model.py +++ b/src/bonsai/bonsai/tool/model.py @@ -50,7 +50,7 @@ from bonsai.bim.module.geometry.helper import Helper from bonsai.bim.module.model.data import AuthoringData, RailingData, RoofData, WindowData, DoorData from bonsai.bim.module.model.opening import FilledOpeningGenerator from ifcopenshell.util.shape_builder import ShapeBuilder -from typing import Optional, Union, TypeVar, Any, Iterable, Literal, TYPE_CHECKING, Sequence +from typing import Optional, Union, TypeVar, Any, Iterable, Literal, TYPE_CHECKING, Sequence, TypedDict T = TypeVar("T") V_ = tool.Blender.V_ @@ -603,8 +603,16 @@ class Model(bonsai.core.tool.Model): if not openings[i].obj: openings.remove(i) + class MaterialLayerParameters(TypedDict): + """Float values are in project units.""" + + layer_set_direction: Literal["AXIS1", "AXIS2", "AXIS3"] + thickness: float + offset: float + direction_sense: Literal["NEGATIVE", "POSITIVE"] + @classmethod - def get_material_layer_parameters(cls, element: ifcopenshell.entity_instance) -> dict[str, Any]: + def get_material_layer_parameters(cls, element: ifcopenshell.entity_instance) -> MaterialLayerParameters: unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) layer_set_direction = "AXIS2" offset = 0.0 @@ -619,12 +627,12 @@ class Model(bonsai.core.tool.Model): material = material.ForLayerSet if material.is_a("IfcMaterialLayerSet"): thickness = sum([l.LayerThickness for l in material.MaterialLayers]) * unit_scale - return { - "layer_set_direction": layer_set_direction, - "thickness": thickness, - "offset": offset, - "direction_sense": direction_sense, - } + return cls.MaterialLayerParameters( + layer_set_direction=layer_set_direction, + thickness=thickness, + offset=offset, + direction_sense=direction_sense, + ) @classmethod def get_booleans( @@ -2045,7 +2053,7 @@ class Model(bonsai.core.tool.Model): FilledOpeningGenerator().generate(filling_obj, voided_obj) @classmethod - def add_extrusion_position(cls, extrusion: ifcopenshell.entity_instance, position: tuple) -> None: + def add_extrusion_position(cls, extrusion: ifcopenshell.entity_instance, position: Vector) -> None: ifc_file = tool.Ifc.get() new_position = ifc_file.createIfcAxis2Placement3D( diff --git a/src/ifcopenshell-python/ifcopenshell/api/georeference/edit_georeferencing.py b/src/ifcopenshell-python/ifcopenshell/api/georeference/edit_georeferencing.py index af967e3a79..0d4da93bcb 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/georeference/edit_georeferencing.py +++ b/src/ifcopenshell-python/ifcopenshell/api/georeference/edit_georeferencing.py @@ -18,6 +18,7 @@ import ifcopenshell import ifcopenshell.api.pset +import ifcopenshell.util.element from typing import Optional, Any diff --git a/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py b/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py index 6f02a4531d..56100a403c 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py +++ b/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py @@ -54,9 +54,7 @@ def copy_class(file: ifcopenshell.file, product: ifcopenshell.entity_instance) - connections are still valid. :param product: The IfcProduct to copy. - :type param: ifcopenshell.entity_instance :return: The copied product - :rtype: ifcopenshell.entity_instance Example: @@ -70,26 +68,26 @@ def copy_class(file: ifcopenshell.file, product: ifcopenshell.entity_instance) - """ usecase = Usecase() usecase.file = file - usecase.settings = {"product": product} - return usecase.execute() + return usecase.execute(product) class Usecase: file: ifcopenshell.file - settings: dict[str, Any] - def execute(self): - result = ifcopenshell.util.element.copy(self.file, self.settings["product"]) + def execute(self, product: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance: + result = ifcopenshell.util.element.copy(self.file, product) self.copy_direct_attributes(result) - self.copy_indirect_attributes(self.settings["product"], result) + self.copy_indirect_attributes(product, result) return result - def copy_direct_attributes(self, to_element): + def copy_direct_attributes(self, to_element: ifcopenshell.entity_instance) -> None: self.remove_representations(to_element) self.copy_object_placements(to_element) self.copy_psets(to_element) - def copy_indirect_attributes(self, from_element, to_element): + def copy_indirect_attributes( + self, from_element: ifcopenshell.entity_instance, to_element: ifcopenshell.entity_instance + ) -> None: for inverse in self.file.get_inverse(from_element): if inverse.is_a("IfcRelDefinesByProperties"): # Properties must not be shared between objects for convenience of authoring @@ -175,13 +173,13 @@ class Usecase: new_value.append(to_element) inverse[i] = new_value - def remove_representations(self, element): + def remove_representations(self, element: ifcopenshell.entity_instance) -> None: if element.is_a("IfcProduct"): element.Representation = None elif element.is_a("IfcTypeProduct"): element.RepresentationMaps = None - def copy_object_placements(self, element): + def copy_object_placements(self, element: ifcopenshell.entity_instance) -> None: if not element.is_a("IfcProduct") or not element.ObjectPlacement: return element.ObjectPlacement = ifcopenshell.util.element.copy(self.file, element.ObjectPlacement) @@ -189,7 +187,7 @@ class Usecase: self.file, element.ObjectPlacement.RelativePlacement ) - def copy_psets(self, element): + def copy_psets(self, element: ifcopenshell.entity_instance) -> None: if not element.is_a("IfcTypeObject") or not element.HasPropertySets: return element.HasPropertySets = [ diff --git a/src/ifcopenshell-python/ifcopenshell/util/unit.py b/src/ifcopenshell-python/ifcopenshell/util/unit.py index c28595ae5b..b1862d3882 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/unit.py +++ b/src/ifcopenshell-python/ifcopenshell/util/unit.py @@ -20,7 +20,6 @@ from fractions import Fraction from math import pi from typing import Any from typing import Dict -from typing import Iterable from typing import Literal from typing import Optional from typing import Union @@ -821,7 +820,8 @@ def iter_element_and_attributes_per_type(ifc_file: ifcopenshell.file, attr_type_ for element in ifc_file: entity = schema.declaration_by_name(element.is_a()) attrs = entity.all_attributes() - for attr, val, is_derived in zip(attrs, list(element), entity.derived()): + attrs_derived: tuple[bool, ...] = entity.derived() + for attr, val, is_derived in zip(attrs, list(element), attrs_derived): if is_derived: continue @@ -877,7 +877,7 @@ def convert_file_length_units(ifc_file: ifcopenshell.file, target_units: str = " new_length = ifcopenshell.api.unit.add_conversion_based_unit(file_patched, name=target_units) # support tuple of tuples, as in IfcCartesianPointList3D.CoordList - def convert_value(value): + def convert_value(value: FloatOrSequenceOfFloats) -> FloatOrSequenceOfFloats: if not isinstance(value, tuple): return convert_unit(value, old_length, new_length) return tuple(convert_value(v) for v in value) diff --git a/src/ifcopenshell-python/test/util/test_unit.py b/src/ifcopenshell-python/test/util/test_unit.py index 98c33397a9..b5f1fc537e 100644 --- a/src/ifcopenshell-python/test/util/test_unit.py +++ b/src/ifcopenshell-python/test/util/test_unit.py @@ -18,9 +18,12 @@ import test.bootstrap import numpy as np +import ifcopenshell.api.context import ifcopenshell.api.unit import ifcopenshell.api.root import ifcopenshell.api.georeference +import ifcopenshell.api.pset +import ifcopenshell.util.element import ifcopenshell.util.geolocation import ifcopenshell.util.unit as subject from ifcopenshell.util.shape_builder import ShapeBuilder