Showcase · SO-101

LeRobot's arm, from its raw CAD to a working robot, by an AI agent in Polykrom.

The SO-101 is the open-source six-axis arm LeRobot trains its policies on. Its published CAD is 97 parts: 3D-printed links, six Feetech ST3215 servos, about 70 screws, cables and a servo bus board, all in one folded pose. An AI agent was given that file and four plain-language briefs, and nothing but Polykrom's tools. It came back with a robot: links, joints found from the servo geometry, a pick-and-place study and a stress check on the printed arm. We then compared its kinematics with LeRobot's own URDF.

Loading the SO-101 (4 MB of meshes)…
step 0pan 0°lift 0°elbow 0°wrist 0°grip 0°

Live: the URDF Polykrom exported from the agent's design (377 links, the CAD's own colours), driven by the motion study's recorded joint values. Drag to orbit, scroll to zoom.

97
parts in (267 bodies)
7 · 6
links · joints out
≤ 0.81 mm
from LeRobot's kinematics
12.0 mm
fingertip above the table at the pick
SF 1.9
printed arm at servo stall
110 min
agent time, $12.90

It matches LeRobot's robot

The geometry that decides how an arm moves is fixed inside each link: the angle between consecutive joint axes and the distance between them. Unlike joint values, it doesn't depend on calibration. Below are the agent's axes, found from the servo horns in the CAD, set against LeRobot's published URDF. Both are measured by the same independent script from the two URDF files.

BetweenLeRobot angleAgent angleLeRobot distanceAgent distanceOff by
shoulder_pan → shoulder_lift90.00°90.00°30.40 mm30.70 mm0.30 mm
shoulder_lift → elbow_flex (upper arm)0.00°0.50°116.00 mm116.02 mm0.02 mm
elbow_flex → wrist_flex (lower arm)0.00°0.71°135.00 mm134.19 mm0.81 mm
wrist_flex → wrist_roll90.00°90.00°0.00 mm0.00 mm0.00 mm
wrist_roll → gripper90.00°90.00°20.20 mm20.07 mm0.13 mm

The 0.5° and 0.7° are in the CAD itself: the printed parts' own bores lean by that much. The agent kept the true servo axes rather than squaring them up, and said so. LeRobot's URDF was generated from a cleaned-up Onshape model.

How it was built

Each stage was one request to Claude Code running headless, with Polykrom's MCP server as its tool. After each stage, a separate script opened the saved design and re-measured what the agent claimed.

1. Organise the CAD77 min75 tool calls$4.94

Import the STEP, turn its 97 parts into the arm's seven links, and give everything a real material. We said which printed parts belong to which link; the agent had to place every servo, screw, nut and "clip" by geometry.

  • verifiedEvery one of the 267 bodies got a real material (PLA, steel, aluminium, FR-4, nylon, brass); the design totals 1,091 g
  • verifiedEach servo landed in the link that holds its case, and each screw group in the link its horn drives
  • notedThe "clips" turned out to be ribbon cables; it found that, and judged which side of a joint each one rides with
  • notedThe servo CAD is solid envelopes that overlap inside, so real materials gave 48–90 g; it calibrated two densities to the datasheet's 55 g and said so
  • notedExact contact checks took 20–30 minutes per call on this model, so it used distance measurements instead
2. Joints and limits19 min88 tool calls$5.12

Add the six joints about each servo's output axis, found from the geometry, named as LeRobot names them. Find each one's range by sweeping until parts touch.

  • verifiedKinematics within 0.81 mm and 0.71° of LeRobot's URDF (table above)
  • verifiedEvery stop confirmed by an exact distance (0.0003–0.023 mm gaps at contact)
  • notedBroken faces in the imported part meshes made the sweep see contacts that weren't there; it checked the real edges and worked around them
  • notedThe sweep refused the whole model (267 bodies, over a 256 cap), so each sweep listed the arm parts
3. Pick and place7 min51 tool calls$1.85

A 150-step study the way a LeRobot policy would move: unfold, reach the table, grip, lift, turn 90°, place, fold back. Check collisions, trace the fingertip, export a GIF and a URDF.

  • verifiedAll 21 link pairs clear at every step; the fingertip reaches 12.0 mm above the table at the pick and 13.4 mm at the place
  • verifiedThe viewer above replays the exported URDF; it matches Polykrom's own trace of the fingertip to 0.26 µm
  • notedThe reach solver couldn't hold the gripper pointing straight down, so the grasp is angled
  • notedSome tool replies were too large to read, and the agent used jq outside Polykrom to read them (it said so)
4. Will the print hold?6.5 min26 tool calls$1.00

Load the PLA upper arm the way its servos can at worst (the ST3215 stalls at about 2.9 N m) and say what that means for a printed part.

  • verifiedHeld at the shoulder horn, with the elbow servo's full stall torque as a force couple on its mount: 25.7–27.8 MPa peak, converged over meshes down to 1.5 mm; deflection 0.39 mm
  • verifiedSafety factor about 1.9 against bulk PLA, and about 1.0–1.3 once layer lines are counted: marginal at repeated stalls, fine in normal use
  • notedPolykrom reports the peak but not where it is, so the agent found it by changing the fixtures and loads (the shoulder horn boss), then proposed fixes that halve it

The cycle, as Polykrom renders it

Made by motion.export_animation with the headless renderer: 151 frames.

The SO-101 unfolds, reaches the table, grips, turns 90 degrees, places and folds back

Where this version falls short

Real CAD from a real robot finds what tidy test parts don't. Green items were fixed while building this page; the rest are on the roadmap.

Checks at scale

Exact interference on the 267-body arm ran out of a 15-minute budget with 445 pairs unchecked; kernel booleans on imported curved parts take minutes per pair.

Fixed: the six identical servos' inner pairs are now computed once (107 pairs reused). inspect.interference {method: "mesh"} now checks the whole arm in 8.8 seconds, reporting which pairs overlap and how deep, without the overlap volume. → #55

Sweep size

Joint sweeps refused models over 256 bodies.

Fixed: the cap is now 1,024, with contact trees cached per part and parts that move together set aside. Sweeps past thin hardware that moves relative to the arm (washers, small screws) still take tiny steps. → #25

Imported solids

Some imported printed parts aren't closed solids in Polykrom's kernel.

They draw correctly, but the URDF gives them no mass or collision. Broken faces made a sweep see contacts that weren't there. → #42

Printed mass

Printed parts count as solid PLA, so the arm weighs 1,091 g where LeRobot's model says 632 g.

There's no infill or effective-density setting for printed parts yet. Vendor parts modelled as solid envelopes, like the servos, need calibrated densities. → #35

Instanced parts

A servo's output horn can't turn with the link it drives.

It's a body inside the servo part, which is one definition placed six times. The URDF also writes every nested part as its own link, 377 in all. → #27

Stress results

Stress comes back as a peak value with no location.

The agent found the peak by experiment. Contours on the part are in progress (Nightfall writes the field). Automatic mesh refinement barely refined; the agent set the mesh size itself. → #29

Motion tools

The reach solver couldn't keep the gripper pointing down.

Each joint's range is found with the other joints at rest, and there's no reachable-workspace check yet. → #25

Agent experience

Some MCP replies on a model this size are too large for an agent to read, so it reached for jq outside Polykrom.

Replies need paging and summaries. → #18

Take it apart yourself

The agent's design, the robot description for simulators, and the motion. The design contains TheRobotStudio's SO-ARM100 CAD under its Apache-2.0 licence.