Robot simulation, explained

Robot models,without the folklore.

Ten questions we are asked in almost every first conversation about getting a robot into simulation — what URDF cannot say, why three engines disagree about the same file, and what it takes to review a model the way you review code.

What is the difference between URDF and MJCF, and when does it matter?

Both are XML descriptions of a robot, and that is where the similarity stops. URDF is the ROS robot description: a tree of links and joints carrying mass, inertia, visual and collision geometry, joint limits, and a little damping and friction. It describes structure. MJCF is MuJoCo’s native format, and it describes a whole simulation — bodies and joints, but also contact parameters, equality constraints, actuators with gear ratios, tendons, sites, sensors, default classes and solver settings.

The difference matters the moment your model has to say something URDF has no word for. A four-bar linkage. A tendon-driven finger. An actuator with a real torque limit and reflected inertia rather than an abstract effort number. Contact behavior you want reproduced next month.

It also matters in the other direction. URDF is what the ecosystem consumes — RViz, MoveIt, KDL, Foxglove, most ROS 2 packages — so you cannot stop producing it. The practical rule is to make the lossy conversion run one way: author the richer model, then emit URDF from it. You can always drop information you have. You can never recover information you never wrote down.

CadSyn keeps an MJCF-first core for that reason, and every URDF export carries a degradation report naming what was dropped and why.

Why can’t URDF express closed kinematic chains — and what do humanoid teams do about it?

URDF’s data model is a tree. Every joint names one parent link and one child link, and every link may be a child exactly once. That rule is what makes the format cheap to parse and lets a solver walk the structure recursively from the root. It also makes a loop unrepresentable: close a four-bar knee and the last link needs a second parent, and the schema has no element that says “these two frames are coincident.”

This is not a niche complaint. In the 510-respondent survey of URDF users by Tola and Corke, the inability to model parallel linkages and closed-chain systems came back as the top-reported limitation, alongside the absence of a real standard, thin documentation and too few dynamic parameters.

Three workarounds are in common use, and every humanoid team has picked one:

  • Cut the loop. Replace the linkage with a single revolute joint and fit an effective transmission ratio. Fast, and wrong under load — it discards the mechanical advantage that changes across the range of motion.
  • Close it per engine. A MuJoCo equality constraint added after import, an SDF joint for Gazebo, a loop closure authored in the USD stage for Isaac. Three files, three behaviors, and an argument about which one is the reference.
  • Keep two models. A URDF for ROS and the real one somewhere else. They drift within a revision.

The loop belongs in the source of truth, stated once. CadSyn expresses it as an equality constraint in the MJCF core, and the URDF export says what it lost.

How do you get a robot model from SolidWorks or Onshape into simulation?

Four things have to come out of CAD: which assembly nodes become links, which mates become joints, the mass properties, and the meshes. Everything after that is bookkeeping.

From SolidWorks, the well-worn path is the sw2urdf exporter, and two of its habits cost people days. It expects the subassembly structure to mirror the link structure exactly, so a tidy CAD tree and a valid kinematic tree are not the same tree.

And it bakes the assembly’s current pose into the joint origins — if someone dragged a component before exporting, your zero pose is quietly wrong. Author joint axes and origins as reference geometry first, and export from a known pose.

From Onshape, onshape-to-robot reads the document through the API and takes its mapping from named mates. It is the more reproducible path, because the document is versioned in the cloud and the export can be re-run against a specific version rather than whatever happened to be open.

Carry these across rather than re-deriving them: inertia tensors about each link’s center of mass from CAD mass properties, with real material densities assigned — default-material inertia is the most common silent error; joint limits from the mates; a decimated visual mesh; and a separate collision mesh.

Doing this once is a day’s work. Doing it every time the hardware revs is a job. CadSyn maps mates to joints once, then re-syncs on document version and returns the change as a diff.

Why does the same robot behave differently in MuJoCo, Isaac Sim and Gazebo?

Because a robot description is not a simulation. It states bodies, joints, geometry and inertia. Everything about how contact and constraints are actually solved lives in the engine, and the three make different choices.

  • Contact model. MuJoCo solves soft, convex contacts parameterized by solref and solimp. Isaac Sim runs PhysX with rigid contacts and a TGS solver. Gazebo Classic defaults to ODE and an LCP formulation with its own ERP and CFM; current Gazebo defaults to DART. The same friction coefficient does not produce the same slip.
  • Timestep and integrator. A stiff implicit integrator at a small fixed step and a substepped explicit one at a larger step disagree about damping. A value that is stable in one is a spring in the other.
  • Defaults. Whatever the file did not say, the engine fills in: armature and reflected inertia, joint friction, contact softness, solver iterations, self-collision filtering. Most divergence lives in the fields nobody wrote.
  • Import behavior. Articulation roots, merged fixed links, and unit and up-axis conventions each shift the model before physics runs at all — see the next question.

