π Motion Preview
A stick figure grid instead of opening Blender
- motion_path
Text-to-motion models hand you a .npz - a bag of joint positions over time - and that's it. No viewport, no thumbnail, no way to tell whether the thing you generated is a walk cycle or a seizure without loading it into a rig and finding out. MotionPreview reads the file, draws a few of the poses as a stick figure grid, and drops it in your ComfyUI output. Two seconds of looking beats five minutes of rigging.
What it does
The brief in the node's own docs is "inspect a KimodoTextToPose result before you apply it to a rig" - same idea for the HY-Motion nodes. The failure mode it's aimed at is expensive: bake a bad motion onto a mesh, then discover it. Here you check the source data first, and the output passes through untouched either way, so the preview sits mid-chain instead of at the end.
How it works
It loads the NPZ with numpy and hunts for the joint positions. It looks for an array under positions, joints, joint_positions or motion, and accepts the first one whose shape is (frames, joints, 3). If none of those keys exist it falls back to the first 3D array in the file whose last dimension is 3. Only if there's genuinely nothing shaped like that does it give up - and when it does, it prints the keys it actually found, which is a much better error than "could not load".
From the full motion it picks num_keyframes poses at even intervals, clamped to at least 3 and at most 12, and also capped at the total frame count so a 4-frame test file doesn't get over-sampled.
Then matplotlib - Agg backend, so no display needed and it works headless - renders a grid, three poses per row, dark background, and saves it as a PNG in the output directory. The bones come from a hard-coded list matching the SOMA joint ordering: root, pelvis, spine, neck, head, both arms, both legs. Each pose is drawn in the XZ plane, which for this convention means you're looking at the figure side-on: horizontal is depth, vertical is height. Joints are plotted as dots, bones as lines, and each cell is titled with its frame number so you can correlate what you see with an index in the data.
The output image is attached to the node as UI data, so it appears in the node itself, the way PreviewImage does.
Inputs and output
motion_path - STRING, and declared as a forced input in the source, so you wire it rather than typing it. Feed it the NPZ path coming from your motion generator.
num_keyframes - INT, 3 to 12, default 6. How many poses tile the grid. Six is readable; twelve is a flip-book you can actually trace a motion through, at the cost of smaller figures.
motion_path - the same string out, unchanged. Passthrough on purpose: generate β preview β bake, with no rewiring.
Installing it
cd /path/to/ComfyUI/custom_nodes
git clone https://github.com/JayDataEngineer/melite-utility-nodes.git
Restart ComfyUI, or install Melite Utility Nodes from ComfyUI Manager. This node is why the pack declares matplotlib in its requirements.txt - it's a headless plotting dependency, and without it you'll get an import error at render time. numpy comes from your ComfyUI environment.
Where it bites
- It trusts the joint order. The code doesn't check that your file has the expected number of joints - it draws every bone whose two indices exist in the array. So a file from a different skeleton convention renders as a confident, wrong-looking figure rather than an error. If the pose looks anatomically impossible, suspect the convention before you suspect the motion.
- Side-on only. You get one projection. A motion that reads as a big arm swing from the front may look nearly static here, because forward/back movement is compressed. It's a smoke test, not a review pass.
- One PNG per run. It's an output node, so it always executes, and the filename is timestamped. Queue ten runs and you have ten files in
output/. Cheap, but tidy people will notice. - It's about the only documentation this format has. There's no community discussion of SOMA-77 motion files or the Kimodo/HY-Motion line in ComfyUI at all - no threads, no shared workflows. Which is exactly why a node that renders the data is more valuable than it sounds: it's the only feedback loop you've got.
- For the final check, use a different node. This shows you the source motion; once it's baked, the pack's GLB preview is what you want, with a real rotatable mesh rather than a figure drawing.
Inputs (2)
| Name | Type | Default | Description |
|---|---|---|---|
| motion_path | STRING | Path to a SOMA-77 motion NPZ file (output of KimodoTextToPose or HYMotion). | |
| num_keyframes | INT | 63β12 | Number of keyframe poses to render in the grid. More frames = denser preview but larger image. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| motion_path | STRING | β |