From c05abc4d5b38a9f1743b5683d102085600950732 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 31 May 2024 16:58:10 +0500 Subject: [PATCH] ifc4x3 support for adding IfcQuantityCount #4776 In IFC4X3 IfcQuantityCount is now more strict and requires only interger values. Error for a reference: TypeError: attribute 'CountValue' for entity 'IFC4X3.IfcQuantityCount' is expecting value of type 'INT', got 'float'. --- src/ifc5d/ifc5d/csv2ifc.py | 2 +- .../api/cost/add_cost_item_quantity.py | 6 ++- .../api/resource/add_resource_quantity.py | 5 +- .../api/cost/test_add_cost_item_quantity.py | 49 +++++++++++++++++++ .../resource/test_add_resource_quantity.py | 47 ++++++++++++++++++ 5 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 src/ifcopenshell-python/test/api/cost/test_add_cost_item_quantity.py create mode 100644 src/ifcopenshell-python/test/api/resource/test_add_resource_quantity.py diff --git a/src/ifc5d/ifc5d/csv2ifc.py b/src/ifc5d/ifc5d/csv2ifc.py index 218a257d48..139de17310 100644 --- a/src/ifc5d/ifc5d/csv2ifc.py +++ b/src/ifc5d/ifc5d/csv2ifc.py @@ -185,7 +185,7 @@ class Csv2Ifc: "cost.add_cost_item_quantity", self.file, cost_item=cost_item["ifc"], ifc_class=quantity_class ) # 3 IfcPhysicalSimpleQuantity Value - quantity[3] = cost_item["Quantity"] + quantity[3] = int(cost_item["Quantity"]) if quantity_class == "IfcQuantityCount" else cost_item["Quantity"] if prop_name: quantity.Name = prop_name diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_item_quantity.py b/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_item_quantity.py index 55484741bd..59a3f1f8a2 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_item_quantity.py +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_item_quantity.py @@ -79,14 +79,16 @@ def add_cost_item_quantity( settings = {"cost_item": cost_item, "ifc_class": ifc_class} quantity = file.create_entity(settings["ifc_class"], Name="Unnamed") - quantity[3] = 0.0 + # 3 IfcPhysicalSimpleQuantity Value # This is a bold assumption # https://forums.buildingsmart.org/t/how-does-a-cost-item-know-that-it-is-counting-a-controlled-product/3564 - if settings["ifc_class"] == "IfcQuantityCount" and settings["cost_item"].Controls: + if settings["ifc_class"] == "IfcQuantityCount": count = 0 for rel in settings["cost_item"].Controls: count += len(rel.RelatedObjects) quantity[3] = count + else: + quantity[3] = 0.0 quantities = list(settings["cost_item"].CostQuantities or []) quantities.append(quantity) settings["cost_item"].CostQuantities = quantities diff --git a/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_quantity.py b/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_quantity.py index 5bba6b3ae1..8d3d6de9e9 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_quantity.py +++ b/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_quantity.py @@ -68,7 +68,10 @@ def add_resource_quantity( quantity = file.create_entity(settings["ifc_class"], Name="Unnamed") # 3 IfcPhysicalSimpleQuantity Value - quantity[3] = 0.0 + if settings["ifc_class"] == "IfcQuantityCount": + quantity[3] = 0 + else: + quantity[3] = 0.0 old_quantity = settings["resource"].BaseQuantity settings["resource"].BaseQuantity = quantity if old_quantity: diff --git a/src/ifcopenshell-python/test/api/cost/test_add_cost_item_quantity.py b/src/ifcopenshell-python/test/api/cost/test_add_cost_item_quantity.py new file mode 100644 index 0000000000..49660dc0a9 --- /dev/null +++ b/src/ifcopenshell-python/test/api/cost/test_add_cost_item_quantity.py @@ -0,0 +1,49 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2024 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 TestAddCostItemQuantity(test.bootstrap.IFC4): + def test_run(self): + schema = ifcopenshell.schema_by_name(self.file.schema) + quantity_types = [t.name() for t in schema.declaration_by_name("IfcPhysicalSimpleQuantity").subtypes()] + schedule = ifcopenshell.api.run("cost.add_cost_schedule", self.file) + item = ifcopenshell.api.run("cost.add_cost_item", self.file, cost_schedule=schedule) + wall = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + ifcopenshell.api.run("control.assign_control", self.file, relating_control=item, related_object=wall) + + quantities = [] + for quantity_type in quantity_types: + quantity = ifcopenshell.api.run( + "cost.add_cost_item_quantity", self.file, cost_item=item, ifc_class=quantity_type + ) + assert quantity.is_a(quantity_type) + assert quantity.Name == "Unnamed" + if quantity_type == "IfcQuantityCount": + assert quantity[3] == 1 + else: + assert quantity[3] == 0.0 + quantities.append(quantity) + assert item.CostQuantities == tuple(quantities) + + +# CostQuantities was added to IfcCostItem in IFC4. +class TestAddCostItemQuantityIFC4X3(test.bootstrap.IFC4X3, TestAddCostItemQuantity): + pass diff --git a/src/ifcopenshell-python/test/api/resource/test_add_resource_quantity.py b/src/ifcopenshell-python/test/api/resource/test_add_resource_quantity.py new file mode 100644 index 0000000000..bde9451d11 --- /dev/null +++ b/src/ifcopenshell-python/test/api/resource/test_add_resource_quantity.py @@ -0,0 +1,47 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2024 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 TestAddResourceQuantity(test.bootstrap.IFC4): + def test_run(self): + schema = ifcopenshell.schema_by_name(self.file.schema) + quantity_types = [t.name() for t in schema.declaration_by_name("IfcPhysicalSimpleQuantity").subtypes()] + self.file.create_entity("IfcProject") # add_resource + resource = ifcopenshell.api.run("resource.add_resource", self.file, ifc_class="IfcCrewResource") + + for quantity_type in quantity_types: + quantity = ifcopenshell.api.run( + "resource.add_resource_quantity", self.file, resource=resource, ifc_class=quantity_type + ) + assert quantity.is_a(quantity_type) + assert quantity.Name == "Unnamed" + assert quantity[3] == 0.0 + # previous quantity is reassigned and removed + assert resource.BaseQuantity == quantity + assert len(self.file.by_type("IfcPhysicalSimpleQuantity")) == 1 + + +class TestAddResourceQuantityIFC2X3(test.bootstrap.IFC2X3, TestAddResourceQuantity): + pass + + +class TestAddResourceQuantityIFC4X3(test.bootstrap.IFC4X3, TestAddResourceQuantity): + pass