Add documentation for pset API module

This commit is contained in:
Dion Moult
2022-12-15 16:02:27 +11:00
parent 241406f676
commit 0ccea3cef8
6 changed files with 392 additions and 40 deletions
@@ -68,9 +68,7 @@ class Usecase:
if not application:
return
if not self.settings["element"].OwnerHistory:
self.settings["element"].OwnerHistory = ifcopenshell.api.run(
"owner.create_owner_history", self.file, **self.settings
)
self.settings["element"].OwnerHistory = ifcopenshell.api.run("owner.create_owner_history", self.file)
return self.settings["element"].OwnerHistory
if self.file.get_total_inverses(self.settings["element"].OwnerHistory) > 1:
new = ifcopenshell.util.element.copy(self.file, self.settings["element"].OwnerHistory)
@@ -20,11 +20,64 @@ import ifcopenshell
class Usecase:
def __init__(self, file, **settings):
def __init__(self, file, product=None, name=None):
"""Adds a new property set to a product
Products, such as physical objects or types in IFC may have properties
associated with them. These properties are typically simple key value
metadata with data types. For example, a wall type may have a property
called FireRating with a text value of "2HR". Properties are grouped
into property sets, so that related properties are grouped together.
If a property is assigned to a type, the property is inherited by all
occurrences of that type. For example, a wall type with a FireRating
property of "2HR" automatically implies that all walls of that wall type
also have a FireRating of "2HR". It is not necessary to explictly define
the property again for each occurrence. This also means that properties
are typically defined on types. If the same property is defined at an
occurrence, this overrides the property defined on the type.
buildingSMART has come up with a long list of standardised properties
for the most common properties required internationally. This solves the
age-old question of "where do I store my FireRating data for walls"? The
answer, in this case, is in the "FireRating" property with an "IfcLabel"
data type grouped in the "Pset_WallCommon" property set. It is
recommended to view the list of standardised buildingSMART properties
and see if any suit your needs first. If none are appropriate, then you
are free to create your own custom properties.
This function adds a blank named property set. One you have a property
set you may add properties using ifcopenshell.api.pset.edit_pset.
See also ifcopenshell.api.pset.add_qto if you want to add quantification
data, rather than arbitrary metadata.
:param product: The IfcObject that you want to assign a property set to.
:type product: ifcopenshell.entity_instance.entity_instance
:param name: The name of the property set. Property sets that are
standardised by buildingSMART typically have a prefix of "Pset_",
like "Pset_WallCommon". If you create your own, you must not use
that prefix. It is recommended to use your own prefix tailored to
your project, company, or local government requirement.
:type name: str
:return: The newly created IfcPropertySet
:rtype: ifcopenshell.entity_instance.entity_instance
Example::
# Let's imagine we have a new wall type.
wall_type = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWallType")
# Note that this only creates and assigns an empty property set. We
# still need to add properties into the property set. Having blank
# property sets are invalid.
pset = ifcopenshell.api.run("pset.add_pset", model, product=wall_type, name="Pset_WallCommon")
# Add a fire rating property standardised by buildingSMART.
ifcopenshell.api.run("pset.edit_pset", model, pset=pset, properties={"FireRating": "2HR"})
"""
self.file = file
self.settings = {"product": None, "name": None}
for key, value in settings.items():
self.settings[key] = value
self.settings = {"product": product, "name": name}
def execute(self):
if self.settings["product"].is_a("IfcObject") or self.settings["product"].is_a("IfcContext"):
@@ -21,11 +21,63 @@ import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
def __init__(self, file, product=None, name=None):
"""Adds a new quantity set to a product
Products, such as physical objects or types in IFC may have quantities
associated with them. These quantities are typically simple key value
metadata with data types. For example, a wall type may have a quantity
called NetSideArea with a area value of "4.2". Quantities are grouped
into quantity sets, so that related quantities are grouped together.
Quantities can only be assigned to occurrences, not types.
Quantities are similar to, but different from properties in that they
may store a method of measurement or formula. Quantities may also have
parametric relationships to other calculated values, such as cost
schedules, resource utilisation, or construction task durations.
buildingSMART has come up with a long list of standardised quantities
for the most common quantities required internationally. This solves the
age-old question of "what's the standard way of storing quantity
take-off data"? It is recommended to view the list of standardised
buildingSMART quantities and see if any suit your needs first. If none
are appropriate, then you are free to create your own custom quantities.
This function adds a blank named quantity set. One you have a quantity
set you may add quantities using ifcopenshell.api.pset.edit_qto.
See also ifcopenshell.api.pset.add_qto if you want to arbitrary
metadata, rather than quantification data.
:param product: The IfcObject that you want to assign a quantity set to.
:type product: ifcopenshell.entity_instance.entity_instance
:param name: The name of the quantity set. Quantity sets that are
standardised by buildingSMART typically have a prefix of "Qto_",
like "Qto_WallBaseQuantities". If you create your own, you must not
use that prefix. It is recommended to use your own prefix tailored
to your project, company, or local government requirement.
:type name: str
:return: The newly created IfcElementQuantity
:rtype: ifcopenshell.entity_instance.entity_instance
Example::
# Let's imagine we have a new wall.
wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
# Note that this only creates and assigns an empty quantity set. We
# still need to add quantities into the property set. Having blank
# quantity sets are invalid.
qto = ifcopenshell.api.run("pset.add_qto", model, product=wall_type, name="Qto_WallBaseQuantities")
# Add a side area property standardised by buildingSMART. This
# allows quantity take-off to occur, even though no geometry has
# even been modelled!
ifcopenshell.api.run("pset.edit_qto", model, qto=qto, properties={"NetSideArea": 4.2})
"""
self.file = file
self.settings = {"product": None, "name": None}
for key, value in settings.items():
self.settings[key] = value
self.settings = {"product": product, "name": name}
def execute(self):
if self.settings["product"].is_a("IfcObject") or self.settings["product"].is_a("IfcContext"):
@@ -21,11 +21,140 @@ import ifcopenshell.util.pset
class Usecase:
def __init__(self, file, **settings):
def __init__(self, file, pset=None, name=None, properties=None, pset_template=None, should_purge=False):
"""Edits a property set and its properties
At its simplest usage, this may be used to edit the name of a property
set. It may also be used to add, edit, or remove properties, either
arbitrarily or using a property set template.
A list of properties are provided as a dictionary, where the keys are
property names, and values are property values. Keys that don't already
exist are interpreted as properties to be added. Keys that already exist
are interpreted as properties to be edited. A "None" value may specify a
property to be deleted.
Properties must have a data type. There are lots of data types in IFCs,
not just simple unitless data types like integers, booleans, text, but
also distinguishing between types of text, like labels versus
descriptive text. There are also lots of unit-based data types like
areas, volumes, lengths, power, density, flow rates, pressure, etc.
To ensure the appropriate data type is used for properties, a property
set template may be used. These can be seen as "property
specifications". A default selection is provided by buildingSMART, so
that all buildingSMART defined standard properties have exactly the same
data types and exactly the right property names without fear of invalid
data or typos. The built-in buildingSMART templates are always loaded.
However, you may also specify your own templates. If you try to add a
non-standard property that does not exist in either your own template or
in the built-in buildingSMART template, then you have the responsibility
to ensure that data types are always consistent and correct.
:param pset: The IfcPropertySet to edit.
:type pset: ifcopenshell.entity_instance.entity_instance
:param name: A new name for the property set. If no name is specified,
the property set name is not changed.
:type name: str, optional
:param properties: A dictionary of properties. The keys must be a string
of the name of the property. The data type of the value will be
determined by the property set template. If no property set
template is found, the data types of the Python values will
influence the IFC data type of the property. String values will
become IfcLabel, float values will become IfcReal, booleans will
become IfcBoolean, and integers will become IfcInteger. If more
control is desired, you may explicitly specify IFC data objects
directly.
:type properties: dict
:param pset_template: If a property set template is provided, this will
be used to determine data types. If no user-defined template is
provided, the built-in buildingSMART templates will be loaded.
:type pset_template: ifcopenshell.entity_instance.entity_instance
:param should_purge: If left as False, properties set to None will be
left as None but not removed. If set to true, properties set to None
will actually be removed.
:type should_purge: bool, optional
:return: None
:rtype: None
Example::
# Let's imagine we have a new wall type.
wall_type = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWallType")
# This is a standard buildingSMART property set.
pset = ifcopenshell.api.run("pset.add_pset", model, product=wall_type, name="Pset_WallCommon")
# In this scenario, we don't specify any pset_template because it is
# part of the built-in buildingSMART templates, and so the
# FireRating will automatically be an IfcLabel, and the thermal
# transmittance value will automatically be an
# IfcThermalTransmittanceMeasure. Neither of these properties exist
# yet, so they will be created.
ifcopenshell.api.run("pset.edit_pset", model,
pset=pset, properties={"FireRating": "2HR", "ThermalTransmittance": 42.3})
# We can edit existing properties. In this case, "FireRating" is
# edited from "2HR" to "1HR". Combustible is new, and will be added.
# The existing "ThermalTransmittance" property will be left
# unchanged.
ifcopenshell.api.run("pset.edit_pset", model,
pset=pset, properties={"FireRating": "1HR", "Combustible": False})
# Setting to None will change the value but not delete the property.
ifcopenshell.api.run("pset.edit_pset", model, pset=pset, properties={"Combustible": None})
# If you actually want to delete the property, enable purging.
ifcopenshell.api.run("pset.edit_pset", model, pset=pset,
properties={"Combustible": None}, should_purge=True)
# What if we wanted to manage our own properties? Let's create our
# own "Company Standard" property set templates. Notice how we
# prefix our property set with "Foo_", if our company name was "Foo"
# this would make sense.
pset_template = ifcopenshell.api.run("pset_template.add_pset_template", model)
ifcopenshell.api.run("pset_template.edit_pset_template", model, attributes={"Name": "Foo_Bar"})
# Let's imagine we want all model authors to specify two properties,
# one being a length measurement and another being a boolean.
prop1 = ifcopenshell.api.run("pset_template.add_prop_template", model, pset_template=pset_template)
ifcopenshell.api.run("pset_template.edit_prop_template", model,
prop_template=prop1, attributes={"Name": "DemoA", "PrimaryMeasureType": "IfcLengthMeasure"})
prop2 = ifcopenshell.api.run("pset_template.add_prop_template", model, pset_template=pset_template)
ifcopenshell.api.run("pset_template.edit_prop_template", model,
prop_template=prop2, attributes={"Name": "DemoB", "PrimaryMeasureType": "IfcBoolean"})
# Now we can use our property set template to add our properties,
# and the data types will always match our template.
pset = ifcopenshell.api.run("pset.add_pset", model, product=wall_type, name="Foo_Bar")
ifcopenshell.api.run("pset.edit_pset", model,
pset=pset, properties={"DemoA": 42.3, "DemoB": True}, pset_template=pset_template)
# Here's a third scenario where we want to add arbitrary properties
# that are not standardised by anything, not even our own custom
# templates.
pset = ifcopenshell.api.run("pset.add_pset", model, product=wall_type, name="Custom_Pset")
ifcopenshell.api.run("pset.edit_pset", model,
pset=pset, properties={
# Basic Python data types are mapped to a sensible default
"SomeLabel": "Foo",
"SomeNumber": 12.3,
# But we can always specify exactly what we're after too
"ExplicitLength": model.createIfcLengthMeasure(42.3)
})
# Editing existing properties will retain their current data types
# if possible. So this will still be a length measure.
ifcopenshell.api.run("pset.edit_pset", model, pset=pset, properties={"ExplicitLength": 12.3})
"""
self.file = file
self.settings = {"pset": None, "name": None, "properties": {}, "pset_template": None}
for key, value in settings.items():
self.settings[key] = value
self.settings = {
"pset": pset,
"name": name,
"properties": properties or {},
"pset_template": pset_template,
"should_purge": should_purge,
}
def execute(self):
self.update_pset_name()
@@ -46,8 +175,8 @@ class Usecase:
self.psetqto = ifcopenshell.util.pset.get_template("IFC4")
self.pset_template = self.psetqto.get_by_name(self.settings["pset"].Name)
#TODO - Add support for changing property types?
# For example - IfcPropertyEnumeratedValue to
# TODO - Add support for changing property types?
# For example - IfcPropertyEnumeratedValue to
# IfcPropertySingleValue. Or maybe the user should
# just delete the property first? - vulevukusej
def update_existing_properties(self):
@@ -56,7 +185,7 @@ class Usecase:
self.update_existing_enum(prop)
else:
self.update_existing_property(prop)
def update_existing_enum(self, prop):
if prop.Name not in self.settings["properties"]:
return
@@ -64,26 +193,35 @@ class Usecase:
if isinstance(value, list):
sel_vals = []
for val in value:
primary_measure_type = prop.EnumerationReference.EnumerationValues[0].is_a() #Only need the first enum type since all enums are of the same type
primary_measure_type = prop.EnumerationReference.EnumerationValues[
0
].is_a() # Only need the first enum type since all enums are of the same type
ifc_val = self.file.create_entity(primary_measure_type, val)
sel_vals.append(ifc_val)
prop.EnumerationValues = tuple(sel_vals)
else:
if value.EnumerationReference.EnumerationValues == ():
if self.settings["should_purge"]:
del self.settings["properties"][prop.Name]
self.file.remove(prop)
return
prop.EnumerationReference.EnumerationValues = ()
prop.EnumerationValues = ()
elif isinstance(value, ifcopenshell.entity_instance):
prop.EnumerationReference.EnumerationValues = value.EnumerationReference.EnumerationValues
prop.EnumerationValues = value.EnumerationValues
del self.settings["properties"][prop.Name]
def update_existing_property(self, prop):
def update_existing_property(self, prop):
if prop.Name not in self.settings["properties"]:
return
value = self.settings["properties"][prop.Name]
if value is None:
if self.settings["should_purge"]:
del self.settings["properties"][prop.Name]
self.file.remove(prop)
return
prop.NominalValue = None
elif isinstance(value, ifcopenshell.entity_instance):
prop.NominalValue = value
@@ -103,7 +241,7 @@ class Usecase:
if isinstance(value, ifcopenshell.entity_instance):
if value.is_a(True) == "IFC4.IfcPropertyEnumeratedValue":
properties.append(value)
continue
continue
else:
properties.append(
self.file.create_entity(
@@ -111,21 +249,22 @@ class Usecase:
**{"Name": name, "NominalValue": value},
)
)
#TODO-The following "elif" is temporary code, will need to refactor at some point - vulevukusej
# TODO-The following "elif" is temporary code, will need to refactor at some point - vulevukusej
elif isinstance(value, list):
for pset_template in self.settings["pset_template"].HasPropertyTemplates:
if pset_template.Name == name:
prop_enum = self.file.create_entity(
"IFCPROPERTYENUMERATION",
Name=name,
EnumerationValues=pset_template.Enumerators.EnumerationValues
EnumerationValues=pset_template.Enumerators.EnumerationValues,
)
prop_enum_value = self.file.create_entity(
"IFCPROPERTYENUMERATEDVALUE",
Name=name,
EnumerationValues=tuple(self.file.create_entity(
pset_template.PrimaryMeasureType, v) for v in value),
EnumerationReference=prop_enum
EnumerationValues=tuple(
self.file.create_entity(pset_template.PrimaryMeasureType, v) for v in value
),
EnumerationReference=prop_enum,
)
properties.append(prop_enum_value)
continue
@@ -133,7 +272,7 @@ class Usecase:
primary_measure_type = self.get_primary_measure_type(name, new_value=value)
value = self.cast_value_to_primary_measure_type(value, primary_measure_type)
nominal_value = self.file.create_entity(primary_measure_type, value)
properties.append(
self.file.create_entity(
"IfcPropertySingleValue",
@@ -21,11 +21,100 @@ import ifcopenshell.util.pset
class Usecase:
def __init__(self, file, **settings):
def __init__(self, file, qto=None, name=None, properties=None, pset_template=None):
"""Edits a quantity set and its quantities
At its simplest usage, this may be used to edit the name of a quantity
set. It may also be used to add, edit, or remove quantities.
See ifcopenshell.api.pset.edit_pset for documentation on how this is
intended to be used.
One major difference is that quantities set to None are always purged.
It is not allowed to have None quantities in IFC.
:param qto: The IfcElementQuantity to edit.
:type qto: ifcopenshell.entity_instance.entity_instance
:param name: A new name for the quantity set. If no name is specified,
the quantity set name is not changed.
:type name: str, optional
:param properties: A dictionary of properties. The keys must be a string
of the name of the quantity. The data type of the value will be
determined by the quantity set template. If no quantity set
template is found, the data types of the Python values will
influence the IFC data type of the quantity. String values will
become IfcLabel, float values will become IfcReal, booleans will
become IfcBoolean, and integers will become IfcInteger. If more
control is desired, you may explicitly specify IFC data objects
directly.
:type properties: dict
:param pset_template: If a quantity set template is provided, this will
be used to determine data types. If no user-defined template is
provided, the built-in buildingSMART templates will be loaded.
:type pset_template: ifcopenshell.entity_instance.entity_instance
:return: None
:rtype: None
Example::
# Let's imagine we have a new wall type.
wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
# This is a standard buildingSMART property set.
qto = ifcopenshell.api.run("pset.add_qto", model, product=wall, name="Qto_WallBaseQuantities")
# In this scenario, we don't specify any pset_template because it is
# part of the built-in buildingSMART templates, and so the Length
# will automatically be an IfcLengthMeasure, and the NetVolume will
# automatically be an IfcVolumeMeasure. Neither of these properties
# exist yet, so they will be created.
ifcopenshell.api.run("pset.edit_qto", model, qto=qto, properties={"Length": 12, "NetVolume": 7.2})
# Setting to None will delete the quantity.
ifcopenshell.api.run("pset.edit_qto", model, qto=qto, properties={"Length": None})
# What if we wanted to manage our own properties? Let's create our
# own "Company Standard" property set templates. Notice how we
# prefix our property set with "Foo_", if our company name was "Foo"
# this would make sense. In this example, we say that our template
# only applies to walls and is for quantities.
pset_template = ifcopenshell.api.run("pset_template.add_pset_template", model)
ifcopenshell.api.run("pset_template.edit_pset_template", model,
attributes={
"Name": "Foo_Wall", "TemplateType": "QTO_OCCURRENCEDRIVEN", "ApplicableEntity": "IfcWall"
})
# Let's imagine we want all model authors to specify a length
# measurement for the portion of a wall that is overhanging.
prop = ifcopenshell.api.run("pset_template.add_prop_template", model, pset_template=pset_template)
ifcopenshell.api.run("pset_template.edit_prop_template", model,
prop_template=prop, attributes={
"Name": "OverhangLength", "TemplateType": "Q_LENGTH", "PrimaryMeasureType": "IfcLengthMeasure"
})
# Now we can use our property set template to add our properties,
# and the data types will always match our template.
qto = ifcopenshell.api.run("pset.add_qto", model, product=wall, name="Foo_Wall")
ifcopenshell.api.run("pset.edit_qto", model,
qto=qto, properties={"OverhangLength": 42.3}, pset_template=pset_template)
# Here's a third scenario where we want to add arbitrary quantities
# that are not standardised by anything, not even our own custom
# templates.
qto = ifcopenshell.api.run("pset.add_qto", model, product=wall, name="Custom_Qto")
ifcopenshell.api.run("pset.edit_qto", model,
qto=qto, properties={
"SomeLength": model.createIfcLengthMeasure(42.3),
"SomeArea": model.createIfcAreaMeasure(21.0)
})
# Editing existing quantities will retain their current data types
# if possible. So this will still be a length measure.
ifcopenshell.api.run("pset.edit_qto", model, qto=qto, properties={"SomeLength": 12.3})
"""
self.file = file
self.settings = {"qto": None, "name": None, "properties": {}}
for key, value in settings.items():
self.settings[key] = value
self.settings = {"qto": qto, "name": name, "properties": properties or {}, "pset_template": pset_template}
def execute(self):
self.qto_idx = 5
@@ -43,9 +132,12 @@ class Usecase:
self.settings["qto"].Name = self.settings["name"]
def load_qto_template(self):
# TODO: add IFC2X3 PsetQto template support
self.psetqto = ifcopenshell.util.pset.get_template("IFC4")
self.qto_template = self.psetqto.get_by_name(self.settings["qto"].Name)
if self.settings["pset_template"]:
self.pset_template = self.settings["pset_template"]
else:
# TODO: add IFC2X3 PsetQto template support
self.psetqto = ifcopenshell.util.pset.get_template("IFC4")
self.qto_template = self.psetqto.get_by_name(self.settings["qto"].Name)
def update_existing_properties(self):
for prop in self.settings["qto"][self.qto_idx] or []:
@@ -18,11 +18,29 @@
class Usecase:
def __init__(self, file, **settings):
def __init__(self, file, product=None, pset=None):
"""Removes a property set from a product
All properties that are part of this property set are also removed.
:param product: The IfcObject to remove the property set from.
:type product: ifcopenshell.entity_instance.entity_instance
:param pset: The IfcPropertySet or IfcElementQuantity to remove.
:type pset: ifcopenshell.entity_instance.entity_instance
:return: None
:rtype: None
Example::
# Let's imagine we have a new wall type with a property set.
wall_type = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWallType")
pset = ifcopenshell.api.run("pset.add_pset", model, product=wall_type, name="Pset_WallCommon")
# Remove it!
ifcopenshell.api.run("pset.remove_pset", model, product=wall_type, pset=pset)
"""
self.file = file
self.settings = {"product": None, "pset": None}
for key, value in settings.items():
self.settings[key] = value
self.settings = {"product": product, "pset": pset}
def execute(self):
to_purge = []