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

FieldTypeRequiredDescription
urdfstringyesThe URDF this YAML pairs with, resolved relative to the YAML's own directory.
anchorsmapyesNamed frames on the object that skills can target. Must include object.
articulationsmapyesNamed joint configurations. Must include default.
partsmapnoNamed groups of URDF links (see below).
motionsmapnoNamed recorded tool paths (see below).

anchors

An 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]
FieldTypeDescription
parent_linkstringThe URDF link this anchor is attached to. Must exist in the URDF.
descriptionstringFree 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:

BlockWhat it is
graspGripper 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".
viewpointA spherical camera distribution around the anchor. Angles are written in degrees and converted to radians on load.
prior_weightsA penalty matrix for pose fitting. Written as either a 6-element diagonal shorthand or a symmetric 6×6 nested list.
samplingMulti-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

An 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
FieldTypeDescription
descriptionstringFree text.
jointsmapJoint 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

A 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
FieldTypeDescription
linkslist of stringsURDF link names belonging to this part. Always a list, even for one link.
descriptionstringHuman-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

A 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
FieldTypeRequiredDescription
parent_linkstringyesThe URDF link this motion is attached to. Must exist in the URDF.
descriptionstringyesFree text: what this path does, and in which direction.
gripper_variantstringnoWhich gripper was fitted when the motion was demonstrated. Defaults to "stock", and is omitted from the file when it is stock.
keyposeslistyesThe path, in order. At least two.

Each keypose:

FieldTypeRequiredDescription
tfloatyesSeconds 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]yesThe tool tip's position in the parent_link frame, in metres.
link_T_tcp.wxyz[w, x, y, z]yesIts orientation, scalar first. Normalized on load.
seedlist of floatsnoThe 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.
gripperfloatnoThe 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]
FieldRequiredDescription
schemanoDefaults to tag_collection/v1, and if present must be exactly that. A file with no schema: line loads.
objectyesThe object this belongs to.
familynoDefaults 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_mnoThe printed tag's edge length in metres. Defaults to 0.020, which is 20 mm. Must be positive.
description, calibrated_at, calibration_sourcenoFree text and provenance.
tagsyesA 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 named tag_<id>. Those keep working untouched, and most objects in the catalogue are still that way. An object carrying both an inline tag_<id> anchor and a tag_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
worldUp. Used for anchors fixed relative to the world, like support surfaces.
tcpThe approach direction into the grasp. The pre-grasp pose retracts along -Z.
cameraThe optical axis, pointing toward the scene. Follows the OpenCV convention.
look_atThe 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, or articulations.default.
  • An anchor whose parent_link isn'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 default articulation that doesn't list exactly the actuated joints.
  • A quaternion with a near-zero norm.
  • A motion whose parent_link isn'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:

  1. 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.
  2. Add articulations: same idea, for a joint configuration the catalog didn't define.
  3. 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


Did this page help you?