Detail physics answers one question: does this one operation work mechanically? A gripper that holds a part by contact and can let it slip. A part that jams in a fixture. A free body that gets shoved out of place. Cases whose answer depends on friction, on contact stiffness and on the force an axis can really produce.
It is not the tool for a general virtual commissioning run. Material flow stays where it is: conveyors, the part stream and dense accumulation keep running on kinematic transport, on Physics (Rapier) and in 2D interaction zones. Rapier and Box2D own the material flow; detail physics owns the detail areas.
The split is a cost decision, not a matter of taste. MuJoCo’s effort grows with the number of simultaneous contacts, not with the number of parts — and it grows most where contacts are permanent rather than passing. A walking robot’s feet touch and let go again; a part wedged in a fixture keeps every one of its contacts alive for as long as it sits there. Running a whole station this way buys computing time for information nobody asked for, which is why detail physics is always bounded by a region instead of being a switch for the scene.
So: if your question is cycle time, part flow, accumulation or a PLC program, you are on the wrong page — see Multi-Model Simulation. If your question is whether these jaws hold that part, read on.
The region
Section titled “The region”A MuJoCo Region marks the part of the model that is simulated in detail. Add it in the Editor to the node that holds everything belonging to the operation — the robot, the gripper, the fixture, the loose parts.
What the region simulates is its subtree in the hierarchy. Its box volume is a separate thing: it states where parts would change engine, and it is what the gizmo draws.
| Property | Description |
|---|---|
| RegionEnabled | On by default. Switch it off to keep the authored region without simulating it; a disabled region loads no MuJoCo runtime at all. |
| TimestepS | Integration step in seconds, default 0.002. The region runs as many physics steps per frame as fit into the frame, capped at 16. Below about 1 ms per step the region therefore falls behind real time and runs in slow motion. If a trained policy is in the region, this must match its training step. |
| ShowGizmo | On by default. Draws the region volume as a violet wireframe box — distinct from the cyan 3D physics zone and the orange 2D zone. Turn it off once the placement is right. |
The volume comes from a Box Collider on the same node, exactly as for a physics zone or a 2D interaction zone, so there is no second place that states it. Without a Box Collider the region falls back to the node’s own bounds.
The region also carries fields that are authored but not yet acted on: ExitBandM and LoadTreeMode belong to the engine handover at the boundary, and MjcfSource, MeshBase, Contract, TrunkBody, TrunkFreeJoint and GyroSensor to a hand-maintained physics model. See What this does not do yet.
It loads on demand. The detail-physics runtime, including its WebAssembly payload, is fetched the first time a loaded model declares a region. A model without one never pays for it.
Collision geometry — the one rule to internalise
Section titled “Collision geometry — the one rule to internalise”MuJoCo turns every collision mesh into its convex hull, and a convex hull has no hole. A board with a bore, convexified, is a board without a bore: a peg pushed at it stops on the surface and nothing in the simulation is wrong about that. Whatever has to behave as concave must be described as an arrangement of convex primitives.
mjcf-mesh-convexified — but only you know whether that concavity was load-bearing.A Collision Shape Set holds that arrangement.
| Property | Description |
|---|---|
| Shapes | The convex primitives in author order. Each entry has a Kind (box, sphere, capsule, cylinder, ellipsoid), a Center in millimetres, a Size in millimetres and a Rotation in degrees, all in the node’s local frame. Add, remove, duplicate and reorder entries in the inspector. |
| ReplacesMesh | Authored but not read. A Collision Shape Set with at least one shape is already the node’s complete collision geometry: the node’s own collider is then not written at all, whatever this flag says. Emitting the hull alongside the primitives would fill exactly the opening the primitives keep open, so there is nothing for the flag to switch. |
Size is read as half extents, and how many of its three numbers count depends on the kind: a box reads three half extents, a sphere one radius, a capsule and a cylinder a radius and a half length, an ellipsoid three radii. This is deliberately not the convention of the Box Collider next door, whose size is a full size in metres. A size component that is zero or negative is refused and reported as mjcf-shape-degenerate rather than quietly grown.
Two things make a decomposition cheaper than it looks. Primitives of the same body never collide with each other, so a decomposition needs no exclusions. And the contact class comes from the contact role, which keeps the parts of a decomposition that only stand on the floor from each generating a floor contact: a measurement on a segmented plate produced up to 112 contacts from 28 boxes lying on one plane, where the same plate standing on a single underside box has the four contacts a flat plate really has.
The role is a property of the node, not of the single primitive. Every primitive of a Collision Shape Set inherits the one profile resolved on its node, so a set cannot hold a grippy pad and a plain housing side by side. If one surface has to play a different role, give it a node of its own with its own Contact Profile.
Contact roles, and why Priority is not optional
Section titled “Contact roles, and why Priority is not optional”A Contact Profile gives a surface a named role instead of a loose set of numbers. The numbers behind the roles come from a profile library that ships with the viewer.
| Property | Description |
|---|---|
| Profile | The role this surface plays: Pallet, Workpiece (the default), BeltSurface, StopperFace, GripperJaw, MachineFrame, FixtureBore or Custom. Every role but Custom takes its numbers from the library. |
| Priority | Default 0. The higher-priority side of a contact supplies friction and compliance for the whole pairing. |
| Solmix | Default 1. Weight when the two sides’ contact compliance is averaged. No effect on friction. |
| FrictionSlide / FrictionSpin / FrictionRoll | Sliding, torsional and rolling friction coefficients (1, 0.005, 0.0001). Read only with Profile set to Custom. Spin friction needs CondDim SlidingTorsional or above, rolling friction needs Full. |
| CondDim | How many dimensions a contact has: Frictionless, Sliding, SlidingTorsional (the default) or Full. |
| SolrefTimeConst / SolrefDampRatio | Time constant (0.02 s) and damping ratio (1) of the contact’s reference spring-damper — how soft the contact is and how it settles. |
Read this before you pick a role. MuJoCo maximises friction element-wise between two surfaces: the rougher side always wins, so a pairing cannot be expressed by two independent entries, and one grippy pad would pull every other pairing up with it. The expressible form is Priority on the side that sets the tone. The library gives priority 1 to the roles that should set it — GripperJaw, FixtureBore and StopperFace — and 0 to everything else. If you choose one of those roles and leave Priority at 0, you silently get a pad that does not behave like a pad. That case is reported as mjcf-profile-priority-not-applied; the pairing itself still runs.
CondDim defaults to SlidingTorsional on purpose, not to MuJoCo’s own value, which has no spin friction. Measured: without spin friction a gripped part rotated out of the jaws within five seconds and was caught again elsewhere — a successful run that turned into a jam with nothing reported.
What the jaws can hold is the product of force and friction — and the friction is the half that usually goes undeclared. Holding capacity scales with both: for a 15 mm two-finger gripper, a force-closure calculation gives 0.51 kg at a friction coefficient of 0.1 and 3.57 kg at 0.7, at the same clamping force. That is a factor of seven from the pad alone, which is why the profile is worth checking before the force is raised — not because the force does not matter.
The library numbers are declared, not measured. Each entry states its own provenance, and today they are plausible, reproducible starting assumptions — they are not datasheet values and must not be quoted as such. A surface without any profile is reported as mjcf-geom-no-profile, because then nothing has declared a friction.
Free bodies and parts you move by hand
Section titled “Free bodies and parts you move by hand”A Free Body is a body the simulation moves: a block, a pallet, an obstacle, the workpiece itself. Each tick its pose is written back onto the scene node, so what you see is where the physics put it.
| Property | Description |
|---|---|
| Enabled | On by default. Switch it off to keep the declaration without the body. |
| MassKg | Mass in kilograms. 0 means derive it from the node’s mechanism body data. A moving body with no mass at all is refused by MuJoCo and reported as mjcf-body-no-mass; a coarser mass source than the authored one is reported as mjcf-mass-degraded. |
Enabled and MassKg are the whole component today. CondDim, FrictionSlide, LinearDamping, AngularDamping and Restitution are authored but not read: the contact behaviour of a free body comes from the Contact Profile resolved on its node, exactly as for every other surface. Put the friction and the contact dimensionality there, not here. See What this does not do yet.
A Movable declares that the user may move a node, where a Free Body declares that the physics may. Both can sit on the same node: a block you place by hand and the simulation then pushes around.
| Property | Description |
|---|---|
| Enabled | On by default. |
| Axes | Which directions a drag may change: XZ (the floor plane, default), XYZ, X, Y or Z. |
| SnapMm | Grid the drag snaps to, in millimetres. 0 is free movement. |
| ResetOnStop | Authored but not acted on. The component captures the authored position and can return to it, but nothing in the viewer asks it to on a simulation stop. |
Dragging a Movable with the mouse is not wired up in the viewer yet, so the whole component is a declaration today — see What this does not do yet.
Dynamic axes: an axis with a force limit
Section titled “Dynamic axes: an axis with a force limit”Put a Dynamic Axis on the node of a Drive and the drive stops being a kinematic setter. Its setpoint becomes the command of a force-limited actuator, the solver moves the body, and the joint position that comes out is written back as the drive’s actual position. A kinematic axis has no following error by construction. A dynamic one has, and on a real machine that error is the overload signal.
The axis is not switched off and no second motion path appears: whatever wrote the setpoint before — an IK path, a bound PLC signal, a logic step — keeps writing it. The travel limits also stay where they are: the actuator’s command range follows Drive → LowerLimit / UpperLimit and is not a field here.
| Property | Description |
|---|---|
| SetpointSource | A declaration of intent, not a switch. It does not select who writes the setpoint: the actuator always follows the drive’s commanded position, whoever wrote it. Its one effect is a load-time check — policy on an axis that a path planner also commands is reported as axis-role-conflict. operator is reserved for manual jogging, which does not exist yet. |
| ActuatorKind | position (the default), velocity or motor. |
| Kp / Kv | Proportional and derivative gain of the servo (80000 and 0). |
| ForceRangeMin / ForceRangeMax | Force or torque limit of the actuator. Both 0 means unlimited, which is reported as mjcf-axis-no-force-limit: an axis that cannot saturate cannot refuse, and this is the one number the overload report and the grip force both hang on. |
| Gear | Authored but not read. A transmission ratio is expressed by the separate Drive_Gear component, which is the implemented route and couples one setpoint to several joints. |
| Armature | Added joint inertia, default 0.1. A declared starting assumption, and the right knob when a stiff servo needs a smaller step — raise this before lowering TimestepS. |
| Damping / FrictionLoss | Viscous joint damping, and a constant friction force to overcome before the joint moves. Both 0. |
| BaseFollowingError | The structural share of the following error that the overload report subtracts. In the drive’s own unit. See below. |
| FollowingErrorLimit | Window on the net error, i.e. after the base share is subtracted. 0 switches monitoring off. |
| FollowingErrorTimeMs | How long the net error must stay outside the window before the axis is reported, default 200 ms. |
| ArrivalTolerance | Arrival tolerance for path and logic-step waits. 0 derives it from the base share plus the window, never below 0.01. |
Why there is a base following error at all
Section titled “Why there is a base following error at all”MuJoCo’s position actuator is a P+D controller without an integral term. A permanent gravity sag of torque over Kp therefore remains even with no contact at all, and the axis never gets any closer to its setpoint than that. Compare the raw error against a window and the viewer reports permanent overload — the servo working exactly as designed, filling the Problems panel.
Declare the sag in BaseFollowingError and only what exceeds it is reported. Measured on a 39 kg six-axis arm: 1.07° of axis sag at the stock gain of a public robot model, and 0.10° at the Kp this component defaults to.
Leaving the window is a report, not a trip. The Overload signal goes true and a Problems entry appears that names the net error, the raw error, the base share, the window and the actuator force. The motion keeps running.
Grip load: the jam shows up at the gripper
Section titled “Grip load: the jam shows up at the gripper”Put a Grip Load on the node of a Grip to declare how it holds a part — and to measure whether it still does.
| Property | Description |
|---|---|
| Mode | formfit (the default) parents the part to the jaws, as gripping always has. The part then cannot slip, and PartSlip reads 0 — not because nothing slipped, but because nothing was measured. Only forcefit lets the physics contact hold the part, and only there is the slip a real measurement. |
| GripForce | Declared clamping force per jaw in newtons. 0 means not declared, and only the measured actuator force is shown. |
| SlipLimit | How far the part may travel inside the jaws, in millimetres, before it counts as slipping. 0 switches the check off. |
| SlipTimeMs | How long the slip must stay above the limit before it is reported, default 200 ms. |
Slip is measured in the jaw frame, since the grasp began, not in world space — the jaws are moving, after all. In formfit the measurement does not happen at all, so a zero there carries no information: read it only in forcefit.
This is where you look first, and that is a measured result, not a preference. Three failure cases of one joining attempt were measured against a successful run:
| Case | Axis following error | Part slip in the jaws |
|---|---|---|
| Wrong rotation | 0.07–0.08°, as in the good run | 20.0 mm |
| Wrong shape | 0.07–0.08°, as in the good run | 20.3 mm |
| Skew push | distinguishable; one axis saturated 71 % of the time | 16.7 mm |
In two of three cases the axes behaved exactly as in the run that worked, while the part slid 17 to 20 mm. An overload model that knows only following error and torque sees nothing there. The reason is mechanical: a two-jaw gripper transmits at most twice force times friction along the joining axis — about 50 N for the catalogue gripper used — while the arm pushes with over 100 N at the tool. The part gives way before the axis does.
What you read out
Section titled “What you read out”Every quantity leaves the simulation as a signal, so it is visible in the signal list, forceable, recordable, and readable from a controller over realvirtual CONNECT. They are PLC inputs: the viewer writes them, a controller reads them, exactly as a real drive does with its following error. Each one sits in the Signals folder of its component root, prefixed with the node’s name so six axes of one robot do not collide.
| Signal | What it carries |
|---|---|
<Axis>.FollowingError | The net following error, in the drive’s own unit — the raw error minus BaseFollowingError. It is the same number the overload window is compared against, so with a base share declared this signal does not carry the raw error. |
<Axis>.IsTorque | The force or torque the actuator is currently producing — newtons on a linear axis, newton-metres on a rotary one. |
<Axis>.Saturated | True while the actuator sits at its force limit. |
<Axis>.Overload | True while the net following error is outside its window. |
<Grip>.PartSlip | How far the held part has travelled inside the jaws, in millimetres. Always 0 in formfit, where nothing is measured. |
<Grip>.GripForce | The clamping force at the jaws, in newtons. |
<Grip>.PartSlipping | True while the slip is above its limit. |
Nothing has to be bound for this to work. An unbound signal is still a signal.
The Problems panel is the second output, and every entry names the node it belongs to. The ones you will meet most:
| Entry | What it means |
|---|---|
mujoco-region-failed | The region’s physics model would not load. The region is out; the other regions of the model keep running. |
mjcf-mesh-convexified | A collision mesh was replaced by its convex hull. Decide whether that concavity mattered. |
mjcf-shape-degenerate | A collision primitive has a zero or negative extent and was refused. |
mjcf-geom-no-profile | A surface has no Contact Profile, so nothing declared its friction. |
mjcf-profile-priority-not-applied | A role that should set the tone of its pairings carries Priority 0. |
mjcf-axis-no-force-limit | A Dynamic Axis has no force limit, so it cannot saturate and cannot refuse. |
mjcf-body-no-mass / mjcf-body-no-geometry / mjcf-mass-degraded | A body has no mass, no collision geometry, or a coarser mass source than the authored one. |
axis-overload | The net following error left its window. A report; the motion continues. |
grip-slip | The held part is slipping in the jaws. |
When a region is the wrong tool
Section titled “When a region is the wrong tool”Cost grows with the number of simultaneous contacts, not with the number of parts, so there is no figure per body that would mean anything. A few bodies in permanent contact can cost more than many that only touch in passing — which also means the cost of a region is a property of your model, not of the component. Measure the region on the hardware you will run it on, with the frame budget you have.
Four signs that a detail region is not the right answer:
- The region has to grow to enclose a contact. Every contact across the boundary forces an extension. A region that ends up covering half the scene is not a region.
- Many bodies are in permanent contact with each other. That is a 2D zone’s job: seen from above, conveyed goods are a 2D problem anyway, and Box2D carries dense flow at a fraction of the cost of 3D contact.
- The parameters are not obtainable. A region with guessed friction and guessed contact stiffness produces a picture, not a statement — and a picture is cheaper to produce kinematically.
- A step below 1 ms seems necessary. That happens with stiff servos and no Armature. Get the armature value first; do not lower the step.
What this does not do yet
Section titled “What this does not do yet”- Parts do not cross the region boundary. There is no handover between conveyor transport and a detail region: a part is not taken over at the box and not given back. What a region simulates is what is authored inside it.
ExitBandMandLoadTreeModeare the authored half of that handover and currently have no effect. - Inserting a part into a fixture is not a working example. In the current measurement the jaws clamp at 42 N, carry the part about 180 mm and then lose it. Holding and slipping are measurable today; completing a fit is not.
- Dragging a Movable by hand is not wired up. The component declares who may move the node, along which axes and on which grid, and the physics-side Free Body is what actually moves today.
ResetOnStopbelongs to that unwired half: the authored position is captured, but nothing restores it on a stop. - Several authored fields have no consumer yet. They load, validate and save, and nothing reads them:
ReplacesMeshon a Collision Shape Set,Gearon a Dynamic Axis (use Drive_Gear), andCondDim,FrictionSlide,LinearDamping,AngularDampingandRestitutionon a Free Body (use a Contact Profile).SetpointSourceis read only for the load-time role check, not to choose who writes the setpoint. - There is no manual mode. No cartesian jogging, and
SetpointSourceoperator is reserved rather than implemented. - Nothing trips. An overload or a slip is reported and the motion path keeps running. Stopping on it is your logic’s decision.
- The friction and compliance numbers are declared. They are reproducible starting assumptions with stated provenance, not measurements of your surfaces.
- A hand-maintained physics model cannot be supplied.
MjcfSourceandMeshBaseare authored but not read; the model is always generated from the components.
See also
Section titled “See also”- Physics — Moving Parts and Payload — the rigid-body physics that owns the material flow, and the payload it reports to drive sizing
- 2D Interaction Zones — dense flow, jams, buffer tables, diverters and merges
- Multi-Model Simulation — which simulation approach answers which question, and how they combine
- Drives — the drive a Dynamic Axis is attached to
- Gripping — the Grip a Grip Load is attached to
- Rigid-Body Mechanisms — the joint graph and the mass data a Free Body derives from
- rv-ODT specification — every field of every component, with bounds and units