The object model file
An object's metadata: anchors, articulations, parts, and motions, paired with a URDF.
Every object in a project is at least two files: the URDF (geometry and joints) and the object_model.yaml (the Zeon metadata that sits on top of it). Both come from the shared mesh database. Some objects add a tag_collections/ folder alongside, covered at the end of this page.
You rarely author these by hand; they arrive from the catalog. This page is for reading them, and for the occasional local edit.
Example
A simple object, coldblock_holder.object_model.yaml, in full:
urdf: coldblock_holder.urdf
articulations:
default:
description: All joints at rest position
joints: {}
anchors:
object:
parent_link: world
description: Table placement frame, origin at (+X, +Y, -Z) bbox corner
link_T_anchor:
xyz: [0.238, 0.0, 0.0]
wxyz: [-0.707107, 0.0, 0.0, 0.707107]A plate has the same structure with far more anchors, one per well. A centrifuge adds parts and a second articulation.
Top-level fields
| Field | Type | Required | Description |
|---|---|---|---|
urdf | string | yes | The URDF this YAML pairs with, resolved relative to the YAML's own directory. |
anchors | map | yes | Named frames on the object that skills can target. Must include object. |
articulations | map | yes | Named joint configurations. Must include default. |
parts | map | no | Named groups of URDF links (see below). |
motions | map | no | Named recorded tool paths (see below). |
anchors
anchorsAn anchor is a named pose attached to a link in the URDF. Skills use anchors to know where to grab, dispense, place, or align.
From coldblock_small:
anchors:
hole_1:
parent_link: body
description: hole 1 xyz location
link_T_anchor:
xyz: [-0.0397037, 0.00730662, 0.020485]
wxyz: [0.986324, -5.82899e-33, -3.07483e-34, 0.164815]| Field | Type | Description |
|---|---|---|
parent_link | string | The URDF link this anchor is attached to. Must exist in the URDF. |
description | string | Free text: what this anchor represents, and which frame convention it follows. |
link_T_anchor.xyz | [x, y, z] | Translation from parent_link to the anchor, in metres. |
link_T_anchor.wxyz | [w, x, y, z] | Quaternion from parent_link to the anchor, scalar first. Normalized on load. |
Every object must have an anchor named object, the object's own reference frame, used for placement.
Well anchors on a plate are named for the well itself, in uppercase and with no prefix: wellplate_pcr has anchors A1 through H12, all parented to its body link. Workflows and skills reference them by those names.
Beyond those three required fields, an anchor may carry any combination of four optional blocks:
| Block | What it is |
|---|---|
grasp | Gripper parameters. width is required, in metres. standoff defaults to 0.05, the distance retracted along -Z for the pre-grasp pose. gripper_variant defaults to "stock". |
viewpoint | A spherical camera distribution around the anchor. Angles are written in degrees and converted to radians on load. |
prior_weights | A penalty matrix for pose fitting. Written as either a 6-element diagonal shorthand or a symmetric 6×6 nested list. |
sampling | Multi-start seed configuration for ICP: a covariance matrix in the same two shapes, plus num_samples. |
A convention key is also accepted on an anchor and silently ignored; it exists for backward compatibility with older catalog files.
articulations
articulationsAn articulation is a named joint configuration. Static objects need only a default with no joints:
articulations:
default:
description: All joints at rest position
joints: {}Movable objects have more. A centrifuge, for example, has a closed and an open state:
articulations:
default:
description: Lid closed, rotor at home
joints:
lid_joint: 0.0
rotor_joint: 0.0
open:
description: Lid fully open, rotor at home
joints:
lid_joint: 1.69
rotor_joint: 0.0| Field | Type | Description |
|---|---|---|
description | string | Free text. |
joints | map | Joint name from the URDF → value. Radians for revolute joints, metres for prismatic. |
The default articulation is special: it must list exactly every actuated joint in the URDF. Missing one, or naming a joint that doesn't exist, fails the load. Other articulations may be partial, but may still only name actuated joints.
parts
partsA part is a named group of URDF links, a way to refer to a piece of an object without naming its links individually. The centrifuge:
parts:
body:
links: [body]
description: Centrifuge base
lid:
links: [lid]
description: Hinged lid
rotor:
links: [rotor]
description: Spinning rotor that carries the microplate| Field | Type | Description |
|---|---|---|
links | list of strings | URDF link names belonging to this part. Always a list, even for one link. |
description | string | Human-readable label. Also usable as a segmentation prompt. |
Parts are optional and many objects skip them entirely. Every link named in a part must exist in the URDF.
motions
motionsA motion is a recorded tool path, attached to a link the same way an anchor is. An anchor names a place; a motion names a path through places. Because both hang off a link rather than off the world, a motion follows the object when the object is re-localized.
motions:
lid_lift:
parent_link: body
description: Lift the lid clear of the deck
keyposes:
- t: 0.0
link_T_tcp:
xyz: [0.012, -0.004, 0.18]
wxyz: [0.0, 1.0, 0.0, 0.0]
seed: [0.104, -0.412, 0.203, 0.0, 1.108, 0.301]
gripper: 0.03
- t: 0.62
link_T_tcp:
xyz: [0.014, -0.004, 0.221]
wxyz: [0.0, 1.0, 0.0, 0.0]
seed: [0.111, -0.377, 0.219, 0.0, 1.081, 0.303]
- t: 1.35
link_T_tcp:
xyz: [0.061, -0.005, 0.244]
wxyz: [0.0, 1.0, 0.0, 0.0]
gripper: 0.03| Field | Type | Required | Description |
|---|---|---|---|
parent_link | string | yes | The URDF link this motion is attached to. Must exist in the URDF. |
description | string | yes | Free text: what this path does, and in which direction. |
gripper_variant | string | no | Which gripper was fitted when the motion was demonstrated. Defaults to "stock", and is omitted from the file when it is stock. |
keyposes | list | yes | The path, in order. At least two. |
Each keypose:
| Field | Type | Required | Description |
|---|---|---|---|
t | float | yes | Seconds from the start of the motion. Must be at least 0 and must not decrease along the list. |
link_T_tcp.xyz | [x, y, z] | yes | The tool tip's position in the parent_link frame, in metres. |
link_T_tcp.wxyz | [w, x, y, z] | yes | Its orientation, scalar first. Normalized on load. |
seed | list of floats | no | The joint angles recorded at this sample, in radians. A hint only: replay uses it to stay on the arm configuration the path was demonstrated with. The pose is what is authoritative. |
gripper | float | no | The gripper opening at this sample, in metres. Must not be negative. |
Two differences from anchors are worth knowing:
- A motion needs at least two keyposes. One pose is an anchor, and the loader says so.
- A motion's name may collide with a URDF link name. Anchors are injected into the kinematic model as frames, so their names have to be unique against links. A keypose is somewhere to drive to rather than a frame the kinematics needs, so motions are not injected and the collision rule does not apply to them.
Keyposes are a thinned representation of a much denser recording. They are not a trajectory, and replay does not step from one to the next; it interpolates, so a three-keypose motion still runs as a smooth curve. See Recording a motion.
Tag collections
Some objects have a tag_collections/ folder beside them:
objects/wellplate_holder/
├── wellplate_holder.urdf
├── wellplate_holder.object_model.yaml
└── tag_collections/
├── bench_left.yaml
└── bench_right.yaml
Each file records the fiducial tags stuck to one physical copy of the object. The shared model above carries everything true of the type, and each unit adds only its own tags, so a lab can own three of something without keeping three near-identical copies of its model. The unit's handle is the filename without the extension, and it is what the app shows as a Tagged unit. Handles are lowercase, start with a letter, and hold only letters, digits, underscores and hyphens; a file whose name does not fit that is ignored, so an editor backup cannot break anything.
schema: tag_collection/v1
object: wellplate_holder
family: apriltag_36h11
size_m: 0.020
description: Holder on the left bench
tags:
14:
parent_link: body
description: Front face, upper left
link_T_tag:
xyz: [0.031, 0.0, 0.048]
wxyz: [0.5, -0.5, 0.5, 0.5]
15:
parent_link: body
link_T_tag:
xyz: [-0.031, 0.0, 0.048]
wxyz: [0.5, -0.5, 0.5, 0.5]| Field | Required | Description |
|---|---|---|
schema | no | Defaults to tag_collection/v1, and if present must be exactly that. A file with no schema: line loads. |
object | yes | The object this belongs to. |
family | no | Defaults to apriltag_36h11, and that is the only accepted value, here or on an individual tag. Any other family is rejected when the file loads, so a typo fails loudly rather than leaving tags undetectable. |
size_m | no | The printed tag's edge length in metres. Defaults to 0.020, which is 20 mm. Must be positive. |
description, calibrated_at, calibration_source | no | Free text and provenance. |
tags | yes | A map of tag id to its placement. At least one. Each entry takes a parent_link, a link_T_tag pose in the same form as an anchor's, and optional size_m, family and description overrides. |
The collection is merged with the shared model when the object is loaded, and is never written back into it.
An object uses one encoding or the other, never both. Older objects record their tags directly in
object_model.yaml, as anchors namedtag_<id>. Those keep working untouched, and most objects in the catalogue are still that way. An object carrying both an inlinetag_<id>anchor and atag_collections/folder is refused rather than silently merged. There is no tool for authoring a collection, so in practice one either ships with an object or does not exist.
Coordinate convention
Three things always hold:
- XYZ is in metres.
- Quaternions are
[w, x, y, z], scalar first, and are normalized on load. - Anchors are expressed in the frame of their
parent_link.
The axis triad does not always hold, and nothing enforces it at run time. Which way +Z points depends on what the anchor is for:
| Convention | +Z is |
|---|---|
world | Up. Used for anchors fixed relative to the world, like support surfaces. |
tcp | The approach direction into the grasp. The pre-grasp pose retracts along -Z. |
camera | The optical axis, pointing toward the scene. Follows the OpenCV convention. |
look_at | The preferred viewing direction, outward from the target surface. |
This is why anchor description fields are expected to say which frame they mean. One catalogue object's view_rotor anchor opens its description with "Camera view (OpenCV convention) looking down at the rotor disk, ~25 cm above", naming the convention before anything else. Read the description rather than assuming +Z is up.
When the file is wrong
The object won't load, and the platform validates more than YAML syntax. These all fail:
- A missing
urdf,anchors.object, orarticulations.default. - An anchor whose
parent_linkisn't in the URDF. - An anchor whose name collides with a URDF link name.
- A part naming a link that isn't in the URDF.
- A
defaultarticulation that doesn't list exactly the actuated joints. - A quaternion with a near-zero norm.
- A motion whose
parent_linkisn't in the URDF. - A motion with fewer than two keyposes.
- A motion whose keypose times go backwards, or start below zero.
- A keypose with a negative
gripper.
A stale urdf value self-heals: if the named file isn't there, the loader falls back to the sibling URDF with the same basename.
Editing by hand
Three reasons to touch this file:
- Add anchors: when the catalog's version lacks one you need. A custom well, a non-standard pour point. The anchors already there stay as the catalog shipped them.
- Add articulations: same idea, for a joint configuration the catalog didn't define.
- Clarify descriptions: particularly to state an anchor's frame convention.
Leave the URDF alone unless you're rebuilding the geometry. Nearly every edit belongs in the YAML.
Motions are the exception to all three. Hand-writing a keypose list is not a reasonable way to spend an afternoon, and the annotation editor records one in a couple of minutes. Read a motions block to understand what an object can do; record and edit them in the editor.
See also
Updated about 2 months ago