The fix is not to make the engines agree. It is to measure the disagreement on purpose.

Pin a handful of reproducible fixtures — drop from a fixed height and record settle time and final pose, hold a pose against gravity and record steady-state torque, sweep an actuator and record tracking error — and run them on every engine on every revision. Divergence becomes a number you watch instead of a surprise you meet mid-training-run. CadSyn runs those packs in MuJoCo, Isaac and Gazebo and reports where they part company.

What breaks when a URDF is imported — and how do you diagnose it?

Import is a translation, and translations lose things. Six failures account for most of the lost afternoons.

  • Articulation root in the wrong place. The importer picks a root body you did not intend, so a fixed base floats or a floating base is pinned. The robot sinks, or refuses to fall. Print the tree the importer built, not the one you wrote.
  • Package paths that do not resolve. Outside a ROS workspace there is nothing to resolve package:// against. A mesh goes missing and the link becomes an invisible point mass. Resolve every mesh URI before import.
  • Silently merged fixed links. Engines collapse fixed joints into the parent body for speed, and the frames you referenced by link name — sensor mounts, tool frames — stop existing. Compare body count after import against link count before.
  • Dropped properties. Mimic joints, transmissions, joint friction, safety controllers: parsed by ROS, ignored by the importer. The symptom is a gripper whose second finger never moves.
  • Millimeter and meter mixups. CAD exports millimeters, URDF is meters, and a scale factor lands on the visual mesh but not the collision mesh. Check the bounding box of every mesh, and the total mass against the real thing.
  • Identity inertia tensors. An exporter without mass properties writes unit mass and a unit diagonal. Nothing crashes; everything is wrong. Compare each link’s inertia against a solid box of the same bounding box and mass, and check the triangle inequality on the principal moments.

The general method: import, dump the engine’s own view of the model back out, and diff it against what you wrote. CadSyn scores these on every compile and keeps per-engine import profiles so the export lands right the first time.

How should collision geometry be generated from CAD meshes?

Start from the rule the whole answer hangs on: the visual mesh is not the collision mesh. A CAD export carries fillets, fastener heads and cable channels across a few hundred thousand triangles, and it is often neither watertight nor free of self-intersections. Every one of those triangles is a candidate contact.

  • Primitives first. A capsule for a link, a box for a base, a sphere for a fingertip. Analytic contact is faster and better conditioned than any mesh, and most links need nothing more.
  • Convex pieces where shape matters. Engines collide convex shapes quickly and stably. A concave mesh is either decomposed for you, unpredictably, or treated as a triangle soup with no interior — which is how things tunnel.
  • Decompose deliberately. V-HACD was the standard; CoACD tends to produce fewer, better-placed parts at the same fidelity. Budget the hulls — a dozen or two per link is usually plenty, and hundreds means a step was skipped.
  • Decimate and strip before decomposing, never after. Remove internal geometry, fasteners and anything inside the outer shell. Decomposition cost scales with what you feed it.
  • Know your margin. Contact offsets and a slightly inflated or shrunk hull change grasp and foot behavior. Choose the sign on purpose.

Then author the self-collision matrix. Adjacent links always touch, and if those pairs are not excluded the solver spends its budget on contacts that can never separate. CadSyn runs decomposition, decimation and inertia from mass properties on every compile, and exclusion pairs are edited visually rather than by hand in XML.

Are Xacro macros the right way to handle robot variants?

Xacro is a macro preprocessor for XML: properties, arithmetic, includes, conditionals and macros that expand to URDF. It solved a real problem, because URDF cannot express repetition and a quadruped otherwise means four hand-edited copies of the same leg with the signs flipped.

It is still the right tool for repetition inside one robot — one leg written once, a sensor mast wrapped as a reusable block, a dimension that appears in twenty places.

It is the wrong tool for variants. Once arm length, end effector and sensor loadout are all arguments, the file stops being a description and becomes a program whose output nobody reads. You cannot diff two variants; you can only diff the generator that makes them.

Errors surface at expansion time against generated line numbers. Conditionals nest until the only way to know what a configuration contains is to expand it and look. The community has documented this as a chronic source of confusion, and it is.

