🦴 Poser Deform SOMA (joints → mesh)
Turning 77 joints into an actual posed mesh
- joints
- mesh
Joints are numbers. A mesh is what a renderer, a depth ControlNet, or an exported GLB actually needs. Poser Deform SOMA is the bridge: it takes a SOMA-77 joint set and applies linear blend skinning to the bind mesh, returning posed vertices. In the pack's own pipeline this is the node that turns "I moved the shoulders" into "here is a body".
It wraps the same SkinEngine.deform() call the pack's interactive HTTP route uses, so an interactive slider drag in their Body Studio and a queued graph run produce the same geometry. The difference is observability: a typed node with a validateable input is something you can see, log, and replay.
How it works
Linear blend skinning, the standard rig maths - each vertex is transformed by a weighted combination of its bone transforms, weights coming from the identity template. The engine does that in metres; the node handles the conversion in both directions.
The validation here is unusually thorough, and deliberately so, because the failure modes are all silent. It checks that
jointsreally is a(positions, rotations)tuple fromPoserBindPose,- positions are
(T, 77, 3)- a bare(77, 3)gets rejected, because numpy would happily broadcast it wrong, - rotations are
(T, 77, 3, 3)- the source comment notes that the transposed(T, 3, 3, 77)variant "silently reduces along the wrong axis", which is exactly the bug that burned them, - and both have the same frame count
T.
Then it converts cm → m, deforms, squeezes the batch dimension if the engine returned (1, V, 3), converts m → cm back, and pulls face indices from the same identity's engine - with a comment explaining why cross-identity faces would be catastrophic (indices pointing at vertices that don't exist, or wrapping silently through numpy indexing).
Inputs and outputs
joints- aJOINTSvalue, normally fromPoserBindPose. Positions in centimetres, rotations world-space. The frame countTcan be more than 1, but withT > 1the engine returns a(T, V, 3)array and this node expects to squeeze(1, V, 3)- one pose per execution is the well-trodden path.identity_model(defaultanny) - must be the same model the joints came from. This is a free-text string, not a combo, so a mismatch is your responsibility.
One output: mesh, typed MESH - (vertices, faces) with vertices float32 in centimetres. Wire it into PoserRender for a depth/normal/rgb pass, into a GLB export, or into whatever else wants geometry.
Install
cd /path/to/ComfyUI/custom_nodes
git clone https://github.com/JayDataEngineer/melite-poser-nodes.git
Restart, or install melite-poser-nodes via ComfyUI Manager. The deform path needs py-soma-x[anny] plus a local copy of the identity assets - the pack's requirements.txt carries a note about a 0.3.0 wheel bug where soma.body.assets isn't shipped, which skin.py works around by passing a data_root. _pick_device() prefers CUDA because the anny pass is roughly 10× faster there, and falls back to CPU without complaining.
Where people get burned
Units: the pack's most-repeated warning lives in this node's source. The single positions_cm / 100.0 line is flagged as "the most silent-failure-prone line in the chain". Feed it metres and your deformed body is tiny; every shape check passes and every downstream render just shows a speck. Centimetres in, centimetres out, always.
soma cannot deform. The NPZ-backed SOMA engine has no deform support. Use PoserBindMesh for a rest-pose soma, or use anny. This node's docstring is explicit that PoserDeformSoma "would fail" on soma.
Geometry that isn't PoserBindPose output. The tuple structure isn't decorative - anything else gets a TypeError listing what it got. If you're synthesising joints yourself (a motion retarget, a mocap NPZ), match (1, 77, 3) and (1, 77, 3, 3) exactly, cm and world-space rotations.
Identity mismatch between joints and deform. The node can't detect that your joints were shaped with identity_coeffs='0.7,0.4,0.55' while the engine is holding neutral. You get a subtly wrong body, which is much worse than an error. Keep a single source of truth for the identity in your graph.
Inputs (2)
| Name | Type | Default | Description |
|---|---|---|---|
| joints | JOINTS | — | |
| identity_model | STRING | anny | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| mesh | MESH | — |