Files
IfcOpenShell/src/ifcpatch/ifcpatch/recipes/ConvertPropertiesToQuantities.py
T

110 lines
4.9 KiB
Python

# IfcPatch - IFC patching utiliy
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcPatch.
#
# IfcPatch 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.
#
# IfcPatch 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 IfcPatch. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.util.pset
import ifcopenshell.util.element
class Patcher:
def __init__(self, src, file, logger, property_name=None, quantity_name=None):
"""Converts a property to a standardised quantity
IFC can store arbitrary key value metadata associated with a elements
known as properties and quantities. The difference between the two is
that quantities specifically measure physical dimensions, and can be
used parametrically. A collection of standardised quantities have also
been published by buildingSMART. Using standardised quantities can help
automate BIM workflows, instead of arbitrary properties.
Sometimes, proprietary BIM software incorrectly stores quantities and
properties. This patch lets you convert these incorrect properties to
standardised quantities instead.
The existing property will not be removed.
:param property_name: The name of the property to convert into a
quantity. The name of the property set is not considered.
:type property_name: str
:param quantity_name: The name of the quantity that this property should
be stored in. This should be a standard name that is one of the
quantity names of a buildingSMART quantity template. For example, it
may be "NetSideArea" for walls, which exists in
Qto_WallBaseQuantities. The quantity set name will be based on the
standard buildingSMART quantity template.
:type quantity_name: str
Example:
.. code:: python
# Any property named "Area" is converted to a quantity named
# "NetSideArea", if that standardised quantity exists.
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "ConvertPropertiesToQuantities", "arguments": ["Area", "NetSideArea"]})
"""
self.src = src
self.file = file
self.logger = logger
self.source_property_name = property_name
self.destination_quantity_name = quantity_name
def patch(self):
self.qto_template_cache = {}
self.psetqto = ifcopenshell.util.pset.get_template("IFC4")
for product in self.file.by_type("IfcTypeProduct"):
self.process_product(product, product.HasPropertySets or [])
for product in self.file.by_type("IfcProduct"):
self.process_product(
product,
[r.RelatingPropertyDefinition for r in product.IsDefinedBy if r.is_a("IfcRelDefinesByProperties")],
)
def process_product(self, product, definitions):
value = None
has_quantity = False
qtos = {}
for definition in definitions or []:
if definition.is_a("IfcPropertySet"):
for prop in definition.HasProperties:
if prop.is_a("IfcPropertySingleValue") and prop.Name == self.source_property_name:
value = prop.NominalValue.wrappedValue if prop.NominalValue else None
elif definition.is_a("IfcElementQuantity"):
qtos[definition.Name] = definition
for quantity in definition.Quantities:
if quantity.is_a("IfcPhysicalSimpleQuantity") and quantity.Name == self.destination_quantity_name:
has_quantity = True
if value and not has_quantity:
qto_name = self.get_qto_name(product.is_a())
qto = qtos.get(qto_name, ifcopenshell.api.run("pset.add_qto", self.file, product=product, Name=qto_name))
ifcopenshell.api.run(
"pset.edit_qto", self.file, qto=qto, Properties={self.destination_quantity_name: value}
)
def get_qto_name(self, ifc_class):
for template in self.get_qto_templates(ifc_class):
if self.destination_quantity_name in [t.Name for t in template.HasPropertyTemplates]:
return template.Name
def get_qto_templates(self, ifc_class):
if ifc_class not in self.qto_template_cache:
self.qto_template_cache[ifc_class] = self.psetqto.get_applicable(ifc_class, qto_only=True)
return self.qto_template_cache[ifc_class]