Component library format
A component library is a directory the compiler is given with --library. It holds the physical parts a program assembles a robot from, the objects a world places and the environments a scenario takes. The compiler reads two kinds of file from it, library.yaml and each part's component.yaml, and checks that the assets they name exist. It never opens a mesh, and it never reads the catalog, archive, or generated pages that a library repository may keep beside them. There is no install step and no default location.
This page is the schema. The library repository documents how parts are built; the compiler is the only implementation of what a declaration means.
Layout
<library>/
├── library.yaml name and the registered ids: parts, objects, environments, walkers
├── parts/<part-id>/
│ ├── component.yaml the declaration specified here
│ ├── assets/ the mesh the declaration names, and what that mesh keeps beside it
│ └── ... README, LICENSE, CHANGELOG, images, source: not read
└── walkers/<walker-id>/
├── walker.yaml a walker's declaration (see Walkers)
└── assets/ its policylibrary.yaml
name: demo_parts # an identifier
parts: # each id is parts/<id>/component.yaml
- tslot_1515_drone
- mecanum_black
walkers: # optional; each id is walkers/<id>/walker.yaml
- quad_walkerA part that is not listed does not exist to the compiler. An id listed twice or without a declaration is a diagnostic; the other parts still load. An object and an environment are listed and laid out as a part is; its category says which an entry is, so an id names one thing. A walker is listed under walkers and laid out in walkers/.
component.yaml
Values are YAML, read as JSON: only true and false are booleans, null, ~ and an empty value are null, and a plain scalar is a number when it spells a JSON number (1e-3 is a number here, unlike in YAML 1.1). A key given twice is a diagnostic. A key the schema does not list is a diagnostic; there are no free-form fields, except inside provenance, where unknown keys are ignored.
Every length is in metres, every angle in radians, every mass in kilograms, and every vector is [x, y, z] in the part's own frame: +X forward, +Y left, +Z up. Every number is 0 or between 1e-100 and 1e30 in size, judged as the float it reads as: no quantity of a part is smaller or larger, and what the targets work out from such a number can overflow or be lost to zero. A zero that a computation left a rounding off, such as -2.7e-32, is within it; a number written nearer zero than any float, such as 1e-400, reads as 0 but is no zero, and is not. Nothing is defaulted. A value the declaration does not state is absent, and a later phase that needs it reports what is missing rather than assuming.
schema_version: 1
id: mecanum_black # equals the directory name
category: wheel # what the part is; see Fields
description: Black 97 mm mecanum wheel.
visual: # the mesh that is shown
file: assets/model.dae # relative to the part directory
xyz: [-0.0002, 0, -0.001] # where the mesh sits in the part frame
rpy: [0, 0, 1.57079]
wheel: # wheels only, and required for them
radius: 0.0487 # across the axle
width: 0.0479 # along the axle
inertial:
mass: 0.09643
origin_xyz: [0, 0, 0] # centre of mass
inertia: {ixx: 7.5613e-05, ixy: 0, ixz: 0, iyy: 7.5613e-05, iyz: 0, izz: 0.000114351}
source: estimated # measured | datasheet | cad_measured | estimated | placeholder
basis: solid cylinder at 270 kg/m^3 # how an estimate was chosen; required for estimated and placeholder
connectors: # mating points, at least one
mount:
pos: [-0.0013, 0.0011, 0]
dir: [0, 0, 1] # mating axis, unit length
joint: {type: continuous, axis: [0, 0, 1]} # the joint created when mated; fixed when absent
profiles: # the component types the part can be used as
Mecanum:
mounts: {default: mount} # logical mount name -> connectorFields
| Field | Required | Meaning |
|---|---|---|
schema_version | yes | 1 |
id | yes | The part id; must equal the directory name |
category | yes | chassis, wheel, track, sensor, attachment, leg, motor, battery, or motor_driver for a part of a robot; object for a thing a world places and environment for a place a scenario takes, which state the fields under Objects and Environments and not these |
description | yes | One sentence for people |
visual | yes | file, xyz, rpy. file is a relative path to a mesh that exists, in a directory of its own inside the part directory, conventionally assets/, and not beside the declaration. That directory goes into an output whole: a mesh may keep what it is made of in other files, a glTF its buffers and textures, an OBJ its materials, and names them by relative path, and the compiler opens no mesh to learn which. So what a mesh names lies in that directory or below it, and nothing else is kept there; each of those files is the part's own, not a link out of the part directory |
collision | no; not for wheels | type: box with size (three positive numbers) and origin_xyz, or type: hull with vertices (at least four [x, y, z] points) and faces (triangles as three indices into the vertices, wound either way) that bound a convex solid: a corner more than 1 µm outside a face's plane is refused, and so are triangles that do not close one solid of some volume, every edge bordering exactly two of them and none naming a corner twice or given twice. Stands in for the mesh in collision checks and in the simulators, where a hull is written as a binary STL. Without it the mesh is the collision shape |
wheel | wheels only, required | radius, width, both positive. A wheel's rolling contact is derived from these, so a wheel declares no collision |
track | a Track part, required; no other part | A track's belt, from its maker's data: travel_per_turn_m, how far the belt travels per turn of the hub the part mates by (positive), when the maker's data gives it; width (positive); and ground: {from: [x, y, z], to: [x, y, z]}, the two ends of the belt's run on the ground in the part frame, which may not be one point. The travel is stated and not a radius: a link tread moves its sprocket's teeth times its links' pitch per turn, less than the sprocket's outline gives. Without it kinematics.tracks_state_their_travel cannot decide and no target drives the base. A track may declare a collision box around its whole unit, for the footprint; in the simulators it collides through a box on its belt's run |
inertial | yes | mass (positive), origin_xyz, inertia (all six terms; ixx, iyy, izz positive, and the principal moments, which the products ixy, ixz, iyz turn the diagonal into, positive and each at most the sum of the other two, judged in exact arithmetic on the numbers as stated, allowing for their rounding to the nearest doubles, 5 × 2⁻⁵³ of the largest entry: a thin plate stated in round decimals on the edge passes, and a tensor beyond it by more does not; a part's own tensor, which the MuJoCo targets hand on as it is stated, is held as MuJoCo holds it too, its two lesser principal moments adding up in floats to at least the largest, and a link's, which the mjcf target writes within the inequality, is not), source, basis |
connectors | yes, non-empty | Named mating points, below |
profiles | yes | The component types the part can be used as, below. A part with profiles: {} is a catalog entry that no program can select |
motor | a Motor part, required; no other part | What the motor's datasheet states: rated_voltage_v, the voltage its ratings hold at, and at that voltage no_load_speed_rad_s, stall_torque_nm and stall_current_a (its output shaft's speed unloaded, its torque stalled and the current it then draws), all positive. The wheel on its shaft turns within that speed and torque. voltage_min_v and voltage_max_v, the supply it accepts, only when its maker states them: both or neither, the minimum not negative, the rated voltage between them. bus, a name, when the motor carries its driver and is commanded over that bus |
battery | a Battery part, required; no other part | What the battery's page states: voltage_nominal_v, the voltage it is sold by (positive); voltage_min_v and voltage_max_v, what it delivers run down and full, both or neither, both positive and the nominal between them; capacity_ah (positive) when stated. A pack that states no range leaves the voltage rules undecided |
driver | a MotorDriver part, required; no other part | What the board's datasheet states. channels, a positive integer: the motors it drives from outputs of its own; with it, required, voltage_min_v and voltage_max_v (the motor supply it accepts, the minimum not negative) and continuous_current_per_channel_a, and peak_current_per_channel_a when stated, not below the continuous. buses, a list of names: the buses it commands motors over that carry their driver. A board states channels, buses, or both; one with buses alone may state its supply range. output_voltage_v when it regulates what it feeds its motors and does not pass its supply on |
range | a Range part, required; an Ultrasonic part, optional; no other part | What a range finder measures, from its datasheet: min_m (not negative), max_m (positive, at least min_m), field_of_view_rad (the full cone about +x, positive and below pi) and rate_hz (positive). Equal min_m and max_m make a fixed-distance sensor, which says only whether something is within that distance. The part's frame is the sensor's emitter: its origin where the beam starts, +x along it |
contacts | no | A list of {xyz, radius, note}: molded ground contacts of a chassis, such as a skid or a ball, as spheres. The statics rule counts each one that reaches the wheels' plane, and the targets simulate each as a sphere that slides |
links | a leg's, required; a chassis's or an attachment's, optional; no other part's | The bodies the part moves below its frame, below |
provenance | no | name, manufacturer, source_url, license; each a string or null. license is an SPDX expression when known. Any other key here is ignored: a library may keep what its own tools need (a price, a maker's catalog number) with the part, and the compiler never sees it |
Connectors
A connector is where another part mates. pos is the mating point, dir the mating axis: a wheel's axle, a mount's normal. Two parts mate with their dir vectors anti-parallel and their pos points coincident. up, when given, fixes the roll about dir for a part whose orientation matters (a camera lens, a lidar scan plane, a deck); it must be a unit vector perpendicular to dir. A rotation-symmetric part states no up.
joint is the joint that mating this connector creates: {type: fixed} (the default), or continuous, revolute, or prismatic with a unit axis. A wheel's axle connector carries continuous; a chassis mount carries nothing. Only a wheel turns on its connector. The targets refuse a moving joint on a connector a mating uses (target.unsupported): on the mating part's own connector unless the part is a wheel, and on the connector it mates onto whatever mates there. A connector no mating uses is not checked. A chassis, an attachment or a leg moves bodies of its own through links.
well, when given, is the void inside the part's collision box that what mates on this connector may fill: {radius: 0.052, width: 0.084} is a cylinder about the connector's axis, centred on it. A box is a bound drawn around the whole part, so a kit's chassis box covers the arches its wheels turn in and a motor's box the hub its wheel sits against; the clearance rules let a part into the declared shape exactly as far as a well on the connector it mates by, or mates onto, holds, and no further.
steering is stated by a chassis on a wheel mount that steers: {axis: [0, 0, 1], max_angle_rad: 0.6} is the kingpin's axis in the part frame and how far the wheel turns each way. Only a chassis may state it, and the connector of every mount its chassis type steers must. The compiler articulates the kingpin; the steering actuator and the knuckle body are the target's.
driveline is stated by a chassis on a connector where a motor seats to turn wheels that sit on other connectors, through shafts and gears of the chassis' own: {turns: [hub_rear_left, hub_rear_right], reduction: 2.33} names the connectors whose wheels the motor turns, and how many turns of the motor's shaft make one turn of theirs. A car's axle with one motor and a differential is the case. reduction is stated when the maker's data gives it; without it the targets leave those wheels' joints without limits and say so. Only a chassis may state a driveline, each connector it names once, and none of them a connector that states a driveline itself. Which wheels one motor may turn together is vocabulary, as is which mounts drive.
Links
A chassis, an attachment or a leg may move bodies of its own below its frame: a legged robot's legs, a pan-tilt mount's head, a leg's thigh and shin. links maps each link's name, an identifier, to what it states, each link after the one it hangs from:
links:
head:
parent: null # the part frame, or a link declared above this one
joint: {type: revolute, xyz: [0, 0, 0.02], rpy: [0, 0, 0], axis: [0, 0, 1], range: [-1.57, 1.57]}
inertial:
mass: 0.04
origin_xyz: [0, 0, 0.015]
inertia: {ixx: 1.1e-05, ixy: 0, ixz: 0, iyy: 1.1e-05, iyz: 0, izz: 1.0e-05}
source: estimated
basis: a 40 g servo of 30 by 50 by 30 mm
visual: {file: assets/head.glb, xyz: [0, 0, 0], rpy: [0, 0, 0]}
collision: # the shapes it collides by; absent when it touches nothing
- {type: box, size: [0.03, 0.05, 0.03], origin_xyz: [0, 0, 0.015], rpy: [0, 0, 0]}
servo: {kp: 4.0, damping: 0.05, armature: 0.0002, frictionloss: 0.01, effort: 0.6, hold: 0.5}| Field | Required | Meaning |
|---|---|---|
parent | yes | null for the part frame, or the name of a link declared above this one |
joint | yes | type, xyz, rpy: the link's frame is its parent's, moved to xyz and turned by the fixed-axis roll, pitch and yaw rpy. A revolute joint then turns it about axis, a unit vector in the link's own frame, within range: [lowest, highest], in radians, the lowest below the highest. A fixed joint states no axis and no range |
inertial | yes | As a part's, in the link's frame |
visual | yes | As a part's, in the link's frame |
collision | no | A list of at least one shape, as an object's, in the link's frame, or a capsule, a cylinder with half a sphere on each end: its diameter in x and in z the distance between the centres of its two ends. Without it the link collides with nothing |
servo | a revolute joint's, required; not a fixed one's | The position servo that turns the joint, as a simulator holds it: kp (N m/rad, positive), how stiffly it pulls toward the angle it is told; damping (N m s/rad), armature, the turning inertia of its motor behind its gearbox (kg m²), and frictionloss (N m), none negative; effort (N m, positive), the most torque it gives; hold, within the joint's range, the angle it holds when nothing commands it (rad); and basis, when given, where the values come from. Nothing stands in for a value the library does not state |
The part's own inertial is its base alone, the body at its frame that the links hang from: the compiler adds each link's mass to it, so a part whose maker states the whole product's mass states the base's share of it there and each link's in that link. The compiler weighs each link and places it where its servo holds it: the robot's mass and centre of mass, its footprint and the clearance between its parts take each link there. A sphere and a capsule are judged for clearance as they are, by the distance from the sphere's centre or from the segment between the capsule's two centres less the radius; only to tell whether a well lets one in is it taken as the solid of flat faces drawn around it, whose corners stand no more than 1.74 % of its radius past it, so that a well never lets in more than it holds. A part's own links are not judged against each other or against its base, which they meet at their joints. A well on a connector lets things only into the part's own declared shape, never into a link's: a clearance finding names the shape it is of, and for a link advises a tighter shape, or, when its joint or one it hangs from turns, that joint's servo holding it elsewhere. The mjcf target writes a part's links, each a body on a hinge its servo turns or fixed to the link above it (the output reference); ros-gz-nav2 and ros-mujoco-nav2 do not write them yet, and refuse such a part (target.unsupported).
Feet and gait
A chassis used as Legged walks on legs: its own links, or legs that are parts of their own placed on it (Legs as parts). Its profiles list Legged, with at least one mount, such as a deck others mount on; it states what its walking controller takes, and, when its legs are its own, its links and the shapes on them it stands on:
profiles:
Legged: {mounts: {deck: deck}}
feet: # the shapes on its links that stand on the floor
front_left: {link: FL_knee, shape: 1, friction: 0.8}
front_right: {link: FR_knee, shape: 1, friction: 0.8}
gait: {ahead: [-0.5, 1.0], aside: [-0.4, 0.4], turn: [-1.0, 1.0]}| Field | Required | Meaning |
|---|---|---|
feet | a leg's, and a Legged chassis's whose legs are its own, required; no other part's | Each foot by a name, an identifier: the link it is on, the shape it is among that link's collision, counting from 0, a sphere, a capsule or a box, and the friction it grips the floor with (positive). Two feet name two shapes |
gait | a Legged chassis's, required; no other part's | What its walking controller takes, in the chassis frame: ahead and aside, its speeds ahead and to its left (m/s), and turn, its turn to the left (rad/s), each [lowest, highest] with standing still, 0, among them |
The robot stands where the servos hold its legs, each hold: the compiler finds each foot's points lowest down there, below a sphere's centre, below a capsule's lower end or both ends of one lying flat, a box's lowest corners, each point within 2 mm of the foot's lowest standing with it, as the feet stand within 2 mm of one plane; the ground is where they stand. assembly.feet_touch_the_ground asks that every foot stand on the ground with the others, and points at the foot and at the servos that hold it; assembly.wheels_touch_one_plane asks the same of a wheel or a caster placed under the chassis; and assembly.links_clear_the_ground that no other shape of the links reach below the ground, which finds a foot named by the wrong shape. The statics rule takes the feet as the robot's supports, and cannot judge them while one stands off the ground.
The gait says which way and how fast the base goes. A Spin needs a turn that way and a Strafe a speed aside to that side, an Advance and a Navigate a speed ahead and a BackUp one back, and a speed_mps the task states may be no more than the gait's that way (kinematics.spin_supported, kinematics.strafe_supported and kinematics.walk_supported). A Navigate needs a turn either way, as a navigation planner turns a robot either way to follow its path. An Advance, a BackUp or a Strafe without a speed is judged by its way alone: the speed it goes at is the target's.
The feet and the gait are the Legged type's, and a leg's feet are its own. A part whose profiles list another type too stands, used as that type, on that type's wheels or tracks, its feet being shapes of its links like the rest. A Legged chassis has no wheel or track mounts. The two ROS targets refuse a legged base for its links, its part's or its legs'; the mjcf target writes one that walks by the walking controller its part states, or by a walker, and one that walks by neither standing, the model a walker for it is trained on.
Legs as parts
A leg may be a part of its own, of category leg, used as a Leg: a leg made or sold apart from the body it walks under, placed by glue on a Legged chassis that has no legs of its own. Such a chassis is a body: it states its gait, and no feet, and walks by the gait its controller states or by a walker trained for it on its legs; links of its own, a head or arms, are no legs, and a gait holds them where their servos hold them. Each leg states its links, on hinges its servos turn from its frame down, and the feet on them, as a chassis's own legs do, and neither a gait nor a controller: the chassis's controller, or its walker, walks all its legs together.
# parts/hip_leg/component.yaml, in part
category: leg
connectors:
mount: {pos: [0, 0, 0], dir: [0, 0, 1], up: [1, 0, 0]} # where it meets the body
profiles:
Leg: {mounts: {default: mount}}
links: # a thigh on a hip and a shin on a knee, both pitching
hip:
parent: null
joint: {type: revolute, xyz: [0, 0, 0], rpy: [0, 0, 0], axis: [0, 1, 0], range: [-1.5, 1.5]}
# its inertial, visual, collision and servo, as under Links
knee:
parent: hip
joint: {type: revolute, xyz: [0, 0, -0.15], rpy: [0, 0, 0], axis: [0, 1, 0], range: [-2.6, 0]}
# the same; its second collision shape is a ball at the shin's end
feet: # the ball the leg stands on
foot: {link: knee, shape: 1, friction: 0.8}Robot quad {
Legged body(ref = "quad_body")
Leg front_left(ref = "hip_leg", glue = (0.12, 0.08, -0.04, 0, 0, -1))
Leg front_right(ref = "hip_leg", glue = (0.12, -0.08, -0.04, 0, 0, -1))
Leg rear_left(ref = "hip_leg", glue = (-0.12, 0.08, -0.04, 0, 0, -1))
Leg rear_right(ref = "hip_leg", glue = (-0.12, -0.08, -0.04, 0, 0, -1))
}The robot stands on the feet of all its legs, each foot named by its leg in a finding, and every rule above takes them as it takes a chassis's own. assembly.legs_on_a_legged_chassis asks that every leg be placed on a Legged chassis with no legs of its own, hanging from no connector of it that steers, and that such a chassis carry legs: a leg on a base that rolls would hang from it, a chassis with legs of its own walks on them alone, as its walking controller was made for them, and a leg on a mount that steers, or on a part on that mount, would turn about a kingpin no servo turns. A leg on a part fixed to the chassis, a spacer, hangs as fixed as one on the chassis. A policy in a part's controller was trained on the legs it walks, the part's own, so a chassis with no feet of its own walks by a gait, or by a walker trained for it on its legs; a policy in its own controller is refused.
Walking controller
A Legged chassis may state the walking controller its legs walk by: a policy, an ONNX file in the part's directory, that a page or a simulator calls every so often with what it observes, and whose action sets the servos' targets; or a gait, which needs no training (below), the one a chassis whose legs are parts placed on it walks by. Without one the part loads and the analysis and the rules judge it as they judge any; the mjcf target writes a legged base only by its controller, or by a walker trained for it.
controller:
policy: assets/policy.onnx # an ONNX file in the part directory
period: 0.02 # s between two calls
imu: {link: null, xyz: [-0.016, -0.067, -0.006], rpy: [0, 0, 0]}
observation: [linear_velocity, angular_velocity, gravity, joint_angles,
joint_velocities, last_action, command]
action_scale: 0.5 # a servo's target: its hold plus this times the action
simulator: {timestep: 0.004, integrator: Euler, iterations: 1, ls_iterations: 5,
eulerdamp: false}
basis: MuJoCo Playground's go1_policy.onnx (Apache-2.0)| Field | Required | Meaning |
|---|---|---|
policy | yes | The policy, an ONNX file inside the part directory. The compiler copies it as it is and reads nothing of it |
period | yes | The time between two calls (s), a whole number of the simulator's steps |
imu | yes | Where the IMU is whose readings the policy observes: the link it is on, null for the part's frame, and its place xyz and turn rpy there |
observation | yes | What the policy observes, each term once, in its order: linear_velocity, the IMU's linear velocity in its own frame; angular_velocity, its angular velocity; gravity, straight down, (0, 0, -1), in its frame, the transpose of its turn applied to it; command, ahead, to the left and turning left; joint_angles, each servo's angle from its hold, in the order of the part's links; joint_velocities, their speeds; last_action, the action it gave last; and phase, the gait's phase, the cosine of each foot's and then their sines |
hidden | no | Links whose servos the policy reads as still: their angle and their speed read 0 |
action_scale | yes | Each servo's target is its hold plus this times the action's term for it (positive) |
phase | when observation lists phase, and only then | The gait's frequency (Hz) and each phase's offset, offsets (rad), at least one; with both still_below (positive) and still_at, each phase reads still_at while the command's length is under still_below, and goes on turning meanwhile |
simulator | yes | The simulator the policy was trained in, as MuJoCo's <option> states it: timestep (s), integrator (Euler, implicit or implicitfast: not RK4, since the model is stepped in two halves, mj_step1, the controls, then mj_step2, which integrates by Euler under RK4), the solver's iterations and ls_iterations (whole, from 1 to 2147483647, the most MuJoCo reads), and eulerdamp, whether Euler's integrator damps the joints implicitly |
basis | yes | Where the policy comes from |
The policy moves the chassis's servos, in the order of its links; the servos of the parts mounted on it hold where they hold.
A gait
The walking controller may instead be a gait, which needs no training: a page or a simulator works it out from the robot's own model as the robot walks. The legs, taken in turn about the chassis's middle, step every other one half a period apart. The feet on the floor carry the body level at its standing height and at the speed it is told, and each foot in the air lands where that speed needs it. The command it follows moves toward the one it is told by at most 1 m/s and 2 rad/s each second, so that told to stop at once from a run it slows over a moment; it goes on stepping until the robot has settled, moving and turning hardly at all at a period's workings of the gait in a row, and stops with every foot down. The output contract says how.
controller:
gait: {period: 0.4, duty: 0.55, step_height: 0.07}
basis: a gait from the part's own model, with no training| Field | Required | Meaning |
|---|---|---|
gait.period | yes | The time a foot takes to step and stand again (s, positive) |
gait.duty | yes | The part of the period each foot is on the floor: more than 0.5 and less than 1. Every foot is on the floor together for (duty - 0.5) times the period, twice a period, and the gait stops only then, once the robot has settled: at least 8 ms, two of the 4 ms the gait is worked out every. Each foot is in the air for (1 - duty) times the period, at least 40 ms, ten of them, to follow its arc |
gait.step_height | yes | How high a foot is lifted (m, positive) |
basis | yes | Where the gait's numbers come from |
A gait lifts every other leg, so the robot stands on an even number of feet, four or more: a robot on two feet is balanced otherwise, which this version does not do. Each foot steps by at least two servos between it and its part's frame, one to lift it and one to swing it, and by servos of its own: a servo above two feet, such as a spine's or a waist's, is not stepped by a gait in this version. The loader asks this of a chassis's own legs, and kinematics.gait_walks_the_feet of the robot's, the legs placed on it too. A leg whose servos only pitch steps fore and aft alone, so the robot turns by the strides of its sides, slowly; its gait states the turn it reaches. A gait balances the model as it is, with the parts mounted on the chassis, and moves the servos of the feet's legs; every other servo holds where it holds.
Walkers
A robot assembled from a Legged body and legs that are parts of their own walks by the gait its body states, which needs no training, or by a walker: a policy trained for exactly that robot, which the library registers beside its parts. A program whose Legged body and legs are a walker's walks by the walker, whatever its body states, and takes the command the walker takes; the mjcf target writes the walker's policy as the robot's drive. Its legs are the walker's when each is the same part as one of the walker's, placed within a millimetre of where the analysis places that one by its glue and turned within a milliradian of how it turns it, each leg one of the walker's and each of the walker's one leg, in whatever order the program declares them. Parts a program mounts besides are carried, a load the policy was not trained with. Two walkers of a library for the same robot are refused (assembly.one_walker): which one the robot walks by is not the program's to say.
# walkers/quad_walker/walker.yaml
schema_version: 1
id: quad_walker
description: the body on its four legs, walking by a policy trained for it
assembly:
body: quad_body # a Legged chassis part without legs of its own
limbs: # each leg on it, by a name of the walker's own
fl: {part: hip_leg, glue: [0.12, 0.08, -0.04, 0, 0, -1]}
fr: {part: hip_leg, glue: [0.12, -0.08, -0.04, 0, 0, -1]}
rl: {part: hip_leg, glue: [-0.12, 0.08, -0.04, 0, 0, -1]}
rr: {part: hip_leg, glue: [-0.12, -0.08, -0.04, 0, 0, -1]}
gait: {ahead: [-0.3, 0.6], aside: [-0.2, 0.2], turn: [-0.8, 0.8]}
controller:
policy: assets/policy.onnx
joints: [fl/hip, fl/knee, fr/hip, fr/knee, rl/hip, rl/knee, rr/hip, rr/knee]
period: 0.02
imu: {link: null, xyz: [0, 0, 0.02], rpy: [0, 0, 0]}
observation: [linear_velocity, angular_velocity, gravity, command, joint_angles,
joint_velocities, last_action]
action_scale: 0.25
simulator: {timestep: 0.004, integrator: Euler, iterations: 1, ls_iterations: 5,
eulerdamp: false}
basis: trained on the body on its four legs, in MuJoCo
provenance: {name: The body on its four legs, manufacturer: null, source_url: null, license: null}| Field | Required | Meaning |
|---|---|---|
assembly.body | yes | The body the walker was trained with: a chassis part of the library with a Legged profile and no legs of its own |
assembly.limbs | yes | Each leg placed on the body, by a name of the walker's own, any but body: its part, a leg of the library, and its glue, x, y and z, the unit normal nx, ny and nz and, when the program states one, the roll about it, as a program places it |
gait | yes | The command the policy takes, its ranges as a Legged chassis states them |
controller | yes | The policy, as a part's walking controller states it, and joints: every servo of the body and its legs, each once, as owner/link, the owner body or a limb's name, in the order the policy reads and moves them. hidden names servos as joints does, and the IMU's link is one of the body's links, or null for its frame |
provenance | yes | Where the walker comes from, as a part's |
A walker that does not state a robot of the library's parts, whose file gives a key twice, whose policy does not move every servo of it or has none to move, or that places two legs of one part by glues within twice that millimetre and milliradian of each other, where a leg placed between them would be either, is a diagnostic, and the robot does not walk by it; the parts still load.
Objects
An entry of category object is a thing a world places, a table, a shelf, a ramp, and not a part of a robot. A .world entity names it by ref. It states what is shown of it and what of it collides:
schema_version: 1
id: ramp_10deg # equals the directory name
category: object
description: A ramp 0.8 m wide that rises 10 degrees along +x over one metre, from the floor at its frame.
visual: # the mesh that is shown
file: assets/model.glb
xyz: [0, 0, 0]
rpy: [0, 0, 0]
collision: # the shapes it collides by; absent when it is only shown
- type: box # box | sphere | cylinder
size: [1.01543, 0.8, 0.05] # a slab, tilted so that its top face is the ramp's surface
origin_xyz: [0.50434, 0, 0.06354]
rpy: [0, -0.174533, 0]
provenance:
name: null
manufacturer: null
source_url: null
license: null| Field | Required | Meaning |
|---|---|---|
schema_version, id, category, description | yes | As for a part; category is object |
visual | yes | As for a part: the mesh a simulator shows, with where it sits in the object's frame |
collision | no | A list of at least one shape. Each states type (box, sphere or cylinder), size (three positive numbers: a box's sides; a sphere's diameter and a cylinder's in x, a cylinder's height in z, as a world entity's size reads), origin_xyz and rpy, its place and its fixed-axis roll, pitch and yaw in the object's frame. The targets collide the object by these shapes, the navigation map is drawn from them and a run is judged against them, each shape projected onto the ground where the entity stands. Without collision the object is only shown, and no entity that collides can be made of it |
provenance | no | As for a part, without a price: an object is not bought. Of an object whose mesh is a model someone else published, name is the model, manufacturer its author, source_url where it lies and license the licence it is passed on under |
An object collides by shapes and never by its mesh. The compiler opens no mesh, so it could neither draw one onto the map nor judge a run against it, and the two simulators would not agree on it: MuJoCo collides a mesh by its convex hull, which fills the space under a table, and reads no glTF.
Both simulators take a mesh's coordinates as they are, in metres with z up, after the transforms of the file's nodes; neither turns a glTF from its y-up convention. Gazebo decodes the mesh. MuJoCo decodes no glTF, so the ros-mujoco-nav2 target's node reads it: a .glb, or a .gltf with its buffers beside it or written into it, as every triangle of the file's default scene in its material's base colour (baseColorFactor) with the picture laid on it (baseColorTexture), without the material's other maps, the light it states it gives off (emissiveFactor), normals or vertex colours. It does not read a mesh that requires an extension (a compressed or quantized mesh), a sparse accessor, anything drawn as points, lines, strips or fans, a picture that is not a PNG, which is all MuJoCo decodes, or one moved or scaled where it is laid (KHR_texture_transform): the node stops as it starts and says which stands in the way. Of an object whose mesh is not glTF that target shows the shapes the object collides by, and it refuses one that only shows (target.unsupported), of which nothing would be in the scene.
An object states none of inertial, connectors, profiles, wheel, track, range, motor, battery, driver and contacts; each is library.unsupported on it. A world holds an object where the program places it, so it states no mass; nothing mates onto it, so it states no connectors; and whether the robot may touch it is the entity type's to say in the world that places it, so it states no profiles.
Environments
An entry of category environment is a place a .world scenario takes whole by ref: a room, a hall with its shelves. It has no mesh and no shape of its own; it states its floor and the entities that stand in it.
schema_version: 1
id: room_6x4 # equals the directory name; never `empty`
category: environment
description: A storeroom 6 m by 4 m with walls 0.5 m high, a work table in a corner and a mark on the floor where a robot docks.
ground_plane: true # whether it brings its floor
entities: # name -> what a .world entity states
wall_north: {entity: Obstacle, type: box, size: [6.2, 0.1, 0.5], pos: [2, 2.05, 0.25, 0, 0, 0, 1]}
table: {entity: Obstacle, ref: work_table, pos: [4.5, 1.1, 0, 0, 0, 0, 1]}
dock: {entity: Marker, ref: floor_disc, pos: [4.0, -1.0, 0, 0, 0, 0, 1]}| Field | Required | Meaning |
|---|---|---|
schema_version, id, category, description | yes | As for a part; category is environment. The id may not be empty, which a scenario writes for no environment |
ground_plane | yes | true or false: whether the environment brings its floor, which is the world's one ground plane. No scenario of a world that takes such an environment may add ground_plane = true; two environments that each bring a floor share the one |
entities | yes | A mapping, which may be empty, from an entity's name (an identifier) to its fields, below |
provenance | no | As for a part |
An entity's fields are the parameters of a .world entity, by their names and in their forms, so that the lines of a scenario move into a library unchanged in meaning:
| Field | Required | Meaning |
|---|---|---|
entity | yes | The entity type: Obstacle, SupportSurface or Marker. The vocabulary's, so it is resolved when a program takes the environment |
type | one of type and ref | A primitive: box, sphere or cylinder |
size | with type; not with ref | [x, y, z], positive, as a .world entity's |
ref | one of type and ref | The id of a library object. It may be another library's that the program is compiled with, so it is resolved when a program takes the environment |
pos | yes | [x, y, z, qx, qy, qz, qw] in the world's frame; the quaternion of unit length |
collision | no | true or false; without it the entity type decides, as in a .world file |
An environment's entities are static, like every entity, and state no is_static. A scenario takes them where they stand: it gives the environment no pose. Once taken they are the scenario's own entities, in the world's one namespace beside the program's.
Bus names
A bus name is the library's own: the compiler only compares a motor's bus with the buses of the driver that commands it. Name the wire and the protocol together (dynamixel_ttl, dynamixel_rs485), since two makers' servos on the same wire do not answer each other's boards.
Profiles
A profile is one component type the part can be used as, named exactly as the vocabulary names the type, and maps the logical mount names of that type to the part's connectors. The Mecanum_four_wheel profile of a frame maps front_left_wheel, ..., and middle_top; a wheel's or a sensor's profile maps default, the connector the part mates by, and so do a battery's and a motor driver's; a track's maps default, the hub that drives it, whose connector carries no joint, since the unit does not turn about its hub; a motor's maps default, the face it seats on a chassis' drive mount by, and shaft, the end of its output shaft, where a wheel mates. The shaft connector carries no joint: the wheel's connector carries the continuous joint, so the joint is between motor and wheel. Two logical mounts may not share a connector. Which types exist and which mounts each needs is vocabulary, decided by the semantic phase, not by this format.
A profile maps mounts and nothing else. Which mounts of a chassis type steer is vocabulary; which of them are driven is what the program places, a motor on the mount or on a seat whose driveline turns it, within what the type allows.
Diagnostics
Every problem is a diagnostic in the library_load phase that points into the file. A problem with library.yaml itself blocks: nothing can be loaded. A part with any problem in its declaration is left out of the loaded library, so later phases never see a half-checked record, and the diagnostic is a warning that does not block a program which never names the part; a program that names such a part gets a name-resolution error that repeats why the part could not be loaded. A shared catalog with a few incomplete declarations thus still serves every program that uses its complete parts.
| Code | Cause |
|---|---|
library.manifest_missing | No library.yaml at the given root (an environment failure: there is nothing to point at) |
library.yaml_syntax | The file is not valid YAML, or no tree that can be read: an alias inside the value its anchor names, values nested more than 64 levels deep, or aliases that expand it to more than 200,000 values. An alias otherwise stands for a copy of the value its anchor names |
library.duplicate_key | A key given twice; both occurrences are shown |
library.invalid_key | A key that is not a plain name |
library.unknown_field | A key the schema does not list; the message lists the allowed ones |
library.missing_field | A required key is absent, or a link that turns on a revolute joint has no servo, or a wheel has no wheel block, or a part with a Range profile no range block, or one with a Motor profile no motor block or no shaft mount, or one with a Battery profile no battery block, or one with a Track profile no track block, or one with a MotorDriver profile no driver block, neither channels nor buses in it, or channels without their supply range and continuous current, or a part states one end of a voltage range without the other, or an estimate has no basis, or a chassis used as Legged states no gait, or feet without links, or a leg states no links or no feet, or a foot no link, shape or friction, or a gait no ahead, aside or turn, or a controller states neither a policy nor a gait, or a policy no period, imu, observation, action_scale, simulator or basis, or no phase while its observation lists the phase, or its phase one of still_below and still_at without the other, or a gait no period, duty or step_height, or no basis |
library.wrong_type | A value of the wrong shape: not a mapping, not a number, not [x, y, z], an object's or a link's collision that is not a list of shapes or is an empty one, links that name no link, a link joint's range that is not two numbers, feet that name no foot or a foot that is not a mapping, a foot's link that is not a name or its shape or friction not a number, a gait's range that is not two numbers, a controller's observation that names no term, a phase's offsets that give none, a simulator's eulerdamp that is not true or false, ... |
library.invalid_value | A value of the right shape but impossible: a number other than 0 nearer zero than 1e-100 or larger than 1e30, zero mass, a negative radius, an inertia tensor that is not positive definite or whose principal moments violate the triangle inequality by more than the rounding of its decimals, an unknown category, an unknown joint type, a driveline that names a connector twice or turns a motor's seat, a track whose run on the ground has no length, a visual.file that lies beside the declaration and not in a directory of its own, a shape of an object's collision that is none of box, sphere and cylinder, an environment named empty or one of its entities with both or neither of type and ref, a path that escapes the part directory or is not written plainly with / (./, a backslash, an empty segment), a file that does, or a file beside a mesh, that links out of the part directory, a hull with fewer than four corners, a face that is not a triangle or names a corner the hull does not have, a hull that is not convex, a hull whose triangles leave an edge open, put a third triangle on an edge, repeat a triangle or a corner of one, or enclose no volume, a link's name that is not an identifier, a link's parent that is no link declared above it, a link joint that is neither revolute nor fixed or whose range does not run from a lower angle to a higher one, a link's shape that is none of box, sphere, cylinder and capsule, a servo's kp or effort that is not positive, its damping, armature or frictionloss negative, or its hold outside its joint's range, a part used as Legged that is not a chassis, a foot's name that is not an identifier, a foot on a link the part does not have or on a shape its link does not have (counted from 0, a whole number), two feet on one shape, a foot's friction that is not positive, a gait's range that runs from a higher speed to a lower one or leaves out standing still, a controller's period that is no whole number of its simulator's steps, an observation term that is none of the eight or is named twice, a hidden link that no servo turns or that is named twice, an IMU on a link the part does not have, a phase its observation does not list, a policy on a part none of whose links has a servo, an integrator other than Euler, implicit and implicitfast, iterations that are not whole numbers from 1 to 2147483647, or a period, an action_scale, a frequency, a still_below or a timestep that is not positive |
library.not_unit_vector | A dir, up, or joint axis, a link joint's among them, that is not unit length (tolerance 0.001), or the quaternion of an environment entity's pos |
library.unsupported | A field that exists but is not valid for this part: collision on a wheel, wheel on a sensor, range on a part that is not a range finder, motor on a part that is not a motor, battery on a part that is not a battery, driver on a part that is not a motor driver, track on a part that is not a track, a joint on a track's connector, a channel's current on a driver without channels, steering or driveline outside a chassis, an axis on a fixed joint, a box's size or origin_xyz on a hull, a hull's vertices or faces on a box, a part's field (inertial, connectors, profiles, ...) on an object or an environment, size beside an environment entity's ref, links on a part that is not a chassis, an attachment or a leg, an axis or a range on a fixed link joint, a servo on a fixed one, feet on a part that is neither used as Legged nor a leg, a gait or a controller on a part whose profiles do not list Legged or on a leg, a controller that is a policy on a chassis with no feet of its own, a foot on a cylinder |
library.asset_missing | visual.file or a controller's policy names a file that does not exist |
library.id_mismatch | id differs from the directory name |
library.unknown_connector | A mount names a connector the part does not declare, or a driveline does |
library.duplicate_part, library.part_missing | A part registered twice, or registered without a declaration |
Not in this version
These are needed and not yet accepted; a declaration that contains them is rejected rather than silently ignored: a field for them with library.unknown_field, a link joint that slides or a link whose parent is declared after it with library.invalid_value.
- A connector on one of a part's links (a camera on a pan-tilt's head): a part's connectors are in its own frame. A link joint that slides, and links that close a loop, such as a leg moved through a linkage.
- Shared interface types (
interfaces/) that make two connectors compatible by declaration rather than by name. - The frame of a sensor part follows REP 103: it looks along its own +x axis, so a mount whose
upis the chassis' +x makes a camera look forward. - Sensor specifications (field of view, range, rate, noise), except a range finder's
rangeblock, and a steering actuator's limits. The target still fills these in from its own defaults. - Two motors on one driveline that are geared to each other, a driveline's losses, and how a differential shares torque when it is locked.
- A track on a steered mount, flippers, and a belt's own links and tension.
- Source tags on values other than the inertial.
- Assemblies: a part made of other parts, such as a kit, that a program places as one. A walker names the parts it was trained with, which a program places one by one.
- An object that moves, is pushed or opens, an object's scale, and an object that collides by its mesh.
- An environment placed at a pose or taken twice, one built of other environments, and goals or regions an environment names beside its entities.
Changes from the previous compiler's part.yaml
The facts are the same; the file moved and a few names changed. A converter belongs to the library repository.
| Was | Is | Why |
|---|---|---|
<category>/<id>/part.yaml, JSON text | parts/<id>/component.yaml, YAML | the library repository's layout; category is a field |
model | visual | it is the visual model; collision and inertial are separate |
geometry: {radius, height} | wheel: {radius, width} | says what it is |
top-level joint | connectors.<name>.joint | the joint belongs to the mating point that creates it |
pos_local, dir_local, up_local | pos, dir, up | every vector is in the part frame; the suffix said nothing |
estimate_basis | basis | |
images, catalog | not read | presentation and catalog data; the compiler no longer fails a compile because a preview image is missing |
From the previous format
The component library repository declared its parts for the previous compiler as <category>/<id>/part.yaml (schema_version: 3). tools/convert-library SOURCE DESTINATION writes such a checkout in this format: model becomes visual with the mesh copied under assets/, pos_local, dir_local and up_local lose their suffix, a wheel's geometry becomes wheel (its height is the width), the part's joint moves onto its connector, and estimate_basis becomes basis. Each profile is renamed to the component type it is (laser to Lidar, diff_four_wheel to Diff_drive, the arm, battery and circuit kinds to Attachment), and what the compiler never reads (images, catalog) is dropped. The converter invents nothing: a placeholder inertial without a basis, or a steered mount whose connector declares no kingpin, stays as it was and the loader reports it on the result. The report of the conversion names every value it dropped or could not carry.