Metadata-Version: 2.4
Name: atlisp-pdf2dwg
Version: 0.1.0
Summary: Convert PDF drawings and documents to DXF (optionally DWG) using local OCR, native PDF vectors and raster image vectorization.
Author: vitalgg
License-Expression: MIT
Project-URL: Homepage, https://github.com/atlisp/pdf2dwg
Project-URL: Repository, https://github.com/atlisp/pdf2dwg
Project-URL: Issues, https://github.com/atlisp/pdf2dwg/issues
Keywords: pdf,dxf,dwg,cad,ocr,vectorize,ezdxf
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Manufacturing
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
Classifier: Topic :: Scientific/Engineering
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pymupdf>=1.24
Requires-Dist: ezdxf>=1.3
Requires-Dist: numpy>=1.24
Requires-Dist: Pillow>=9.0
Requires-Dist: opencv-python-headless>=4.8
Provides-Extra: rapid
Requires-Dist: rapidocr>=3.0; extra == "rapid"
Requires-Dist: onnxruntime>=1.17; extra == "rapid"
Provides-Extra: surya
Requires-Dist: surya-ocr>=0.6; extra == "surya"
Provides-Extra: potrace
Requires-Dist: potracer>=0.0.4; extra == "potrace"
Provides-Extra: all
Requires-Dist: pdf2dwg[potrace,rapid]; extra == "all"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: build>=1.0; extra == "dev"
Requires-Dist: twine>=4; extra == "dev"
Requires-Dist: ruff>=0.5; extra == "dev"
Dynamic: license-file

# atlisp-pdf2dwg

把 PDF 图纸/文档转换为可编辑的 **DXF**（可选 **DWG**）。

流程：**OCR 图文混排 → 原生 PDF 矢量提取 + 位图矢量化 → ezdxf 组装 DXF**。

- **OCR 图文混排**：默认为本地 RapidOCR（PP-OCR，逐行坐标、CPU、快）；可选 surya VLM
  （整页布局 + 文本，精度高但重）。
- **原生矢量**：若 PDF 仍是矢量（含被转曲的文字），直接提取线条，比追踪位图更清晰、更小；
  文字块内的字形轮廓会被剔除并替换为真实文字实体。
- **位图矢量化**：PDF 内嵌图片先做中值分割颜色量化，再用 **potrace** 拟合平滑贝塞尔曲线
  （回退到 OpenCV 轮廓追踪），随后采样+简化多边形，按原始变换/旋转映射回图纸并裁剪到页面。
  相同图片（页眉 logo、二维码等）跨页只追踪一次，并可多进程并行，质量与速度兼顾。
- **默认输出线条（LWPOLYLINE），不用 HATCH**。这是实测决定的：同一张图经 LibreDWG 转 DWG，
  HATCH 版报 249 条 `Skip HATCH common handles` 告警（填充大面积丢失），线条版只有 1 条。
  代价是墨迹召回 84.2% → 68.3%（平铺多段线依赖绘制顺序，HATCH 则是逐环奇偶填充）。
  若 DXF 是最终交付、且更在意洞的正确性，可加 `--vector-mode rings` 改用 HATCH。
- **页面堆叠**：多页 PDF 默认纵向堆叠到模型空间，页间留间距；可用 `--flat` 关闭。

## 安装

```bash
pip install atlisp-pdf2dwg     # 核心（矢量 + OpenCV 位图，不含 OCR）
pip install "atlisp-pdf2dwg[potrace]"  # + potrace 曲线追踪（推荐，图片更精细）
pip install "atlisp-pdf2dwg[rapid]"    # + RapidOCR（推荐的本地 OCR）
pip install "atlisp-pdf2dwg[all]"      # rapid + potrace
pip install "atlisp-pdf2dwg[surya]"    # + surya VLM（可选，重）
```

