7.5 KiB
ModelTranslator 减面可选后端 Quad Remesher 设计
日期:2026-07-22
背景与动机
现有减面管线 model_decimate.py → bl_decimate.py 用 Blender 原生
DECIMATE(COLLAPSE) modifier 按 --tris 目标三角面数减面。用户不满的核心痛点:
- 拓扑乱 / 碎三角:COLLAPSE 产出狭长不规则三角,影响着色、变形、法线。
- 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 内执行,步骤:
find_qr_engine();None → warning,return False。- 建工作目录(
tempfile.gettempdir()/mt_qr/),清旧retopo.fbx/progress.txt。 - 选中
obj,bpy.ops.export_scene.fbx(use_selection=True)导出inputMesh.fbx。 build_settings(...)写RetopoSettings.txt(hard_edges=True,exact=False)。subprocess.Popen([engine, "-s", settings]),阻塞轮询progress.txt(间隔 0.3s, 超时默认 120s):parse_progress→success且retopo.fbx非空 → 进入 6;error/ 超时 / 进程退出但无输出 → warning,return False。
- 记录旧对象集合 →
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 拓扑 + 法线修复不冲突)。