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
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
:param element: The IFC Element entity
|
2022-08-19 18:43:39 +10:00
|
|
|
:param psets_only: Default as False. Set to true if only property sets are needed.
|
2022-08-15 19:40:20 +01:00
|
|
|
:param qtos_only: Default as False. Set to true if only quantities are needed.
|
|
|
|
|
:param should_inherit: Default as True. Set to false if you don't want to inherit property sets from the Type.
|
|
|
|
|
:return: dictionnary: key, value pair of psets' names and their properties' names & values
|
|
|
|
|
|
|
|
|
|
Example::
|
|
|
|
|
element = ifcopenshell.by_type("IfcBuildingElement")[0]
|
|
|
|
|
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"):
|
|
|
|
|
for definition in element.HasProperties or []:
|
|
|
|
|
if qtos_only:
|
|
|
|
|
continue
|
|
|
|
|
psets[definition.Name] = get_property_definition(definition)
|
2021-06-06 20:54:40 +10:00
|
|
|
elif hasattr(element, "IsDefinedBy"):
|
2022-05-11 09:18:11 +10:00
|
|
|
element_type = ifcopenshell.util.element.get_type(element)
|
|
|
|
|
if element_type and should_inherit:
|
2022-06-08 09:07:10 +10:00
|
|
|
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
|
|
|
|
|
|
|
|
def get_property_definition(definition):
|
2020-05-17 18:32:56 +10:00
|
|
|
if definition is not None:
|
|
|
|
|
props = {}
|
2020-11-01 20:08:27 +07:00
|
|
|
if definition.is_a("IfcElementQuantity"):
|
2020-08-04 20:18:08 +10:00
|
|
|
props.update(get_quantities(definition.Quantities))
|
2020-11-01 20:08:27 +07:00
|
|
|
elif definition.is_a("IfcPropertySet"):
|
2020-08-04 20:18:08 +10:00
|
|
|
props.update(get_properties(definition.HasProperties))
|
2021-10-25 16:09:04 +11:00
|
|
|
elif definition.is_a("IfcMaterialProperties") or definition.is_a("IfcProfileProperties"):
|
|
|
|
|
props.update(get_properties(definition.Properties))
|
2020-05-17 18:32:56 +10:00
|
|
|
else:
|
|
|
|
|
# Entity introduced in IFC4
|
|
|
|
|
# definition.is_a('IfcPreDefinedPropertySet'):
|
|
|
|
|
for prop in range(4, len(definition)):
|
2021-08-31 15:44:57 +10:00
|
|
|
if definition[prop] is not None:
|
|
|
|
|
props[definition.attribute_name(prop)] = definition[prop]
|
2021-10-25 14:21:32 +11:00
|
|
|
props["id"] = definition.id()
|
2020-05-17 18:32:56 +10:00
|
|
|
return props
|
2020-08-04 20:18:08 +10:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_quantities(quantities):
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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-06-29 08:22:43 +02:00
|
|
|
results[prop.Name] = [v.wrappedValue for v in prop.EnumerationValues] or 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):
|
2022-08-15 19:40:20 +01:00
|
|
|
"""
|
|
|
|
|
Retrieves the PrefefinedType attribute of an element.
|
|
|
|
|
|
|
|
|
|
:param element: The IFC Element entity
|
|
|
|
|
:return: The predefined type of the element
|
|
|
|
|
|
|
|
|
|
Example::
|
|
|
|
|
element = ifcopenshell.by_type("IfcWall")[0]
|
|
|
|
|
predefined_type = ifcopenshell.util.element.get_predefined_type(element)
|
|
|
|
|
"""
|
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):
|
2022-08-15 19:40:20 +01:00
|
|
|
"""
|
|
|
|
|
Retrieves the Element Type entity related to an element entity.
|
2022-08-19 18:43:39 +10:00
|
|
|
|
2022-08-15 19:40:20 +01:00
|
|
|
:param element: The IFC Element entity
|
|
|
|
|
:return: The Element Type entity defining the element
|
|
|
|
|
|
|
|
|
|
Example::
|
|
|
|
|
element = ifcopenshell.by_type("IfcWall")[0]
|
|
|
|
|
element_type = ifcopenshell.util.element.get_type(element)
|
|
|
|
|
"""
|
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):
|
|
|
|
|
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):
|
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
|
|
|
|
|
|
|
|
|
2022-03-22 12:50:10 +11:00
|
|
|
def get_elements_by_material(ifc_file, material):
|
2022-08-15 19:40:20 +01:00
|
|
|
"""
|
|
|
|
|
Retrieves the elements related to a material.
|
2022-08-19 18:43:39 +10:00
|
|
|
|
2022-08-15 19:40:20 +01:00
|
|
|
:param ifc_file: The IFC file
|
|
|
|
|
:param material: The IFC Material entity
|
|
|
|
|
:return: The elements related to the material
|
|
|
|
|
|
|
|
|
|
Example::
|
|
|
|
|
material = file.by_type("IfcMaterial")[0]
|
|
|
|
|
elements = ifcopenshell.util.element.get_elements_by_material(file, material)
|
|
|
|
|
"""
|
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):
|
2022-08-15 19:40:20 +01:00
|
|
|
"""
|
|
|
|
|
Retrieves the elements related to a style.
|
2022-08-19 18:43:39 +10:00
|
|
|
|
2022-08-15 19:40:20 +01:00
|
|
|
:param ifc_file: The IFC file
|
|
|
|
|
:param style: The IFC Style entity
|
|
|
|
|
:return: The elements related to the style
|
|
|
|
|
|
|
|
|
|
Example::
|
|
|
|
|
style = file.by_type("IfcSurfaceStyle")[0]
|
|
|
|
|
elements = ifcopenshell.util.element.get_elements_by_style(file, style)
|
|
|
|
|
|
|
|
|
|
"""
|
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):
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
2021-11-16 18:07:27 +11:00
|
|
|
def get_layers(ifc_file, element):
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
2021-10-06 12:52:01 +11:00
|
|
|
def get_container(element, should_get_direct=False):
|
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
|
|
|
|
|
:return: The direct or indirect container of the element or None.
|
2022-08-15 19:40:20 +01:00
|
|
|
|
|
|
|
|
Example::
|
|
|
|
|
|
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:
|
|
|
|
|
return element.ContainedInStructure[0].RelatingStructure
|
|
|
|
|
else:
|
|
|
|
|
aggregate = get_aggregate(element)
|
|
|
|
|
if aggregate:
|
|
|
|
|
return get_container(aggregate, should_get_direct)
|
|
|
|
|
if hasattr(element, "ContainedInStructure") and element.ContainedInStructure:
|
|
|
|
|
return element.ContainedInStructure[0].RelatingStructure
|
2021-01-22 15:52:48 +11:00
|
|
|
|
|
|
|
|
|
2022-08-19 18:43:39 +10:00
|
|
|
def get_referenced_structures(element):
|
|
|
|
|
"""
|
|
|
|
|
Retreives a list of referenced structural elements
|
|
|
|
|
|
|
|
|
|
:param element: The IFC element
|
|
|
|
|
:type element: ifcopenshell.entity_instance.entity_instance
|
|
|
|
|
|
|
|
|
|
Example::
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
2021-09-17 15:40:27 +10:00
|
|
|
def get_decomposition(element):
|
2022-08-15 19:40:20 +01:00
|
|
|
"""
|
|
|
|
|
Retrieves the decomposition 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 decomposition of the element
|
|
|
|
|
|
|
|
|
|
Example::
|
|
|
|
|
|
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)
|
|
|
|
|
return results
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
Example::
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
Example::
|
|
|
|
|
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):
|
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()
|
|
|
|
|
|
|
|
|
|
|
2021-10-21 16:28:55 +11:00
|
|
|
def remove_deep2(ifc_file, element, also_consider=[], do_not_delete=[]):
|
2022-08-30 20:08:03 +10:00
|
|
|
"""
|
|
|
|
|
Recursively purges a subgraph safely, starting at an element
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
: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:])
|
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):
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def copy_deep(ifc_file, element):
|
|
|
|
|
new = ifc_file.create_entity(element.is_a())
|
|
|
|
|
for i, attribute in enumerate(element):
|
|
|
|
|
if attribute is None:
|
|
|
|
|
continue
|
|
|
|
|
if isinstance(attribute, ifcopenshell.entity_instance):
|
|
|
|
|
attribute = copy_deep(ifc_file, attribute)
|
|
|
|
|
elif isinstance(attribute, tuple) and attribute and isinstance(attribute[0], ifcopenshell.entity_instance):
|
2021-05-14 15:12:25 +10:00
|
|
|
attribute = list(attribute)
|
2021-05-13 15:11:18 +10:00
|
|
|
for j, item in enumerate(attribute):
|
|
|
|
|
attribute[j] = copy_deep(ifc_file, item)
|
|
|
|
|
new[i] = attribute
|
|
|
|
|
return new
|