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/>.
|
|
|
|
|
|
2017-11-01 10:41:27 +01:00
|
|
|
|
2025-06-10 10:34:14 +05:00
|
|
|
from __future__ import annotations
|
2025-12-19 18:53:04 +05:00
|
|
|
|
2016-06-22 15:03:18 +02:00
|
|
|
import functools
|
2022-12-26 11:29:32 +01:00
|
|
|
import importlib
|
2016-06-22 15:03:18 +02:00
|
|
|
import itertools
|
2025-12-19 18:53:04 +05:00
|
|
|
import numbers
|
2023-02-05 11:47:10 +01:00
|
|
|
import operator
|
2023-10-12 10:37:44 +02:00
|
|
|
import subprocess
|
|
|
|
|
import sys
|
|
|
|
|
import time
|
2025-06-09 10:21:22 +05:00
|
|
|
from collections.abc import Callable, Sequence
|
2025-12-19 18:53:04 +05:00
|
|
|
from typing import TYPE_CHECKING, Any, NoReturn, TypeVar, Union, cast, overload
|
2016-06-22 15:03:18 +02:00
|
|
|
|
2025-12-19 18:53:04 +05:00
|
|
|
from . import ifcopenshell_wrapper, settings
|
2016-06-22 15:03:18 +02:00
|
|
|
|
2025-06-10 10:34:14 +05:00
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
import ifcopenshell
|
|
|
|
|
|
2017-03-09 11:47:54 +01:00
|
|
|
try:
|
|
|
|
|
import logging
|
2024-05-07 19:08:13 +10:00
|
|
|
except ImportError:
|
2020-11-01 20:08:27 +07:00
|
|
|
logging = type("logger", (object,), {"exception": staticmethod(lambda s: print(s))})
|
2017-03-09 11:47:54 +01:00
|
|
|
|
2024-03-29 12:38:37 +05:00
|
|
|
T = TypeVar("T")
|
|
|
|
|
|
2017-01-04 09:15:52 +01:00
|
|
|
|
2026-01-04 10:40:02 +01:00
|
|
|
class entity_instance_mixin:
|
2024-05-07 19:08:13 +10:00
|
|
|
"""Represents an entity (wall, slab, property, etc) of an IFC model
|
|
|
|
|
|
|
|
|
|
An IFC model consists of entities. Examples of entities include walls,
|
|
|
|
|
slabs, doors and so on. Entities can also be non-physical things, like
|
|
|
|
|
properties, systems, construction tasks, colours, geometry, and more.
|
|
|
|
|
|
|
|
|
|
Entities are defined through an **IFC Class**. There are hundreds of **IFC
|
|
|
|
|
Classes** defined as part of the ISO standard by the buildingSMART
|
|
|
|
|
International organisation. The **IFC Class** defines the attributes of an
|
|
|
|
|
entity, as well as the data types and whether or not an attribute is
|
|
|
|
|
mandatory or optional.
|
|
|
|
|
|
|
|
|
|
IfcOpenShell's API dynamically implements the IFC schema. You will not find
|
|
|
|
|
documentation about available **IFC Classes**, or what attributes they
|
|
|
|
|
have. Please consult the buildingSMART official documentation or start
|
|
|
|
|
reading :doc:`/introduction/introduction_to_ifc`.
|
2017-12-04 09:16:30 -08:00
|
|
|
|
2024-05-07 19:08:13 +10:00
|
|
|
In addition to the Python methods you see documented here, an instantiated
|
|
|
|
|
entity_instance will have attributes defined by its IFC class. For example,
|
|
|
|
|
an entity instance which is an IfcWall class will have a ``Name``
|
|
|
|
|
attribute, and an IfcColourRgb will have a ``Red`` attribute. Please
|
|
|
|
|
consult the buildingSMART official documentation.
|
2017-12-04 09:16:30 -08:00
|
|
|
|
2023-01-10 10:16:28 +11:00
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
2017-12-04 09:16:30 -08:00
|
|
|
|
2024-05-07 19:08:13 +10:00
|
|
|
model = ifcopenshell.open(file_path)
|
|
|
|
|
walls = model.by_type("IfcWall")
|
|
|
|
|
wall = walls[0]
|
|
|
|
|
|
|
|
|
|
print(wall) # #38=IFCWALL('2MEinnTPbCMwLOgceaQZFu',$,$,'My Wall',$,#52,#47,$,$);
|
|
|
|
|
print(wall.is_a()) # IfcWall
|
|
|
|
|
|
|
|
|
|
# Note: the `Name` attribute is dynamic, based on the IFC class.
|
|
|
|
|
print(wall.Name) # My Wall
|
|
|
|
|
|
|
|
|
|
# Attributes are ordered and may also be accessed via index.
|
|
|
|
|
print(wall[3]) # My Wall
|
|
|
|
|
|
|
|
|
|
print(wall.__class__) # <class 'ifcopenshell.entity_instance'>
|
2017-12-04 09:16:30 -08:00
|
|
|
"""
|
2020-11-01 20:08:27 +07:00
|
|
|
|
2024-06-04 13:50:00 +05:00
|
|
|
def __getattr__(self, name: str) -> Any:
|
2026-01-04 10:40:02 +01:00
|
|
|
if name in ("this", "thisown") or name.startswith("_swig_"):
|
|
|
|
|
return object.__getattr__(self, name)
|
2025-04-25 16:14:31 +05:00
|
|
|
"""
|
|
|
|
|
Any aggregate attributes (e.g. `SET`) are returns as Python tuples.
|
|
|
|
|
|
2026-01-04 10:40:02 +01:00
|
|
|
Inverse attributes are returned as tuples, even it's not a set origially in IFC
|
2025-04-25 16:14:31 +05:00
|
|
|
(e.g. IfcFeatureElementSubtraction.VoidsElements)
|
2026-01-04 10:40:02 +01:00
|
|
|
(unless settings.unpack_non_aggregate_inverses is used, which is necessary for express rule execution)
|
2025-04-25 16:14:31 +05:00
|
|
|
"""
|
2026-01-04 10:40:02 +01:00
|
|
|
INVALID, FORWARD, INVERSE, DERIVED = range(4)
|
|
|
|
|
attr_cat = self.get_attribute_category(name)
|
2026-01-13 08:43:52 +01:00
|
|
|
if attr_cat == FORWARD:
|
2026-01-04 10:40:02 +01:00
|
|
|
idx = self.get_argument_index(name)
|
|
|
|
|
return self.get_argument(idx)
|
2016-07-18 15:53:20 +02:00
|
|
|
elif attr_cat == INVERSE:
|
2026-05-06 11:41:43 +02:00
|
|
|
vs = self._get_inverse(name)
|
2023-02-14 09:52:17 +01:00
|
|
|
if settings.unpack_non_aggregate_inverses:
|
2026-01-04 10:40:02 +01:00
|
|
|
schema_name = self.is_a(True).split(".")[0]
|
2025-06-11 11:40:25 +05:00
|
|
|
ent: ifcopenshell_wrapper.entity
|
2023-06-16 16:17:17 +10:00
|
|
|
ent = ifcopenshell_wrapper.schema_by_name(schema_name).declaration_by_name(self.is_a())
|
2025-06-11 12:49:47 +05:00
|
|
|
inv = next(i for i in ent.all_inverse_attributes() if i.name() == name)
|
2023-02-14 09:52:17 +01:00
|
|
|
if (inv.bound1(), inv.bound2()) == (-1, -1):
|
|
|
|
|
if vs:
|
|
|
|
|
vs = vs[0]
|
|
|
|
|
else:
|
|
|
|
|
vs = None
|
|
|
|
|
return vs
|
2026-01-13 08:43:52 +01:00
|
|
|
else:
|
2026-01-04 10:40:02 +01:00
|
|
|
schema_name = self.is_a(True).split(".")[0]
|
|
|
|
|
try:
|
|
|
|
|
rules = importlib.import_module(f"ifcopenshell.express.rules.{schema_name}")
|
|
|
|
|
except:
|
|
|
|
|
import os
|
|
|
|
|
|
|
|
|
|
current_dir_files = {fn.lower(): fn for fn in os.listdir(".")}
|
|
|
|
|
exp_filename = schema_name.lower() + ".exp"
|
|
|
|
|
schema_path = current_dir_files.get(exp_filename)
|
|
|
|
|
if schema_path is None:
|
|
|
|
|
raise Exception(
|
|
|
|
|
f"Couldn't find express file '{schema_name.lower()}.exp' in the current folder: '{os.getcwd()}'."
|
|
|
|
|
)
|
|
|
|
|
fn = schema_path[:-4] + ".py"
|
|
|
|
|
if not os.path.exists(fn):
|
|
|
|
|
subprocess.run(
|
|
|
|
|
[sys.executable, "-m", "ifcopenshell.express.rule_compiler", schema_path, fn], check=True
|
|
|
|
|
)
|
|
|
|
|
time.sleep(1.0)
|
|
|
|
|
rules = importlib.import_module(schema_name)
|
2023-02-05 11:47:10 +01:00
|
|
|
|
2026-01-04 10:40:02 +01:00
|
|
|
def yield_supertypes():
|
|
|
|
|
decl = ifcopenshell_wrapper.schema_by_name(schema_name).declaration_by_name(self.is_a())
|
|
|
|
|
while decl:
|
|
|
|
|
yield decl.name()
|
|
|
|
|
decl = decl.supertype()
|
2022-12-26 11:29:32 +01:00
|
|
|
|
2026-01-04 10:40:02 +01:00
|
|
|
for sty in yield_supertypes():
|
2026-01-13 08:43:52 +01:00
|
|
|
if fn := getattr(rules, f"calc_{sty}_{name}", None):
|
2026-01-04 10:40:02 +01:00
|
|
|
return fn(self)
|
2026-01-14 14:09:40 +01:00
|
|
|
|
2026-01-13 08:43:52 +01:00
|
|
|
raise AttributeError("entity instance of type '%s' has no attribute '%s'" % (self.is_a(True), name))
|
2017-01-04 09:15:52 +01:00
|
|
|
|
2016-07-18 15:53:20 +02:00
|
|
|
@staticmethod
|
2024-03-11 12:06:56 +05:00
|
|
|
def walk(f: Callable[[Any], bool], g: Callable[[Any], Any], value: Any) -> Any:
|
2024-05-07 17:32:47 +10:00
|
|
|
"""Applies a transformation to `value` based on a given condition.
|
|
|
|
|
|
|
|
|
|
If value is a nested structure (e.g., a list or a tuple) will apply
|
|
|
|
|
transformation to it's elements.
|
|
|
|
|
|
|
|
|
|
:param f: A callable that takes a single argument and returns a boolean
|
|
|
|
|
value. It represents the condition.
|
|
|
|
|
:param g: A callable that takes a single argument and returns a
|
|
|
|
|
transformed value. It represents the transformation.
|
|
|
|
|
:param value: Any object, the input value to be processed
|
|
|
|
|
:return: Transformed value
|
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
# Define condition and transformation functions
|
|
|
|
|
condition = lambda v: v == old
|
|
|
|
|
transform = lambda v: new
|
2024-07-26 12:00:28 +10:00
|
|
|
|
2024-05-07 17:32:47 +10:00
|
|
|
# Usage example
|
|
|
|
|
attribute_value = element.RelatedElements
|
|
|
|
|
print(old in attribute_value, new in attribute_value) # True, False
|
|
|
|
|
|
|
|
|
|
result = element.walk(condition, transform, element.RelatedElements)
|
|
|
|
|
print(old in attribute_value, new in attribute_value) # False, True
|
2024-03-11 12:06:56 +05:00
|
|
|
"""
|
|
|
|
|
|
2017-01-04 09:15:52 +01:00
|
|
|
if isinstance(value, (tuple, list)):
|
2026-01-04 10:40:02 +01:00
|
|
|
return tuple(map(functools.partial(entity_instance_mixin.walk, f, g), value))
|
2017-01-04 09:15:52 +01:00
|
|
|
elif f(value):
|
|
|
|
|
return g(value)
|
|
|
|
|
else:
|
|
|
|
|
return value
|
|
|
|
|
|
2024-03-29 12:38:37 +05:00
|
|
|
def __setattr__(self, key: str, value: Any) -> None:
|
2026-01-04 10:40:02 +01:00
|
|
|
if key in ("this", "thisown") or key.startswith("_swig_"):
|
|
|
|
|
return object.__setattr__(self, key, value)
|
|
|
|
|
|
|
|
|
|
index = self.get_argument_index(key)
|
2024-04-25 12:14:55 +05:00
|
|
|
try:
|
|
|
|
|
self[index] = value
|
|
|
|
|
except IndexError as e:
|
|
|
|
|
# get_argument_index returns 0xFFFFFFFF if attribute is not found
|
|
|
|
|
if index == 0xFFFFFFFF:
|
2026-01-10 11:52:08 +01:00
|
|
|
raise AttributeError("entity instance of type '%s' has no attribute '%s'" % (self.is_a(True), key))
|
2024-04-25 12:14:55 +05:00
|
|
|
raise e
|
2017-01-04 09:15:52 +01:00
|
|
|
|
2024-03-29 12:38:37 +05:00
|
|
|
def __getitem__(self, key: int) -> Any:
|
2017-08-02 16:05:56 +02:00
|
|
|
if key < 0 or key >= len(self):
|
2023-06-16 16:17:17 +10:00
|
|
|
raise IndexError("Attribute index {} out of range for instance of type {}".format(key, self.is_a()))
|
2026-01-04 10:40:02 +01:00
|
|
|
return self.get_argument(key)
|
2017-01-04 09:15:52 +01:00
|
|
|
|
2024-03-29 12:38:37 +05:00
|
|
|
def __setitem__(self, idx: int, value: T) -> T:
|
2026-01-04 10:40:02 +01:00
|
|
|
if self.file and self.file.transaction:
|
|
|
|
|
self.file.transaction.store_edit(self, idx, value)
|
2026-01-10 11:52:08 +01:00
|
|
|
|
2026-01-04 10:40:02 +01:00
|
|
|
self.set_attribute_value_py(idx, value)
|
2018-11-19 16:38:52 +01:00
|
|
|
|
2016-07-18 15:53:20 +02:00
|
|
|
return value
|
2017-01-04 09:15:52 +01:00
|
|
|
|
2026-01-04 10:40:02 +01:00
|
|
|
def __eq__(self, other: entity_instance_mixin) -> bool:
|
2026-04-11 16:27:06 +10:00
|
|
|
if other is None or not isinstance(other, entity_instance_mixin):
|
2017-11-06 09:10:28 +01:00
|
|
|
return False
|
2022-09-04 14:17:06 +02:00
|
|
|
else:
|
2026-01-04 10:40:02 +01:00
|
|
|
raise NotImplementedError
|
2026-01-10 11:52:08 +01:00
|
|
|
|
2026-04-11 16:27:06 +10:00
|
|
|
def __ne__(self, other: entity_instance_mixin) -> bool:
|
|
|
|
|
if other is None or not isinstance(other, entity_instance_mixin):
|
|
|
|
|
return True
|
|
|
|
|
else:
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
2024-03-04 15:46:36 +05:00
|
|
|
def is_entity(self) -> bool:
|
2023-02-05 11:47:10 +01:00
|
|
|
"""Tests whether the instance is an entity type as opposed to a simple data type.
|
|
|
|
|
|
2025-11-17 14:20:54 +05:00
|
|
|
:return: True if the instance is an entity
|
2023-02-05 11:47:10 +01:00
|
|
|
"""
|
2026-01-15 15:38:32 +01:00
|
|
|
return isinstance(self.declaration, ifcopenshell_wrapper.entity)
|
2023-02-05 11:47:10 +01:00
|
|
|
|
|
|
|
|
def compare(self, other, op, reverse=False):
|
|
|
|
|
"""Compares with another instance.
|
|
|
|
|
|
|
|
|
|
For simple types the declaration name is not taken into account:
|
|
|
|
|
|
|
|
|
|
>>> f = ifcopenshell.file()
|
|
|
|
|
>>> f.createIfcInteger(0) < f.createIfcPositiveInteger(1)
|
|
|
|
|
True
|
|
|
|
|
|
|
|
|
|
For entity types the declaration name is taken into account:
|
|
|
|
|
|
|
|
|
|
>>> f.createIfcWall('a') < f.createIfcWall('b')
|
|
|
|
|
True
|
|
|
|
|
|
|
|
|
|
>>> f.createIfcWallStandardCase('a') < f.createIfcWall('b')
|
|
|
|
|
False
|
|
|
|
|
|
|
|
|
|
Comparing simple types with different underlying types throws an exception:
|
|
|
|
|
|
|
|
|
|
>>> f.createIfcInteger(0) < f.createIfcLabel('x')
|
|
|
|
|
Traceback (most recent call last):
|
|
|
|
|
File "<stdin>", line 1, in <module>
|
|
|
|
|
File "entity_instance.py", line 371, in compare
|
|
|
|
|
return op(a, b)
|
|
|
|
|
TypeError: '<' not supported between instances of 'int' and 'str'
|
|
|
|
|
|
2025-11-17 14:20:54 +05:00
|
|
|
:param other: Right hand side (or lhs when reverse = True)
|
|
|
|
|
:param op: The comparison operator (likely from the operator module)
|
|
|
|
|
:param reverse: When true swaps lhs and rhs. Defaults to False.
|
2023-02-05 11:47:10 +01:00
|
|
|
|
2025-11-17 14:20:54 +05:00
|
|
|
:return: bool: The comparison predicate applied to self and other
|
2023-02-05 11:47:10 +01:00
|
|
|
"""
|
|
|
|
|
|
2026-01-04 10:40:02 +01:00
|
|
|
if isinstance(other, entity_instance_mixin):
|
2023-02-05 11:47:10 +01:00
|
|
|
a, b = map(tuple, (self, other))
|
2026-01-04 10:40:02 +01:00
|
|
|
if any(map(entity_instance_mixin.is_entity, (self, other))):
|
2023-02-05 11:47:10 +01:00
|
|
|
a = (self.is_a(),) + a
|
|
|
|
|
b = (other.is_a(),) + b
|
|
|
|
|
elif self.is_entity():
|
|
|
|
|
a = tuple(self)
|
|
|
|
|
b = other
|
|
|
|
|
if isinstance(b, list):
|
|
|
|
|
b = tuple(b)
|
|
|
|
|
if not isinstance(b, tuple):
|
|
|
|
|
b = (b,)
|
|
|
|
|
else:
|
|
|
|
|
a = self[0]
|
|
|
|
|
b = other
|
|
|
|
|
|
|
|
|
|
if reverse:
|
|
|
|
|
a, b = b, a
|
|
|
|
|
|
|
|
|
|
return op(a, b)
|
|
|
|
|
|
|
|
|
|
__le__ = functools.partialmethod(compare, op=operator.le)
|
|
|
|
|
__lt__ = functools.partialmethod(compare, op=operator.lt)
|
|
|
|
|
__ge__ = functools.partialmethod(compare, op=operator.ge)
|
|
|
|
|
__gt__ = functools.partialmethod(compare, op=operator.gt)
|
|
|
|
|
__rle__ = functools.partialmethod(compare, op=operator.le, reverse=True)
|
|
|
|
|
__rlt__ = functools.partialmethod(compare, op=operator.lt, reverse=True)
|
|
|
|
|
__rge__ = functools.partialmethod(compare, op=operator.ge, reverse=True)
|
|
|
|
|
__rgt__ = functools.partialmethod(compare, op=operator.gt, reverse=True)
|
|
|
|
|
|
2016-07-18 15:53:20 +02:00
|
|
|
def __dir__(self):
|
2020-11-01 20:08:27 +07:00
|
|
|
return sorted(
|
|
|
|
|
set(
|
|
|
|
|
itertools.chain(
|
|
|
|
|
dir(type(self)),
|
2026-01-04 10:40:02 +01:00
|
|
|
map(str, self.get_attribute_names()),
|
|
|
|
|
map(str, self.get_inverse_attribute_names()),
|
2020-11-01 20:08:27 +07:00
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
)
|
2017-01-04 09:15:52 +01:00
|
|
|
|
2024-03-04 15:46:36 +05:00
|
|
|
def get_info(
|
2024-10-02 18:24:01 +05:00
|
|
|
self,
|
|
|
|
|
include_identifier: bool = True,
|
|
|
|
|
recursive: bool = False,
|
|
|
|
|
return_type: Union[type[dict], type] = dict,
|
2024-12-27 13:39:55 +05:00
|
|
|
ignore: Sequence[str] = (),
|
2024-10-02 18:24:01 +05:00
|
|
|
scalar_only: bool = False,
|
|
|
|
|
) -> dict[str, Any]:
|
2020-04-02 05:53:14 +02:00
|
|
|
"""Return a dictionary of the entity_instance's properties (Python and IFC) and their values.
|
|
|
|
|
|
2025-07-08 12:55:49 +05:00
|
|
|
Resulting dictionary keys: 'id', 'type', all entity attribute names.
|
|
|
|
|
|
2020-04-02 05:53:14 +02:00
|
|
|
:param include_identifier: Whether or not to include the STEP numerical identifier
|
|
|
|
|
:param recursive: Whether or not to convert referenced IFC elements into dictionaries too. All attributes also apply recursively
|
|
|
|
|
:param return_type: The return data type to be casted into
|
|
|
|
|
:param ignore: A list of attribute names to ignore
|
2023-02-14 20:11:24 +01:00
|
|
|
:param scalar_only: Filters out all values that are IFC instances
|
2020-04-02 05:53:14 +02:00
|
|
|
:returns: A dictionary of properties and their corresponding values
|
|
|
|
|
|
2023-01-10 10:16:28 +11:00
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
2020-04-02 05:53:14 +02:00
|
|
|
|
|
|
|
|
ifc_file = ifcopenshell.open(file_path)
|
|
|
|
|
products = ifc_file.by_type("IfcProduct")
|
|
|
|
|
obj_info = products[0].get_info()
|
|
|
|
|
print(obj_info.keys())
|
|
|
|
|
>>> dict_keys(['Description', 'Name', 'BuildingAddress', 'LongName', 'GlobalId', 'ObjectPlacement', 'OwnerHistory', 'ObjectType',
|
|
|
|
|
>>> ...'ElevationOfTerrain', 'CompositionType', 'id', 'Representation', 'type', 'ElevationOfRefHeight'])
|
2017-12-04 09:16:30 -08:00
|
|
|
"""
|
2020-11-01 20:08:27 +07:00
|
|
|
|
2017-08-04 16:30:43 +02:00
|
|
|
def _():
|
2017-01-01 16:19:39 +01:00
|
|
|
try:
|
2017-08-04 16:30:43 +02:00
|
|
|
if include_identifier:
|
|
|
|
|
yield "id", self.id()
|
|
|
|
|
yield "type", self.is_a()
|
2017-11-06 11:06:40 +01:00
|
|
|
except BaseException:
|
2023-06-16 16:17:17 +10:00
|
|
|
logging.exception("unhandled exception while getting id / type info on {}".format(self))
|
2017-08-04 16:30:43 +02:00
|
|
|
for i in range(len(self)):
|
|
|
|
|
try:
|
2026-01-04 10:40:02 +01:00
|
|
|
if self.get_attribute_names()[i] in ignore:
|
2017-08-04 16:30:43 +02:00
|
|
|
continue
|
|
|
|
|
attr_value = self[i]
|
2023-02-14 20:11:24 +01:00
|
|
|
|
2023-06-16 16:17:17 +10:00
|
|
|
to_include = {"v": True}
|
2023-02-15 09:52:21 +01:00
|
|
|
|
2023-02-14 20:11:24 +01:00
|
|
|
if recursive or scalar_only:
|
2020-11-01 20:08:27 +07:00
|
|
|
|
|
|
|
|
def is_instance(e):
|
2026-01-04 10:40:02 +01:00
|
|
|
return isinstance(e, entity_instance_mixin)
|
2017-11-06 09:10:28 +01:00
|
|
|
|
2017-08-04 16:30:43 +02:00
|
|
|
def get_info_(inst):
|
2026-01-04 10:40:02 +01:00
|
|
|
return entity_instance_mixin.get_info(
|
2020-11-01 20:08:27 +07:00
|
|
|
inst,
|
|
|
|
|
include_identifier=include_identifier,
|
|
|
|
|
recursive=recursive,
|
|
|
|
|
return_type=return_type,
|
|
|
|
|
ignore=ignore,
|
|
|
|
|
)
|
2017-11-06 11:06:40 +01:00
|
|
|
|
2023-02-14 20:11:24 +01:00
|
|
|
def do_ignore(inst):
|
2023-06-16 16:17:17 +10:00
|
|
|
to_include["v"] = False
|
2023-02-14 20:11:24 +01:00
|
|
|
return None
|
|
|
|
|
|
2026-01-04 10:40:02 +01:00
|
|
|
attr_value = entity_instance_mixin.walk(
|
2023-02-14 20:11:24 +01:00
|
|
|
is_instance, get_info_ if recursive else do_ignore, attr_value
|
2022-10-04 10:21:45 +02:00
|
|
|
)
|
2023-02-14 20:11:24 +01:00
|
|
|
|
2023-06-16 16:17:17 +10:00
|
|
|
if to_include["v"]:
|
2023-02-14 20:11:24 +01:00
|
|
|
yield self.attribute_name(i), attr_value
|
2017-11-06 11:06:40 +01:00
|
|
|
except BaseException:
|
2023-06-16 16:17:17 +10:00
|
|
|
logging.exception("unhandled exception occurred setting attribute name for {}".format(self))
|
2017-11-06 11:06:40 +01:00
|
|
|
|
2017-08-04 16:30:43 +02:00
|
|
|
return return_type(_())
|
2017-01-04 09:15:52 +01:00
|
|
|
|
|
|
|
|
__dict__ = property(get_info)
|
2021-06-30 18:34:12 +10:00
|
|
|
|
2024-10-02 18:24:01 +05:00
|
|
|
def get_info_2(
|
|
|
|
|
self,
|
|
|
|
|
include_identifier: bool = True,
|
|
|
|
|
recursive: bool = False,
|
|
|
|
|
return_type: type[dict] = dict,
|
2025-03-05 14:56:29 +05:00
|
|
|
ignore: Sequence[str] = (),
|
2024-10-02 18:24:01 +05:00
|
|
|
) -> dict[str, Any]:
|
2024-02-01 16:30:50 +05:00
|
|
|
"""More perfomant version of `.get_info()` but with limited arguments values.\n
|
|
|
|
|
Method has exactly the same signature as `.get_info()` but it doesn't support getting information non-recursively.
|
|
|
|
|
|
|
|
|
|
Currently supported arguments values:
|
|
|
|
|
* recursive: `True` (will fail with default `False` value from `.get_info()`)
|
|
|
|
|
* return_type: `dict`
|
|
|
|
|
* ignore: `()` (empty tuple)
|
|
|
|
|
"""
|
|
|
|
|
|
2021-01-10 12:36:47 +01:00
|
|
|
assert return_type is dict
|
|
|
|
|
assert len(ignore) == 0
|
2026-01-15 12:37:12 +01:00
|
|
|
return ifcopenshell_wrapper.get_info_cpp(self, recursive, include_identifier)
|
2026-04-11 16:27:06 +10:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# Alias for backwards compatibility — external code imports this name.
|
|
|
|
|
entity_instance = entity_instance_mixin
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Monkey-patch SWIG's __eq__, __ne__, __lt__ on the generated entity_instance
|
|
|
|
|
# class to guard against None / non-entity arguments. SWIG generates these
|
|
|
|
|
# directly on the class (overriding the mixin), and they pass arguments straight
|
|
|
|
|
# to C++ which rejects null references.
|
|
|
|
|
# Deferred until after ifcopenshell_wrapper finishes loading to avoid circular import.
|
|
|
|
|
_swig_comparisons_patched = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _patch_swig_comparisons():
|
|
|
|
|
global _swig_comparisons_patched
|
|
|
|
|
if _swig_comparisons_patched:
|
|
|
|
|
return
|
|
|
|
|
_swig_cls = ifcopenshell_wrapper.entity_instance
|
|
|
|
|
_orig_eq = _swig_cls.__eq__
|
|
|
|
|
_orig_ne = _swig_cls.__ne__
|
|
|
|
|
_orig_lt = _swig_cls.__lt__
|
|
|
|
|
|
|
|
|
|
def _safe_eq(self, other):
|
|
|
|
|
if other is None or not isinstance(other, _swig_cls):
|
|
|
|
|
return NotImplemented
|
|
|
|
|
return _orig_eq(self, other)
|
|
|
|
|
|
|
|
|
|
def _safe_ne(self, other):
|
|
|
|
|
if other is None or not isinstance(other, _swig_cls):
|
|
|
|
|
return NotImplemented
|
|
|
|
|
return _orig_ne(self, other)
|
|
|
|
|
|
|
|
|
|
def _safe_lt(self, other):
|
|
|
|
|
if other is None or not isinstance(other, _swig_cls):
|
|
|
|
|
return NotImplemented
|
|
|
|
|
return _orig_lt(self, other)
|
|
|
|
|
|
|
|
|
|
_swig_cls.__eq__ = _safe_eq
|
|
|
|
|
_swig_cls.__ne__ = _safe_ne
|
|
|
|
|
_swig_cls.__lt__ = _safe_lt
|
|
|
|
|
_swig_comparisons_patched = True
|