ModelTranslator: document QR quad remesh backend

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ud18010
2026-07-22 17:29:36 +08:00
co-authored by Claude Opus 4.8
parent b8eb40f3e4
commit ac99d104c0
3 changed files with 768 additions and 0 deletions
@@ -0,0 +1,137 @@
# ModelTranslator 减面可选后端 Quad Remesher 设计
日期:2026-07-22
## 背景与动机
现有减面管线 `model_decimate.py``bl_decimate.py` 用 Blender 原生
**DECIMATE(COLLAPSE)** modifier 按 `--tris` 目标三角面数减面。用户不满的核心痛点:
1. **拓扑乱 / 碎三角**:COLLAPSE 产出狭长不规则三角,影响着色、变形、法线。
2. **UV 展开难**:碎三角导致 seam/UV 岛碎、利用率低。
用户已安装 **Quad Remesher(Exoside,重打包版)** Blender 插件,可产出规整四边拓扑,
从根子上改善以上两点。本设计将其作为**可选减面后端**接入,默认不改变现有行为。
## 关键技术结论(已 spike 验证)
`bpy.ops.qremesher.remesh()`**modal 操作符**`modal_handler_add` + timer,返回
`RUNNING_MODAL`)。`blender -b` 后台无事件循环,modal 永不触发,结果 FBX 永不导回——
**直接调操作符在 headless 必然失败**(与 UVPM headless 崩的教训同类)。
真正的重活是**解耦的外部引擎进程**:插件只是"桥",做导出 FBX → 写 settings →
`Engine/xremesh.exe -s settings.txt` 子进程 → 轮询 `progress.txt` → 导回 retopo。
本设计**自己重实现这 ~40 行桥胶水**,完全确定、后台安全、不依赖 modal、不受
`--factory-startup` 影响、不需 enable 插件。
**Spike 实测**icosphere 5120 tris → `TargetQuadCount=500` → 0.7s 出 441 面全 quad
exit 0、progress=2;重打包版无许可问题能直接跑。
## 约束
- 不加外部依赖(纯标准库 + bpy)。
- 不覆盖输入 FBX;只在生成的 `_low.fbx` 输出重拓扑数据。
- 现有 CLI/输出目录/命名/`collapse` 路径行为**零变化**`--reducer` 默认 `collapse`)。
- QR 失败(引擎缺失/超时/负值/导回空)一律记 warning 并**回退 collapse,绝不中断**——
与现有 UVPM"失败即回退"同哲学。
- 面数口径:`target_quads = tris // 2`,接受 QR 近似输出(约 ±20%),不做二次 collapse
(二次 collapse 会重新引入碎三角,抵消 QR 好处)。**用 `ExactQuadCount=1` 尊重目标数**
——真机验证发现自适应模式(`ExactQuadCount=0`)在高细节硬表面模型上面数暴涨数倍
well1500 目标 2500→11500 quads)并连带 seam 展开重叠爆炸,故禁用自适应。
- `QR_ENGINE` 覆盖为**权威**语义:显式设了就以它为准,无效路径即视同缺失走 collapse 回退
(不再静默自动发现,保证回退契约可测)。
## 架构
分三层,纯逻辑与 Blender 操作分离,便于 TDD
### 1. 新增纯模块 `qr_bridge.py`(不 import bpy
| 函数 | 职责 |
|---|---|
| `find_qr_engine() -> str \| None` | 按标准 addon 路径查 `xremesh.exe`,找不到返回 None |
| `tris_to_target_quads(tris) -> int` | `max(1, tris // 2)`1 quad ≈ 2 tris |
| `build_settings(in_fbx, out_fbx, prog, target_quads, *, adaptive=50, exact=False, hard_edges=True) -> str` | 生成 `RetopoSettings.txt` 文本(HostApp/FileIn/FileOut/ProgressFile/TargetQuadCount/CurvatureAdaptivness/ExactQuadCount/AutoDetectHardEdges 等) |
| `parse_progress(text) -> (state, msg)` | `state ∈ {"running","success","error"}`;首行 `2`=success`<0`=error(次行为文本),空/其它=running |
`find_qr_engine` 搜索顺序:`QR_ENGINE` 环境变量 → `%APPDATA%/Blender Foundation/Blender/*/scripts/addons/QuadRemesher/Engine/xremesh.exe`
### 2. `bl_decimate.py` 新增 `_remesh_quad(obj, target_tris, warnings) -> bool`
Blender 内执行,步骤:
1. `find_qr_engine()`None → warningreturn False。
2. 建工作目录(`tempfile.gettempdir()/mt_qr/`),清旧 `retopo.fbx`/`progress.txt`
3. 选中 `obj``bpy.ops.export_scene.fbx(use_selection=True)` 导出 `inputMesh.fbx`
4. `build_settings(...)``RetopoSettings.txt``hard_edges=True``exact=False`)。
5. `subprocess.Popen([engine, "-s", settings])`,阻塞轮询 `progress.txt`(间隔 0.3s
超时默认 **120s**):
- `parse_progress``success``retopo.fbx` 非空 → 进入 6
- `error` / 超时 / 进程退出但无输出 → warningreturn False。
6. 记录旧对象集合 → `bpy.ops.import_scene.fbx(retopo.fbx)` → 删除原 `obj`,把导回的
mesh 设为新 active object(供后续展 UV/导出复用)。return True。
**任何异常都被捕获 → warning → return False**,不抛出。
### 3. `main()` 分流
- 解析第 5 个 argv `reducer``"collapse"`(默认)/`"quad"`)。
- 流程:
- `_clean_mesh(obj)`(现有)
- `reducer == "quad"``_remesh_quad()` 成功 → **跳过 DECIMATE**,记 `reducer="quad"`
否则(或 QR 失败回退)走现有 `_triangulate` + collapse 循环,记 `reducer="collapse"`
`"quad->collapse"`
- 之后照旧:`_do_unwrap`(在更干净的拓扑上展 UV)→ 三角化保证导出为三角 → 导出。
- `MT_SUMMARY``reducer` 字段。
> 注:QR 输出为 quad,展 UV 在 quad 上进行(seam 更干净);导出前需确保三角化
> (复用现有 `_triangulate`),使 `_low.fbx` 仍为三角网格,兼容下游烘焙。
### 4. `model_decimate.py` CLI
-`--reducer {collapse,quad}``default="collapse"`,透传给 Blender argv 第 5 位。
- 打印实际后端与面数:`后端=quad 面数 20000 -> 882`(或 `quad->collapse` 回退提示)。
## 数据流
```
FBX --import--> join --clean--> [reducer 分流]
quad: export inputMesh.fbx --xremesh.exe--> retopo.fbx --import--> 全 quad obj
collapse: DECIMATE(ratio) 循环
--> _do_unwrap(seam+UVPM) --triangulate--> export _low.fbx + MT_SUMMARY{reducer,...}
```
## 错误处理
| 情况 | 处理 |
|---|---|
| 找不到 xremesh.exe | warning,回退 collapse |
| 引擎超时(>120s | kill 进程,warning,回退 collapse |
| progress 负值(含无许可/EULA 的 -2) | warning(带引擎文本),回退 collapse |
| 进程退出但 retopo.fbx 空/缺 | warning,回退 collapse |
| 导回 FBX 无 mesh | warning,回退 collapse |
回退后 `reducer` 字段标 `quad->collapse`,用户可从摘要看出实际走了哪条路。
## 测试
- **`tests/test_qr_bridge.py`**(纯函数,TDD):
- `tris_to_target_quads`:偶/奇/极小值(`1→1`, `10000→5000`, `1→1`)。
- `build_settings`:含关键行、路径转义、`exact`/`hard_edges` 开关映射正确。
- `parse_progress``"2"`→success、`"-3\n失败"`→error 带文本、`""`/`"0.5"`→running。
- `find_qr_engine`:环境变量优先、缺失返回 None(用临时目录构造)。
- **回归**`python -m unittest discover -s tests` 全绿(现有测试不受影响)。
- **真机 smoke**
- `python model_decimate.py src/well1500.fbx --tris 5000 --reducer quad -o out_qr`
→ 输出存在、`reducer=quad`、面数近似 5000(±20%)、UV 岛数/翻转/重叠 打印。
-`--reducer collapse`(默认)对比 UV 指标,确认岛数下降/更规整。
- 强制回退验证:临时改 `QR_ENGINE` 指向不存在路径 → `reducer=quad->collapse`
仍正常出 `_low.fbx`
## 明确不做(YAGNI
- 不改烘焙 `bl_bake.py`QR 只作用于减面产出的低模)。
- 不做二次 collapse 卡精确面数。
- 不暴露 QR 全部参数(对称/顶点色/材质引导);只用 target/adaptive/hard_edges 合理默认。
- 不动进行中的 `2026-07-16-model-bake-quality-optimization` 计划;本功能与其正交,
可后续叠加(quad 拓扑 + 法线修复不冲突)。