Files
AIC-Project/Tools/ModelTranslator/bl_decimate.py
T

121 lines
3.9 KiB
Python
Raw 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.
"""Blender 内运行:FBX 减面到指定三角面数 + Smart UV 重展。
调用:blender -b --factory-startup --python bl_decimate.py -- <src.fbx> <out.fbx> <target_tris>
"""
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
TOLERANCE = 0.03 # 面数相对误差容忍
MAX_RETRY = 2 # ratio 修正轮数上限
def decimate_ratio(target, cur):
"""Decimate collapse ratiocur <= target 或非法时返回 1.0(不减面)。"""
if cur <= 0 or cur <= target:
return 1.0
return target / float(cur)
def within_tolerance(target, actual, tol=TOLERANCE):
"""减面结果是否落在目标 ±tol 内。"""
if target <= 0:
return False
return abs(actual - target) <= target * tol
# ---------------- 以下仅在 Blender 内执行 ----------------
def join_meshes(objs):
"""多 mesh join 成单对象,返回结果对象(bl_bake 复用)。"""
import bpy
bpy.ops.object.select_all(action='DESELECT')
for o in objs:
o.select_set(True)
bpy.context.view_layer.objects.active = objs[0]
if len(objs) > 1:
bpy.ops.object.join()
return bpy.context.view_layer.objects.active
def _apply_modifier(obj, mod):
import bpy
bpy.context.view_layer.objects.active = obj
bpy.ops.object.modifier_apply(modifier=mod.name)
def _triangulate(obj):
mod = obj.modifiers.new("mt_tri", 'TRIANGULATE')
_apply_modifier(obj, mod)
def _decimate(obj, ratio):
mod = obj.modifiers.new("mt_dec", 'DECIMATE')
mod.decimate_type = 'COLLAPSE'
mod.ratio = ratio
mod.use_collapse_triangulate = True
_apply_modifier(obj, mod)
def _smart_unwrap(obj):
"""删旧 UV 层后 Smart UV Project 重展。island_margin 0.002 ≈ 2048 图 4px。"""
import bpy
import math
mesh = obj.data
while mesh.uv_layers:
mesh.uv_layers.remove(mesh.uv_layers[0])
bpy.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.uv.smart_project(angle_limit=math.radians(66.0), island_margin=0.002)
bpy.ops.object.mode_set(mode='OBJECT')
def main():
import bpy
import json
argv = sys.argv[sys.argv.index("--") + 1:]
src, out_fbx, target = argv[0], argv[1], int(argv[2])
warnings = []
bpy.ops.wm.read_factory_settings(use_empty=True)
bpy.ops.import_scene.fbx(filepath=src)
meshes = [o for o in bpy.data.objects if o.type == 'MESH']
skipped = [o.name for o in bpy.data.objects if o.type != 'MESH']
if skipped:
warnings.append("忽略非 mesh 对象:%s(本工具只处理静态网格)" % ", ".join(skipped))
if not meshes:
print("MT_SUMMARY " + json.dumps({"error": "FBX 中没有 mesh"}, ensure_ascii=False))
return
obj = join_meshes(meshes)
_triangulate(obj)
orig = len(obj.data.polygons)
cur = orig
if cur <= target:
warnings.append("当前 %d 面 <= 目标 %d,跳过减面" % (cur, target))
else:
# collapse 结果是近似值;未达容差时按新面数修正 ratio 再来,最多 MAX_RETRY 轮
for _ in range(1 + MAX_RETRY):
_decimate(obj, decimate_ratio(target, cur))
_triangulate(obj) # collapse triangulate 后仍可能残留非三角面
cur = len(obj.data.polygons)
if within_tolerance(target, cur) or cur <= target:
break
_smart_unwrap(obj)
out_dir = os.path.dirname(os.path.abspath(out_fbx))
os.makedirs(out_dir, exist_ok=True)
bpy.ops.export_scene.fbx(filepath=out_fbx, path_mode='STRIP', embed_textures=False)
print("MT_SUMMARY " + json.dumps(
{"src": os.path.basename(src), "fbx": os.path.basename(out_fbx),
"tris_before": orig, "tris_after": cur, "target": target,
"warnings": warnings}, ensure_ascii=False))
if __name__ == "__main__" and "--" in sys.argv:
main()