The AY-Robots SO-100 hub page, the starting point for building, calibrating and training an SO-100 robot arm
3D printingSO-100SO-101TolerancesRobot hardwareLeRobot

3D Printing an SO-100 Arm That Holds Tolerance

AY-Robots ResearchAugust 23, 202619 min read

The SO-101 servo gauge has a 24.700 mm pocket for a 24.7 mm servo. Zero designed clearance. What material, layer height, infill and slicer compensation actually decide.

The SO-101 repository ships a small part whose only job is to tell you whether your printer is good enough to build the arm. Slice Gauge_0.STL halfway up and the pocket inside measures 34.900 mm by 24.700 mm. The Feetech STS3215 servo that has to drop into it is 24.7 mm wide. There is no designed clearance on that axis. If your walls land 0.05 mm proud of nominal, the servo does not go in.

That measurement is the whole story of printing an SO-100 or SO-101. The geometry is not hard. What the design demands is that a 25 mm feature comes out within about a tenth of a millimetre, roughly 0.4 percent. Most printers are not there out of the box, and the failure does not look like a print failure. It looks like an arm you cannot assemble, or an arm you can assemble that has play in the wrist and records noisy episodes.

What decides whether the parts fit

  • Gauge_0.STL has a 34.900 x 24.700 mm pocket. The STS3215 body is quoted at 35.0 x 24.7 mm, so the pocket is exactly nominal on the 24.7 mm axis and 0.100 mm under it on the other.
  • The two gauge variants differ by exactly 0.100 mm. That is the correction step you are meant to dial into your slicer.
  • The STL files are not the bottleneck. The screw holes are drawn with 24 segments, which costs about 0.027 mm of hole diameter. An uncalibrated extruder costs several times that.
  • Holes measure 3.20 mm for M3 (0.10 mm clearance per side) and 1.50 mm for M2 (the screw cuts its own thread). The M3 holes close up first.
  • Upstream says PLA+, 0.2 mm layers, 0.4 mm nozzle, 15 percent infill. Infill is not what makes these parts stiff. Wall count is.
  • PETG handles heat better, 68 C against 55 C, but is 35 percent less stiff. For structural links, PLA.

The fits that actually decide anything

I pulled the current STL files from the build repository and measured the meshes directly rather than trusting the README. The method is in the callout below.

FeatureMeasuredHas to fitMargin
Gauge_0.STL pocket34.900 x 24.700 mmSTS3215 body, 45.2 x 24.7 x 35.0 mm0.000 mm on the 24.7 mm axis
Gauge_tight_1.STL pocket34.800 x 24.600 mmsame servo, 0.100 mm tighternegative, it is a fail gauge
Lego_Size_Test_02_zero.STL pocket12.900 x 17.400 mmthe Lego gauge feature, for people whose servos have not arrivednominal
Lego_Size_Test_02_minuspoint1.STL pocket12.800 x 17.300 mmsame feature, exactly 0.100 mm tighter on both axesnegative, it is a fail gauge
Larger screw holes3.20 mm, 24 segmentsM3 screw, 3.00 mm major diameter0.10 mm per side
Smaller screw holes1.50 mm, 24 segmentsM2 screw, 2.00 mm major diameternegative, it self-taps
Largest bore in a link, Wrist_Roll_Pitch_SO1015.40 mm, 28 segmentsnot a screw, not a clearance fitn/a

The M3 row is the one that eats an evening. A 3.20 mm hole for a 3.00 mm screw leaves 0.10 mm of air per side. A printer laying its walls 0.10 mm proud on each side of an internal feature, entirely normal for an uncalibrated machine, takes that hole down to 3.00 mm, which is the screw's own major diameter, and it will not pass. Forcing it splits the boss along a layer line, and you will not see the crack until the end effector starts wobbling weeks later.

How these numbers were measured

