mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-25 09:32:37 +00:00
Rocksdb storage -- ifcopenshell.file compatibility
This commit is contained in:
@@ -85,7 +85,7 @@ try:
|
|||||||
except Exception:
|
except Exception:
|
||||||
raise ImportError("IfcOpenShell not built for '%s'" % python_distribution)
|
raise ImportError("IfcOpenShell not built for '%s'" % python_distribution)
|
||||||
|
|
||||||
from .file import file
|
from .file import rocksdb_lazy_instance, file
|
||||||
from . import guid
|
from . import guid
|
||||||
from .entity_instance import entity_instance, register_schema_attributes
|
from .entity_instance import entity_instance, register_schema_attributes
|
||||||
from .sql import sqlite, sqlite_entity
|
from .sql import sqlite, sqlite_entity
|
||||||
|
|||||||
@@ -276,22 +276,22 @@ binary_deserializers = (
|
|||||||
lambda __, val: struct.unpack("@d", val)[0],
|
lambda __, val: struct.unpack("@d", val)[0],
|
||||||
lambda __, val: val.decode("utf-8"),
|
lambda __, val: val.decode("utf-8"),
|
||||||
lambda __, val: val.decode("utf-8"),
|
lambda __, val: val.decode("utf-8"),
|
||||||
lambda storage, val: ifcopenshell_wrapper.schema_by_name(storage.schema)
|
lambda storage, val: ifcopenshell_wrapper.schema_by_name(storage.schema_identifier)
|
||||||
.declarations()[struct.unpack("@q", val[:8])[0]]
|
.declarations()[struct.unpack("@q", val[:8])[0]]
|
||||||
.enumeration_items()[struct.unpack("@q", val[8:])[0]],
|
.enumeration_items()[struct.unpack("@q", val[8:])[0]],
|
||||||
lambda storage, val: storage.by_id((val[0] == "i", struct.unpack("@q", val[1:])[0])),
|
lambda storage, val: storage.by_id((val[0] == 105, struct.unpack("@q", val[1:])[0])),
|
||||||
lambda __, _: (),
|
lambda __, _: (),
|
||||||
lambda __, val: struct.unpack("@" + "i" * (len(val) // 4), val),
|
lambda __, val: struct.unpack("@" + "i" * (len(val) // 4), val),
|
||||||
lambda __, val: struct.unpack("@" + "d" * (len(val) // 8), val),
|
lambda __, val: struct.unpack("@" + "d" * (len(val) // 8), val),
|
||||||
lambda __, val: tuple(consume_buffer(val, lambda inner: inner.decode("utf-8"))),
|
lambda __, val: tuple(consume_buffer(val, lambda inner: inner.decode("utf-8"))),
|
||||||
lambda __, val: tuple(consume_buffer(val, lambda inner: inner.decode("utf-8"))),
|
lambda __, val: tuple(consume_buffer(val, lambda inner: inner.decode("utf-8"))),
|
||||||
lambda storage, val: tuple(
|
lambda storage, val: tuple(
|
||||||
storage.by_id((val[i * 9] == "i", struct.unpack("@q", val[i * 9 + 1 : i * 9 + 9])[0]))
|
storage.by_id((val[i * 9] == 105, struct.unpack("@q", val[i * 9 + 1 : i * 9 + 9])[0]))
|
||||||
for i in range(len(val) // 9)
|
for i in range(len(val) // 9)
|
||||||
),
|
),
|
||||||
lambda __, _: ((),),
|
lambda __, _: ((),),
|
||||||
lambda __, val: tuple(
|
lambda __, val: tuple(
|
||||||
consume_buffer(val, lambda inner: struct.unpack("@" + "i" * (len(inner) // struct.calcsize("@i")), inner))
|
consume_buffer(val, lambda inner: struct.unpack("@" + 105 * (len(inner) // struct.calcsize("@i")), inner))
|
||||||
),
|
),
|
||||||
lambda __, val: tuple(
|
lambda __, val: tuple(
|
||||||
consume_buffer(val, lambda inner: struct.unpack("@" + "d" * (len(inner) // struct.calcsize("@d")), inner))
|
consume_buffer(val, lambda inner: struct.unpack("@" + "d" * (len(inner) // struct.calcsize("@d")), inner))
|
||||||
@@ -300,15 +300,40 @@ binary_deserializers = (
|
|||||||
consume_buffer(
|
consume_buffer(
|
||||||
val,
|
val,
|
||||||
lambda inner: tuple(
|
lambda inner: tuple(
|
||||||
storage.by_id((inner[i * 9] == "i", struct.unpack("@q", inner[i * 9 + 1 : i * 9 + 9])[0]))
|
storage.by_id((inner[i * 9] == 105, struct.unpack("@q", inner[i * 9 + 1 : i * 9 + 9])[0]))
|
||||||
for i in range(len(inner) // 9)
|
for i in range(len(inner) // 9)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@functools.cache
|
||||||
|
def attribute_lookup(schema_name, entity_name):
|
||||||
|
decl = ifcopenshell_wrapper.schema_by_name(schema_name).declaration_by_name(entity_name)
|
||||||
|
attributes = (
|
||||||
|
decl
|
||||||
|
.as_entity()
|
||||||
|
.all_attributes()
|
||||||
|
)
|
||||||
|
di = {v:k for k, v in enumerate(a.name() for a in attributes)}
|
||||||
|
all_inverses = decl.as_entity().all_inverse_attributes()
|
||||||
|
for inv in all_inverses:
|
||||||
|
def visit(decl):
|
||||||
|
yield decl.index_in_schema()
|
||||||
|
for ty in decl.subtypes():
|
||||||
|
yield from visit(ty)
|
||||||
|
attribute_index = inv.entity_reference().attribute_index(inv.attribute_reference())
|
||||||
|
entity_indices = list(visit(inv.entity_reference()))
|
||||||
|
di[inv.name()] = (entity_indices, attribute_index)
|
||||||
|
return di
|
||||||
|
|
||||||
|
@functools.cache
|
||||||
|
def entity_name_lookup(schema_name, index):
|
||||||
|
return ifcopenshell_wrapper.schema_by_name(schema_name).declarations()[index].name()
|
||||||
|
|
||||||
class rocksdb_lazy_instance:
|
class rocksdb_lazy_instance:
|
||||||
|
__slots__ = ("storage", "name")
|
||||||
|
|
||||||
def _transform_value(self, val: bytes) -> Any:
|
def _transform_value(self, val: bytes) -> Any:
|
||||||
if not val:
|
if not val:
|
||||||
return None
|
return None
|
||||||
@@ -319,25 +344,29 @@ class rocksdb_lazy_instance:
|
|||||||
self.storage = storage
|
self.storage = storage
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
|
@functools.cache
|
||||||
def is_a(self):
|
def is_a(self):
|
||||||
if self.name.startswith("h|"):
|
if self.name.startswith("h|"):
|
||||||
return self.name[2:]
|
return self.name[2:]
|
||||||
idx = struct.unpack("@q", self.storage.read(f"{self.name}|_"))[0]
|
idx = struct.unpack("@q", self.storage.read(f"{self.name}|_"))[0]
|
||||||
return ifcopenshell_wrapper.schema_by_name(self.storage.schema).declarations()[idx].name()
|
return entity_name_lookup(self.storage.schema_identifier, idx)
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
attributes = (
|
attr = attribute_lookup(self.storage.schema_identifier, self.is_a()).get(name)
|
||||||
ifcopenshell_wrapper.schema_by_name(self.storage.schema)
|
if isinstance(attr, int):
|
||||||
.declaration_by_name(self.is_a())
|
return self[attr]
|
||||||
.as_entity()
|
else:
|
||||||
.all_attributes()
|
entity_indices, attribute_index = attr
|
||||||
)
|
def _():
|
||||||
if idx := next((i + 1 for i, n in enumerate(a.name() for a in attributes) if n == name), None):
|
for index_in_schema in entity_indices:
|
||||||
return self[idx - 1]
|
buffer = self.storage.read(f'v|{self.name[2:]}|{index_in_schema}|{attribute_index}') or b''
|
||||||
|
yield from map(self.storage.by_id, struct.unpack('<' + 'I' * (len(buffer) // 4), buffer))
|
||||||
|
return list(_())
|
||||||
|
|
||||||
def __getitem__(self, index):
|
def __getitem__(self, index):
|
||||||
return self._transform_value(self.storage.read(f"{self.name}|{index}"))
|
return self._transform_value(self.storage.read(f"{self.name}|{index}"))
|
||||||
|
|
||||||
|
@functools.cache
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return (
|
return (
|
||||||
max(
|
max(
|
||||||
@@ -378,9 +407,24 @@ class rocksdb_lazy_instance:
|
|||||||
def id(self):
|
def id(self):
|
||||||
if self.name.startswith("i|"):
|
if self.name.startswith("i|"):
|
||||||
return int(self.name[2:])
|
return int(self.name[2:])
|
||||||
|
else:
|
||||||
|
# compatibility with C++
|
||||||
|
return 0
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
return len(self) > 0
|
return len(self) > 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _comparison_tuple(self):
|
||||||
|
return self.storage.file.file_pointer(), self.name
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, rocksdb_lazy_instance):
|
||||||
|
return False
|
||||||
|
return self._comparison_tuple == other._comparison_tuple
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self._comparison_tuple)
|
||||||
|
|
||||||
|
|
||||||
class rocksdb_file_storage:
|
class rocksdb_file_storage:
|
||||||
@@ -406,6 +450,18 @@ class rocksdb_file_storage:
|
|||||||
raise KeyError(f"Instance with name {name} not found in file")
|
raise KeyError(f"Instance with name {name} not found in file")
|
||||||
return inst
|
return inst
|
||||||
|
|
||||||
|
def by_type(self, ty:str):
|
||||||
|
def visit(decl):
|
||||||
|
yield decl.index_in_schema()
|
||||||
|
for ty in decl.subtypes():
|
||||||
|
yield from visit(ty)
|
||||||
|
decl = ifcopenshell_wrapper.schema_by_name(self.schema_identifier).declaration_by_name(ty)
|
||||||
|
def _():
|
||||||
|
for index in visit(decl):
|
||||||
|
buff = self.read(f't|{index}') or b''
|
||||||
|
yield from map(self.by_id, struct.unpack('@' + 'q' * (len(buff) // 8), buff))
|
||||||
|
return list(_())
|
||||||
|
|
||||||
__getitem__ = by_id
|
__getitem__ = by_id
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
@@ -421,9 +477,24 @@ class rocksdb_file_storage:
|
|||||||
return rocksdb_file_storage(self.file, self._prefix + prefix)
|
return rocksdb_file_storage(self.file, self._prefix + prefix)
|
||||||
|
|
||||||
@functools.cached_property
|
@functools.cached_property
|
||||||
def schema(self):
|
def schema_identifier(self):
|
||||||
|
"""Full IFC schema version: IFC2X3_TC1, IFC4_ADD2, IFC4X3_ADD2, etc."""
|
||||||
return rocksdb_lazy_instance(self, f"h|file_schema")[0][0]
|
return rocksdb_lazy_instance(self, f"h|file_schema")[0][0]
|
||||||
|
|
||||||
|
@functools.cached_property
|
||||||
|
def schema(self) -> ifcopenshell.util.schema.IFC_SCHEMA:
|
||||||
|
"""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.schema_identifier)
|
||||||
|
version_tuple = tuple(
|
||||||
|
map(
|
||||||
|
lambda pp: int(pp[1][len(pp[0]) :]) if pp[1] else None,
|
||||||
|
((p, match.group(p)) for p in prefixes),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return "".join("".join(map(str, t)) if t[1] else "" for t in zip(prefixes, version_tuple[0:2]))
|
||||||
|
|
||||||
|
|
||||||
class file:
|
class file:
|
||||||
"""Base class for containing IFC files.
|
"""Base class for containing IFC files.
|
||||||
|
|||||||
Reference in New Issue
Block a user