Work towards v1.0 data model with encapsulated weak_ptr as basis for instances

This commit is contained in:
Thomas Krijnen
2026-01-04 10:40:02 +01:00
parent f09ca658f1
commit 7098beb819
210 changed files with 28269 additions and 26471 deletions
@@ -88,12 +88,12 @@ except Exception:
# `_file`, `_stream` is used only for annotations inside this file,
# see https://github.com/microsoft/pyright/discussions/9065.
from .file import file as _file
from .file import file
from .ifcopenshell_wrapper import file as _file
from .ifcopenshell_wrapper import file
from .file import rocksdb_lazy_instance
from . import guid
from .entity_instance import entity_instance, register_schema_attributes
from .ifcopenshell_wrapper import entity_instance
from .sql import sqlite, sqlite_entity
# explicitly specify available imported symbols
@@ -251,7 +251,6 @@ def register_schema(schema: ifcopenshell.express.schema_class.SchemaClass) -> No
schema.schema.this.disown()
schema.disown()
ifcopenshell_wrapper.register_schema(schema.schema)
register_schema_attributes(schema.schema)
def schema_by_name(
@@ -43,75 +43,7 @@ except ImportError:
T = TypeVar("T")
def set_derived_attribute(*args):
raise TypeError("Unable to set derived attribute")
def set_unsupported_attribute(*args):
raise TypeError("This is an unsupported attribute type")
# For every schema and its entities populate a list
# of functions for every entity attribute (including
# inherited attributes) to set that particular
# attribute by index.
# For example. IFC2X3.IfcWall with have a list of
# 9 methods. The first will point at
# ifcopenshell.ifcopenshell_wrapper.entity_instance.setArgumentAsString
# because the first attribute GlobalId ultimately
# is of type string.
# Previously, resolving the appropriate function was
# done for each invocation of __setitem__. Now this
# mapping is built once during initialization of the
# module.
MethodList = list[Callable[[ifcopenshell_wrapper.entity_instance, int, Any], Union[None, NoReturn]]]
"""List of setter methods for class attributes."""
_method_dict: dict[str, MethodList] = {}
"""Mapping of entity classes (e.g. 'IFC4.IfcWall') to MethodLists."""
def register_schema_attributes(schema: ifcopenshell_wrapper.schema_definition) -> None:
for decl in schema.declarations():
if hasattr(decl, "argument_types"):
fq_name = ".".join((schema.name(), decl.name()))
# get type strings as reported by IfcOpenShell C++
type_strs = decl.argument_types()
type_strs = cast(Sequence[str], type_strs)
# convert case for setter function
type_strs = [x.title().replace(" ", "") for x in type_strs]
# binary and enumeration are passed from python as string as well
type_strs = [x.replace("Binary", "String") for x in type_strs]
type_strs = [x.replace("Enumeration", "String") for x in type_strs]
# prefix to get method names
fn_names = ["setArgumentAs" + x for x in type_strs]
# resolve to actual functions in wrapper
functions = [
(
set_derived_attribute
if mname == "setArgumentAsDerived"
else (
set_unsupported_attribute
if mname == "setArgumentAsUnknown"
else getattr(ifcopenshell_wrapper.entity_instance, mname)
)
)
for mname in fn_names
]
_method_dict[fq_name] = functions
for nm in ifcopenshell_wrapper.schema_names():
schema = ifcopenshell_wrapper.schema_by_name(nm)
register_schema_attributes(schema)
class entity_instance:
class entity_instance_mixin:
"""Represents an entity (wall, slab, property, etc) of an IFC model
An IFC model consists of entities. Examples of entities include walls,
@@ -155,67 +87,33 @@ class entity_instance:
print(wall.__class__) # <class 'ifcopenshell.entity_instance'>
"""
wrapped_data: ifcopenshell_wrapper.entity_instance
method_list: Union[MethodList, None] = None
def __init__(
self,
e: Union[ifcopenshell_wrapper.entity_instance, tuple[str, str]],
file: Union[ifcopenshell.file, None] = None,
):
"""
:param e: Wrapper's ``entity_instance`` or a tuple ``(schema_identifier, ifc_class)``.
"""
# Instances of this class will be created and removed very often,
# so it's important to keep it very optimized.
if isinstance(e, tuple):
e = ifcopenshell_wrapper.new_IfcBaseClass(*e)
object.__setattr__(self, "wrapped_data", e)
# Make sure the file is not gc'ed while we have live instances
e.file = file
def __del__(self):
"""
#2471 while the precise chain of action is unclear, creating
instance references prevents file gc, even with all instance
refs deleted. This is a work-around for that.
"""
# Avoid infinite recursion if entity is failed to initialize
# and wrapped_data is unset. Hacky since we override
# both __dict__ and __dir__.
try:
wrapped_data = object.__getattribute__(self, "wrapped_data")
wrapped_data.file = None
except AttributeError:
return
@property
def file(self):
# ugh circular imports, name collisions
from . import file
return file.from_pointer(self.wrapped_data.file_pointer())
raise NotImplementedError
def __getattr__(self, name: str) -> Any:
if name in ("this", "thisown") or name.startswith("_swig_"):
return object.__getattr__(self, name)
"""
Any aggregate attributes (e.g. `SET`) are returns as Python tuples.
Inverse attributes are always returned as tuples, even it's not a set origially in IFC
Inverse attributes are returned as tuples, even it's not a set origially in IFC
(e.g. IfcFeatureElementSubtraction.VoidsElements)
(unless settings.unpack_non_aggregate_inverses is used, which is necessary for express rule execution)
"""
INVALID, FORWARD, INVERSE = range(3)
attr_cat = self.wrapped_data.get_attribute_category(name)
if attr_cat == FORWARD:
idx = self.wrapped_data.get_argument_index(name)
if _method_dict[self.is_a(True)][idx] != set_derived_attribute:
# A bit ugly, but we fall through to derived attribute handling below
return entity_instance.wrap_value(self.wrapped_data.get_argument(idx), self.wrapped_data.file)
INVALID, FORWARD, INVERSE, DERIVED = range(4)
attr_cat = self.get_attribute_category(name)
if attr_cat == INVALID:
raise AttributeError(
"entity instance of type '%s' has no attribute '%s'" % (self.is_a(True), name)
)
elif attr_cat == FORWARD:
idx = self.get_argument_index(name)
return self.get_argument(idx)
elif attr_cat == INVERSE:
vs = entity_instance.wrap_value(self.wrapped_data.get_inverse(name), self.wrapped_data.file)
vs = self.get_inverse(name)
if settings.unpack_non_aggregate_inverses:
schema_name = self.wrapped_data.is_a(True).split(".")[0]
schema_name = self.is_a(True).split(".")[0]
ent: ifcopenshell_wrapper.entity
ent = ifcopenshell_wrapper.schema_by_name(schema_name).declaration_by_name(self.is_a())
inv = next(i for i in ent.all_inverse_attributes() if i.name() == name)
@@ -225,44 +123,38 @@ class entity_instance:
else:
vs = None
return vs
elif attr_cat == DERIVED:
schema_name = self.is_a(True).split(".")[0]
try:
rules = importlib.import_module(f"ifcopenshell.express.rules.{schema_name}")
except:
import os
# derived attribute perhaps?
schema_name = self.wrapped_data.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)
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)
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()
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()
for sty in yield_supertypes():
fn = getattr(rules, f"calc_{sty}_{name}", None)
if fn:
return fn(self)
if attr_cat != FORWARD:
raise AttributeError(
"entity instance of type '%s' has no attribute '%s'" % (self.wrapped_data.is_a(True), name)
)
for sty in yield_supertypes():
fn = getattr(rules, f"calc_{sty}_{name}", None)
if fn:
return fn(self)
@staticmethod
def walk(f: Callable[[Any], bool], g: Callable[[Any], Any], value: Any) -> Any:
@@ -295,180 +187,53 @@ class entity_instance:
"""
if isinstance(value, (tuple, list)):
return tuple(map(functools.partial(entity_instance.walk, f, g), value))
return tuple(map(functools.partial(entity_instance_mixin.walk, f, g), value))
elif f(value):
return g(value)
else:
return value
@staticmethod
def wrap_value(v, file: ifcopenshell.file):
def wrap(e: ifcopenshell_wrapper.entity_instance) -> entity_instance:
return entity_instance(e, file)
def is_instance(e: Any) -> bool:
return isinstance(e, ifcopenshell_wrapper.entity_instance)
return entity_instance.walk(is_instance, wrap, v)
@staticmethod
def unwrap_value(v):
def unwrap(e):
return e.wrapped_data
def is_instance(e):
return isinstance(e, entity_instance)
return entity_instance.walk(is_instance, unwrap, v)
def attribute_type(self, attr: Union[int, str]) -> str:
"""Return the data type of a positional attribute of the element
:param attr: The index or name of the attribute
"""
attr_idx = attr if isinstance(attr, numbers.Integral) else self.wrapped_data.get_argument_index(attr)
return self.wrapped_data.get_argument_type(attr_idx)
def attribute_name(self, attr_idx: int) -> str:
"""Return the name of a positional attribute of the element
:param attr_idx: The index of the attribute
"""
return self.wrapped_data.get_argument_name(attr_idx)
def __setattr__(self, key: str, value: Any) -> None:
index = self.wrapped_data.get_argument_index(key)
if key in ("this", "thisown") or key.startswith("_swig_"):
return object.__setattr__(self, key, value)
index = self.get_argument_index(key)
try:
self[index] = value
except IndexError as e:
# get_argument_index returns 0xFFFFFFFF if attribute is not found
if index == 0xFFFFFFFF:
raise AttributeError(
"entity instance of type '%s' has no attribute '%s'" % (self.wrapped_data.is_a(True), key)
"entity instance of type '%s' has no attribute '%s'" % (self.is_a(True), key)
)
raise e
def __getitem__(self, key: int) -> Any:
if key < 0 or key >= len(self):
raise IndexError("Attribute index {} out of range for instance of type {}".format(key, self.is_a()))
return entity_instance.wrap_value(self.wrapped_data.get_argument(key), self.wrapped_data.file)
return self.get_argument(key)
def __setitem__(self, idx: int, value: T) -> T:
if self.wrapped_data.file and self.wrapped_data.file.transaction:
self.wrapped_data.file.transaction.store_edit(self, idx, value)
if self.method_list is None:
super().__setattr__("method_list", _method_dict[self.is_a(True)])
method = self.method_list[idx]
if value is None:
if method is not set_derived_attribute:
try:
self.wrapped_data.setArgumentAsNull(idx)
except RuntimeError as e:
if e.args == ("Attribute not set",):
raise TypeError(
"attribute '%s' is not optional for entity instance of type '%s'"
% (self.wrapped_data.get_argument_name(idx), self.wrapped_data.is_a(True))
)
raise e
else:
try:
self.method_list[idx](self.wrapped_data, idx, entity_instance.unwrap_value(value))
except TypeError:
raise TypeError(
"attribute '%s' for entity '%s' is expecting value of type '%s', got '%s'."
% (
self.wrapped_data.get_argument_name(idx),
self.wrapped_data.is_a(True),
self.wrapped_data.get_argument_type(idx),
type(value).__name__,
)
)
if self.file and self.file.transaction:
self.file.transaction.store_edit(self, idx, value)
self.set_attribute_value_py(idx, value)
return value
def __len__(self):
return len(self.wrapped_data)
def __repr__(self):
return repr(self.wrapped_data)
def to_string(self, valid_spf=True) -> str:
"""Returns a string representation of the current entity instance.
Equal to str(self) when valid_spf=False. When valid_spf is True
returns a representation of the string that conforms to valid Step
Physical File notation. The difference being entity names in upper
case and string attribute values with unicode values encoded per
the specific control directives.
"""
return self.wrapped_data.to_string(valid_spf)
@overload
def is_a(self) -> str: ...
@overload
def is_a(self, ifc_class: str) -> bool: ...
@overload
def is_a(self, with_schema: bool) -> str: ...
def is_a(self, *args: Union[str, bool]) -> Union[str, bool]:
"""Return the IFC class name of an instance, or checks if an instance belongs to a class.
The check will also return true if a parent class name is provided.
:param args: If specified, is a case insensitive IFC class name to check
or if specified as a boolean then will define whether
returned IFC class name should include schema name
(e.g. "IFC4.IfcWall" if `True` and "IfcWall" if `False`).
If omitted will act as `False`.
:returns: Either the name of the class, or a boolean if it passes the check
Example:
.. code:: python
f = ifcopenshell.file()
f.create_entity('IfcPerson')
f.is_a()
>>> 'IfcPerson'
f.is_a('IfcPerson')
>>> True
"""
return self.wrapped_data.is_a(*args)
def id(self) -> int:
"""Return the STEP numerical identifier"""
return self.wrapped_data.id()
def __eq__(self, other: entity_instance) -> bool:
def __eq__(self, other: entity_instance_mixin) -> bool:
if not isinstance(self, type(other)):
return False
elif None in (self.wrapped_data.file, other.wrapped_data.file):
# when not added to a file, we can only compare attribute values
# and we need this for where rule evaluation
return self.get_info_2(recursive=True, include_identifier=False) == other.get_info_2(
recursive=True, include_identifier=False
)
else:
# Proper entity instances have a stable identity by means of the numeric
# step id. Selected type instances (such as IfcPropertySingleValue.NominalValue
# always have id=0, so we compare <type, value, file pointer>
if self.id():
return self.wrapped_data == other.wrapped_data
else:
return (self.is_a(), self[0], self.wrapped_data.file_pointer()) == (
other.is_a(),
other[0],
other.wrapped_data.file_pointer(),
)
raise NotImplementedError
def is_entity(self) -> bool:
"""Tests whether the instance is an entity type as opposed to a simple data type.
:return: True if the instance is an entity
"""
schema_name = self.wrapped_data.is_a(True).split(".")[0]
schema_name = self.is_a(True).split(".")[0]
decl = ifcopenshell_wrapper.schema_by_name(schema_name).declaration_by_name(self.is_a())
return isinstance(decl, ifcopenshell_wrapper.entity)
@@ -505,9 +270,9 @@ class entity_instance:
:return: bool: The comparison predicate applied to self and other
"""
if isinstance(other, entity_instance):
if isinstance(other, entity_instance_mixin):
a, b = map(tuple, (self, other))
if any(map(entity_instance.is_entity, (self, other))):
if any(map(entity_instance_mixin.is_entity, (self, other))):
a = (self.is_a(),) + a
b = (other.is_a(),) + b
elif self.is_entity():
@@ -540,17 +305,17 @@ class entity_instance:
# step id. Selected type instances (such as IfcPropertySingleValue.NominalValue
# always have id=0, so we hash <type, value, file pointer>
if id_ := self.id():
return hash((id_, self.wrapped_data.file_pointer()))
return hash((id_, self.file_pointer()))
else:
return hash((self.is_a(), self[0], self.wrapped_data.file_pointer()))
return hash((self.is_a(), self[0], self.file_pointer()))
def __dir__(self):
return sorted(
set(
itertools.chain(
dir(type(self)),
map(str, self.wrapped_data.get_attribute_names()),
map(str, self.wrapped_data.get_inverse_attribute_names()),
map(str, self.get_attribute_names()),
map(str, self.get_inverse_attribute_names()),
)
)
)
@@ -595,7 +360,7 @@ class entity_instance:
logging.exception("unhandled exception while getting id / type info on {}".format(self))
for i in range(len(self)):
try:
if self.wrapped_data.get_attribute_names()[i] in ignore:
if self.get_attribute_names()[i] in ignore:
continue
attr_value = self[i]
@@ -604,10 +369,10 @@ class entity_instance:
if recursive or scalar_only:
def is_instance(e):
return isinstance(e, entity_instance)
return isinstance(e, entity_instance_mixin)
def get_info_(inst):
return entity_instance.get_info(
return entity_instance_mixin.get_info(
inst,
include_identifier=include_identifier,
recursive=recursive,
@@ -619,7 +384,7 @@ class entity_instance:
to_include["v"] = False
return None
attr_value = entity_instance.walk(
attr_value = entity_instance_mixin.walk(
is_instance, get_info_ if recursive else do_ignore, attr_value
)
@@ -651,4 +416,4 @@ class entity_instance:
assert recursive
assert return_type is dict
assert len(ignore) == 0
return ifcopenshell_wrapper.get_info_cpp(self.wrapped_data, include_identifier)
return ifcopenshell_wrapper.get_info_cpp(self, include_identifier)
@@ -26,16 +26,14 @@ import documentation
from collections import defaultdict
USE_VIRTUAL_INHERITANCE = True
class Header(codegen.Base):
def __init__(self, mapping):
declarations = []
case_lookup = lambda nm: [k for k in mapping.schema.keys if k.lower() == nm.lower()][0]
case_normalize = lambda nm: nm if nm.startswith("IfcUtil::") else case_lookup(nm)
case_normalize = lambda nm: nm if nm.startswith("express::") else case_lookup(nm)
create_supertype_statement = lambda nms: ", ".join(
"public %s %s" % ("" if c.startswith("IfcUtil::") else "", c) for c in nms
"public %s %s" % ("" if c.startswith("express::") else "", c) for c in nms
)
write = lambda str, **kwargs: declarations.append(
@@ -43,7 +41,7 @@ class Header(codegen.Base):
% dict({"documentation": templates.multi_line_comment(documentation.description(kwargs["name"]))}, **kwargs)
)
forward_names = list(mapping.schema.entities.keys()) + list(mapping.schema.simpletypes.keys())
forward_names = list(mapping.schema.entities.keys()) + list(mapping.schema.simpletypes.keys()) + list(mapping.schema.selects.keys())
forward_definitions = "".join(["class %s; " % n for n in forward_names])
select_super_types = defaultdict(list)
@@ -51,7 +49,20 @@ class Header(codegen.Base):
for name, type in mapping.schema.selects.items():
for nm in type.values:
select_super_types[str(nm).lower()].append(name)
write(templates.select_virtual if USE_VIRTUAL_INHERITANCE else templates.select_plain, name=name)
# Previously we used (virtual) inheritance, now we use casts to go from Base to Select.
# Casts only go one conversion step deep, so we need to explicitly all descendant selected leafs.
def visit_select(s):
for x in map(str, s.values):
yield x
if mapping.schema.is_select(x):
yield from visit_select(mapping.schema.selects[x])
write(templates.select,
name=name,
template_items="\n".join(templates.select_list_item % {'item_name': nm} for nm in visit_select(type)),
cast_functions="\n".join(templates.select_cast_function % {'name': name, 'item_name': nm} for nm in visit_select(type)),
)
def get_select_super_types(nm, bases=[]):
x = list(select_super_types[nm.lower()])
@@ -82,13 +93,15 @@ class Header(codegen.Base):
all_superclasses.append(superclass)
superclass = mapping.simple_type_parent(superclass)
else:
superclasses.append("IfcUtil::IfcBaseType")
superclasses.append("express::DeclaredType")
if USE_VIRTUAL_INHERITANCE:
superclasses.extend(get_select_super_types(name, bases=all_superclasses))
# This is no longer used, previously virtual inheritance was used, now
# a variant-like approach is used instead, so the definition of selects
# is on the other side again, as it is in Express.
# superclasses.extend(get_select_super_types(name, bases=all_superclasses))
is_emitted = (
lambda nm: nm == "IfcUtil::IfcBaseType"
lambda nm: nm == "express::DeclaredType"
or nm in mapping.schema.selects
or nm.lower() in emitted_simpletypes
)
@@ -99,7 +112,9 @@ class Header(codegen.Base):
emitted_simpletypes.add(name.lower())
superclass_statement = create_supertype_statement(superclasses)
# with the v1 data model we're back to exactly one supertype, no more virtual inheritance to handle selects
assert len(superclasses) == 1
superclass_statement = superclasses[0]
write(
templates.simpletype, name=name, type=type_str, attr_type=attr_type, superclass=superclass_statement
@@ -128,7 +143,11 @@ class Header(codegen.Base):
type_str = mapping.get_parameter_type(attr)
if mapping.make_argument_type(attr) != "IfcUtil::Argument_UNKNOWN":
attr_lines.append("%s %s() const;" % (type_str, attr.name))
attr_lines.append("void set%s(%s v);" % (attr.name, type_str))
attr_lines.append("void set%s(const %s& v);" % (attr.name, type_str))
if type_str == 'std::optional< std::string >':
# because a 2-step char[] -> std::string -> optional<string> is not allowed
# attr_lines.append("void set%s(const %s& v);" % (attr.name, 'std::string'))
pass
[write_method(attr) for attr in type.attributes]
@@ -157,11 +176,11 @@ class Header(codegen.Base):
all_supertypes.append(tt.supertypes[0])
tt = mapping.schema.entities[tt.supertypes[0]]
supertypes = list(type.supertypes) if len(type.supertypes) else ["IfcUtil::IfcBaseEntity"]
if USE_VIRTUAL_INHERITANCE:
supertypes.extend(get_select_super_types(name, bases=all_supertypes))
supertypes = list(type.supertypes) if len(type.supertypes) else ["express::Entity"]
# supertypes.extend(get_select_super_types(name, bases=all_supertypes))
supertypes = list(map(case_normalize, supertypes))
superclass = create_supertype_statement(supertypes)
assert len(supertypes) == 1
superclass = supertypes[0]
argument_count = mapping.argument_count(type)
@@ -13,7 +13,7 @@ ENTITY file_name;
organization : LIST [1:?] OF STRING (256);
preprocessor_version : STRING (256);
originating_system : STRING (256);
authorisation : STRING (256);
authorization : STRING (256);
END_ENTITY;
ENTITY file_description;
@@ -22,8 +22,6 @@ import templates
from schema import OrderedCaseInsensitiveDict
from header import USE_VIRTUAL_INHERITANCE
class Implementation(codegen.Base):
def __init__(self, mapping):
@@ -70,15 +68,14 @@ class Implementation(codegen.Base):
),
)
if USE_VIRTUAL_INHERITANCE:
for name, enum in mapping.schema.selects.items():
write(
templates.select_function,
name=name,
schema_name=schema_name,
schema_name_upper=schema_name_upper,
index_in_schema=self.names.index(str(name)),
)
for name, enum in mapping.schema.selects.items():
write(
templates.select_function,
name=name,
schema_name=schema_name,
schema_name_upper=schema_name_upper,
index_in_schema=self.names.index(str(name)),
)
write = lambda str, **kwargs: entity_implementations.append(str % kwargs)
@@ -101,7 +98,6 @@ class Implementation(codegen.Base):
def find_template(arg):
simple = mapping.schema.is_simpletype(arg["list_instance_type"])
select = arg["list_instance_type"] == "IfcUtil::IfcBaseClass"
express = (
mapping.flatten_type_string(arg["list_instance_type"]) in mapping.express_to_cpp_typemapping
)
@@ -109,9 +105,9 @@ class Implementation(codegen.Base):
return templates.get_attr_stmt_enum
elif arg["is_nested"] and arg["is_templated_list"]:
return templates.get_attr_stmt_nested_array
elif arg["is_templated_list"] and not (select or simple or express):
elif arg["is_templated_list"] and not (simple or express):
return templates.get_attr_stmt_array
elif arg["non_optional_type"].endswith("*"):
elif arg["argument_type_enum"] == 'IfcUtil::Argument_ENTITY_INSTANCE':
return templates.get_attr_stmt_entity
else:
return templates.get_attr_stmt
@@ -122,10 +118,10 @@ class Implementation(codegen.Base):
"if(get_attribute_value(%d).isNull()) { return %%s; }"
% (arg["index"] - 1,)
)
if "boost::optional" in arg["full_type"]:
null_check = attr_check % "boost::none"
if "std::optional" in arg["full_type"]:
null_check = attr_check % "std::nullopt"
else:
null_check = attr_check % "nullptr"
null_check = attr_check % (arg['full_type'] + "{}")
tmpl = find_template(arg)
write_attr(
@@ -151,13 +147,14 @@ class Implementation(codegen.Base):
def find_template(arg):
simple = mapping.schema.is_simpletype(arg["list_instance_type"])
select = arg["list_instance_type"] == "IfcUtil::IfcBaseClass"
express = arg["list_instance_type"] in mapping.express_to_cpp_typemapping
if arg["is_enum"]:
return templates.set_attr_stmt_enum
elif arg["is_templated_list"] and not (select or simple or express):
elif arg["is_nested"] and arg["is_templated_list"]:
return templates.set_attr_stmt_nested_array
elif arg["is_templated_list"] and not (simple or express):
return templates.set_attr_stmt_array
elif arg["full_type"].endswith('*'):
elif arg["argument_type_enum"] == 'IfcUtil::Argument_ENTITY_INSTANCE':
return templates.set_attr_instance
else:
return templates.set_attr_stmt
@@ -167,7 +164,7 @@ class Implementation(codegen.Base):
templates.function,
class_name=name,
name="set%s" % arg["name"],
arguments="%s v" % arg["full_type"],
arguments="const %s& v" % arg["full_type"],
return_type="void",
schema_name=schema_name,
schema_name_upper=schema_name_upper,
@@ -176,10 +173,10 @@ class Implementation(codegen.Base):
"index": arg["index"] - 1,
"type": arg["full_type"].replace("::Value", ""),
"non_optional_type": arg["non_optional_type"].replace("::Value", ""),
"star_if_optional": "*" if "boost::optional" in arg["full_type"] else "",
"check_optional_set_begin": "if (v) {" if "boost::optional" in arg["full_type"] else "",
"check_optional_set_else": "} else {" if "boost::optional" in arg["full_type"] else "if constexpr (false)",
"check_optional_set_end": "}" if "boost::optional" in arg["full_type"] else "",
"star_if_optional": "*" if "std::optional" in arg["full_type"] else "",
"check_optional_set_begin": "if (v) {" if "std::optional" in arg["full_type"] else "",
"check_optional_set_else": "} else {" if "std::optional" in arg["full_type"] else "if constexpr (false)",
"check_optional_set_end": "}" if "std::optional" in arg["full_type"] else "",
},
)
@@ -226,7 +223,7 @@ class Implementation(codegen.Base):
"schema_name_upper": schema_name_upper,
"name": i.name,
"arguments": "",
"return_type": "::%s::%s::list::ptr" % (schema_name, i.entity),
"return_type": "std::vector<::%s::%s>" % (schema_name, i.entity),
"body": templates.get_inverse
% {
"type": i.entity,
@@ -240,15 +237,15 @@ class Implementation(codegen.Base):
]
superclass = (
"%s(std::move(e))" % type.supertypes[0]
"%s(e)" % type.supertypes[0]
if len(type.supertypes) == 1
else "IfcUtil::IfcBaseEntity(std::move(e))"
else "express::Entity(e)"
)
superclass_num_attrs = (
"%s(IfcEntityInstanceData(in_memory_attribute_storage(%%d)))" % type.supertypes[0]
"%s(const std::weak_ptr<InstanceData>&(in_memory_attribute_storage(%%d)))" % type.supertypes[0]
if len(type.supertypes) == 1
else "IfcUtil::IfcBaseEntity(IfcEntityInstanceData(in_memory_attribute_storage(%d)))"
else "express::Entity(const std::weak_ptr<InstanceData>&(in_memory_attribute_storage(%d)))"
) % len(constructor_arguments)
write(
@@ -313,7 +310,7 @@ class Implementation(codegen.Base):
for class_name, type in mapping.schema.simpletypes.items():
type_str = mapping.make_type_string(mapping.flatten_type_string(type))
attr_type = mapping.make_argument_type(type)
superclass = mapping.simple_type_parent(class_name) or "IfcUtil::IfcBaseType"
superclass = mapping.simple_type_parent(class_name) or "express::DeclaredType"
simpletype_impl_is = (
templates.simpletype_impl_is_with_supertype
@@ -358,24 +355,24 @@ class Implementation(codegen.Base):
(),
templates.simpletype_impl_class,
),
(
"",
"declaration",
templates.const_function,
"const IfcParse::type_declaration&",
(),
templates.simpletype_impl_declaration,
),
(
"std::move(e)",
"",
constructor,
"",
("IfcEntityInstanceData&& e",),
"",
),
("", "", constructor, "", ("%s v" % type_str,), ("set_attribute_value(0, v%s);" % ("->generalize()" if mapping.is_templated_list(type) else ""))) if mapping.simple_type_parent(class_name) is None else \
("v", "", constructor, "", ("%s v" % type_str,), ""),
# (
# "",
# "declaration",
# templates.const_function,
# "const IfcParse::type_declaration&",
# (),
# templates.simpletype_impl_declaration,
# ),
# (
# "e",
# "",
# constructor,
# "",
# ("const std::weak_ptr<InstanceData>& e",),
# "",
# ),
# ("", "", constructor, "", ("%s v" % type_str,), ("set_attribute_value(0, v%s);" % ("->generalize()" if mapping.is_templated_list(type) else ""))) if mapping.simple_type_parent(class_name) is None else \
# ("v", "", constructor, "", ("%s v" % type_str,), ""),
("", "", templates.cast_function, type_str, (), simpletype_impl_cast),
),
),
@@ -23,8 +23,6 @@ import nodes
import templates
import schema
from header import USE_VIRTUAL_INHERITANCE
class Mapping:
express_to_cpp_typemapping = {
@@ -177,15 +175,8 @@ class Mapping:
elif isinstance(type_str, nodes.AggregationType):
is_nested_list = isinstance(attr_type.type, nodes.AggregationType)
ty = self.get_parameter_type(attr_type.type if is_nested_list else attr_type)
# We do not use pointers in aggregate_of<T>. aggregate_of has member vector<T*>
ty = ty.replace("*", "")
# https://github.com/IfcOpenShell/IfcOpenShell/issues/2805
# We do support statically typed select types as aggregates when USE_VIRTUAL_INHERITANCE=True
if not USE_VIRTUAL_INHERITANCE and self.schema.is_select(attr_type.type):
type_str = templates.untyped_list
elif self.schema.is_simpletype(ty) or str(ty) in self.express_to_cpp_typemapping.values():
if self.schema.is_simpletype(ty) or str(ty) in self.express_to_cpp_typemapping.values():
tmpl = templates.nested_array_type if is_nested_list else templates.array_type
bounds = (attr_type.bounds.lower, attr_type.bounds.upper) if attr_type.bounds else (-1, -1)
type_str = tmpl % {"instance_type": ty, "lower": bounds[0], "upper": bounds[1]}
@@ -193,11 +184,11 @@ class Mapping:
tmpl = templates.list_list_type if is_nested_list else templates.list_type
type_str = tmpl % {"instance_type": ty}
elif self.schema.is_entity(type_str) or self.schema.is_select(type_str):
type_str = "::%s::%s*" % (self.schema.name.capitalize(), attr_type)
type_str = "::%s::%s" % (self.schema.name.capitalize(), attr_type)
is_ptr = True
if allow_optional and attr.optional and not is_ptr:
# pointers are still handled with nullptr for the time being
type_str = "boost::optional< %s >" % type_str
type_str = "std::optional< %s >" % type_str
return type_str
def argument_count(self, t):
@@ -224,8 +215,6 @@ class Mapping:
isinstance(v, nodes.SimpleType) and isinstance(v.type, nodes.StringType)
):
return "string"
if not USE_VIRTUAL_INHERITANCE and self.schema.is_select(v):
return "IfcUtil::IfcBaseClass"
if str(v) in self.schema.types or str(v) in self.schema.entities:
return "::%s::%s" % (self.schema.name.capitalize(), v)
else:
@@ -254,8 +243,8 @@ class Mapping:
arr = self.is_array(attr_type)
simple = self.schema.is_simpletype(ty)
express = self.flatten_type_string(ty) in self.express_to_cpp_typemapping
select = ty == "IfcUtil::IfcBaseClass"
return arr and not simple and not express and not select
# select = ty == "IfcUtil::IfcBaseClass"
return arr and not simple and not express
def get_assignable_arguments(self, t, include_derived=False):
count = self.argument_count(t)
@@ -738,7 +738,7 @@ class AttributeGetattrTransformer(ast.NodeTransformer):
while n := getattr(n, "parent", 0):
parents.append(n)
custom_funcs = "is_entity", "usedin", "express_len", "express_getitem", "typeof"
custom_funcs = "is_entity", "usedin", "express_len", "express_getitem", "typeof", "express_getattr"
function_defs = [p.name for p in parents if isinstance(p, ast.FunctionDef)]
if any(fn in function_defs for fn in custom_funcs):
return node
@@ -755,7 +755,7 @@ class AttributeGetattrTransformer(ast.NodeTransformer):
# Replace the Attribute node with a call to the built-in `getattr` function
return ast.copy_location(
ast.Call(
func=ast.Name(id="getattr", ctx=ast.Load()),
func=ast.Name(id="express_getattr", ctx=ast.Load()),
args=[
new_value,
ast.Str(s=node.attr),
@@ -772,7 +772,7 @@ class AttributeGetattrTransformer(ast.NodeTransformer):
while n := getattr(n, "parent", 0):
parents.append(n)
custom_funcs = "is_entity", "usedin", "express_len", "express_getitem", "typeof"
custom_funcs = "is_entity", "usedin", "express_len", "express_getitem", "typeof", "express_getattr"
function_defs = [p.name for p in parents if isinstance(p, ast.FunctionDef)]
if any(fn in function_defs for fn in custom_funcs):
return node
@@ -937,6 +937,14 @@ def express_getitem(aggr, idx, default):
except IndexError as e: return None
def express_getattr(aggr, name, default):
v = getattr(aggr, name, default)
if v is None:
return default
else:
return v
EXPRESS_ONE_BASED_INDEXING = 1
@@ -9,7 +9,7 @@ from codegen import indent
def reverse_compile(s):
return re.sub(
return re.sub(r'\bself\b', 'SELF', re.sub(
r"\s*\-\s*EXPRESS_ONE_BASED_INDEXING",
"",
re.sub(
@@ -22,11 +22,11 @@ def reverse_compile(s):
.replace("len(", "SIZEOF(")
.replace("assert ", "")
.replace(" is not False", "")
.replace("getattr(", "")
.replace("express_getattr(", "")
.replace("express_getitem(", ""),
)[::-1],
)[::-1],
)
))
@dataclass
@@ -2,173 +2,173 @@
:: python bootstrap.py express.bnf > express_parser.py
IF EXIST IFC2X3_TC1.exp (
python express_parser.py IFC2X3_TC1.exp header implementation schema_class definitions
IF EXIST Ifc2x3-schema.cpp (
:: v0.6.0
python cat.py -o ..\..\..\ifcparse\Ifc2x3.cpp txt/header_ifc2x3.txt Ifc2x3.cpp
python cat.py -o ..\..\..\ifcparse\Ifc2x3.h txt/header_ifc2x3.txt Ifc2x3.h
python cat.py -o ..\..\..\ifcparse\Ifc2x3-schema.cpp txt/header_ifc2x3.txt Ifc2x3-schema.cpp
python cat.py -o ..\..\..\ifcparse\Ifc2x3-definitions.h txt/header_ifc2x3.txt Ifc2x3-definitions.h
) ELSE (
:: v0.5.0
python cat.py -o ..\..\..\ifcparse\Ifc2x3.cpp txt/header_ifc2x3.txt txt/ifndef_ifc4.txt Ifc2x3.cpp txt/endif.txt
python cat.py -o ..\..\..\ifcparse\Ifc2x3.h txt/header_ifc2x3.txt Ifc2x3.h
python cat.py -o ..\..\..\ifcparse\Ifc2x3enum.h txt/header_ifc2x3.txt Ifc2x3enum.h
python cat.py -o ..\..\..\ifcparse\Ifc2x3-latebound.cpp txt/header_ifc2x3.txt txt/ifndef_ifc4.txt Ifc2x3-latebound.cpp txt/endif.txt
python cat.py -o ..\..\..\ifcparse\Ifc2x3-latebound.h txt/header_ifc2x3.txt Ifc2x3-latebound.h
)
del *.cpp *.h
)
IF EXIST IFC4_ADD2TC1.exp (
python express_parser.py IFC4_ADD2TC1.exp header implementation schema_class definitions
IF EXIST Ifc4-schema.cpp (
:: v0.6.0
python cat.py -o ..\..\..\ifcparse\Ifc4.cpp txt/header_ifc4.txt Ifc4.cpp
python cat.py -o ..\..\..\ifcparse\Ifc4.h txt/header_ifc4.txt Ifc4.h
python cat.py -o ..\..\..\ifcparse\Ifc4-schema.cpp txt/header_ifc4.txt Ifc4-schema.cpp
python cat.py -o ..\..\..\ifcparse\Ifc4-definitions.h txt/header_ifc4.txt Ifc4-definitions.h
) ELSE (
:: v0.5.0
python cat.py -o ..\..\..\ifcparse\Ifc4.cpp txt/header_ifc4.txt txt/ifdef_ifc4.txt Ifc4.cpp txt/endif.txt
python cat.py -o ..\..\..\ifcparse\Ifc4.h txt/header_ifc4.txt Ifc4.h
python cat.py -o ..\..\..\ifcparse\Ifc4enum.h txt/header_ifc4.txt Ifc4enum.h
python cat.py -o ..\..\..\ifcparse\Ifc4-latebound.cpp txt/header_ifc4.txt txt/ifdef_ifc4.txt Ifc4-latebound.cpp txt/endif.txt
python cat.py -o ..\..\..\ifcparse\Ifc4-latebound.h txt/header_ifc4.txt Ifc4-latebound.h
)
del *.cpp *.h
)
IF EXIST IFC4x1.exp (
python express_parser.py IFC4x1.exp header implementation schema_class definitions
IF EXIST Ifc4x1-schema.cpp (
:: v0.6.0
python cat.py -o ..\..\..\ifcparse\Ifc4x1.cpp txt/header_ifc4x1.txt Ifc4x1.cpp
python cat.py -o ..\..\..\ifcparse\Ifc4x1.h txt/header_ifc4x1.txt Ifc4x1.h
python cat.py -o ..\..\..\ifcparse\Ifc4x1-schema.cpp txt/header_ifc4x1.txt Ifc4x1-schema.cpp
python cat.py -o ..\..\..\ifcparse\Ifc4x1-definitions.h txt/header_ifc4x1.txt Ifc4x1-definitions.h
)
del *.cpp *.h
)
IF EXIST IFC4x2.exp (
python express_parser.py IFC4x2.exp header implementation schema_class definitions
IF EXIST Ifc4x2-schema.cpp (
:: v0.6.0
python cat.py -o ..\..\..\ifcparse\Ifc4x2.cpp txt/header_ifc4x2.txt Ifc4x2.cpp
python cat.py -o ..\..\..\ifcparse\Ifc4x2.h txt/header_ifc4x2.txt Ifc4x2.h
python cat.py -o ..\..\..\ifcparse\Ifc4x2-schema.cpp txt/header_ifc4x2.txt Ifc4x2-schema.cpp
python cat.py -o ..\..\..\ifcparse\Ifc4x2-definitions.h txt/header_ifc4x2.txt Ifc4x2-definitions.h
)
del *.cpp *.h
)
IF EXIST IFC4x3_RC1.exp (
python express_parser.py IFC4x3_RC1.exp header implementation schema_class definitions
IF EXIST Ifc4x3_rc1-schema.cpp (
:: v0.6.0
python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc1.cpp txt/header_ifc4x3_rc1.txt Ifc4x3_rc1.cpp
python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc1.h txt/header_ifc4x3_rc1.txt Ifc4x3_rc1.h
python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc1-schema.cpp txt/header_ifc4x3_rc1.txt Ifc4x3_rc1-schema.cpp
python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc1-definitions.h txt/header_ifc4x3_rc1.txt Ifc4x3_rc1-definitions.h
)
del *.cpp *.h
)
IF EXIST IFC4x3_RC2.exp (
python express_parser.py IFC4x3_RC2.exp header implementation schema_class definitions
IF EXIST Ifc4x3_rc2-schema.cpp (
:: v0.6.0
python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc2.cpp txt/header_ifc4x3_rc2.txt Ifc4x3_rc2.cpp
python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc2.h txt/header_ifc4x3_rc2.txt Ifc4x3_rc2.h
python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc2-schema.cpp txt/header_ifc4x3_rc2.txt Ifc4x3_rc2-schema.cpp
python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc2-definitions.h txt/header_ifc4x3_rc2.txt Ifc4x3_rc2-definitions.h
)
del *.cpp *.h
)
IF EXIST IFC4x3_RC3.exp (
python express_parser.py IFC4x3_RC3.exp header implementation schema_class definitions
IF EXIST Ifc4x3_rc3-schema.cpp (
:: v0.6.0
python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc3.cpp txt/header_ifc4x3_rc2.txt Ifc4x3_rc3.cpp
python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc3.h txt/header_ifc4x3_rc2.txt Ifc4x3_rc3.h
python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc3-schema.cpp txt/header_ifc4x3_rc2.txt Ifc4x3_rc3-schema.cpp
python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc3-definitions.h txt/header_ifc4x3_rc2.txt Ifc4x3_rc3-definitions.h
)
del *.cpp *.h
)
IF EXIST IFC4x3_RC4.exp (
python express_parser.py IFC4x3_RC4.exp header implementation schema_class definitions
IF EXIST Ifc4x3_rc4-schema.cpp (
:: v0.6.0
python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc4.cpp txt/header_ifc4x3_rc2.txt Ifc4x3_rc4.cpp
python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc4.h txt/header_ifc4x3_rc2.txt Ifc4x3_rc4.h
python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc4-schema.cpp txt/header_ifc4x3_rc2.txt Ifc4x3_rc4-schema.cpp
python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc4-definitions.h txt/header_ifc4x3_rc2.txt Ifc4x3_rc4-definitions.h
)
del *.cpp *.h
)
IF EXIST IFC4X3.exp (
python express_parser.py IFC4X3.exp header implementation schema_class definitions
IF EXIST Ifc4x3-schema.cpp (
:: v0.6.0
python cat.py -o ..\..\..\ifcparse\Ifc4x3.cpp txt/header_ifc4x3_rc2.txt Ifc4x3.cpp
python cat.py -o ..\..\..\ifcparse\Ifc4x3.h txt/header_ifc4x3_rc2.txt Ifc4x3.h
python cat.py -o ..\..\..\ifcparse\Ifc4x3-schema.cpp txt/header_ifc4x3_rc2.txt Ifc4x3-schema.cpp
python cat.py -o ..\..\..\ifcparse\Ifc4x3-definitions.h txt/header_ifc4x3_rc2.txt Ifc4x3-definitions.h
)
del *.cpp *.h
)
IF EXIST IFC4X3_TC1.exp (
python express_parser.py IFC4X3_TC1.exp header implementation schema_class definitions
IF EXIST Ifc4x3_tc1-schema.cpp (
:: v0.6.0
python cat.py -o ..\..\..\ifcparse\Ifc4x3_tc1.cpp txt/header_ifc4x3_tc1.txt Ifc4x3_tc1.cpp
python cat.py -o ..\..\..\ifcparse\Ifc4x3_tc1.h txt/header_ifc4x3_tc1.txt Ifc4x3_tc1.h
python cat.py -o ..\..\..\ifcparse\Ifc4x3_tc1-schema.cpp txt/header_ifc4x3_tc1.txt Ifc4x3_tc1-schema.cpp
python cat.py -o ..\..\..\ifcparse\Ifc4x3_tc1-definitions.h txt/header_ifc4x3_tc1.txt Ifc4x3_tc1-definitions.h
)
del *.cpp *.h
)
IF EXIST IFC4X3_ADD1.exp (
python express_parser.py IFC4X3_ADD1.exp header implementation schema_class definitions
IF EXIST Ifc4x3_add1-schema.cpp (
:: v0.6.0
python cat.py -o ..\..\..\ifcparse\Ifc4x3_add1.cpp txt/header_ifc4x3_add1.txt Ifc4x3_add1.cpp
python cat.py -o ..\..\..\ifcparse\Ifc4x3_add1.h txt/header_ifc4x3_add1.txt Ifc4x3_add1.h
python cat.py -o ..\..\..\ifcparse\Ifc4x3_add1-schema.cpp txt/header_ifc4x3_add1.txt Ifc4x3_add1-schema.cpp
python cat.py -o ..\..\..\ifcparse\Ifc4x3_add1-definitions.h txt/header_ifc4x3_add1.txt Ifc4x3_add1-definitions.h
)
del *.cpp *.h
)
:: IF EXIST IFC2X3_TC1.exp (
:: python express_parser.py IFC2X3_TC1.exp header implementation schema_class definitions
::
:: IF EXIST Ifc2x3-schema.cpp (
:: :: v0.6.0
:: python cat.py -o ..\..\..\ifcparse\Ifc2x3.cpp txt/header_ifc2x3.txt Ifc2x3.cpp
:: python cat.py -o ..\..\..\ifcparse\Ifc2x3.h txt/header_ifc2x3.txt Ifc2x3.h
:: python cat.py -o ..\..\..\ifcparse\Ifc2x3-schema.cpp txt/header_ifc2x3.txt Ifc2x3-schema.cpp
:: python cat.py -o ..\..\..\ifcparse\Ifc2x3-definitions.h txt/header_ifc2x3.txt Ifc2x3-definitions.h
:: ) ELSE (
:: :: v0.5.0
:: python cat.py -o ..\..\..\ifcparse\Ifc2x3.cpp txt/header_ifc2x3.txt txt/ifndef_ifc4.txt Ifc2x3.cpp txt/endif.txt
:: python cat.py -o ..\..\..\ifcparse\Ifc2x3.h txt/header_ifc2x3.txt Ifc2x3.h
:: python cat.py -o ..\..\..\ifcparse\Ifc2x3enum.h txt/header_ifc2x3.txt Ifc2x3enum.h
:: python cat.py -o ..\..\..\ifcparse\Ifc2x3-latebound.cpp txt/header_ifc2x3.txt txt/ifndef_ifc4.txt Ifc2x3-latebound.cpp txt/endif.txt
:: python cat.py -o ..\..\..\ifcparse\Ifc2x3-latebound.h txt/header_ifc2x3.txt Ifc2x3-latebound.h
:: )
::
:: del *.cpp *.h
:: )
::
:: IF EXIST IFC4_ADD2TC1.exp (
:: python express_parser.py IFC4_ADD2TC1.exp header implementation schema_class definitions
::
:: IF EXIST Ifc4-schema.cpp (
:: :: v0.6.0
:: python cat.py -o ..\..\..\ifcparse\Ifc4.cpp txt/header_ifc4.txt Ifc4.cpp
:: python cat.py -o ..\..\..\ifcparse\Ifc4.h txt/header_ifc4.txt Ifc4.h
:: python cat.py -o ..\..\..\ifcparse\Ifc4-schema.cpp txt/header_ifc4.txt Ifc4-schema.cpp
:: python cat.py -o ..\..\..\ifcparse\Ifc4-definitions.h txt/header_ifc4.txt Ifc4-definitions.h
:: ) ELSE (
:: :: v0.5.0
:: python cat.py -o ..\..\..\ifcparse\Ifc4.cpp txt/header_ifc4.txt txt/ifdef_ifc4.txt Ifc4.cpp txt/endif.txt
:: python cat.py -o ..\..\..\ifcparse\Ifc4.h txt/header_ifc4.txt Ifc4.h
:: python cat.py -o ..\..\..\ifcparse\Ifc4enum.h txt/header_ifc4.txt Ifc4enum.h
:: python cat.py -o ..\..\..\ifcparse\Ifc4-latebound.cpp txt/header_ifc4.txt txt/ifdef_ifc4.txt Ifc4-latebound.cpp txt/endif.txt
:: python cat.py -o ..\..\..\ifcparse\Ifc4-latebound.h txt/header_ifc4.txt Ifc4-latebound.h
:: )
::
:: del *.cpp *.h
:: )
::
:: IF EXIST IFC4x1.exp (
:: python express_parser.py IFC4x1.exp header implementation schema_class definitions
::
:: IF EXIST Ifc4x1-schema.cpp (
:: :: v0.6.0
:: python cat.py -o ..\..\..\ifcparse\Ifc4x1.cpp txt/header_ifc4x1.txt Ifc4x1.cpp
:: python cat.py -o ..\..\..\ifcparse\Ifc4x1.h txt/header_ifc4x1.txt Ifc4x1.h
:: python cat.py -o ..\..\..\ifcparse\Ifc4x1-schema.cpp txt/header_ifc4x1.txt Ifc4x1-schema.cpp
:: python cat.py -o ..\..\..\ifcparse\Ifc4x1-definitions.h txt/header_ifc4x1.txt Ifc4x1-definitions.h
:: )
::
:: del *.cpp *.h
:: )
::
:: IF EXIST IFC4x2.exp (
:: python express_parser.py IFC4x2.exp header implementation schema_class definitions
::
:: IF EXIST Ifc4x2-schema.cpp (
:: :: v0.6.0
:: python cat.py -o ..\..\..\ifcparse\Ifc4x2.cpp txt/header_ifc4x2.txt Ifc4x2.cpp
:: python cat.py -o ..\..\..\ifcparse\Ifc4x2.h txt/header_ifc4x2.txt Ifc4x2.h
:: python cat.py -o ..\..\..\ifcparse\Ifc4x2-schema.cpp txt/header_ifc4x2.txt Ifc4x2-schema.cpp
:: python cat.py -o ..\..\..\ifcparse\Ifc4x2-definitions.h txt/header_ifc4x2.txt Ifc4x2-definitions.h
:: )
::
:: del *.cpp *.h
:: )
::
:: IF EXIST IFC4x3_RC1.exp (
:: python express_parser.py IFC4x3_RC1.exp header implementation schema_class definitions
::
:: IF EXIST Ifc4x3_rc1-schema.cpp (
:: :: v0.6.0
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc1.cpp txt/header_ifc4x3_rc1.txt Ifc4x3_rc1.cpp
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc1.h txt/header_ifc4x3_rc1.txt Ifc4x3_rc1.h
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc1-schema.cpp txt/header_ifc4x3_rc1.txt Ifc4x3_rc1-schema.cpp
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc1-definitions.h txt/header_ifc4x3_rc1.txt Ifc4x3_rc1-definitions.h
:: )
::
:: del *.cpp *.h
:: )
::
:: IF EXIST IFC4x3_RC2.exp (
:: python express_parser.py IFC4x3_RC2.exp header implementation schema_class definitions
::
:: IF EXIST Ifc4x3_rc2-schema.cpp (
:: :: v0.6.0
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc2.cpp txt/header_ifc4x3_rc2.txt Ifc4x3_rc2.cpp
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc2.h txt/header_ifc4x3_rc2.txt Ifc4x3_rc2.h
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc2-schema.cpp txt/header_ifc4x3_rc2.txt Ifc4x3_rc2-schema.cpp
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc2-definitions.h txt/header_ifc4x3_rc2.txt Ifc4x3_rc2-definitions.h
:: )
::
:: del *.cpp *.h
:: )
::
:: IF EXIST IFC4x3_RC3.exp (
:: python express_parser.py IFC4x3_RC3.exp header implementation schema_class definitions
::
:: IF EXIST Ifc4x3_rc3-schema.cpp (
:: :: v0.6.0
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc3.cpp txt/header_ifc4x3_rc2.txt Ifc4x3_rc3.cpp
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc3.h txt/header_ifc4x3_rc2.txt Ifc4x3_rc3.h
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc3-schema.cpp txt/header_ifc4x3_rc2.txt Ifc4x3_rc3-schema.cpp
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc3-definitions.h txt/header_ifc4x3_rc2.txt Ifc4x3_rc3-definitions.h
:: )
::
:: del *.cpp *.h
:: )
::
:: IF EXIST IFC4x3_RC4.exp (
:: python express_parser.py IFC4x3_RC4.exp header implementation schema_class definitions
::
:: IF EXIST Ifc4x3_rc4-schema.cpp (
:: :: v0.6.0
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc4.cpp txt/header_ifc4x3_rc2.txt Ifc4x3_rc4.cpp
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc4.h txt/header_ifc4x3_rc2.txt Ifc4x3_rc4.h
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc4-schema.cpp txt/header_ifc4x3_rc2.txt Ifc4x3_rc4-schema.cpp
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3_rc4-definitions.h txt/header_ifc4x3_rc2.txt Ifc4x3_rc4-definitions.h
:: )
::
:: del *.cpp *.h
:: )
::
:: IF EXIST IFC4X3.exp (
:: python express_parser.py IFC4X3.exp header implementation schema_class definitions
::
:: IF EXIST Ifc4x3-schema.cpp (
:: :: v0.6.0
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3.cpp txt/header_ifc4x3_rc2.txt Ifc4x3.cpp
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3.h txt/header_ifc4x3_rc2.txt Ifc4x3.h
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3-schema.cpp txt/header_ifc4x3_rc2.txt Ifc4x3-schema.cpp
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3-definitions.h txt/header_ifc4x3_rc2.txt Ifc4x3-definitions.h
:: )
::
:: del *.cpp *.h
:: )
::
:: IF EXIST IFC4X3_TC1.exp (
:: python express_parser.py IFC4X3_TC1.exp header implementation schema_class definitions
::
:: IF EXIST Ifc4x3_tc1-schema.cpp (
:: :: v0.6.0
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3_tc1.cpp txt/header_ifc4x3_tc1.txt Ifc4x3_tc1.cpp
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3_tc1.h txt/header_ifc4x3_tc1.txt Ifc4x3_tc1.h
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3_tc1-schema.cpp txt/header_ifc4x3_tc1.txt Ifc4x3_tc1-schema.cpp
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3_tc1-definitions.h txt/header_ifc4x3_tc1.txt Ifc4x3_tc1-definitions.h
:: )
::
:: del *.cpp *.h
:: )
::
:: IF EXIST IFC4X3_ADD1.exp (
:: python express_parser.py IFC4X3_ADD1.exp header implementation schema_class definitions
::
:: IF EXIST Ifc4x3_add1-schema.cpp (
:: :: v0.6.0
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3_add1.cpp txt/header_ifc4x3_add1.txt Ifc4x3_add1.cpp
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3_add1.h txt/header_ifc4x3_add1.txt Ifc4x3_add1.h
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3_add1-schema.cpp txt/header_ifc4x3_add1.txt Ifc4x3_add1-schema.cpp
:: python cat.py -o ..\..\..\ifcparse\Ifc4x3_add1-definitions.h txt/header_ifc4x3_add1.txt Ifc4x3_add1-definitions.h
:: )
::
:: del *.cpp *.h
:: )
IF EXIST IFC4X3_ADD2.exp (
python express_parser.py IFC4X3_ADD2.exp header implementation schema_class definitions
@@ -183,3 +183,17 @@ IF EXIST IFC4X3_ADD2.exp (
del *.cpp *.h
)
IF EXIST header_schema.exp (
python express_parser.py header_schema.exp header implementation schema_class definitions
IF EXIST Header_section_schema-schema.cpp (
:: v0.6.0
python cat.py -o ..\..\..\ifcparse\Header_section_schema.cpp Header_section_schema.cpp
python cat.py -o ..\..\..\ifcparse\Header_section_schema.h Header_section_schema.h
python cat.py -o ..\..\..\ifcparse\Header_section_schema-schema.cpp Header_section_schema-schema.cpp
python cat.py -o ..\..\..\ifcparse\Header_section_schema-definitions.h Header_section_schema-definitions.h
)
del *.cpp *.h
)
@@ -178,7 +178,7 @@ class EarlyBoundCodeWriter:
num_names = len(self.names)
self.statements.append("declaration* %(schema_name)s_types[%(num_names)d] = {nullptr};" % locals())
self.statements.append("{factory_placeholder}")
# self.statements.append("{factory_placeholder}")
# self.statements.append(
# """
@@ -285,7 +285,7 @@ class EarlyBoundCodeWriter:
declarations = ",".join(_())
schema_name_ref = self.strings.append(schema_name)
self.statements.append(
' return new schema_definition(%(schema_name_ref)s, {%(declarations)s}, new %(schema_name)s_instance_factory());'
' return new schema_definition(%(schema_name_ref)s, {%(declarations)s});'
% locals()
)
self.statements.append("}");
@@ -340,16 +340,17 @@ class EarlyBoundCodeWriter:
)
)
self.statements[self.statements.index("{factory_placeholder}")] = (
"""
class %(schema_name)s_instance_factory : public IfcParse::instance_factory {
virtual IfcUtil::IfcBaseClass* operator()(const IfcParse::declaration* decl, IfcEntityInstanceData&& data) const {
%(instance_mapping)s
}
};
"""
% locals()
)
# Factor no longer exists because we don't have virtual methods anymore.
# self.statements[self.statements.index("{factory_placeholder}")] = (
# """
# class %(schema_name)s_instance_factory : public IfcParse::instance_factory {
# virtual IfcUtil::IfcBaseClass* operator()(const IfcParse::declaration* decl, const std::weak_ptr<InstanceData>& data) const {
# %(instance_mapping)s
# }
# };
# """
# % locals()
# )
""
self.statements[self.statements.index("{string_pool_placeholder}")] = (
@@ -23,17 +23,20 @@ header = """
#include <string>
#include <vector>
#include <boost/optional.hpp>
#include <optional>
#include "../ifcparse/ifc_parse_api.h"
#include "../ifcparse/aggregate_of_instance.h"
#include "../ifcparse/IfcBaseClass.h"
#include "../ifcparse/express.h"
#include "../ifcparse/IfcSchema.h"
#include "../ifcparse/IfcException.h"
#include "../ifcparse/Argument.h"
namespace IfcParse {
class IfcFile;
class IfcSpfHeader;
} // namespace IfcParse
struct %(schema_name)s {
IFC_PARSE_API static const IfcParse::schema_definition& get_schema();
@@ -60,7 +63,7 @@ enum_header = """
#include "../ifcparse/ifc_parse_api.h"
#include <string>
#include <boost/optional.hpp>
#include <optional>
#endif
"""
@@ -108,12 +111,14 @@ derived_field_statement = " {std::set<int> idxs; %(statements)sderived_map[Ty
derived_field_statement_attrs = "idxs.insert(%d); "
simpletype = """%(documentation)s
class IFC_PARSE_API %(name)s : %(superclass)s {
class IFC_PARSE_API %(name)s : public %(superclass)s {
public:
virtual const IfcParse::type_declaration& declaration() const;
%(name)s() {}
explicit %(name)s (const std::weak_ptr<InstanceData>& data) : %(superclass)s(data) {}
// virtual const IfcParse::type_declaration& declaration() const;
static const IfcParse::type_declaration& Class();
explicit %(name)s (IfcEntityInstanceData&& e);
%(name)s (%(type)s v);
// %(name)s (%(type)s v);
operator %(type)s() const;
};
"""
@@ -127,51 +132,60 @@ simpletype_impl_type = "return *((IfcParse::type_declaration*)%(schema_name_uppe
simpletype_impl_class = "return *((IfcParse::type_declaration*)%(schema_name_upper)s_types[%(index_in_schema)d]);"
simpletype_impl_explicit_constructor = "data_ = e;"
simpletype_impl_constructor = (
"data_ = new IfcEntityInstanceData(%(schema_name_upper)s_types[%(index_in_schema)d]); set_attribute_value(0, v);"
"data_ = new const std::weak_ptr<InstanceData>&(%(schema_name_upper)s_types[%(index_in_schema)d]); set_attribute_value(0, v);"
)
simpletype_impl_constructor_templated = "data_ = new IfcEntityInstanceData(%(schema_name_upper)s_types[%(index_in_schema)d]); set_attribute_value(0, v->generalize());"
simpletype_impl_constructor_templated = "data_ = new const std::weak_ptr<InstanceData>&(%(schema_name_upper)s_types[%(index_in_schema)d]); set_attribute_value(0, v->generalize());"
simpletype_impl_cast = "return get_attribute_value(0);"
simpletype_impl_cast_templated = (
"aggregate_of_instance::ptr es = get_attribute_value(0); return es->as< %(underlying_type)s >();"
)
simpletype_impl_cast_templated = "std::vector<express::Base> es = get_attribute_value(0); return cast_vector<%(underlying_type)s>(es);"
simpletype_impl_declaration = "return *((IfcParse::type_declaration*)%(schema_name_upper)s_types[%(index_in_schema)d]);"
select_virtual = """%(documentation)s
class IFC_PARSE_API %(name)s : public virtual IfcUtil::IfcBaseInterface {
select = """%(documentation)s
class IFC_PARSE_API %(name)s : public express::Select {
public:
%(name)s() {}
explicit %(name)s(const express::Base& c) : express::Select(c) {}
static const IfcParse::select_type& Class();
typedef aggregate_of< %(name)s > list;
%(template_items)s
%(cast_functions)s
};
"""
select_plain = """%(documentation)s
typedef IfcUtil::IfcBaseClass %(name)s;
select_list_item = """ template<class T, std::enable_if_t<std::is_same_v<T, %(item_name)s>, int> = 0>
%(item_name)s as() const { return express::Base::as<%(item_name)s>(); }
"""
enumeration = """class IFC_PARSE_API %(name)s : public IfcUtil::IfcBaseType {
%(documentation)s
select_cast_function = """ %(name)s(const %(item_name)s& c) : express::Select(c) {};
"""
enumeration = """%(documentation)s
class IFC_PARSE_API %(name)s : public express::DeclaredType {
public:
%(name)s() {}
explicit %(name)s (const std::weak_ptr<InstanceData>& data) : express::DeclaredType(data) {}
typedef enum {%(values)s} Value;
static const char* ToString(Value v);
static Value FromString(const std::string& s);
virtual const IfcParse::enumeration_type& declaration() const;
// virtual const IfcParse::enumeration_type& declaration() const;
static const IfcParse::enumeration_type& Class();
%(name)s (IfcEntityInstanceData&& e);
%(name)s (Value v);
%(name)s (const std::string& v);
// %(name)s (Value v);
// %(name)s (const std::string& v);
operator Value() const;
};
"""
entity = """%(documentation)s
class IFC_PARSE_API %(name)s : %(superclass)s {
class IFC_PARSE_API %(name)s : public %(superclass)s {
public:
%(attributes)s %(inverse)s virtual const IfcParse::entity& declaration() const;
%(name)s() {}
explicit %(name)s (const std::weak_ptr<InstanceData>& data) : %(superclass)s(data) {}
%(attributes)s %(inverse)s // virtual const IfcParse::entity& declaration() const;
static const IfcParse::entity& Class();
%(name)s (IfcEntityInstanceData&& e);
%(name)s (%(constructor_arguments)s);
typedef aggregate_of< %(name)s > list;
// %(name)s (%(constructor_arguments)s);
};
"""
@@ -180,20 +194,22 @@ const IfcParse::select_type& %(schema_name)s::%(name)s::Class() { return *((IfcP
"""
enumeration_function = """
const IfcParse::enumeration_type& %(schema_name)s::%(name)s::declaration() const { return *((IfcParse::enumeration_type*)%(schema_name_upper)s_types[%(index_in_schema)d]); }
// const IfcParse::enumeration_type& %(schema_name)s::%(name)s::declaration() const { return *((IfcParse::enumeration_type*)%(schema_name_upper)s_types[%(index_in_schema)d]); }
const IfcParse::enumeration_type& %(schema_name)s::%(name)s::Class() { return *((IfcParse::enumeration_type*)%(schema_name_upper)s_types[%(index_in_schema)d]); }
%(schema_name)s::%(name)s::%(name)s(IfcEntityInstanceData&& e)
: IfcBaseType(std::move(e))
/*
%(schema_name)s::%(name)s::%(name)s(const std::weak_ptr<InstanceData>& e)
: express::DeclaredType(e)
{}
%(schema_name)s::%(name)s::%(name)s(Value v) {
set_attribute_value(0, EnumerationReference(&declaration(), static_cast<size_t>(v)));
set_attribute_value(0, EnumerationReference(&Class(), static_cast<size_t>(v)));
}
%(schema_name)s::%(name)s::%(name)s(const std::string& v) {
set_attribute_value(0, EnumerationReference(&declaration(), declaration().lookup_enum_offset(v)));
set_attribute_value(0, EnumerationReference(&Class(), Class().lookup_enum_offset(v)));
}
*/
const char* %(schema_name)s::%(name)s::ToString(Value v) {
return %(schema_name)s::%(name)s::%(name)s::Class().lookup_enum_value((size_t)v);
@@ -211,14 +227,14 @@ const char* %(schema_name)s::%(name)s::ToString(Value v) {
entity_implementation = """// Function implementations for %(name)s
%(attributes)s
%(inverse)s
const IfcParse::entity& %(schema_name)s::%(name)s::declaration() const { return *((IfcParse::entity*)%(schema_name_upper)s_types[%(index_in_schema)d]); }
// const IfcParse::entity& %(schema_name)s::%(name)s::declaration() const { return *((IfcParse::entity*)%(schema_name_upper)s_types[%(index_in_schema)d]); }
const IfcParse::entity& %(schema_name)s::%(name)s::Class() { return *((IfcParse::entity*)%(schema_name_upper)s_types[%(index_in_schema)d]); }
%(schema_name)s::%(name)s::%(name)s(IfcEntityInstanceData&& e) : %(superclass)s { }
%(schema_name)s::%(name)s::%(name)s(%(constructor_arguments)s) : %(superclass_num_attrs)s { %(constructor_implementation)s; populate_derived(); }
// %(schema_name)s::%(name)s::%(name)s(const std::weak_ptr<InstanceData>& e) : %(superclass)s { }
// %(schema_name)s::%(name)s::%(name)s(%(constructor_arguments)s) : %(superclass_num_attrs)s { %(constructor_implementation)s; populate_derived(); }
"""
# data_ = e;
# data_ = new IfcEntityInstanceData(%(schema_name_upper)s_types[%(index_in_schema)d]);
# data_ = new const std::weak_ptr<InstanceData>&(%(schema_name_upper)s_types[%(index_in_schema)d]);
optional_attribute_description = "/// Whether the optional attribute %s is defined for this %s"
@@ -232,10 +248,10 @@ cast_function = "%(schema_name)s::%(class_name)s::operator %(return_type)s() con
array_type = "std::vector< %(instance_type)s > /*[%(lower)s:%(upper)s]*/"
nested_array_type = "std::vector< std::vector< %(instance_type)s > >"
list_type = "aggregate_of< %(instance_type)s >::ptr"
list_list_type = "aggregate_of_aggregate_of< %(instance_type)s >::ptr"
list_type = "std::vector< %(instance_type)s >"
list_list_type = "std::vector< std::vector< %(instance_type)s > >"
untyped_list = "aggregate_of_instance::ptr"
inverse_attr = "aggregate_of< %(entity)s >::ptr %(name)s() const; // INVERSE %(entity)s::%(attribute)s"
inverse_attr = "std::vector< %(entity)s > %(name)s() const; // INVERSE %(entity)s::%(attribute)s"
enum_from_string_stmt = ' if (s == "%(value)s") return ::%(schema_name)s::%(name)s::%(short_name)s_%(value)s;'
@@ -249,21 +265,24 @@ optional_attr_stmt = "return !get_attribute_value(%(index)d).isNull();"
get_attr_stmt = "%(null_check)s %(non_optional_type)s v = get_attribute_value(%(index)d); return v;"
get_attr_stmt_enum = "%(null_check)s return %(non_optional_type)s::FromString(get_attribute_value(%(index)d));"
get_attr_stmt_entity = "%(null_check)s return ((IfcUtil::IfcBaseClass*)(get_attribute_value(%(index)d)))->as<%(non_optional_type_no_pointer)s>(true);"
get_attr_stmt_array = "%(null_check)s aggregate_of_instance::ptr es = get_attribute_value(%(index)d); return es->as< %(list_instance_type)s >();"
get_attr_stmt_nested_array = "%(null_check)s aggregate_of_aggregate_of_instance::ptr es = get_attribute_value(%(index)d); return es->as< %(list_instance_type)s >();"
get_attr_stmt_entity = "%(null_check)s return ((express::Base)(get_attribute_value(%(index)d))).as<%(non_optional_type_no_pointer)s>();"
get_attr_stmt_array = "%(null_check)s std::vector<express::Base> es = get_attribute_value(%(index)d); return cast_vector<%(list_instance_type)s>(es);"
get_attr_stmt_nested_array = "%(null_check)s std::vector<std::vector<express::Base>> es = get_attribute_value(%(index)d); return cast_vector_vector<%(list_instance_type)s>(es);"
get_inverse = "if (!file_) { return nullptr; } return file_->getInverse(id_, %(schema_name_upper)s_types[%(type_index)d], %(index)d)->as<%(type)s>();"
get_inverse = "return cast_vector<%(type)s>(data()->file()->getInverse(data()->id(), %(schema_name_upper)s_types[%(type_index)d], %(index)d));"
set_attr_stmt = (
"%(check_optional_set_begin)sset_attribute_value(%(index)d, %(star_if_optional)sv);%(check_optional_set_else)sunset_attribute_value(%(index)d);%(check_optional_set_end)s"
)
set_attr_instance = (
"%(check_optional_set_begin)sset_attribute_value(%(index)d, v->as<IfcUtil::IfcBaseClass>());%(check_optional_set_else)sunset_attribute_value(%(index)d);%(check_optional_set_end)s"
"%(check_optional_set_begin)sset_attribute_value(%(index)d, v);%(check_optional_set_else)sunset_attribute_value(%(index)d);%(check_optional_set_end)s"
)
set_attr_stmt_enum = "%(check_optional_set_begin)sset_attribute_value(%(index)d, EnumerationReference(&%(non_optional_type)s::Class(), (size_t) %(star_if_optional)sv));%(check_optional_set_else)sunset_attribute_value(%(index)d);%(check_optional_set_end)s"
set_attr_stmt_array = (
"%(check_optional_set_begin)sset_attribute_value(%(index)d, (%(star_if_optional)sv)->generalize());%(check_optional_set_else)sunset_attribute_value(%(index)d);%(check_optional_set_end)s"
"%(check_optional_set_begin)sset_attribute_value(%(index)d, cast_vector<express::Base>(%(star_if_optional)sv));%(check_optional_set_else)sunset_attribute_value(%(index)d);%(check_optional_set_end)s"
)
set_attr_stmt_nested_array = (
"%(check_optional_set_begin)sset_attribute_value(%(index)d, cast_vector_vector<express::Base>(%(star_if_optional)sv));%(check_optional_set_else)sunset_attribute_value(%(index)d);%(check_optional_set_end)s"
)
constructor_stmt = (
@@ -289,3 +308,5 @@ inverse_implementation = ' inverse_map[Type::%(type)s].insert(std::make_pair(
def multi_line_comment(li):
return ("/// %s" % ("\n/// ".join(li))) if len(li) else ""
+43 -189
View File
@@ -31,9 +31,6 @@ from typing import Any, Optional, TYPE_CHECKING, Union, overload, Literal, Typed
from collections.abc import Callable, Generator
from typing_extensions import assert_never
from . import ifcopenshell_wrapper
from .entity_instance import entity_instance
from ifcopenshell.util.mvd_info import MvdInfo, LARK_AVAILABLE
if TYPE_CHECKING:
@@ -108,7 +105,7 @@ class Transaction:
def serialise_value(self, element, value) -> Any:
return element.walk(
lambda v: isinstance(v, entity_instance),
lambda v: isinstance(v, ifcopenshell.entity_instance),
lambda v: {"id": v.id()} if v.id() else {"type": v.is_a(), "value": v.wrappedValue},
value,
)
@@ -237,28 +234,8 @@ class Transaction:
else:
assert_never(operation["action"])
file_dict: dict[int, tuple[weakref.ReferenceType[file], int]] = {}
"""Mapping of internal IfcFile pointer address to existing ``ifcopenshell.file``
and the timestamp when it was created.
Needed only to quickly access related from ``entity_instance`` it's ``file``.
"""
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
INVALID_SYNTAX = ifcopenshell_wrapper.file_open_status.INVALID_SYNTAX
# TODO: Workaround for old builds, remove after build stabilizes.
try:
UNKNOWN = ifcopenshell_wrapper.file_open_status.UNKNOWN
except:
UNKNOWN = 5 # Workaround
import struct
def consume_buffer(val, inner):
while val:
s = struct.unpack("@q", val[:8])[0]
@@ -508,20 +485,21 @@ class file_header:
self.file = file
self.header_data = header_data
# @todo these are probably no longer necessary now as we no longer depend on decoration of file
@property
def file_description(self) -> entity_instance:
return entity_instance.wrap_value(self.header_data.file_description_py(), file=self.file)
def file_description(self) -> ifcopenshell.entity_instance:
return self.header_data.file_description_py()
@property
def file_name(self) -> entity_instance:
return entity_instance.wrap_value(self.header_data.file_name_py(), file=self.file)
def file_name(self) -> ifcopenshell.entity_instance:
return self.header_data.file_name_py()
@property
def file_schema(self) -> entity_instance:
return entity_instance.wrap_value(self.header_data.file_schema_py(), file=self.file)
def file_schema(self) -> ifcopenshell.entity_instance:
return self.header_data.file_schema_py()
class file:
class file_mixin:
"""Base class for containing IFC files.
Class has instance methods for filtering by element Id, Type, etc.
@@ -537,8 +515,7 @@ class file:
print(products[0] == model[122] == model["2XQ$n5SLP5MBLyL442paFx"]) # True
"""
wrapped_data: ifcopenshell_wrapper.file
units: dict[str, entity_instance] = {}
units: dict[str, ifcopenshell.entity_instance] = {}
history_size: int = 64
history: list[Transaction]
"""Chronological order - from oldest to newest."""
@@ -548,104 +525,13 @@ class file:
to_delete: Union[set[ifcopenshell.entity_instance], None] = None
"""Entities for batch removal."""
def __init__(
self,
f: Optional[ifcopenshell_wrapper.file] = None,
schema: Optional[ifcopenshell.util.schema.IFC_SCHEMA] = None,
schema_version: Optional[tuple[int, int, int, int]] = None,
):
"""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))
"""
if schema_version:
prefixes = ("IFC", "X", "_ADD", "_TC")
schema = "".join("".join(map(str, t)) if t[1] else "" for t in zip(prefixes, schema_version))
else:
schema = {"IFC4X3": "IFC4X3_ADD2"}.get(schema, schema)
if f is not None:
self.wrapped_data = f
if not f.good():
from . import Error, SchemaError
exc, msg = {
READ_ERROR: lambda: (IOError, "Unable to open file for reading"),
NO_HEADER: lambda: (Error, "Unable to parse IFC SPF header"),
UNSUPPORTED_SCHEMA: lambda: (
SchemaError,
"Unsupported schema: %s" % ",".join(self.header.file_schema.schema_identifiers),
),
INVALID_SYNTAX: lambda: (Error, "Syntax error during parse, check logs"),
# This is the case when passing uninitialized_tag
UNKNOWN: lambda: (None, None),
}[f.good().value()]()
if exc is not None:
raise exc(msg)
else:
args = filter(None, [schema])
args = map(ifcopenshell_wrapper.schema_by_name, args)
self.wrapped_data = ifcopenshell_wrapper.file(*args)
def post_init(self):
self.history = []
self.future = []
self.transaction: Optional[Transaction] = None
# we store a tuple of C++ file pointer address and creation time stamp so that
# when memory addresses get recycled we do not run into collisions when the
# address is used as a cache key.
file_dict[self.wrapped_data.file_pointer()] = (weakref.ref(self), time.monotonic_ns())
@property
def identifier(self) -> tuple[int, int]:
"""Pair of C++ file pointer address and creation time stamp to uniquely identify a file
over the life time of ifcopenshell module that should be mostly safe except in pathological
cases
Returns:
tuple[int, int]: Pair of C++ file pointer address and creation time stamp
"""
return (self.wrapped_data.file_pointer(), file_dict[self.wrapped_data.file_pointer()][1])
def __del__(self) -> None:
# Avoid infinite recursion if file is failed to initialize
# and wrapped_data is unset.
if "wrapped_data" not in dir(self):
return
del file_dict[self.file_pointer()]
def set_history_size(self, size: int) -> None:
self.history_size = size
while len(self.history) > self.history_size:
@@ -716,7 +602,7 @@ class file:
"""
eid = kwargs.pop("id", -1)
e = entity_instance((self.schema_identifier, type), self)
e = self.create(type)
# Create pairs of {attribute index, attribute value}.
# Keyword arguments are mapped to their corresponding
@@ -758,15 +644,6 @@ class file:
if attrs:
self.transaction = transaction
# 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()
if self.transaction:
self.transaction.store_create(e)
@@ -777,7 +654,7 @@ class file:
"""General IFC schema version: IFC2X3, IFC4, IFC4X3."""
prefixes = ("IFC", "X", "_ADD", "_TC")
reg = "".join(f"(?P<{s}>{s}\\d+)?" for s in prefixes)
match = re.match(reg, self.wrapped_data.schema)
match = re.match(reg, self.schema)
version_tuple = tuple(
map(
lambda pp: int(pp[1][len(pp[0]) :]) if pp[1] else None,
@@ -789,7 +666,7 @@ class file:
@property
def schema_identifier(self) -> str:
"""Full IFC schema version: IFC2X3_TC1, IFC4_ADD2, IFC4X3_ADD2, etc."""
return self.wrapped_data.schema
return self.schema
@property
def schema_version(self) -> tuple[int, int, int, int]:
@@ -797,7 +674,7 @@ class file:
E.g. IFC4X3_ADD2 is represented as (4, 3, 2, 0).
"""
schema = self.wrapped_data.schema
schema = self.schema
version = []
for prefix in ("IFC", "X", "_ADD", "_TC"):
number = re.search(prefix + r"(\d)", schema)
@@ -814,35 +691,16 @@ class file:
if attr[0:6] == "create":
return functools.partial(self.create_entity, attr[6:])
else:
return getattr(self.wrapped_data, attr)
return getattr(self, attr)
def __getitem__(self, key: Union[numbers.Integral, str, bytes]) -> entity_instance:
if isinstance(key, numbers.Integral):
return entity_instance(self.wrapped_data.by_id(key), self)
return self.by_id(key)
elif isinstance(key, (str, bytes)):
return entity_instance(self.wrapped_data.by_guid(str(key)), self)
return self.by_guid(str(key))
else:
raise TypeError("Indexing into file requires either an integral number or compressed guid string")
def by_id(self, id: int) -> ifcopenshell.entity_instance:
"""Return an IFC entity instance filtered by IFC ID.
:param id: STEP numerical identifier
:raises RuntimeError: If `id` is not found.
:returns: An ifcopenshell.entity_instance
"""
return self[id]
def by_guid(self, guid: str) -> ifcopenshell.entity_instance:
"""Return an IFC entity instance filtered by IFC GUID.
:param guid: GlobalId value in 22-character encoded form
:raises RuntimeError: If `guid` is not found.
:returns: An ifcopenshell.entity_instance
"""
return self[guid]
def add(self, inst: ifcopenshell.entity_instance, _id: int = None) -> ifcopenshell.entity_instance:
"""Adds an entity including any dependent entities to an IFC file.
@@ -853,9 +711,10 @@ class file:
"""
if self.transaction:
max_id = self.wrapped_data.getMaxId()
inst.wrapped_data.this.disown()
result = entity_instance(self.wrapped_data.add(inst.wrapped_data, -1 if _id is None else _id), self)
max_id = self.getMaxId()
result = self._add(inst, -1 if _id is None else _id)
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)]
@@ -874,8 +733,8 @@ class file:
:returns: A list of ifcopenshell.entity_instance objects
"""
if include_subtypes:
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)]
return self._by_type(type)
return self._by_type_excl_subtypes(type)
def traverse(
self, inst: ifcopenshell.entity_instance, max_levels: Optional[int] = None, breadth_first: bool = False
@@ -891,11 +750,11 @@ class file:
max_levels = -1
if breadth_first:
fn = self.wrapped_data.traverse_breadth_first
fn = self.traverse_breadth_first
else:
fn = self.wrapped_data.traverse
fn = self.traverse
return [entity_instance(e, self) for e in fn(inst.wrapped_data, max_levels)]
return fn(inst, max_levels)
@overload
def get_inverse(
@@ -940,11 +799,11 @@ class file:
if with_attribute_indices and not allow_duplicate:
raise ValueError("with_attribute_indices requires allow_duplicate to be True")
inverses = [entity_instance(e, self) for e in self.wrapped_data.get_inverse(inst.wrapped_data)]
inverses = [entity_instance(e, self) for e in self.get_inverse(inst.wrapped_data)]
if allow_duplicate:
if with_attribute_indices:
idxs = self.wrapped_data.get_inverse_indices(inst.wrapped_data)
idxs = self.get_inverse_indices(inst.wrapped_data)
# TODO: include in typing.
return list(zip(inverses, idxs))
else:
@@ -961,7 +820,7 @@ class file:
:param inst: The entity instance to get inverse relationships
:returns: The total number of references
"""
return self.wrapped_data.get_total_inverses(inst.wrapped_data)
return self.get_total_inverses(inst.wrapped_data)
def remove(self, inst: ifcopenshell.entity_instance) -> None:
"""Deletes an IFC object in the file.
@@ -974,22 +833,22 @@ class file:
"""
if self.transaction:
self.transaction.store_delete(inst)
return self.wrapped_data.remove(inst.wrapped_data)
return self.remove(inst.wrapped_data)
def batch(self):
"""Low-level mechanism to speed up deletion of large subgraphs"""
if self.transaction:
self.transaction.batch()
return self.wrapped_data.batch()
return self.batch()
def unbatch(self):
"""Low-level mechanism to speed up deletion of large subgraphs"""
if self.transaction:
self.transaction.unbatch()
return self.wrapped_data.unbatch()
return self.unbatch()
def __iter__(self) -> Generator[ifcopenshell.entity_instance, None, None]:
return iter(self[id] for id in self.wrapped_data.entity_names())
return iter(self[id] for id in self.entity_names())
def assign_header_from(self, other: ifcopenshell.file) -> None:
for k, vs in HEADER_FIELDS.items():
@@ -1024,7 +883,7 @@ class file:
raise NotImplementedError("Writing .ifcXML files is not supported")
if format == ".ifcZIP":
return self.write(path, ".ifc", zipped=True)
self.wrapped_data.write(str(path))
self.write(str(path))
if zipped:
unzipped_path = path.with_suffix(format)
@@ -1042,23 +901,18 @@ class file:
def from_string(s: str) -> file:
return file(ifcopenshell_wrapper.read(s))
@staticmethod
def from_pointer(address: int) -> file:
assert (f := file_dict[address][0]()) is not None
return f
def to_string(self) -> str:
return self.wrapped_data.to_string()
return self.to_string()
@property
def header(self) -> file_header:
# TODO: Workaround for old builds, remove after build stabilizes.
# TODO: No need for `wrapped_data.header` to be a method - should use `@property`?
header = self.wrapped_data.header
header = self.header
if isinstance(header, types.MethodType):
return file_header(self, self.wrapped_data.header())
return file_header(self, self.header())
else:
return self.wrapped_data.header
return self.header
@property
def storage(self) -> Optional[rocksdb_file_storage]:
@@ -1066,5 +920,5 @@ class file:
Returns:
Optional[rocksdb_file_storage]: underlying key-value store interface when opened as a RocksDB-backed file
"""
if self.wrapped_data.storage_mode() == 1:
if self.storage_mode() == 1:
return rocksdb_file_storage(self)
+2 -2
View File
@@ -27,8 +27,8 @@ import ifcopenshell.util.schema
from pathlib import Path
from typing import Any, NoReturn, Union, Optional, TYPE_CHECKING, TypedDict
from . import ifcopenshell_wrapper
from .file import file
from .entity_instance import entity_instance
from . import file
from . import entity_instance
if TYPE_CHECKING:
import sqlite3
@@ -24,9 +24,9 @@ try:
import ifcopenshell.util.attribute
import ifcopenshell.util.schema
from .file import file
from . import file
from . import ifcopenshell_wrapper
from .entity_instance import entity_instance
from . import entity_instance
from lark import Lark, Transformer
from typing import Any, NoReturn, Union, Optional