225 lines
6.0 KiB
Python
225 lines
6.0 KiB
Python
# Copyright (c) 2022-2024, Manfred Moitzi
|
|
# License: MIT License
|
|
from __future__ import annotations
|
|
from typing import TypeVar, Generic, TYPE_CHECKING, Optional
|
|
from abc import ABC, abstractmethod
|
|
from .const import NULL_PTR_NAME, MIN_EXPORT_VERSION
|
|
from .hdr import AcisHeader
|
|
|
|
if TYPE_CHECKING:
|
|
from .entities import AcisEntity
|
|
from ezdxf.math import Vec3
|
|
|
|
|
|
T = TypeVar("T", bound="AbstractEntity")
|
|
|
|
|
|
class AbstractEntity(ABC):
|
|
"""Unified query interface for SAT and SAB data."""
|
|
|
|
name: str
|
|
id: int = -1
|
|
|
|
def __str__(self):
|
|
return f"{self.name}"
|
|
|
|
@property
|
|
def is_null_ptr(self) -> bool:
|
|
"""Returns ``True`` if this entity is the ``NULL_PTR`` entity."""
|
|
return self.name == NULL_PTR_NAME
|
|
|
|
|
|
class DataLoader(ABC):
|
|
"""Data loading interface to create high level AcisEntity data from low
|
|
level AbstractEntity representation.
|
|
|
|
"""
|
|
|
|
version: int = MIN_EXPORT_VERSION
|
|
|
|
@abstractmethod
|
|
def has_data(self) -> bool:
|
|
pass
|
|
|
|
@abstractmethod
|
|
def read_int(self, skip_sat: Optional[int] = None) -> int:
|
|
"""There are sometimes additional int values in SAB files which are
|
|
not present in SAT files, maybe reference counters e.g. vertex, coedge.
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def read_double(self) -> float:
|
|
pass
|
|
|
|
@abstractmethod
|
|
def read_interval(self) -> float:
|
|
pass
|
|
|
|
@abstractmethod
|
|
def read_vec3(self) -> tuple[float, float, float]:
|
|
pass
|
|
|
|
@abstractmethod
|
|
def read_bool(self, true: str, false: str) -> bool:
|
|
pass
|
|
|
|
@abstractmethod
|
|
def read_str(self) -> str:
|
|
pass
|
|
|
|
@abstractmethod
|
|
def read_ptr(self) -> AbstractEntity:
|
|
pass
|
|
|
|
@abstractmethod
|
|
def read_transform(self) -> list[float]:
|
|
pass
|
|
|
|
|
|
class DataExporter(ABC):
|
|
version: int = MIN_EXPORT_VERSION
|
|
|
|
@abstractmethod
|
|
def write_int(self, value: int, skip_sat=False) -> None:
|
|
"""There are sometimes additional int values in SAB files which are
|
|
not present in SAT files, maybe reference counters e.g. vertex, coedge.
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def write_double(self, value: float) -> None:
|
|
pass
|
|
|
|
@abstractmethod
|
|
def write_interval(self, value: float) -> None:
|
|
pass
|
|
|
|
@abstractmethod
|
|
def write_loc_vec3(self, value: Vec3) -> None:
|
|
pass
|
|
|
|
@abstractmethod
|
|
def write_dir_vec3(self, value: Vec3) -> None:
|
|
pass
|
|
|
|
@abstractmethod
|
|
def write_bool(self, value: bool, true: str, false: str) -> None:
|
|
pass
|
|
|
|
@abstractmethod
|
|
def write_str(self, value: str) -> None:
|
|
pass
|
|
|
|
@abstractmethod
|
|
def write_literal_str(self, value: str) -> None:
|
|
pass
|
|
|
|
@abstractmethod
|
|
def write_ptr(self, entity: AcisEntity) -> None:
|
|
pass
|
|
|
|
@abstractmethod
|
|
def write_transform(self, data: list[str]) -> None:
|
|
pass
|
|
|
|
|
|
class AbstractBuilder(Generic[T]):
|
|
header: AcisHeader
|
|
bodies: list[T]
|
|
entities: list[T]
|
|
|
|
def reorder_records(self) -> None:
|
|
if len(self.entities) == 0:
|
|
return
|
|
header: list[T] = []
|
|
entities: list[T] = []
|
|
for e in self.entities:
|
|
if e.name == "body":
|
|
header.append(e)
|
|
elif e.name == "asmheader":
|
|
header.insert(0, e) # has to be the first record
|
|
else:
|
|
entities.append(e)
|
|
self.entities = header + entities
|
|
|
|
def reset_ids(self, start: int = 0) -> None:
|
|
for num, entity in enumerate(self.entities, start=start):
|
|
entity.id = num
|
|
|
|
def clear_ids(self) -> None:
|
|
for entity in self.entities:
|
|
entity.id = -1
|
|
|
|
|
|
class EntityExporter(Generic[T]):
|
|
def __init__(self, header: AcisHeader):
|
|
self.header = header
|
|
self.version = header.version
|
|
self._exported_entities: dict[int, T] = {}
|
|
if self.header.has_asm_header:
|
|
self.export(self.header.asm_header())
|
|
|
|
def export_records(self) -> list[T]:
|
|
return list(self._exported_entities.values())
|
|
|
|
@abstractmethod
|
|
def make_record(self, entity: AcisEntity) -> T:
|
|
pass
|
|
|
|
@abstractmethod
|
|
def make_data_exporter(self, record: T) -> DataExporter:
|
|
pass
|
|
|
|
def get_record(self, entity: AcisEntity) -> T:
|
|
assert not entity.is_none
|
|
return self._exported_entities[id(entity)]
|
|
|
|
def export(self, entity: AcisEntity):
|
|
if entity.is_none:
|
|
raise TypeError("invalid NONE_REF entity given")
|
|
self._make_all_records(entity)
|
|
self._export_data(entity)
|
|
|
|
def _has_record(self, entity: AcisEntity) -> bool:
|
|
return id(entity) in self._exported_entities
|
|
|
|
def _add_record(self, entity: AcisEntity, record: T) -> None:
|
|
assert not entity.is_none
|
|
self._exported_entities[id(entity)] = record
|
|
|
|
def _make_all_records(self, entity: AcisEntity):
|
|
def add(e: AcisEntity) -> bool:
|
|
if not e.is_none and not self._has_record(e):
|
|
self._add_record(e, self.make_record(e))
|
|
return True
|
|
return False
|
|
|
|
entities = [entity]
|
|
while entities:
|
|
next_entity = entities.pop(0)
|
|
add(next_entity)
|
|
for sub_entity in next_entity.entities():
|
|
if add(sub_entity):
|
|
entities.append(sub_entity)
|
|
|
|
def _export_data(self, entity: AcisEntity):
|
|
def _export_record(e: AcisEntity):
|
|
if id(e) not in done:
|
|
done.add(id(e))
|
|
record = self.get_record(e)
|
|
if not e.attributes.is_none:
|
|
record.attributes = self.get_record(e.attributes) # type: ignore
|
|
e.export(self.make_data_exporter(record))
|
|
return True
|
|
return False
|
|
|
|
entities = [entity]
|
|
done: set[int] = set()
|
|
while entities:
|
|
next_entity = entities.pop(0)
|
|
_export_record(next_entity)
|
|
for sub_entity in next_entity.entities():
|
|
if _export_record(sub_entity):
|
|
entities.append(sub_entity)
|