diff --git a/src/ifcopenshell-python/ifcopenshell/api/pset/edit_qto.py b/src/ifcopenshell-python/ifcopenshell/api/pset/edit_qto.py
index 6bb23a74e6..1abb97f1fa 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/pset/edit_qto.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/pset/edit_qto.py
@@ -17,6 +17,7 @@
# along with IfcOpenShell. If not, see .
import ifcopenshell
+import ifcopenshell.util.pset
class Usecase:
@@ -54,6 +55,7 @@ class Usecase:
if value is None:
self.file.remove(prop)
elif prop.is_a("IfcPhysicalSimpleQuantity"):
+ value = value.wrappedValue if isinstance(value, ifcopenshell.entity_instance) else value
prop[3] = float(value)
del self.settings["properties"][name]
@@ -62,7 +64,8 @@ class Usecase:
for name, value in self.settings["properties"].items():
if value is None:
continue
- property_type = self.get_canonical_property_type(name)
+ property_type = self.get_canonical_property_type(name, value)
+ value = value.wrappedValue if isinstance(value, ifcopenshell.entity_instance) else value
properties.append(
self.file.create_entity(
"IfcQuantity{}".format(property_type),
@@ -76,13 +79,20 @@ class Usecase:
props.extend(new_properties)
self.settings["qto"].Quantities = props
- def get_canonical_property_type(self, name):
- if not self.qto_template:
- return "Length"
- for prop_template in self.qto_template.HasPropertyTemplates:
- if prop_template.Name != name:
- continue
- return prop_template.TemplateType[2:].lower().capitalize()
+ def get_canonical_property_type(self, name, value):
+ if isinstance(value, ifcopenshell.entity_instance):
+ result = value.is_a().replace("Ifc", "").replace("Measure", "")
+ # Sigh, IFC inconsistencies
+ if result == "Numeric":
+ result = "Number"
+ elif result == "Mass":
+ result = "Weight"
+ return result
+ if self.qto_template:
+ for prop_template in self.qto_template.HasPropertyTemplates:
+ if prop_template.Name != name:
+ continue
+ return prop_template.TemplateType[2:].lower().capitalize()
return "Length"
def get_primary_measure_type(self, name, previous_value=None):
diff --git a/src/ifcopenshell-python/test/api/pset/test_edit_qto.py b/src/ifcopenshell-python/test/api/pset/test_edit_qto.py
new file mode 100644
index 0000000000..1800a85662
--- /dev/null
+++ b/src/ifcopenshell-python/test/api/pset/test_edit_qto.py
@@ -0,0 +1,158 @@
+# IfcOpenShell - IFC toolkit and geometry engine
+# Copyright (C) 2022 Dion Moult
+#
+# 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 .
+
+import test.bootstrap
+import ifcopenshell.api
+
+
+class TestEditQto(test.bootstrap.IFC4):
+ def test_editing_a_blank_buildingsmart_templated_qto(self):
+ element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
+ qto = ifcopenshell.api.run("pset.add_qto", self.file, product=element, name="Qto_WallBaseQuantities")
+ ifcopenshell.api.run(
+ "pset.edit_qto",
+ self.file,
+ qto=qto,
+ properties={"Length": 1, "NetSideArea": 2, "NetVolume": 3},
+ )
+ pset = element.IsDefinedBy[0].RelatingPropertyDefinition
+
+ assert pset.Quantities[0].Name == "Length"
+ assert pset.Quantities[0].LengthValue == 1
+
+ assert pset.Quantities[1].Name == "NetSideArea"
+ assert pset.Quantities[1].AreaValue == 2
+
+ assert pset.Quantities[2].Name == "NetVolume"
+ assert pset.Quantities[2].VolumeValue == 3
+
+ def test_editing_an_existing_buildingsmart_templated_pset(self):
+ element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
+ qto = ifcopenshell.api.run("pset.add_qto", self.file, product=element, name="Qto_WallBaseQuantities")
+ ifcopenshell.api.run(
+ "pset.edit_qto",
+ self.file,
+ qto=qto,
+ properties={"Length": 1, "NetSideArea": 2, "NetVolume": 3},
+ )
+ ifcopenshell.api.run("pset.edit_qto", self.file, qto=qto, properties={"Length": 42, "NetSideArea": None})
+ qto = element.IsDefinedBy[0].RelatingPropertyDefinition
+
+ assert qto.Quantities[0].Name == "Length"
+ assert qto.Quantities[0].LengthValue == 42
+
+ assert qto.Quantities[1].Name == "NetVolume"
+ assert qto.Quantities[1].VolumeValue == 3
+
+ assert len(qto.Quantities) == 2
+
+ def test_not_adding_a_property_if_it_is_none(self):
+ element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
+ qto = ifcopenshell.api.run("pset.add_qto", self.file, product=element, name="Qto_WallBaseQuantities")
+ ifcopenshell.api.run("pset.edit_qto", self.file, qto=qto, properties={"Length": None})
+ qto = element.IsDefinedBy[0].RelatingPropertyDefinition
+ assert len(qto.Quantities) == 0
+
+ def test_editing_a_qto_name(self):
+ element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
+ qto = ifcopenshell.api.run("pset.add_qto", self.file, product=element, name="foo")
+ ifcopenshell.api.run("pset.edit_qto", self.file, qto=qto, name="bar")
+ qto = element.IsDefinedBy[0].RelatingPropertyDefinition
+ assert qto.Name == "bar"
+
+ def test_adding_quantities_without_a_template_with_autodetected_and_manual_data_types(self):
+ element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
+ qto = ifcopenshell.api.run("pset.add_qto", self.file, product=element, name="Foo_Bar")
+ ifcopenshell.api.run(
+ "pset.edit_qto",
+ self.file,
+ qto=qto,
+ properties={
+ "MyArea": self.file.createIfcAreaMeasure(1),
+ "MyCount": self.file.createIfcCountMeasure(2),
+ "MyLength": self.file.createIfcLengthMeasure(3),
+ "MyTime": self.file.createIfcTimeMeasure(5),
+ "MyVolume": self.file.createIfcVolumeMeasure(6),
+ "MyWeight": self.file.createIfcMassMeasure(7),
+ },
+ )
+ qto = element.IsDefinedBy[0].RelatingPropertyDefinition
+
+ assert qto.Quantities[0].Name == "MyArea"
+ assert qto.Quantities[0].AreaValue == 1
+
+ assert qto.Quantities[1].Name == "MyCount"
+ assert qto.Quantities[1].CountValue == 2
+
+ assert qto.Quantities[2].Name == "MyLength"
+ assert qto.Quantities[2].LengthValue == 3
+
+ assert qto.Quantities[3].Name == "MyTime"
+ assert qto.Quantities[3].TimeValue == 5
+
+ assert qto.Quantities[4].Name == "MyVolume"
+ assert qto.Quantities[4].VolumeValue == 6
+
+ assert qto.Quantities[5].Name == "MyWeight"
+ assert qto.Quantities[5].WeightValue == 7
+
+ def test_editing_quantities_with_autodetected_existing_types(self):
+ element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
+ qto = ifcopenshell.api.run("pset.add_qto", self.file, product=element, name="Foo_Bar")
+ ifcopenshell.api.run(
+ "pset.edit_qto",
+ self.file,
+ qto=qto,
+ properties={
+ "MyLength": self.file.createIfcLengthMeasure(12),
+ },
+ )
+ ifcopenshell.api.run(
+ "pset.edit_qto",
+ self.file,
+ qto=qto,
+ properties={
+ "MyLength": 34,
+ },
+ )
+ qto = element.IsDefinedBy[0].RelatingPropertyDefinition
+ assert qto.Quantities[0].Name == "MyLength"
+ assert qto.Quantities[0].LengthValue == 34
+
+ def test_editing_properties_with_an_explicit_type(self):
+ element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
+ qto = ifcopenshell.api.run("pset.add_qto", self.file, product=element, name="Foo_Bar")
+ ifcopenshell.api.run(
+ "pset.edit_qto",
+ self.file,
+ qto=qto,
+ properties={
+ "MyLength": self.file.createIfcLengthMeasure(12),
+ },
+ )
+ ifcopenshell.api.run(
+ "pset.edit_qto",
+ self.file,
+ qto=qto,
+ properties={
+ "MyLength": self.file.createIfcAreaMeasure(34),
+ },
+ )
+ qto = element.IsDefinedBy[0].RelatingPropertyDefinition
+ assert qto.Quantities[0].Name == "MyLength"
+ assert qto.Quantities[0].LengthValue == 34