Nodes/Melite Poser Nodes/🔧 Somax Bake (motion NPZ → animated GLB)
ComfyUI Node

🔧 Somax Bake (motion NPZ → animated GLB)

Motion NPZ to animated GLB, no Blender, no retargeting

By JayDataEngineer·Created 2 months ago·Updated 4 days ago· 1
🔧 Somax Bake (motion NPZ → animated GLB)
    • animated_glb_path
    ◄rigged_glb_path—►
    ◄motion_npz_path—►
    â—„anim_namemotionâ–º
    â—„fps30.00â–º

    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's input/.
    • motion_npz_path - the SOMA-77 motion NPZ, also input/.
    • anim_name (optional, default motion) - 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.

    CategoryTechNoir/Animation

    Inputs (4)

    NameTypeDefaultDescription
    rigged_glb_pathSTRINGSOMAX-77 rigged GLB (filename in input/).
    motion_npz_pathSTRINGSOMA-77 motion NPZ (filename in input/).
    anim_nameoptSTRINGmotion—
    fpsoptFLOAT30.001–120—

    Outputs (1)

    NameTypeDescription
    animated_glb_pathSTRING—