ModelTranslator: 烘焙固定 -t 1——Blender 5.0 多线程 selected-to-active 射线求交竞争崩溃(store 复现+单线程验证)
This commit is contained in:
@@ -46,7 +46,7 @@ python model_translator.py out_bake/well1500/well1500.fbx # -> out/well1500/
|
|||||||
```
|
```
|
||||||
|
|
||||||
- **model_decimate.py**:多 mesh 自动 join;三角化后 Decimate(collapse) 减到 `--tris`(±3%,最多 2 轮修正);旧 UV 全删后重展——默认锐边标 seam + MINIMUM_STRETCH 整岛展开(碎岛少、接缝落在硬边),质量不达标(翻转 >2% 或重叠 >8%)自动回退 Smart UV Project,`--unwrap smart` 可强制旧行为;输出 `<名>_low.fbx`(`-o` 改目录),日志报 UV 岛数/利用率
|
- **model_decimate.py**:多 mesh 自动 join;三角化后 Decimate(collapse) 减到 `--tris`(±3%,最多 2 轮修正);旧 UV 全删后重展——默认锐边标 seam + MINIMUM_STRETCH 整岛展开(碎岛少、接缝落在硬边),质量不达标(翻转 >2% 或重叠 >8%)自动回退 Smart UV Project,`--unwrap smart` 可强制旧行为;输出 `<名>_low.fbx`(`-o` 改目录),日志报 UV 岛数/利用率
|
||||||
- **model_bake.py**:Cycles selected-to-active 烘焙。normal(切线空间 OpenGL +Y)/ao 直接烘;color/metallic/roughness 把高模 Principled 对应输入接 Emission 烘 EMIT。`--size` 默认 2048,`--samples` AO 采样默认 64,射线距离默认低模包围盒对角线 2%(`--ray-distance` 覆盖);低模无 UV 会报错,高低模包围盒差超 10% 打警告;导入后自动应用物体变换——未应用缩放的 FBX(如 cm 单位导出的 scale=0.01)会把射线距离缩到近零导致大面积烘空(脏色)
|
- **model_bake.py**:Cycles selected-to-active 烘焙。normal(切线空间 OpenGL +Y)/ao 直接烘;color/metallic/roughness 把高模 Principled 对应输入接 Emission 烘 EMIT。`--size` 默认 2048,`--samples` AO 采样默认 64,射线距离默认低模包围盒对角线 2%(`--ray-distance` 覆盖);低模无 UV 会报错,高低模包围盒差超 10% 打警告;导入后自动应用物体变换——未应用缩放的 FBX(如 cm 单位导出的 scale=0.01)会把射线距离缩到近零导致大面积烘空(脏色);固定 `-t 1` 单线程跑 Blender——5.0 的 selected-to-active 射线求交多线程有竞争,会随机 EXCEPTION_ACCESS_VIOLATION 崩溃
|
||||||
- 输出命名 `<名>_color/normal/metallic/roughness/ao.png`,正好命中转换器的纯网格贴图命名约定
|
- 输出命名 `<名>_color/normal/metallic/roughness/ao.png`,正好命中转换器的纯网格贴图命名约定
|
||||||
|
|
||||||
## 转换规则
|
## 转换规则
|
||||||
|
|||||||
@@ -25,9 +25,12 @@ def main():
|
|||||||
stem = output_stem(os.path.splitext(os.path.basename(args.low))[0])
|
stem = output_stem(os.path.splitext(os.path.basename(args.low))[0])
|
||||||
outdir = os.path.join(args.out, stem)
|
outdir = os.path.join(args.out, stem)
|
||||||
ray = "auto" if args.ray_distance is None else str(args.ray_distance)
|
ray = "auto" if args.ray_distance is None else str(args.ray_distance)
|
||||||
|
# -t 1:Blender 5.0 的 selected-to-active 烘焙射线求交是多线程的(cast_ray_highpoly
|
||||||
|
# / TBB parallel_for),存在竞争会随机 EXCEPTION_ACCESS_VIOLATION 崩溃;单线程稳定
|
||||||
s = run_blender_script(blender, "bl_bake.py",
|
s = run_blender_script(blender, "bl_bake.py",
|
||||||
[args.high, args.low, outdir, str(args.size),
|
[args.high, args.low, outdir, str(args.size),
|
||||||
ray, str(args.samples)])
|
ray, str(args.samples)],
|
||||||
|
blender_args=("-t", "1"))
|
||||||
print("== %s(高 %d 面 / 低 %d 面,射线距离 %.4f)-> %s" %
|
print("== %s(高 %d 面 / 低 %d 面,射线距离 %.4f)-> %s" %
|
||||||
(s["stem"], s["high_tris"], s["low_tris"], s["ray_distance"], outdir))
|
(s["stem"], s["high_tris"], s["low_tris"], s["ray_distance"], outdir))
|
||||||
for name, fname in s["outputs"].items():
|
for name, fname in s["outputs"].items():
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ def find_blender(cli_arg):
|
|||||||
sys.exit("找不到 blender.exe,请用 --blender 指定或设置环境变量 BLENDER_EXE")
|
sys.exit("找不到 blender.exe,请用 --blender 指定或设置环境变量 BLENDER_EXE")
|
||||||
|
|
||||||
|
|
||||||
def run_blender_script(blender, script, script_args):
|
def run_blender_script(blender, script, script_args, blender_args=()):
|
||||||
"""跑 HERE 下的 bl_*.py,返回 MT_SUMMARY JSON;失败或 summary 带 error 则退出。"""
|
"""跑 HERE 下的 bl_*.py,返回 MT_SUMMARY JSON;失败或 summary 带 error 则退出。"""
|
||||||
cmd = [blender, "-b", "--factory-startup",
|
cmd = [blender, "-b", *blender_args, "--factory-startup",
|
||||||
"--python", os.path.join(HERE, script), "--"] + list(script_args)
|
"--python", os.path.join(HERE, script), "--"] + list(script_args)
|
||||||
proc = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8",
|
proc = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8",
|
||||||
errors="replace")
|
errors="replace")
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
"""诊断:导入 FBX 检查几何是否损坏(NaN 顶点/退化面/validate 修复量)。
|
||||||
|
用法:blender -b --factory-startup --python tests/bl_diag_mesh.py -- <model.fbx> [apply]
|
||||||
|
"""
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
import bpy
|
||||||
|
from bl_decimate import join_meshes
|
||||||
|
argv = sys.argv[sys.argv.index("--") + 1:]
|
||||||
|
path = argv[0]
|
||||||
|
do_apply = len(argv) > 1 and argv[1] == "apply"
|
||||||
|
|
||||||
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||||
|
before = set(bpy.data.objects)
|
||||||
|
bpy.ops.import_scene.fbx(filepath=path)
|
||||||
|
new = [o for o in bpy.data.objects if o not in before]
|
||||||
|
meshes = [o for o in new if o.type == 'MESH']
|
||||||
|
print("DIAG objects=%d meshes=%d" % (len(new), len(meshes)))
|
||||||
|
for o in new:
|
||||||
|
print("DIAG obj=%r type=%s scale=%s" % (o.name, o.type, tuple(round(s, 4) for s in o.scale)))
|
||||||
|
obj = join_meshes(meshes)
|
||||||
|
if do_apply:
|
||||||
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
|
obj.select_set(True)
|
||||||
|
bpy.context.view_layer.objects.active = obj
|
||||||
|
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||||
|
print("DIAG transform applied")
|
||||||
|
|
||||||
|
me = obj.data
|
||||||
|
print("DIAG verts=%d polys=%d dims=%s" % (
|
||||||
|
len(me.vertices), len(me.polygons),
|
||||||
|
tuple(round(d, 4) for d in obj.dimensions)))
|
||||||
|
|
||||||
|
nan_verts = 0
|
||||||
|
huge_verts = 0
|
||||||
|
for v in me.vertices:
|
||||||
|
x, y, z = v.co
|
||||||
|
if any(math.isnan(c) or math.isinf(c) for c in (x, y, z)):
|
||||||
|
nan_verts += 1
|
||||||
|
elif max(abs(x), abs(y), abs(z)) > 1e6:
|
||||||
|
huge_verts += 1
|
||||||
|
print("DIAG nan_verts=%d huge_verts=%d" % (nan_verts, huge_verts))
|
||||||
|
|
||||||
|
zero_area = sum(1 for p in me.polygons if p.area < 1e-12)
|
||||||
|
print("DIAG zero_area_polys=%d" % zero_area)
|
||||||
|
|
||||||
|
# validate(verbose) 会打印它修的每个问题;返回 True 表示有改动
|
||||||
|
changed = me.validate(verbose=True)
|
||||||
|
print("DIAG validate_changed=%s" % changed)
|
||||||
|
print("DIAG after_validate verts=%d polys=%d" % (len(me.vertices), len(me.polygons)))
|
||||||
|
print("DIAG DONE")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__" and "--" in sys.argv:
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user