2022-01-19 12:18:33 +11:00
|
|
|
# IfcOpenShell - IFC toolkit and geometry engine
|
|
|
|
|
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
|
|
|
|
#
|
|
|
|
|
# This file is part of IfcOpenShell.
|
|
|
|
|
#
|
|
|
|
|
# IfcOpenShell is free software: you can redistribute it and/or modify
|
|
|
|
|
# it under the terms of the GNU Lesser General Public License as published by
|
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
# (at your option) any later version.
|
|
|
|
|
#
|
|
|
|
|
# IfcOpenShell is distributed in the hope that it will be useful,
|
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
# GNU Lesser General Public License for more details.
|
|
|
|
|
#
|
|
|
|
|
# You should have received a copy of the GNU Lesser General Public License
|
|
|
|
|
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
2024-02-19 17:33:49 +05:00
|
|
|
from __future__ import annotations
|
2021-05-13 15:11:18 +10:00
|
|
|
import ifcopenshell
|
2024-03-14 14:03:29 +05:00
|
|
|
import ifcopenshell.util.element
|
2024-04-10 12:08:09 +05:00
|
|
|
from typing import Any, Callable, Optional, Union, Literal, overload
|
2024-03-11 12:06:56 +05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_pset(
|
|
|
|
|
element: ifcopenshell.entity_instance,
|
|
|
|
|
name: str,
|
2024-03-14 14:03:29 +05:00
|
|
|
prop: Optional[str] = None,
|
2024-03-11 12:06:56 +05:00
|
|
|
psets_only=False,
|
|
|
|
|
qtos_only=False,
|
|
|
|
|
should_inherit=True,
|
|
|
|
|
verbose=False,
|
2024-03-14 14:03:29 +05:00
|
|
|
) -> Union[Any, dict[str, Any]]:
|
2023-02-12 15:01:47 +11:00
|
|
|
"""Retrieve a single property set or single property
|
|
|
|
|
|
|
|
|
|
This is more efficient than ifcopenshell.util.element.get_psets if you know
|
|
|
|
|
exactly which property set and property you are after.
|
|
|
|
|
|
2023-07-04 14:24:09 +10:00
|
|
|
If should_inherit is true, the pset "id" only refers to the ID of the
|
|
|
|
|
occurrence, not the type's pset.
|
|
|
|
|
|
2023-02-12 15:01:47 +11:00
|
|
|
:param element: The IFC Element entity
|
|
|
|
|
:type element: ifcopenshell.entity_instance.entity_instance
|
|
|
|
|
:param name: The name of the pset
|
|
|
|
|
:type name: str
|
|
|
|
|
:param prop: The name of the property
|
2023-05-10 16:41:19 +02:00
|
|
|
:type prop: str,optional
|
2023-12-08 15:07:46 +11:00
|
|
|
:param psets_only: Default as False. Set to true if only property sets are needed.
|
|
|
|
|
:type psets_only: bool,optional
|
|
|
|
|
:param qtos_only: Default as False. Set to true if only quantities are needed.
|
|
|
|
|
:type qtos_only: bool,optional
|
2023-02-12 15:01:47 +11:00
|
|
|
:param should_inherit: Default as True. Set to false if you don't want to inherit property sets from the Type.
|
|
|
|
|
:type should_inherit: bool,optional
|
|
|
|
|
:return: A dictionary of property names and values, or a single value if a
|
|
|
|
|
property is specified.
|
2024-04-10 12:08:09 +05:00
|
|
|
:rtype: Union[Any, dict[str, Any]]
|
2023-02-12 15:01:47 +11:00
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
2024-04-10 12:08:09 +05:00
|
|
|
element = ifc_file.by_type("IfcWall")[0]
|
2023-02-12 15:01:47 +11:00
|
|
|
psets_and_qtos = ifcopenshell.util.element.get_pset(element, "Pset_WallCommon")
|
|
|
|
|
"""
|
|
|
|
|
pset = None
|
2023-07-04 14:24:09 +10:00
|
|
|
type_pset = None
|
|
|
|
|
|
2023-02-12 15:01:47 +11:00
|
|
|
if element.is_a("IfcTypeObject"):
|
|
|
|
|
for definition in element.HasPropertySets or []:
|
|
|
|
|
if definition.Name == name:
|
|
|
|
|
pset = definition
|
|
|
|
|
break
|
|
|
|
|
elif element.is_a("IfcMaterialDefinition") or element.is_a("IfcProfileDef"):
|
|
|
|
|
for definition in element.HasProperties or []:
|
|
|
|
|
if definition.Name == name:
|
|
|
|
|
pset = definition
|
|
|
|
|
break
|
2024-04-09 18:36:55 +05:00
|
|
|
elif (is_defined_by := getattr(element, "IsDefinedBy", None)) is not None:
|
2024-04-10 12:19:16 +05:00
|
|
|
# other IfcObjectDefinition
|
2023-07-04 14:24:09 +10:00
|
|
|
if should_inherit:
|
|
|
|
|
element_type = ifcopenshell.util.element.get_type(element)
|
|
|
|
|
if element_type:
|
2023-10-15 08:57:26 +11:00
|
|
|
type_pset = get_pset(element_type, name, prop, should_inherit=False, verbose=verbose)
|
2024-04-09 18:36:55 +05:00
|
|
|
for relationship in is_defined_by:
|
2023-02-12 15:01:47 +11:00
|
|
|
if relationship.is_a("IfcRelDefinesByProperties"):
|
|
|
|
|
definition = relationship.RelatingPropertyDefinition
|
|
|
|
|
if definition.Name == name:
|
|
|
|
|
pset = definition
|
|
|
|
|
break
|
|
|
|
|
|
2023-12-08 15:07:46 +11:00
|
|
|
if pset:
|
|
|
|
|
if psets_only and not pset.is_a("IfcPropertySet"):
|
|
|
|
|
pset = None
|
|
|
|
|
elif qtos_only and not pset.is_a("IfcElementQuantity"):
|
|
|
|
|
pset = None
|
|
|
|
|
|
2024-04-10 12:57:52 +10:00
|
|
|
if type_pset is not None:
|
2023-12-08 15:07:46 +11:00
|
|
|
if psets_only and not type_pset.is_a("IfcPropertySet"):
|
|
|
|
|
type_pset = None
|
|
|
|
|
elif qtos_only and not type_pset.is_a("IfcElementQuantity"):
|
|
|
|
|
type_pset = None
|
|
|
|
|
|
2024-04-10 12:19:16 +05:00
|
|
|
if pset is None and type_pset is None:
|
2023-07-05 10:36:43 +10:00
|
|
|
return
|
2023-02-12 15:01:47 +11:00
|
|
|
|
|
|
|
|
if not prop:
|
2023-07-04 14:24:09 +10:00
|
|
|
if type_pset:
|
2023-10-15 08:57:26 +11:00
|
|
|
occurrence_pset = get_property_definition(pset, verbose=verbose)
|
2023-07-04 15:50:29 +10:00
|
|
|
if occurrence_pset:
|
|
|
|
|
type_pset.update(occurrence_pset)
|
2023-07-04 14:24:09 +10:00
|
|
|
return type_pset
|
2023-10-15 08:57:26 +11:00
|
|
|
return get_property_definition(pset, verbose=verbose)
|
2023-02-12 15:01:47 +11:00
|
|
|
|
2023-10-15 08:57:26 +11:00
|
|
|
value = get_property_definition(pset, prop=prop, verbose=verbose)
|
2023-07-05 10:36:43 +10:00
|
|
|
if value is None and type_pset is not None:
|
|
|
|
|
return type_pset
|
2023-07-04 14:24:09 +10:00
|
|
|
return value
|
2023-02-12 15:01:47 +11:00
|
|
|
|
2023-08-25 16:51:45 +10:00
|
|
|
|
2024-03-11 12:06:56 +05:00
|
|
|
def get_psets(
|
|
|
|
|
element: ifcopenshell.entity_instance, psets_only=False, qtos_only=False, should_inherit=True, verbose=False
|
2024-03-14 14:03:29 +05:00
|
|
|
) -> dict[str, dict[str, Any]]:
|
2022-08-15 19:40:20 +01:00
|
|
|
"""Retrieve property sets, their related properties' names & values and ids.
|
|
|
|
|
|
2023-07-04 14:24:09 +10:00
|
|
|
If should_inherit is true, the pset "id" only refers to the ID of the
|
|
|
|
|
occurrence, not the type's pset.
|
|
|
|
|
|
2022-08-15 19:40:20 +01:00
|
|
|
:param element: The IFC Element entity
|
2023-02-12 15:01:47 +11:00
|
|
|
:type element: ifcopenshell.entity_instance.entity_instance
|
2022-08-19 18:43:39 +10:00
|
|
|
:param psets_only: Default as False. Set to true if only property sets are needed.
|
2023-02-12 15:01:47 +11:00
|
|
|
:type psets_only: bool,optional
|
2022-08-15 19:40:20 +01:00
|
|
|
:param qtos_only: Default as False. Set to true if only quantities are needed.
|
2023-02-12 15:01:47 +11:00
|
|
|
:type qtos_only: bool,optional
|
2022-08-15 19:40:20 +01:00
|
|
|
:param should_inherit: Default as True. Set to false if you don't want to inherit property sets from the Type.
|
2023-02-12 15:01:47 +11:00
|
|
|
:type should_inherit: bool,optional
|
2024-04-10 12:08:09 +05:00
|
|
|
:param verbose: More detailed prop values, defaults to False.
|
|
|
|
|
:type verbose: bool,optional
|
2023-02-12 15:01:47 +11:00
|
|
|
:return: Key, value pair of psets' names and their properties' names & values
|
2024-03-14 14:03:29 +05:00
|
|
|
:rtype: dict[str, dict[str, Any]]
|
2022-08-15 19:40:20 +01:00
|
|
|
|
2023-01-10 10:16:28 +11:00
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
2023-01-12 12:28:09 +11:00
|
|
|
|
2024-04-10 12:08:09 +05:00
|
|
|
element = ifc_file.by_type("IfcWall")[0]
|
2022-08-15 19:40:20 +01:00
|
|
|
psets = ifcopenshell.util.element.get_psets(element, psets_only=True)
|
|
|
|
|
qsets = ifcopenshell.util.element.get_psets(element, qtos_only=True)
|
|
|
|
|
psets_and_qtos = ifcopenshell.util.element.get_psets(element)
|
|
|
|
|
"""
|
2020-05-17 18:32:56 +10:00
|
|
|
psets = {}
|
2021-06-06 20:54:40 +10:00
|
|
|
if element.is_a("IfcTypeObject"):
|
2021-10-25 16:09:04 +11:00
|
|
|
for definition in element.HasPropertySets or []:
|
|
|
|
|
if psets_only and not definition.is_a("IfcPropertySet"):
|
|
|
|
|
continue
|
|
|
|
|
if qtos_only and not definition.is_a("IfcElementQuantity"):
|
|
|
|
|
continue
|
2023-10-15 08:57:26 +11:00
|
|
|
psets[definition.Name] = get_property_definition(definition, verbose=verbose)
|
2021-10-25 16:09:04 +11:00
|
|
|
elif element.is_a("IfcMaterialDefinition") or element.is_a("IfcProfileDef"):
|
2023-06-16 13:22:01 +10:00
|
|
|
for definition in getattr(element, "HasProperties", None) or []:
|
2021-10-25 16:09:04 +11:00
|
|
|
if qtos_only:
|
|
|
|
|
continue
|
2023-10-15 08:57:26 +11:00
|
|
|
psets[definition.Name] = get_property_definition(definition, verbose=verbose)
|
2024-04-09 18:36:55 +05:00
|
|
|
elif (is_defined_by := getattr(element, "IsDefinedBy", None)) is not None:
|
2024-04-10 12:19:16 +05:00
|
|
|
# other IfcObjectDefinition
|
2023-07-04 15:50:29 +10:00
|
|
|
if should_inherit:
|
|
|
|
|
element_type = ifcopenshell.util.element.get_type(element)
|
|
|
|
|
if element_type:
|
2024-04-10 12:24:45 +05:00
|
|
|
psets = get_psets(
|
|
|
|
|
element_type, psets_only=psets_only, qtos_only=qtos_only, should_inherit=False, verbose=verbose
|
|
|
|
|
)
|
2024-04-09 18:36:55 +05:00
|
|
|
for relationship in is_defined_by:
|
2021-06-06 20:54:40 +10:00
|
|
|
if relationship.is_a("IfcRelDefinesByProperties"):
|
|
|
|
|
definition = relationship.RelatingPropertyDefinition
|
2021-10-25 15:36:55 +11:00
|
|
|
if psets_only and not definition.is_a("IfcPropertySet"):
|
|
|
|
|
continue
|
|
|
|
|
if qtos_only and not definition.is_a("IfcElementQuantity"):
|
|
|
|
|
continue
|
2023-10-15 08:57:26 +11:00
|
|
|
psets.setdefault(definition.Name, {}).update(get_property_definition(definition, verbose=verbose))
|
2020-05-17 18:32:56 +10:00
|
|
|
return psets
|
|
|
|
|
|
2020-08-04 20:18:08 +10:00
|
|
|
|
2024-04-10 12:08:09 +05:00
|
|
|
@overload
|
2024-03-14 14:03:29 +05:00
|
|
|
def get_property_definition(
|
2024-04-10 12:08:09 +05:00
|
|
|
definition: Optional[ifcopenshell.entity_instance], prop: None = None, verbose=False
|
|
|
|
|
) -> dict[str, Any]: ...
|
|
|
|
|
@overload
|
|
|
|
|
def get_property_definition(definition: Optional[ifcopenshell.entity_instance], prop: str, verbose=False) -> Any: ...
|
|
|
|
|
@overload
|
|
|
|
|
def get_property_definition(definition: None, prop: None = None, verbose: bool = False) -> None: ...
|
|
|
|
|
def get_property_definition(
|
|
|
|
|
definition: Optional[ifcopenshell.entity_instance], prop: Optional[str] = None, verbose=False
|
2024-03-14 14:03:29 +05:00
|
|
|
) -> Union[Any, dict[str, Any]]:
|
2024-04-10 12:08:09 +05:00
|
|
|
"""if prop name is not provided in `prop`, will return dict of all available properties
|
|
|
|
|
otherwise will return the value of the specified `prop`.
|
|
|
|
|
"""
|
2023-07-04 14:24:09 +10:00
|
|
|
if not definition:
|
|
|
|
|
return
|
|
|
|
|
|
2024-03-27 16:09:13 +11:00
|
|
|
ifc_class = definition.is_a()
|
|
|
|
|
|
2023-07-04 14:24:09 +10:00
|
|
|
if prop:
|
2024-03-27 16:09:13 +11:00
|
|
|
if ifc_class == "IfcElementQuantity":
|
2023-10-15 08:57:26 +11:00
|
|
|
return get_quantity(definition.Quantities, prop, verbose=verbose)
|
2024-03-27 16:09:13 +11:00
|
|
|
elif ifc_class == "IfcPropertySet":
|
2023-10-15 08:57:26 +11:00
|
|
|
return get_property(definition.HasProperties, prop, verbose=verbose)
|
2024-03-27 16:09:13 +11:00
|
|
|
elif ifc_class == "IfcMaterialProperties" or ifc_class == "IfcProfileProperties":
|
2024-04-10 12:19:16 +05:00
|
|
|
# IfcExtendedProperties
|
2023-10-15 08:57:26 +11:00
|
|
|
return get_property(definition.Properties, prop, verbose=verbose)
|
2020-05-17 18:32:56 +10:00
|
|
|
else:
|
|
|
|
|
# Entity introduced in IFC4
|
|
|
|
|
# definition.is_a('IfcPreDefinedPropertySet'):
|
2023-07-04 14:24:09 +10:00
|
|
|
for i in range(4, len(definition)):
|
2024-04-10 12:19:16 +05:00
|
|
|
if definition.attribute_name(i) == prop:
|
|
|
|
|
if (v := definition[i]) is not None:
|
|
|
|
|
return v
|
2023-07-04 14:24:09 +10:00
|
|
|
return
|
|
|
|
|
|
|
|
|
|
props = {}
|
2024-03-27 16:09:13 +11:00
|
|
|
if ifc_class == "IfcElementQuantity":
|
2024-04-10 12:19:16 +05:00
|
|
|
# 5 IfcElementQuantity.Quantities
|
2024-03-27 16:09:13 +11:00
|
|
|
props.update(get_quantities(definition[5], verbose=verbose))
|
|
|
|
|
elif ifc_class == "IfcPropertySet":
|
2024-04-10 12:19:16 +05:00
|
|
|
# 5 IfcPropertySet.HasProperties
|
2024-03-27 16:09:13 +11:00
|
|
|
props.update(get_properties(definition[4], verbose=verbose))
|
|
|
|
|
elif ifc_class == "IfcMaterialProperties" or ifc_class == "IfcProfileProperties":
|
2024-04-10 12:19:16 +05:00
|
|
|
# 2 IfcExtendedProperties.Properties
|
2024-03-27 16:09:13 +11:00
|
|
|
props.update(get_properties(definition[2], verbose=verbose))
|
2023-07-04 14:24:09 +10:00
|
|
|
else:
|
|
|
|
|
# Entity introduced in IFC4
|
|
|
|
|
# definition.is_a('IfcPreDefinedPropertySet'):
|
2024-03-14 14:03:29 +05:00
|
|
|
for prop_i in range(4, len(definition)):
|
2024-04-10 12:19:16 +05:00
|
|
|
if (v := definition[prop_i]) is not None:
|
|
|
|
|
props[definition.attribute_name(prop_i)] = v
|
2023-07-04 14:24:09 +10:00
|
|
|
props["id"] = definition.id()
|
|
|
|
|
return props
|
2020-08-04 20:18:08 +10:00
|
|
|
|
|
|
|
|
|
2024-04-10 12:08:09 +05:00
|
|
|
@overload
|
|
|
|
|
def get_quantity(quantities: list[ifcopenshell.entity_instance], name: str, verbose: Literal[False] = False) -> Any: ...
|
|
|
|
|
@overload
|
|
|
|
|
def get_quantity(
|
|
|
|
|
quantities: list[ifcopenshell.entity_instance], name: str, verbose: Literal[True]
|
|
|
|
|
) -> dict[str, Any]: ...
|
2024-03-14 14:03:29 +05:00
|
|
|
def get_quantity(
|
|
|
|
|
quantities: list[ifcopenshell.entity_instance], name: str, verbose=False
|
|
|
|
|
) -> Union[Any, dict[str, Any]]:
|
2023-02-12 15:01:47 +11:00
|
|
|
for quantity in quantities or []:
|
2024-04-10 12:19:16 +05:00
|
|
|
# 0 IfcPhysicalQuantity.Name
|
2024-03-27 16:09:13 +11:00
|
|
|
if quantity[0] != name:
|
2023-02-12 15:01:47 +11:00
|
|
|
continue
|
|
|
|
|
if quantity.is_a("IfcPhysicalSimpleQuantity"):
|
2024-04-10 12:19:16 +05:00
|
|
|
# 3 IfcPhysicalSimpleQuantity.Unit
|
2023-10-15 08:57:26 +11:00
|
|
|
result = quantity[3]
|
2023-02-12 15:01:47 +11:00
|
|
|
elif quantity.is_a("IfcPhysicalComplexQuantity"):
|
|
|
|
|
data = {k: v for k, v in quantity.get_info().items() if v is not None and k != "Name"}
|
2024-04-10 12:24:45 +05:00
|
|
|
data["properties"] = get_quantities(quantity.HasQuantities, verbose=verbose)
|
2023-02-12 15:01:47 +11:00
|
|
|
del data["HasQuantities"]
|
2023-10-15 08:57:26 +11:00
|
|
|
result = data
|
|
|
|
|
if verbose:
|
|
|
|
|
result = {"id": quantity.id(), "class": quantity.is_a(), "value": result}
|
|
|
|
|
return result
|
2023-02-12 15:01:47 +11:00
|
|
|
|
|
|
|
|
|
2024-04-10 12:08:09 +05:00
|
|
|
@overload
|
|
|
|
|
def get_quantities(
|
|
|
|
|
quantities: list[ifcopenshell.entity_instance], verbose: Literal[False] = False
|
|
|
|
|
) -> dict[str, Any]: ...
|
|
|
|
|
@overload
|
|
|
|
|
def get_quantities(
|
|
|
|
|
quantities: list[ifcopenshell.entity_instance], verbose: Literal[True]
|
|
|
|
|
) -> dict[str, dict[str, Any]]: ...
|
|
|
|
|
def get_quantities(
|
|
|
|
|
quantities: list[ifcopenshell.entity_instance], verbose=False
|
|
|
|
|
) -> dict[str, Union[Any, dict[str, Any]]]:
|
2020-08-04 20:18:08 +10:00
|
|
|
results = {}
|
2021-10-25 16:09:04 +11:00
|
|
|
for quantity in quantities or []:
|
2024-04-10 12:19:16 +05:00
|
|
|
# 0 IfcPhysicalQuantity.Name
|
|
|
|
|
quantity_name = quantity[0]
|
2020-11-01 20:08:27 +07:00
|
|
|
if quantity.is_a("IfcPhysicalSimpleQuantity"):
|
2024-04-10 12:19:16 +05:00
|
|
|
# 3 IfcPhysicalSimpleQuantity.Unit
|
|
|
|
|
results[quantity_name] = quantity[3]
|
2023-10-15 08:57:26 +11:00
|
|
|
if verbose:
|
2024-04-10 12:19:16 +05:00
|
|
|
results[quantity_name] = {
|
2023-10-15 08:57:26 +11:00
|
|
|
"id": quantity.id(),
|
|
|
|
|
"class": quantity.is_a(),
|
2024-04-10 12:19:16 +05:00
|
|
|
"value": results[quantity_name],
|
2023-10-15 08:57:26 +11:00
|
|
|
}
|
2022-09-12 22:35:05 +10:00
|
|
|
elif quantity.is_a("IfcPhysicalComplexQuantity"):
|
|
|
|
|
data = {k: v for k, v in quantity.get_info().items() if v is not None and k != "Name"}
|
2024-04-10 12:24:45 +05:00
|
|
|
data["properties"] = get_quantities(quantity.HasQuantities, verbose=verbose)
|
2022-09-12 22:35:05 +10:00
|
|
|
del data["HasQuantities"]
|
2024-04-10 12:19:16 +05:00
|
|
|
results[quantity_name] = data
|
2023-10-15 08:57:26 +11:00
|
|
|
if verbose:
|
2024-04-10 12:19:16 +05:00
|
|
|
results[quantity_name] = {
|
|
|
|
|
"id": data["id"],
|
|
|
|
|
"class": data["class"],
|
|
|
|
|
"value": results[quantity_name],
|
2023-10-15 08:57:26 +11:00
|
|
|
}
|
2020-08-04 20:18:08 +10:00
|
|
|
return results
|
|
|
|
|
|
|
|
|
|
|
2024-04-10 12:08:09 +05:00
|
|
|
@overload
|
|
|
|
|
def get_property(properties: list[ifcopenshell.entity_instance], name: str, verbose: Literal[False] = False) -> Any: ...
|
|
|
|
|
@overload
|
|
|
|
|
def get_property(
|
|
|
|
|
properties: list[ifcopenshell.entity_instance], name: str, verbose: Literal[True]
|
|
|
|
|
) -> dict[str, Any]: ...
|
2024-03-14 14:03:29 +05:00
|
|
|
def get_property(
|
|
|
|
|
properties: list[ifcopenshell.entity_instance], name: str, verbose=False
|
|
|
|
|
) -> Union[Any, dict[str, Any]]:
|
2023-02-12 15:01:47 +11:00
|
|
|
for prop in properties or []:
|
|
|
|
|
if prop.Name != name:
|
|
|
|
|
continue
|
|
|
|
|
if prop.is_a("IfcPropertySingleValue"):
|
2024-04-10 12:19:16 +05:00
|
|
|
# 2 IfcPropertySingleValue.NominalValue
|
|
|
|
|
result = v.wrappedValue if (v := prop[2]) else None
|
2023-02-12 15:01:47 +11:00
|
|
|
elif prop.is_a("IfcPropertyEnumeratedValue"):
|
2024-04-10 12:19:16 +05:00
|
|
|
# 2 IfcPropertyEnumeratedValue.EnumerationValues
|
|
|
|
|
result = [v.wrappedValue for v in values] if (values := prop[2]) else None
|
2023-02-12 15:01:47 +11:00
|
|
|
elif prop.is_a("IfcPropertyListValue"):
|
2024-04-10 12:19:16 +05:00
|
|
|
# 2 IfcPropertyListValue.ListValues
|
|
|
|
|
result = [v.wrappedValue for v in values] if (values := prop[2]) else None
|
2023-02-12 15:01:47 +11:00
|
|
|
elif prop.is_a("IfcPropertyBoundedValue"):
|
|
|
|
|
data = prop.get_info()
|
|
|
|
|
del data["Unit"]
|
2023-10-15 08:57:26 +11:00
|
|
|
result = data
|
2023-02-12 15:01:47 +11:00
|
|
|
elif prop.is_a("IfcPropertyTableValue"):
|
2023-10-15 08:57:26 +11:00
|
|
|
result = prop.get_info()
|
2023-02-12 15:01:47 +11:00
|
|
|
elif prop.is_a("IfcComplexProperty"):
|
|
|
|
|
data = {k: v for k, v in prop.get_info().items() if v is not None and k != "Name"}
|
2024-04-10 12:24:45 +05:00
|
|
|
data["properties"] = get_properties(prop.HasProperties, verbose=verbose)
|
2023-02-12 15:01:47 +11:00
|
|
|
del data["HasProperties"]
|
2023-10-15 08:57:26 +11:00
|
|
|
result = data
|
|
|
|
|
if verbose:
|
|
|
|
|
result = {"id": prop.id(), "class": prop.is_a(), "value": result}
|
|
|
|
|
return result
|
2023-02-12 15:01:47 +11:00
|
|
|
|
|
|
|
|
|
2024-04-10 12:08:09 +05:00
|
|
|
@overload
|
|
|
|
|
def get_properties(
|
|
|
|
|
properties: list[ifcopenshell.entity_instance], verbose: Literal[False] = False
|
|
|
|
|
) -> dict[str, Any]: ...
|
|
|
|
|
@overload
|
|
|
|
|
def get_properties(
|
|
|
|
|
properties: list[ifcopenshell.entity_instance], verbose: Literal[True]
|
|
|
|
|
) -> dict[str, dict[str, Any]]: ...
|
|
|
|
|
def get_properties(
|
|
|
|
|
properties: list[ifcopenshell.entity_instance], verbose=False
|
|
|
|
|
) -> dict[str, Union[Any, dict[str, Any]]]:
|
2020-08-04 20:18:08 +10:00
|
|
|
results = {}
|
2021-09-20 14:45:31 +10:00
|
|
|
for prop in properties or []:
|
2024-03-27 16:09:13 +11:00
|
|
|
ifc_class = prop.is_a()
|
2024-04-10 12:19:16 +05:00
|
|
|
prop_name = prop[0] # 0 IfcProperty.Name
|
2024-03-27 16:09:13 +11:00
|
|
|
if ifc_class == "IfcPropertySingleValue":
|
2024-04-10 12:19:16 +05:00
|
|
|
# 2 IfcPropertySingleValue.NominalValue
|
|
|
|
|
results[prop_name] = v.wrappedValue if (v := prop[2]) else None
|
2023-10-15 08:57:26 +11:00
|
|
|
if verbose:
|
2024-04-10 12:19:16 +05:00
|
|
|
results[prop_name] = {
|
|
|
|
|
"id": prop.id(),
|
|
|
|
|
"class": prop.is_a(),
|
|
|
|
|
"value": results[prop_name],
|
|
|
|
|
}
|
2024-03-27 16:09:13 +11:00
|
|
|
elif ifc_class == "IfcPropertyEnumeratedValue":
|
2024-04-10 12:19:16 +05:00
|
|
|
# 2 IfcPropertyEnumeratedValue.EnumerationValues
|
|
|
|
|
results[prop_name] = [v.wrappedValue for v in values] if (values := prop[2]) else None
|
2023-10-15 08:57:26 +11:00
|
|
|
if verbose:
|
2024-04-10 12:19:16 +05:00
|
|
|
results[prop_name] = {
|
|
|
|
|
"id": prop.id(),
|
|
|
|
|
"class": prop.is_a(),
|
|
|
|
|
"value": results[prop_name],
|
|
|
|
|
}
|
2024-03-27 16:09:13 +11:00
|
|
|
elif ifc_class == "IfcPropertyListValue":
|
2024-04-10 12:19:16 +05:00
|
|
|
# 2 IfcPropertyListValue.ListValues
|
|
|
|
|
results[prop_name] = [v.wrappedValue for v in values] if (values := prop[2]) else None
|
2023-10-15 08:57:26 +11:00
|
|
|
if verbose:
|
2024-04-10 12:19:16 +05:00
|
|
|
results[prop_name] = {
|
|
|
|
|
"id": prop.id(),
|
|
|
|
|
"class": prop.is_a(),
|
|
|
|
|
"value": results[prop_name],
|
|
|
|
|
}
|
2024-03-27 16:09:13 +11:00
|
|
|
elif ifc_class == "IfcPropertyBoundedValue":
|
2022-09-12 22:35:05 +10:00
|
|
|
data = prop.get_info()
|
|
|
|
|
del data["Unit"]
|
2024-04-10 12:19:16 +05:00
|
|
|
results[prop_name] = data
|
2023-10-15 08:57:26 +11:00
|
|
|
if verbose:
|
2024-04-10 12:19:16 +05:00
|
|
|
results[prop_name] = {
|
|
|
|
|
"id": data["id"],
|
|
|
|
|
"class": data["type"],
|
|
|
|
|
"value": results[prop_name],
|
|
|
|
|
}
|
2024-03-27 16:09:13 +11:00
|
|
|
elif ifc_class == "IfcPropertyTableValue":
|
2024-04-10 12:19:16 +05:00
|
|
|
data = prop.get_info()
|
|
|
|
|
results[prop_name] = data
|
2023-10-15 08:57:26 +11:00
|
|
|
if verbose:
|
2024-04-10 12:19:16 +05:00
|
|
|
results[prop_name] = {
|
|
|
|
|
"id": data["id"],
|
|
|
|
|
"class": data["type"],
|
|
|
|
|
"value": results[prop_name],
|
|
|
|
|
}
|
2024-03-27 16:09:13 +11:00
|
|
|
elif ifc_class == "IfcComplexProperty":
|
2021-08-31 15:44:57 +10:00
|
|
|
data = {k: v for k, v in prop.get_info().items() if v is not None and k != "Name"}
|
2024-04-10 12:24:45 +05:00
|
|
|
data["properties"] = get_properties(prop.HasProperties, verbose=verbose)
|
2020-11-01 20:08:27 +07:00
|
|
|
del data["HasProperties"]
|
2024-04-10 12:19:16 +05:00
|
|
|
results[prop_name] = data
|
2023-10-15 08:57:26 +11:00
|
|
|
if verbose:
|
2024-04-10 12:19:16 +05:00
|
|
|
results[prop_name] = {"id": data["id"], "class": data["class"], "value": results[prop_name]}
|
2020-08-04 20:18:08 +10:00
|
|
|
return results
|
2020-08-04 21:08:49 +10:00
|
|
|
|
|
|
|
|
|
2024-03-11 12:06:56 +05:00
|
|
|
def get_predefined_type(element: ifcopenshell.entity_instance) -> str:
|
2023-01-12 12:28:09 +11:00
|
|
|
"""Retrieves the PrefefinedType attribute of an element.
|
|
|
|
|
|
|
|
|
|
If the predefined type is user defined, the custom type (such as object
|
|
|
|
|
type, element type, or process type depending on the class) is returned
|
|
|
|
|
instead. Predefined types from the associated type element are also
|
|
|
|
|
considered first.
|
2022-08-15 19:40:20 +01:00
|
|
|
|
|
|
|
|
:param element: The IFC Element entity
|
2023-01-12 12:28:09 +11:00
|
|
|
:type element: ifcopenshell.entity_instance.entity_instance
|
2022-08-15 19:40:20 +01:00
|
|
|
:return: The predefined type of the element
|
2023-01-12 12:28:09 +11:00
|
|
|
:rtype: str
|
2022-08-15 19:40:20 +01:00
|
|
|
|
2023-01-10 10:16:28 +11:00
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
2023-01-12 12:28:09 +11:00
|
|
|
|
|
|
|
|
element = ifcopenshell.by_type("IfcWall")[0]
|
|
|
|
|
predefined_type = ifcopenshell.util.element.get_predefined_type(element)
|
2022-08-15 19:40:20 +01:00
|
|
|
"""
|
2022-01-12 14:11:23 +11:00
|
|
|
element_type = get_type(element)
|
|
|
|
|
if element_type:
|
2022-06-29 08:22:43 +02:00
|
|
|
predefined_type = getattr(element_type, "PredefinedType", None)
|
2022-04-18 10:27:06 +10:00
|
|
|
if predefined_type == "USERDEFINED" or not predefined_type:
|
2024-04-09 18:36:55 +05:00
|
|
|
predefined_type = getattr(element_type, "ElementType", ...)
|
|
|
|
|
if predefined_type == ...:
|
|
|
|
|
predefined_type = getattr(element_type, "ProcessType", None)
|
2022-01-12 14:11:23 +11:00
|
|
|
if predefined_type and predefined_type != "NOTDEFINED":
|
|
|
|
|
return predefined_type
|
2024-04-09 18:36:55 +05:00
|
|
|
|
2022-01-12 14:11:23 +11:00
|
|
|
predefined_type = getattr(element, "PredefinedType", None)
|
2022-04-18 10:27:06 +10:00
|
|
|
if predefined_type == "USERDEFINED" or not predefined_type:
|
2022-01-12 14:11:23 +11:00
|
|
|
predefined_type = getattr(element, "ObjectType", None)
|
|
|
|
|
return predefined_type
|
|
|
|
|
|
|
|
|
|
|
2024-03-11 12:06:56 +05:00
|
|
|
def get_type(element: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
2023-01-12 12:28:09 +11:00
|
|
|
"""Retrieves the construction type element of an element occurrence
|
2022-08-19 18:43:39 +10:00
|
|
|
|
2023-01-12 12:28:09 +11:00
|
|
|
:param element: The element occurrence
|
|
|
|
|
:type: ifcopenshell.entity_instance.entity_instance
|
|
|
|
|
:return: The related type element
|
2024-01-24 16:36:58 +05:00
|
|
|
:rtype: ifcopenshell.entity_instance.entity_instance
|
2022-08-15 19:40:20 +01:00
|
|
|
|
2023-01-10 10:16:28 +11:00
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
2023-01-12 12:28:09 +11:00
|
|
|
|
|
|
|
|
element = ifcopenshell.by_type("IfcWall")[0]
|
|
|
|
|
element_type = ifcopenshell.util.element.get_type(element)
|
2022-08-15 19:40:20 +01:00
|
|
|
"""
|
2021-06-07 19:43:02 +10:00
|
|
|
if element.is_a("IfcTypeObject"):
|
|
|
|
|
return element
|
2024-04-09 18:36:55 +05:00
|
|
|
elif (is_typed_by := getattr(element, "IsTypedBy", None)) is not None and is_typed_by:
|
|
|
|
|
return is_typed_by[0].RelatingType
|
|
|
|
|
elif (is_defined_by := getattr(element, "IsDefinedBy", None)) is not None and is_defined_by: # IFC2X3
|
|
|
|
|
for relationship in is_defined_by:
|
2020-11-01 20:08:27 +07:00
|
|
|
if relationship.is_a("IfcRelDefinesByType"):
|
2020-08-08 17:58:45 +10:00
|
|
|
return relationship.RelatingType
|
|
|
|
|
|
|
|
|
|
|
2024-04-09 16:41:03 +05:00
|
|
|
def get_types(type: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
2023-01-12 12:28:09 +11:00
|
|
|
"""Get all the occurrences of a type element
|
|
|
|
|
|
|
|
|
|
:param type: The type element
|
|
|
|
|
:type type: ifcopenshell.entity_instance.entity_instance
|
|
|
|
|
:return: A list of occurrences of that type
|
|
|
|
|
:rtype: list[ifcopenshell.entity_instance.entity_instance]
|
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
element_type = ifcopenshell.by_type("IfcWallType")[0]
|
|
|
|
|
walls = ifcopenshell.util.element.get_types(element_type)
|
|
|
|
|
"""
|
2021-11-08 20:40:49 +11:00
|
|
|
for rel in getattr(type, "Types", []):
|
|
|
|
|
return rel.RelatedObjects
|
2021-11-16 17:52:08 +11:00
|
|
|
for rel in getattr(type, "ObjectTypeOf", []):
|
|
|
|
|
return rel.RelatedObjects
|
2021-11-08 20:40:49 +11:00
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
2024-04-09 16:41:03 +05:00
|
|
|
def get_shape_aspects(element: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
2024-02-19 17:33:49 +05:00
|
|
|
"""Gets element shape aspects
|
|
|
|
|
|
|
|
|
|
:param element: The element to get the shape aspects of.
|
|
|
|
|
:type element: ifcopenshell.entity_instance.entity_instance
|
|
|
|
|
:return: The associated shape aspects of the element.
|
|
|
|
|
:rtype: list[ifcopenshell.entity_instance.entity_instance]
|
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
element = ifcopenshell.by_type("IfcWall")[0]
|
|
|
|
|
shape_aspect = ifcopenshell.util.element.get_shape_aspects(element)
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# IfcProduct
|
2024-04-09 18:36:55 +05:00
|
|
|
if (representation := getattr(element, "Representation", ...)) != ...:
|
|
|
|
|
return representation.HasShapeAspects
|
2024-02-19 17:33:49 +05:00
|
|
|
|
|
|
|
|
# IfcTypeProduct
|
|
|
|
|
shape_aspects = []
|
|
|
|
|
for repersentation_map in element.RepresentationMaps:
|
|
|
|
|
shape_aspects += repersentation_map.HasShapeAspects
|
|
|
|
|
return shape_aspects
|
|
|
|
|
|
|
|
|
|
|
2024-03-11 12:06:56 +05:00
|
|
|
def get_material(
|
|
|
|
|
element: ifcopenshell.entity_instance, should_skip_usage=False, should_inherit=True
|
2024-04-10 12:08:09 +05:00
|
|
|
) -> Union[ifcopenshell.entity_instance, None]:
|
2023-01-12 12:28:09 +11:00
|
|
|
"""Gets the material of the element
|
|
|
|
|
|
|
|
|
|
The material may be a single material, material set (layered, profiled, or
|
|
|
|
|
constituent), or a material set usage.
|
|
|
|
|
|
2023-08-14 11:55:15 +10:00
|
|
|
:param element: The element to get the material of.
|
|
|
|
|
:type element: ifcopenshell.entity_instance.entity_instance
|
2023-01-12 12:28:09 +11:00
|
|
|
:param should_skip_usage: If set to True, if the material is a material set
|
|
|
|
|
usage, the material set itself will be returned. Useful if you don't
|
|
|
|
|
care about occurrence usage parameters. If False, the usage will be
|
|
|
|
|
returned.
|
|
|
|
|
:type should_skip_usage: bool
|
|
|
|
|
:param should_inherit: If True, any inherited materials from associated
|
|
|
|
|
types will be considered.
|
|
|
|
|
:type should_inherit: bool
|
2024-04-10 12:08:09 +05:00
|
|
|
:return: The associated material of the element or `None`.
|
|
|
|
|
:rtype: Union[ifcopenshell.entity_instance.entity_instance, None]
|
2023-01-12 12:28:09 +11:00
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
element = ifcopenshell.by_type("IfcWall")[0]
|
2023-03-09 17:48:10 +11:00
|
|
|
material = ifcopenshell.util.element.get_material(element)
|
2023-01-12 12:28:09 +11:00
|
|
|
"""
|
2024-04-09 18:36:55 +05:00
|
|
|
if (has_associations := getattr(element, "HasAssociations", None)) is not None and has_associations:
|
|
|
|
|
for relationship in has_associations:
|
2020-12-02 09:03:18 +11:00
|
|
|
if relationship.is_a("IfcRelAssociatesMaterial"):
|
2021-06-21 12:21:46 +10:00
|
|
|
if should_skip_usage:
|
2024-04-09 18:36:55 +05:00
|
|
|
relating_material = relationship.RelatingMaterial
|
|
|
|
|
if relating_material.is_a("IfcMaterialLayerSetUsage"):
|
|
|
|
|
return relating_material.ForLayerSet
|
|
|
|
|
elif relating_material.is_a("IfcMaterialProfileSetUsage"):
|
|
|
|
|
return relating_material.ForProfileSet
|
2020-12-02 09:03:18 +11:00
|
|
|
return relationship.RelatingMaterial
|
2022-05-12 12:06:23 +10:00
|
|
|
if should_inherit:
|
|
|
|
|
relating_type = get_type(element)
|
2024-04-09 18:36:55 +05:00
|
|
|
if relating_type != element and (has_associations := getattr(relating_type, "HasAssociations", None)):
|
2022-05-12 12:06:23 +10:00
|
|
|
return get_material(relating_type, should_skip_usage)
|
2020-12-02 09:03:18 +11:00
|
|
|
|
|
|
|
|
|
2024-04-09 16:41:03 +05:00
|
|
|
def get_materials(element: ifcopenshell.entity_instance, should_inherit=True) -> list[ifcopenshell.entity_instance]:
|
2023-03-09 17:48:10 +11:00
|
|
|
"""Gets individual materials of an element
|
|
|
|
|
|
|
|
|
|
If the element has a material set, the individual materials of that set are
|
|
|
|
|
returned as a list.
|
|
|
|
|
|
2023-08-14 11:55:15 +10:00
|
|
|
:param element: The element to get the materials of.
|
|
|
|
|
:type element: ifcopenshell.entity_instance.entity_instance
|
2023-03-09 17:48:10 +11:00
|
|
|
:param should_inherit: If True, any inherited materials from associated
|
|
|
|
|
types will be considered.
|
|
|
|
|
:return: The associated materials of the element.
|
|
|
|
|
:rtype: list[ifcopenshell.entity_instance.entity_instance]
|
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
element = ifcopenshell.by_type("IfcWall")[0]
|
|
|
|
|
materials = ifcopenshell.util.element.get_materials(element)
|
|
|
|
|
"""
|
|
|
|
|
material = get_material(element, should_skip_usage=True, should_inherit=should_inherit)
|
|
|
|
|
if not material:
|
|
|
|
|
return []
|
|
|
|
|
elif material.is_a("IfcMaterial"):
|
|
|
|
|
return [material]
|
|
|
|
|
elif material.is_a("IfcMaterialLayerSet"):
|
|
|
|
|
return [l.Material for l in material.MaterialLayers]
|
|
|
|
|
elif material.is_a("IfcMaterialProfileSet"):
|
|
|
|
|
return [p.Material for p in material.MaterialProfiles]
|
|
|
|
|
elif material.is_a("IfcMaterialConstituentSet"):
|
|
|
|
|
return [c.Material for c in material.MaterialConstituents]
|
2023-11-01 22:48:31 +11:00
|
|
|
elif material.is_a("IfcMaterialList"):
|
|
|
|
|
return list(material.Materials)
|
2023-03-09 17:48:10 +11:00
|
|
|
|
|
|
|
|
|
2024-04-09 16:41:03 +05:00
|
|
|
def get_styles(element: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
2023-08-14 11:55:15 +10:00
|
|
|
"""Retrieves the styles used in an element's representation.
|
|
|
|
|
|
|
|
|
|
Styles may be retreived from the material or the body representation.
|
|
|
|
|
|
|
|
|
|
:param element: The element to get the styles of.
|
|
|
|
|
:type element: ifcopenshell.entity_instance.entity_instance
|
|
|
|
|
:return: A list of surface styles
|
|
|
|
|
:rtype: list[ifcopenshell.entity_instance.entity_instance]
|
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
wall = file.by_type("IfcWall")[0]
|
|
|
|
|
styles = ifcopenshell.util.element.get_styles(wall)
|
|
|
|
|
"""
|
|
|
|
|
styles = []
|
|
|
|
|
|
|
|
|
|
materials = ifcopenshell.util.element.get_materials(element)
|
|
|
|
|
for material in materials:
|
|
|
|
|
for material_definition_representation in material.HasRepresentation or []:
|
|
|
|
|
for representation in material_definition_representation.Representations:
|
|
|
|
|
for item in representation.Items:
|
|
|
|
|
styles.extend([s for s in item.Styles if s.is_a("IfcSurfaceStyle")])
|
|
|
|
|
|
|
|
|
|
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
|
|
|
|
if not body:
|
|
|
|
|
return styles
|
|
|
|
|
|
|
|
|
|
for representation in [body]:
|
|
|
|
|
queue = list(representation.Items)
|
|
|
|
|
while queue:
|
|
|
|
|
item = queue.pop()
|
|
|
|
|
if item.is_a("IfcMappedItem"):
|
|
|
|
|
queue.extend(item.MappingSource.MappedRepresentation.Items)
|
|
|
|
|
if item.is_a("IfcBooleanResult"):
|
|
|
|
|
queue.append(item.FirstOperand)
|
|
|
|
|
queue.append(item.SecondOperand)
|
|
|
|
|
if item.StyledByItem:
|
|
|
|
|
styles.extend([s for s in item.StyledByItem[0].Styles if s.is_a("IfcSurfaceStyle")])
|
|
|
|
|
return styles
|
|
|
|
|
|
|
|
|
|
|
2024-03-11 12:06:56 +05:00
|
|
|
def get_elements_by_material(
|
|
|
|
|
ifc_file: ifcopenshell.file, material: ifcopenshell.entity_instance
|
2024-04-09 16:41:03 +05:00
|
|
|
) -> list[ifcopenshell.entity_instance]:
|
2023-01-12 12:28:09 +11:00
|
|
|
"""Retrieves the elements related to a material.
|
2022-08-19 18:43:39 +10:00
|
|
|
|
2022-10-09 17:15:39 +11:00
|
|
|
This includes elements using the material as part of a material set or set
|
|
|
|
|
usage.
|
|
|
|
|
|
2022-08-15 19:40:20 +01:00
|
|
|
:param ifc_file: The IFC file
|
2023-01-12 12:28:09 +11:00
|
|
|
:type ifc_file: ifcopenshell.file.file
|
2022-08-15 19:40:20 +01:00
|
|
|
:param material: The IFC Material entity
|
2023-01-12 12:28:09 +11:00
|
|
|
:type material: ifcopenshell.entity_instance.entity_instance
|
2022-10-09 17:15:39 +11:00
|
|
|
:return: A list of elements using the to the material
|
2023-01-12 12:28:09 +11:00
|
|
|
:rtype: list[ifcopenshell.entity_instance.entity_instance]
|
2022-08-15 19:40:20 +01:00
|
|
|
|
2023-01-10 10:16:28 +11:00
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
2022-10-09 17:15:39 +11:00
|
|
|
|
|
|
|
|
material = file.by_type("IfcMaterial")[0]
|
|
|
|
|
elements = ifcopenshell.util.element.get_elements_by_material(file, material)
|
2022-08-15 19:40:20 +01:00
|
|
|
"""
|
2022-05-08 18:03:18 +10:00
|
|
|
results = set()
|
2022-03-22 12:50:10 +11:00
|
|
|
for inverse in ifc_file.get_inverse(material):
|
|
|
|
|
if inverse.is_a("IfcRelAssociatesMaterial"):
|
2023-08-25 16:51:45 +10:00
|
|
|
results.update(inverse.RelatedObjects or []) # See Revit bug #675
|
2022-03-22 12:50:10 +11:00
|
|
|
elif inverse.is_a("IfcMaterialLayer"):
|
|
|
|
|
for material_set in inverse.ToMaterialLayerSet:
|
2022-05-08 18:03:18 +10:00
|
|
|
results.update(get_elements_by_material(ifc_file, material_set))
|
2022-03-22 12:50:10 +11:00
|
|
|
elif inverse.is_a("IfcMaterialProfile"):
|
|
|
|
|
for material_set in inverse.ToMaterialProfileSet:
|
2022-05-08 18:03:18 +10:00
|
|
|
results.update(get_elements_by_material(ifc_file, material_set))
|
2022-03-22 12:50:10 +11:00
|
|
|
elif inverse.is_a("IfcMaterialConstituent"):
|
|
|
|
|
for material_set in inverse.ToMaterialConstituentSet:
|
2022-05-08 18:03:18 +10:00
|
|
|
results.update(get_elements_by_material(ifc_file, material_set))
|
2022-03-22 12:50:10 +11:00
|
|
|
elif inverse.is_a("IfcMaterialLayerSetUsage"):
|
2022-05-08 18:03:18 +10:00
|
|
|
results.update(get_elements_by_material(ifc_file, inverse))
|
2022-03-22 12:50:10 +11:00
|
|
|
elif inverse.is_a("IfcMaterialProfileSetUsage"):
|
2022-05-08 18:03:18 +10:00
|
|
|
results.update(get_elements_by_material(ifc_file, inverse))
|
2022-03-22 12:50:10 +11:00
|
|
|
elif inverse.is_a("IfcMaterialList"):
|
2022-05-08 18:03:18 +10:00
|
|
|
results.update(get_elements_by_material(ifc_file, inverse))
|
|
|
|
|
return results
|
|
|
|
|
|
|
|
|
|
|
2024-03-11 12:06:56 +05:00
|
|
|
def get_elements_by_style(
|
|
|
|
|
ifc_file: ifcopenshell.file, style: ifcopenshell.entity_instance
|
2024-04-09 16:41:03 +05:00
|
|
|
) -> list[ifcopenshell.entity_instance]:
|
2023-01-12 12:28:09 +11:00
|
|
|
"""Retrieves the elements whose geometric representation uses a style
|
2022-08-19 18:43:39 +10:00
|
|
|
|
2022-08-15 19:40:20 +01:00
|
|
|
:param ifc_file: The IFC file
|
2023-01-12 12:28:09 +11:00
|
|
|
:type ifc_file: ifcopenshell.file.file
|
|
|
|
|
:param style: The IfcPresentationStyle entity
|
|
|
|
|
:type style: ifcopenshell.entity_instance.entity_instance
|
2022-08-15 19:40:20 +01:00
|
|
|
:return: The elements related to the style
|
2023-01-12 12:28:09 +11:00
|
|
|
:rtype: list[ifcopenshell.entity_instance.entity_instance]
|
2022-08-15 19:40:20 +01:00
|
|
|
|
2023-01-10 10:16:28 +11:00
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
2022-08-15 19:40:20 +01:00
|
|
|
|
2022-10-09 17:15:39 +11:00
|
|
|
style = file.by_type("IfcSurfaceStyle")[0]
|
|
|
|
|
elements = ifcopenshell.util.element.get_elements_by_style(file, style)
|
2022-08-15 19:40:20 +01:00
|
|
|
"""
|
2022-05-08 18:03:18 +10:00
|
|
|
results = set()
|
|
|
|
|
inverses = list(ifc_file.get_inverse(style))
|
|
|
|
|
while inverses:
|
|
|
|
|
inverse = inverses.pop()
|
|
|
|
|
if inverse.is_a("IfcPresentationStyleAssignment"):
|
|
|
|
|
inverses.extend(ifc_file.get_inverse(inverse))
|
|
|
|
|
continue
|
|
|
|
|
if not inverse.is_a("IfcStyledItem"):
|
|
|
|
|
continue
|
|
|
|
|
if inverse.Item:
|
|
|
|
|
[
|
|
|
|
|
results.update(get_elements_by_representation(ifc_file, i))
|
|
|
|
|
for i in ifc_file.get_inverse(inverse.Item)
|
|
|
|
|
if i.is_a("IfcShapeRepresentation")
|
|
|
|
|
]
|
|
|
|
|
else:
|
|
|
|
|
styled_reps = [i for i in ifc_file.get_inverse(inverse) if i.is_a("IfcStyledRepresentation")]
|
|
|
|
|
for styled_rep in styled_reps:
|
|
|
|
|
for material_def_rep in styled_rep.OfProductRepresentation:
|
|
|
|
|
results.update(get_elements_by_material(ifc_file, material_def_rep.RepresentedMaterial))
|
|
|
|
|
return results
|
|
|
|
|
|
|
|
|
|
|
2024-03-11 12:06:56 +05:00
|
|
|
def get_elements_by_representation(
|
|
|
|
|
ifc_file: ifcopenshell.file, representation: ifcopenshell.entity_instance
|
2024-04-09 16:41:03 +05:00
|
|
|
) -> list[ifcopenshell.entity_instance]:
|
2023-01-12 12:28:09 +11:00
|
|
|
"""Gets all elements using a geometric representation
|
|
|
|
|
|
|
|
|
|
:param ifc_file: The IFC file
|
|
|
|
|
:type ifc_file: ifcopenshell.file.file
|
|
|
|
|
:param representation: The IfcShapeRepresentation representation
|
|
|
|
|
:type representation: ifcopenshell.entity_instance.entity_instance
|
|
|
|
|
:return: The elements using the geometric representation
|
|
|
|
|
:rtype: list[ifcopenshell.entity_instance.entity_instance]
|
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
representation = file.by_type("IfcShapeRepresentation")[0]
|
|
|
|
|
elements = ifcopenshell.util.element.get_elements_by_representation(file, representation)
|
|
|
|
|
"""
|
2022-05-08 18:03:18 +10:00
|
|
|
results = set()
|
|
|
|
|
[results.update(pr.ShapeOfProduct) for pr in representation.OfProductRepresentation]
|
|
|
|
|
for rep_map in representation.RepresentationMap:
|
|
|
|
|
for inverse in ifc_file.get_inverse(rep_map):
|
|
|
|
|
if inverse.is_a("IfcTypeProduct"):
|
|
|
|
|
results.add(inverse)
|
|
|
|
|
elif inverse.is_a("IfcMappedItem"):
|
|
|
|
|
[
|
|
|
|
|
results.update(get_elements_by_representation(ifc_file, rep))
|
|
|
|
|
for rep in ifc_file.get_inverse(inverse)
|
|
|
|
|
if rep.is_a("IfcShapeRepresentation")
|
|
|
|
|
]
|
2022-03-22 12:50:10 +11:00
|
|
|
return results
|
|
|
|
|
|
|
|
|
|
|
2024-03-11 12:06:56 +05:00
|
|
|
def get_elements_by_layer(
|
|
|
|
|
ifc_file: ifcopenshell.file, layer: ifcopenshell.entity_instance
|
2024-04-09 16:41:03 +05:00
|
|
|
) -> list[ifcopenshell.entity_instance]:
|
2023-01-12 12:28:09 +11:00
|
|
|
"""Get all the elements that are used by a presentation layer
|
|
|
|
|
|
|
|
|
|
:param ifc_file: The IFC file
|
|
|
|
|
:type ifc_file: ifcopenshell.file.file
|
|
|
|
|
:param layer: The IfcPresentationLayerAssignment layer
|
|
|
|
|
:type layer: ifcopenshell.entity_instance.entity_instance
|
|
|
|
|
:return: The elements using the geometric representation
|
|
|
|
|
:rtype: list[ifcopenshell.entity_instance.entity_instance]
|
|
|
|
|
"""
|
|
|
|
|
results = set()
|
2023-10-12 22:56:21 +11:00
|
|
|
for item in layer.AssignedItems or []:
|
2023-01-12 12:28:09 +11:00
|
|
|
if item.is_a("IfcShapeRepresentation"):
|
|
|
|
|
results.update(get_elements_by_representation(ifc_file, item))
|
|
|
|
|
elif item.is_a("IfcRepresentationItem"):
|
|
|
|
|
for inverse in ifc_file.get_inverse(item):
|
|
|
|
|
if inverse.is_a("IfcShapeRepresentation"):
|
|
|
|
|
results.update(get_elements_by_representation(ifc_file, inverse))
|
|
|
|
|
return results
|
|
|
|
|
|
|
|
|
|
|
2024-03-11 12:06:56 +05:00
|
|
|
def get_layers(
|
|
|
|
|
ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance
|
2024-04-09 16:41:03 +05:00
|
|
|
) -> list[ifcopenshell.entity_instance]:
|
2023-01-12 12:28:09 +11:00
|
|
|
"""Get the CAD layers that an element is part of
|
|
|
|
|
|
|
|
|
|
An element may have portions or all of its geometry assigned to a
|
|
|
|
|
traditional CAD presentation layer.
|
|
|
|
|
|
|
|
|
|
:param ifc_file: The IFC file object
|
|
|
|
|
:type ifc_file: ifcopenshell.file.file
|
|
|
|
|
:param element: The IFC element to interrogate
|
|
|
|
|
:type element: ifcopenshell.entity_instance.entity_instance
|
|
|
|
|
:return: A list of IfcPresentationLayerAssignment
|
|
|
|
|
:rtype: list[ifcopenshell.entity_instance.entity_instance]
|
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
element = ifcopenshell.by_type("IfcWall")[0]
|
|
|
|
|
layers = ifcopenshell.util.element.get_layers(element)
|
|
|
|
|
"""
|
2021-11-16 18:07:27 +11:00
|
|
|
layers = []
|
|
|
|
|
representations = []
|
2024-04-09 18:36:55 +05:00
|
|
|
if representation := getattr(element, "Representation", None):
|
|
|
|
|
representations = [representation]
|
|
|
|
|
elif representation_maps := getattr(element, "RepresentationMaps", None):
|
|
|
|
|
representations = representation_maps
|
2021-11-16 18:07:27 +11:00
|
|
|
for representation in representations:
|
|
|
|
|
for subelement in ifc_file.traverse(representation):
|
|
|
|
|
if subelement.is_a("IfcShapeRepresentation"):
|
|
|
|
|
layers.extend(subelement.LayerAssignments or [])
|
|
|
|
|
elif subelement.is_a("IfcGeometricRepresentationItem"):
|
2021-11-19 19:08:26 +11:00
|
|
|
if ifc_file.schema == "IFC2X3":
|
|
|
|
|
layers.extend(subelement.LayerAssignments or [])
|
|
|
|
|
else:
|
|
|
|
|
layers.extend(subelement.LayerAssignment or [])
|
2021-11-16 18:07:27 +11:00
|
|
|
return layers
|
|
|
|
|
|
|
|
|
|
|
2024-03-11 12:06:56 +05:00
|
|
|
def get_container(
|
2024-03-14 14:03:29 +05:00
|
|
|
element: ifcopenshell.entity_instance, should_get_direct=False, ifc_class: Optional[str] = None
|
2024-03-11 12:06:56 +05:00
|
|
|
) -> ifcopenshell.entity_instance:
|
2022-08-15 19:40:20 +01:00
|
|
|
"""
|
2022-08-19 18:43:39 +10:00
|
|
|
Retrieves the spatial structure container of an element.
|
2022-08-15 19:40:20 +01:00
|
|
|
|
2022-08-19 18:43:39 +10:00
|
|
|
:param element: The IFC element
|
|
|
|
|
:type element: ifcopenshell.entity_instance.entity_instance
|
|
|
|
|
:param should_get_direct: If True, a result is only returned if the element
|
|
|
|
|
is directly contained in a spatial structure element. If False, an
|
|
|
|
|
indirect spatial container may be returned, such as if an element is a
|
|
|
|
|
part of an aggregate, and then if that aggregate is contained in a
|
|
|
|
|
spatial structure element.
|
|
|
|
|
:type should_get_direct: bool
|
2023-08-21 23:06:49 +10:00
|
|
|
:param ifc_class: Optionally filter the type of container you're after. For
|
|
|
|
|
example, you may be after the storey, not a space.
|
2024-03-14 14:03:29 +05:00
|
|
|
:type ifc_class: str, optional
|
2022-08-19 18:43:39 +10:00
|
|
|
:return: The direct or indirect container of the element or None.
|
2024-03-11 12:06:56 +05:00
|
|
|
:rtype: ifcopenshell.entity_instance.entity_instance
|
2022-08-15 19:40:20 +01:00
|
|
|
|
2023-01-10 10:16:28 +11:00
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
2022-08-15 19:40:20 +01:00
|
|
|
|
2022-08-19 18:43:39 +10:00
|
|
|
element = file.by_type("IfcWall")[0]
|
|
|
|
|
container = ifcopenshell.util.element.get_container(element)
|
2022-08-15 19:40:20 +01:00
|
|
|
"""
|
2021-10-06 12:52:01 +11:00
|
|
|
if should_get_direct:
|
2024-04-09 18:36:55 +05:00
|
|
|
if (
|
|
|
|
|
contained_in_structure := getattr(element, "ContainedInStructure", None)
|
|
|
|
|
) is not None and contained_in_structure:
|
|
|
|
|
container = contained_in_structure[0].RelatingStructure
|
2023-08-21 23:06:49 +10:00
|
|
|
if not ifc_class:
|
|
|
|
|
return container
|
|
|
|
|
if container.is_a(ifc_class):
|
|
|
|
|
return container
|
2021-10-06 12:52:01 +11:00
|
|
|
else:
|
|
|
|
|
aggregate = get_aggregate(element)
|
|
|
|
|
if aggregate:
|
|
|
|
|
return get_container(aggregate, should_get_direct)
|
2023-11-20 16:57:19 +11:00
|
|
|
nest = get_nest(element)
|
|
|
|
|
if nest:
|
|
|
|
|
return get_container(nest, should_get_direct)
|
2024-04-09 18:36:55 +05:00
|
|
|
if (
|
|
|
|
|
contained_in_structure := getattr(element, "ContainedInStructure", None)
|
|
|
|
|
) is not None and contained_in_structure:
|
|
|
|
|
container = contained_in_structure[0].RelatingStructure
|
2023-08-21 23:06:49 +10:00
|
|
|
if not ifc_class:
|
|
|
|
|
return container
|
|
|
|
|
while container:
|
|
|
|
|
if container.is_a(ifc_class):
|
|
|
|
|
return container
|
|
|
|
|
container = get_aggregate(container)
|
2021-01-22 15:52:48 +11:00
|
|
|
|
|
|
|
|
|
2024-04-09 16:41:03 +05:00
|
|
|
def get_referenced_structures(element: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
2023-01-12 12:28:09 +11:00
|
|
|
"""Retreives a list of referenced spatial elements
|
|
|
|
|
|
|
|
|
|
Typically useful for multistorey elements, such as columns or facade
|
|
|
|
|
elements, or elements that span multiple spaces or in-between spaces, such
|
|
|
|
|
as stairs, doors, etc.
|
2022-08-19 18:43:39 +10:00
|
|
|
|
|
|
|
|
:param element: The IFC element
|
|
|
|
|
:type element: ifcopenshell.entity_instance.entity_instance
|
2024-03-11 12:06:56 +05:00
|
|
|
:return: A list of IfcSpatialElement
|
|
|
|
|
:rtype: list[ifcopenshell.entity_instance.entity_instance]
|
2022-08-19 18:43:39 +10:00
|
|
|
|
2023-01-10 10:16:28 +11:00
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
2022-08-19 18:43:39 +10:00
|
|
|
|
|
|
|
|
element = file.by_type("IfcWall")[0]
|
|
|
|
|
print(ifcopenshell.util.element.get_referenced_structures(element))
|
|
|
|
|
"""
|
2024-04-09 18:36:55 +05:00
|
|
|
return [r.RelatingStructure for r in getattr(element, "ReferencedInStructures", [])]
|
2022-08-19 18:43:39 +10:00
|
|
|
|
|
|
|
|
|
2024-04-09 16:41:03 +05:00
|
|
|
def get_decomposition(element: ifcopenshell.entity_instance, is_recursive=True) -> list[ifcopenshell.entity_instance]:
|
2022-08-15 19:40:20 +01:00
|
|
|
"""
|
2022-10-08 23:42:03 +11:00
|
|
|
Retrieves all subelements of an element based on the spatial decomposition
|
|
|
|
|
hierarchy. This includes all subspaces and elements contained in subspaces,
|
|
|
|
|
parts of an aggreate, all openings, and all fills of any openings.
|
2022-08-19 18:43:39 +10:00
|
|
|
|
2022-08-15 19:40:20 +01:00
|
|
|
:param element: The IFC element
|
2023-06-05 15:11:55 +10:00
|
|
|
:type element: ifcopenshell.entity_instance.entity_instance
|
2022-08-15 19:40:20 +01:00
|
|
|
:return: The decomposition of the element
|
2023-06-05 15:11:55 +10:00
|
|
|
:rtype: list[ifcopenshell.entity_instance.entity_instance]
|
2022-08-15 19:40:20 +01:00
|
|
|
|
2023-01-10 10:16:28 +11:00
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
2022-08-15 19:40:20 +01:00
|
|
|
|
2022-08-19 18:43:39 +10:00
|
|
|
element = file.by_type("IfcProject")[0]
|
|
|
|
|
decomposition = ifcopenshell.util.element.get_decomposition(element)
|
2022-08-15 19:40:20 +01:00
|
|
|
"""
|
2021-09-17 15:40:27 +10:00
|
|
|
queue = [element]
|
|
|
|
|
results = []
|
|
|
|
|
while queue:
|
|
|
|
|
element = queue.pop()
|
|
|
|
|
for rel in getattr(element, "ContainsElements", []):
|
2024-04-09 18:36:55 +05:00
|
|
|
related = rel.RelatedElements
|
|
|
|
|
queue.extend(related)
|
|
|
|
|
results.extend(related)
|
2021-09-17 15:40:27 +10:00
|
|
|
for rel in getattr(element, "IsDecomposedBy", []):
|
2024-04-09 18:36:55 +05:00
|
|
|
related = rel.RelatedObjects
|
|
|
|
|
queue.extend(related)
|
|
|
|
|
results.extend(related)
|
2022-10-08 23:42:03 +11:00
|
|
|
for rel in getattr(element, "HasOpenings", []):
|
2024-04-09 18:36:55 +05:00
|
|
|
related = rel.RelatedOpeningElement
|
|
|
|
|
queue.append(related)
|
|
|
|
|
results.append(related)
|
2022-10-08 23:42:03 +11:00
|
|
|
for rel in getattr(element, "HasFillings", []):
|
2024-04-09 18:36:55 +05:00
|
|
|
related = rel.RelatedBuildingElement
|
|
|
|
|
queue.append(related)
|
|
|
|
|
results.append(related)
|
2023-04-19 10:44:52 +05:30
|
|
|
for rel in getattr(element, "IsNestedBy", []):
|
2024-04-09 18:36:55 +05:00
|
|
|
related = rel.RelatedObjects
|
|
|
|
|
queue.extend(related)
|
|
|
|
|
results.extend(related)
|
2023-06-05 15:11:55 +10:00
|
|
|
if not is_recursive:
|
|
|
|
|
break
|
2021-09-17 15:40:27 +10:00
|
|
|
return results
|
|
|
|
|
|
2022-11-03 17:13:56 +11:00
|
|
|
|
2024-04-09 16:41:03 +05:00
|
|
|
def get_grouped_by(element: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
2023-02-08 12:29:40 +11:00
|
|
|
"""Retrieves all subelements of an element based on the group.
|
2022-10-19 09:48:55 +02:00
|
|
|
|
|
|
|
|
:param element: The IFC element
|
2024-03-11 12:06:56 +05:00
|
|
|
:type element: ifcopenshell.entity_instance.entity_instance
|
2022-10-19 09:48:55 +02:00
|
|
|
:return: All subelements of the group
|
2024-03-11 12:06:56 +05:00
|
|
|
:rtype: list[ifcopenshell.entity_instance.entity_instance]
|
2021-09-17 15:40:27 +10:00
|
|
|
|
2023-01-10 10:16:28 +11:00
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
2022-10-19 09:48:55 +02:00
|
|
|
|
|
|
|
|
element = file.by_type("IfcGroup")[0]
|
2023-02-08 12:29:40 +11:00
|
|
|
subelements = ifcopenshell.util.element.get_grouped_by(element)
|
2022-10-19 09:48:55 +02:00
|
|
|
"""
|
|
|
|
|
queue = [element]
|
|
|
|
|
results = []
|
|
|
|
|
while queue:
|
|
|
|
|
element = queue.pop()
|
|
|
|
|
for rel in getattr(element, "IsGroupedBy", []):
|
2024-04-09 18:36:55 +05:00
|
|
|
related_objects = rel.RelatedObjects
|
|
|
|
|
queue.extend(related_objects)
|
|
|
|
|
results.extend(related_objects)
|
2022-10-19 09:48:55 +02:00
|
|
|
return results
|
2022-11-03 17:13:56 +11:00
|
|
|
|
|
|
|
|
|
2024-03-11 12:06:56 +05:00
|
|
|
def get_aggregate(element: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
2022-08-15 19:40:20 +01:00
|
|
|
"""
|
2023-11-20 16:57:19 +11:00
|
|
|
Retrieves the aggregate parent of an element.
|
2022-08-19 18:43:39 +10:00
|
|
|
|
2022-08-15 19:40:20 +01:00
|
|
|
:param element: The IFC element
|
2024-03-11 12:06:56 +05:00
|
|
|
:type element: ifcopenshell.entity_instance.entity_instance
|
2022-08-15 19:40:20 +01:00
|
|
|
:return: The aggregate of the element
|
2024-03-11 12:06:56 +05:00
|
|
|
:rtype: ifcopenshell.entity_instance.entity_instance
|
2022-08-15 19:40:20 +01:00
|
|
|
|
2023-01-10 10:16:28 +11:00
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
2024-01-24 16:36:58 +05:00
|
|
|
|
|
|
|
|
element = file.by_type("IfcBeam")[0]
|
|
|
|
|
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
2022-08-15 19:40:20 +01:00
|
|
|
"""
|
2024-04-09 18:36:55 +05:00
|
|
|
if decomposes := getattr(element, "Decomposes", None):
|
|
|
|
|
if decomposes[0].is_a("IfcRelAggregates"): # IFC2X3
|
|
|
|
|
return decomposes[0].RelatingObject
|
2021-04-19 16:08:57 +10:00
|
|
|
|
|
|
|
|
|
2024-03-11 12:06:56 +05:00
|
|
|
def get_nest(element: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
2023-11-20 16:57:19 +11:00
|
|
|
"""
|
|
|
|
|
Retrieves the nest parent of an element.
|
|
|
|
|
|
|
|
|
|
:param element: The IFC element
|
2024-03-11 12:06:56 +05:00
|
|
|
:type element: ifcopenshell.entity_instance.entity_instance
|
2023-11-20 16:57:19 +11:00
|
|
|
:return: The nested whole of the element
|
2024-03-11 12:06:56 +05:00
|
|
|
:rtype: ifcopenshell.entity_instance.entity_instance
|
2023-11-20 16:57:19 +11:00
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
2024-01-24 16:36:58 +05:00
|
|
|
|
|
|
|
|
element = file.by_type("IfcBeam")[0]
|
|
|
|
|
aggregate = ifcopenshell.util.element.get_nest(element)
|
2023-11-20 16:57:19 +11:00
|
|
|
"""
|
2024-04-09 18:36:55 +05:00
|
|
|
if (nests := getattr(element, "Nests", None)) is not None:
|
|
|
|
|
if nests:
|
|
|
|
|
return nests[0].RelatingObject
|
|
|
|
|
elif (decomposes := getattr(element, "Decomposes", None)) is not None and decomposes: # IFC2X3
|
|
|
|
|
if decomposes[0].is_a("IfcRelNests"):
|
|
|
|
|
return decomposes[0].RelatingObject
|
2023-11-20 16:57:19 +11:00
|
|
|
|
|
|
|
|
|
2024-04-09 16:41:03 +05:00
|
|
|
def get_parts(element: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
2022-08-15 19:40:20 +01:00
|
|
|
"""
|
2023-12-07 13:18:33 +11:00
|
|
|
Retrieves the parts of an element that have an aggregation relationship.
|
2022-08-19 18:43:39 +10:00
|
|
|
|
2022-08-15 19:40:20 +01:00
|
|
|
:param element: The IFC element
|
2024-03-11 12:06:56 +05:00
|
|
|
:type element: ifcopenshell.entity_instance.entity_instance
|
2022-08-15 19:40:20 +01:00
|
|
|
:return: The parts of the element
|
2024-03-11 12:06:56 +05:00
|
|
|
:rtype: list[ifcopenshell.entity_instance.entity_instance]
|
2022-08-15 19:40:20 +01:00
|
|
|
|
2023-01-10 10:16:28 +11:00
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
2022-08-19 18:43:39 +10:00
|
|
|
|
2024-01-24 16:36:58 +05:00
|
|
|
element = file.by_type("IfcElementAssembly")[0]
|
|
|
|
|
parts = ifcopenshell.util.element.get_parts(element)
|
2022-08-15 19:40:20 +01:00
|
|
|
"""
|
2024-04-09 18:36:55 +05:00
|
|
|
if (is_decomposed_by := getattr(element, "IsDecomposedBy", None)) is not None and is_decomposed_by:
|
|
|
|
|
if is_decomposed_by[0].is_a("IfcRelAggregates"):
|
|
|
|
|
return is_decomposed_by[0].RelatedObjects
|
2023-12-07 13:18:33 +11:00
|
|
|
|
|
|
|
|
|
2024-04-09 16:41:03 +05:00
|
|
|
def get_components(element: ifcopenshell.entity_instance, include_ports=False) -> list[ifcopenshell.entity_instance]:
|
2023-12-07 13:18:33 +11:00
|
|
|
"""
|
|
|
|
|
Retrieves the components of an element that have an nest relationship.
|
|
|
|
|
|
2023-12-14 16:47:10 +11:00
|
|
|
For nested ports, see ifcopenshell.util.system.
|
|
|
|
|
|
2023-12-07 13:18:33 +11:00
|
|
|
:param element: The IFC element
|
2023-12-14 16:47:10 +11:00
|
|
|
:param include_ports: Default as False. Set to true if you also want to get ports.
|
|
|
|
|
:type include_ports: bool,optional
|
2024-03-11 12:06:56 +05:00
|
|
|
:return: The components of the element
|
|
|
|
|
:rtype: list[ifcopenshell.entity_instance.entity_instance]
|
2023-12-07 13:18:33 +11:00
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
2024-01-24 16:36:58 +05:00
|
|
|
element = file.by_type("IfcElementAssembly")[0]
|
|
|
|
|
components = ifcopenshell.util.element.get_components(element)
|
2023-12-07 13:18:33 +11:00
|
|
|
"""
|
2024-04-09 18:36:55 +05:00
|
|
|
if (is_nested_by := getattr(element, "IsNestedBy", None)) is not None:
|
|
|
|
|
if is_nested_by:
|
2023-12-14 16:47:10 +11:00
|
|
|
if include_ports:
|
2024-04-09 18:36:55 +05:00
|
|
|
return is_nested_by[0].RelatedObjects
|
|
|
|
|
return [e for e in is_nested_by[0].RelatedObjects if not e.is_a("IfcPort")]
|
|
|
|
|
elif (is_decomposed_by := getattr(element, "IsDecomposedBy", None)) is not None and is_decomposed_by:
|
|
|
|
|
if is_decomposed_by[0].is_a("IfcRelNests"):
|
|
|
|
|
return is_decomposed_by[0].RelatedObjects
|
2022-02-03 07:16:43 +01:00
|
|
|
|
|
|
|
|
|
2024-03-11 12:06:56 +05:00
|
|
|
def replace_attribute(element: ifcopenshell.entity_instance, old: Any, new: Any) -> None:
|
|
|
|
|
for i, attribute_value in enumerate(element):
|
|
|
|
|
if has_element_reference(attribute_value, old):
|
|
|
|
|
element[i] = element.walk(lambda v: v == old, lambda v: new, attribute_value)
|
2021-07-04 19:45:34 +10:00
|
|
|
|
|
|
|
|
|
2024-03-11 12:06:56 +05:00
|
|
|
def has_element_reference(value: Any, element: ifcopenshell.entity_instance) -> bool:
|
2021-07-04 19:45:34 +10:00
|
|
|
if isinstance(value, (tuple, list)):
|
|
|
|
|
for v in value:
|
2021-08-30 21:32:03 +10:00
|
|
|
if has_element_reference(v, element):
|
|
|
|
|
return True
|
|
|
|
|
return False
|
2021-07-04 19:45:34 +10:00
|
|
|
return value == element
|
2021-01-01 12:15:18 +11:00
|
|
|
|
|
|
|
|
|
2024-03-11 12:06:56 +05:00
|
|
|
def remove_deep(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> None:
|
2023-01-12 12:28:09 +11:00
|
|
|
"""Recursively purges a subgraph safely.
|
|
|
|
|
|
|
|
|
|
Do not use, use remove_deep2() instead.
|
|
|
|
|
"""
|
2021-04-18 19:02:39 +10:00
|
|
|
# @todo maybe some sort of try-finally mechanism.
|
|
|
|
|
ifc_file.batch()
|
2021-08-12 13:35:45 +02:00
|
|
|
subgraph = list(ifc_file.traverse(element, breadth_first=True))
|
2021-01-24 14:32:04 +11:00
|
|
|
subgraph_set = set(subgraph)
|
|
|
|
|
for ref in subgraph[::-1]:
|
|
|
|
|
if ref.id() and len(set(ifc_file.get_inverse(ref)) - subgraph_set) == 0:
|
|
|
|
|
ifc_file.remove(ref)
|
2021-03-13 12:16:40 +01:00
|
|
|
ifc_file.unbatch()
|
|
|
|
|
|
|
|
|
|
|
2024-03-11 12:06:56 +05:00
|
|
|
def batch_remove_deep2(ifc_file: ifcopenshell.file) -> None:
|
2023-06-06 17:37:49 +10:00
|
|
|
"""Enable batch removal after running remove_deep2 using serialisation
|
|
|
|
|
|
|
|
|
|
See #944 and #3226. Removing elements in an IFC graph is slow as a lot of
|
|
|
|
|
mappings need to be edited. In larger models (>100MB) and when removing
|
|
|
|
|
many elements (>10000), it is faster to serialise the IFC, remove elements
|
|
|
|
|
using string replacement, and then reload the modified serialised IFC.
|
|
|
|
|
|
|
|
|
|
The trade-off is that extra memory will be used, and string replacement
|
|
|
|
|
only works with remove_deep2 where the removed elements have no inverses.
|
|
|
|
|
In addition, transaction history will be lost, and any scripts using this
|
|
|
|
|
method will have to refetch elements from the reloaded IFC and cannot rely
|
|
|
|
|
on existing variables in memory.
|
|
|
|
|
|
|
|
|
|
:param ifc_file: The IFC file object
|
|
|
|
|
:type ifc_file: ifcopenshell.file.file
|
|
|
|
|
:rtype: None
|
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
element1 = model.by_id(123)
|
|
|
|
|
element2 = model.by_id(456)
|
|
|
|
|
|
|
|
|
|
ifcopenshell.util.element.batch_remove_deep2(model)
|
|
|
|
|
ifcopenshell.util.element.remove_deep2(model, element2)
|
|
|
|
|
|
|
|
|
|
# Notice how we reload the model.
|
|
|
|
|
model = ifcopenshell.util.element.unbatch_remove_deep2(model)
|
|
|
|
|
|
|
|
|
|
print(element1) # Don't call element1!
|
|
|
|
|
"""
|
|
|
|
|
ifc_file.to_delete = set()
|
|
|
|
|
|
|
|
|
|
|
2024-03-11 12:06:56 +05:00
|
|
|
def unbatch_remove_deep2(ifc_file: ifcopenshell.file) -> ifcopenshell.file:
|
2023-06-06 17:37:49 +10:00
|
|
|
"""Finish removing elements batched from remove_deep2 using string replacement
|
|
|
|
|
|
|
|
|
|
See documentation for batch_remove_deep2.
|
|
|
|
|
|
|
|
|
|
:param ifc_file: The IFC file object
|
|
|
|
|
:type ifc_file: ifcopenshell.file.file
|
|
|
|
|
:return: A newly loaded file with the elements removed.
|
|
|
|
|
:rtype: ifcopenshell.file.file
|
|
|
|
|
"""
|
|
|
|
|
ifc_string = ifc_file.to_string()
|
2023-08-25 16:51:45 +10:00
|
|
|
lines = iter(ifc_string.split("\n"))
|
2023-06-06 17:37:49 +10:00
|
|
|
ids_to_delete = iter(sorted([e.id() for e in ifc_file.to_delete]))
|
|
|
|
|
id_to_delete = next(ids_to_delete, None)
|
|
|
|
|
result = []
|
|
|
|
|
|
|
|
|
|
for line in lines:
|
|
|
|
|
if id_to_delete is None:
|
|
|
|
|
result.append(line)
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
if line.startswith(f"#{id_to_delete}="):
|
|
|
|
|
id_to_delete = next(ids_to_delete, None)
|
|
|
|
|
else:
|
|
|
|
|
result.append(line)
|
|
|
|
|
|
|
|
|
|
ifc_file.to_delete = None
|
|
|
|
|
return ifcopenshell.file.from_string("\n".join(result))
|
|
|
|
|
|
|
|
|
|
|
2024-03-11 12:06:56 +05:00
|
|
|
def remove_deep2(
|
|
|
|
|
ifc_file: ifcopenshell.file,
|
|
|
|
|
element: ifcopenshell.entity_instance,
|
2024-04-09 16:41:03 +05:00
|
|
|
also_consider: list[ifcopenshell.entity_instance] = [],
|
|
|
|
|
do_not_delete: list[ifcopenshell.entity_instance] = [],
|
2024-03-11 12:06:56 +05:00
|
|
|
) -> None:
|
2023-01-12 12:28:09 +11:00
|
|
|
"""Recursively purges a subgraph safely, starting at an element
|
2022-08-30 20:08:03 +10:00
|
|
|
|
|
|
|
|
This should always be used instead of remove_deep. See #1812. The start
|
|
|
|
|
element must have no inverses. The subgraph to be purged is calculated using
|
|
|
|
|
all forward relationships determined by the traverse() function.
|
|
|
|
|
|
|
|
|
|
The deletion process starts at element and traverses forward through the
|
|
|
|
|
subgraph. Each subelement is checked for any inverses outside the subgraph.
|
|
|
|
|
If there are no inverses outside, it may be safely purged. If there are
|
|
|
|
|
inverses that aren't part of this subgraph, that subelement, and all of its
|
|
|
|
|
subelements (i.e. that entire branch of subelements) will not be deleted as
|
|
|
|
|
it is used elsewhere.
|
|
|
|
|
|
|
|
|
|
For simple subgraphs, traverse() is sufficient to fully represent all
|
|
|
|
|
related subelements. When it isn't, the ``also_consider`` argument may be
|
|
|
|
|
used. These are typically inverses futher down the subelement chain.
|
|
|
|
|
|
|
|
|
|
Note that remove_deep2 will _not_ remove elements in also_consider. Instead,
|
|
|
|
|
it is only used as a consideration for whether or not an element has all
|
|
|
|
|
inverses fully contained in the subgraph.
|
|
|
|
|
|
|
|
|
|
The do_not_delete argument contains all elements that may be part of the
|
|
|
|
|
subgraph but are protected from deletion.
|
|
|
|
|
|
2023-01-12 12:28:09 +11:00
|
|
|
:param ifc_file: The IFC file object
|
|
|
|
|
:type ifc_file: ifcopenshell.file.file
|
2024-03-11 12:06:56 +05:00
|
|
|
:param also_consider: elements to also consider as a part of a subgraph
|
|
|
|
|
:type also_consider: list[ifcopenshell.entity_instance.entity_instance], optional
|
|
|
|
|
:param do_not_delete: elements to protect from deletion
|
|
|
|
|
:type do_not_delete: list[ifcopenshell.entity_instance.entity_instance], optional
|
2022-08-30 20:08:03 +10:00
|
|
|
:param element: The starting element that defines the subgraph
|
|
|
|
|
:type element: ifcopenshell.entity_instance.entity_instance
|
|
|
|
|
"""
|
2023-10-31 12:31:05 +11:00
|
|
|
# ifc_file.batch()
|
2021-10-20 11:44:09 +11:00
|
|
|
to_delete = set()
|
|
|
|
|
subgraph = list(ifc_file.traverse(element, breadth_first=True))
|
2021-10-21 16:28:55 +11:00
|
|
|
subgraph.extend(also_consider)
|
2021-10-20 11:44:09 +11:00
|
|
|
subgraph_set = set(subgraph)
|
|
|
|
|
subelement_queue = ifc_file.traverse(element, max_levels=1)
|
|
|
|
|
while subelement_queue:
|
|
|
|
|
subelement = subelement_queue.pop(0)
|
2021-10-21 16:28:55 +11:00
|
|
|
if (
|
|
|
|
|
subelement.id()
|
|
|
|
|
and subelement not in do_not_delete
|
2022-08-30 20:08:03 +10:00
|
|
|
and len(set(ifc_file.get_inverse(subelement)) - subgraph_set) == 0
|
2021-10-21 16:28:55 +11:00
|
|
|
):
|
2021-10-20 11:44:09 +11:00
|
|
|
to_delete.add(subelement)
|
|
|
|
|
subelement_queue.extend(ifc_file.traverse(subelement, max_levels=1)[1:])
|
2023-04-27 20:15:05 +10:00
|
|
|
# See #3052. IfcOpenShell is extremely slow in removing elements if
|
|
|
|
|
# the element has an inverse, and that inverse references that
|
|
|
|
|
# element in a big list. The most common example is an
|
|
|
|
|
# IfcPolygonalFaceSet with a Faces attribute of tens of thousands
|
|
|
|
|
# of IfcIndexedPolygonalFace. In this situation, removing a
|
|
|
|
|
# IfcIndexedPolygonalFace will take very, very long. If we are
|
|
|
|
|
# going to delete an element (i.e. added to the to_delete set), we
|
|
|
|
|
# clear any large lists (10 is an arbitrary threshold) to prevent
|
|
|
|
|
# this issue.
|
|
|
|
|
for i, attribute in enumerate(subelement):
|
|
|
|
|
if isinstance(attribute, tuple) and len(attribute) > 10:
|
|
|
|
|
subelement[i] = []
|
2023-06-06 17:37:49 +10:00
|
|
|
|
|
|
|
|
if getattr(ifc_file, "to_delete", None) is not None:
|
|
|
|
|
ifc_file.to_delete.update(to_delete)
|
|
|
|
|
return
|
|
|
|
|
|
2022-08-30 20:08:03 +10:00
|
|
|
# We delete elements from subgraph in reverse order to allow batching to work
|
|
|
|
|
for subelement in filter(lambda e: e in to_delete, subgraph[::-1]):
|
2021-10-20 11:44:09 +11:00
|
|
|
ifc_file.remove(subelement)
|
2023-10-31 12:31:05 +11:00
|
|
|
# ifc_file.unbatch()
|
2021-10-20 11:44:09 +11:00
|
|
|
|
|
|
|
|
|
2024-03-11 12:06:56 +05:00
|
|
|
def copy(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
2022-10-09 17:15:39 +11:00
|
|
|
"""
|
|
|
|
|
Copy a single element. Any referenced elements are not copied.
|
|
|
|
|
|
|
|
|
|
GlobalIds are regenerated.
|
|
|
|
|
|
|
|
|
|
:param ifc_file: The IFC file object
|
2023-01-12 12:28:09 +11:00
|
|
|
:type ifc_file: ifcopenshell.file.file
|
2022-10-09 17:15:39 +11:00
|
|
|
:param element: The IFC element to copy
|
2023-01-12 12:28:09 +11:00
|
|
|
:type element: ifcopenshell.entity_instance.entity_instance
|
2022-10-09 17:15:39 +11:00
|
|
|
:return: The newly copied element
|
2023-01-12 12:28:09 +11:00
|
|
|
:rtype: ifcopenshell.entity_instance.entity_instance
|
2022-10-09 17:15:39 +11:00
|
|
|
"""
|
2021-05-13 15:11:18 +10:00
|
|
|
new = ifc_file.create_entity(element.is_a())
|
|
|
|
|
for i, attribute in enumerate(element):
|
|
|
|
|
if attribute is None:
|
|
|
|
|
continue
|
2021-08-10 11:48:38 +10:00
|
|
|
if new.attribute_name(i) == "GlobalId":
|
|
|
|
|
new[i] = ifcopenshell.guid.new()
|
|
|
|
|
else:
|
|
|
|
|
new[i] = attribute
|
2021-05-13 15:11:18 +10:00
|
|
|
return new
|
|
|
|
|
|
|
|
|
|
|
2024-03-11 12:06:56 +05:00
|
|
|
def copy_deep(
|
|
|
|
|
ifc_file: ifcopenshell.file,
|
|
|
|
|
element: ifcopenshell.entity_instance,
|
2024-04-09 16:41:03 +05:00
|
|
|
exclude: Optional[list[str]] = None,
|
2024-03-14 14:03:29 +05:00
|
|
|
exclude_callback: Optional[Callable[[ifcopenshell.entity_instance], bool]] = None,
|
|
|
|
|
copied_entities: Optional[dict[int, ifcopenshell.entity_instance]] = None,
|
2024-03-11 12:06:56 +05:00
|
|
|
) -> ifcopenshell.entity_instance:
|
2022-10-09 17:15:39 +11:00
|
|
|
"""
|
|
|
|
|
Recursively copy an element and all of its directly related subelements.
|
|
|
|
|
|
|
|
|
|
GlobalIds are regenerated.
|
|
|
|
|
|
|
|
|
|
:param ifc_file: The IFC file object
|
2023-01-12 12:28:09 +11:00
|
|
|
:type ifc_file: ifcopenshell.file.file
|
2022-10-09 17:15:39 +11:00
|
|
|
:param element: The IFC element to copy
|
2023-01-12 12:28:09 +11:00
|
|
|
:type element: ifcopenshell.entity_instance.entity_instance
|
2022-10-09 17:15:39 +11:00
|
|
|
:param exclude: An optional list of strings of IFC class names to not copy.
|
|
|
|
|
If any of the subelement is this class, it will not be copied and the
|
|
|
|
|
original instance will be referenced.
|
2023-01-28 21:51:00 +11:00
|
|
|
:type exclude: list[str],optional
|
|
|
|
|
:param exclude_callback: A callback to determine whether or not to exclude
|
|
|
|
|
an entity or not. Returns True to exclude and False to exclude.
|
|
|
|
|
:type exclude_callback: function,optional
|
2023-03-19 22:03:45 +11:00
|
|
|
:param copied_entities: A dictionary of IDs as keys and entities as values
|
|
|
|
|
to reuse when coming across the same entity twice. This can typically
|
|
|
|
|
be left as None.
|
2024-03-11 12:06:56 +05:00
|
|
|
:type copied_entities: dict[int:ifcopenshell.entity_instance.entity_instance], optional
|
2022-10-09 17:15:39 +11:00
|
|
|
:return: The newly copied element
|
2023-01-12 12:28:09 +11:00
|
|
|
:rtype: ifcopenshell.entity_instance.entity_instance
|
2022-10-09 17:15:39 +11:00
|
|
|
"""
|
2023-03-19 22:03:45 +11:00
|
|
|
if copied_entities is None:
|
|
|
|
|
copied_entities = {}
|
|
|
|
|
else:
|
|
|
|
|
copied_entity = copied_entities.get(element.id(), None)
|
|
|
|
|
if copied_entity:
|
|
|
|
|
return copied_entity
|
2021-05-13 15:11:18 +10:00
|
|
|
new = ifc_file.create_entity(element.is_a())
|
2023-03-19 22:03:45 +11:00
|
|
|
if element.id():
|
|
|
|
|
copied_entities[element.id()] = new
|
2021-05-13 15:11:18 +10:00
|
|
|
for i, attribute in enumerate(element):
|
|
|
|
|
if attribute is None:
|
|
|
|
|
continue
|
|
|
|
|
if isinstance(attribute, ifcopenshell.entity_instance):
|
2023-01-28 21:51:00 +11:00
|
|
|
if exclude and any([attribute.is_a(e) for e in exclude]):
|
|
|
|
|
pass
|
|
|
|
|
elif exclude_callback and exclude_callback(attribute):
|
|
|
|
|
pass
|
|
|
|
|
else:
|
2023-09-29 09:29:25 +05:00
|
|
|
attribute = copy_deep(
|
|
|
|
|
ifc_file,
|
|
|
|
|
attribute,
|
|
|
|
|
exclude=exclude,
|
|
|
|
|
copied_entities=copied_entities,
|
|
|
|
|
exclude_callback=exclude_callback,
|
|
|
|
|
)
|
2021-05-13 15:11:18 +10:00
|
|
|
elif isinstance(attribute, tuple) and attribute and isinstance(attribute[0], ifcopenshell.entity_instance):
|
2023-01-28 21:51:00 +11:00
|
|
|
if exclude and any([attribute[0].is_a(e) for e in exclude]):
|
|
|
|
|
pass
|
|
|
|
|
elif exclude_callback and exclude_callback(attribute[0]):
|
|
|
|
|
pass
|
|
|
|
|
else:
|
2022-10-09 17:15:39 +11:00
|
|
|
attribute = list(attribute)
|
|
|
|
|
for j, item in enumerate(attribute):
|
2023-03-19 22:03:45 +11:00
|
|
|
attribute[j] = copy_deep(
|
|
|
|
|
ifc_file,
|
|
|
|
|
item,
|
|
|
|
|
exclude=exclude,
|
|
|
|
|
exclude_callback=exclude_callback,
|
|
|
|
|
copied_entities=copied_entities,
|
|
|
|
|
)
|
2022-10-09 17:15:39 +11:00
|
|
|
if new.attribute_name(i) == "GlobalId":
|
|
|
|
|
new[i] = ifcopenshell.guid.new()
|
|
|
|
|
else:
|
|
|
|
|
new[i] = attribute
|
2021-05-13 15:11:18 +10:00
|
|
|
return new
|