Every STL was downloaded from TheRobotStudio/SO-ARM100 on 2026-08-24 and sliced in software at fixed Z heights, with closed loops extracted and measured. The gauge pockets were read at z = 5.0 mm, halfway up a 10 mm part, so no first-layer or top-surface effects are in the numbers. Hole tessellation was counted from the mesh itself, by taking the vertex rings of each hole: the screw holes come out at one vertex every 15.000 degrees, which is 24 segments. The STL directory was last changed on 2025-09-02, so these are current files.

The published settings, and which document you are reading

Three sets of recommended settings live in the same repository and they do not agree. Here they are side by side.

Source fileMaterialNozzle and layerInfillSupports
SO100.md (deprecated)PLA0.4 mm at 0.2 mm layers, or 0.6 mm at 0.4 mm13 percenteverywhere, ignore slopes over 45 degrees, none in horizontal screw holes
README.md (current, SO-101)PLA+0.4 mm at 0.2 mm layers, or 0.6 mm at 0.4 mm15 percentsame rule
3DPRINT.md (ordering from a service)PLA+service picks20 percentservice default

That is not a contradiction so much as three risk appetites. 13 percent was the original SO-100 figure, 15 percent is current, and print bureaus quote 20 percent because they would rather over-build than field a complaint. None of the three is why your parts do or do not fit. Infill density has almost nothing to do with dimensional accuracy at this size.

If you are building the SO-100 you are following a document the maintainers marked deprecated. Everything here applies to both, and the SO-100 against SO-101 comparison covers what changed. The visible difference in the print files is the leader, which now uses three different gear ratios rather than one.

bash
git clone --depth 1 https://github.com/TheRobotStudio/SO-ARM100.git
cd SO-ARM100

# the gauges you print first
ls STL/Gauges/
# Gauge_0.STL  Gauge_tight_1.STL
# Lego_Size_Test_02_zero.STL  Lego_Size_Test_02_minuspoint1.STL

# pre-arranged plates, already oriented Z-up to minimise supports
ls STL/SO101/Follower/    # Ender_Follower_SO101.stl  Prusa_Follower_SO101.stl
ls STL/SO101/Leader/      # Ender_Leader_SO101.stl    Prusa_Leader_SO101.stl

# individual parts, for reprinting one at a time
ls STL/SO101/Individual/
Get the current print files and the gauges (checked 2026-08-24)

The plates use nearly the entire bed

The combined plates are convenient and they are also a trap. Measured from the mesh bounding boxes:

FileFootprintHeightStated bedSpare
Ender_Follower_SO101.stl216.344 x 215.282 mm87.000 mm220 x 220 mm1.83 mm per side in X
Ender_Leader_SO101.stl216.344 x 214.197 mm88.125 mm220 x 220 mm1.83 mm per side in X
Prusa_Follower_SO101.stl243.411 x 204.864 mm87.000 mm205 x 250 mm0.136 mm total in Y
Prusa_Leader_SO101.stl243.411 x 204.665 mm88.125 mm205 x 250 mm0.335 mm total in Y
A brim will not fit, and neither will a skirt

The Prusa follower plate leaves 0.136 mm of bed depth spare. Any brim, skirt or draft shield and the slicer will refuse to slice or silently clip geometry at the edge. Fix a poor first layer with bed adhesive, not with extra perimeter geometry. If you cannot get a clean first layer without a brim, print from STL/SO101/Individual/ in smaller batches instead.

There is a second reason to split the plate. Both are 87 to 88 mm tall, so a single failure eight hours in wastes everything on it. Batches give you a restart point, and let you check the first parts against a real servo before committing the rest of the roll.

Material: PLA, PLA+ and PETG, with real numbers

Upstream says PLA+, which is good advice with the reasoning left out. PLA+ is not a standard, it is a vendor blend, and every vendor means something different by it. Prusa's own trade name for the material below is Prusament PLA/PLA (Blend). What matters is the property set. Both columns come from the Prusa Polymers datasheets, version 1.1 dated 16-02-2022. The mechanical rows are measured on printed specimens; hardness and interlayer adhesion come from the filament table on the page before.

