Files

110 lines
4.4 KiB
Python
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 CLIFBX -> 纯模型 FBX + XPbr 贴图 + Unity .mat/.meta。
用法:python model_translator.py src/tong.fbx [-o out] [--max-size 2048] [--blender exe]"""
import argparse
import json
import os
import shutil
import subprocess
import sys
import unity_assets as ua
DEFAULT_BLENDER = r"D:\tools\blender-5.0.0-windows-x64\blender.exe"
HERE = os.path.dirname(os.path.abspath(__file__))
def find_blender(cli_arg):
for cand in (cli_arg, os.environ.get("BLENDER_EXE"), DEFAULT_BLENDER,
shutil.which("blender")):
if cand and os.path.isfile(cand):
return cand
sys.exit("找不到 blender.exe,请用 --blender 指定或设置环境变量 BLENDER_EXE")
def run_blender(blender, src, outdir, max_size):
cmd = [blender, "-b", "--factory-startup",
"--python", os.path.join(HERE, "bl_convert.py"),
"--", src, outdir, str(max_size)]
proc = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8",
errors="replace")
summary = None
for line in (proc.stdout or "").splitlines():
if line.startswith("MT_SUMMARY "):
summary = json.loads(line[len("MT_SUMMARY "):])
if proc.returncode != 0 or summary is None:
sys.stderr.write(proc.stdout[-2000:] + "\n" + proc.stderr[-2000:] + "\n")
sys.exit("Blender 转换失败:%s" % src)
return summary
def write_unity_assets(outdir, summary, max_size):
remaps = []
for m in summary["materials"]:
base_png = os.path.join(outdir, m["base_png"])
mix_png = os.path.join(outdir, m["mix_png"])
base_guid = ua.guid_for(base_png)
mix_guid = ua.guid_for(mix_png)
alpha_test = m["alpha_mode"] == "transparency"
with open(base_png + ".meta", "w", encoding="utf-8", newline="\n") as f:
f.write(ua.texture_meta(base_guid, srgb=True, max_size=max_size,
alpha_is_transparency=alpha_test))
with open(mix_png + ".meta", "w", encoding="utf-8", newline="\n") as f:
f.write(ua.texture_meta(mix_guid, srgb=False, max_size=max_size))
# 与 bl_convert 的贴图命名一致:材质名等于模型名时不重复拼接
mat_name = summary["model"] if m["safe_name"] == summary["model"] \
else "%s_%s" % (summary["model"], m["safe_name"])
mat_path = os.path.join(outdir, mat_name + ".mat")
mat_guid = ua.guid_for(mat_path)
with open(mat_path, "w", encoding="utf-8", newline="\n") as f:
f.write(ua.material_yaml(mat_name, ua.XPBR_SHADER_GUID,
base_guid, mix_guid, alpha_test))
with open(mat_path + ".meta", "w", encoding="utf-8", newline="\n") as f:
f.write(ua.material_meta(mat_guid))
remaps.append((m["name"], mat_guid))
fbx_path = os.path.join(outdir, summary["fbx"])
fbx_guid = ua.guid_for(fbx_path) # 必须在 open("w") 截断文件之前读取,否则复用失效
with open(fbx_path + ".meta", "w", encoding="utf-8", newline="\n") as f:
f.write(ua.model_meta(fbx_guid, remaps))
def report(summary, outdir):
print("== %s -> %s" % (summary["model"], outdir))
for m in summary["materials"]:
print(" 材质 %-24s A通道=%s 尺寸=%dx%d" %
(m["name"], m["alpha_mode"], m["size"][0], m["size"][1]))
for k, v in m["sources"].items():
print(" %-10s %s" % (k, v or "(常量)"))
for w in m["warnings"]:
print(" [警告] %s" % w)
def main():
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("input", help="FBX 文件或目录")
ap.add_argument("-o", "--out", default=os.path.join(HERE, "out"))
ap.add_argument("--max-size", type=int, default=2048)
ap.add_argument("--blender", default=None)
args = ap.parse_args()
blender = find_blender(args.blender)
if os.path.isdir(args.input):
srcs = [os.path.join(args.input, f) for f in sorted(os.listdir(args.input))
if f.lower().endswith(".fbx")]
if not srcs:
sys.exit("目录中没有 FBX%s" % args.input)
else:
srcs = [args.input]
for src in srcs:
name = os.path.splitext(os.path.basename(src))[0]
outdir = os.path.join(args.out, name)
summary = run_blender(blender, src, outdir, args.max_size)
write_unity_assets(outdir, summary, args.max_size)
report(summary, outdir)
if __name__ == "__main__":
main()