2022-01-19 12:18:33 +11:00
|
|
|
# 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/>.
|
|
|
|
|
|
2021-04-21 20:49:58 +10:00
|
|
|
import ifcopenshell
|
|
|
|
|
import ifcopenshell.api
|
2022-02-09 15:16:22 +11:00
|
|
|
import ifcopenshell.api.owner.settings
|
2021-04-21 20:49:58 +10:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class Usecase:
|
2022-12-14 17:28:34 +11:00
|
|
|
def __init__(self, file, library=None, element=None):
|
|
|
|
|
"""Appends an asset from a library into the active project
|
|
|
|
|
|
|
|
|
|
A BIM library asset may be a type product (e.g. wall type), product
|
|
|
|
|
(e.g. pump), material, profile, or cost schedule.
|
|
|
|
|
|
|
|
|
|
This copies the asset from the specified library file into the active
|
|
|
|
|
project. It handles all details like ensuring that product materials,
|
|
|
|
|
styles, properties, quantities, and so on are preserved.
|
|
|
|
|
|
|
|
|
|
If an asset contains geometry, the geometric contexts are also
|
|
|
|
|
intelligentely transplanted such that existing equivalent contexts are
|
|
|
|
|
reused.
|
|
|
|
|
|
|
|
|
|
Do not mix units.
|
|
|
|
|
|
|
|
|
|
:param library: The file object containing the asset.
|
|
|
|
|
:type library: ifcopenshell.file.file
|
|
|
|
|
:param element: An element in the library file of the asset. It may be
|
|
|
|
|
an IfcTypeProduct, IfcProduct, IfcMaterial, IfcCostSchedule, or
|
|
|
|
|
IfcProfileDef.
|
|
|
|
|
:type library: ifcopenshell.entity_instance.entity_instance
|
|
|
|
|
:return: The appended element
|
|
|
|
|
:rtype: ifcopenshell.entity_instance.entity_instance
|
|
|
|
|
|
2023-01-10 10:16:28 +11:00
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
2022-12-14 17:28:34 +11:00
|
|
|
|
|
|
|
|
# Programmatically generate a library. You could do this visually too.
|
|
|
|
|
library = ifcopenshell.api.run("project.create_file")
|
|
|
|
|
root = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcProject", name="Demo Library")
|
|
|
|
|
context = ifcopenshell.api.run("root.create_entity", library,
|
|
|
|
|
ifc_class="IfcProjectLibrary", name="Demo Library")
|
|
|
|
|
ifcopenshell.api.run("project.assign_declaration", library, definition=context, relating_context=root)
|
|
|
|
|
|
|
|
|
|
# Assign units for our example library
|
|
|
|
|
unit = ifcopenshell.api.run("unit.add_si_unit", library,
|
|
|
|
|
unit_type="LENGTHUNIT", name="METRE", prefix="MILLI")
|
|
|
|
|
ifcopenshell.api.run("unit.assign_unit", library, units=[unit])
|
|
|
|
|
|
|
|
|
|
# Let's create a single asset of a 200mm thick concrete wall
|
|
|
|
|
wall_type = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWallType", name="WAL01")
|
|
|
|
|
concrete = ifcopenshell.api.run("material.add_material", self.file, name="CON", category="concrete")
|
|
|
|
|
rel = ifcopenshell.api.run("material.assign_material", library,
|
|
|
|
|
product=wall_type, type="IfcMaterialLayerSet")
|
|
|
|
|
layer = ifcopenshell.api.run("material.add_layer", library,
|
|
|
|
|
layer_set=rel.RelatingMaterial, material=concrete)
|
|
|
|
|
layer.Name = "Structure"
|
|
|
|
|
layer.LayerThickness = 200
|
|
|
|
|
|
|
|
|
|
# Mark our wall type as a reusable asset in our library.
|
|
|
|
|
ifcopenshell.api.run("project.assign_declaration", library,
|
|
|
|
|
definition=wall_type, relating_context=context)
|
|
|
|
|
|
|
|
|
|
# Let's imagine we're starting a new project
|
|
|
|
|
model = ifcopenshell.api.run("project.create_file")
|
|
|
|
|
project = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcProject", name="Test")
|
|
|
|
|
|
|
|
|
|
# Now we can easily append our wall type from our libary
|
|
|
|
|
wall_type = ifcopenshell.api.run("project.append_asset", model, library=library, element=wall_type)
|
|
|
|
|
"""
|
2021-04-21 20:49:58 +10:00
|
|
|
self.file = file
|
2022-12-14 17:28:34 +11:00
|
|
|
self.settings = {"library": library, "element": element}
|
2021-04-21 20:49:58 +10:00
|
|
|
|
|
|
|
|
def execute(self):
|
2024-02-14 14:23:13 +05:00
|
|
|
# mapping of old element ids to new elements
|
|
|
|
|
self.added_elements:dict[int, ifcopenshell.entity_instance] = {}
|
2021-08-14 12:05:59 +10:00
|
|
|
self.whitelisted_inverse_attributes = {}
|
2021-08-08 20:56:31 +10:00
|
|
|
if self.settings["element"].is_a("IfcTypeProduct"):
|
2022-09-18 23:19:23 +10:00
|
|
|
self.target_class = "IfcTypeProduct"
|
2021-06-18 15:54:01 +10:00
|
|
|
return self.append_type_product()
|
2022-01-18 17:44:50 +11:00
|
|
|
elif self.settings["element"].is_a("IfcProduct"):
|
2022-09-18 23:19:23 +10:00
|
|
|
self.target_class = "IfcProduct"
|
2022-01-18 17:44:50 +11:00
|
|
|
return self.append_product()
|
2021-08-09 11:17:10 +10:00
|
|
|
elif self.settings["element"].is_a("IfcMaterial"):
|
2022-09-18 23:19:23 +10:00
|
|
|
self.target_class = "IfcMaterial"
|
2021-08-09 11:17:10 +10:00
|
|
|
return self.append_material()
|
2021-08-10 09:35:09 +10:00
|
|
|
elif self.settings["element"].is_a("IfcCostSchedule"):
|
2022-09-18 23:19:23 +10:00
|
|
|
self.target_class = "IfcCostSchedule"
|
2021-08-10 09:35:09 +10:00
|
|
|
return self.append_cost_schedule()
|
2021-08-14 12:05:59 +10:00
|
|
|
elif self.settings["element"].is_a("IfcProfileDef"):
|
2022-09-18 23:19:23 +10:00
|
|
|
self.target_class = "IfcProfileDef"
|
2021-08-14 12:05:59 +10:00
|
|
|
return self.append_profile_def()
|
2021-08-10 09:35:09 +10:00
|
|
|
|
2023-02-24 13:01:14 +11:00
|
|
|
def get_existing_element(self, element):
|
|
|
|
|
if element.id() in self.added_elements:
|
|
|
|
|
return self.added_elements[element.id()]
|
2021-08-10 09:35:09 +10:00
|
|
|
try:
|
2023-02-24 13:01:14 +11:00
|
|
|
if element.is_a("IfcRoot"):
|
|
|
|
|
return self.file.by_guid(element.GlobalId)
|
|
|
|
|
elif element.is_a("IfcMaterial"):
|
|
|
|
|
return [e for e in self.file.by_type("IfcMaterial") if e.Name == element.Name][0]
|
|
|
|
|
elif element.is_a("IfcProfileDef"):
|
|
|
|
|
return [e for e in self.file.by_type("IfcProfileDef") if e.ProfileName == element.ProfileName][0]
|
2021-08-10 09:35:09 +10:00
|
|
|
except:
|
|
|
|
|
return False
|
2021-08-09 11:17:10 +10:00
|
|
|
|
|
|
|
|
def append_material(self):
|
2023-02-24 13:01:14 +11:00
|
|
|
self.whitelisted_inverse_attributes = {
|
|
|
|
|
"IfcMaterial": ["HasExternalReferences", "HasProperties", "HasRepresentation"]
|
|
|
|
|
}
|
2022-09-03 22:16:35 +10:00
|
|
|
self.existing_contexts = self.file.by_type("IfcGeometricRepresentationContext")
|
2022-07-27 00:13:55 +01:00
|
|
|
element = self.add_element(self.settings["element"])
|
2023-02-24 13:01:14 +11:00
|
|
|
if element.HasRepresentation:
|
|
|
|
|
self.reuse_existing_contexts()
|
2022-07-27 00:13:55 +01:00
|
|
|
return element
|
2021-06-18 15:54:01 +10:00
|
|
|
|
2021-08-10 09:35:09 +10:00
|
|
|
def append_cost_schedule(self):
|
|
|
|
|
self.whitelisted_inverse_attributes = {"IfcCostSchedule": ["Controls"], "IfcCostItem": ["IsNestedBy"]}
|
|
|
|
|
return self.add_element(self.settings["element"])
|
|
|
|
|
|
2021-08-14 12:05:59 +10:00
|
|
|
def append_profile_def(self):
|
2021-08-14 13:01:22 +10:00
|
|
|
self.whitelisted_inverse_attributes = {"IfcProfileDef": ["HasProperties"]}
|
2021-08-14 12:05:59 +10:00
|
|
|
return self.add_element(self.settings["element"])
|
|
|
|
|
|
2021-06-18 15:54:01 +10:00
|
|
|
def append_type_product(self):
|
|
|
|
|
self.whitelisted_inverse_attributes = {
|
|
|
|
|
"IfcObjectDefinition": ["HasAssociations"],
|
2022-11-07 16:20:23 +11:00
|
|
|
"IfcMaterialDefinition": ["HasExternalReferences", "HasProperties", "HasRepresentation"],
|
2021-06-18 15:54:01 +10:00
|
|
|
"IfcRepresentationItem": ["StyledByItem"],
|
|
|
|
|
}
|
2021-04-21 20:49:58 +10:00
|
|
|
self.existing_contexts = self.file.by_type("IfcGeometricRepresentationContext")
|
2022-09-03 22:16:35 +10:00
|
|
|
element = self.add_element(self.settings["element"])
|
2022-11-07 16:20:23 +11:00
|
|
|
self.reuse_existing_contexts()
|
2021-04-21 20:49:58 +10:00
|
|
|
return element
|
|
|
|
|
|
2022-01-18 17:44:50 +11:00
|
|
|
def append_product(self):
|
|
|
|
|
self.whitelisted_inverse_attributes = {
|
|
|
|
|
"IfcObjectDefinition": ["HasAssociations"],
|
|
|
|
|
"IfcObject": ["IsDefinedBy.IfcRelDefinesByProperties"],
|
|
|
|
|
"IfcElement": ["HasOpenings"],
|
2023-02-24 13:01:14 +11:00
|
|
|
"IfcMaterialDefinition": ["HasExternalReferences", "HasProperties", "HasRepresentation"],
|
2022-01-18 17:44:50 +11:00
|
|
|
"IfcRepresentationItem": ["StyledByItem"],
|
|
|
|
|
}
|
|
|
|
|
self.existing_contexts = self.file.by_type("IfcGeometricRepresentationContext")
|
2022-09-03 22:16:35 +10:00
|
|
|
element = self.add_element(self.settings["element"])
|
2022-11-07 16:20:23 +11:00
|
|
|
self.reuse_existing_contexts()
|
2022-01-18 17:44:50 +11:00
|
|
|
|
|
|
|
|
element_type = ifcopenshell.util.element.get_type(self.settings["element"])
|
|
|
|
|
if element_type:
|
|
|
|
|
ifcopenshell.api.owner.settings.factory_reset()
|
|
|
|
|
new_type = ifcopenshell.api.run(
|
|
|
|
|
"project.append_asset", self.file, library=self.settings["library"], element=element_type
|
|
|
|
|
)
|
|
|
|
|
ifcopenshell.api.run(
|
|
|
|
|
"type.assign_type",
|
|
|
|
|
self.file,
|
|
|
|
|
should_run_listeners=False,
|
|
|
|
|
related_object=element,
|
|
|
|
|
relating_type=new_type,
|
2023-08-09 00:13:16 +10:00
|
|
|
should_map_representations=False,
|
2022-01-18 17:44:50 +11:00
|
|
|
)
|
|
|
|
|
ifcopenshell.api.owner.settings.restore()
|
|
|
|
|
|
|
|
|
|
return element
|
|
|
|
|
|
2021-06-18 15:54:01 +10:00
|
|
|
def add_element(self, element):
|
2022-09-18 23:19:23 +10:00
|
|
|
if element.id() == 0:
|
2021-06-18 15:54:01 +10:00
|
|
|
return
|
2023-02-24 13:01:14 +11:00
|
|
|
existing_element = self.get_existing_element(element)
|
|
|
|
|
if existing_element:
|
|
|
|
|
return existing_element
|
2021-06-18 15:54:01 +10:00
|
|
|
new = self.file.add(element)
|
2022-09-18 23:19:23 +10:00
|
|
|
self.added_elements[element.id()] = new
|
2023-02-24 13:01:14 +11:00
|
|
|
self.check_inverses(element)
|
2023-03-24 17:35:10 +11:00
|
|
|
subelement_queue = self.settings["library"].traverse(element, max_levels=1)[1:]
|
|
|
|
|
while subelement_queue:
|
|
|
|
|
subelement = subelement_queue.pop(0)
|
2023-02-24 13:01:14 +11:00
|
|
|
existing_element = self.get_existing_element(subelement)
|
|
|
|
|
if existing_element:
|
|
|
|
|
self.added_elements[subelement.id()] = existing_element
|
|
|
|
|
if not self.has_whitelisted_inverses(existing_element):
|
|
|
|
|
self.check_inverses(subelement)
|
2023-03-24 17:35:10 +11:00
|
|
|
else:
|
|
|
|
|
self.added_elements[subelement.id()] = self.file.add(subelement)
|
|
|
|
|
self.check_inverses(subelement)
|
|
|
|
|
subelement_queue.extend(self.settings["library"].traverse(subelement, max_levels=1)[1:])
|
2021-06-18 15:54:01 +10:00
|
|
|
return new
|
|
|
|
|
|
2023-02-24 13:01:14 +11:00
|
|
|
def has_whitelisted_inverses(self, element):
|
|
|
|
|
for source_class, attributes in self.whitelisted_inverse_attributes.items():
|
|
|
|
|
if not element.is_a(source_class):
|
|
|
|
|
continue
|
|
|
|
|
for attribute in attributes:
|
|
|
|
|
attribute_class = None
|
|
|
|
|
if "." in attribute:
|
|
|
|
|
attribute, attribute_class = attribute.split(".")
|
|
|
|
|
value = getattr(element, attribute, [])
|
|
|
|
|
if attribute_class:
|
|
|
|
|
for subvalue in value:
|
|
|
|
|
if subvalue.is_a(attribute_class):
|
|
|
|
|
return True
|
|
|
|
|
elif value:
|
|
|
|
|
return True
|
|
|
|
|
|
2022-09-18 23:19:23 +10:00
|
|
|
def check_inverses(self, element):
|
2022-01-18 17:44:50 +11:00
|
|
|
for source_class, attributes in self.whitelisted_inverse_attributes.items():
|
|
|
|
|
if not element.is_a(source_class):
|
|
|
|
|
continue
|
|
|
|
|
for attribute in attributes:
|
|
|
|
|
attribute_class = None
|
|
|
|
|
if "." in attribute:
|
|
|
|
|
attribute, attribute_class = attribute.split(".")
|
|
|
|
|
for inverse in getattr(element, attribute, []):
|
|
|
|
|
if attribute_class and inverse.is_a(attribute_class):
|
2022-09-18 23:19:23 +10:00
|
|
|
self.add_inverse_element(inverse)
|
2022-01-18 17:44:50 +11:00
|
|
|
elif not attribute_class:
|
2022-09-18 23:19:23 +10:00
|
|
|
self.add_inverse_element(inverse)
|
|
|
|
|
|
|
|
|
|
def add_inverse_element(self, element):
|
|
|
|
|
# Inverse attributes are added manually because they are basically
|
|
|
|
|
# relationships that can reference many other assets that we are not
|
|
|
|
|
# interested in.
|
|
|
|
|
new = self.file.create_entity(element.is_a())
|
|
|
|
|
for i, attribute in enumerate(element):
|
|
|
|
|
new_attribute = None
|
|
|
|
|
if isinstance(attribute, ifcopenshell.entity_instance):
|
|
|
|
|
if not self.is_another_asset(attribute):
|
|
|
|
|
new_attribute = self.add_element(attribute)
|
|
|
|
|
elif isinstance(attribute, tuple) and attribute and isinstance(attribute[0], ifcopenshell.entity_instance):
|
|
|
|
|
new_attribute = []
|
|
|
|
|
for item in attribute:
|
|
|
|
|
if not self.is_another_asset(item):
|
|
|
|
|
new_attribute.append(self.add_element(item))
|
|
|
|
|
else:
|
|
|
|
|
new_attribute = attribute
|
|
|
|
|
if new_attribute is not None:
|
|
|
|
|
new[i] = new_attribute
|
|
|
|
|
|
|
|
|
|
def is_another_asset(self, element):
|
|
|
|
|
if element == self.settings["element"]:
|
|
|
|
|
return False
|
|
|
|
|
elif element.is_a("IfcFeatureElement"):
|
|
|
|
|
# Feature elements match the target class but aren't considered "assets"
|
|
|
|
|
return False
|
|
|
|
|
elif element.is_a(self.target_class):
|
|
|
|
|
return True
|
2023-03-24 20:53:55 +11:00
|
|
|
elif self.target_class == "IfcProduct" and element.is_a("IfcTypeProduct"):
|
|
|
|
|
return True
|
2024-02-14 14:15:02 +05:00
|
|
|
elif self.target_class == "IfcTypeProduct" and element.is_a("IfcProduct"):
|
|
|
|
|
return True
|
2022-09-18 23:19:23 +10:00
|
|
|
return False
|
2021-06-18 15:54:01 +10:00
|
|
|
|
2022-11-07 16:20:23 +11:00
|
|
|
def reuse_existing_contexts(self):
|
|
|
|
|
added_contexts = set([e for e in self.added_elements.values() if e.is_a("IfcGeometricRepresentationContext")])
|
2022-10-14 18:19:26 +11:00
|
|
|
for added_context in added_contexts:
|
|
|
|
|
equivalent_existing_context = self.get_equivalent_existing_context(added_context)
|
|
|
|
|
if not equivalent_existing_context:
|
|
|
|
|
equivalent_existing_context = self.create_equivalent_context(added_context)
|
|
|
|
|
for inverse in self.file.get_inverse(added_context):
|
|
|
|
|
ifcopenshell.util.element.replace_attribute(inverse, added_context, equivalent_existing_context)
|
|
|
|
|
for added_context in added_contexts:
|
|
|
|
|
ifcopenshell.util.element.remove_deep2(self.file, added_context)
|
|
|
|
|
|
2021-04-21 20:49:58 +10:00
|
|
|
def get_equivalent_existing_context(self, added_context):
|
|
|
|
|
for context in self.existing_contexts:
|
|
|
|
|
if context.is_a() != added_context.is_a():
|
|
|
|
|
continue
|
|
|
|
|
if context.is_a("IfcGeometricRepresentationSubContext"):
|
|
|
|
|
if (
|
|
|
|
|
context.ContextType == added_context.ContextType
|
|
|
|
|
and context.ContextIdentifier == added_context.ContextIdentifier
|
|
|
|
|
and context.TargetView == added_context.TargetView
|
|
|
|
|
):
|
|
|
|
|
return context
|
|
|
|
|
elif (
|
|
|
|
|
context.ContextType == added_context.ContextType
|
|
|
|
|
and context.ContextIdentifier == added_context.ContextIdentifier
|
|
|
|
|
):
|
|
|
|
|
return context
|
|
|
|
|
|
|
|
|
|
def create_equivalent_context(self, added_context):
|
|
|
|
|
if added_context.is_a("IfcGeometricRepresentationSubContext"):
|
2022-09-05 11:04:29 +10:00
|
|
|
parent = self.get_equivalent_existing_context(added_context.ParentContext)
|
|
|
|
|
if not parent:
|
|
|
|
|
parent = self.create_equivalent_context(added_context.ParentContext)
|
2021-04-21 20:49:58 +10:00
|
|
|
return ifcopenshell.api.run(
|
|
|
|
|
"context.add_context",
|
2022-09-05 11:04:29 +10:00
|
|
|
self.file,
|
|
|
|
|
parent=parent,
|
|
|
|
|
context_type=added_context.ContextType,
|
|
|
|
|
context_identifier=added_context.ContextIdentifier,
|
2021-04-21 20:49:58 +10:00
|
|
|
target_view=added_context.TargetView,
|
|
|
|
|
)
|
|
|
|
|
return ifcopenshell.api.run(
|
2022-11-07 16:20:23 +11:00
|
|
|
"context.add_context",
|
|
|
|
|
self.file,
|
|
|
|
|
context_type=added_context.ContextType,
|
|
|
|
|
context_identifier=added_context.ContextIdentifier,
|
2021-04-21 20:49:58 +10:00
|
|
|
)
|