PropertyPrusament PLAPrusament PETGBetter here
Tensile yield, horizontal51 +/- 3 MPa47 +/- 2 MPaPLA, slightly
Tensile modulus (stiffness)2.3 +/- 0.1 GPa1.5 +/- 0.1 GPaPLA, clearly
Flexural strength83 +/- 6 MPa66 +/- 2 MPaPLA
Charpy impact, unnotched13 +/- 1 kJ/m2no breakPETG, clearly
Heat deflection at 0.45 MPa55 C68 CPETG
Interlayer adhesion17 +/- 3 MPa18 +/- 4 MPaa wash
Shore D hardness8174PLA, for thread-cutting bosses

Tensile modulus decides it. PLA is 2.3 GPa, PETG 1.5 GPa, a 35 percent stiffness drop. On a link carrying the degrees of freedom above it, that shows up as sag under load, and sag is a systematic error baked into every LeRobot dataset you record. The servo reports its commanded angle, not where the gripper actually is, so a flexing forearm is invisible in the data and very visible in the results.

PLA against PETG for the structural links
Why PLA wins here
  • 2.3 GPa modulus against 1.5 GPa: less sag, less play at the gripper.
  • Holds size far better. PETG swells and oozes, so pockets come out inconsistent even on a tuned machine.
  • Shore D 81 against 74, so the M2 bosses cut a cleaner thread and strip less.
  • It is what upstream tested with, so the file tolerances were validated against it.
Where PLA costs you
  • Heat deflection at 55 C. A hard-working servo gets warm, a closed car gets much warmer.
  • Creeps under sustained load. A gripper clamped hard for days takes a permanent set.
  • PETG's unnotched Charpy is 'no break' against PLA's 13 kJ/m2, so PETG is tougher if drops are your failure mode.
Two heat traps, one electrical and one thermal

The STS3215 servos in an SO-100 or SO-101 are the 7.4 V variant. Feeding them 12 V destroys them, and a 12 V version of the same part number exists, which is exactly how people get this wrong. Separately, PLA deflects at 55 C, low enough that a stalled servo or a sunlit car can soften a motor mount and let the servo shift in its pocket. If that is your reality, print the motor holders in PETG and take the stiffness penalty on those parts only. Do not mix materials across the long links unless you want asymmetric sag.

Layer height, walls and infill: which knob does what

These three get discussed as if they trade off against each other. They mostly do not. Only one of them has much to do with whether parts fit.

SettingUpstream valueWhat it controlsEffect on fit
Layer height0.2 mm with a 0.4 mm nozzleZ resolution and print timeAlmost none in XY
Nozzle diameter0.4 mm, or 0.6 mm at 0.4 mm layersHow fine a wall you can layLarge: 0.6 mm cannot resolve the thin ribs around the servo pockets
Infill density15 percentWeight and filament useEffectively none
Wall countnot specified upstreamNearly all real stiffness and thread strengthIndirect but large: more walls, more corner bulge
Extrusion multiplierprinter specificHow much plastic comes outDominant. This is what makes holes close up

If you take one thing from this section: 4 walls with 10 percent infill is stiffer in bending than 2 walls with 40 percent, and uses less filament. Bending stiffness scales with how far material sits from the neutral axis, and walls are the material furthest out. Use 3 walls on the links, 4 on the motor holders and base, and leave infill at 15 percent. At a typical 0.45 mm extrusion width that is 1.35 mm and 1.80 mm of solid shell.

Raising wall count does cost you something on tolerance. Every extra perimeter is another chance for the slicer to over-pack plastic into an inside corner, and the servo pockets are full of inside corners. That is what the compensation settings are for, and it is why you print the gauges after choosing your wall count, not before.

Print the gauges before you print the arm

