101 lines
3.2 KiB
Python
101 lines
3.2 KiB
Python
"""Lettura del contenuto dei disegni Creo (`.drw`).
|
|||
|
|
|
||
|
|
Un disegno non contiene la tabella parametri del modello: contiene un blocco
|
||
|
|
`PictureObj`, compresso con compress(1), con la rappresentazione vettoriale
|
||
|
|
della tavola. Dentro ci sono le primitive grafiche (linee, archi, polilinee,
|
||
|
|
testi con font, colore e rotazione) e, soprattutto, **tutti i testi**: voci
|
||
|
|
del cartiglio, note, tolleranze.
|
||
|
|
|
||
|
|
Qui estraiamo i testi, che rendono le tavole ricercabili per contenuto. La
|
||
|
|
geometria e' presente e decodificabile (le coordinate sono double IEEE con i
|
||
|
|
byte bassi omessi) ma il suo disegno non e' implementato.
|
||
|
|
|
||
|
|
I testi seguono uno schema regolare:
|
||
|
|
|
||
|
|
18 e3 e3 43 <a> <b> <testo> 00
|
||
|
|
|
||
|
|
dove il secondo byte vale 2*len+2. Verifichiamo la lunghezza dichiarata
|
||
|
|
contro quella reale: e' un controllo a costo nullo che scarta i falsi
|
||
|
|
riscontri.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import re
|
||
|
|
|
||
|
|
from . import ugc
|
||
|
|
|
||
|
|
TEXT_MARK = b"\x18\xe3\xe3\x43"
|
||
|
|
MAX_TEXT = 512
|
||
|
|
|
||
|
|
_STRING_BYTES = frozenset(
|
||
|
|
set(range(0x20, 0x7F)) | (set(range(0xA0, 0x100)) - {0xE1, 0xE3, 0xF6, 0xF7})
|
||
|
|
)
|
||
|
|
|
||
|
|
#: Primitive grafiche note, utili per descrivere il contenuto della tavola.
|
||
|
|
_PRIMITIVE_RE = re.compile(rb"value\((prim_[a-z_]+)\)")
|
||
|
|
|
||
|
|
|
||
|
|
def _decode(raw: bytes) -> str:
|
||
|
|
try:
|
||
|
|
return raw.decode("utf-8")
|
||
|
|
except UnicodeDecodeError:
|
||
|
|
return raw.decode("cp1252", errors="replace")
|
||
|
|
|
||
|
|
|
||
|
|
def iter_texts(blob: bytes) -> "list[str]":
|
||
|
|
"""Testi presenti in un blocco PGL decompresso, in ordine di comparsa."""
|
||
|
|
texts, pos = [], 0
|
||
|
|
while True:
|
||
|
|
pos = blob.find(TEXT_MARK, pos)
|
||
|
|
if pos < 0:
|
||
|
|
return texts
|
||
|
|
start = pos + len(TEXT_MARK) + 2
|
||
|
|
declared = blob[pos + len(TEXT_MARK) + 1]
|
||
|
|
|
||
|
|
end = start
|
||
|
|
limit = min(len(blob), start + MAX_TEXT)
|
||
|
|
while end < limit and blob[end] != 0x00:
|
||
|
|
if blob[end] not in _STRING_BYTES:
|
||
|
|
break
|
||
|
|
end += 1
|
||
|
|
|
||
|
|
length = end - start
|
||
|
|
# Lo schema dichiara 2*len+2: se non torna, non e' un testo.
|
||
|
|
if length and end < limit and blob[end] == 0x00 and declared == (2 * length + 2) % 256:
|
||
|
|
texts.append(_decode(blob[start:end]))
|
||
|
|
pos = end
|
||
|
|
else:
|
||
|
|
pos += 1
|
||
|
|
|
||
|
|
|
||
|
|
#: Blocchi che contengono la rappresentazione grafica della tavola.
|
||
|
|
GRAPHIC_BLOCKS = ("PictureObj", "PicturePrimdata", "PicturePersistTable")
|
||
|
|
|
||
|
|
|
||
|
|
def read(data: bytes) -> dict:
|
||
|
|
"""Estrae testi e primitive dai blocchi grafici della tavola."""
|
||
|
|
blocks = [b for b in ugc.iter_blocks(data)
|
||
|
|
if b[0].split("#", 1)[0] in GRAPHIC_BLOCKS]
|
||
|
|
blob = b"".join(payload for _, payload, _ in blocks)
|
||
|
|
compressed = sum(1 for _, _, was_compressed in blocks if was_compressed)
|
||
|
|
|
||
|
|
texts = iter_texts(blob)
|
||
|
|
seen, unique = set(), []
|
||
|
|
for text in texts:
|
||
|
|
stripped = text.strip()
|
||
|
|
if stripped and stripped not in seen:
|
||
|
|
seen.add(stripped)
|
||
|
|
unique.append(stripped)
|
||
|
|
|
||
|
|
primitives = sorted({m.decode() for m in _PRIMITIVE_RE.findall(blob)})
|
||
|
|
|
||
|
|
return {
|
||
|
|
"graphic_blocks": len(blocks),
|
||
|
|
"compressed_blocks": compressed,
|
||
|
|
"decompressed_bytes": len(blob),
|
||
|
|
"primitives": primitives,
|
||
|
|
"text_count": len(texts),
|
||
|
|
"texts": unique,
|
||
|
|
}
|