需要 DWG 输出时另装 [GNU LibreDWG](https://www.gnu.org/software/libredwg/) 的 `dxf2dwg`，
或设置环境变量 `PDF2DWG_DXF2DWG=/path/to/dxf2dwg`。

## 命令行

```bash
# 默认：RapidOCR + 矢量 + 位图，输出 DXF
pdf2dwg convert 图纸.pdf -o 图纸.dxf

# 指定页码、DPI、关闭位图矢量化
pdf2dwg convert 图纸.pdf --pages 1-5,8 --dpi 300 --no-images

# 只提取矢量+位图（不 OCR，文字保留为轮廓）
pdf2dwg convert 图纸.pdf --no-ocr

# 输出 DWG（需 dxf2dwg）
pdf2dwg convert 图纸.pdf --format dwg

# 复用/缓存 OCR 结果
pdf2dwg convert 图纸.pdf --ocr-cache .ocr_cache

# 图片追踪质量/性能
pdf2dwg convert 图纸.pdf --trace potrace --image-max-dim 1600 --image-colors 8 --jobs 8
pdf2dwg convert 图纸.pdf --trace opencv        # 不装 potrace 时的回退
pdf2dwg convert 图纸.pdf --image-min-area 8 --image-max-polys 20000   # 压体积

# 其它子命令
pdf2dwg ocr 图纸.pdf -o ocr_json --engine rapid
pdf2dwg render 图纸.dxf -o preview.png --scale 1.5
pdf2dwg to-dwg 图纸.dxf
pdf2dwg info                 # 查看引擎与 dxf2dwg 路径
```

## Python API

```python
from pdf2dwg import convert_pdf

convert_pdf(
    "图纸.pdf",
    "图纸.dxf",
    page_spec="1-10",      # 1-based；或 pages=[0,1,2]
    dpi=200,
    ocr="rapid",           # 或 "surya" / None
    images=True,           # 位图矢量化
    vectors=True,          # 原生 PDF 矢量
    stack=True,            # 多页纵向堆叠
    ocr_cache=".ocr_cache",
)
```

分步使用：

```python
import pymupdf
from pdf2dwg import get_engine, build_dxf, render_page

doc = pymupdf.open("图纸.pdf")
engine = get_engine("rapid")
pages_ocr = {i: engine.run_image(render_page(doc, i, 200), i + 1, 200)
             for i in range(doc.page_count)}
build_dxf("图纸.pdf", "图纸.dxf", pages_ocr=pages_ocr)
```

## 输出图层

| 图层 | 内容 |
|------|------|
| `TEXT` / `TEXT-TITLE` | OCR 文字（正文 / 标题），样式 `HZ`（默认 `simsun.ttc`） |
| `GRAPHICS` / `GRAPHICS-FILL` | PDF 原生矢量（描边 / 填充），真彩色 |
| `IMAGES` | 位图矢量化结果（真彩色） |
| `PAGE` | 页面边框与页码 |

## 说明

- 坐标单位与 PDF 一致（points，1 pt = 1/72 inch）。
- 输出为 **R2004** DXF：更早的版本无法写出真彩色（420 组码），颜色会全部丢失。
- 默认 `--vector-mode rings`（HATCH 带洞）；`legacy` 用平铺 LWPOLYLINE，洞靠绘制顺序
  用背景色多边形遮挡，重叠时可能出错。
- 中文文字样式默认字体 `simsun.ttc`，可在 `build_dxf(..., text_font=...)` 中修改；
  若 CAD 中字体缺失，替换为本地已安装的中文字体即可。
- 位图矢量化对线条图效果最佳；照片类图片会产生较多多边形，可用
  `--image-min-area` / `--image-max-polys` / `--no-images` 控制。
- **灰阶抗锯齿归并**（默认开启，`merge_neutral`）：~200 DPI 扫描件里的 0.25 pt 线只有一个
  抗锯齿像素，一条黑线会散成 (121,121,121)…(216,216,216) 一整条灰阶，每一级都变成独立的
  填充，最浅那级在白纸上基本看不见。现���把**严格中性灰**（max-min ≤ 12）归并到最深的一档，
  并合并标签消除重复环。只处理中性灰是有意为之：封面的米色 (200,197,185) 通道差为 15、
  蓝色 logo 根本不是中性色，都会被保留。实测墨迹召回 70.1% → 82.0%，实体数 −19.8%。
  用 `vectorize_image(..., merge_neutral=False)` 可关闭。
- 合并后的中性灰掩膜再做 **3×3 形态学闭运算**（`neutral_close`，默认 3）。合并会交错灰阶各级，掩膜边界呈阶梯状，产生大量冗余环。只对这**一个标签**做闭运算即可：9 页实测召回 82.0% → 84.2%，IoU 0.482 → 0.487，实体数 −9.4%，体积 −8.0%（第 5 页 2.18 → 1.59 MB）。注意闭运算会让笔画略微变粗，`rgb_mae` 会上升。
- `--image-max-dim`（默认 600）是体积与保真的主要开关。绝大多数内嵌图实际放置尺寸
  远小于其像素尺寸，600 已足够；只有当某页把扫描图**放大显示**时（如 2500px 的图放到
  700pt 宽）才需要调到 1000~1600，否则细线会丢。
- 质量度量工具。**`ink_iou` / `pct_bad_px` 是灰度的，对颜色完全失明**——把整张图压成
  单一墨色也能拿高 IoU。所以务必同时看 `ink_colour_acc` 与 `rgb_mae`：

  ```bash
  python tools/compare_modes.py 图纸.pdf --pages 1-5 --dpi 100   # legacy vs rings
  python tools/auto_tune.py    图纸.pdf --pages 1-61              # 逐文档评估追踪参数
  ```

- `--auto-tune N`（实验性）会抽样 N 页评估追踪参数并采纳胜出者。它只在超过保守阈值时
  才改变配置；在参考图纸上它的建议在**未抽样页上反而更差**（颜色 −1.2 点、实体 +4.6%，
  仅换 +0.005 IoU），因此默认值被保留。建议先用 `tools/auto_tune.py` 诊断再决定。

## 开发

```bash
pip install -e ".[dev,rapid]"
pytest
ruff check src tests tools    # 硬闸门：只查真 bug
python -m build
```

## 许可

MIT