This is the step almost everyone skips and it is the highest-value fifteen minutes in the build. Four gauge files ship in STL/Gauges/: two check against a real STS3215, two against a standard 4x2 Lego brick for people whose servos have not arrived.

  1. 1
    Slice both servo gauges with your real profile

    Same material, nozzle, layer height and wall count you will use for the arm. A gauge printed with a different profile tells you nothing.

    bash
    STL/Gauges/Gauge_0.STL          # 34.900 x 24.700 mm pocket
    STL/Gauges/Gauge_tight_1.STL    # 0.100 mm tighter on both axes
    
    # if your servos have not arrived yet
    STL/Gauges/Lego_Size_Test_02_zero.STL
    STL/Gauges/Lego_Size_Test_02_minuspoint1.STL
  2. 2
    Print both on the same plate in one run

    Printing them separately adds run-to-run variation and defeats the point. Together they take well under half an hour.

  3. 3
    Read the result the way it was designed to be read

    Gauge_0 should accept the servo with firm hand pressure and no tool. Gauge_tight_1 should not. Both fit means you print undersize, neither fits means oversize, and only Gauge_0 fitting means you are inside 0.1 mm and can print the arm.

  4. 4
    If both fit, add material

    Increase XY compensation in the positive direction in 0.05 mm steps and reprint Gauge_tight_1 only.

    ini
    ; PrusaSlicer, Print Settings > Advanced
    xy_size_compensation = 0.05
    
    ; OrcaSlicer / Bambu Studio, Quality > Precision
    xy_contour_compensation = 0.05
    xy_hole_compensation = 0.05
  5. 5
    If neither fits, remove material

    The more common direction with a fresh profile. Go negative in the same steps and check flow rate before you pass -0.10 mm. Compensation trims hundredths, it does not hide a 5 percent over-extrusion problem.

    ini
    ; PrusaSlicer
    xy_size_compensation = -0.05
    elefant_foot_compensation = 0.2   ; first layer, 0.4 mm nozzle
    
    ; Cura, Shell settings
    xy_offset = -0.05                 ; "Horizontal Expansion"
    xy_offset_layer_0 = -0.2          ; "Initial Layer Horizontal Expansion"
    hole_xy_offset = 0.05             ; "Hole Horizontal Expansion", note the sign
  6. 6
    Only now slice the arm

    Carry the same profile across. If you change filament brand or colour later, reprint Gauge_tight_1 first: pigment loading changes flow more than people expect.

Compensation settings, by slicer, with the real keys

The names differ in every slicer and one pair has an inverted sign convention, which is a reliable way to spend an afternoon going the wrong way. These come straight out of each project's current source, fetched 2026-08-24.

SlicerKeyUI labelDefaultSign
PrusaSlicerxy_size_compensationXY Size Compensation0 mmnegative inwards, positive outwards, whole object
PrusaSlicerelefant_foot_compensationElephant foot compensation0 mm in source, 0.2 mm in Prusa's own 0.4 mm profilespositive shrinks the first layer
Curaxy_offsetHorizontal Expansion0 mmpositive grows the part and shrinks holes
Curaxy_offset_layer_0Initial Layer Horizontal Expansion0 mmnegative compensates elephant foot
Curahole_xy_offsetHole Horizontal Expansion0 mmpositive makes holes bigger, opposite of xy_offset
OrcaSlicerxy_hole_compensationX-Y hole compensation0 mmpositive makes holes bigger
OrcaSlicerxy_contour_compensationX-Y contour compensation0 mmpositive makes contours bigger
OrcaSlicerprecise_outer_wallPrecise wallenablednot a compensation; it adjusts outer wall spacing for shell precision
The sign trap that costs an evening

In Cura, xy_offset and hole_xy_offset move holes in opposite directions for the same positive number. The tooltip for xy_offset reads "Positive values can compensate for too big holes"; hole_xy_offset reads "Positive values increase the size of the holes". Set both positive and you have partly cancelled yourself out. Use one for outside dimensions and the other for screw holes, and change one at a time with a gauge print in between.

