First Commit

This commit is contained in:
2026-05-31 10:17:09 +07:00
commit 17a9c69379
4547 changed files with 1170384 additions and 0 deletions
@@ -0,0 +1,72 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2022 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/>.
"""Manage cost schedules, cost items, cost estimation and parametric quantity
take-off
IFC supports storing cost schedules and detailed cost breakdown structures,
including formulas, subtotals, and parametric links to model element
quantities.
"""
from .. import wrap_usecases
from .add_cost_item import add_cost_item
from .add_cost_item_quantity import add_cost_item_quantity
from .add_cost_schedule import add_cost_schedule
from .add_cost_value import add_cost_value
from .assign_cost_item_quantity import assign_cost_item_quantity
from .assign_cost_value import assign_cost_value
from .calculate_cost_item_resource_value import calculate_cost_item_resource_value
from .copy_cost_item import copy_cost_item
from .copy_cost_item_values import copy_cost_item_values
from .copy_cost_schedule import copy_cost_schedule
from .edit_cost_item import edit_cost_item
from .edit_cost_item_quantity import edit_cost_item_quantity
from .edit_cost_schedule import edit_cost_schedule
from .edit_cost_value import edit_cost_value
from .edit_cost_value_formula import edit_cost_value_formula
from .remove_cost_item import remove_cost_item
from .remove_cost_item_quantity import remove_cost_item_quantity
from .remove_cost_schedule import remove_cost_schedule
from .remove_cost_value import remove_cost_value
from .unassign_cost_item_quantity import unassign_cost_item_quantity
wrap_usecases(__path__, __name__)
__all__ = [
"add_cost_item",
"add_cost_item_quantity",
"add_cost_schedule",
"add_cost_value",
"assign_cost_item_quantity",
"assign_cost_value",
"calculate_cost_item_resource_value",
"copy_cost_item",
"copy_cost_item_values",
"copy_cost_schedule",
"edit_cost_item",
"edit_cost_item_quantity",
"edit_cost_schedule",
"edit_cost_value",
"edit_cost_value_formula",
"remove_cost_item",
"remove_cost_item_quantity",
"remove_cost_schedule",
"remove_cost_value",
"unassign_cost_item_quantity",
]
@@ -0,0 +1,66 @@
# 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/>.
from typing import Optional
import ifcopenshell.api.control
import ifcopenshell.api.nest
import ifcopenshell.api.root
import ifcopenshell.guid
def add_cost_item(
file: ifcopenshell.file,
cost_schedule: Optional[ifcopenshell.entity_instance] = None,
cost_item: Optional[ifcopenshell.entity_instance] = None,
) -> ifcopenshell.entity_instance:
"""Add a new cost item
A cost item represents a single line item in a cost schedule. Cost items
may then be broken down into cost subitems.
Either `cost_schedule` or `cost_item` must be provided.
:param cost_schedule: If the cost item is to be added as a root or top
level cost item to a cost schedule, the IfcCostSchedule may be
specified. This is mutually exlclusive to the cost_item parameter.
:param cost_item: If the cost item is to be added as a subitem to an
existing cost item, the parent IfcCostItem may be specified. This is
mutually exclusive to the cost_schedule parameter.
:return: The newly created IfcCostItem
Example:
.. code:: python
# The very first cost item must be in a cost schedule
schedule = ifcopenshell.api.cost.add_cost_schedule(model)
# You may add cost items as top level item in the schedule
item1 = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
# Alternatively you may add them as subitems
item2 = ifcopenshell.api.cost.add_cost_item(model, cost_item=item1)
"""
cost_item_ = ifcopenshell.api.root.create_entity(file, ifc_class="IfcCostItem")
if cost_schedule:
ifcopenshell.api.control.assign_control(file, cost_schedule, [cost_item_])
elif cost_item:
ifcopenshell.api.nest.assign_object(file, related_objects=[cost_item_], relating_object=cost_item)
return cost_item_
@@ -0,0 +1,90 @@
# 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/>.
import ifcopenshell.api
import ifcopenshell.util.unit
def add_cost_item_quantity(
file: ifcopenshell.file,
cost_item: ifcopenshell.entity_instance,
ifc_class: ifcopenshell.util.unit.QUANTITY_CLASS = "IfcQuantityCount",
) -> ifcopenshell.entity_instance:
"""Adds a new quantity associated with a cost item
Cost items calculate their subtotal by multiplying the sum of the cost
item's "values" by the sum of the cost item's "quantities". The
quantities may be either parametrically linked to quantities measured on
physical product, or manually specified.
The quantity must be of a particular type, common examples are:
- IfcQuantityCount: to count the total occurrences of a product, useful
for things like doors, windows, and furniture
- IfcQuantityNumber: any other generic numeric quantity
- IfcQuantityLength
- IfcQuantityArea
- IfcQuantityVolume
- IfcQuantityWeight
- IfcQuantityTime
A cost item must not mix quantities of different types.
If an IfcQuantityCount is used, then this API will automatically count
all products that this cost item controls (see
ifcopenshell.api.controls.assign_control) and prefill that quantity.
For all other quantity types, the quantity is left as zero and the user
must either manually specify the quantity or parametrically link it
using another API call.
:param cost_item: The IfcCostItem to add the quantity to
:param ifc_class: The type of quantity to add
:return: The newly created quantity entity, chosen from the ifc_class
parameter
Example:
.. code:: python
chair = ifcopenshell.api.root.create_entity(model, ifc_class="IfcFurniture")
schedule = ifcopenshell.api.cost.add_cost_schedule(model)
item = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
ifcopenshell.api.control.assign_control(model,
relating_control=item, related_objects=[chair])
# Let's assume we want to count the amount of chairs to calculate our cost item
# Because this is an IfcQuantityCount the count will be automatically set to "1" chair
ifcopenshell.api.cost.add_cost_item_quantity(model,
cost_item=item, ifc_class="IfcQuantityCount")
"""
quantity = file.create_entity(ifc_class, Name="Unnamed")
# 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 ifc_class == "IfcQuantityCount":
count = 0
for rel in cost_item.Controls:
count += len(rel.RelatedObjects)
quantity[3] = count
else:
quantity[3] = 0.0
quantities = list(cost_item.CostQuantities or [])
quantities.append(quantity)
cost_item.CostQuantities = quantities
return quantity
@@ -0,0 +1,63 @@
# 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/>.
from datetime import datetime
from typing import Optional
import ifcopenshell.api.root
import ifcopenshell.api.sequence
def add_cost_schedule(
file: ifcopenshell.file, name: Optional[str] = None, predefined_type: str = "NOTDEFINED"
) -> ifcopenshell.entity_instance:
"""Add a new cost schedule
A cost schedule is a group of cost items which typically represent a
cost plan or breakdown of the project. This may be used as an estimate,
bid, or actual cost.
Alternatively, a cost schedule may also represent a schedule of rates,
which include cost items which capture unit rates for different elements
or processes.
As such, creating a cost schedule is necessary prior to creating and
managing any cost items.
:param name: The name of the cost schedule.
:param predefined_type: The predefined type of the cost schedule, chosen
from a valid type in the IFC documentation for
IfcCostScheduleTypeEnum
:return: The newly created IfcCostSchedule entity
Example:
.. code:: python
schedule = ifcopenshell.api.cost.add_cost_schedule(model)
# Now that we have a cost schedule, we may add cost items to it
item = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
"""
cost_schedule = ifcopenshell.api.root.create_entity(
file,
ifc_class="IfcCostSchedule",
predefined_type=predefined_type,
name=name,
)
cost_schedule.UpdateDate = ifcopenshell.api.sequence.add_date_time(file, datetime.now())
return cost_schedule
@@ -0,0 +1,105 @@
# 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/>.
import ifcopenshell
def add_cost_value(file: ifcopenshell.file, parent: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
"""Adds a new value or subvalue to a cost item
A cost item's subtotal can be specified in two ways.
Option 1 is by simply manually specifying the subtotal value, which
represents the full cost of that cost item. This option occurs when a
cost item has no quantities associated with it.
Option 2 is by specifying a unit cost value of the cost item, which is
then multiplied by the associated quantity of the cost item, to give us
the subtotal. This option occurs when a cost item has quantities
associated with it.
For either option 1 (full cost value) or option 2 (unit cost value), the
cost value may be specified as a single number, or as a sum of
subcomponents or formulas (e.g. multiplication by wastage factor, or
adding taxes or other adjustments).
This function lets you add a single top level unit value to a cost item,
or alternatively price subcomponents by using the "parent" parameter.
More advanced usage, which involves summing, subcategory-filtered costs,
and formulas are possible but not yet documented.
:param parent: A parent IfcCostItem, if specifying a price directly to a
cost item, or a top-level price component. Alternatively, this can
be set to a IfcCostValue, if specifying price subcomponents.
:return: The newly created IfcCostValue
Example:
.. code:: python
# We always need a schedule first prior to adding any cost items
schedule = ifcopenshell.api.cost.add_cost_schedule(model)
# Option 1: This cost item will have a full cost of 42.0
item1 = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
value = ifcopenshell.api.cost.add_cost_value(model, parent=item1)
ifcopenshell.api.cost.edit_cost_value(model, cost_value=value,
attributes={"AppliedValue": 42.0})
# Option 2: This cost item will have a unit cost of 5.0 per unit
# area, multiplied by the quantity of area specified explicitly as
# 3.0, giving us a subtotal cost of 15.0.
item2 = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
value = ifcopenshell.api.cost.add_cost_value(model, parent=item2)
ifcopenshell.api.cost.edit_cost_value(model, cost_value=value,
attributes={"AppliedValue": 5.0})
quantity = ifcopenshell.api.cost.add_cost_item_quantity(model,
cost_item=item2, ifc_class="IfcQuantityVolume")
ifcopenshell.api.cost.edit_cost_item_quantity(model,
physical_quantity=quantity, "attributes": {"VolumeValue": 3.0})
# A cost value may also be specified in terms of the sum of its
# subcomponents. In this case, it's broken down into 2 subvalues.
item1 = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
value = ifcopenshell.api.cost.add_cost_value(model, parent=item1)
subvalue1 = ifcopenshell.api.cost.add_cost_value(model, parent=value)
subvalue2 = ifcopenshell.api.cost.add_cost_value(model, parent=value)
# This specifies that the value is the sum of all subitems
# regardless of their cost category. The first subvalue is 2.0 and
# the second is 3.0, giving a total value of 5.0.
ifcopenshell.api.cost.edit_cost_value(model, cost_value=value, attributes={"Category": "*"})
ifcopenshell.api.cost.edit_cost_value(model,
cost_value=subvalue1, attributes={"AppliedValue": 2.0})
ifcopenshell.api.cost.edit_cost_value(model,
cost_value=subvalue2, attributes={"AppliedValue": 3.0})
"""
value = file.create_entity("IfcCostValue")
if parent.is_a("IfcCostItem"):
values = list(parent.CostValues or [])
values.append(value)
parent.CostValues = values
elif parent.is_a("IfcConstructionResource"):
values = list(parent.BaseCosts or [])
values.append(value)
parent.BaseCosts = values
elif parent.is_a("IfcCostValue"):
values = list(parent.Components or [])
values.append(value)
parent.Components = values
return value
@@ -0,0 +1,160 @@
# 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/>.
from typing import Any
import ifcopenshell.api.control
import ifcopenshell.api.cost
def assign_cost_item_quantity(
file: ifcopenshell.file,
cost_item: ifcopenshell.entity_instance,
products: list[ifcopenshell.entity_instance],
prop_name: str = "",
) -> None:
"""Adds a cost item quantity that is parametrically connected to a product
A cost item may have its subtotal calculated by multiplying a unit value
by a quantity associated with the cost item. That quantity may be either
manually specified or parametrically connected to a quantity on a
product. This API function lets you create that parametric connection.
For example, you may wish to have a cost item linked to the "NetVolume"
quantity on all IfcSlabs. Each quantity has a name which you can
specify. If the quantity is updated in-place (which should occur for
Native IFC applications) then the quantity for the cost item will
automatically update as well. If the quantity is deleted and then
re-added, then the parametric relationship is also lost.
This API also automatically assigns a control relationship between the
cost item and the product, so it is not necessary to use
ifcopenshell.api.control.assign_control.
If cost item has just 1 quantity and it's IfcQuantityCount, API will
assume that quantity is used for counting controlled objects
and it will recalculate the quantity value at the end of the API call
as long as the RelatedObjects are not IfcConstructionResource which do not
count towards the cost item (they only provide value).
:param cost_item: The IfcCostItem to assign parametric quantities to
:param products: The IfcObjects to assign parametric quantities to
:param prop_name: The name of the quantity. If this is not specified,
then it is assumed that there is no calculated quantity, and the
number of objects are counted instead.
:return: None
Example:
.. code:: python
schedule = ifcopenshell.api.cost.add_cost_schedule(model)
item = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
# Let's imagine a unit cost of 5.0 per unit volume
value = ifcopenshell.api.cost.add_cost_value(model, parent=item)
ifcopenshell.api.cost.edit_cost_value(model, cost_value=value,
attributes={"AppliedValue": 5.0})
slab = ifcopenshell.api.root.create_entity(model, ifc_class="IfcSlab")
# Usually the quantity would be automatically calculated via a
# graphical authoring application but let's assign a manual quantity
# for now.
qto = ifcopenshell.api.pset.add_qto(model, product=slab, name="Qto_SlabBaseQuantities")
ifcopenshell.api.pset.edit_qto(model, qto=qto, properties={"NetVolume": 42.0})
# Now let's parametrically link the slab's quantity to the cost
# item. If the slab is edited in the future and 42.0 changes, then
# the updated value will also automatically be applied to the cost
# item.
ifcopenshell.api.cost.assign_cost_item_quantity(model,
cost_item=item, products=[slab], prop_name="NetVolume")
"""
usecase = Usecase()
usecase.file = file
usecase.settings = {
"cost_item": cost_item,
"products": products or [],
"prop_name": prop_name,
}
return usecase.execute()
class Usecase:
file: ifcopenshell.file
settings: dict[str, Any]
def execute(self):
if self.settings["prop_name"]:
self.quantities = set(self.settings["cost_item"].CostQuantities or [])
for product in self.settings["products"]:
if product.is_a("IfcSpatialElement"):
continue
self.assign_cost_control(related_object=product, cost_item=self.settings["cost_item"])
if self.settings["prop_name"]:
if (
self.settings["cost_item"].CostQuantities
and self.settings["cost_item"].CostQuantities[0].Name.lower() != self.settings["prop_name"].lower()
):
continue
self.add_quantity_from_related_object(product)
if self.settings["prop_name"]:
self.settings["cost_item"].CostQuantities = list(self.quantities)
else:
self.update_cost_item_count()
def assign_cost_control(
self, related_object: ifcopenshell.entity_instance, cost_item: ifcopenshell.entity_instance
) -> ifcopenshell.entity_instance:
return ifcopenshell.api.control.assign_control(
self.file,
related_objects=[related_object],
relating_control=cost_item,
)
def add_quantity_from_related_object(self, element: ifcopenshell.entity_instance) -> None:
for relationship in element.IsDefinedBy:
if relationship.is_a("IfcRelDefinesByProperties"):
self.add_quantity_from_qto(relationship.RelatingPropertyDefinition)
def add_quantity_from_qto(self, qto: ifcopenshell.entity_instance) -> None:
if not qto.is_a("IfcElementQuantity"):
return
for prop in qto.Quantities:
if prop.is_a("IfcPhysicalSimpleQuantity") and prop.Name.lower() == self.settings["prop_name"].lower():
self.quantities.add(prop)
def update_cost_item_count(self):
# 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 not self.settings["cost_item"].CostQuantities:
ifcopenshell.api.cost.add_cost_item_quantity(
self.file,
cost_item=self.settings["cost_item"],
ifc_class="IfcQuantityCount",
)
if len(self.settings["cost_item"].CostQuantities) == 1:
quantity = self.settings["cost_item"].CostQuantities[0]
if quantity.is_a("IfcQuantityCount"):
count = 0
for rel in self.settings["cost_item"].Controls:
for obj in rel.RelatedObjects:
# Only increment if not a resource
if not obj.is_a("IfcConstructionResource"):
count += 1
quantity[3] = count
@@ -0,0 +1,71 @@
# 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/>.
import ifcopenshell.api.cost
def assign_cost_value(
file: ifcopenshell.file, cost_item: ifcopenshell.entity_instance, cost_rate: ifcopenshell.entity_instance
) -> None:
"""Assigns a cost value to a cost item from a schedule of rates
Instead of assigning cost values from scratch for each cost item in a
cost schedule, the cost values may instead be assigned from a schedule
of rates.
A schedule of rates is just another cost schedule which have cost values
but no quantities. This API will allow you to "copy" the values from a
cost item in the schedule of rates into another cost item in your own
cost schedule. When the schedule of rates value is updated, then your
cost item values will also be updated. You can think of the schedule of
rates as a "template" to quickly populate your rates from.
:param cost_item: The IfcCostItem that you want to copy the values to
:param cost_rate: The IfcCostItem that you want to copy the values from
:return: None
Example:
.. code:: python
# Let's create a schedule of rates with a single rate in it of 5.0
rate_tables = ifcopenshell.api.cost.add_cost_schedule(model,
predefined_type="SCHEDULEOFRATES")
rate = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
value = ifcopenshell.api.cost.add_cost_value(model, parent=rate)
ifcopenshell.api.cost.edit_cost_value(model, cost_value=value,
attributes={"AppliedValue": 5.0})
# And this schedule will be for our actual cost plan / estimate / etc
schedule = ifcopenshell.api.cost.add_cost_schedule(model)
item = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
# Now the cost item has the same rate as the one from the schedule of rate's item
ifcopenshell.api.cost.assign_cost_value(model, cost_item=item, cost_rate=rate)
"""
if cost_item.CostValues:
[
ifcopenshell.api.cost.remove_cost_value(
file,
parent=cost_item,
cost_value=cost_value,
)
for cost_value in cost_item.CostValues
]
# This is an assumption, and not part of the official IFC documentation
cost_item.CostValues = cost_rate.CostValues
@@ -0,0 +1,112 @@
# 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/>.
import ifcopenshell.api.cost
import ifcopenshell.util.resource
def calculate_cost_item_resource_value(file: ifcopenshell.file, cost_item: ifcopenshell.entity_instance) -> None:
"""Calculates the total cost of all resources associated with a cost item
A cost item may have construction resources (e.g. equipment, material,
etc) assigned to it. Construction resources may be assigned directly to
the cost item, or assigned first to a task, and the task is then
assigned to the cost item.
The cost of a resource is calculated by the total sum of all of its base
costs. If no quantity is provided, that sum is considered to be the
total cost. Otherwise, it is considered to be a unit cost, and is then
multiplied by the resource quantity. The quantity is either stored as a
base quantity (such as a volume) for a things like material resources,
or as a duration as a daily rate for labour resources.
The final calculated cost is set as the cost item's value. Any
previously existing values are removed.
:param cost_item: The IfcCostItem to calculate
:return: None
Example:
.. code:: python
# First, we need a cost schedule and item
schedule = ifcopenshell.api.cost.add_cost_schedule(model)
item = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
# Let's imagine we have our own formworking crew
crew = ifcopenshell.api.resource.add_resource(model, ifc_class="IfcCrewResource")
# ... and they need concrete
concrete = ifcopenshell.api.resource.add_resource(model,
ifc_class="IfcConstructionMaterialResource", parent_resource=crew)
ifcopenshell.api.control.assign_control(model,
relating_control=item, related_objects=[concrete])
# ... which has a unit price of 42.0 per m3
value = ifcopenshell.api.cost.add_cost_value(model, parent=concrete)
ifcopenshell.api.cost.edit_cost_value(model, cost_value=value,
attributes={"AppliedValue": 42.0})
# ... and a volume of 200m3
quantity = ifcopenshell.api.resource.add_resource_quantity(model,
resource=concrete, ifc_class="IfcQuantityVolume")
ifcopenshell.api.resource.edit_resource_quantity(model,
physical_quantity=quantity, "attributes": {"VolumeValue": 200.0})
# Let's say they also need some equipment
equipment = ifcopenshell.api.resource.add_resource(model,
ifc_class="IfcConstructionEquipmentResource", parent_resource=crew)
ifcopenshell.api.control.assign_control(model,
relating_control=item, related_objects=[equipment])
# ... with a fixed price of 50,000
value = ifcopenshell.api.cost.add_cost_value(model, parent=concrete)
ifcopenshell.api.cost.edit_cost_value(model, cost_value=value,
attributes={"AppliedValue": 42.0})
# (42 * 200) + 50000 = 58400 is our calculated cost
ifcopenshell.api.cost.calculate_cost_item_resource_value(model, cost_item=item)
"""
for cost_value in cost_item.CostValues or []:
ifcopenshell.api.cost.remove_cost_value(file, parent=cost_item, cost_value=cost_value)
resources = []
for rel in cost_item.Controls or []:
for related_object in rel.RelatedObjects:
if related_object.is_a("IfcConstructionResource"):
resources.append(related_object)
elif related_object.is_a("IfcTask"):
for rel2 in related_object.OperatesOn or []:
for related_object2 in rel2.RelatedObjects:
if related_object2.is_a("IfcConstructionResource"):
resources.append(related_object2)
for resource in resources:
cost, unit = ifcopenshell.util.resource.get_cost(resource)
if cost is None:
# Concept to standardise - Not defined in schema, but this makes manual scheduling of resources 10x faster and less duplicate data.
parent_cost = ifcopenshell.util.resource.get_parent_cost(resource)
if parent_cost:
cost, unit = parent_cost
quantity = ifcopenshell.util.resource.get_quantity(resource)
if cost is None:
continue
if unit and "day" in unit:
quantity = quantity / 8 # Assume 8 hour working day - TODO implement resource calendar
formula = "{}*{}".format(cost, quantity)
cost_value = ifcopenshell.api.cost.add_cost_value(file, parent=cost_item)
cost_value.Name = resource.Name
ifcopenshell.api.cost.edit_cost_value_formula(file, cost_value=cost_value, formula=formula)
@@ -0,0 +1,115 @@
# 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/>.
from typing import Union
import ifcopenshell
import ifcopenshell.api.nest
import ifcopenshell.util.element
def copy_cost_item(
file: ifcopenshell.file, cost_item: ifcopenshell.entity_instance
) -> Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]]:
"""Copies all cost items and related relationships
The following relationships are also duplicated:
* The copy will have the same attributes and property sets as the original cost item
* The copy will be assigned to the parent cost schedule
* The copy will have duplicated nested cost items
:param cost_item: The cost item to be duplicated
:return: The duplicated cost item or the list of duplicated cost items if the latter has children
Example:
.. code:: python
# We have a cost item
cost_item = CostItem(name="Design new feature", deadline="2023-03-01")
# And now we have two
duplicated_cost_item = project.duplicate_cost_item(cost_item)
"""
usecase = Usecase()
usecase.file = file
return usecase.execute(cost_item)
class Usecase:
file: ifcopenshell.file
new_cost_items: list[ifcopenshell.entity_instance]
def execute(
self, cost_item: ifcopenshell.entity_instance
) -> Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]]:
self.new_cost_items = []
self.duplicate_cost_item(cost_item)
return self.new_cost_items[0] if len(self.new_cost_items) == 1 else self.new_cost_items
def duplicate_cost_item(self, cost_item: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
new_cost_item = ifcopenshell.util.element.copy_deep(self.file, cost_item)
self.new_cost_items.append(new_cost_item)
self.copy_indirect_attributes(cost_item, new_cost_item)
return new_cost_item
def copy_indirect_attributes(
self, from_element: ifcopenshell.entity_instance, to_element: ifcopenshell.entity_instance
) -> None:
for inverse in self.file.get_inverse(from_element):
if inverse.is_a("IfcRelDefinesByProperties"):
inverse = ifcopenshell.util.element.copy(self.file, inverse)
inverse.RelatedObjects = [to_element]
pset = ifcopenshell.util.element.copy_deep(self.file, inverse.RelatingPropertyDefinition)
inverse.RelatingPropertyDefinition = pset
elif inverse.is_a("IfcRelNests") and inverse.RelatingObject == from_element:
nested_cost_items = [e for e in inverse.RelatedObjects]
if nested_cost_items:
new_cost_items = []
for cost_item in nested_cost_items:
new_cost_item = self.duplicate_cost_item(cost_item)
new_cost_items.append(new_cost_item)
inverse = ifcopenshell.util.element.copy(self.file, inverse)
inverse.RelatingObject = to_element
inverse.RelatedObjects = new_cost_items
ifcopenshell.api.nest.unassign_object(self.file, related_objects=new_cost_items)
ifcopenshell.api.nest.assign_object(
self.file, related_objects=new_cost_items, relating_object=to_element
)
# elif inverse.is_a("IfcRelAssignsToProduct"):
# continue
# to_element.IsDefinedBy = inverse.RelatingOrder
# elif inverse.is_a("IfcRelAssignsToProcess"):
# to_element.IsDefinedBy = inverse.RelatingProcess
# elif inverse.is_a("IfcRelAssignsToResource"):
# continue
# elif inverse.is_a("IfcRelAssignsToControl"):
# continue
# elif inverse.is_a("IfcRelDefinesByObject"):
# continue
else:
for i, value in enumerate(inverse):
if value == from_element:
new_inverse = ifcopenshell.util.element.copy(self.file, inverse)
new_inverse[i] = to_element
elif isinstance(value, (tuple, list)) and from_element in value:
new_value = list(value)
new_value.append(to_element)
inverse[i] = new_value
@@ -0,0 +1,58 @@
# 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/>.
import ifcopenshell.api.cost
import ifcopenshell.util.element
def copy_cost_item_values(
file: ifcopenshell.file, source: ifcopenshell.entity_instance, destination: ifcopenshell.entity_instance
) -> None:
"""Copies all cost values from one cost item to another
Any previously existing values will be removed. The entire value is
copied, including all components and formulas. However they are not
parametrically linked, so if one value changes, the other will not.
:param source: The IfcCostItem to copy cost values from
:param destination: The IfcCostItem to copy cost values from
:return: None
Example:
.. code:: python
# Assume we have a schedule with multiple items in it
schedule = ifcopenshell.api.cost.add_cost_schedule(model)
item1 = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
item2 = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
# One of the items has a value
value = ifcopenshell.api.cost.add_cost_value(model, parent=item)
ifcopenshell.api.cost.edit_cost_value(model, cost_value=value,
attributes={"AppliedValue": 5000.0})
# Let's copy the value from one item to another
ifcopenshell.api.cost.copy_cost_item_values(model, source=item1, destination=item2)
"""
for cost_value in destination.CostValues or []:
ifcopenshell.api.cost.remove_cost_value(file, source, cost_value=cost_value)
copied_cost_values = []
for cost_value in source.CostValues or []:
copied_cost_values.append(ifcopenshell.util.element.copy_deep(file, cost_value))
destination.CostValues = copied_cost_values
@@ -0,0 +1,49 @@
# 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/>.
import ifcopenshell.api.control
import ifcopenshell.api.cost
import ifcopenshell.util.element
def copy_cost_schedule(
file: ifcopenshell.file, cost_schedule: ifcopenshell.entity_instance
) -> ifcopenshell.entity_instance:
"""Copy a cost schedule.
:param cost_schedule: IfcCostSchedule to copy.
:return: The duplicated IfcCostSchedule entity
Example:
.. code:: python
schedule = ifcopenshell.api.cost.add_cost_schedule(model)
new_schedule = ifcopenshell.api.cost.copy_cost_schedule(schedule)
"""
# Shared code logic with copy_work_schedule.
new_schedule = ifcopenshell.util.element.copy(file, cost_schedule)
for rel in cost_schedule.Controls:
for cost_item in rel.RelatedObjects:
duplicated_cost_item = ifcopenshell.api.cost.copy_cost_item(file, cost_item)
if isinstance(duplicated_cost_item, list):
# All other nested items are not connected to the cost schedule explicitly.
duplicated_cost_item = duplicated_cost_item[0]
ifcopenshell.api.control.assign_control(file, new_schedule, [duplicated_cost_item])
return new_schedule
@@ -0,0 +1,44 @@
# 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/>.
from typing import Any
import ifcopenshell
def edit_cost_item(
file: ifcopenshell.file, cost_item: ifcopenshell.entity_instance, attributes: dict[str, Any]
) -> None:
"""Edits the attributes of an IfcCostItem
For more information about the attributes and data types of an
IfcCostItem, consult the IFC documentation.
:param cost_item: The IfcCostItem entity you want to edit
:param attributes: a dictionary of attribute names and values.
:return: None
Example:
.. code:: python
schedule = ifcopenshell.api.cost.add_cost_schedule(model)
item = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
ifcopenshell.api.cost.edit_cost_item(model, cost_item=item, attributes={"Name": "Foo"})
"""
for name, value in attributes.items():
setattr(cost_item, name, value)
@@ -0,0 +1,52 @@
# 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/>.
from typing import Any
import ifcopenshell
def edit_cost_item_quantity(
file: ifcopenshell.file, physical_quantity: ifcopenshell.entity_instance, attributes: dict[str, Any]
) -> None:
"""Edits the attributes of an IfcPhysicalQuantity
For more information about the attributes and data types of an
IfcPhysicalQuantity, consult the IFC documentation.
:param physical_quantity: The IfcPhysicalQuantity entity you want to edit
:param attributes: a dictionary of attribute names and values.
:return: None
Example:
.. code:: python
schedule = ifcopenshell.api.cost.add_cost_schedule(model)
item = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
# This cost item will have a unit cost of 5 and a volume of 3
value = ifcopenshell.api.cost.add_cost_value(model, parent=item)
ifcopenshell.api.cost.edit_cost_value(model, cost_value=value,
attributes={"AppliedValue": 5.0})
quantity = ifcopenshell.api.cost.add_cost_item_quantity(model,
cost_item=item, ifc_class="IfcQuantityVolume")
ifcopenshell.api.cost.edit_cost_item_quantity(model,
physical_quantity=quantity, "attributes": {"VolumeValue": 3.0})
"""
for name, value in attributes.items():
setattr(physical_quantity, name, value)
@@ -0,0 +1,44 @@
# 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/>.
from typing import Any
import ifcopenshell
def edit_cost_schedule(
file: ifcopenshell.file, cost_schedule: ifcopenshell.entity_instance, attributes: dict[str, Any]
) -> None:
"""Edits the attributes of an IfcCostSchedule
For more information about the attributes and data types of an
IfcCostSchedule, consult the IFC documentation.
:param cost_schedule: The IfcCostSchedule entity you want to edit
:param attributes: a dictionary of attribute names and values.
:return: None
Example:
.. code:: python
schedule = ifcopenshell.api.cost.add_cost_schedule(model)
ifcopenshell.api.cost.edit_cost_schedule(model,
cost_schedule=schedule, attributes={"Name": "Foo"})
"""
for name, value in attributes.items():
setattr(cost_schedule, name, value)
@@ -0,0 +1,64 @@
# 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/>.
from typing import Any
import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.util.unit
def edit_cost_value(
file: ifcopenshell.file, cost_value: ifcopenshell.entity_instance, attributes: dict[str, Any]
) -> None:
"""Edits the attributes of an IfcCostValue
For more information about the attributes and data types of an
IfcCostValue, consult the IFC documentation.
:param cost_value: The IfcCostValue entity you want to edit
:param attributes: a dictionary of attribute names and values.
:return: None
Example:
.. code:: python
schedule = ifcopenshell.api.cost.add_cost_schedule(model)
item = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
# This cost item will have a total cost of 42
value = ifcopenshell.api.cost.add_cost_value(model, parent=item)
ifcopenshell.api.cost.edit_cost_value(model, cost_value=value,
attributes={"AppliedValue": 42.0})
"""
for name, value in attributes.items():
if name == "AppliedValue" and value is not None:
# TODO: support all applied value select types
value = file.createIfcMonetaryMeasure(value)
elif name == "UnitBasis":
old_unit_basis = cost_value.UnitBasis
if value:
value_component = file.create_entity(
ifcopenshell.util.unit.get_unit_measure_class(value["UnitComponent"].UnitType),
value["ValueComponent"],
)
value = file.create_entity("IfcMeasureWithUnit", value_component, value["UnitComponent"])
if old_unit_basis:
ifcopenshell.util.element.remove_deep2(file, old_unit_basis)
setattr(cost_value, name, value)
@@ -0,0 +1,80 @@
# 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/>.
from typing import Any
import ifcopenshell
import ifcopenshell.api.cost
import ifcopenshell.util.cost
def edit_cost_value_formula(file: ifcopenshell.file, cost_value: ifcopenshell.entity_instance, formula: str) -> None:
"""Sets a cost value based on a formula, similar to formulas in spreadsheets
Costs may be made up of many components (e.g. labour, material, waste
factor, taxes, etc). This can be easily represented in the form of a
formula similar thta would be used in spreadsheet applications.
For more information, see ifcopenshell.util.cost
:param cost_value: The IfcCostValue to set the values of
:param formula: The formula following the language of ifcopenshell.util.cost
:return: None
Example:
.. code:: python
schedule = ifcopenshell.api.cost.add_cost_schedule(model)
item = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
value = ifcopenshell.api.cost.add_cost_value(model, parent=item)
ifcopenshell.api.cost.edit_cost_value_formula(model, cost_value=value,
formula="5000 * 1.19")
"""
usecase = Usecase()
usecase.file = file
usecase.settings = {"cost_value": cost_value, "formula": formula or {}}
return usecase.execute()
class Usecase:
file: ifcopenshell.file
settings: dict[str, Any]
def execute(self):
try:
data = ifcopenshell.util.cost.unserialise_cost_value(self.settings["formula"], self.settings["cost_value"])
except:
return
self.edit_cost_value(data)
def edit_cost_value(self, data, parent=None):
ifc = data.get("ifc", None)
if not ifc:
ifc = ifcopenshell.api.cost.add_cost_value(self.file, parent=parent)
if "AppliedValue" in data:
if data["AppliedValue"]:
ifc.AppliedValue = self.file.createIfcMonetaryMeasure(data["AppliedValue"])
else:
ifc.AppliedValue = None
ifc.Category = data["Category"] if "Category" in data else None
ifc.ArithmeticOperator = data["ArithmeticOperator"] if "ArithmeticOperator" in data else None
if "Components" in data:
for component in data["Components"]:
self.edit_cost_value(component, ifc)
@@ -0,0 +1,63 @@
# 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/>.
import ifcopenshell
import ifcopenshell.api.cost
import ifcopenshell.util.element
def remove_cost_item(file: ifcopenshell.file, cost_item: ifcopenshell.entity_instance) -> None:
"""Removes a cost item
All associated relationships with the cost item are also removed,
however the related resources, products, and tasks themselves are
retained.
:param cost_item: The IfcCostItem entity you want to remove
:return: None
Example:
.. code:: python
schedule = ifcopenshell.api.cost.add_cost_schedule(model)
item = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
ifcopenshell.api.cost.remove_cost_item(model, cost_item=item)
"""
# TODO: do a deep purge
for inverse in file.get_inverse(cost_item):
if inverse.is_a("IfcRelNests"):
if inverse.RelatingObject == cost_item:
for related_object in inverse.RelatedObjects:
ifcopenshell.api.cost.remove_cost_item(file, cost_item=related_object)
elif inverse.RelatedObjects == (cost_item,):
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelAssignsToControl"):
if len(inverse.RelatedObjects) >= 2 or inverse.RelatingControl == cost_item:
continue
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
history = cost_item.OwnerHistory
file.remove(cost_item)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
@@ -0,0 +1,51 @@
# 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/>.
import ifcopenshell
def remove_cost_item_quantity(
file: ifcopenshell.file, cost_item: ifcopenshell.entity_instance, physical_quantity: ifcopenshell.entity_instance
) -> None:
"""Removes a quantity assigned to a cost item
If the quantity is part of a product (e.g. wall), then the quantity will
still exist and merely the relationship to the cost item will be
removed.
:param cost_item: The IfcCostItem that the quantity is assigned to
:param physical_quantity: The IfcPhysicalQuantity to remove
:return: None
Example:
.. code:: python
schedule = ifcopenshell.api.cost.add_cost_schedule(model)
item = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
quantity = ifcopenshell.api.cost.add_cost_item_quantity(model,
cost_item=item, ifc_class="IfcQuantityVolume")
# Let's change our mind and delete it
ifcopenshell.api.cost.remove_cost_item(model,
cost_item=item, physical_quantity=quantity)
"""
if file.get_total_inverses(physical_quantity) == 1:
file.remove(physical_quantity)
return
quantities = list(cost_item.CostQuantities or [])
quantities.remove(physical_quantity)
cost_item.CostQuantities = quantities
@@ -0,0 +1,52 @@
# 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/>.
import ifcopenshell
import ifcopenshell.api.cost
import ifcopenshell.util.element
def remove_cost_schedule(file: ifcopenshell.file, cost_schedule: ifcopenshell.entity_instance) -> None:
"""Removes a cost schedule
All associated relationships with the cost schedule are also removed,
including all cost items.
:param cost_schedule: The IfcCostSchedule entity you want to remove
:return: None
Example:
.. code:: python
schedule = ifcopenshell.api.cost.add_cost_schedule(model)
item = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
ifcopenshell.api.cost.remove_cost_schedule(model, cost_schedule=schedule)
"""
# TODO: do a deep purge
for inverse in file.get_inverse(cost_schedule):
if inverse.is_a("IfcRelAssignsToControl"):
[
ifcopenshell.api.cost.remove_cost_item(file, cost_item=related_object)
for related_object in inverse.RelatedObjects
if related_object.is_a("IfcCostItem")
]
history = cost_schedule.OwnerHistory
file.remove(cost_schedule)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
@@ -0,0 +1,62 @@
# 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/>.
import ifcopenshell
def remove_cost_value(
file: ifcopenshell.file, parent: ifcopenshell.entity_instance, cost_value: ifcopenshell.entity_instance
) -> None:
"""Removes a cost value
The cost value may be assigned either to a cost item, a construction
resource, or another cost value (i.e. it is a subcomponent of a cost)
:param parent: The IfcCostItem, IfcConstructionResource, or IfcCostValue
that the IfcCostValue is assigned to.
:param cost_value: The IfcCostValue that you want to remove
:return: None
Example:
.. code:: python
schedule = ifcopenshell.api.cost.add_cost_schedule(model)
item = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
# This cost item will have a unit cost of 5 and a volume of 3
value = ifcopenshell.api.cost.add_cost_value(model, parent=item)
ifcopenshell.api.cost.edit_cost_value(model, cost_value=value,
attributes={"AppliedValue": 5.0})
ifcopenshell.api.cost.remove_cost_value(model, parent=item, cost_value=value)
"""
if file.get_total_inverses(cost_value) == 1:
file.remove(cost_value)
# TODO deep purge
elif parent.is_a("IfcCostItem"):
values = list(parent.CostValues)
values.remove(cost_value)
parent.CostValues = values if values else None
elif parent.is_a("IfcConstructionResource"):
values = list(parent.BaseCosts)
values.remove(cost_value)
parent.BaseCosts = values if values else None
elif parent.is_a("IfcCostValue"):
components = list(parent.Components)
components.remove(cost_value)
parent.Components = components if components else None
@@ -0,0 +1,106 @@
# 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/>.
import ifcopenshell.api.control
def unassign_cost_item_quantity(
file: ifcopenshell.file, cost_item: ifcopenshell.entity_instance, products: list[ifcopenshell.entity_instance]
) -> None:
"""Removes quantities of a cost item that are calculated on products
A cost item may have quantities that are parametrically calculated on
physical products. This lets you remove those quantities. This means
that any future changes in the physical product's dimensions will not
have any impact on the cost item.
:param cost_item: The IfcCostItem to remove quantities from
:param products: A list of IfcProducts that may have parametrically
connected quantities to the cost item
:return: None
Example:
.. code:: python
schedule = ifcopenshell.api.cost.add_cost_schedule(model)
item = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
# Let's imagine a unit cost of 5.0 per unit volume
value = ifcopenshell.api.cost.add_cost_value(model, parent=item)
ifcopenshell.api.cost.edit_cost_value(model, cost_value=value,
attributes={"AppliedValue": 5.0})
slab = ifcopenshell.api.root.create_entity(model, ifc_class="IfcSlab")
# Usually the quantity would be automatically calculated via a
# graphical authoring application but let's assign a manual quantity
# for now.
qto = ifcopenshell.api.pset.add_qto(model, product=slab, name="Qto_SlabBaseQuantities")
ifcopenshell.api.pset.edit_qto(model, qto=qto, properties={"NetVolume": 42.0})
# Now let's parametrically link the slab's quantity to the cost
# item. If the slab is edited in the future and 42.0 changes, then
# the updated value will also automatically be applied to the cost
# item.
ifcopenshell.api.cost.assign_cost_item_quantity(model,
cost_item=item, products=[slab], prop_name="NetVolume")
# Let's change our mind and remove the parametric connection
ifcopenshell.api.cost.unassign_cost_item_quantity(model,
cost_item=item, products=[slab])
"""
usecase = Usecase()
usecase.file = file
return usecase.execute(cost_item, products or [])
class Usecase:
file: ifcopenshell.file
def execute(self, cost_item: ifcopenshell.entity_instance, products: list[ifcopenshell.entity_instance]) -> None:
quantities = set(cost_item.CostQuantities or [])
for quantity in cost_item.CostQuantities or []:
for inverse in self.file.get_inverse(quantity):
if not inverse.is_a("IfcElementQuantity"):
continue
for rel in inverse.DefinesOccurrence or []:
for related_object in rel.RelatedObjects:
if related_object in products:
quantities.remove(quantity)
cost_item.CostQuantities = list(quantities)
for product in products:
ifcopenshell.api.control.unassign_control(
self.file,
related_objects=[product],
relating_control=cost_item,
)
self.update_cost_item_count(cost_item)
def update_cost_item_count(self, cost_item: ifcopenshell.entity_instance) -> None:
# 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 len(cost_item.CostQuantities) == 1:
quantity = cost_item.CostQuantities[0]
if quantity.is_a("IfcQuantityCount"):
count = 0
for rel in cost_item.Controls:
count += len(rel.RelatedObjects)
if count:
quantity[3] = count
else:
self.file.remove(quantity)