Files
AIC-Project/docs/superpowers/specs/2026-07-22-quadremesher-decimate-backend-design.md
2026-07-22 17:29:36 +08:00

138 lines
7.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 拓扑 + 法线修复不冲突)。