194 lines
6.4 KiB
Python
194 lines
6.4 KiB
Python
# Copyright (c) 2022-2024, Manfred Moitzi
|
|
# License: MIT License
|
|
from __future__ import annotations
|
|
from typing import Iterator, Callable, Any
|
|
from .entities import (
|
|
AcisEntity,
|
|
NONE_REF,
|
|
Face,
|
|
Coedge,
|
|
Loop,
|
|
Vertex,
|
|
)
|
|
from . import sab
|
|
|
|
|
|
class AcisDebugger:
|
|
def __init__(self, root: AcisEntity = NONE_REF, start_id: int = 1):
|
|
self._next_id = start_id - 1
|
|
self._root: AcisEntity = root
|
|
self.entities: dict[int, AcisEntity] = dict()
|
|
if not root.is_none:
|
|
self._store_entities(root)
|
|
|
|
@property
|
|
def root(self) -> AcisEntity:
|
|
return self._root
|
|
|
|
def _get_id(self) -> int:
|
|
self._next_id += 1
|
|
return self._next_id
|
|
|
|
def _store_entities(self, entity: AcisEntity) -> None:
|
|
if not entity.is_none and entity.id == -1:
|
|
entity.id = self._get_id()
|
|
self.entities[entity.id] = entity
|
|
for e in vars(entity).values():
|
|
if isinstance(e, AcisEntity) and e.id == -1:
|
|
self._store_entities(e)
|
|
|
|
def set_entities(self, entity: AcisEntity) -> None:
|
|
self.entities.clear()
|
|
self._root = entity
|
|
self._store_entities(entity)
|
|
|
|
def walk(self, root: AcisEntity = NONE_REF) -> Iterator[AcisEntity]:
|
|
def _walk(entity: AcisEntity):
|
|
if entity.is_none:
|
|
return
|
|
yield entity
|
|
done.add(entity.id)
|
|
for e in vars(entity).values():
|
|
if isinstance(e, AcisEntity) and e.id not in done:
|
|
yield from _walk(e)
|
|
|
|
if root.is_none:
|
|
root = self._root
|
|
done: set[int] = set()
|
|
yield from _walk(root)
|
|
|
|
def filter(
|
|
self, func: Callable[[AcisEntity], bool], entity: AcisEntity = NONE_REF
|
|
) -> Iterator[Any]:
|
|
if entity.is_none:
|
|
entity = self._root
|
|
yield from filter(func, self.walk(entity))
|
|
|
|
def filter_type(
|
|
self, name: str, entity: AcisEntity = NONE_REF
|
|
) -> Iterator[Any]:
|
|
if entity.is_none:
|
|
entity = self._root
|
|
yield from filter(lambda x: x.type == name, self.walk(entity))
|
|
|
|
@staticmethod
|
|
def entity_attributes(entity: AcisEntity, indent: int = 0) -> Iterator[str]:
|
|
indent_str = " " * indent
|
|
for name, data in vars(entity).items():
|
|
if name == "id":
|
|
continue
|
|
yield f"{indent_str}{name}: {data}"
|
|
|
|
def face_link_structure(self, face: Face, indent: int = 0) -> Iterator[str]:
|
|
indent_str = " " * indent
|
|
|
|
while not face.is_none:
|
|
partner_faces = list(self.partner_faces(face))
|
|
error = ""
|
|
linked_partner_faces = []
|
|
unlinked_partner_faces = []
|
|
for pface_id in partner_faces:
|
|
pface = self.entities.get(pface_id)
|
|
if pface is None:
|
|
error += f" face {pface_id} does not exist;"
|
|
if isinstance(pface, Face):
|
|
reverse_faces = self.partner_faces(pface)
|
|
if face.id in reverse_faces:
|
|
linked_partner_faces.append(pface_id)
|
|
else:
|
|
unlinked_partner_faces.append(pface_id)
|
|
else:
|
|
error += f" entity {pface_id} is not a face;"
|
|
if unlinked_partner_faces:
|
|
error = f"unlinked partner faces: {unlinked_partner_faces} {error}"
|
|
yield f"{indent_str}{str(face)} >> {partner_faces} {error}"
|
|
face = face.next_face
|
|
|
|
@staticmethod
|
|
def partner_faces(face: Face) -> Iterator[int]:
|
|
coedges: list[Coedge] = []
|
|
loop = face.loop
|
|
while not loop.is_none:
|
|
coedges.extend(co for co in loop.coedges())
|
|
loop = loop.next_loop
|
|
for coedge in coedges:
|
|
for partner_coedge in coedge.partner_coedges():
|
|
yield partner_coedge.loop.face.id
|
|
|
|
@staticmethod
|
|
def coedge_structure(face: Face, ident: int = 4) -> list[str]:
|
|
lines: list[str] = []
|
|
coedges: list[Coedge] = []
|
|
loop = face.loop
|
|
|
|
while not loop.is_none:
|
|
coedges.extend(co for co in loop.coedges())
|
|
loop = loop.next_loop
|
|
for coedge in coedges:
|
|
edge1 = coedge.edge
|
|
sense1 = coedge.sense
|
|
lines.append(f"Coedge={coedge.id} edge={edge1.id} sense={sense1}")
|
|
for partner_coedge in coedge.partner_coedges():
|
|
edge2 = partner_coedge.edge
|
|
sense2 = partner_coedge.sense
|
|
lines.append(
|
|
f" Partner Coedge={partner_coedge.id} edge={edge2.id} sense={sense2}"
|
|
)
|
|
ident_str = " " * ident
|
|
return [ident_str + line for line in lines]
|
|
|
|
@staticmethod
|
|
def loop_vertices(loop: Loop, indent: int = 0) -> str:
|
|
indent_str = " " * indent
|
|
return f"{indent_str}{loop} >> {list(AcisDebugger.loop_edges(loop))}"
|
|
|
|
@staticmethod
|
|
def loop_edges(loop: Loop) -> Iterator[list[int]]:
|
|
coedge = loop.coedge
|
|
first = coedge
|
|
while not coedge.is_none:
|
|
edge = coedge.edge
|
|
sv = edge.start_vertex
|
|
ev = edge.end_vertex
|
|
if coedge.sense:
|
|
yield [ev.id, sv.id]
|
|
else:
|
|
yield [sv.id, ev.id]
|
|
coedge = coedge.next_coedge
|
|
if coedge is first:
|
|
break
|
|
|
|
def vertex_to_edge_relation(self) -> Iterator[str]:
|
|
for vertex in (
|
|
e for e in self.entities.values() if isinstance(e, Vertex)
|
|
):
|
|
edge = vertex.edge
|
|
sv = edge.start_vertex
|
|
ev = edge.end_vertex
|
|
yield f"{vertex}: parent edge is {edge.id}; {sv.id} => {ev.id}; {edge.curve}"
|
|
|
|
def is_manifold(self) -> bool:
|
|
for coedge in self.filter_type("coedge"):
|
|
if len(coedge.partner_coedges()) > 1:
|
|
return False
|
|
return True
|
|
|
|
|
|
def dump_sab_as_text(data: bytes) -> Iterator[str]:
|
|
def entity_data(e):
|
|
for tag, value in e:
|
|
name = sab.Tags(tag).name
|
|
yield f"{name} = {value}"
|
|
|
|
decoder = sab.Decoder(data)
|
|
header = decoder.read_header()
|
|
yield from header.dumps()
|
|
index = 0
|
|
try:
|
|
for record in decoder.read_records():
|
|
yield f"--------------------- record: {index}"
|
|
yield from entity_data(record)
|
|
index += 1
|
|
except sab.ParsingError as e:
|
|
yield str(e)
|