First Commit
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
# 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/>.
|
||||
|
||||
"""Create an IFC project
|
||||
|
||||
All IFCs must have one, and only one IFC project before any data may be
|
||||
associated. If you are starting from scratch, see :func:create_file.
|
||||
|
||||
Once a project exists, you may optionally create project libraries and
|
||||
associate type assets with it. You may also append assets from other projects
|
||||
into your project.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .append_asset import append_asset
|
||||
from .assign_declaration import assign_declaration
|
||||
from .create_file import create_file
|
||||
from .unassign_declaration import unassign_declaration
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"append_asset",
|
||||
"assign_declaration",
|
||||
"create_file",
|
||||
"unassign_declaration",
|
||||
]
|
||||
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,826 @@
|
||||
# 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 collections.abc import Callable
|
||||
from functools import partial
|
||||
from typing import Any, Literal, Optional, Union, get_args
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.context
|
||||
import ifcopenshell.api.geometry
|
||||
import ifcopenshell.api.owner.settings
|
||||
import ifcopenshell.api.project
|
||||
import ifcopenshell.api.type
|
||||
import ifcopenshell.ifcopenshell_wrapper as W
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.geolocation
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.unit
|
||||
|
||||
APPENDABLE_ASSET = Literal[
|
||||
"IfcTypeProduct",
|
||||
"IfcProduct",
|
||||
"IfcMaterial",
|
||||
"IfcCostSchedule",
|
||||
"IfcProfileDef",
|
||||
"IfcPresentationStyle",
|
||||
]
|
||||
APPENDABLE_ASSET_TYPES: tuple[APPENDABLE_ASSET, ...] = get_args(APPENDABLE_ASSET)
|
||||
MATERIAL_SETS = ("IfcMaterialLayerSet", "IfcMaterialConstituentSet", "IfcMaterialProfileSet")
|
||||
|
||||
|
||||
def append_asset(
|
||||
file: ifcopenshell.file,
|
||||
library: ifcopenshell.file,
|
||||
element: ifcopenshell.entity_instance,
|
||||
reuse_identities: Optional[dict[int, ifcopenshell.entity_instance]] = None,
|
||||
assume_asset_uniqueness_by_name: bool = True,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""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.
|
||||
:param element: An element in the library file of the asset. It may be
|
||||
an IfcTypeProduct, IfcProduct, IfcMaterial, IfcCostSchedule, or
|
||||
IfcProfileDef.
|
||||
:param reuse_identities: Optional dictionary of mapped entities' identities to the
|
||||
already created elements. It will be used to avoid creating
|
||||
duplicated inverse elements during multiple `project.append_asset` calls. If you want
|
||||
to add just 1 asset or if added assets won't have any shared elements, then it can be left empty.
|
||||
:param assume_asset_uniqueness_by_name: If True, checks if elements (profiles, materials, styles)
|
||||
with the same name already exist in the project and reuses them instead of appending new ones.
|
||||
:return: The appended element
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Programmatically generate a library. You could do this visually too.
|
||||
library = ifcopenshell.api.project.create_file()
|
||||
root = ifcopenshell.api.root.create_entity(library, ifc_class="IfcProject", name="Demo Library")
|
||||
context = ifcopenshell.api.root.create_entity(library,
|
||||
ifc_class="IfcProjectLibrary", name="Demo Library")
|
||||
ifcopenshell.api.project.assign_declaration(library, definitions=[context], relating_context=root)
|
||||
|
||||
# Assign units for our example library
|
||||
unit = ifcopenshell.api.unit.add_si_unit(library,
|
||||
unit_type="LENGTHUNIT", prefix="MILLI")
|
||||
ifcopenshell.api.unit.assign_unit(library, units=[unit])
|
||||
|
||||
# Let's create a single asset of a 200mm thick concrete wall
|
||||
wall_type = ifcopenshell.api.root.create_entity(library, ifc_class="IfcWallType", name="WAL01")
|
||||
concrete = ifcopenshell.api.material.add_material(usecase.file, name="CON", category="concrete")
|
||||
rel = ifcopenshell.api.material.assign_material(library,
|
||||
products=[wall_type], type="IfcMaterialLayerSet")
|
||||
layer = ifcopenshell.api.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.project.assign_declaration(library,
|
||||
definitions=[wall_type], relating_context=context)
|
||||
|
||||
# Let's imagine we're starting a new project
|
||||
model = ifcopenshell.api.project.create_file()
|
||||
project = ifcopenshell.api.root.create_entity(model, ifc_class="IfcProject", name="Test")
|
||||
|
||||
# Now we can easily append our wall type from our library
|
||||
wall_type = ifcopenshell.api.project.append_asset(model, library=library, element=wall_type)
|
||||
|
||||
Example of adding multiple assets and avoiding duplicated inverses:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# since occurrences of IfcWindow of the same type
|
||||
# might have shared inverses (e.g. IfcStyledItem)
|
||||
# we provide a dictionary that will be populated with newly created items
|
||||
# and reused to avoid duplicated elements
|
||||
reuse_identities = dict()
|
||||
|
||||
for element in ifcopenshell.util.selector.filter_elements(model, "IfcWindow"):
|
||||
ifcopenshell.api.project.append_asset(
|
||||
model, library=library,
|
||||
element=wall_type
|
||||
reuse_identities=reuse_identities
|
||||
)
|
||||
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file: ifcopenshell.file = file
|
||||
usecase.settings = {
|
||||
"library": library,
|
||||
"element": element,
|
||||
"reuse_identities": {} if reuse_identities is None else reuse_identities,
|
||||
"assume_asset_uniqueness_by_name": assume_asset_uniqueness_by_name,
|
||||
}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class SafeRemovalContext:
|
||||
"""Context manager to ensure `remove_deep` won't create invalid entities
|
||||
in `reuse_identities` leading to possible crashes.
|
||||
|
||||
Should be always used if removing an entity that was possibly added by `file_add`.
|
||||
"""
|
||||
|
||||
file: ifcopenshell.file
|
||||
reuse_identities: dict[int, ifcopenshell.entity_instance]
|
||||
|
||||
assume_asset_uniqueness_by_name: bool
|
||||
"""If `False`, then all job is done by `file.add`
|
||||
and we don't need to worry about invalid entities."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
ifc_file: ifcopenshell.file,
|
||||
reuse_identities: dict[int, ifcopenshell.entity_instance],
|
||||
assume_asset_uniqueness_by_name: bool,
|
||||
):
|
||||
self.file = ifc_file
|
||||
self.reuse_identities = reuse_identities
|
||||
self.assume_asset_uniqueness_by_name = assume_asset_uniqueness_by_name
|
||||
|
||||
def __enter__(self):
|
||||
if not self.assume_asset_uniqueness_by_name:
|
||||
return
|
||||
|
||||
ifcopenshell.util.element.batch_remove_deep2(self.file)
|
||||
|
||||
def __exit__(self, *args):
|
||||
if not self.assume_asset_uniqueness_by_name:
|
||||
return
|
||||
|
||||
# Collect identities.
|
||||
removed_identities: dict[ifcopenshell.entity_instance, int] = {}
|
||||
assert self.file.to_delete is not None
|
||||
removed_elements = self.file.to_delete
|
||||
for identity, element in self.reuse_identities.items():
|
||||
if element in removed_elements:
|
||||
removed_identities[element] = identity
|
||||
assert len(removed_identities) == len(removed_elements)
|
||||
|
||||
# Actually remove elements.
|
||||
for element in self.file.to_delete:
|
||||
if element in self.file.to_delete:
|
||||
self.file.remove(element)
|
||||
self.file.to_delete = None
|
||||
|
||||
# Clean up dead identities.
|
||||
for identity in removed_identities.values():
|
||||
del self.reuse_identities[identity]
|
||||
|
||||
|
||||
class Usecase:
|
||||
file: ifcopenshell.file
|
||||
settings: dict[str, Any]
|
||||
assume_asset_uniqueness_by_name: bool
|
||||
whitelisted_inverse_attributes: dict[str, list[str]]
|
||||
|
||||
added_elements: dict[int, ifcopenshell.entity_instance]
|
||||
"""Elements added with ``add_element``."""
|
||||
|
||||
reuse_identities: dict[int, ifcopenshell.entity_instance]
|
||||
"""Mapping of old element ids to new elements, usually fiiled by ``file_add``."""
|
||||
|
||||
def execute(self):
|
||||
# mapping of old element ids to new elements
|
||||
self.added_elements: dict[int, ifcopenshell.entity_instance] = {}
|
||||
self.reuse_identities: dict[int, ifcopenshell.entity_instance] = self.settings["reuse_identities"]
|
||||
self.whitelisted_inverse_attributes = {}
|
||||
self.base_material_class = "IfcMaterial" if self.file.schema == "IFC2X3" else "IfcMaterialDefinition"
|
||||
self.assume_asset_uniqueness_by_name = self.settings["assume_asset_uniqueness_by_name"]
|
||||
|
||||
if self.settings["element"].is_a("IfcTypeProduct"):
|
||||
self.target_class = "IfcTypeProduct"
|
||||
return self.append_type_product()
|
||||
elif self.settings["element"].is_a("IfcProduct"):
|
||||
self.target_class = "IfcProduct"
|
||||
return self.append_product()
|
||||
elif self.settings["element"].is_a("IfcMaterial"):
|
||||
self.target_class = "IfcMaterial"
|
||||
return self.append_material()
|
||||
elif self.settings["element"].is_a("IfcCostSchedule"):
|
||||
self.target_class = "IfcCostSchedule"
|
||||
return self.append_cost_schedule()
|
||||
elif self.settings["element"].is_a("IfcProfileDef"):
|
||||
self.target_class = "IfcProfileDef"
|
||||
return self.append_profile_def()
|
||||
elif self.settings["element"].is_a("IfcPresentationStyle"):
|
||||
self.target_class = "IfcPresentationStyle"
|
||||
return self.append_presentation_style()
|
||||
|
||||
def by_guid(self, guid: str) -> Union[ifcopenshell.entity_instance, None]:
|
||||
try:
|
||||
return self.file.by_guid(guid)
|
||||
except RuntimeError:
|
||||
return None
|
||||
|
||||
def material_sets_are_equal(self, set1: ifcopenshell.entity_instance, set2: ifcopenshell.entity_instance) -> bool:
|
||||
"""Check if two material sets are structurally equivalent."""
|
||||
if set1.is_a() != set2.is_a():
|
||||
return False
|
||||
|
||||
ifc_class = set1.is_a()
|
||||
|
||||
if ifc_class == "IfcMaterialLayerSet":
|
||||
layers1 = set1.MaterialLayers or []
|
||||
layers2 = set2.MaterialLayers or []
|
||||
if len(layers1) != len(layers2):
|
||||
return False
|
||||
for l1, l2 in zip(layers1, layers2):
|
||||
if (l1.Material is None) != (l2.Material is None):
|
||||
return False
|
||||
if l1.Material and l1.Material.Name != l2.Material.Name:
|
||||
return False
|
||||
if l1.LayerThickness != l2.LayerThickness:
|
||||
return False
|
||||
|
||||
elif ifc_class == "IfcMaterialConstituentSet":
|
||||
constituents1 = set1.MaterialConstituents or []
|
||||
constituents2 = set2.MaterialConstituents or []
|
||||
if len(constituents1) != len(constituents2):
|
||||
return False
|
||||
for c1, c2 in zip(constituents1, constituents2):
|
||||
if (c1.Material is None) != (c2.Material is None):
|
||||
return False
|
||||
if c1.Material and c1.Material.Name != c2.Material.Name:
|
||||
return False
|
||||
if c1.Name != c2.Name:
|
||||
return False
|
||||
|
||||
elif ifc_class == "IfcMaterialProfileSet":
|
||||
profiles1 = set1.MaterialProfiles or []
|
||||
profiles2 = set2.MaterialProfiles or []
|
||||
if len(profiles1) != len(profiles2):
|
||||
return False
|
||||
for p1, p2 in zip(profiles1, profiles2):
|
||||
if (p1.Material is None) != (p2.Material is None):
|
||||
return False
|
||||
if p1.Material and p1.Material.Name != p2.Material.Name:
|
||||
return False
|
||||
if (p1.Profile is None) != (p2.Profile is None):
|
||||
return False
|
||||
if p1.Profile:
|
||||
profile_name1 = getattr(p1.Profile, "ProfileName", None)
|
||||
profile_name2 = getattr(p2.Profile, "ProfileName", None)
|
||||
if profile_name1 != profile_name2:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def get_existing_element(self, element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Get existing element for a library element.
|
||||
|
||||
Return element if it was already added with ``add_element``
|
||||
or if it's not necessary (model already has a replacement for it).
|
||||
|
||||
Note that if element is returned, it will be accepted as-is,
|
||||
it's subgraph inverses won't be checked.
|
||||
|
||||
Return ``None`` if element wasn't added before and needs to be added.
|
||||
"""
|
||||
if element.id() in self.added_elements:
|
||||
return self.added_elements[element.id()]
|
||||
if element.is_a("IfcRoot"):
|
||||
return self.by_guid(element.GlobalId)
|
||||
elif not self.assume_asset_uniqueness_by_name:
|
||||
return None
|
||||
elif element.is_a("IfcMaterial"):
|
||||
name = element.Name
|
||||
return next((e for e in self.file.by_type("IfcMaterial") if e.Name == name), None)
|
||||
|
||||
elif element.is_a() in MATERIAL_SETS:
|
||||
ifc_class = element.is_a()
|
||||
name_attr = "LayerSetName" if ifc_class == "IfcMaterialLayerSet" else "Name"
|
||||
material_set_name = getattr(element, name_attr)
|
||||
if material_set_name is None:
|
||||
return
|
||||
for candidate in self.file.by_type(ifc_class):
|
||||
if getattr(candidate, name_attr) == material_set_name:
|
||||
if self.material_sets_are_equal(element, candidate):
|
||||
return candidate
|
||||
return None
|
||||
|
||||
elif element.is_a("IfcProfileDef"):
|
||||
profile_name = element.ProfileName
|
||||
if profile_name is None:
|
||||
return None
|
||||
return next((e for e in self.file.by_type("IfcProfileDef") if e.ProfileName == profile_name), None)
|
||||
elif element.is_a("IfcPresentationStyle"):
|
||||
name = element.Name
|
||||
if name is None:
|
||||
return None
|
||||
return next((e for e in self.file.by_type(element.is_a()) if e.Name == name), None)
|
||||
|
||||
# Not really assets but if we don't check them here,
|
||||
# their subgraph entities may be appended twice.
|
||||
elif (ifc_class := element.is_a()) == "IfcOrganization":
|
||||
attr_name = "Id" if self.file.schema == "IFC2X3" else "Identification"
|
||||
org_id = getattr(element, attr_name)
|
||||
if org_id is not None:
|
||||
return next((e for e in self.file.by_type("IfcOrganization") if getattr(e, attr_name) == org_id), None)
|
||||
elif ifc_class == "IfcPerson":
|
||||
attr_name = "Id" if self.file.schema == "IFC2X3" else "Identification"
|
||||
person_id = getattr(element, attr_name)
|
||||
if person_id is not None:
|
||||
return next((e for e in self.file.by_type("IfcPerson") if getattr(e, attr_name) == person_id), None)
|
||||
|
||||
else:
|
||||
return None
|
||||
|
||||
def append_material(self):
|
||||
self.whitelisted_inverse_attributes = {
|
||||
"IfcMaterial": ["HasExternalReferences", "HasProperties", "HasRepresentation"]
|
||||
}
|
||||
self.existing_contexts = self.file.by_type("IfcGeometricRepresentationContext")
|
||||
element = self.add_element(self.settings["element"])
|
||||
if element.HasRepresentation:
|
||||
self.reuse_existing_contexts()
|
||||
return element
|
||||
|
||||
def append_cost_schedule(self):
|
||||
self.whitelisted_inverse_attributes = {"IfcCostSchedule": ["Controls"], "IfcCostItem": ["IsNestedBy"]}
|
||||
return self.add_element(self.settings["element"])
|
||||
|
||||
def append_profile_def(self):
|
||||
self.whitelisted_inverse_attributes = {"IfcProfileDef": ["HasProperties"]}
|
||||
return self.add_element(self.settings["element"])
|
||||
|
||||
def append_presentation_style(self):
|
||||
self.whitelisted_inverse_attributes = {}
|
||||
return self.add_element(self.settings["element"])
|
||||
|
||||
def append_type_product(self):
|
||||
self.whitelisted_inverse_attributes = {
|
||||
"IfcObjectDefinition": ["HasAssociations"],
|
||||
"IfcDistributionElementType": ["IsNestedBy"],
|
||||
self.base_material_class: ["HasExternalReferences", "HasProperties", "HasRepresentation"],
|
||||
"IfcRepresentationItem": ["StyledByItem", "LayerAssignment"],
|
||||
"IfcRepresentation": ["LayerAssignments"],
|
||||
"IfcProductDefinitionShape": ["HasShapeAspects"],
|
||||
"IfcRepresentationMap": ["HasShapeAspects"],
|
||||
}
|
||||
self.existing_contexts = self.file.by_type("IfcGeometricRepresentationContext")
|
||||
element = self.add_element(self.settings["element"])
|
||||
self.reuse_existing_contexts()
|
||||
return element
|
||||
|
||||
def append_product(self):
|
||||
self.whitelisted_inverse_attributes = {
|
||||
"IfcObjectDefinition": ["HasAssociations"],
|
||||
"IfcObject": ["IsDefinedBy.IfcRelDefinesByProperties"],
|
||||
"IfcElement": ["HasOpenings"],
|
||||
"IfcDistributionElement": ["IsNestedBy"],
|
||||
self.base_material_class: ["HasExternalReferences", "HasProperties", "HasRepresentation"],
|
||||
"IfcRepresentationItem": [
|
||||
"StyledByItem",
|
||||
"LayerAssignments" if self.file.schema == "IFC2X3" else "LayerAssignment",
|
||||
],
|
||||
"IfcRepresentation": ["LayerAssignments"],
|
||||
"IfcProductDefinitionShape": ["HasShapeAspects"],
|
||||
"IfcRepresentationMap": ["HasShapeAspects"],
|
||||
}
|
||||
self.existing_contexts = self.file.by_type("IfcGeometricRepresentationContext")
|
||||
element = self.add_element(self.settings["element"])
|
||||
self.reuse_existing_contexts()
|
||||
|
||||
placement = element.ObjectPlacement
|
||||
if placement is not None:
|
||||
matrix = ifcopenshell.util.placement.get_local_placement(placement)
|
||||
matrix = ifcopenshell.util.geolocation.auto_local2global(self.settings["library"], matrix)
|
||||
matrix = ifcopenshell.util.geolocation.auto_global2local(self.file, matrix)
|
||||
with SafeRemovalContext(self.file, self.reuse_identities, self.assume_asset_uniqueness_by_name):
|
||||
ifcopenshell.api.geometry.edit_object_placement(self.file, element, matrix, is_si=False)
|
||||
|
||||
element_type = ifcopenshell.util.element.get_type(self.settings["element"])
|
||||
if element_type:
|
||||
ifcopenshell.api.owner.settings.factory_reset()
|
||||
new_type = ifcopenshell.api.project.append_asset(
|
||||
self.file,
|
||||
library=self.settings["library"],
|
||||
element=element_type,
|
||||
reuse_identities=self.reuse_identities,
|
||||
)
|
||||
ifcopenshell.api.type.assign_type(
|
||||
self.file,
|
||||
should_run_listeners=False, # ty:ignore[unknown-argument]
|
||||
related_objects=[element],
|
||||
relating_type=new_type,
|
||||
should_map_representations=False,
|
||||
)
|
||||
ifcopenshell.api.owner.settings.restore()
|
||||
|
||||
return element
|
||||
|
||||
def add_element(self, element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Add element and check all it's subgraph inverses."""
|
||||
if element.id() == 0:
|
||||
return
|
||||
existing_element = self.get_existing_element(element)
|
||||
if existing_element:
|
||||
return existing_element
|
||||
new = self.file_add(element)
|
||||
self.added_elements[element.id()] = new
|
||||
self.check_inverses(element)
|
||||
subelement_queue = self.settings["library"].traverse(element, max_levels=1)[1:]
|
||||
while subelement_queue:
|
||||
subelement = subelement_queue.pop(0)
|
||||
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)
|
||||
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:])
|
||||
return new
|
||||
|
||||
def has_whitelisted_inverses(self, element: ifcopenshell.entity_instance) -> bool:
|
||||
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
|
||||
return False
|
||||
|
||||
def check_inverses(self, element: ifcopenshell.entity_instance) -> None:
|
||||
"""Add inverse elements for the whitelisted inverse attributes."""
|
||||
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):
|
||||
self.add_inverse_element(inverse)
|
||||
elif not attribute_class:
|
||||
self.add_inverse_element(inverse)
|
||||
|
||||
def add_inverse_element(self, element: ifcopenshell.entity_instance) -> None:
|
||||
"""Add inverse element.
|
||||
|
||||
Inverse elements are requiring different method than ``file_add``
|
||||
because they can reference many other assets that we are not
|
||||
interested in.
|
||||
|
||||
E.g. a IfcRelAssociatesMaterial referencing products unrelated
|
||||
to the current asset.
|
||||
"""
|
||||
# For layer assignment we don't want to add it's items
|
||||
# to avoid adding representations / items that are not related to current append_asset.
|
||||
skip_not_reused_entities_attr_i = None
|
||||
if element.is_a("IfcPresentationLayerAssignment"):
|
||||
# 3 IfcPresentationLayerAssignment.AssignedItems
|
||||
skip_not_reused_entities_attr_i = 2
|
||||
|
||||
element_identity = element.wrapped_data.identity()
|
||||
|
||||
# Check if inverse element was created before.
|
||||
# Still need to recreate it again - e.g. it could be some rel
|
||||
# that now needs it's RelatingObjects to be extended by the current asset.
|
||||
existing_rel = None
|
||||
if (new := self.reuse_identities.get(element_identity)) is not None:
|
||||
# Currently known cases requiring attributes reassignment are rels.
|
||||
if not new.is_a("IfcRelationship"):
|
||||
return
|
||||
elif element.is_a("IfcRelationship") and (existing_rel := self.by_guid(element.GlobalId)):
|
||||
new = existing_rel
|
||||
else:
|
||||
new = self.file.create_entity(element.is_a())
|
||||
self.reuse_identities[element_identity] = new
|
||||
|
||||
for i, attribute in enumerate(element):
|
||||
new_attribute = None
|
||||
if isinstance(attribute, ifcopenshell.entity_instance):
|
||||
# Void and projection relationships are special because they
|
||||
# are "dependent" relationships, so we always consider them.
|
||||
# We do _not_ whitelist (i.e. in is_another_asset)
|
||||
# IfcFeatureElement because you can have things like
|
||||
# IfcRelAssociatesClassification to openings! We only ever want
|
||||
# to consider IfcFeatureElements in IfcRelVoidsElements and
|
||||
# IfcRelProjectsElements.
|
||||
if element.is_a() in ("IfcRelVoidsElement", "IfcRelProjectsElement") or 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 self.is_another_asset(item):
|
||||
continue
|
||||
if skip_not_reused_entities_attr_i is not None and i == skip_not_reused_entities_attr_i:
|
||||
identity = item.wrapped_data.identity()
|
||||
if (item := self.reuse_identities.get(identity)) is None:
|
||||
continue
|
||||
else:
|
||||
item = self.add_element(item)
|
||||
new_attribute.append(item)
|
||||
# If rel exists we need to make sure previously assigned elements are untouched
|
||||
# e.g. not to assign a material or a pset from element.
|
||||
if existing_rel:
|
||||
new_attribute.extend(existing_rel[i])
|
||||
new_attribute = list(set(new_attribute))
|
||||
else:
|
||||
new_attribute = attribute
|
||||
if new_attribute is not None:
|
||||
new[i] = new_attribute
|
||||
|
||||
def is_another_asset(self, element: ifcopenshell.entity_instance) -> bool:
|
||||
"""Is IFC entity from inverse attribute is another asset to append that should be skipped."""
|
||||
if element == self.settings["element"]:
|
||||
return False
|
||||
elif element.is_a("IfcRoot") and self.by_guid(element.GlobalId) is not None:
|
||||
return False
|
||||
elif element.is_a("IfcDistributionPort"):
|
||||
return False
|
||||
elif element.is_a(self.target_class):
|
||||
return True
|
||||
elif self.target_class == "IfcProduct" and element.is_a("IfcTypeProduct"):
|
||||
return True
|
||||
elif self.target_class == "IfcTypeProduct" and element.is_a("IfcProduct"):
|
||||
return True
|
||||
return False
|
||||
|
||||
def reuse_existing_contexts(self) -> None:
|
||||
added_contexts = set([e for e in self.added_elements.values() if e.is_a("IfcGeometricRepresentationContext")])
|
||||
added_contexts -= set(self.existing_contexts)
|
||||
sorted_added_contexts = [c for c in added_contexts if c.is_a() == "IfcGeometricRepresentationContext"]
|
||||
sorted_added_contexts.extend([c for c in added_contexts if c.is_a() == "IfcGeometricRepresentationSubContext"])
|
||||
for added_context in sorted_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)
|
||||
|
||||
with SafeRemovalContext(self.file, self.reuse_identities, self.assume_asset_uniqueness_by_name):
|
||||
for added_context in added_contexts:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, added_context)
|
||||
|
||||
def get_equivalent_existing_context(
|
||||
self, added_context: ifcopenshell.entity_instance
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
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: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
if added_context.is_a("IfcGeometricRepresentationSubContext"):
|
||||
parent = self.get_equivalent_existing_context(added_context.ParentContext)
|
||||
if not parent:
|
||||
parent = self.create_equivalent_context(added_context.ParentContext)
|
||||
self.existing_contexts.append(parent)
|
||||
context = ifcopenshell.api.context.add_context(
|
||||
self.file,
|
||||
parent=parent,
|
||||
context_type=added_context.ContextType,
|
||||
context_identifier=added_context.ContextIdentifier,
|
||||
target_view=added_context.TargetView,
|
||||
)
|
||||
else:
|
||||
context = ifcopenshell.api.context.add_context(
|
||||
self.file,
|
||||
context_type=added_context.ContextType,
|
||||
context_identifier=added_context.ContextIdentifier,
|
||||
)
|
||||
self.existing_contexts.append(context)
|
||||
return context
|
||||
|
||||
def file_add(
|
||||
self, element: ifcopenshell.entity_instance, conversion_factor: Optional[float] = None
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Reimplementation of `file.add` but taking into account that some elements (profiles, materials)
|
||||
are already existing (checking by their name) and shouldn't be duplicated.
|
||||
|
||||
The problem with `file.add` it's recursively adding element and all it's attributes
|
||||
and there is no control to prevent it from adding certain type of elements.
|
||||
"""
|
||||
|
||||
def get_conversion_factor() -> float:
|
||||
nonlocal conversion_factor
|
||||
if conversion_factor is not None:
|
||||
return conversion_factor
|
||||
library_scale = ifcopenshell.util.unit.calculate_unit_scale(self.settings["library"])
|
||||
current_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
conversion_factor = library_scale / current_scale
|
||||
return conversion_factor
|
||||
|
||||
ifc_file = self.file
|
||||
if not self.assume_asset_uniqueness_by_name or element.id() == 0:
|
||||
# file.add doesn't convert units for IfcLengthMeasure entities.
|
||||
if element.is_a("IfcLengthMeasure"):
|
||||
return ifc_file.create_entity(element.is_a(), element.wrappedValue * get_conversion_factor())
|
||||
return ifc_file.add(element)
|
||||
|
||||
reuse_identities = self.reuse_identities
|
||||
element_identity = element.wrapped_data.identity()
|
||||
if added_element := reuse_identities.get(element_identity):
|
||||
return added_element
|
||||
|
||||
ifc_class = element.is_a()
|
||||
attributes_ = None
|
||||
|
||||
def get_attributes() -> tuple[W.attribute, ...]:
|
||||
nonlocal attributes_
|
||||
if attributes_ is not None:
|
||||
return attributes_
|
||||
attributes_ = element.wrapped_data.declaration().as_entity().all_attributes()
|
||||
return attributes_
|
||||
|
||||
def get_existing_element_(
|
||||
subelement: ifcopenshell.entity_instance,
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
# Check identity because `subelement` might not be the current `element`,
|
||||
# e.g. for IfcPersonAndOrganization.
|
||||
element_identity = subelement.wrapped_data.identity()
|
||||
if subelement_ := reuse_identities.get(element_identity):
|
||||
return subelement_
|
||||
|
||||
ifc_class = subelement.is_a()
|
||||
assert ifc_class in ("IfcOrganization", "IfcPerson")
|
||||
attr_name = "Id" if ifc_file.schema == "IFC2X3" else "Identification"
|
||||
subelement_id = getattr(subelement, attr_name)
|
||||
|
||||
if subelement_id is not None:
|
||||
existing_org = next(
|
||||
(e for e in ifc_file.by_type(ifc_class) if getattr(e, attr_name) == subelement_id), None
|
||||
)
|
||||
if existing_org is not None:
|
||||
reuse_identities[element_identity] = existing_org
|
||||
return existing_org
|
||||
|
||||
# Check if element already exists.
|
||||
# NOTE: Ensure this part is in sync with `get_existing_element`,
|
||||
# if some class is present here but not in `get_existing_element`,
|
||||
# then it might create duplicated subelements.
|
||||
if element.is_a("IfcProfileDef"):
|
||||
profile_name = element.ProfileName
|
||||
if profile_name is not None:
|
||||
existing_profile = next(
|
||||
(e for e in ifc_file.by_type("IfcProfileDef") if e.ProfileName == profile_name), None
|
||||
)
|
||||
if existing_profile is not None:
|
||||
reuse_identities[element_identity] = existing_profile
|
||||
return existing_profile
|
||||
|
||||
elif element.is_a("IfcMaterial"):
|
||||
material_name = element.Name
|
||||
existing_material = next((e for e in ifc_file.by_type("IfcMaterial") if e.Name == material_name), None)
|
||||
if existing_material is not None:
|
||||
reuse_identities[element_identity] = existing_material
|
||||
return existing_material
|
||||
|
||||
elif ifc_class in MATERIAL_SETS:
|
||||
name_attr = "LayerSetName" if ifc_class == "IfcMaterialLayerSet" else "Name"
|
||||
material_set_name = getattr(element, name_attr)
|
||||
if material_set_name is not None:
|
||||
for candidate in ifc_file.by_type(ifc_class):
|
||||
if getattr(candidate, name_attr) == material_set_name:
|
||||
if self.material_sets_are_equal(element, candidate):
|
||||
reuse_identities[element_identity] = candidate
|
||||
return candidate
|
||||
|
||||
elif element.is_a("IfcPresentationStyle"):
|
||||
style_name = element.Name
|
||||
if style_name is not None:
|
||||
existing_style = next((e for e in ifc_file.by_type(ifc_class) if e.Name == style_name), None)
|
||||
if existing_style is not None:
|
||||
reuse_identities[element_identity] = existing_style
|
||||
return existing_style
|
||||
|
||||
elif ifc_class == "IfcApplication":
|
||||
app_id = element.ApplicationIdentifier
|
||||
if app_id is not None:
|
||||
existing_app = next(
|
||||
(e for e in ifc_file.by_type("IfcApplication") if e.ApplicationIdentifier == app_id), None
|
||||
)
|
||||
if existing_app is not None:
|
||||
reuse_identities[element_identity] = existing_app
|
||||
return existing_app
|
||||
|
||||
elif ifc_class == "IfcOrganization":
|
||||
existing_org = get_existing_element_(element)
|
||||
if existing_org is not None:
|
||||
reuse_identities[element_identity] = existing_org
|
||||
return existing_org
|
||||
|
||||
elif ifc_class == "IfcPerson":
|
||||
existing_person = get_existing_element_(element)
|
||||
if existing_person is not None:
|
||||
reuse_identities[element_identity] = existing_person
|
||||
return existing_person
|
||||
|
||||
elif ifc_class == "IfcPersonAndOrganization":
|
||||
if (person := get_existing_element_(element.ThePerson)) and (
|
||||
org := get_existing_element_(element.TheOrganization)
|
||||
):
|
||||
for pao in ifc_file.by_type("IfcPersonAndOrganization"):
|
||||
if pao.ThePerson == person and pao.TheOrganization == org:
|
||||
reuse_identities[element_identity] = pao
|
||||
return pao
|
||||
|
||||
attrs: dict[int, Any] = {}
|
||||
|
||||
# Utils method for the loop.
|
||||
def get_tuple_type(tuple_: tuple) -> type:
|
||||
while isinstance(tuple_, tuple):
|
||||
tuple_ = tuple_[0]
|
||||
return type(tuple_)
|
||||
|
||||
def is_length_measure(attribute: W.attribute) -> bool:
|
||||
return "<type IfcLengthMeasure: <real>>" in str(attribute.type_of_attribute())
|
||||
|
||||
def apply_to_array(arr: Any, func: Callable[[Any], Any]) -> Any:
|
||||
if isinstance(arr, tuple):
|
||||
return tuple(apply_to_array(sub, func) for sub in arr)
|
||||
return func(arr)
|
||||
|
||||
file_add_ = partial(self.file_add, conversion_factor=conversion_factor)
|
||||
apply_conversion = lambda x: x * conversion_factor
|
||||
|
||||
# Migrate attributes to another file.
|
||||
for attr_index, attr_value in enumerate(element):
|
||||
# `None` is set by default already.
|
||||
if attr_value is None:
|
||||
continue
|
||||
|
||||
elif isinstance(attr_value, ifcopenshell.entity_instance):
|
||||
attr_value = file_add_(attr_value)
|
||||
|
||||
elif isinstance(attr_value, tuple):
|
||||
# Assume type is consistent across the tuple.
|
||||
tuple_type = get_tuple_type(attr_value)
|
||||
if tuple_type == ifcopenshell.entity_instance:
|
||||
attr_value = apply_to_array(attr_value, file_add_)
|
||||
elif tuple_type == float:
|
||||
attributes = get_attributes()
|
||||
if is_length_measure(attributes[attr_index]):
|
||||
get_conversion_factor() # Ensure conversion factor is not None.
|
||||
attr_value = apply_to_array(attr_value, apply_conversion)
|
||||
|
||||
elif isinstance(attr_value, float):
|
||||
attributes = get_attributes()
|
||||
if is_length_measure(attributes[attr_index]):
|
||||
attr_value *= get_conversion_factor()
|
||||
|
||||
attrs[attr_index] = attr_value
|
||||
|
||||
# Adding entity at the end just to keep it consistent with `file.add`.
|
||||
new = ifc_file.create_entity(ifc_class)
|
||||
reuse_identities[element_identity] = new
|
||||
for attr_index, attr_value in attrs.items():
|
||||
new[attr_index] = attr_value
|
||||
|
||||
return new
|
||||
@@ -0,0 +1,142 @@
|
||||
# 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.owner
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def assign_declaration(
|
||||
file: ifcopenshell.file,
|
||||
definitions: list[ifcopenshell.entity_instance],
|
||||
relating_context: ifcopenshell.entity_instance,
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Declares the list of elements to the project
|
||||
|
||||
Feature was added in IFC4.
|
||||
|
||||
All data in a model must be directly or indirectly related to the
|
||||
project. Most data is indirectly related, existing instead within the
|
||||
spatial decomposition tree. Other data, such as types, may be declared
|
||||
at the top level.
|
||||
|
||||
Most of the time, the API handles declaration automatically for you.
|
||||
There is one scenario where you might want to explicitly declare objects
|
||||
to the project, and that's when you want to organise objects into
|
||||
project libraries for future use (such as an assets library). Assigning
|
||||
a declaration lets you say that an object belongs to a library.
|
||||
|
||||
:param definitions: The list of objects you want to declare. Typically a list of assets.
|
||||
:param relating_context: The IfcProject, or more commonly the
|
||||
IfcProjectLibrary that you want the object to be part of.
|
||||
:return: The new IfcRelDeclares relationship or None if all definitions
|
||||
were already declared / do not support declaration.
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Programmatically generate a library. You could do this visually too.
|
||||
library = ifcopenshell.api.project.create_file()
|
||||
root = ifcopenshell.api.root.create_entity(library, ifc_class="IfcProject", name="Demo Library")
|
||||
context = ifcopenshell.api.root.create_entity(library,
|
||||
ifc_class="IfcProjectLibrary", name="Demo Library")
|
||||
|
||||
# It's necessary to say our library is part of our project.
|
||||
ifcopenshell.api.project.assign_declaration(library, definitions=[context], relating_context=root)
|
||||
|
||||
# Assign units for our example library
|
||||
unit = ifcopenshell.api.unit.add_si_unit(library,
|
||||
unit_type="LENGTHUNIT", prefix="MILLI")
|
||||
ifcopenshell.api.unit.assign_unit(library, units=[unit])
|
||||
|
||||
# Let's create a single asset of a 200mm thick concrete wall
|
||||
wall_type = ifcopenshell.api.root.create_entity(library, ifc_class="IfcWallType", name="WAL01")
|
||||
concrete = ifcopenshell.api.material.add_material(file, name="CON", category="concrete")
|
||||
rel = ifcopenshell.api.material.assign_material(library,
|
||||
products=[wall_type], type="IfcMaterialLayerSet")
|
||||
layer = ifcopenshell.api.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.project.assign_declaration(library,
|
||||
definitions=[wall_type], relating_context=context)
|
||||
|
||||
# All done, just for fun let's save our asset library to disk for later use.
|
||||
library.write("/path/to/my-library.ifc")
|
||||
"""
|
||||
|
||||
all_declares = relating_context.Declares
|
||||
definitions_set = set(definitions)
|
||||
|
||||
previous_declares_rels: set[ifcopenshell.entity_instance] = set()
|
||||
objects_without_contexts: list[ifcopenshell.entity_instance] = []
|
||||
objects_with_contexts: list[ifcopenshell.entity_instance] = []
|
||||
|
||||
# check if there is anything to change
|
||||
for definition in definitions_set:
|
||||
has_context = getattr(definition, "HasContext", None)
|
||||
if has_context is None:
|
||||
continue
|
||||
|
||||
object_rel = next(iter(has_context), None)
|
||||
if object_rel is None:
|
||||
objects_without_contexts.append(definition)
|
||||
continue
|
||||
|
||||
# either rel doesn't exist or product is part of different rel
|
||||
if object_rel not in all_declares:
|
||||
previous_declares_rels.add(object_rel)
|
||||
objects_with_contexts.append(definition)
|
||||
|
||||
objects_to_change = objects_without_contexts + objects_with_contexts
|
||||
# nothing to change
|
||||
if not objects_to_change:
|
||||
return None
|
||||
|
||||
for has_context in previous_declares_rels:
|
||||
related_definitions = set(has_context.RelatedDefinitions) - set(objects_with_contexts)
|
||||
if related_definitions:
|
||||
has_context.RelatedDefinitions = list(related_definitions)
|
||||
ifcopenshell.api.owner.update_owner_history(file, element=has_context)
|
||||
else:
|
||||
history = has_context.OwnerHistory
|
||||
file.remove(has_context)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
|
||||
declares = next(iter(all_declares), None)
|
||||
if declares:
|
||||
declares.RelatedDefinitions = list(set(declares.RelatedDefinitions) | set(objects_to_change))
|
||||
ifcopenshell.api.owner.update_owner_history(file, element=declares)
|
||||
else:
|
||||
declares = file.create_entity(
|
||||
"IfcRelDeclares",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(file),
|
||||
"RelatedDefinitions": list(objects_to_change),
|
||||
"RelatingContext": relating_context,
|
||||
},
|
||||
)
|
||||
return declares
|
||||
@@ -0,0 +1,61 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021, 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/>.
|
||||
|
||||
import datetime
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.schema
|
||||
|
||||
|
||||
def create_file(version: ifcopenshell.util.schema.IFC_SCHEMA = "IFC4") -> ifcopenshell.file:
|
||||
"""Create a blank IFC model file object
|
||||
|
||||
Create a new IFC file object based on the nominated schema version. The
|
||||
schema version you choose determines what type of IFC data you can store
|
||||
in this model. The file is blank and contains no entities.
|
||||
|
||||
It also sets up header data for STEP file serialisation, such as the
|
||||
current timestamp, IfcOpenShell as the preprocessor, and defaults to a
|
||||
DesignTransferView MVD.
|
||||
|
||||
:param version: The schema version of the IFC file. Choose from
|
||||
"IFC2X3", "IFC4", or "IFC4X3". If you have loaded in a custom
|
||||
schema, you may specify that schema identifier here too.
|
||||
:return: The created IFC file object.
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Start a new model.
|
||||
model = ifcopenshell.api.project.create_file()
|
||||
|
||||
# It's currently a blank model, so typically the first thing we do
|
||||
# is create a project in it.
|
||||
project = ifcopenshell.api.root.create_entity(model, ifc_class="IfcProject", name="Test")
|
||||
|
||||
# ... and off we go!
|
||||
"""
|
||||
file = ifcopenshell.file(schema=version)
|
||||
file.header.file_name.name = "/dev/null" # Hehehe
|
||||
file.header.file_name.time_stamp = datetime.datetime.now().astimezone().replace(microsecond=0).isoformat()
|
||||
file.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version)
|
||||
file.header.file_name.originating_system = "IfcOpenShell {}".format(ifcopenshell.version)
|
||||
file.header.file_name.authorization = "Nobody"
|
||||
file.header.file_description.description = ("ViewDefinition[DesignTransferView]",)
|
||||
return file
|
||||
@@ -0,0 +1,72 @@
|
||||
# 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.owner
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def unassign_declaration(
|
||||
file: ifcopenshell.file,
|
||||
definitions: list[ifcopenshell.entity_instance],
|
||||
relating_context: ifcopenshell.entity_instance,
|
||||
) -> None:
|
||||
"""Unassigns a list of objects from a project or project library
|
||||
|
||||
Typically used to remove an asset from a project library.
|
||||
|
||||
:param definitions: The list of objects you want to undeclare.
|
||||
Typically a list of assets.
|
||||
:param relating_context: The IfcProject, or more commonly the
|
||||
IfcProjectLibrary that you want the object to no longer be part of.
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Programmatically generate a library. You could do this visually too.
|
||||
library = ifcopenshell.api.project.create_file()
|
||||
root = ifcopenshell.api.root.create_entity(library, ifc_class="IfcProject", name="Demo Library")
|
||||
context = ifcopenshell.api.root.create_entity(library,
|
||||
ifc_class="IfcProjectLibrary", name="Demo Library")
|
||||
|
||||
# It's necessary to say our library is part of our project.
|
||||
ifcopenshell.api.project.assign_declaration(library, definitions=[context], relating_context=root)
|
||||
|
||||
# Remove the library from our project
|
||||
ifcopenshell.api.project.unassign_declaration(library, definitions=[context], relating_context=root)
|
||||
"""
|
||||
settings = {
|
||||
"definitions": definitions,
|
||||
"relating_context": relating_context,
|
||||
}
|
||||
|
||||
definitions = set(settings["definitions"])
|
||||
rels = {rel for obj in definitions if (rel := next(iter(obj.HasContext), None))}
|
||||
|
||||
for rel in rels:
|
||||
related_definitions = set(rel.RelatedDefinitions) - definitions
|
||||
if related_definitions:
|
||||
rel.RelatedDefinitions = list(related_definitions)
|
||||
ifcopenshell.api.owner.update_owner_history(file, element=rel)
|
||||
else:
|
||||
history = rel.OwnerHistory
|
||||
file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
Reference in New Issue
Block a user