The model button now accepts .fbx too: a new model:convert IPC runs Blender with scripts/fbx2modelglb.py (mesh + skin kept, animations dropped, embedded diffuse recovered even when the FBX references missing external textures) into userData/models3d, then the resulting GLB loads as usual. anim/model conversion share one convertFbx helper. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
61 lines
2.2 KiB
Python
61 lines
2.2 KiB
Python
# Convert a character FBX (e.g. a Mixamo T-pose download) into a GLB model
|
|
# for DJ Visualizer: mesh + skeleton/skin kept, animations dropped (the dance
|
|
# library provides the moves), embedded diffuse texture recovered even when
|
|
# the FBX references missing external files.
|
|
# blender -b -P fbx2modelglb.py -- input.fbx output.glb
|
|
import bpy, sys, os
|
|
argv = sys.argv[sys.argv.index('--')+1:]
|
|
SRC, OUT = argv[0], argv[1]
|
|
TMP = OUT + '.diffuse.png'
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
bpy.ops.import_scene.fbx(filepath=SRC)
|
|
arm = None; meshes = []
|
|
for o in bpy.data.objects:
|
|
if o.type == 'ARMATURE' and arm is None:
|
|
arm = o
|
|
if o.type == 'MESH':
|
|
meshes.append(o)
|
|
if not meshes:
|
|
raise SystemExit('nessuna mesh nel file')
|
|
# drop any animation, keep the rest pose
|
|
for o in bpy.data.objects:
|
|
o.animation_data_clear()
|
|
for a in list(bpy.data.actions):
|
|
bpy.data.actions.remove(a)
|
|
if arm:
|
|
for pb in arm.pose.bones:
|
|
pb.location = (0, 0, 0)
|
|
pb.rotation_quaternion = (1, 0, 0, 0)
|
|
pb.scale = (1, 1, 1)
|
|
# recover the diffuse: prefer an embedded image that actually has data
|
|
valid = [i for i in bpy.data.images if i.has_data and i.size[0] > 0]
|
|
diff = next((i for i in valid if 'Diffuse' in i.name), valid[0] if valid else None)
|
|
if diff:
|
|
try:
|
|
diff.filepath_raw = TMP
|
|
diff.file_format = 'PNG'
|
|
diff.save()
|
|
diff = bpy.data.images.load(TMP)
|
|
diff.pack()
|
|
mat = bpy.data.materials.new('skin')
|
|
mat.use_nodes = True
|
|
bsdf = next(n for n in mat.node_tree.nodes if n.type == 'BSDF_PRINCIPLED')
|
|
tex = mat.node_tree.nodes.new('ShaderNodeTexImage')
|
|
tex.image = diff
|
|
mat.node_tree.links.new(bsdf.inputs['Base Color'], tex.outputs['Color'])
|
|
for m in meshes:
|
|
m.data.materials.clear()
|
|
m.data.materials.append(mat)
|
|
except Exception as e:
|
|
print('texture non recuperata:', e)
|
|
try:
|
|
bpy.ops.export_scene.gltf(filepath=OUT, export_format='GLB', export_animations=False,
|
|
export_skins=True, export_rest_position_armature=True)
|
|
except TypeError:
|
|
bpy.ops.export_scene.gltf(filepath=OUT, export_format='GLB', export_animations=False, export_skins=True)
|
|
try:
|
|
os.remove(TMP)
|
|
except OSError:
|
|
pass
|
|
print('EXPORT_OK')
|