ModelTranslator: quad backend precollapse + fill-holes + keep-quads

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ud18010
2026-07-23 15:00:46 +08:00
co-authored by Claude Opus 4.8
parent bb6f32b1c1
commit 5fda5d6bef
+30 -5
View File
@@ -1,6 +1,6 @@
"""Blender 内运行:FBX 减面到指定三角面数 + UV 重展(锐边 seam 或 Smart UV Project)。 """Blender 内运行:FBX 减面到指定三角面数 + UV 重展(锐边 seam 或 Smart UV Project)。
调用:blender -b --factory-startup --python bl_decimate.py -- 调用:blender -b --factory-startup --python bl_decimate.py --
<src.fbx> <out.fbx> <target_tris> [unwrap:seam|smart] [overlap_tol] [reducer:collapse|quad] <src.fbx> <out.fbx> <target_tris> [unwrap:seam|smart] [overlap_tol] [reducer:collapse|quad] [qr_input_cap]
""" """
import os import os
import sys import sys
@@ -129,6 +129,21 @@ def _edge_defects(obj):
return boundary, nonman return boundary, nonman
def _fill_small_holes(obj, max_sides, warnings):
"""补掉 <=max_sides 边的小洞(QR 薄部位零星孔洞);异常降级不中断。"""
import bpy
try:
bpy.ops.object.select_all(action='DESELECT')
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.fill_holes(sides=max_sides)
bpy.ops.object.mode_set(mode='OBJECT')
except RuntimeError as e:
warnings.append("fill_holes 失败(%s),跳过补洞" % e)
def _remesh_quad(obj, target_tris, warnings): def _remesh_quad(obj, target_tris, warnings):
"""Quad Remesher 重拓扑 objheadless 直调 xremesh.exe,绕开 modal 操作符)。 """Quad Remesher 重拓扑 objheadless 直调 xremesh.exe,绕开 modal 操作符)。
成功返回 True,并把导回的 quad 网格设为新 active object(原 obj 已删); 成功返回 True,并把导回的 quad 网格设为新 active object(原 obj 已删);
@@ -210,11 +225,14 @@ def _remesh_quad(obj, target_tris, warnings):
warnings.append("QR 导回无 mesh,回退 collapse") warnings.append("QR 导回无 mesh,回退 collapse")
return False return False
retopo = new[0] retopo = new[0]
# 水密门QR 引擎非确定性地会打出孔洞/非流形,破损即弃用 QR 回退 collapse # 补小洞QR 常在薄部位留零星小洞,先补掉再判软门
_fill_small_holes(retopo, qr_bridge.QR_HOLE_MAX_SIDES, warnings)
# 软水密门:补洞后仍比输入多出超容忍的破损才判灾难性,弃用 QR 回退 collapse
qr_boundary, qr_nonman = _edge_defects(retopo) qr_boundary, qr_nonman = _edge_defects(retopo)
if not qr_bridge.topology_acceptable(src_boundary, src_nonman, if not qr_bridge.topology_acceptable(src_boundary, src_nonman,
qr_boundary, qr_nonman): qr_boundary, qr_nonman,
warnings.append("QR 输出拓扑破损(开边 %d%d,非流形 %d%d),回退 collapse" tol=qr_bridge.QR_CATASTROPHIC_TOL):
warnings.append("QR 输出灾难性破损(开边 %d%d,非流形 %d%d),回退 collapse"
% (src_boundary, qr_boundary, src_nonman, qr_nonman)) % (src_boundary, qr_boundary, src_nonman, qr_nonman))
bpy.data.objects.remove(retopo, do_unlink=True) # 弃坏网格,原 obj 完好 bpy.data.objects.remove(retopo, do_unlink=True) # 弃坏网格,原 obj 完好
# FBX 导入把选中挪给了 retopo;恢复 obj 选中+active,供 collapse 与最终导出 # FBX 导入把选中挪给了 retopo;恢复 obj 选中+active,供 collapse 与最终导出
@@ -515,6 +533,7 @@ def main():
# 直接调 bl_decimate 缺省用 collapse(安全确定性)model_decimate CLI 默认 quad # 直接调 bl_decimate 缺省用 collapse(安全确定性)model_decimate CLI 默认 quad
# 但总显式传第 6 位,故此兜底仅用于直接 Blender 调用,两者默认不同是有意为之 # 但总显式传第 6 位,故此兜底仅用于直接 Blender 调用,两者默认不同是有意为之
reducer_arg = argv[5] if len(argv) > 5 else "collapse" reducer_arg = argv[5] if len(argv) > 5 else "collapse"
qr_input_cap = int(argv[6]) if len(argv) > 6 else qr_bridge.QR_INPUT_CAP
warnings = [] warnings = []
bpy.ops.wm.read_factory_settings(use_empty=True) bpy.ops.wm.read_factory_settings(use_empty=True)
@@ -536,9 +555,15 @@ def main():
used_reducer = "collapse" used_reducer = "collapse"
did_quad = False did_quad = False
if reducer_arg == "quad": if reducer_arg == "quad":
# 预 collapse 到中等密度:给 QR 一个不炸洞的输入(高密度直接 QR 必破)
pre = qr_bridge.precollapse_target(len(obj.data.polygons), qr_input_cap)
if pre is not None:
_decimate(obj, decimate_ratio(pre, len(obj.data.polygons)))
_triangulate(obj)
cur = len(obj.data.polygons) # 预 collapse 后更新,QR 失败时 collapse 从此续减
if _remesh_quad(obj, target, warnings): if _remesh_quad(obj, target, warnings):
obj = bpy.context.view_layer.objects.active # QR 导回的 quad 对象 obj = bpy.context.view_layer.objects.active # QR 导回的 quad 对象
_triangulate(obj) # 三角化供导出/计数 # 保四边:不三角化,直接导出四边网格(FBX/烘焙/Unity 均支持四边)
cur = len(obj.data.polygons) cur = len(obj.data.polygons)
used_reducer = "quad" used_reducer = "quad"
did_quad = True did_quad = True