Body plan¶
A body plan describes the kinematic tree QuickIK solves against – a robot's joints, or an animal's skeleton – loaded once from JSON and reused across every solve. Unlike a typical robotics kinematic tree, which separates joints (that move) from frames or links (that get tracked), here every joint doubles as a keypoint: its world position is always available as a potential target, whether or not it carries a rotational DOF of its own. This is what lets one uniform KeypointObservation list – one entry per joint, in the body plan's own tree order – describe a whole tracked pose, DOF-bearing joints and fixed leaf keypoints alike.
One modeling consequence worth knowing: a joint's own DOF reorients its children, not itself – rotating a joint never moves its own keypoint, only the keypoints downstream of it. So every DOF needs at least one keypoint further down its own chain to be observable at all; a chain that ends exactly at its last DOF-bearing joint, with nothing past it, leaves that DOF's angle undetermined by any observation. This is why even a fixed, 0-DOF "tip" joint (a fingertip, a fly's claw, a robot's end effector) is usually worth keeping in the body plan even though it never actuates anything itself.
By default the root is a free-floating base with its own 6 DOFs (position and rotation), solved for like everything else – this fits a tracked animal or a robot free to move through the world. Setting the top-level "fixed_base": true instead anchors the root in place (e.g. a robot arm bolted to a table), removing those 6 DOFs from the state entirely. A semi-fixed base – one that only slides along a rail or spins on a turntable – isn't a separate setting: keep fixed_base set and give the root a zero-offset child joint carrying that one hinge/slide DOF, then attach the rest of the body to that joint instead of directly to the root. Because a joint's own DOF only moves its descendants (see above), this joint acts as exactly that one-DOF base – and unlike the root's own DOFs, it gets limits and weight_scaler like any other DOF.
Body plan JSON schema
{
"fixed_base": false,
"joints": [
{
"name": "root",
"parent": null,
"offset_pos": [0.0, 0.0, 0.0],
"offset_quat": [1.0, 0.0, 0.0, 0.0],
"weight_scaler": 1.0,
"dofs": []
},
{
"name": "elbow",
"parent": "root",
"offset_pos": [1.0, 0.0, 0.0],
"offset_quat": [1.0, 0.0, 0.0, 0.0],
"weight_scaler": 1.0,
"dofs": [
{
"axis": [0.0, 0.0, 1.0],
"type": "hinge",
"neutral": 0.0,
"weight_scaler": 1.0,
"limits": [-3.0, 3.0]
}
]
},
{
"name": "wrist",
"parent": "elbow",
"offset_pos": [1.0, 0.0, 0.0],
"offset_quat": [1.0, 0.0, 0.0, 0.0],
"weight_scaler": 1.0,
"dofs": []
}
]
}
fixed_base: whether the root is fixed in the world rather than a free-floating base. Optional, defaults tofalse.parent: joint name, ornullfor the root.offset_pos/offset_quat: this joint's offset from its parent.weight_scaler: multiplied together with each frame'sKeypointObservation'sweightfor this joint's keypoint. Optional, defaults to1.0.dofs: this joint's degrees of freedom, each with:type:"hinge"(rotational) or"slide"(translational).axis: rotation/translation axis in local frame.neutral: neutral angle (radians) or position.limits: optional[min, max]limits; unbounded if omitted ornull.weight_scaler: multiplied together withSolverConfig'sweightfor this DOF's deviation-from-neutral penalty. Optional, defaults to1.0.
See the API reference for the full schema.