diff --git a/src/bonsai/bonsai/bim/module/pset/data.py b/src/bonsai/bonsai/bim/module/pset/data.py index b71a7fabb6..ab89d67b31 100644 --- a/src/bonsai/bonsai/bim/module/pset/data.py +++ b/src/bonsai/bonsai/bim/module/pset/data.py @@ -117,7 +117,10 @@ class ObjectPsetsData(Data): if not element: return [] psets = bonsai.bim.schema.ifc.psetqto.get_applicable( - element.is_a(), ifcopenshell.util.element.get_predefined_type(element), pset_only=True + element.is_a(), + ifcopenshell.util.element.get_predefined_type(element), + pset_only=True, + schema=tool.Ifc.get_schema(), ) psetnames = cls.format_pset_enum(psets) assigned_names = ifcopenshell.util.element.get_psets(element, psets_only=True, should_inherit=False).keys() @@ -130,7 +133,10 @@ class ObjectPsetsData(Data): if not element: return [] qtos = bonsai.bim.schema.ifc.psetqto.get_applicable( - element.is_a(), ifcopenshell.util.element.get_predefined_type(element), qto_only=True + element.is_a(), + ifcopenshell.util.element.get_predefined_type(element), + qto_only=True, + schema=tool.Ifc.get_schema(), ) return cls.format_pset_enum(qtos) @@ -188,7 +194,9 @@ class MaterialPsetsData(Data): if material.ifc_definition_id: material = tool.Ifc.get().by_id(material.ifc_definition_id) category = getattr(material, "Category", None) or None - psets = bonsai.bim.schema.ifc.psetqto.get_applicable(props.material_type, category, pset_only=True) + psets = bonsai.bim.schema.ifc.psetqto.get_applicable( + props.material_type, category, pset_only=True, schema=tool.Ifc.get_schema() + ) psetnames = cls.format_pset_enum(psets) assigned_names = ifcopenshell.util.element.get_psets( material, psets_only=True, should_inherit=False diff --git a/src/bonsai/bonsai/bim/module/pset/prop.py b/src/bonsai/bonsai/bim/module/pset/prop.py index 1edf364167..2014ff1bd6 100644 --- a/src/bonsai/bonsai/bim/module/pset/prop.py +++ b/src/bonsai/bonsai/bim/module/pset/prop.py @@ -105,7 +105,7 @@ def get_material_set_pset_names(self, context): if not ifc_class or "Set" not in ifc_class: return [] if ifc_class not in psetnames: - psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True) + psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True, schema=tool.Ifc.get_schema()) psetnames[ifc_class] = blender_formatted_enum_from_psets(psets) return psetnames[ifc_class] @@ -117,7 +117,7 @@ def get_material_set_item_pset_names(self, context): return [] ifc_class = tool.Ifc.get().by_id(ifc_definition_id).is_a() if ifc_class not in psetnames: - psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True) + psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True, schema=tool.Ifc.get_schema()) psetnames[ifc_class] = blender_formatted_enum_from_psets(psets) return psetnames[ifc_class] @@ -126,7 +126,7 @@ def get_task_qto_names(self, context): global qtonames ifc_class = "IfcTask" if ifc_class not in qtonames: - psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True) + psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True, schema=tool.Ifc.get_schema()) qtonames[ifc_class] = blender_formatted_enum_from_psets(psets) return qtonames[ifc_class] @@ -137,7 +137,7 @@ def get_resource_pset_names(self, context): rtprops = context.scene.BIMResourceTreeProperties ifc_class = IfcStore.get_file().by_id(rtprops.resources[rprops.active_resource_index].ifc_definition_id).is_a() if ifc_class not in psetnames: - psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True) + psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True, schema=tool.Ifc.get_schema()) psetnames[ifc_class] = blender_formatted_enum_from_psets(psets) return psetnames[ifc_class] @@ -148,7 +148,7 @@ def get_resource_qto_names(self, context): rtprops = context.scene.BIMResourceTreeProperties ifc_class = IfcStore.get_file().by_id(rtprops.resources[rprops.active_resource_index].ifc_definition_id).is_a() if ifc_class not in qtonames: - psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True) + psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True, schema=tool.Ifc.get_schema()) qtonames[ifc_class] = blender_formatted_enum_from_psets(psets) return qtonames[ifc_class] @@ -157,7 +157,7 @@ def get_group_pset_names(self, context): global psetnames ifc_class = "IfcGroup" if ifc_class not in psetnames: - psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True) + psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True, schema=tool.Ifc.get_schema()) psetnames[ifc_class] = blender_formatted_enum_from_psets(psets) return psetnames[ifc_class] @@ -166,7 +166,7 @@ def get_group_qto_names(self, context): global qtonames ifc_class = "IfcGroup" if ifc_class not in qtonames: - psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True) + psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True, schema=tool.Ifc.get_schema()) qtonames[ifc_class] = blender_formatted_enum_from_psets(psets) return qtonames[ifc_class] @@ -176,7 +176,7 @@ def get_profile_pset_names(self, context): pprops = context.scene.BIMProfileProperties ifc_class = IfcStore.get_file().by_id(pprops.profiles[pprops.active_profile_index].ifc_definition_id).is_a() if ifc_class not in psetnames: - psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True) + psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True, schema=tool.Ifc.get_schema()) psetnames[ifc_class] = blender_formatted_enum_from_psets(psets) return psetnames[ifc_class] @@ -185,7 +185,7 @@ def get_work_schedule_pset_names(self, context): global psetnames ifc_class = "IfcWorkSchedule" if ifc_class not in psetnames: - psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True) + psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True, schema=tool.Ifc.get_schema()) psetnames[ifc_class] = blender_formatted_enum_from_psets(psets) return psetnames[ifc_class] diff --git a/src/bonsai/bonsai/bim/module/root/operator.py b/src/bonsai/bonsai/bim/module/root/operator.py index 9c82d71c97..3579f0b89d 100644 --- a/src/bonsai/bonsai/bim/module/root/operator.py +++ b/src/bonsai/bonsai/bim/module/root/operator.py @@ -97,7 +97,7 @@ class ReassignClass(bpy.types.Operator, tool.Ifc.Operator): root_props = context.scene.BIMRootProperties ifc_product: str = root_props.ifc_product ifc_class: str = root_props.ifc_class - type_ifc_class = next(iter(ifcopenshell.util.type.get_applicable_types(ifc_class)), None) + type_ifc_class = next(iter(ifcopenshell.util.type.get_applicable_types(ifc_class, self.file.schema)), None) predefined_type = root_props.ifc_predefined_type if predefined_type == "USERDEFINED": diff --git a/src/bonsai/bonsai/bim/module/root/prop.py b/src/bonsai/bonsai/bim/module/root/prop.py index 9e4e4ab1fc..5243d95c03 100644 --- a/src/bonsai/bonsai/bim/module/root/prop.py +++ b/src/bonsai/bonsai/bim/module/root/prop.py @@ -67,11 +67,12 @@ def refresh_classes(self, context): self.representation_template = "OBJ" self.representation_obj = obj + ifc_file = tool.Ifc.get() # When switching between ElementType and Element, keep the same class and predefined type if possible if self.ifc_product == "IfcElement": - ifc_class = next(iter(ifcopenshell.util.type.get_applicable_entities(old_class)), None) + ifc_class = next(iter(ifcopenshell.util.type.get_applicable_entities(old_class, ifc_file.schema)), None) elif self.ifc_product == "IfcElementType": - ifc_class = next(iter(ifcopenshell.util.type.get_applicable_types(old_class)), None) + ifc_class = next(iter(ifcopenshell.util.type.get_applicable_types(old_class, ifc_file.schema)), None) else: return if ifc_class: diff --git a/src/bonsai/bonsai/tool/pset.py b/src/bonsai/bonsai/tool/pset.py index bcac21d7ca..39e4b0d721 100644 --- a/src/bonsai/bonsai/tool/pset.py +++ b/src/bonsai/bonsai/tool/pset.py @@ -81,7 +81,9 @@ class Pset(bonsai.core.tool.Pset): predefined_type = ifcopenshell.util.element.get_predefined_type(element) return bool( pset_name - in bonsai.bim.schema.ifc.psetqto.get_applicable_names(element.is_a(), predefined_type, pset_only=True) + in bonsai.bim.schema.ifc.psetqto.get_applicable_names( + element.is_a(), predefined_type, pset_only=True, schema=tool.Ifc.get_schema() + ) ) @classmethod diff --git a/src/ifc5d/ifc5d/qto.py b/src/ifc5d/ifc5d/qto.py index 2736a82bf2..e23ae2f442 100644 --- a/src/ifc5d/ifc5d/qto.py +++ b/src/ifc5d/ifc5d/qto.py @@ -61,7 +61,7 @@ def quantify(ifc_file: ifcopenshell.file, elements: set[ifcopenshell.entity_inst calculator = calculators[calculator] for ifc_class, qtos in queries.items(): filtered_elements = set() - ifc_classes = [ifc_class] + ifcopenshell.util.type.get_applicable_types(ifc_class) + ifc_classes = [ifc_class] + ifcopenshell.util.type.get_applicable_types(ifc_class, ifc_file.schema) for ifc_class in ifc_classes: if ifc_class not in elements_by_classes: continue diff --git a/src/ifcopenshell-python/ifcopenshell/api/root/reassign_class.py b/src/ifcopenshell-python/ifcopenshell/api/root/reassign_class.py index e9ac4b6891..3e4cd3faf4 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/root/reassign_class.py +++ b/src/ifcopenshell-python/ifcopenshell/api/root/reassign_class.py @@ -176,12 +176,12 @@ class Usecase: element = self.reassign_class(element, ifc_class, predefined_type) if element.is_a("IfcTypeProduct"): for occurrence in ifcopenshell.util.element.get_types(element): - ifc_class_ = ifcopenshell.util.type.get_applicable_entities(ifc_class)[0] + ifc_class_ = ifcopenshell.util.type.get_applicable_entities(ifc_class, self.file.schema)[0] self.reassign_class(occurrence, ifc_class_, predefined_type) else: element_type = ifcopenshell.util.element.get_type(element) if element_type: - ifc_class_ = next(iter(ifcopenshell.util.type.get_applicable_types(ifc_class))) + ifc_class_ = next(iter(ifcopenshell.util.type.get_applicable_types(ifc_class, self.file.schema))) element_type = self.reassign_class(element_type, ifc_class_, predefined_type) ifc_class = element.is_a() for occurrence in ifcopenshell.util.element.get_types(element_type): diff --git a/src/ifcopenshell-python/ifcopenshell/util/pset.py b/src/ifcopenshell-python/ifcopenshell/util/pset.py index 4d9cf699e8..08b10996c0 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/pset.py +++ b/src/ifcopenshell-python/ifcopenshell/util/pset.py @@ -19,11 +19,12 @@ import re import pathlib import ifcopenshell +import ifcopenshell.ifcopenshell_wrapper as W import ifcopenshell.util.schema import ifcopenshell.util.type from ifcopenshell.entity_instance import entity_instance from functools import lru_cache -from typing import List, Optional, Literal +from typing import Optional, Literal templates: dict[str, "PsetQto"] = {} @@ -62,10 +63,16 @@ class PsetQto: @lru_cache() def get_applicable( - self, ifc_class="", predefined_type="", pset_only=False, qto_only=False - ) -> List[entity_instance]: + self, + ifc_class="", + predefined_type="", + pset_only=False, + qto_only=False, + schema: ifcopenshell.util.schema.IFC_SCHEMA = "IFC4", + ) -> list[entity_instance]: any_class = not ifc_class if not any_class: + entity: W.entity entity = self.schema.declaration_by_name(ifc_class) result = [] for template in self.templates: @@ -77,18 +84,32 @@ class PsetQto: if prop_set.TemplateType and prop_set.TemplateType.startswith("PSET_"): continue if any_class or self.is_applicable( - entity, prop_set.ApplicableEntity or "IfcRoot", predefined_type, prop_set.TemplateType + entity, prop_set.ApplicableEntity or "IfcRoot", predefined_type, prop_set.TemplateType, schema ): result.append(prop_set) return result @lru_cache() - def get_applicable_names(self, ifc_class: str, predefined_type="", pset_only=False, qto_only=False) -> List[str]: + def get_applicable_names( + self, + ifc_class: str, + predefined_type: str = "", + pset_only: bool = False, + qto_only: bool = False, + schema: ifcopenshell.util.schema.IFC_SCHEMA = "IFC4", + ) -> list[str]: """Return names instead of objects for other use eg. enum""" - return [prop_set.Name for prop_set in self.get_applicable(ifc_class, predefined_type, pset_only, qto_only)] + return [ + prop_set.Name for prop_set in self.get_applicable(ifc_class, predefined_type, pset_only, qto_only, schema) + ] def is_applicable( - self, entity: entity_instance, applicables: str, predefined_type="", template_type="NOTDEFINED" + self, + entity: W.entity, + applicables: str, + predefined_type: str = "", + template_type: str = "NOTDEFINED", + schema: ifcopenshell.util.schema.IFC_SCHEMA = "IFC4", ) -> bool: """applicables can have multiple possible patterns : IfcBoilerType (IfcClass) @@ -118,7 +139,7 @@ class PsetQto: # This will be fixed in IFC4.3 template_type = template_type or "" if "TYPE" in template_type and ifcopenshell.util.schema.is_a(entity, "IfcTypeObject"): - types = ifcopenshell.util.type.get_applicable_types(applicable_class, "IFC4") + types = ifcopenshell.util.type.get_applicable_types(applicable_class, schema) if not types: # Abstract classes will not have an "applicable type" but # the implementer agreement still applies to them. diff --git a/src/ifcopenshell-python/ifcopenshell/util/type.py b/src/ifcopenshell-python/ifcopenshell/util/type.py index fed14d69a0..8ff0fe1a3c 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/type.py +++ b/src/ifcopenshell-python/ifcopenshell/util/type.py @@ -51,7 +51,7 @@ for schema in mapped_schemas: type_to_entity_map[schema][element_type] = [e for e in elements if guessed_element in e] -def get_applicable_types(ifc_class: str, schema="IFC4") -> list[str]: +def get_applicable_types(ifc_class: str, schema: ifcopenshell.util.schema.IFC_SCHEMA = "IFC4") -> list[str]: """Get applicable types IFC classes for the occurrence IFC class. E.g. "IfcWindow" -> ["IfcWindowType"]. @@ -60,7 +60,7 @@ def get_applicable_types(ifc_class: str, schema="IFC4") -> list[str]: return entity_to_type_map[schema].get(ifc_class, []) -def get_applicable_entities(ifc_type_class: str, schema="IFC4") -> list[str]: +def get_applicable_entities(ifc_type_class: str, schema: ifcopenshell.util.schema.IFC_SCHEMA = "IFC4") -> list[str]: """Get applicable occurrence IFC classes for the type IFC class. E.g. "IfcWindowType" -> ["IfcWindow"]. diff --git a/src/ifcopenshell-python/test/util/test_pset.py b/src/ifcopenshell-python/test/util/test_pset.py index de3b7b3bf2..90b90f6e35 100644 --- a/src/ifcopenshell-python/test/util/test_pset.py +++ b/src/ifcopenshell-python/test/util/test_pset.py @@ -24,7 +24,7 @@ from ifcopenshell import util class TestPsetQto: @classmethod def setup_class(cls): - cls.pset_qto = util.pset.PsetQto("IFC4") + cls.pset_qto = pset.PsetQto("IFC4") def test_get_applicables(self): for i in range(1000): diff --git a/src/ifcpatch/ifcpatch/recipes/ConvertPropertiesToQuantities.py b/src/ifcpatch/ifcpatch/recipes/ConvertPropertiesToQuantities.py index 969461ecc2..5667defdbb 100644 --- a/src/ifcpatch/ifcpatch/recipes/ConvertPropertiesToQuantities.py +++ b/src/ifcpatch/ifcpatch/recipes/ConvertPropertiesToQuantities.py @@ -112,5 +112,7 @@ class Patcher: def get_qto_templates(self, ifc_class: str) -> list[ifcopenshell.entity_instance]: if ifc_class not in self.qto_template_cache: - self.qto_template_cache[ifc_class] = self.psetqto.get_applicable(ifc_class, qto_only=True) + self.qto_template_cache[ifc_class] = self.psetqto.get_applicable( + ifc_class, qto_only=True, schema=self.file.schema + ) return self.qto_template_cache[ifc_class]