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/>.
|
|
|
|
|
|
2021-05-13 15:11:18 +10:00
|
|
|
import ifcopenshell
|
|
|
|
|
|
|
|
|
|
|
2023-02-12 15:01:47 +11:00
|
|
|
def get_pset(element, name, prop=None, should_inherit=True):
|
|
|
|
|
"""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-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.
|
|
|
|
|
:rtype: dict
|
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
element = ifcopenshell.by_type("IfcWall")[0]
|
|
|
|
|
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
|
|
|
|
|
elif hasattr(element, "IsDefinedBy"):
|
2023-07-04 14:24:09 +10:00
|
|
|
if should_inherit:
|
|
|
|
|
element_type = ifcopenshell.util.element.get_type(element)
|
|
|
|
|
if element_type:
|
|
|
|
|
type_pset = get_pset(element_type, name, prop, should_inherit=False)
|
2023-02-12 15:01:47 +11:00
|
|
|
for relationship in element.IsDefinedBy:
|
|
|
|
|
if relationship.is_a("IfcRelDefinesByProperties"):
|
|
|
|
|
definition = relationship.RelatingPropertyDefinition
|
|
|
|
|
if definition.Name == name:
|
|
|
|
|
pset = definition
|
|
|
|
|
break
|
|
|
|
|
|
2023-07-04 14:24:09 +10:00
|
|
|
if not pset and not type_pset:
|
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-07-04 15:50:29 +10:00
|
|
|
occurrence_pset = get_property_definition(pset)
|
|
|
|
|
if occurrence_pset:
|
|
|
|
|
type_pset.update(occurrence_pset)
|
2023-07-04 14:24:09 +10:00
|
|
|
return type_pset
|
2023-02-12 15:01:47 +11:00
|
|
|
return get_property_definition(pset)
|
|
|
|
|
|
2023-07-04 14:24:09 +10:00
|
|
|
value = get_property_definition(pset, prop)
|
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
|
|
|
|
2022-05-11 09:18:11 +10:00
|
|
|
def get_psets(element, psets_only=False, qtos_only=False, should_inherit=True):
|
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
|
|
|
|
|
:return: Key, value pair of psets' names and their properties' names & values
|
|
|
|
|
:rtype: dict
|
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
|
|
|
|
2023-02-12 15:01:47 +11:00
|
|
|
element = ifcopenshell.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
|
|
|
|
|
psets[definition.Name] = get_property_definition(definition)
|
|
|
|
|
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
|
|
|
|
|
psets[definition.Name] = get_property_definition(definition)
|
2021-06-06 20:54:40 +10:00
|
|
|
elif hasattr(element, "IsDefinedBy"):
|
2023-07-04 15:50:29 +10:00
|
|
|
if should_inherit:
|
|
|
|
|
element_type = ifcopenshell.util.element.get_type(element)
|
|
|
|
|
if element_type:
|
|
|
|
|
psets = get_psets(element_type, psets_only=psets_only, qtos_only=qtos_only, should_inherit=False)
|
2021-06-06 20:54:40 +10:00
|
|
|
for relationship in element.IsDefinedBy:
|
|
|
|
|
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
|
2022-05-11 09:18:11 +10:00
|
|
|
psets.setdefault(definition.Name, {}).update(get_property_definition(definition))
|
2020-05-17 18:32:56 +10:00
|
|
|
return psets
|
|
|
|
|
|
2020-08-04 20:18:08 +10:00
|
|
|
|
2023-07-04 14:24:09 +10:00
|
|
|
def get_property_definition(definition, prop=None):
|
|
|
|
|
if not definition:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if prop:
|
2020-11-01 20:08:27 +07:00
|
|
|
if definition.is_a("IfcElementQuantity"):
|
2023-07-04 14:24:09 +10:00
|
|
|
return get_quantity(definition.Quantities, prop)
|
2020-11-01 20:08:27 +07:00
|
|
|
elif definition.is_a("IfcPropertySet"):
|
2023-07-04 14:24:09 +10:00
|
|
|
return get_property(definition.HasProperties, prop)
|
2021-10-25 16:09:04 +11:00
|
|
|
elif definition.is_a("IfcMaterialProperties") or definition.is_a("IfcProfileProperties"):
|
2023-07-04 14:24:09 +10:00
|
|
|
return get_property(definition.Properties, prop)
|
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)):
|
|
|
|
|
if definition[i] is not None:
|
|
|
|
|
if definition.attribute_name(i) == prop:
|
|
|
|
|
return definition[i]
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
props = {}
|
|
|
|
|
if definition.is_a("IfcElementQuantity"):
|
|
|
|
|
props.update(get_quantities(definition.Quantities))
|
|
|
|
|
elif definition.is_a("IfcPropertySet"):
|
|
|
|
|
props.update(get_properties(definition.HasProperties))
|
|
|
|
|
elif definition.is_a("IfcMaterialProperties") or definition.is_a("IfcProfileProperties"):
|
|
|
|
|
props.update(get_properties(definition.Properties))
|
|
|
|
|
else:
|
|
|
|
|
# Entity introduced in IFC4
|
|
|
|
|
# definition.is_a('IfcPreDefinedPropertySet'):
|
|
|
|
|
for prop in range(4, len(definition)):
|
|
|
|
|
if definition[prop] is not None:
|
|
|
|
|
props[definition.attribute_name(prop)] = definition[prop]
|
|
|
|
|
props["id"] = definition.id()
|
|
|
|
|
return props
|
2020-08-04 20:18:08 +10:00
|
|
|
|
|
|
|
|
|
2023-02-12 15:01:47 +11:00
|
|
|
def get_quantity(quantities, name):
|
|
|
|
|
for quantity in quantities or []:
|
|
|
|
|
if quantity.Name != name:
|
|
|
|
|
continue
|
|
|
|
|
if quantity.is_a("IfcPhysicalSimpleQuantity"):
|
|
|
|
|
return quantity[3]
|
|
|
|
|
results[quantity.Name] = quantity[3]
|
|
|
|
|
elif quantity.is_a("IfcPhysicalComplexQuantity"):
|
|
|
|
|
data = {k: v for k, v in quantity.get_info().items() if v is not None and k != "Name"}
|
|
|
|
|
data["properties"] = get_quantities(quantity.HasQuantities)
|
|
|
|
|
del data["HasQuantities"]
|
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_quantities(quantities, name=None):
|
2020-08-04 20:18:08 +10:00
|
|
|
results = {}
|
2021-10-25 16:09:04 +11:00
|
|
|
for quantity in quantities or []:
|
2020-11-01 20:08:27 +07:00
|
|
|
if quantity.is_a("IfcPhysicalSimpleQuantity"):
|
2020-08-04 20:18:08 +10:00
|
|
|
results[quantity.Name] = quantity[3]
|
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"}
|
|
|
|
|
data["properties"] = get_quantities(quantity.HasQuantities)
|
|
|
|
|
del data["HasQuantities"]
|
|
|
|
|
results[quantity.Name] = data
|
2020-08-04 20:18:08 +10:00
|
|
|
return results
|
|
|
|
|
|
|
|
|
|
|
2023-02-12 15:01:47 +11:00
|
|
|
def get_property(properties, name):
|
|
|
|
|
for prop in properties or []:
|
|
|
|
|
if prop.Name != name:
|
|
|
|
|
continue
|
|
|
|
|
if prop.is_a("IfcPropertySingleValue"):
|
|
|
|
|
return prop.NominalValue.wrappedValue if prop.NominalValue else None
|
|
|
|
|
elif prop.is_a("IfcPropertyEnumeratedValue"):
|
|
|
|
|
return [v.wrappedValue for v in prop.EnumerationValues] if prop.EnumerationValues else None
|
|
|
|
|
elif prop.is_a("IfcPropertyListValue"):
|
|
|
|
|
return [v.wrappedValue for v in prop.ListValues] or None
|
|
|
|
|
elif prop.is_a("IfcPropertyBoundedValue"):
|
|
|
|
|
data = prop.get_info()
|
|
|
|
|
del data["Unit"]
|
|
|
|
|
return data
|
|
|
|
|
elif prop.is_a("IfcPropertyTableValue"):
|
|
|
|
|
return prop.get_info()
|
|
|
|
|
elif prop.is_a("IfcComplexProperty"):
|
|
|
|
|
data = {k: v for k, v in prop.get_info().items() if v is not None and k != "Name"}
|
|
|
|
|
data["properties"] = get_properties(prop.HasProperties)
|
|
|
|
|
del data["HasProperties"]
|
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
|
2020-08-04 20:18:08 +10:00
|
|
|
def get_properties(properties):
|
|
|
|
|
results = {}
|
2021-09-20 14:45:31 +10:00
|
|
|
for prop in properties or []:
|
2020-11-01 20:08:27 +07:00
|
|
|
if prop.is_a("IfcPropertySingleValue"):
|
2021-02-01 10:18:10 +11:00
|
|
|
results[prop.Name] = prop.NominalValue.wrappedValue if prop.NominalValue else None
|
2022-06-28 15:28:37 +02:00
|
|
|
elif prop.is_a("IfcPropertyEnumeratedValue"):
|
2022-11-18 14:35:57 +01:00
|
|
|
results[prop.Name] = [v.wrappedValue for v in prop.EnumerationValues] if prop.EnumerationValues else None
|
2022-09-12 21:34:06 +10:00
|
|
|
elif prop.is_a("IfcPropertyListValue"):
|
|
|
|
|
results[prop.Name] = [v.wrappedValue for v in prop.ListValues] or None
|
2022-09-12 22:35:05 +10:00
|
|
|
elif prop.is_a("IfcPropertyBoundedValue"):
|
|
|
|
|
data = prop.get_info()
|
|
|
|
|
del data["Unit"]
|
|
|
|
|
results[prop.Name] = data
|
|
|
|
|
elif prop.is_a("IfcPropertyTableValue"):
|
|
|
|
|
results[prop.Name] = prop.get_info()
|
2020-11-01 20:08:27 +07:00
|
|
|
elif prop.is_a("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"}
|
2020-11-01 20:08:27 +07:00
|
|
|
data["properties"] = get_properties(prop.HasProperties)
|
|
|
|
|
del data["HasProperties"]
|
2020-08-04 20:18:08 +10:00
|
|
|
results[prop.Name] = data
|
|
|
|
|
return results
|
2020-08-04 21:08:49 +10:00
|
|
|
|
|
|
|
|
|
2022-01-12 14:11:23 +11:00
|
|
|
def get_predefined_type(element):
|
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:
|
2022-06-29 08:22:43 +02:00
|
|
|
predefined_type = getattr(element_type, "ElementType", getattr(element_type, "ProcessType", None))
|
2022-01-12 14:11:23 +11:00
|
|
|
if predefined_type and predefined_type != "NOTDEFINED":
|
|
|
|
|
return predefined_type
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
2020-08-08 17:58:45 +10:00
|
|
|
def get_type(element):
|
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
|
|
|
|
|
: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
|
|
|
|
|
elif hasattr(element, "IsTypedBy") and element.IsTypedBy:
|
2020-08-08 17:58:45 +10:00
|
|
|
return element.IsTypedBy[0].RelatingType
|
2020-11-01 20:08:27 +07:00
|
|
|
elif hasattr(element, "IsDefinedBy") and element.IsDefinedBy: # IFC2X3
|
2020-08-08 17:58:45 +10:00
|
|
|
for relationship in element.IsDefinedBy:
|
2020-11-01 20:08:27 +07:00
|
|
|
if relationship.is_a("IfcRelDefinesByType"):
|
2020-08-08 17:58:45 +10:00
|
|
|
return relationship.RelatingType
|
|
|
|
|
|
|
|
|
|
|
2021-11-08 20:40:49 +11:00
|
|
|
def get_types(type):
|
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 []
|
|
|
|
|
|
|
|
|
|
|
2022-05-12 12:06:23 +10:00
|
|
|
def get_material(element, should_skip_usage=False, should_inherit=True):
|
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
|
|
|
|
|
:return: The associated material of the element.
|
|
|
|
|
:rtype: ifcopenshell.entity_instance.entity_instance
|
|
|
|
|
|
|
|
|
|
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
|
|
|
"""
|
2020-12-02 09:03:18 +11:00
|
|
|
if hasattr(element, "HasAssociations") and element.HasAssociations:
|
|
|
|
|
for relationship in element.HasAssociations:
|
|
|
|
|
if relationship.is_a("IfcRelAssociatesMaterial"):
|
2021-06-21 12:21:46 +10:00
|
|
|
if should_skip_usage:
|
|
|
|
|
if relationship.RelatingMaterial.is_a("IfcMaterialLayerSetUsage"):
|
|
|
|
|
return relationship.RelatingMaterial.ForLayerSet
|
|
|
|
|
elif relationship.RelatingMaterial.is_a("IfcMaterialProfileSetUsage"):
|
|
|
|
|
return relationship.RelatingMaterial.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)
|
|
|
|
|
if relating_type != element and hasattr(relating_type, "HasAssociations") and relating_type.HasAssociations:
|
|
|
|
|
return get_material(relating_type, should_skip_usage)
|
2020-12-02 09:03:18 +11:00
|
|
|
|
|
|
|
|
|
2023-03-09 17:48:10 +11:00
|
|
|
def get_materials(element, should_inherit=True):
|
|
|
|
|
"""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-08-14 11:55:15 +10:00
|
|
|
def get_styles(element):
|
|
|
|
|
"""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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2022-03-22 12:50:10 +11:00
|
|
|
def get_elements_by_material(ifc_file, material):
|
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"):
|
2022-05-08 18:03:18 +10:00
|
|
|
results.update(inverse.RelatedObjects)
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_elements_by_style(ifc_file, style):
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_elements_by_representation(ifc_file, representation):
|
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
|
|
|
|
|
|
|
|
|
|
|
2023-01-12 12:28:09 +11:00
|
|
|
def get_elements_by_layer(ifc_file, layer):
|
|
|
|
|
"""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()
|
|
|
|
|
for item in layer.AssignedItems:
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
2021-11-16 18:07:27 +11:00
|
|
|
def get_layers(ifc_file, element):
|
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 = []
|
|
|
|
|
if getattr(element, "Representation", None):
|
|
|
|
|
representations = [element.Representation]
|
|
|
|
|
elif getattr(element, "RepresentationMaps", None):
|
|
|
|
|
representations = element.RepresentationMaps
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
2023-08-21 23:06:49 +10:00
|
|
|
def get_container(element, should_get_direct=False, ifc_class=None):
|
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.
|
|
|
|
|
:type ifc_class: str
|
2022-08-19 18:43:39 +10:00
|
|
|
:return: The direct or indirect container of the element or None.
|
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:
|
|
|
|
|
if hasattr(element, "ContainedInStructure") and element.ContainedInStructure:
|
2023-08-21 23:06:49 +10:00
|
|
|
container = element.ContainedInStructure[0].RelatingStructure
|
|
|
|
|
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)
|
|
|
|
|
if hasattr(element, "ContainedInStructure") and element.ContainedInStructure:
|
2023-08-21 23:06:49 +10:00
|
|
|
container = element.ContainedInStructure[0].RelatingStructure
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
2022-08-19 18:43:39 +10:00
|
|
|
def get_referenced_structures(element):
|
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
|
|
|
|
|
|
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))
|
|
|
|
|
"""
|
|
|
|
|
if hasattr(element, "ReferencedInStructures"):
|
|
|
|
|
return [r.RelatingStructure for r in element.ReferencedInStructures]
|
2022-08-22 17:02:05 +10:00
|
|
|
return []
|
2022-08-19 18:43:39 +10:00
|
|
|
|
|
|
|
|
|
2023-06-05 15:11:55 +10:00
|
|
|
def get_decomposition(element, is_recursive=True):
|
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", []):
|
|
|
|
|
queue.extend(rel.RelatedElements)
|
|
|
|
|
results.extend(rel.RelatedElements)
|
|
|
|
|
for rel in getattr(element, "IsDecomposedBy", []):
|
|
|
|
|
queue.extend(rel.RelatedObjects)
|
|
|
|
|
results.extend(rel.RelatedObjects)
|
2022-10-08 23:42:03 +11:00
|
|
|
for rel in getattr(element, "HasOpenings", []):
|
|
|
|
|
queue.append(rel.RelatedOpeningElement)
|
|
|
|
|
results.append(rel.RelatedOpeningElement)
|
|
|
|
|
for rel in getattr(element, "HasFillings", []):
|
|
|
|
|
queue.append(rel.RelatedBuildingElement)
|
|
|
|
|
results.append(rel.RelatedBuildingElement)
|
2023-04-19 10:44:52 +05:30
|
|
|
for rel in getattr(element, "IsNestedBy", []):
|
|
|
|
|
queue.extend(rel.RelatedObjects)
|
|
|
|
|
results.extend(rel.RelatedObjects)
|
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
|
|
|
|
2022-10-19 09:48:55 +02:00
|
|
|
def get_grouped_by(element):
|
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
|
|
|
|
|
:return: All subelements of the group
|
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", []):
|
|
|
|
|
queue.extend(rel.RelatedObjects)
|
|
|
|
|
results.extend(rel.RelatedObjects)
|
|
|
|
|
return results
|
2022-11-03 17:13:56 +11:00
|
|
|
|
|
|
|
|
|
2021-04-19 16:08:57 +10:00
|
|
|
def get_aggregate(element):
|
2022-08-15 19:40:20 +01:00
|
|
|
"""
|
|
|
|
|
Retrieves the aggregate of an element.
|
2022-08-19 18:43:39 +10:00
|
|
|
|
2022-08-15 19:40:20 +01:00
|
|
|
:param element: The IFC element
|
|
|
|
|
:return: The aggregate of the element
|
|
|
|
|
|
2023-01-10 10:16:28 +11:00
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
2022-08-15 19:40:20 +01:00
|
|
|
element = file.by_type("IfcBeam")[0]
|
|
|
|
|
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
|
|
|
|
"""
|
2021-04-19 16:08:57 +10:00
|
|
|
if hasattr(element, "Decomposes") and element.Decomposes:
|
|
|
|
|
return element.Decomposes[0].RelatingObject
|
|
|
|
|
|
|
|
|
|
|
2022-02-03 07:16:43 +01:00
|
|
|
def get_parts(element):
|
2022-08-15 19:40:20 +01:00
|
|
|
"""
|
|
|
|
|
Retrieves the parts of an element.
|
2022-08-19 18:43:39 +10:00
|
|
|
|
2022-08-15 19:40:20 +01:00
|
|
|
:param element: The IFC element
|
|
|
|
|
:return: The parts of the element
|
|
|
|
|
|
2023-01-10 10:16:28 +11:00
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
2022-08-15 19:40:20 +01:00
|
|
|
element = file.by_type("IfcElementAssembly")[0]
|
|
|
|
|
parts = ifcopenshell.util.element.get_parts(element)
|
2022-08-19 18:43:39 +10:00
|
|
|
|
2022-08-15 19:40:20 +01:00
|
|
|
"""
|
2022-02-03 07:16:43 +01:00
|
|
|
if hasattr(element, "IsDecomposedBy") and element.IsDecomposedBy:
|
|
|
|
|
return element.IsDecomposedBy[0].RelatedObjects
|
|
|
|
|
|
|
|
|
|
|
2020-08-04 21:08:49 +10:00
|
|
|
def replace_attribute(element, old, new):
|
|
|
|
|
for i, attribute in enumerate(element):
|
2021-07-04 19:45:34 +10:00
|
|
|
if has_element_reference(attribute, old):
|
2022-09-15 10:31:44 +10:00
|
|
|
element[i] = element.walk(lambda v: v == old, lambda v: new, attribute)
|
2021-07-04 19:45:34 +10:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def has_element_reference(value, element):
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
2021-01-24 14:32:04 +11:00
|
|
|
def remove_deep(ifc_file, element):
|
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()
|
|
|
|
|
|
|
|
|
|
|
2023-06-06 17:37:49 +10:00
|
|
|
def batch_remove_deep2(ifc_file):
|
|
|
|
|
"""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()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def unbatch_remove_deep2(ifc_file):
|
|
|
|
|
"""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()
|
|
|
|
|
lines = iter(ifc_string.split('\n'))
|
|
|
|
|
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))
|
|
|
|
|
|
|
|
|
|
|
2021-10-21 16:28:55 +11:00
|
|
|
def remove_deep2(ifc_file, element, also_consider=[], do_not_delete=[]):
|
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
|
2022-08-30 20:08:03 +10:00
|
|
|
:param element: The starting element that defines the subgraph
|
|
|
|
|
:type element: ifcopenshell.entity_instance.entity_instance
|
|
|
|
|
"""
|
|
|
|
|
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)
|
2022-08-30 20:08:03 +10:00
|
|
|
ifc_file.unbatch()
|
2021-10-20 11:44:09 +11:00
|
|
|
|
|
|
|
|
|
2021-05-13 15:11:18 +10:00
|
|
|
def copy(ifc_file, element):
|
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
|
|
|
|
|
|
|
|
|
|
|
2023-03-19 22:03:45 +11:00
|
|
|
def copy_deep(ifc_file, element, exclude=None, exclude_callback=None, copied_entities=None):
|
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.
|
|
|
|
|
:type copied_entities: dict[int: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
|
|
|
"""
|
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-03-19 22:03:45 +11:00
|
|
|
attribute = copy_deep(ifc_file, attribute, exclude=exclude, copied_entities=copied_entities)
|
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
|