From 590288496eec1fdd97c10cfadf73f901647a85c0 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 7 Mar 2025 12:17:04 +0500 Subject: [PATCH] typing --- .../bonsai/bim/module/material/operator.py | 67 ++++++++------ src/bonsai/bonsai/bim/module/material/ui.py | 2 +- src/bonsai/bonsai/bim/module/model/slab.py | 11 ++- src/bonsai/bonsai/bim/module/profile/prop.py | 4 +- src/bonsai/bonsai/bim/module/pset/data.py | 4 +- src/bonsai/bonsai/bim/module/pset/prop.py | 8 +- src/bonsai/bonsai/bim/module/pset/ui.py | 3 +- src/bonsai/bonsai/tool/blender.py | 17 +++- src/bonsai/bonsai/tool/material.py | 13 ++- src/bonsai/bonsai/tool/profile.py | 5 +- .../api/geometry/add_slab_representation.py | 88 +++++++++++-------- .../ifcopenshell/api/group/assign_group.py | 3 - .../ifcopenshell/api/system/assign_system.py | 14 +-- .../recipes/ResetSpatialElementLocations.py | 14 +-- 14 files changed, 147 insertions(+), 106 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/material/operator.py b/src/bonsai/bonsai/bim/module/material/operator.py index bd515252ee..c6abbde92d 100644 --- a/src/bonsai/bonsai/bim/module/material/operator.py +++ b/src/bonsai/bonsai/bim/module/material/operator.py @@ -262,7 +262,8 @@ class AssignMaterial(bpy.types.Operator, tool.Ifc.Operator): if not (material_type := properties.material_type): if not (obj := context.active_object): return "" - material_type = obj.BIMObjectMaterialProperties.material_type + omprops = tool.Material.get_object_material_props(obj) + material_type = omprops.material_type description = "Assign current IfcMaterial to the selected objects" if material_type != "IfcMaterial": @@ -294,14 +295,12 @@ class AddConstituent(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object + omprops = tool.Material.get_object_material_props(obj) self.file = tool.Ifc.get() - ifcopenshell.api.run( - "material.add_constituent", + ifcopenshell.api.material.add_constituent( self.file, - **{ - "constituent_set": self.file.by_id(self.constituent_set), - "material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material)), - }, + constituent_set=self.file.by_id(self.constituent_set), + material=self.file.by_id(int(omprops.material)), ) @@ -330,13 +329,14 @@ class AddProfile(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object + assert obj self.file = tool.Ifc.get() props = tool.Material.get_material_props() - ifcopenshell.api.run( - "material.add_profile", + omprops = tool.Material.get_object_material_props(obj) + ifcopenshell.api.material.add_profile( self.file, profile_set=self.file.by_id(self.profile_set), - material=self.file.by_id(int(obj.BIMObjectMaterialProperties.material)), + material=self.file.by_id(int(omprops.material)), profile=self.file.by_id(int(props.profiles)), ) @@ -367,11 +367,13 @@ class AddLayer(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object + assert obj + omprops = tool.Material.get_object_material_props(obj) layer_set = tool.Ifc.get().by_id(self.layer_set) ifcopenshell.api.material.add_layer( tool.Ifc.get(), layer_set=layer_set, - material=tool.Ifc.get().by_id(int(obj.BIMObjectMaterialProperties.material)), + material=tool.Ifc.get().by_id(int(omprops.material)), ) slab.DumbSlabPlaner().regenerate_from_layer_set(layer_set) wall.DumbWallPlaner().regenerate_from_layer_set(layer_set) @@ -445,12 +447,13 @@ class AddListItem(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object + assert obj + omprops = tool.Material.get_object_material_props(obj) self.file = tool.Ifc.get() - ifcopenshell.api.run( - "material.add_list_item", + ifcopenshell.api.material.add_list_item( self.file, material_list=self.file.by_id(self.list_item_set), - material=self.file.by_id(int(obj.BIMObjectMaterialProperties.material)), + material=self.file.by_id(int(omprops.material)), ) @@ -484,10 +487,12 @@ class EnableEditingAssignedMaterial(bpy.types.Operator): def execute(self, context): obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object - props = obj.BIMObjectMaterialProperties + assert obj + props = tool.Material.get_object_material_props(obj) props.is_editing = True element = tool.Ifc.get_entity(obj) material = ifcopenshell.util.element.get_material(element) + assert material if material.is_a("IfcMaterial"): props.material = str(material.id()) @@ -545,7 +550,8 @@ class DisableEditingAssignedMaterial(bpy.types.Operator): def execute(self, context): bpy.ops.bim.disable_editing_material_set_item() obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object - props = obj.BIMObjectMaterialProperties + assert obj + props = tool.Material.get_object_material_props(obj) props.is_editing = False return {"FINISHED"} @@ -562,7 +568,7 @@ class EditAssignedMaterial(bpy.types.Operator, tool.Ifc.Operator): 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 + props = tool.Material.get_object_material_props(active_obj) element = tool.Ifc.get_entity(active_obj) assert element material = ifcopenshell.util.element.get_material(element) @@ -628,7 +634,8 @@ class EnableEditingMaterialSetItemProfile(bpy.types.Operator): def execute(self, context): obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object - self.props = obj.BIMObjectMaterialProperties + assert obj + self.props = tool.Material.get_object_material_props(obj) self.props.active_material_set_item_id = self.material_set_item self.props.material_set_item_profile_attributes.clear() profile = tool.Ifc.get().by_id(self.material_set_item).Profile @@ -644,7 +651,8 @@ class DisableEditingMaterialSetItemProfile(bpy.types.Operator): def execute(self, context): obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object - self.props = obj.BIMObjectMaterialProperties + assert obj + self.props = tool.Material.get_object_material_props(obj) self.props.active_material_set_item_id = 0 self.props.material_set_item_profile_attributes.clear() return {"FINISHED"} @@ -659,7 +667,8 @@ class EditMaterialSetItemProfile(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object - self.props = obj.BIMObjectMaterialProperties + assert obj + self.props = tool.Material.get_object_material_props(obj) attributes = bonsai.bim.helper.export_attributes(self.props.material_set_item_profile_attributes) profile = tool.Ifc.get().by_id(self.material_set_item).Profile ifcopenshell.api.run("profile.edit_profile", tool.Ifc.get(), profile=profile, attributes=attributes) @@ -678,11 +687,13 @@ class EnableEditingMaterialSetItem(bpy.types.Operator): def execute(self, context): self.file = tool.Ifc.get() obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object + assert obj self.mprops = tool.Material.get_material_props() - self.props = obj.BIMObjectMaterialProperties + self.props = tool.Material.get_object_material_props(obj) self.props.active_material_set_item_id = self.material_set_item element = tool.Ifc.get_entity(obj) + assert element material = ifcopenshell.util.element.get_material(element, should_skip_usage=True) material_set_item = self.file.by_id(self.material_set_item) @@ -706,7 +717,8 @@ class DisableEditingMaterialSetItem(bpy.types.Operator): def execute(self, context): obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object - props = obj.BIMObjectMaterialProperties + assert obj + props = tool.Material.get_object_material_props(obj) props.active_material_set_item_id = 0 return {"FINISHED"} @@ -721,10 +733,13 @@ class EditMaterialSetItem(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object self.file = tool.Ifc.get() - props = obj.BIMObjectMaterialProperties + assert obj + props = tool.Material.get_object_material_props(obj) mprops = tool.Material.get_material_props() element = tool.Ifc.get_entity(obj) + assert element material = ifcopenshell.util.element.get_material(element, should_skip_usage=True) + assert material attributes = bonsai.bim.helper.export_attributes(props.material_set_item_attributes) @@ -733,7 +748,7 @@ class EditMaterialSetItem(bpy.types.Operator, tool.Ifc.Operator): self.file, constituent=self.file.by_id(self.material_set_item), attributes=attributes, - material=self.file.by_id(int(obj.BIMObjectMaterialProperties.material_set_item_material)), + material=self.file.by_id(int(props.material_set_item_material)), ) elif material.is_a("IfcMaterialLayerSet"): layer = self.file.by_id(self.material_set_item) @@ -741,7 +756,7 @@ class EditMaterialSetItem(bpy.types.Operator, tool.Ifc.Operator): self.file, layer=layer, attributes=attributes, - material=self.file.by_id(int(obj.BIMObjectMaterialProperties.material_set_item_material)), + material=self.file.by_id(int(props.material_set_item_material)), ) slab.DumbSlabPlaner().regenerate_from_layer(layer) wall.DumbWallPlaner().regenerate_from_layer(layer) @@ -755,7 +770,7 @@ class EditMaterialSetItem(bpy.types.Operator, tool.Ifc.Operator): profile=self.file.by_id(self.material_set_item), attributes=attributes, profile_def=profile_def, - material=self.file.by_id(int(obj.BIMObjectMaterialProperties.material_set_item_material)), + material=self.file.by_id(int(props.material_set_item_material)), ) else: pass diff --git a/src/bonsai/bonsai/bim/module/material/ui.py b/src/bonsai/bonsai/bim/module/material/ui.py index 30a7688db5..b743d37ef1 100644 --- a/src/bonsai/bonsai/bim/module/material/ui.py +++ b/src/bonsai/bonsai/bim/module/material/ui.py @@ -156,7 +156,7 @@ class BIM_PT_object_material(Panel): assert obj self.file = tool.Ifc.get() self.oprops = tool.Blender.get_object_bim_props(obj) - self.props = obj.BIMObjectMaterialProperties + self.props = tool.Material.get_object_material_props(obj) self.mprops = tool.Material.get_material_props() if not ObjectMaterialData.data["materials"]: diff --git a/src/bonsai/bonsai/bim/module/model/slab.py b/src/bonsai/bonsai/bim/module/model/slab.py index cb6ba498a2..04b22e8060 100644 --- a/src/bonsai/bonsai/bim/module/model/slab.py +++ b/src/bonsai/bonsai/bim/module/model/slab.py @@ -21,6 +21,7 @@ import json import bmesh import ifcopenshell import ifcopenshell.api +import ifcopenshell.api.geometry import ifcopenshell.util.element import ifcopenshell.util.placement import ifcopenshell.util.representation @@ -343,8 +344,7 @@ class DumbSlabPlaner: else: props = tool.Model.get_model_props() x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else props.x_angle - new_rep = ifcopenshell.api.run( - "geometry.add_slab_representation", + new_rep = ifcopenshell.api.geometry.add_slab_representation( tool.Ifc.get(), context=body_context, depth=thickness * self.unit_scale, @@ -368,15 +368,14 @@ class DumbSlabPlaner: else: props = tool.Model.get_model_props() x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else props.x_angle - representation = ifcopenshell.api.run( - "geometry.add_slab_representation", + representation = ifcopenshell.api.geometry.add_slab_representation( tool.Ifc.get(), context=body_context, depth=thickness * self.unit_scale, x_angle=x_angle, ) - ifcopenshell.api.run( - "geometry.assign_representation", tool.Ifc.get(), product=element, representation=representation + ifcopenshell.api.geometry.assign_representation( + tool.Ifc.get(), product=element, representation=representation ) bonsai.core.geometry.switch_representation( diff --git a/src/bonsai/bonsai/bim/module/profile/prop.py b/src/bonsai/bonsai/bim/module/profile/prop.py index 3f25c5f4e4..d63c555689 100644 --- a/src/bonsai/bonsai/bim/module/profile/prop.py +++ b/src/bonsai/bonsai/bim/module/profile/prop.py @@ -37,7 +37,7 @@ from bpy.props import ( from typing import TYPE_CHECKING, Union -def get_profile_classes(self, context): +def get_profile_classes(self: "BIMProfileProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]: if not ProfileData.is_loaded: ProfileData.load() return ProfileData.data["profile_classes"] @@ -62,7 +62,7 @@ class Profile(PropertyGroup): ifc_definition_id: int -def update_active_profile_index(self, context): +def update_active_profile_index(self: "BIMProfileProperties", context: bpy.types.Context) -> None: ProfileData.data["active_profile_users"] = ProfileData.active_profile_users() diff --git a/src/bonsai/bonsai/bim/module/pset/data.py b/src/bonsai/bonsai/bim/module/pset/data.py index 88d830d976..0c015f784a 100644 --- a/src/bonsai/bonsai/bim/module/pset/data.py +++ b/src/bonsai/bonsai/bim/module/pset/data.py @@ -212,7 +212,9 @@ class MaterialSetItemPsetsData(Data): @classmethod def load(cls): psets = {} - ifc_definition_id = bpy.context.active_object.BIMObjectMaterialProperties.active_material_set_item_id + obj = bpy.context.active_object + assert obj + ifc_definition_id = tool.Material.get_object_material_props(obj).active_material_set_item_id if ifc_definition_id: psets = cls.psetqtos(tool.Ifc.get().by_id(ifc_definition_id)) cls.data = {"psets": psets} diff --git a/src/bonsai/bonsai/bim/module/pset/prop.py b/src/bonsai/bonsai/bim/module/pset/prop.py index 6a1b68841f..81768c6f95 100644 --- a/src/bonsai/bonsai/bim/module/pset/prop.py +++ b/src/bonsai/bonsai/bim/module/pset/prop.py @@ -109,10 +109,12 @@ def get_material_set_pset_names(self, context): return psetnames[ifc_class] -def get_material_set_item_pset_names(self, context): +def get_material_set_item_pset_names(self, context) -> list[tuple[str, str, str]]: global psetnames - ifc_definition_id = context.active_object.BIMObjectMaterialProperties.active_material_set_item_id - if not ifc_definition_id: + obj = context.active_object + assert obj + omprops = tool.Material.get_object_material_props(obj) + if not omprops.active_material_set_item_id: return [] ifc_class = tool.Ifc.get().by_id(ifc_definition_id).is_a() if ifc_class not in psetnames: diff --git a/src/bonsai/bonsai/bim/module/pset/ui.py b/src/bonsai/bonsai/bim/module/pset/ui.py index c155937da9..de8cea8a95 100644 --- a/src/bonsai/bonsai/bim/module/pset/ui.py +++ b/src/bonsai/bonsai/bim/module/pset/ui.py @@ -459,7 +459,8 @@ class BIM_PT_material_set_item_psets(Panel): obj = context.active_object assert obj - if not obj.BIMObjectMaterialProperties.active_material_set_item_id: + omprops = tool.Material.get_object_material_props(obj) + if not omprops.active_material_set_item_id: self.layout.label(text="No Material Set Item Edited.") return diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index c8846ace19..da85bb734f 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -44,6 +44,7 @@ from typing_extensions import assert_never if TYPE_CHECKING: from bonsai.bim.prop import BIMProperties, BIMObjectProperties + T = TypeVar("T") VIEWPORT_ATTRIBUTES = [ "view_matrix", @@ -199,7 +200,9 @@ class Blender(bonsai.core.tool.Blender): props = tool.Material.get_material_props() return props.materials[props.active_material_index].ifc_definition_id elif obj_type == "MaterialSetItem": - return bpy.data.objects.get(obj).BIMObjectMaterialProperties.active_material_set_item_id + obj_ = bpy.data.objects[obj] + omprops = tool.Material.get_object_material_props(obj_) + return omprops.active_material_set_item_id elif obj_type == "Task": tprops = tool.Sequence.get_task_tree_props() return tprops.tasks[context.scene.BIMWorkScheduleProperties.active_task_index].ifc_definition_id @@ -1466,9 +1469,9 @@ class Blender(bonsai.core.tool.Blender): def set_prop_from_path(cls, bpy_object: bpy.types.bpy_struct, prop_path: str, value: Any) -> None: """Set `data_block` property value using path from `path_from_id`.""" - T = TypeVar("T", bound=bpy.types.bpy_struct) + T_ = TypeVar("T_", bound=bpy.types.bpy_struct) - def path_resolve(obj: T, prop_path: str) -> tuple[T, str]: + def path_resolve(obj: T_, prop_path: str) -> tuple[T_, str]: if "." in prop_path: extra_path, prop_path = prop_path.rsplit(".", 1) obj = obj.path_resolve(extra_path) @@ -1605,3 +1608,11 @@ class Blender(bonsai.core.tool.Blender): if isinstance(obj, bpy.types.Object): return tool.Blender.get_object_bim_props(obj).ifc_definition_id return tool.Style.get_material_style_props(obj).ifc_definition_id + + @classmethod + def get_active_uilist_element( + cls, collection: bpy.types.bpy_prop_collection_idprop[T], index: int + ) -> Union[T, None]: + if 0 <= index < len(collection): + return collection[index] + return None diff --git a/src/bonsai/bonsai/tool/material.py b/src/bonsai/bonsai/tool/material.py index 0d32bb1ebd..0139a1b783 100644 --- a/src/bonsai/bonsai/tool/material.py +++ b/src/bonsai/bonsai/tool/material.py @@ -33,7 +33,7 @@ from typing_extensions import assert_never if TYPE_CHECKING: # Avoid circular imports. from bonsai.bim.module.material.prop import Material as MaterialItem - from bonsai.bim.module.material.prop import BIMMaterialProperties + from bonsai.bim.module.material.prop import BIMMaterialProperties, BIMObjectMaterialProperties class Material(bonsai.core.tool.Material): @@ -41,6 +41,10 @@ class Material(bonsai.core.tool.Material): def get_material_props(cls) -> BIMMaterialProperties: return bpy.context.scene.BIMMaterialProperties + @classmethod + def get_object_material_props(cls, obj: bpy.types.Object) -> BIMObjectMaterialProperties: + return obj.BIMObjectMaterialProperties + @classmethod def disable_editing_materials(cls) -> None: props = tool.Material.get_material_props() @@ -203,11 +207,14 @@ class Material(bonsai.core.tool.Material): @classmethod def get_object_ui_material_type(cls) -> str: active_obj = bpy.context.active_object - return active_obj.BIMObjectMaterialProperties.material_type + assert active_obj + return tool.Material.get_object_material_props(active_obj).material_type @classmethod def get_object_ui_active_material(cls) -> ifcopenshell.entity_instance: - return tool.Ifc.get().by_id(int(bpy.context.active_object.BIMObjectMaterialProperties.material)) + obj = bpy.context.active_object + assert obj + return tool.Ifc.get().by_id(int(tool.Material.get_object_material_props(obj).material)) @classmethod def get_material( diff --git a/src/bonsai/bonsai/tool/profile.py b/src/bonsai/bonsai/tool/profile.py index ae8b695b1c..5c14828892 100644 --- a/src/bonsai/bonsai/tool/profile.py +++ b/src/bonsai/bonsai/tool/profile.py @@ -123,16 +123,13 @@ class Profile(bonsai.core.tool.Profile): @classmethod def get_active_profile_ui(cls) -> Union[bonsai.bim.module.profile.prop.Profile, None]: props = cls.get_profile_props() - index = props.active_profile_index - if len(props.profiles) > index >= 0: - return props.profiles[index] + return tool.Blender.get_active_uilist_element(props.profiles, props.active_profile_index) # Lengths are in meters. DEFAULT_PROFILE_ATTRS = { "IfcCircleProfileDef": { "Radius": 0.05, }, - # TODO: test after debug "IfcAsymmetricIShapeProfileDef": { "BottomFlangeWidth": 0.1, "BottomFlangeThickness": 0.01, diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_slab_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_slab_representation.py index 3ceca7094c..c0d78872f4 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_slab_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_slab_representation.py @@ -27,10 +27,11 @@ def add_slab_representation( file: ifcopenshell.file, context: ifcopenshell.entity_instance, depth: float = 0.2, + # TODO: document remaining args. direction_sense: str = "POSITIVE", offset: float = 0.0, x_angle: float = 0.0, - clippings: Optional[list[Union[Clipping, dict[str, Any]]]] = None, + clippings: Optional[list[Union[Clipping, ifcopenshell.entity_instance]]] = None, polyline: Optional[list[tuple[float, float]]] = None, ) -> ifcopenshell.entity_instance: """ @@ -55,60 +56,74 @@ def add_slab_representation( """ usecase = Usecase() usecase.file = file - usecase.settings = { - "context": context, - "depth": depth, - "direction_sense": direction_sense, - "offset": offset, - "x_angle": x_angle, - "clippings": clippings if clippings is not None else [], - "polyline": polyline, - } - return usecase.execute() + return usecase.execute( + context, + depth, + direction_sense, + offset, + x_angle, + clippings if clippings is not None else [], + polyline, + ) class Usecase: file: ifcopenshell.file - settings: dict[str, Any] - def execute(self): - self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file) - return self.file.createIfcShapeRepresentation( - self.settings["context"], - self.settings["context"].ContextIdentifier, - "Clipping" if self.settings["clippings"] else "SweptSolid", + def execute( + self, + context: ifcopenshell.entity_instance, + depth: float, + direction_sense: str, + offset: float, + x_angle: float, + clippings: list[Union[Clipping, ifcopenshell.entity_instance]], + polyline: Optional[list[tuple[float, float]]], + ) -> ifcopenshell.entity_instance: + self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file) + self.clippings = clippings + self.depth = depth + self.direction_sense = direction_sense + self.offset = offset + self.x_angle = x_angle + self.polyline = polyline + return self.file.create_entity( + "IfcShapeRepresentation", + context, + context.ContextIdentifier, + "Clipping" if self.clippings else "SweptSolid", [self.create_item()], ) - def create_item(self): + def create_item(self) -> ifcopenshell.entity_instance: size = self.convert_si_to_unit(1) points = ((0.0, 0.0), (size, 0.0), (size, size), (0.0, size), (0.0, 0.0)) - if self.settings["polyline"]: + if self.polyline: points = [ - (self.convert_si_to_unit(p[0]), self.convert_si_to_unit(p[1] * abs(1 / cos(self.settings["x_angle"])))) - for p in self.settings["polyline"] + (self.convert_si_to_unit(p[0]), self.convert_si_to_unit(p[1] * abs(1 / cos(self.x_angle)))) + for p in self.polyline ] if self.file.schema == "IFC2X3": curve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint(p) for p in points]) else: curve = self.file.createIfcIndexedPolyCurve(self.file.createIfcCartesianPointList2D(points)) - if self.settings["x_angle"]: - direction_ratios = (0.0, sin(self.settings["x_angle"]), cos(self.settings["x_angle"])) + if self.x_angle: + direction_ratios = (0.0, sin(self.x_angle), cos(self.x_angle)) else: direction_ratios = (0.0, 0.0, 1.0) offset_direction = direction_ratios # offset direction doesn't change if direction_sense is negative extrusion_direction = self.file.createIfcDirection(direction_ratios) - if self.settings["direction_sense"] == "NEGATIVE": + if self.direction_sense == "NEGATIVE": direction_ratios = tuple((-n for n in direction_ratios)) extrusion_direction = self.file.createIfcDirection(direction_ratios) - perpendicular_offset = self.convert_si_to_unit(self.settings["offset"]) * abs(1 / cos(self.settings["x_angle"])) - perpendicular_depth = self.convert_si_to_unit(self.settings["depth"]) * abs(1 / cos(self.settings["x_angle"])) + perpendicular_offset = self.convert_si_to_unit(self.offset) * abs(1 / cos(self.x_angle)) + perpendicular_depth = self.convert_si_to_unit(self.depth) * abs(1 / cos(self.x_angle)) position = None # default position for IFC2X3 where .Position is not optional - if self.file.schema == "IFC2X3" or self.settings["offset"] != 0: + if self.file.schema == "IFC2X3" or self.offset != 0: position_vector = ( offset_direction[0] * perpendicular_offset, offset_direction[1] * perpendicular_offset, @@ -120,26 +135,27 @@ class Usecase: self.file.createIfcDirection((1.0, 0.0, 0.0)), ) - extrusion = self.file.createIfcExtrudedAreaSolid( + extrusion = self.file.create_entity( + "IfcExtrudedAreaSolid", self.file.createIfcArbitraryClosedProfileDef("AREA", None, curve), position, extrusion_direction, perpendicular_depth, ) - if self.settings["clippings"]: + if self.clippings: return self.apply_clippings(extrusion) return extrusion - def apply_clippings(self, first_operand): - while self.settings["clippings"]: - clipping = self.settings["clippings"].pop() + def apply_clippings(self, first_operand: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance: + while self.clippings: + clipping = self.clippings.pop() if isinstance(clipping, ifcopenshell.entity_instance): new = ifcopenshell.util.element.copy(self.file, clipping) new.FirstOperand = first_operand first_operand = new else: # Clipping - first_operand = clipping.apply(self.file, first_operand, self.settings["unit_scale"]) + first_operand = clipping.apply(self.file, first_operand, self.unit_scale) return first_operand - def convert_si_to_unit(self, co): - return co / self.settings["unit_scale"] + def convert_si_to_unit(self, co: float) -> float: + return co / self.unit_scale diff --git a/src/ifcopenshell-python/ifcopenshell/api/group/assign_group.py b/src/ifcopenshell-python/ifcopenshell/api/group/assign_group.py index 4972058fb4..4a57f148eb 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/group/assign_group.py +++ b/src/ifcopenshell-python/ifcopenshell/api/group/assign_group.py @@ -31,12 +31,9 @@ def assign_group( twice. :param products: A list of IfcProduct elements to assign to the group - :type products: list[ifcopenshell.entity_instance] :param group: The IfcGroup to assign the products to - :type group: ifcopenshell.entity_instance :return: The IfcRelAssignsToGroup relationship or `None` if `products` was empty list. - :rtype: Union[ifcopenshell.entity_instance, None] Example: diff --git a/src/ifcopenshell-python/ifcopenshell/api/system/assign_system.py b/src/ifcopenshell-python/ifcopenshell/api/system/assign_system.py index 4e6f5b89ee..8677f748b4 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/system/assign_system.py +++ b/src/ifcopenshell-python/ifcopenshell/api/system/assign_system.py @@ -19,24 +19,22 @@ import ifcopenshell import ifcopenshell.api.group import ifcopenshell.util.system +from typing import Union def assign_system( file: ifcopenshell.file, products: list[ifcopenshell.entity_instance], system: ifcopenshell.entity_instance, -) -> None: +) -> Union[ifcopenshell.entity_instance, None]: """Assigns distribution elements to a system Note that it is not necessary to assign distribution ports to a system. :param products: The list of IfcDistributionElements to assign to the system. - :type products: list[ifcopenshell.entity_instance] :param system: The IfcSystem you want to assign the element to. - :type system: ifcopenshell.entity_instance :return: The IfcRelAssignsToGroup relationship or `None` if `products` was empty list. - :rtype: [ifcopenshell.entity_instance, None] Example: @@ -52,14 +50,6 @@ def assign_system( # This duct is part of the system ifcopenshell.api.system.assign_system(model, products=[duct], system=system) """ - settings = { - "products": products, - "system": system, - } - - system = settings["system"] - products = settings["products"] - if not all(ifcopenshell.util.system.is_assignable(failed_product := product, system) for product in products): raise TypeError(f"You cannot assign an {failed_product.is_a()} to an {system.is_a()}") diff --git a/src/ifcpatch/ifcpatch/recipes/ResetSpatialElementLocations.py b/src/ifcpatch/ifcpatch/recipes/ResetSpatialElementLocations.py index aa335320ba..a02ba69285 100644 --- a/src/ifcpatch/ifcpatch/recipes/ResetSpatialElementLocations.py +++ b/src/ifcpatch/ifcpatch/recipes/ResetSpatialElementLocations.py @@ -16,9 +16,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with IfcPatch. If not, see . +import ifcopenshell +import logging + class Patcher: - def __init__(self, file, logger, ifc_class="IfcSite"): + def __init__(self, file: ifcopenshell.file, logger: logging.Logger, ifc_class: str = "IfcSite"): """Resets the location of a spatial element to 0,0,0 Another more specialised patch to fix incorrect coordinate usage is to @@ -26,7 +29,6 @@ class Patcher: to 0,0,0. :param ifc_class: The class of spatial element to reset coordinates for. - :type ifc_class: str Example: @@ -39,13 +41,15 @@ class Patcher: self.logger = logger self.ifc_class = ifc_class - def patch(self): + def patch(self) -> None: project = self.file.by_type("IfcProject")[0] spatial_elements = self.find_decomposed_ifc_class(project, self.ifc_class) for spatial_element in spatial_elements: self.patch_placement_to_origin(spatial_element) - def find_decomposed_ifc_class(self, element, ifc_class): + def find_decomposed_ifc_class( + self, element: ifcopenshell.entity_instance, ifc_class: str + ) -> list[ifcopenshell.entity_instance]: results = [] rel_aggregates = element.IsDecomposedBy if not rel_aggregates: @@ -57,7 +61,7 @@ class Patcher: results.extend(self.find_decomposed_ifc_class(part, ifc_class)) return results - def patch_placement_to_origin(self, element): + def patch_placement_to_origin(self, element: ifcopenshell.entity_instance) -> None: element.ObjectPlacement.RelativePlacement.Location.Coordinates = (0.0, 0.0, 0.0) if element.ObjectPlacement.RelativePlacement.Axis: element.ObjectPlacement.RelativePlacement.Axis.DirectionRatios = (0.0, 0.0, 1.0)