2022-01-19 12:18:33 +11:00
|
|
|
|
# IfcOpenShell - IFC toolkit and geometry engine
|
|
|
|
|
|
# Copyright (C) 2021 Thomas Krijnen <thomas@aecgeeks.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/>.
|
|
|
|
|
|
|
2024-05-20 13:17:17 +05:00
|
|
|
|
from __future__ import annotations
|
2022-11-26 05:19:01 +01:00
|
|
|
|
import os
|
2023-07-06 14:22:36 +10:00
|
|
|
|
import re
|
2016-06-22 15:03:18 +02:00
|
|
|
|
import numbers
|
2022-11-26 05:19:01 +01:00
|
|
|
|
import zipfile
|
2023-07-06 14:22:36 +10:00
|
|
|
|
import functools
|
2024-05-07 10:32:02 +10:00
|
|
|
|
import ifcopenshell
|
2023-07-06 14:22:36 +10:00
|
|
|
|
from pathlib import Path
|
2025-02-18 15:18:23 +05:00
|
|
|
|
from typing import Any
|
2025-01-26 09:04:49 +01:00
|
|
|
|
from typing import Callable
|
|
|
|
|
|
from typing import Generator
|
|
|
|
|
|
from typing import Optional
|
|
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
|
|
from typing import Union
|
2025-01-27 13:07:07 +05:00
|
|
|
|
from typing import overload
|
|
|
|
|
|
from typing import Literal
|
2025-05-08 12:36:44 +05:00
|
|
|
|
from typing import TypedDict
|
|
|
|
|
|
from typing_extensions import assert_never
|
2016-06-22 15:03:18 +02:00
|
|
|
|
|
|
|
|
|
|
from . import ifcopenshell_wrapper
|
|
|
|
|
|
from .entity_instance import entity_instance
|
|
|
|
|
|
|
2025-04-20 17:51:31 +01:00
|
|
|
|
from ifcopenshell.util.mvd_info import MvdInfo, LARK_AVAILABLE
|
2025-02-13 11:52:56 +01:00
|
|
|
|
|
2024-08-30 16:56:13 +05:00
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
|
import ifcopenshell.util.schema
|
|
|
|
|
|
|
2025-05-08 12:36:44 +05:00
|
|
|
|
InverseReference = tuple[int, Any]
|
|
|
|
|
|
ElementInverses = dict[int, list[InverseReference]]
|
|
|
|
|
|
|
|
|
|
|
|
class CreateOperation(TypedDict):
|
|
|
|
|
|
action: Literal["create"]
|
|
|
|
|
|
value: Any
|
|
|
|
|
|
|
|
|
|
|
|
class EditOperation(TypedDict):
|
|
|
|
|
|
action: Literal["edit"]
|
|
|
|
|
|
id: int
|
|
|
|
|
|
index: int
|
|
|
|
|
|
old: Any
|
|
|
|
|
|
new: Any
|
|
|
|
|
|
|
|
|
|
|
|
class DeleteOperation(TypedDict):
|
|
|
|
|
|
action: Literal["delete"]
|
|
|
|
|
|
inverses: ElementInverses
|
|
|
|
|
|
value: Any
|
|
|
|
|
|
|
|
|
|
|
|
class BatchDeleteOperation(TypedDict):
|
|
|
|
|
|
action: Literal["batch_delete"]
|
|
|
|
|
|
inverses: ElementInverses
|
|
|
|
|
|
|
|
|
|
|
|
TransactionOperation = Union[CreateOperation, EditOperation, DeleteOperation, BatchDeleteOperation]
|
|
|
|
|
|
|
2024-09-03 18:49:05 +02:00
|
|
|
|
HEADER_FIELDS = {
|
|
|
|
|
|
"file_description": [
|
|
|
|
|
|
"description",
|
2024-09-04 18:52:06 +05:00
|
|
|
|
"implementation_level",
|
2024-09-03 18:49:05 +02:00
|
|
|
|
],
|
|
|
|
|
|
"file_name": [
|
|
|
|
|
|
"name",
|
|
|
|
|
|
"time_stamp",
|
|
|
|
|
|
"author",
|
|
|
|
|
|
"organization",
|
|
|
|
|
|
"preprocessor_version",
|
|
|
|
|
|
"originating_system",
|
2024-09-04 18:52:06 +05:00
|
|
|
|
"authorization",
|
|
|
|
|
|
],
|
2024-09-03 18:49:05 +02:00
|
|
|
|
}
|
2017-11-06 09:10:28 +01:00
|
|
|
|
|
2024-09-04 18:52:06 +05:00
|
|
|
|
|
2025-05-07 17:47:06 +05:00
|
|
|
|
class UndoSystemError(Exception):
|
|
|
|
|
|
def __init__(self, message: str, transaction: Transaction):
|
|
|
|
|
|
super().__init__(message)
|
|
|
|
|
|
self.transaction = transaction
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-06-30 18:34:12 +10:00
|
|
|
|
class Transaction:
|
2025-05-08 12:36:44 +05:00
|
|
|
|
operations: list[TransactionOperation]
|
|
|
|
|
|
batch_inverses: list[ElementInverses]
|
|
|
|
|
|
batch_delete_ids: set[int]
|
|
|
|
|
|
|
2024-10-22 11:13:59 +05:00
|
|
|
|
def __init__(self, ifc_file: file):
|
|
|
|
|
|
self.file: file = ifc_file
|
2021-06-30 18:34:12 +10:00
|
|
|
|
self.operations = []
|
2021-07-05 14:16:06 +10:00
|
|
|
|
self.is_batched = False
|
|
|
|
|
|
self.batch_delete_index = 0
|
|
|
|
|
|
self.batch_delete_ids = set()
|
|
|
|
|
|
self.batch_inverses = []
|
2021-06-30 18:34:12 +10:00
|
|
|
|
|
2024-04-22 17:01:28 +05:00
|
|
|
|
def serialise_entity_instance(self, element: ifcopenshell.entity_instance) -> dict[str, Any]:
|
2021-06-30 18:34:12 +10:00
|
|
|
|
info = element.get_info()
|
|
|
|
|
|
for key, value in info.items():
|
|
|
|
|
|
info[key] = self.serialise_value(element, value)
|
|
|
|
|
|
return info
|
|
|
|
|
|
|
2025-05-08 12:36:44 +05:00
|
|
|
|
def serialise_value(self, element, value) -> Any:
|
2021-07-06 15:06:21 +10:00
|
|
|
|
return element.walk(
|
|
|
|
|
|
lambda v: isinstance(v, entity_instance),
|
|
|
|
|
|
lambda v: {"id": v.id()} if v.id() else {"type": v.is_a(), "value": v.wrappedValue},
|
|
|
|
|
|
value,
|
|
|
|
|
|
)
|
2021-06-30 18:34:12 +10:00
|
|
|
|
|
2025-05-08 12:36:44 +05:00
|
|
|
|
def unserialise_value(self, element, value) -> Any:
|
2021-07-06 15:06:21 +10:00
|
|
|
|
return element.walk(
|
|
|
|
|
|
lambda v: isinstance(v, dict),
|
|
|
|
|
|
lambda v: self.file.by_id(v["id"]) if v.get("id") else self.file.create_entity(v["type"], v["value"]),
|
|
|
|
|
|
value,
|
|
|
|
|
|
)
|
2021-06-30 18:34:12 +10:00
|
|
|
|
|
2024-05-31 18:02:20 +05:00
|
|
|
|
def batch(self) -> None:
|
2021-07-05 14:16:06 +10:00
|
|
|
|
self.is_batched = True
|
|
|
|
|
|
self.batch_delete_index = len(self.operations)
|
|
|
|
|
|
self.batch_delete_ids = set()
|
|
|
|
|
|
self.batch_inverses = []
|
|
|
|
|
|
|
2024-05-31 18:02:20 +05:00
|
|
|
|
def unbatch(self) -> None:
|
2021-07-05 14:16:06 +10:00
|
|
|
|
for inverses in self.batch_inverses:
|
|
|
|
|
|
if inverses:
|
|
|
|
|
|
self.operations.insert(self.batch_delete_index, {"action": "batch_delete", "inverses": inverses})
|
|
|
|
|
|
self.is_batched = False
|
|
|
|
|
|
self.batch_delete_index = 0
|
|
|
|
|
|
self.batch_delete_ids = set()
|
|
|
|
|
|
self.batch_inverses = []
|
|
|
|
|
|
|
2024-05-31 18:02:20 +05:00
|
|
|
|
def store_create(self, element: ifcopenshell.entity_instance) -> None:
|
2021-07-05 17:09:04 +10:00
|
|
|
|
if element.id():
|
|
|
|
|
|
self.operations.append({"action": "create", "value": self.serialise_entity_instance(element)})
|
2021-06-30 18:34:12 +10:00
|
|
|
|
|
2024-05-31 18:02:20 +05:00
|
|
|
|
def store_edit(self, element: ifcopenshell.entity_instance, index: int, value: Any) -> None:
|
2023-03-09 18:18:56 +11:00
|
|
|
|
if element.id():
|
|
|
|
|
|
self.operations.append(
|
|
|
|
|
|
{
|
|
|
|
|
|
"action": "edit",
|
|
|
|
|
|
"id": element.id(),
|
|
|
|
|
|
"index": index,
|
|
|
|
|
|
"old": self.serialise_value(element, element[index]),
|
|
|
|
|
|
"new": self.serialise_value(element, value),
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
2021-06-30 18:34:12 +10:00
|
|
|
|
|
2024-04-22 17:01:28 +05:00
|
|
|
|
def store_delete(self, element: ifcopenshell.entity_instance) -> None:
|
2021-07-05 14:16:06 +10:00
|
|
|
|
inverses = {}
|
|
|
|
|
|
if self.is_batched:
|
|
|
|
|
|
if element.id() not in self.batch_delete_ids:
|
|
|
|
|
|
self.batch_inverses.append(self.get_element_inverses(element))
|
|
|
|
|
|
self.batch_delete_ids.add(element.id())
|
|
|
|
|
|
else:
|
|
|
|
|
|
inverses = self.get_element_inverses(element)
|
|
|
|
|
|
self.operations.append(
|
|
|
|
|
|
{"action": "delete", "inverses": inverses, "value": self.serialise_entity_instance(element)}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-05-08 12:36:44 +05:00
|
|
|
|
def get_element_inverses(self, element: ifcopenshell.entity_instance) -> ElementInverses:
|
|
|
|
|
|
inverses: ElementInverses = {}
|
2021-06-30 18:34:12 +10:00
|
|
|
|
for inverse in self.file.get_inverse(element):
|
2025-05-08 12:36:44 +05:00
|
|
|
|
inverse_references: list[InverseReference] = []
|
2021-06-30 18:34:12 +10:00
|
|
|
|
for i, attribute in enumerate(inverse):
|
2024-05-07 10:32:02 +10:00
|
|
|
|
if self.has_element_reference(attribute, element):
|
2021-07-04 19:45:34 +10:00
|
|
|
|
inverse_references.append((i, self.serialise_value(inverse, attribute)))
|
2021-06-30 18:34:12 +10:00
|
|
|
|
inverses[inverse.id()] = inverse_references
|
2021-07-05 14:16:06 +10:00
|
|
|
|
return inverses
|
2021-06-30 18:34:12 +10:00
|
|
|
|
|
2024-05-07 10:32:02 +10:00
|
|
|
|
def has_element_reference(self, value: Any, element: ifcopenshell.entity_instance) -> bool:
|
|
|
|
|
|
if isinstance(value, (tuple, list)):
|
|
|
|
|
|
for v in value:
|
|
|
|
|
|
if self.has_element_reference(v, element):
|
|
|
|
|
|
return True
|
|
|
|
|
|
return False
|
|
|
|
|
|
return value == element
|
|
|
|
|
|
|
2024-05-31 18:02:20 +05:00
|
|
|
|
def rollback(self) -> None:
|
2021-06-30 18:34:12 +10:00
|
|
|
|
for operation in self.operations[::-1]:
|
|
|
|
|
|
if operation["action"] == "create":
|
|
|
|
|
|
element = self.file.by_id(operation["value"]["id"])
|
2021-08-31 10:25:13 +10:00
|
|
|
|
if hasattr(element, "GlobalId") and element.GlobalId is None:
|
2021-06-30 18:34:12 +10:00
|
|
|
|
# hack, otherwise ifcopenshell gets upset
|
|
|
|
|
|
element.GlobalId = "x"
|
|
|
|
|
|
self.file.remove(element)
|
|
|
|
|
|
elif operation["action"] == "edit":
|
|
|
|
|
|
element = self.file.by_id(operation["id"])
|
|
|
|
|
|
try:
|
|
|
|
|
|
element[operation["index"]] = self.unserialise_value(element, operation["old"])
|
|
|
|
|
|
except:
|
|
|
|
|
|
# Catch discrepancy where IfcOpenShell creates but doesn't allow editing of invalid values
|
|
|
|
|
|
pass
|
|
|
|
|
|
elif operation["action"] == "delete":
|
|
|
|
|
|
e = self.file.create_entity(operation["value"]["type"], id=operation["value"]["id"])
|
|
|
|
|
|
for k, v in operation["value"].items():
|
|
|
|
|
|
try:
|
|
|
|
|
|
setattr(e, k, self.unserialise_value(e, v))
|
|
|
|
|
|
except:
|
|
|
|
|
|
# Catch discrepancy where IfcOpenShell creates but doesn't allow editing of invalid values
|
|
|
|
|
|
pass
|
|
|
|
|
|
for inverse_id, data in operation["inverses"].items():
|
|
|
|
|
|
inverse = self.file.by_id(inverse_id)
|
2021-07-04 19:45:34 +10:00
|
|
|
|
for index, value in data:
|
|
|
|
|
|
inverse[index] = self.unserialise_value(inverse, value)
|
2021-07-05 14:16:06 +10:00
|
|
|
|
elif operation["action"] == "batch_delete":
|
|
|
|
|
|
for inverse_id, data in operation["inverses"].items():
|
|
|
|
|
|
inverse = self.file.by_id(inverse_id)
|
|
|
|
|
|
for index, value in data:
|
|
|
|
|
|
inverse[index] = self.unserialise_value(inverse, value)
|
2025-05-08 12:36:44 +05:00
|
|
|
|
else:
|
|
|
|
|
|
assert_never(operation["action"])
|
2021-06-30 18:34:12 +10:00
|
|
|
|
|
2024-05-31 18:02:20 +05:00
|
|
|
|
def commit(self) -> None:
|
2021-06-30 18:34:12 +10:00
|
|
|
|
for operation in self.operations:
|
|
|
|
|
|
if operation["action"] == "create":
|
|
|
|
|
|
e = self.file.create_entity(operation["value"]["type"], id=operation["value"]["id"])
|
|
|
|
|
|
for k, v in operation["value"].items():
|
|
|
|
|
|
try:
|
|
|
|
|
|
setattr(e, k, self.unserialise_value(e, v))
|
|
|
|
|
|
except:
|
|
|
|
|
|
# Catch discrepancy where IfcOpenShell creates but doesn't allow editing of invalid values
|
|
|
|
|
|
pass
|
|
|
|
|
|
elif operation["action"] == "edit":
|
|
|
|
|
|
element = self.file.by_id(operation["id"])
|
|
|
|
|
|
element[operation["index"]] = self.unserialise_value(element, operation["new"])
|
|
|
|
|
|
elif operation["action"] == "delete":
|
|
|
|
|
|
element = self.file.by_id(operation["value"]["id"])
|
|
|
|
|
|
self.file.remove(element)
|
2021-07-05 14:16:06 +10:00
|
|
|
|
elif operation["action"] == "batch_delete":
|
|
|
|
|
|
pass
|
2025-05-08 12:36:44 +05:00
|
|
|
|
else:
|
|
|
|
|
|
assert_never(operation["action"])
|
2021-06-30 18:34:12 +10:00
|
|
|
|
|
|
|
|
|
|
|
2023-07-08 12:07:28 +02:00
|
|
|
|
file_dict = {}
|
|
|
|
|
|
|
2024-06-12 17:30:22 +05:00
|
|
|
|
READ_ERROR = ifcopenshell_wrapper.file_open_status.READ_ERROR
|
|
|
|
|
|
NO_HEADER = ifcopenshell_wrapper.file_open_status.NO_HEADER
|
|
|
|
|
|
UNSUPPORTED_SCHEMA = ifcopenshell_wrapper.file_open_status.UNSUPPORTED_SCHEMA
|
2024-11-14 22:00:11 +01:00
|
|
|
|
INVALID_SYNTAX = ifcopenshell_wrapper.file_open_status.INVALID_SYNTAX
|
2023-07-08 12:07:28 +02:00
|
|
|
|
|
2025-05-06 11:11:02 +05:00
|
|
|
|
|
2024-05-07 16:03:07 +10:00
|
|
|
|
class file:
|
2020-04-02 05:53:14 +02:00
|
|
|
|
"""Base class for containing IFC files.
|
2017-12-04 09:16:30 -08:00
|
|
|
|
|
|
|
|
|
|
Class has instance methods for filtering by element Id, Type, etc.
|
|
|
|
|
|
Instantiated objects can be subscripted by Id or Guid
|
|
|
|
|
|
|
2023-01-10 10:16:28 +11:00
|
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
2017-12-04 09:16:30 -08:00
|
|
|
|
|
2024-04-08 12:34:59 +10:00
|
|
|
|
model = ifcopenshell.open(file_path)
|
|
|
|
|
|
products = model.by_type("IfcProduct")
|
2022-05-09 15:35:52 +10:00
|
|
|
|
print(products[0].id(), products[0].GlobalId) # 122 2XQ$n5SLP5MBLyL442paFx
|
2024-04-08 12:34:59 +10:00
|
|
|
|
print(products[0] == model[122] == model["2XQ$n5SLP5MBLyL442paFx"]) # True
|
2017-12-04 09:16:30 -08:00
|
|
|
|
"""
|
2020-11-01 20:08:27 +07:00
|
|
|
|
|
2024-03-04 15:46:36 +05:00
|
|
|
|
wrapped_data: ifcopenshell_wrapper.file
|
2025-01-27 14:40:15 +11:00
|
|
|
|
units: dict[str, entity_instance] = {}
|
2024-10-22 17:40:03 +05:00
|
|
|
|
history_size: int = 64
|
2025-05-08 12:36:44 +05:00
|
|
|
|
history: list[Transaction]
|
|
|
|
|
|
"""Chronological order - from oldest to newest."""
|
|
|
|
|
|
future: list[Transaction]
|
|
|
|
|
|
"""Reversed chronological order - from newest to oldest."""
|
2024-03-04 15:46:36 +05:00
|
|
|
|
|
2025-01-28 14:59:01 +05:00
|
|
|
|
to_delete: Union[set[ifcopenshell.entity_instance], None] = None
|
|
|
|
|
|
"""Entities for batch removal."""
|
|
|
|
|
|
|
2024-04-08 15:05:37 +05:00
|
|
|
|
def __init__(
|
|
|
|
|
|
self,
|
|
|
|
|
|
f: Optional[ifcopenshell_wrapper.file] = None,
|
2024-12-20 18:30:43 +05:00
|
|
|
|
schema: Optional[ifcopenshell.util.schema.IFC_SCHEMA] = None,
|
2024-04-08 15:05:37 +05:00
|
|
|
|
schema_version: Optional[tuple[int, int, int, int]] = None,
|
|
|
|
|
|
):
|
2023-07-06 22:40:15 +10:00
|
|
|
|
"""Create a new blank IFC model
|
|
|
|
|
|
|
|
|
|
|
|
This IFC model does not have any entities in it yet. See the
|
|
|
|
|
|
``create_entity`` function for how to create new entities. All data is
|
|
|
|
|
|
stored in memory. If you wish to write the IFC model to disk, see the
|
|
|
|
|
|
``write`` function.
|
|
|
|
|
|
|
|
|
|
|
|
:param f: The underlying IfcOpenShell file object to be wrapped. This
|
|
|
|
|
|
is an internal implementation detail and should generally be left
|
|
|
|
|
|
as None by users.
|
|
|
|
|
|
:param schema: Which IFC schema to use, chosen from "IFC2X3", "IFC4",
|
|
|
|
|
|
or "IFC4X3". These refer to the ISO approved versions of IFC.
|
|
|
|
|
|
Defaults to "IFC4" if not specified, which is currently recommended
|
|
|
|
|
|
for all new projects.
|
|
|
|
|
|
:param schema_version: If you want to specify an exact version of IFC
|
|
|
|
|
|
that may not be an ISO approved version, use this argument instead
|
|
|
|
|
|
of ``schema``. IFC versions on technical.buildingsmart.org are
|
|
|
|
|
|
described using 4 integers representing the major, minor, addendum,
|
|
|
|
|
|
and corrigendum number. For example, (4, 0, 2, 1) refers to IFC4
|
|
|
|
|
|
ADD2 TC1, which is the official version approved by ISO when people
|
|
|
|
|
|
refer to "IFC4". Generally you should not use this argument unless
|
|
|
|
|
|
you are testing non-ISO IFC releases.
|
|
|
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
|
|
# Create a new IFC4 model, create a wall, then save it to an IFC-SPF file.
|
|
|
|
|
|
model = ifcopenshell.file()
|
|
|
|
|
|
model.create_entity("IfcWall")
|
|
|
|
|
|
model.write("/path/to/model.ifc")
|
|
|
|
|
|
|
|
|
|
|
|
# Create a new IFC4X3 model
|
|
|
|
|
|
model = ifcopenshell.file(schema="IFC4X3")
|
|
|
|
|
|
|
|
|
|
|
|
# A poweruser testing out a particular version of IFC4X3
|
|
|
|
|
|
model = ifcopenshell.file(schema_version=(4, 3, 0, 1))
|
|
|
|
|
|
"""
|
2023-07-06 14:22:36 +10:00
|
|
|
|
if schema_version:
|
2023-07-06 22:40:15 +10:00
|
|
|
|
prefixes = ("IFC", "X", "_ADD", "_TC")
|
|
|
|
|
|
schema = "".join("".join(map(str, t)) if t[1] else "" for t in zip(prefixes, schema_version))
|
2023-07-06 14:22:36 +10:00
|
|
|
|
else:
|
2024-04-08 12:34:59 +10:00
|
|
|
|
schema = {"IFC4X3": "IFC4X3_ADD2"}.get(schema, schema)
|
2017-12-31 12:20:13 +01:00
|
|
|
|
if f is not None:
|
2024-06-12 17:30:22 +05:00
|
|
|
|
if not f.good():
|
|
|
|
|
|
from . import Error, SchemaError
|
|
|
|
|
|
|
|
|
|
|
|
exc, msg = {
|
|
|
|
|
|
READ_ERROR: (IOError, "Unable to open file for reading"),
|
|
|
|
|
|
NO_HEADER: (Error, "Unable to parse IFC SPF header"),
|
|
|
|
|
|
UNSUPPORTED_SCHEMA: (
|
|
|
|
|
|
SchemaError,
|
|
|
|
|
|
"Unsupported schema: %s" % ",".join(f.header.file_schema.schema_identifiers),
|
|
|
|
|
|
),
|
2024-11-14 22:00:11 +01:00
|
|
|
|
INVALID_SYNTAX: (Error, "Syntax error during parse, check logs"),
|
2024-06-12 17:30:22 +05:00
|
|
|
|
}[f.good().value()]
|
|
|
|
|
|
raise exc(msg)
|
2017-12-31 12:20:13 +01:00
|
|
|
|
self.wrapped_data = f
|
|
|
|
|
|
else:
|
|
|
|
|
|
args = filter(None, [schema])
|
|
|
|
|
|
args = map(ifcopenshell_wrapper.schema_by_name, args)
|
|
|
|
|
|
self.wrapped_data = ifcopenshell_wrapper.file(*args)
|
2021-06-30 18:34:12 +10:00
|
|
|
|
self.history = []
|
|
|
|
|
|
self.future = []
|
2024-04-25 11:24:42 +05:00
|
|
|
|
self.transaction: Optional[Transaction] = None
|
2021-06-30 18:34:12 +10:00
|
|
|
|
|
2024-01-16 09:44:04 +01:00
|
|
|
|
import weakref
|
2024-03-04 15:46:36 +05:00
|
|
|
|
|
2024-01-16 09:44:04 +01:00
|
|
|
|
file_dict[self.file_pointer()] = weakref.ref(self)
|
2024-03-04 15:46:36 +05:00
|
|
|
|
|
2024-05-31 18:02:20 +05:00
|
|
|
|
def __del__(self) -> None:
|
2024-06-12 18:13:49 +05:00
|
|
|
|
# Avoid infinite recursion if file is failed to initialize
|
|
|
|
|
|
# and wrapped_data is unset.
|
|
|
|
|
|
if "wrapped_data" not in dir(self):
|
|
|
|
|
|
return
|
2024-01-16 09:44:04 +01:00
|
|
|
|
del file_dict[self.file_pointer()]
|
2023-07-08 12:07:28 +02:00
|
|
|
|
|
2024-05-31 18:02:20 +05:00
|
|
|
|
def set_history_size(self, size: int) -> None:
|
2021-06-30 18:34:12 +10:00
|
|
|
|
self.history_size = size
|
|
|
|
|
|
while len(self.history) > self.history_size:
|
|
|
|
|
|
self.history.pop(0)
|
|
|
|
|
|
|
2024-05-31 18:02:20 +05:00
|
|
|
|
def begin_transaction(self) -> None:
|
2022-09-01 11:16:33 +10:00
|
|
|
|
if self.history_size:
|
|
|
|
|
|
self.transaction = Transaction(self)
|
2021-06-30 18:34:12 +10:00
|
|
|
|
|
2024-05-31 18:02:20 +05:00
|
|
|
|
def end_transaction(self) -> None:
|
2021-06-30 18:34:12 +10:00
|
|
|
|
if self.transaction:
|
|
|
|
|
|
self.history.append(self.transaction)
|
|
|
|
|
|
if len(self.history) > self.history_size:
|
|
|
|
|
|
self.history.pop(0)
|
|
|
|
|
|
self.future = []
|
|
|
|
|
|
self.transaction = None
|
|
|
|
|
|
|
2024-05-31 18:02:20 +05:00
|
|
|
|
def discard_transaction(self) -> None:
|
2021-06-30 18:34:12 +10:00
|
|
|
|
if self.transaction:
|
|
|
|
|
|
self.transaction.rollback()
|
|
|
|
|
|
self.transaction = None
|
|
|
|
|
|
|
2024-05-31 18:02:20 +05:00
|
|
|
|
def undo(self) -> None:
|
2021-06-30 18:34:12 +10:00
|
|
|
|
if not self.history:
|
|
|
|
|
|
return
|
|
|
|
|
|
transaction = self.history.pop()
|
2025-05-07 17:47:06 +05:00
|
|
|
|
try:
|
|
|
|
|
|
transaction.rollback()
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
raise UndoSystemError("Error during transaction undo.", transaction) from e
|
2021-06-30 18:34:12 +10:00
|
|
|
|
self.future.append(transaction)
|
|
|
|
|
|
|
2024-05-31 18:02:20 +05:00
|
|
|
|
def redo(self) -> None:
|
2021-06-30 18:34:12 +10:00
|
|
|
|
if not self.future:
|
|
|
|
|
|
return
|
|
|
|
|
|
transaction = self.future.pop()
|
2025-05-07 17:47:06 +05:00
|
|
|
|
try:
|
|
|
|
|
|
transaction.commit()
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
raise UndoSystemError("Error during transaction redo.", transaction) from e
|
2021-06-30 18:34:12 +10:00
|
|
|
|
self.history.append(transaction)
|
2017-11-06 09:10:28 +01:00
|
|
|
|
|
2024-03-04 15:46:36 +05:00
|
|
|
|
def create_entity(self, type: str, *args, **kwargs) -> ifcopenshell.entity_instance:
|
2020-04-02 05:53:14 +02:00
|
|
|
|
"""Create a new IFC entity in the file.
|
|
|
|
|
|
|
2024-07-01 11:41:15 +05:00
|
|
|
|
You can also use dynamic methods similar to `ifc_file.createIfcWall(...)`
|
|
|
|
|
|
to create IFC entities. They work exactly the same as if you would do
|
|
|
|
|
|
`ifc_file.create_entity("IfcWall", ...)` but the resulting typing
|
|
|
|
|
|
is not as accurate as for `create_entity` due to a dynamic nature
|
|
|
|
|
|
of those methods.
|
|
|
|
|
|
|
2020-04-02 05:53:14 +02:00
|
|
|
|
:param type: Case insensitive name of the IFC class
|
|
|
|
|
|
:param args: The positional arguments of the IFC class
|
|
|
|
|
|
:param kwargs: The keyword arguments of the IFC class
|
|
|
|
|
|
:returns: An entity instance
|
|
|
|
|
|
|
2023-01-10 10:16:28 +11:00
|
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
2020-04-02 05:53:14 +02:00
|
|
|
|
|
|
|
|
|
|
f = ifcopenshell.file()
|
2022-05-09 15:35:52 +10:00
|
|
|
|
f.create_entity("IfcPerson")
|
|
|
|
|
|
# >>> #1=IfcPerson($,$,$,$,$,$,$,$)
|
|
|
|
|
|
f.create_entity("IfcPerson", "Foobar")
|
|
|
|
|
|
# >>> #2=IfcPerson('Foobar',$,$,$,$,$,$,$)
|
|
|
|
|
|
f.create_entity("IfcPerson", Identification="Foobar")
|
|
|
|
|
|
# >>> #3=IfcPerson('Foobar',$,$,$,$,$,$,$)
|
2020-04-02 05:53:14 +02:00
|
|
|
|
"""
|
2021-08-31 17:28:31 +10:00
|
|
|
|
eid = kwargs.pop("id", -1)
|
2021-07-17 09:38:31 +02:00
|
|
|
|
|
2023-07-23 15:42:09 +10:00
|
|
|
|
e = entity_instance((self.schema_identifier, type), self)
|
2021-07-17 09:38:31 +02:00
|
|
|
|
|
|
|
|
|
|
# Create pairs of {attribute index, attribute value}.
|
|
|
|
|
|
# Keyword arguments are mapped to their corresponding
|
|
|
|
|
|
# numeric index with get_argument_index().
|
|
|
|
|
|
|
|
|
|
|
|
# @todo we should probably check that values for
|
|
|
|
|
|
# attributes are not passed as duplicates using
|
|
|
|
|
|
# both regular arguments and keyword arguments.
|
2024-05-09 17:06:18 +05:00
|
|
|
|
kwargs_attrs = [(e.wrapped_data.get_argument_index(name), arg) for name, arg in kwargs.items()]
|
|
|
|
|
|
attrs = list(enumerate(args)) + kwargs_attrs
|
|
|
|
|
|
|
|
|
|
|
|
if len(attrs) > len(e):
|
|
|
|
|
|
raise ValueError(
|
|
|
|
|
|
"entity instance of type '%s' has only %s attributes but %s attributes were provided."
|
|
|
|
|
|
% (e.is_a(True), len(e), len(attrs))
|
|
|
|
|
|
)
|
2021-07-17 09:38:31 +02:00
|
|
|
|
|
|
|
|
|
|
# Don't store these attributes as transactions
|
|
|
|
|
|
# as the creation it self is already stored with
|
|
|
|
|
|
# it's arguments
|
2021-07-05 14:16:06 +10:00
|
|
|
|
if attrs:
|
|
|
|
|
|
transaction = self.transaction
|
|
|
|
|
|
self.transaction = None
|
2021-07-17 09:38:31 +02:00
|
|
|
|
|
2024-05-09 17:06:18 +05:00
|
|
|
|
try:
|
|
|
|
|
|
for idx, arg in attrs:
|
|
|
|
|
|
e[idx] = arg
|
|
|
|
|
|
except IndexError:
|
|
|
|
|
|
invalid_attrs = []
|
|
|
|
|
|
for (attr_index, _), attr_name in zip(kwargs_attrs, kwargs):
|
|
|
|
|
|
if attr_index == 0xFFFFFFFF:
|
|
|
|
|
|
invalid_attrs.append(attr_name)
|
2024-07-30 18:55:54 +05:00
|
|
|
|
raise ValueError(
|
|
|
|
|
|
"entity instance of type '%s' doesn't have the following attributes: %s."
|
|
|
|
|
|
% (e.is_a(True), ", ".join(invalid_attrs))
|
|
|
|
|
|
)
|
2021-07-17 09:38:31 +02:00
|
|
|
|
|
|
|
|
|
|
# Restore transaction status
|
2021-07-05 14:16:06 +10:00
|
|
|
|
if attrs:
|
|
|
|
|
|
self.transaction = transaction
|
2021-07-17 09:38:31 +02:00
|
|
|
|
|
|
|
|
|
|
# Once the values are populated add the instance
|
|
|
|
|
|
# to the file.
|
|
|
|
|
|
self.wrapped_data.add(e.wrapped_data, eid)
|
|
|
|
|
|
|
|
|
|
|
|
# The file container now handles the lifetime of
|
|
|
|
|
|
# this instance. Tell SWIG that it is no longer
|
|
|
|
|
|
# the owner.
|
|
|
|
|
|
e.wrapped_data.this.disown()
|
|
|
|
|
|
|
2021-07-17 18:38:04 +10:00
|
|
|
|
if self.transaction:
|
|
|
|
|
|
self.transaction.store_create(e)
|
|
|
|
|
|
|
2016-07-18 15:53:20 +02:00
|
|
|
|
return e
|
2017-11-06 09:10:28 +01:00
|
|
|
|
|
2024-07-01 11:41:15 +05:00
|
|
|
|
@property
|
2024-08-30 16:56:13 +05:00
|
|
|
|
def schema(self) -> ifcopenshell.util.schema.IFC_SCHEMA:
|
2024-07-01 11:41:15 +05:00
|
|
|
|
"""General IFC schema version: IFC2X3, IFC4, IFC4X3."""
|
|
|
|
|
|
prefixes = ("IFC", "X", "_ADD", "_TC")
|
2024-09-01 18:45:43 +08:00
|
|
|
|
reg = "".join(f"(?P<{s}>{s}\\d+)?" for s in prefixes)
|
2024-07-01 11:41:15 +05:00
|
|
|
|
match = re.match(reg, self.wrapped_data.schema)
|
|
|
|
|
|
version_tuple = tuple(
|
|
|
|
|
|
map(
|
|
|
|
|
|
lambda pp: int(pp[1][len(pp[0]) :]) if pp[1] else None,
|
|
|
|
|
|
((p, match.group(p)) for p in prefixes),
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
return "".join("".join(map(str, t)) if t[1] else "" for t in zip(prefixes, version_tuple[0:2]))
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def schema_identifier(self) -> str:
|
|
|
|
|
|
"""Full IFC schema version: IFC2X3_TC1, IFC4_ADD2, IFC4X3_ADD2, etc."""
|
|
|
|
|
|
return self.wrapped_data.schema
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def schema_version(self) -> tuple[int, int, int, int]:
|
|
|
|
|
|
"""Numeric representation of the full IFC schema version.
|
|
|
|
|
|
|
|
|
|
|
|
E.g. IFC4X3_ADD2 is represented as (4, 3, 2, 0).
|
|
|
|
|
|
"""
|
|
|
|
|
|
schema = self.wrapped_data.schema
|
|
|
|
|
|
version = []
|
|
|
|
|
|
for prefix in ("IFC", "X", "_ADD", "_TC"):
|
|
|
|
|
|
number = re.search(prefix + r"(\d)", schema)
|
|
|
|
|
|
version.append(int(number.group(1)) if number else 0)
|
|
|
|
|
|
return tuple(version)
|
2025-05-06 11:11:02 +05:00
|
|
|
|
|
|
|
|
|
|
@property
|
2025-04-20 17:51:31 +01:00
|
|
|
|
def mvd(self):
|
|
|
|
|
|
if not LARK_AVAILABLE:
|
|
|
|
|
|
return None
|
2025-05-06 11:11:02 +05:00
|
|
|
|
return MvdInfo(self.header)
|
2024-07-01 11:41:15 +05:00
|
|
|
|
|
2024-05-15 10:55:29 +05:00
|
|
|
|
def __getattr__(self, attr) -> Union[Any, Callable[..., ifcopenshell.entity_instance]]:
|
2020-11-01 20:08:27 +07:00
|
|
|
|
if attr[0:6] == "create":
|
2017-11-06 09:10:28 +01:00
|
|
|
|
return functools.partial(self.create_entity, attr[6:])
|
|
|
|
|
|
else:
|
|
|
|
|
|
return getattr(self.wrapped_data, attr)
|
|
|
|
|
|
|
2024-05-31 18:02:20 +05:00
|
|
|
|
def __getitem__(self, key: Union[numbers.Integral, str, bytes]) -> entity_instance:
|
2016-07-18 15:53:20 +02:00
|
|
|
|
if isinstance(key, numbers.Integral):
|
2021-06-30 18:34:12 +10:00
|
|
|
|
return entity_instance(self.wrapped_data.by_id(key), self)
|
2024-05-07 12:17:46 +10:00
|
|
|
|
elif isinstance(key, (str, bytes)):
|
2021-06-30 18:34:12 +10:00
|
|
|
|
return entity_instance(self.wrapped_data.by_guid(str(key)), self)
|
2017-11-06 09:10:28 +01:00
|
|
|
|
|
2024-03-04 15:46:36 +05:00
|
|
|
|
def by_id(self, id: int) -> ifcopenshell.entity_instance:
|
2020-05-21 08:25:47 +10:00
|
|
|
|
"""Return an IFC entity instance filtered by IFC ID.
|
2017-12-04 09:16:30 -08:00
|
|
|
|
|
2020-04-02 05:53:14 +02:00
|
|
|
|
:param id: STEP numerical identifier
|
2024-03-04 15:46:36 +05:00
|
|
|
|
|
|
|
|
|
|
:raises RuntimeError: If `id` is not found.
|
|
|
|
|
|
|
2024-05-07 19:08:13 +10:00
|
|
|
|
:returns: An ifcopenshell.entity_instance
|
2017-12-04 09:16:30 -08:00
|
|
|
|
"""
|
2017-11-06 11:06:40 +01:00
|
|
|
|
return self[id]
|
2017-11-06 09:10:28 +01:00
|
|
|
|
|
2024-03-04 15:46:36 +05:00
|
|
|
|
def by_guid(self, guid: str) -> ifcopenshell.entity_instance:
|
2020-05-21 08:25:47 +10:00
|
|
|
|
"""Return an IFC entity instance filtered by IFC GUID.
|
2017-12-04 09:16:30 -08:00
|
|
|
|
|
2020-04-02 05:53:14 +02:00
|
|
|
|
:param guid: GlobalId value in 22-character encoded form
|
2024-03-04 15:46:36 +05:00
|
|
|
|
|
|
|
|
|
|
:raises RuntimeError: If `guid` is not found.
|
|
|
|
|
|
|
2024-05-07 19:08:13 +10:00
|
|
|
|
:returns: An ifcopenshell.entity_instance
|
2017-12-04 09:16:30 -08:00
|
|
|
|
"""
|
2017-11-06 11:06:40 +01:00
|
|
|
|
return self[guid]
|
2017-11-06 09:10:28 +01:00
|
|
|
|
|
2024-03-04 15:46:36 +05:00
|
|
|
|
def add(self, inst: ifcopenshell.entity_instance, _id: int = None) -> ifcopenshell.entity_instance:
|
2020-05-21 08:25:47 +10:00
|
|
|
|
"""Adds an entity including any dependent entities to an IFC file.
|
2024-03-04 15:46:36 +05:00
|
|
|
|
If the entity already exists, it is not re-added. Existence of entity is checked by it's `.identity()`.
|
|
|
|
|
|
|
|
|
|
|
|
:param inst: The entity instance to add
|
2024-05-07 19:08:13 +10:00
|
|
|
|
:returns: An ifcopenshell.entity_instance
|
2024-03-04 15:46:36 +05:00
|
|
|
|
"""
|
2020-08-09 17:32:54 +10:00
|
|
|
|
|
2021-08-31 10:25:13 +10:00
|
|
|
|
if self.transaction:
|
2021-09-21 10:33:03 +10:00
|
|
|
|
max_id = self.wrapped_data.getMaxId()
|
2016-07-18 15:53:20 +02:00
|
|
|
|
inst.wrapped_data.this.disown()
|
2021-08-31 10:25:13 +10:00
|
|
|
|
result = entity_instance(self.wrapped_data.add(inst.wrapped_data, -1 if _id is None else _id), self)
|
|
|
|
|
|
if self.transaction:
|
|
|
|
|
|
added_elements = [e for e in self.traverse(result) if e.id() > max_id]
|
|
|
|
|
|
[self.transaction.store_create(e) for e in reversed(added_elements)]
|
|
|
|
|
|
return result
|
2017-11-06 09:10:28 +01:00
|
|
|
|
|
2024-04-09 16:41:03 +05:00
|
|
|
|
def by_type(self, type: str, include_subtypes=True) -> list[ifcopenshell.entity_instance]:
|
2020-04-02 05:53:14 +02:00
|
|
|
|
"""Return IFC objects filtered by IFC Type and wrapped with the entity_instance class.
|
2017-12-04 09:16:30 -08:00
|
|
|
|
|
2020-04-02 06:04:38 +02:00
|
|
|
|
If an IFC type class has subclasses, all entities of those subclasses are also returned.
|
|
|
|
|
|
|
2020-04-02 05:53:14 +02:00
|
|
|
|
:param type: The case insensitive type of IFC class to return.
|
2020-08-09 17:32:54 +10:00
|
|
|
|
:param include_subtypes: Whether or not to return subtypes of the IFC class
|
2024-04-26 11:49:44 +05:00
|
|
|
|
|
|
|
|
|
|
:raises RuntimeError: If `type` is not found in IFC schema.
|
|
|
|
|
|
|
2024-05-07 19:08:13 +10:00
|
|
|
|
:returns: A list of ifcopenshell.entity_instance objects
|
2017-12-04 09:16:30 -08:00
|
|
|
|
"""
|
2020-08-09 17:32:54 +10:00
|
|
|
|
if include_subtypes:
|
2021-06-30 18:34:12 +10:00
|
|
|
|
return [entity_instance(e, self) for e in self.wrapped_data.by_type(type)]
|
|
|
|
|
|
return [entity_instance(e, self) for e in self.wrapped_data.by_type_excl_subtypes(type)]
|
2017-11-06 09:10:28 +01:00
|
|
|
|
|
2024-03-04 15:46:36 +05:00
|
|
|
|
def traverse(
|
2024-09-20 22:55:21 +05:00
|
|
|
|
self, inst: ifcopenshell.entity_instance, max_levels: Optional[int] = None, breadth_first: bool = False
|
2024-04-09 16:41:03 +05:00
|
|
|
|
) -> list[ifcopenshell.entity_instance]:
|
2020-05-21 08:25:47 +10:00
|
|
|
|
"""Get a list of all referenced instances for a particular instance including itself
|
2020-04-02 05:53:14 +02:00
|
|
|
|
|
|
|
|
|
|
:param inst: The entity instance to get all sub instances
|
|
|
|
|
|
:param max_levels: How far deep to recursively fetch sub instances. None or -1 means infinite.
|
2021-08-12 13:35:45 +02:00
|
|
|
|
:param breadth_first: Whether to use breadth-first search, the default is depth-first.
|
2024-05-07 19:08:13 +10:00
|
|
|
|
:returns: A list of ifcopenshell.entity_instance objects
|
2020-04-02 05:53:14 +02:00
|
|
|
|
"""
|
2016-07-18 15:53:20 +02:00
|
|
|
|
if max_levels is None:
|
|
|
|
|
|
max_levels = -1
|
2021-08-31 10:25:13 +10:00
|
|
|
|
|
2021-08-12 13:35:45 +02:00
|
|
|
|
if breadth_first:
|
|
|
|
|
|
fn = self.wrapped_data.traverse_breadth_first
|
|
|
|
|
|
else:
|
|
|
|
|
|
fn = self.wrapped_data.traverse
|
2021-09-09 21:27:23 +10:00
|
|
|
|
|
2021-08-12 13:35:45 +02:00
|
|
|
|
return [entity_instance(e, self) for e in fn(inst.wrapped_data, max_levels)]
|
2017-11-06 09:10:28 +01:00
|
|
|
|
|
2025-01-27 13:07:07 +05:00
|
|
|
|
@overload
|
2024-03-04 15:46:36 +05:00
|
|
|
|
def get_inverse(
|
2025-01-27 13:07:07 +05:00
|
|
|
|
self,
|
|
|
|
|
|
inst: ifcopenshell.entity_instance,
|
|
|
|
|
|
allow_duplicate: Literal[False] = False,
|
|
|
|
|
|
with_attribute_indices: bool = False,
|
|
|
|
|
|
) -> set[ifcopenshell.entity_instance]: ...
|
|
|
|
|
|
@overload
|
|
|
|
|
|
def get_inverse(
|
|
|
|
|
|
self,
|
|
|
|
|
|
inst: ifcopenshell.entity_instance,
|
|
|
|
|
|
allow_duplicate: Literal[True],
|
|
|
|
|
|
with_attribute_indices: bool = False,
|
|
|
|
|
|
) -> list[ifcopenshell.entity_instance]: ...
|
|
|
|
|
|
@overload
|
|
|
|
|
|
def get_inverse(
|
|
|
|
|
|
self,
|
|
|
|
|
|
inst: ifcopenshell.entity_instance,
|
|
|
|
|
|
allow_duplicate: bool,
|
|
|
|
|
|
with_attribute_indices: bool = False,
|
|
|
|
|
|
) -> Union[list[ifcopenshell.entity_instance], set[ifcopenshell.entity_instance]]: ...
|
|
|
|
|
|
def get_inverse(
|
|
|
|
|
|
self,
|
|
|
|
|
|
inst: ifcopenshell.entity_instance,
|
|
|
|
|
|
allow_duplicate: bool = False,
|
|
|
|
|
|
with_attribute_indices: bool = False,
|
|
|
|
|
|
) -> Union[list[ifcopenshell.entity_instance], set[ifcopenshell.entity_instance]]:
|
2020-04-02 05:53:14 +02:00
|
|
|
|
"""Return a list of entities that reference this entity
|
|
|
|
|
|
|
2024-05-23 20:15:01 +10:00
|
|
|
|
Warning: this is a slow function, especially when there is a large
|
|
|
|
|
|
number of inverses (such as for a shared owner history). If you are
|
|
|
|
|
|
only interested in the total number of inverses (typically 0, 1, or N),
|
|
|
|
|
|
consider using :func:`get_total_inverses`.
|
|
|
|
|
|
|
2020-04-02 05:53:14 +02:00
|
|
|
|
:param inst: The entity instance to get inverse relationships
|
2022-10-20 13:35:02 +02:00
|
|
|
|
:param allow_duplicate: Returns a `list` when True, `set` when False
|
|
|
|
|
|
:param with_attribute_indices: Returns pairs of <i, idx>
|
|
|
|
|
|
where i[idx] is inst or contains inst. Requires allow_duplicate=True
|
2025-01-27 13:07:07 +05:00
|
|
|
|
:returns: A list or set of ifcopenshell.entity_instance objects.
|
2020-04-02 05:53:14 +02:00
|
|
|
|
"""
|
2022-10-20 13:35:02 +02:00
|
|
|
|
if with_attribute_indices and not allow_duplicate:
|
|
|
|
|
|
raise ValueError("with_attribute_indices requires allow_duplicate to be True")
|
|
|
|
|
|
|
2021-10-03 10:20:24 +11:00
|
|
|
|
inverses = [entity_instance(e, self) for e in self.wrapped_data.get_inverse(inst.wrapped_data)]
|
2022-10-20 13:35:02 +02:00
|
|
|
|
|
2021-10-03 10:20:24 +11:00
|
|
|
|
if allow_duplicate:
|
2022-10-20 13:35:02 +02:00
|
|
|
|
if with_attribute_indices:
|
|
|
|
|
|
idxs = self.wrapped_data.get_inverse_indices(inst.wrapped_data)
|
2025-01-27 13:07:07 +05:00
|
|
|
|
# TODO: include in typing.
|
2022-10-20 13:35:02 +02:00
|
|
|
|
return list(zip(inverses, idxs))
|
|
|
|
|
|
else:
|
|
|
|
|
|
return inverses
|
|
|
|
|
|
|
2021-10-03 10:20:24 +11:00
|
|
|
|
return set(inverses)
|
2017-11-06 09:10:28 +01:00
|
|
|
|
|
2024-03-04 15:46:36 +05:00
|
|
|
|
def get_total_inverses(self, inst: ifcopenshell.entity_instance) -> int:
|
2021-11-30 13:25:02 +11:00
|
|
|
|
"""Returns the number of entities that reference this entity
|
|
|
|
|
|
|
2024-05-23 20:15:01 +10:00
|
|
|
|
This is equivalent to `len(model.get_inverse(element))`, but
|
|
|
|
|
|
significantly faster.
|
|
|
|
|
|
|
2021-11-30 13:25:02 +11:00
|
|
|
|
:param inst: The entity instance to get inverse relationships
|
2021-12-01 15:05:00 +11:00
|
|
|
|
:returns: The total number of references
|
2021-11-30 13:25:02 +11:00
|
|
|
|
"""
|
2021-12-01 15:05:00 +11:00
|
|
|
|
return self.wrapped_data.get_total_inverses(inst.wrapped_data)
|
2021-11-30 13:25:02 +11:00
|
|
|
|
|
2024-03-04 15:46:36 +05:00
|
|
|
|
def remove(self, inst: ifcopenshell.entity_instance) -> None:
|
2020-04-02 05:53:14 +02:00
|
|
|
|
"""Deletes an IFC object in the file.
|
|
|
|
|
|
|
2020-05-21 08:25:47 +10:00
|
|
|
|
Attribute values in other entity instances that reference the deleted
|
|
|
|
|
|
object will be set to null. In the case of a list or set of references,
|
|
|
|
|
|
the reference to the deleted will be removed from the aggregate.
|
2020-04-02 05:53:14 +02:00
|
|
|
|
|
|
|
|
|
|
:param inst: The entity instance to delete
|
|
|
|
|
|
"""
|
2021-06-30 18:34:12 +10:00
|
|
|
|
if self.transaction:
|
|
|
|
|
|
self.transaction.store_delete(inst)
|
2016-07-18 15:53:20 +02:00
|
|
|
|
return self.wrapped_data.remove(inst.wrapped_data)
|
2021-06-30 18:34:12 +10:00
|
|
|
|
|
2021-03-13 12:16:40 +01:00
|
|
|
|
def batch(self):
|
|
|
|
|
|
"""Low-level mechanism to speed up deletion of large subgraphs"""
|
2021-07-05 14:16:06 +10:00
|
|
|
|
if self.transaction:
|
|
|
|
|
|
self.transaction.batch()
|
2021-03-13 12:16:40 +01:00
|
|
|
|
return self.wrapped_data.batch()
|
2021-06-30 18:34:12 +10:00
|
|
|
|
|
2021-03-13 12:16:40 +01:00
|
|
|
|
def unbatch(self):
|
|
|
|
|
|
"""Low-level mechanism to speed up deletion of large subgraphs"""
|
2021-07-05 14:16:06 +10:00
|
|
|
|
if self.transaction:
|
|
|
|
|
|
self.transaction.unbatch()
|
2021-03-13 12:16:40 +01:00
|
|
|
|
return self.wrapped_data.unbatch()
|
2017-11-06 09:10:28 +01:00
|
|
|
|
|
2024-05-31 18:02:20 +05:00
|
|
|
|
def __iter__(self) -> Generator[ifcopenshell.entity_instance, None, None]:
|
2016-07-18 15:53:20 +02:00
|
|
|
|
return iter(self[id] for id in self.wrapped_data.entity_names())
|
2017-11-06 09:10:28 +01:00
|
|
|
|
|
2024-12-23 11:35:48 +05:00
|
|
|
|
def assign_header_from(self, other: ifcopenshell.file) -> None:
|
2024-09-03 18:49:05 +02:00
|
|
|
|
for k, vs in HEADER_FIELDS.items():
|
|
|
|
|
|
for v in vs:
|
2025-02-07 12:38:52 +11:00
|
|
|
|
try:
|
|
|
|
|
|
setattr(getattr(self.header, k), v, getattr(getattr(other.header, k), v))
|
|
|
|
|
|
except:
|
|
|
|
|
|
pass # Header is invalid
|
2024-09-03 18:49:05 +02:00
|
|
|
|
|
2024-05-07 17:32:47 +10:00
|
|
|
|
def write(self, path: "os.PathLike | str", format: Optional[str] = None, zipped: bool = False) -> None:
|
2022-11-26 05:19:01 +01:00
|
|
|
|
"""Write ifc model to file.
|
|
|
|
|
|
|
2024-05-07 17:32:47 +10:00
|
|
|
|
:param format: Force use of a specific format. Guessed from file name
|
|
|
|
|
|
if None. Supported formats : .ifc, .ifcXML, .ifcZIP (equivalent to
|
|
|
|
|
|
format=".ifc" with zipped=True) For zipped .ifcXML use
|
|
|
|
|
|
format=".ifcXML" with zipped=True
|
2022-11-26 05:19:01 +01:00
|
|
|
|
:param zipped: zip the file after it is written
|
|
|
|
|
|
|
2024-05-07 17:32:47 +10:00
|
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
|
|
model.write("path/to/model.ifc")
|
|
|
|
|
|
model.write("path/to/model.ifcXML")
|
|
|
|
|
|
model.write("path/to/model.ifcZIP")
|
|
|
|
|
|
model.write("path/to/model.ifcZIP", format=".ifcXML", zipped=True)
|
|
|
|
|
|
model.write("path/to/model.anyextension", format=".ifcXML")
|
2022-11-26 05:19:01 +01:00
|
|
|
|
"""
|
|
|
|
|
|
path = Path(path)
|
2022-12-19 01:44:40 +01:00
|
|
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
2025-03-17 16:47:42 +05:30
|
|
|
|
|
2022-11-26 05:19:01 +01:00
|
|
|
|
if format == None:
|
2024-05-07 10:32:02 +10:00
|
|
|
|
format = ifcopenshell.guess_format(path)
|
2022-11-26 05:19:01 +01:00
|
|
|
|
if format == ".ifcXML":
|
|
|
|
|
|
serializer = ifcopenshell_wrapper.XmlSerializer(self, str(path))
|
|
|
|
|
|
serializer.finalize()
|
|
|
|
|
|
if zipped:
|
|
|
|
|
|
unzipped_path = path.with_suffix(format)
|
|
|
|
|
|
path.rename(unzipped_path)
|
|
|
|
|
|
with zipfile.ZipFile(path, "w") as zip_file:
|
|
|
|
|
|
zip_file.write(unzipped_path, unzipped_path.name, compress_type=zipfile.ZIP_DEFLATED)
|
|
|
|
|
|
unzipped_path.unlink()
|
|
|
|
|
|
return
|
|
|
|
|
|
if format == ".ifcZIP":
|
|
|
|
|
|
return self.write(path, ".ifc", zipped=True)
|
|
|
|
|
|
self.wrapped_data.write(str(path))
|
2025-03-17 16:38:58 +05:00
|
|
|
|
|
2022-11-26 05:19:01 +01:00
|
|
|
|
if zipped:
|
|
|
|
|
|
unzipped_path = path.with_suffix(format)
|
|
|
|
|
|
path.rename(unzipped_path)
|
|
|
|
|
|
with zipfile.ZipFile(path, "w") as zip_file:
|
2023-07-08 12:07:28 +02:00
|
|
|
|
zip_file.write(
|
|
|
|
|
|
unzipped_path,
|
|
|
|
|
|
unzipped_path.name,
|
|
|
|
|
|
compress_type=zipfile.ZIP_DEFLATED,
|
|
|
|
|
|
)
|
2022-11-26 05:19:01 +01:00
|
|
|
|
unzipped_path.unlink()
|
|
|
|
|
|
return
|
|
|
|
|
|
|
2017-08-22 10:16:07 +02:00
|
|
|
|
@staticmethod
|
2024-05-07 17:32:47 +10:00
|
|
|
|
def from_string(s: str) -> "file":
|
2017-08-22 10:16:07 +02:00
|
|
|
|
return file(ifcopenshell_wrapper.read(s))
|
2023-07-08 12:07:28 +02:00
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
2024-07-25 16:41:02 +05:00
|
|
|
|
def from_pointer(v) -> "file":
|
2024-01-16 09:44:04 +01:00
|
|
|
|
return file_dict.get(v)()
|
2024-10-01 17:20:14 +05:00
|
|
|
|
|
|
|
|
|
|
def to_string(self) -> str:
|
|
|
|
|
|
return self.wrapped_data.to_string()
|