Files
pdm/creoparams/drawing.py
T
davideandClaude Opus 5 b59380fcab Anteprime, decompressione LZW e contenuto dei disegni
Aggiunge tre capacita' al lettore, tutte senza dipendenze esterne.

Anteprima incorporata (preview.py)
    Ogni file contiene una miniatura JPEG dell'ultimo salvataggio: il
    render del pezzo nei modelli, l'immagine della tavola nei disegni.
    Non e' referenziata dall'indice, quindi va cercata per firma: ogni
    candidato viene validato percorrendone i segmenti JPEG, cosi' i byte
    che iniziano per FFD8FF per caso non passano. Sui file campione il
    risultato e' esattamente una immagine per file.

Decompressione compress(1) (lzw.py)
    Creo comprime alcuni blocchi nel vecchio formato .Z. Rispetto a un
    LZW generico va riprodotto l'allineamento a gruppi di otto codici:
    senza, la decompressione produce qualche centinaio di byte corretti
    e poi diverge silenziosamente.

Contenuto dei disegni (drawing.py)
    Un .drw non ha la tabella parametri del modello ma contiene la
    rappresentazione vettoriale della tavola. Ne estraiamo i testi:
    cartiglio, note, tolleranze, quote. Le tavole diventano cosi'
    ricercabili per contenuto. Nel disegno i valori compaiono gia'
    risolti come testo, compreso il peso, mentre nel .prt sono ancora
    in codifica binaria.

I blocchi oltre l'indice sono ora interpretati da ugc.iter_blocks, che
decomprime quando serve: lo stesso blocco puo' essere memorizzato
compresso in un file e in chiaro in un altro, quindi la scelta va fatta
guardando i byte e non il nome. Cercare soltanto gli stream compressi
faceva perdere i testi su un terzo dei disegni.

Sulla codifica dei numeri: sono double IEEE big-endian con i byte bassi
omessi, preceduti da un byte-tag. Lo dimostrano i disegni, dove size_x e
size_y valgono 420,0 x 297,0 (un A3 esatto). La mappatura del byte-tag
resta pero' incerta, quindi PESO e DENSITA continuano a essere riportati
come encoded_not_decoded: un valore plausibile ma sbagliato sarebbe
peggio di un valore assente.

La geometria 3D resta fuori portata, e ora e' verificato: in 200 KB di
blocchi geometrici non compare una sola costante 1.0 in nessuna codifica
in virgola mobile. Quei blocchi sono compressi con un algoritmo che non
espone firme note.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 14:51:39 +02:00

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,
}