feat: add DXF inventory script for main-line (본선) drawings

Scans 21 main-line DXF files (02 평면·종단, 03 본선굴착, 04 본선기타) for
header, layers, entity counts, extents, 3D flag and text samples. Strict
load failures fall back to ezdxf recover mode and are recorded.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A5Cun3RHQKna9qxE6ybZ4A
This commit is contained in:
Claude committed 2026-10-20 23:25:05 +00:00
commit cc381b9d50
3 files changed
+1618

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
data/*.tmp
__pycache__/
*.pyc
File diff suppressed because it is too large. Load diff
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env python3
"""Inventory the main-line (본선) DXF drawings: header, layers, entity counts,
extents, 3D flag and text samples. Read-only on the source RAG folder."""
import collections
import glob
import json
import os
import sys
import ezdxf
from ezdxf import bbox, recover
RAG_DIR = sys.argv[1]
OUT_JSON = sys.argv[2]
# 본선 scope (approved 2026-10-21): 02 평면·종단, 03 본선굴착, 04 본선기타
SUBDIRS = ["02_평면 및 종단면도", "03_본선터널굴착", "04_본선터널기타"]
def inventory(path):
rec = {"file": os.path.basename(path), "ok": False}
rec["recovered"] = False
try:
doc = ezdxf.readfile(path)
except Exception as exc: # strict load failed: retry in recover mode and record it
strict_error = f"{type(exc).__name__}: {exc}"
try:
doc, _auditor = recover.readfile(path)
rec["recovered"] = True
rec["strict_error"] = strict_error
except Exception as exc2:
rec["error"] = f"{strict_error} | recover: {type(exc2).__name__}: {exc2}"
return rec
hdr = doc.header
rec["dxfversion"] = doc.dxfversion
rec["insunits"] = hdr.get("$INSUNITS")
rec["codepage"] = hdr.get("$DWGCODEPAGE")
rec["layers"] = len(doc.layers)
msp = doc.modelspace()
counts = collections.Counter(e.dxftype() for e in msp)
rec["entity_counts"] = dict(counts.most_common())
rec["blocks"] = len([b for b in doc.blocks if not b.name.startswith("*")])
z_vals = []
texts = []
for e in msp:
t = e.dxftype()
if t == "LINE":
z_vals += [e.dxf.start.z, e.dxf.end.z]
elif t in ("TEXT", "MTEXT"):
s = e.dxf.text if t == "TEXT" else e.plain_text()
if s and len(texts) < 40:
texts.append(s.strip()[:80])
elif t in ("POINT", "INSERT", "CIRCLE", "ARC"):
z_vals.append(e.dxf.location.z if t == "POINT" else e.dxf.insert.z if t == "INSERT" else e.dxf.center.z)
rec["z_min"] = round(min(z_vals), 3) if z_vals else None
rec["z_max"] = round(max(z_vals), 3) if z_vals else None
rec["has_3d_geometry"] = bool(z_vals) and (max(z_vals) - min(z_vals)) > 1e-6
try:
ext = bbox.extents(msp)
rec["extents_min"] = [round(v, 2) for v in ext.extmin]
rec["extents_max"] = [round(v, 2) for v in ext.extmax]
except Exception as exc:
rec["extents_error"] = str(exc)
rec["text_sample"] = texts
rec["ok"] = True
return rec
def main():
paths = []
for sub in SUBDIRS:
paths += sorted(glob.glob(os.path.join(RAG_DIR, sub, "*.dxf")))
results = [inventory(p) for p in paths]
os.makedirs(os.path.dirname(OUT_JSON), exist_ok=True)
with open(OUT_JSON, "w", encoding="utf-8") as fh:
json.dump(results, fh, ensure_ascii=False, indent=2)
ok = sum(r["ok"] for r in results)
print(f"files={len(results)} ok={ok} failed={len(results) - ok}")
for r in results:
status = "OK " if r["ok"] else "ERR"
detail = r.get("error") or f"ents={sum(r['entity_counts'].values())} 3D={r['has_3d_geometry']} units={r['insunits']} cp={r['codepage']} recovered={r['recovered']}"
print(f"{status} {r['file'][:60]:60s} {detail}")
if __name__ == "__main__":
main()