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

7.5 KiB
Raw Blame History

ModelTranslator 减面可选后端 Quad Remesher 设计

日期:2026-07-22

背景与动机

现有减面管线 model_decimate.pybl_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. 选中 objbpy.ops.export_scene.fbx(use_selection=True) 导出 inputMesh.fbx
  4. build_settings(...)RetopoSettings.txthard_edges=Trueexact=False)。
  5. subprocess.Popen([engine, "-s", settings]),阻塞轮询 progress.txt(间隔 0.3s 超时默认 120s):
    • parse_progresssuccessretopo.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_SUMMARYreducer 字段。

注: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.pyQR 只作用于减面产出的低模)。
  • 不做二次 collapse 卡精确面数。
  • 不暴露 QR 全部参数(对称/顶点色/材质引导);只用 target/adaptive/hard_edges 合理默认。
  • 不动进行中的 2026-07-16-model-bake-quality-optimization 计划;本功能与其正交, 可后续叠加(quad 拓扑 + 法线修复不冲突)。