One myth worth killing. People blame small holes on the STL approximating circles with flat segments, and the geometry is real: a hole drawn with n segments is only d times cos(pi/n) across the flats. But the SO-101 screw holes are drawn with 24 segments, one vertex every 15.000 degrees.

Segments in the holeAcross flats, 3.20 mm holeUndersize
82.9564 mm0.244 mm
163.1385 mm0.061 mm
24 (what the SO-101 files use)3.1726 mm0.027 mm
323.1846 mm0.015 mm

So if your M3 holes come out 0.15 mm tight, the faceting in the file accounts for about 0.027 mm of that, under a fifth, and your extruder accounts for the rest. Fix flow and compensation rather than hunting a higher-resolution export. OrcaSlicer's hole_to_polyhole, labelled "Convert holes to polyholes" and off by default, runs the other way: it replaces an almost-circular hole with a deliberately low-vertex polygon, scaled up by 1 / cos(pi/n) so the flats sit tangent to the intended circle. That is the HydraRaptor polyhole trick, and it targets holes that come out small because of extrusion, not faceting that is already in the mesh.

The AY-Robots SO-100 hub page, linking setup, calibration, data collection and training guides for the SO-100 arm
The SO-100 hub. Printing is step zero, and everything downstream assumes the mechanics are repeatable.

Why the gripper is where tolerance turns into behaviour

Every joint has some print-related slop, but only the gripper converts it directly into task failure. It sits at the end of the chain, so every upstream error adds to it, and it is the only joint whose job is a binary outcome. A shoulder half a degree off still reaches the object. A gripper half a degree short of contact drops it.

The gripper claw is secured with 4 M3x6 mm screws on both sides, and the smallest bores in these parts measure 1.50 mm, which is where the M2x6 mm screws cut their own thread. Both are places where a bad print produces play rather than an obvious failure.

  • Jaw screws in oversized holes: the jaw rocks under load, actual aperture differs from the reported angle by millimetres, and a policy trained on that data learns a mapping that stops being true once the screws settle.
  • M2 bosses stripped by forcing screws into tight holes: the servo shifts under lateral load, which is one mechanism behind a joint that stops early.
  • Servo pocket a fraction loose: the motor rotates a degree or two inside its mount on every direction reversal, your calibration drifts between sessions, and you will blame the software.
  • The optional TPU 95A jaw from the June 2025 LeRobot hackathon forgives contact error, but it deforms under load, so grip force stops being a clean function of joint angle.
The AY-Robots failure-mode index listing SO-100 problems such as gripper does not close, joint stops early and arm twitches then sags
Several failure modes in the fix index have a mechanical root. Check the arm still measures the same as when you recorded.

If you are chasing a gripper that does not close or an arm that twitches then sags, eliminate the mechanics before touching the model. Hold the arm in a fixed pose, command a small angle change on one joint, and watch whether the joint moves before the reported angle does. Backlash you can see is backlash that is in your training data. The failure-mode index lists which symptoms have mechanical roots.

Two routes to a working arm

You calibrate a printer against the gauges, then print a plate that stacks 87.000 mm high for the follower and 88.125 mm for the leader. The repository states no print time and neither will I: time one plate on your own machine and plan the build around that number.

  1. Clone TheRobotStudio/SO-ARM100 and print Gauge_0.STL and Gauge_tight_1.STL with your intended profile.
  2. Adjust XY compensation in 0.05 mm steps until Gauge_0 accepts a servo and Gauge_tight_1 does not.
  3. Print the individual parts in batches, so a failure costs one part rather than the plate.
  4. Assemble with M2x6 and M3x6 screws per the LeRobot SO-101 assembly guide, which lists the count per joint.
  5. Set motor ids, then calibrate.
bash
pip install -e ".[feetech]"

lerobot-find-port

lerobot-setup-motors \
    --robot.type=so101_follower \
    --robot.port=/dev/ttyACM0

lerobot-calibrate \
    --robot.type=so101_follower \
    --robot.port=/dev/ttyACM0 \
    --robot.id=my_follower_arm
