The formula
Print a square, label its corners A to D and measure the diagonals AC and BD and the side AD. Marlin and Klipper both turn those three lengths into a skew factor in two steps:
AB = √(2 × AC² + 2 × BD² − 4 × AD²) / 2
factor = tan(90° − acos((AC² − AB² − AD²) / (2 × AB × AD)))
- AC and BD are the diagonals. AD is the side along the first axis: X for the XY and XZ planes, Y for YZ.
- AB is the side along the second axis, worked out from the other three with the parallelogram law.
- The acos term is the angle at corner A, from the law of cosines. The factor is the tangent of how far that corner sits from 90°.
These are the lines in Marlin’s Conditionals-5-post.h (_GET_SIDE and _SKEW_FACTOR) and in Klipper’s calc_skew_factor(), which Klipper’s source calls a port of Marlin’s skew correction. The same three lengths give the same factor in both.
The calculator’s other results follow from the factor:
skew angle = atan(factor)
error over 100 mm = 100 × factor
When AC is longer than BD, corner A is under 90° and the factor is positive. On the XY plane both firmwares then plan every move with X reduced by Y × factor, which pulls the leaning side back into line. Marlin’s docs put it in numbers: a factor of 0.004 shifts X by 0.4 mm over 100 mm of Y.
How do you calculate skew correction for a 3D printer?
Print a test square with skew correction off, measure diagonals AC and BD and side AD, and work out side AB = √(2AC² + 2BD² − 4AD²) / 2. The factor is tan(90° − corner A). In Marlin send M852 I<factor> and M500. In Klipper send SET_SKEW XY=AC,BD,AD and save a profile.
- Turn correction off for the test print:
M852 I0in Marlin,SET_SKEW CLEAR=1in Klipper (or leave[skew_correction]out of printer.cfg). - Print the square with corner A toward the plane’s origin, as large as your calipers can measure.
- Measure AC, BD and AD two or three times each and average the readings.
- Type the averages into the calculator and copy the lines for your firmware.
- Print the same square in the same place with correction on and measure it again (see “Checking the correction” below).
Worked example: the square from Marlin’s docs
Marlin’s docs use AC = 141.70 mm, BD = 141.14 mm and AD = 100.00 mm. These are the calculator’s defaults.
- AB = √(2 × 141.70² + 2 × 141.14² − 4 × 100²) / 2 = √(40,157.78 + 39,841.00 − 40,000) / 2 = √39,998.78 / 2 = 99.998 mm.
- cos A = (141.70² − 99.998² − 100²) / (2 × 99.998 × 100) = 79.20 / 19,999.69 = 0.003960, so corner A is 89.773°.
- Factor = tan(90° − 89.773°) = tan(0.227°) = 0.003960. Marlin’s docs give “about 0.00396”.
- Error over 100 mm = 100 × 0.003960 = 0.40 mm. Corners B and C lean 0.40 mm toward +X for every 100 mm of Y.
- Marlin takes
M852 I0.003960thenM500. Klipper takesSET_SKEW XY=141.7,141.14,100, andCALC_MEASURED_SKEWwith the same lengths reports 0.003960 radians, 0.23 degrees.
Klipper prints the factor itself as “radians” and converts that to degrees. For skew this small, the result matches atan(factor) to two decimals.
Worked example: the square from Klipper’s docs
Klipper’s docs measure AC = 140.4 mm, BD = 142.8 mm and AD = 99.8 mm and enter them as SET_SKEW XY=140.4,142.8,99.8. AB works out to 100.459 mm and corner A to 90.971°. The factor is −0.016951: 0.97° of skew, with B and C leaning 1.70 mm toward −X per 100 mm of Y. Marlin would take M852 I-0.016951.
That much skew puts the diagonals of a 100 mm square 2.40 mm apart. Past a difference of about 1 mm, Marlin’s docs say to check the frame with a machinist’s square and re-square the gantry or Y rails, then let software correct what is left. Klipper’s docs give the same advice for a significantly skewed printer.
What is 3D printer skew?
Skew is when two of a printer’s axes are not at 90° to each other: X against Y, or Z against X or Y. Every square then prints as a parallelogram, and the error grows with the size of the part. A skew of 0.23° moves the back of a 100 mm square 0.40 mm sideways.
Prusa’s MK3 firmware measures XY skew during its XYZ calibration and sorts it into bands. Marlin’s docs add the point where the frame needs work before software correction.
| Skew | Error per 100 mm | Diagonals of a 100 mm square differ by | Published label |
|---|---|---|---|
| 0.12° | 0.21 mm | 0.30 mm | Prusa MK3: up to here, “X/Y axes are perpendicular” |
| 0.25° | 0.44 mm | 0.62 mm | Prusa MK3: up to here, “slightly skewed”; above it, “Severe skew” |
| 0.41° | 0.71 mm | 1.00 mm | Marlin docs: past about here, square the frame first |
Prusa’s firmware notes the same bands in its own terms: 0.12° is 0.5 mm over 250 mm and 0.25° is 1.1 mm over 250 mm.
What a skewed print looks like, and what else looks like skew
| What you see | Likely cause | Fix |
|---|---|---|
| A square with unequal diagonals; corners not 90° | X and Y not perpendicular | This calculator, XY plane |
| A wall or tower that leans steadily with height | Z not square to X or Y; Marlin’s docs point to a leaning gantry or uprights | Check the frame, then the XZ or YZ plane |
| A sudden sideways step at one layer | A layer shift: belt tension or a loose pulley, per Prusa | Belt tension calculator, pulley set screws |
| Every dimension off by the same percentage | Wrong steps per mm | Steps per mm calculator |
| Every dimension off by the same amount; small holes | Backlash or over-extrusion | Belts and pulleys first; holes with the hole compensation calculator |
Marlin’s docs put skew after the mechanics, steps per mm and backlash, because backlash makes printed squares measure wrong. Re-measure skew after you change backlash compensation or steps per mm.
Printing and measuring the skew test
- The test object. Marlin’s Configuration.h and Klipper’s docs both link the same square, thing:2563185 on Thingiverse. Marlin’s docs note that its clipped corners make the caliper jaws sit on the same points every time, and that any accurately modeled square works. Klipper’s docs also link an object with all three planes in one print.
- Corner labels. Stand at the front and look down on the bed: X increases to the right and Y toward the back. A is the corner nearest X min and Y min. B sits up the Y side from A, D along the X side, and C opposite A.
- XZ and YZ. Print the square standing up: X across and Z up for XZ, Y across and Z up for YZ. AD runs along X or Y and AB runs up Z.
- Measuring AD. Leave out any flats the test object has on its corners, as Klipper’s docs say.
- Size. A 0.1 mm misread on one diagonal of a 100 mm square moves the factor by about 0.0007, or 0.07 mm per 100 mm. On a 200 mm square the same misread costs half that, which is why Marlin’s docs say to print the square as large as your calipers can measure.
If the diagonals are equal within your measuring error, the axes are square and Marlin’s docs say you don’t need skew correction.
Setting skew correction in Marlin
Marlin needs these options in Configuration.h, in the Bed Skew Compensation section:
SKEW_CORRECTIONenables the feature. It needs either the three lengthsXY_DIAG_AC,XY_DIAG_BDandXY_SIDE_AD, or a factor inXY_SKEW_FACTOR. Without one of them, the firmware won’t compile.SKEW_CORRECTION_GCODEaddsM852and loads the factor from EEPROM (the board’s saved-settings memory). Without it, the configured values are fixed.SKEW_CORRECTION_FOR_Zadds the XZ and YZ planes. The stock file has noXZ_SIDE_AD, so Marlin usesXY_SIDE_ADfor the XZ side unless you add one. The calculator’s Configuration.h output includes that define.
At runtime, M852 takes I for XY (S is the same), J for XZ and K for YZ. M500 saves the factor to EEPROM. For the Marlin docs’ square:
M852 I0.003960
M500
Or let the firmware compute the factor at build time from the lengths:
#define SKEW_CORRECTION
#define XY_DIAG_AC 141.7
#define XY_DIAG_BD 141.14
#define XY_SIDE_AD 100
#define SKEW_CORRECTION_GCODE
- Marlin accepts factors from −1 to 1. A refused value with the reply
min_skew_factor: -1 max_skew_factor: 1usually means millimeters were typed instead of a factor. - After you flash new firmware, a factor saved in EEPROM wins over Configuration.h. Send
M502thenM500to take the configured one.M502resets every setting to its firmware default, not only skew. - Skew isn’t applied within 1 mm of the minimum X or Y limit, or where the corrected point would fall outside the limits. Keep parts away from the edges.
Setting skew correction in Klipper
- Add a
[skew_correction]section to printer.cfg. It takes no options. - Send
SET_SKEW XY=AC,BD,ADwith your three lengths in that order.XZ=andYZ=work the same way, in any combination on one line. - Send
SKEW_PROFILE SAVE=my_skew_profile, thenSAVE_CONFIGto write the profile to printer.cfg. - Load the profile in your start G-code, after homing and after any purge line or nozzle wipe near the edge of the bed.
G28
; purge line or nozzle wipe here
SKEW_PROFILE LOAD=my_skew_profile
Klipper reads saved profiles at startup but starts every session with all three factors at zero. It applies a profile only on SKEW_PROFILE LOAD, so a start G-code without that line prints uncorrected. Klipper’s docs suggest SET_SKEW CLEAR=1 in the end G-code. They also warn that the correction can move the toolhead past the X or Y limits, so place parts away from the edges.
Checking the correction
Print the same square in the same place with correction on, and measure it again.
- Marlin. The diagonals should now match within your measuring error. If a small difference remains, Marlin’s docs say to compute a factor from the new print and add it to the one already set. Type the new lengths, put the active factor in “Second pass”, and the calculator adds the two.
- Klipper. Send
CALC_MEASURED_SKEW AC=<ac> BD=<bd> AD=<ad>with the new lengths. Klipper’s docs say the result should be lower than whatGET_CURRENT_SKEWreports.SET_SKEWexpects lengths from a square printed with no correction, so for a fresh value sendSET_SKEW CLEAR=1and print the test again. - Worse than before. The sign is wrong, usually because the corners were labeled in the wrong order. Re-label with A at X min and Y min and B along +Y, or in Marlin set
M852 Ito the negative of the value.
Skew correction in RepRapFirmware (Duet)
RepRapFirmware uses the opposite sign. Duet’s docs compute the factor as −tan(90° − corner A), so it is negative when AC is longer than BD. M556 takes a length and the deviation over it, with X for the XY plane, Y for YZ and Z for ZX. For the Marlin docs’ square that is M556 S100 X-0.396, which the calculator’s last line gives with the sign already flipped.
Published · Figures last checked
Sources
- Marlin Firmware documentation, Skew, Backlash and X-Twist Compensation (accessed 2026-10-18)
- Marlin Firmware documentation, M852 Bed Skew Compensation (accessed 2026-10-18)
- Marlin source, Configuration.h (Bed Skew Compensation section) (accessed 2026-10-18)
- Marlin source, src/inc/Conditionals-5-post.h (_GET_SIDE and _SKEW_FACTOR) (accessed 2026-10-18)
- Marlin source, src/gcode/calibrate/M852.cpp (accessed 2026-10-18)
- Marlin source, src/module/planner.h (skew and unskew) (accessed 2026-10-18)
- Klipper documentation, Skew correction (accessed 2026-10-18)
- Klipper documentation, G-Codes: skew_correction (accessed 2026-10-18)
- Klipper documentation, Configuration reference: skew_correction (accessed 2026-10-18)
- Klipper source, klippy/extras/skew_correction.py (accessed 2026-10-18)
- Duet3D documentation, Orthogonal axis compensation with M556 (accessed 2026-10-18)
- RepRapFirmware source, src/Movement/Move.cpp (axis skew transform) (accessed 2026-10-18)
- Prusa-Firmware (MK3), mesh_bed_calibration.cpp (skew thresholds) (accessed 2026-10-18)
- Prusa-Firmware (MK3), messages.cpp (XYZ calibration skew messages) (accessed 2026-10-18)
- Prusa Knowledge Base, Layer shifting (accessed 2026-10-18)