🔧 Somax Bake (motion NPZ → animated GLB)
Motion NPZ to animated GLB, no Blender, no retargeting
- animated_glb_path
Retargeting is where animation pipelines go to die - you've got a mocap skeleton with one joint convention, a character rig with another, and a pile of heuristics that mostly work. Somax Bake avoids the entire problem by refusing to retarget anything. If your GLB is a SOMAX-77 rig and your motion is a SOMA-77 NPZ, the joint hierarchies already match one-to-one. So it just writes the motion into the file.
The pipeline it serves is: TRELLIS → SOMAX → bake → animated GLB. Rotations from the NPZ become glTF animation channels targeting the existing joint nodes. No Blender subprocess, no scene-graph mutation, no retargeting heuristics. Pure CPU.
How it works, and the smart bit
The NPZ carries posed joint positions and global rotation matrices for 77 joints. The node converts those to local TRS quaternions and writes them as glTF animation samplers on the existing joint nodes of the rigged GLB. anim_name and fps decide how the channel is labelled and how samples map to time.
Then there's the decision that makes this node worth trusting: translations are deliberately not animated. The source explains why in a paragraph worth quoting to anyone who struggles with scale bugs - the NPZ's posed joints live in SOMA template space, roughly a 170cm adult, while the GLB's mesh might be at source scale, roughly a 100cm child. Baking template-space translations onto a mesh-space rig stretches the character into adult proportions, which the comment calls, accurately, body horror.
The GLB's own bind-pose translations are already in mesh space and are correct. They stay static. Rotations alone carry all the visible motion for in-place animation and for arm, leg, and torso work, which is what these clips are. There is one path here and no fallback - the docstring says so explicitly, and the pack's HTTP route for the same operation is gone.
Inputs and outputs
rigged_glb_path- a SOMAX-77 rigged GLB, filename in ComfyUI'sinput/.motion_npz_path- the SOMA-77 motion NPZ, alsoinput/.anim_name(optional, defaultmotion) - the animation's name in the glTF.fps(optional, default 30, range 1–120) - sample rate.
Resolution is tolerant: absolute paths that exist are used directly, otherwise it looks in input/ then output/, and raises a clear FileNotFoundError naming the file if neither has it. Nice touch for chaining.
One output: animated_glb_path in output/. The node's UI payload also carries a diagnostics dict - frame count, joint count - which is exactly the sort of breadcrumb you want when a bake silently produces a two-frame animation.
Install
cd /path/to/ComfyUI/custom_nodes
git clone https://github.com/JayDataEngineer/melite-poser-nodes.git
Restart ComfyUI, or install melite-poser-nodes from ComfyUI Manager. Pack deps are py-soma-x[anny], trimesh and xatlas. This node uses numpy and scipy.spatial.transform.Rotation for the global-to-local quaternion conversion - scipy ships with ComfyUI's runtime, but if you've pruned your environment it's the dependency that will bite here.
Where people get burned
The rig has to be SOMAX-77. The whole premise is that hierarchies match. A Mixamo rig with different joint ordering, or a creature armature, is not this. The pack names its output skin SOMA-Mixamo with 77 mixamorig joints - that's your target.
In-place motion only. No translation channels means no root motion. A walk cycle baked here walks without going anywhere; you drive locomotion in the engine or the viewer. That's a design decision, not a limitation to work around.
Frames and fps are your problem, not the file's. The NPZ doesn't carry a frame rate. Bake at 30 when the source was captured at 24 and the motion plays at the wrong speed - nothing in the node can tell.
Don't expect in-ComfyUI playback. You get a path to a GLB. Preview it in a viewer (or your engine) to confirm the bake landed. A silently empty animation channel is the classic failure, which is presumably why the node ships diagnostics in the first place.
GLB parsing is hand-rolled here. The module writes its own JSON/BIN chunk reader with magic-number and truncation checks rather than pulling trimesh in. It's robust and it fails loudly, but it also means it's picky about files that other tools would shrug at.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| rigged_glb_path | STRING | SOMAX-77 rigged GLB (filename in input/). | |
| motion_npz_path | STRING | SOMA-77 motion NPZ (filename in input/). | |
| anim_nameopt | STRING | motion | — |
| fpsopt | FLOAT | 30.001–120 | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| animated_glb_path | STRING | — |