What comes immediately after the screws are in

Parts cost about 110 to 150 EUR for an SO-100 and 130 to 170 EUR for an SO-101, before filament. The getting started guide covers what follows.

What a better printer does not fix

Dimensional accuracy is not repeatability. You can print an arm that assembles perfectly and still have a degree or two of backlash at the wrist, because backlash lives in the servo gearbox and the horn spline, not in the printed part. The STS3215 is a 55 g hobby-grade bus servo with a metal gear train. It is remarkably good for the money. It is not a harmonic drive, and no slicer setting changes that.

The platform does not help with any of this

AY-Robots is teleoperation, dataset recording, cloud training and inference. It has no view into your printer, cannot measure your parts, and cannot tell a policy failure caused by a loose gripper from one caused by too few episodes. The failure-mode pages describe symptoms and likely causes, but a mechanical diagnosis is made with your hands on the arm.

Reprinting a part mid-project quietly invalidates your data. Record two hundred episodes, crack a motor holder, reprint it with a different compensation value, and the relation between joint angles and end effector position has changed. Old and new episodes now describe two subtly different robots, and the model averages them. If you must reprint a structural part, note the date and treat what came before as a separate dataset. The same discipline runs through the guide to collecting high-quality VLA training data.

The AY-Robots teleoperator page showing the SO-100 arm remote operators drive
Operator arms are built and maintained centrally, which sidesteps the print-tolerance problem entirely.

If you would rather generate data than filament dust, the operator route is the version where somebody else owns the mechanical problem. The complete SO-100 guide walks the same path from parts to a trained policy.

No arm yet? Find out if the task is learnable first

Before you spend a weekend calibrating a printer, check whether the task you have in mind is one a policy can learn. Three ways in without owning a robot: drive a real arm in the browser, compare the five trainable policies on real numbers, or rent a GPU for a run.

Start without a robot

Questions people actually ask

What layer height should I use for the SO-100 and SO-101 parts?

0.2 mm with a 0.4 mm nozzle, which is what the build repository specifies. The alternative it offers, a 0.6 mm nozzle at 0.4 mm layers, is faster but cannot resolve the thin ribs around the servo pockets as cleanly. Layer height has very little effect on XY accuracy, so going finer buys print time, not fit.

Is 15 percent infill really enough for a robot arm?

Yes, and raising it is the wrong lever. The SO-101 README says 15 percent, the deprecated SO-100 document says 13 percent, and print bureaus quote 20 percent. Bending stiffness at this size is dominated by wall count. Use 3 walls on the links and 4 on the motor holders and base, leave infill at 15 percent, and you get a stiffer arm than 2 walls at 40 percent, with less filament.

PLA or PETG?

PLA, or a PLA+ blend, for everything structural. PETG's modulus is 1.5 GPa against PLA's 2.3 GPa, so PETG links sag more under the same load, and it is harder to hold to size because it swells and oozes. The one case for PETG is heat: 68 C deflection against 55 C. If your arm lives somewhere hot, print the motor holders in PETG.

My M3 screws will not go through the holes. Did I print it wrong?

Almost certainly your printer is slightly over-extruding. The holes are modelled at 3.20 mm for a 3.00 mm screw, so you have 0.10 mm of clearance per side. Print Gauge_0.STL and Gauge_tight_1.STL to confirm, then apply negative XY compensation 0.05 mm at a time, or positive hole compensation if your slicer separates the two. Do not force the screws: that splits the boss along a layer line.

Does a better printed arm make my trained policy better?

Indirectly, and only up to a point. A tighter arm reduces the mismatch between commanded joint angles and actual end effector position, which makes recorded data more consistent. It does not remove servo gearbox backlash, and it does not compensate for too few episodes or bad camera placement. Check the mechanics first, then the data.

Ready for high-quality robotics data?

AY-Robots connects your robots to skilled operators worldwide.

Get Started