If you are staying with it, the discipline that helps: keep macro bodies short, never compute in a macro what CAD already knows, commit the expanded URDF alongside the xacro so reviews have something readable, and expand and load every variant in CI.

The alternative is to treat variants as data rather than text substitution — named configurations as first-class model fields, with shared subassemblies as components you instance. CadSyn does that, so two variants diff structurally instead of textually.

How do you version and review a robot description the way you review code?

Most of it is available today, with no new tool, if you fix four things.

  • Keep the meshes out of the diff. Git LFS, or a content-addressed asset store the description references by hash. A repository that gains a fresh set of STLs every revision stops being reviewable within a quarter.
  • Emit deterministic XML. Stable element order, fixed float precision, one attribute per line. Most exporters reorder and reformat, so a one-joint change arrives as a whole-file diff and the reviewer approves it unread. A canonicalizing formatter on pre-commit is the highest-leverage hour on this list.
  • Gate on CI. Does it parse, does it load in the engine you train in, did total mass move more than a threshold, do the inertia tensors satisfy the triangle inequality, does a settle test land in the same pose within tolerance.
  • Attach evidence, not opinions. A settle-test result or a rendered pose on the change is what lets a reviewer approve something they cannot read in raw XML.

What a text diff still cannot tell you is that a mount moved a few millimeters, that a mesh was replaced, or that the center of mass shifted outboard. That is the case for a structural and geometric diff: a tree diff plus changed-geometry highlighting between two revisions. CadSyn provides it, with branches, required reviewers and CI hooks — and every agent-proposed change arrives through the same door.

How do you choose actuators for a robot arm, and what does the model need to know about them?

Size from the worst case at each joint, not the average one. For every joint, compute the static torque needed to hold the payload at full reach in the worst pose — usually shoulder pitch with the arm horizontal — add the dynamic term from your acceleration target and the link inertia, then apply margin, because the payload always grows and the duty cycle is always worse than the spec sheet assumed.

Then the details that catch people:

  • Torque curves, not a torque number. Peak torque lasts seconds. Continuous torque is what you can hold. An arm sized on peak works in the demo and thermally folds in the pilot.
  • Reflected inertia. Rotor inertia multiplied by the square of the gear ratio — a 9:1 reduction multiplies it by 81. On a low-ratio quasi-direct-drive actuator that term is small and backdrivability is real; behind a high-ratio harmonic drive the rotor dominates the joint’s apparent inertia, and a controller tuned in a model that ignored it will not transfer to the bench.
  • What the gearbox costs. Ratio also sets backlash, efficiency — which is direction-dependent — and whether the joint backdrives at all.
  • Thermal limits. Continuous torque is a winding-temperature claim at an ambient and a duty cycle. Ask for the curve and the assumptions behind it.
  • Lead time and price. An actuator you cannot get this quarter is a design constraint, not a procurement problem.

The model needs the same facts the spreadsheet had: reflected rotor inertia — rotor inertia times the square of the gear ratio — as the joint armature, gear ratio, torque and velocity limits at the joint after the gearbox, damping and dry friction, and the actuator’s own mass where it actually sits. CadSyn holds them in a verified catalog, every joint cites a real SKU, and the bill of materials moves when the design moves.

Can a customer evaluate a robot without a CAD seat?

Today, usually not — and it is a sales problem before it is a technical one. Evaluating an industrial robot means answering three questions: does it reach every point in my cell, does it carry my part at my cycle time, does it clear my fixture.

The normal route to those answers is an NDA, a zip of STEP files, a CAD seat on the customer’s side and a site visit. That is weeks of calendar before the first honest answer, and the prospect with no CAD seat never gets one at all.

The alternative is to make the model itself the deliverable: a browser viewer running the same physics as the engineering model, a small and deliberate set of exposed parameters — payload, reach, end effector, base placement — and the customer’s own cell layout dropped in as a mesh.

Two things decide whether that is useful or a toy. First, it has to be the real model at a stated revision, not a decimated show version, or the reach envelope you demonstrated is not the one you ship. Second, the exposed parameters have to be bounded by the manufacturer. A configurator that lets a prospect specify something the robot cannot do produces a bad quote and a worse meeting.

CadSyn builds those links on the same variants system the engineering model uses, in an embeddable viewer the OEM can white-label. CAD seats required to open one: 0.

Tell us about your robot

We reply to every inbound from a real team, usually within two working days.