Files
djaudiovisualizer/scripts/fbx2modelglb.py
T
lucianoandClaude Fable 5 d329759961 Load FBX characters directly: in-app conversion to GLB
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>
2026-07-31 11:31:26 +02:00

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')