devmaker.net
start/ 3d-printing/ orcaslicer-klipper-34-percent-less-print-time
3D Printing

OrcaSlicer & Klipper: 34 % Less Print Time

1 h 41 m became a measured 1 h 06 m – without loss of quality and without new hardware. The biggest cost was not speed but Z-hop: with eighty separate islands per layer, that is nine thousand lifts on an axis that moves the entire bed. This article shows the per-feature time breakdown, the measurement series on the Z-hop variants, an adaptive bed mesh that only probes the printed area – and the calculation of why a finer mesh actually makes accuracy worse. All numbers are measured, not estimated.

Harry_im_Homelab31 (Portrait)
Harald
2026-10-17 · ~10 min read
The chain octopus mid-print – eighty movable links, eighty separate islands per layer
The chain octopus mid-print – eighty movable links, eighty separate islands per layer

With adhesion working, print time became interesting. The starting point was 1 h 41 m 21 s. The per-feature breakdown immediately showed where the time went:

FeatureTimeShareAvg. flow
Outer wall 32 m 53 s 32.4 % 4.2 mm³/s
Non-extruding 30 m 47 s 30.4 % –
Inner wall 20 m 13 s 19.9 % 8.4 mm³/s
Solid infill 10 m 10 s 10.0 % 12.5 mm³/s
Sparse infill 4 m 50 s 4.8 % 9.3 mm³/s

For almost a third of the time nothing is extruded. With eighty separate islands per layer and 124 layers, that is roughly nine thousand travel moves. And the outer wall ran at 4.2 mm³/s – a fraction of what the hotend can do. The limit there was not flow but speed.

The most expensive finding: Z-hop

A look at the machine limits explains the thirty percent:

yaml
machine_max_acceleration_z: 300   # mm/s^2

The Z axis moves the bed and accelerates correspondingly slowly. A 0.2 mm lift therefore costs about a tenth of a second – not because of the distance, but because of accelerating and decelerating. Nine thousand times.

Z-hopTotal timeExtra costProtection
off 3713 s – none
0.1 mm, Auto 3950 s +237 s half
0.1 mm, Slope 3944 s +231 s half
0.2 mm, Slope 3986 s +273 s full

“Slope” blends the lift diagonally into the travel move instead of stopping, lifting, travelling and lowering. For 42 seconds extra compared to the half lift you get the full lift height – with a model made of eighty loose chain links, that is the right side of the trade. Turning Z-hop off completely would have saved four minutes and dragged the nozzle over loose links nine thousand times.

The levers in order of impact

ChangeSavingReason
Layer height 0.28 → 0.32 ~12 min 124 instead of 142 layers; joints checked beforehand
Outer wall 40 → 100 mm/s ~7 min small perimeters from 50 % to 85 %
Flow 15 → 26 mm³/s ~5 min inner wall was flow-limited, not speed-limited
Gyroid → Lightning 4 m 13 s measured directly: 273 s → 20 s infill time
Z-hop Auto → Slope ~2.5 min lift blended into the travel move
Retraction 30 → 45 mm/s, shells 4→3 / 3→2 ~2 min –

At some point speed stopped helping: raising the outer wall from 90 to 130 mm/s saved 3 % of time, the inner wall even got marginally slower. With eight-millimetre barrels, acceleration is the limit, not speed – and that is where turning numbers stops paying off.

Measured, not estimated: 66:27 instead of 68:00

I had noted a caveat: OrcaSlicer calculates with the jerk from the machine profile (9 mm/s), while Klipper ran with square_corner_velocity: 5 – so the real time should be above the estimate.

The caveat was the wrong way round. The estimate was a minute and a half on the safe side. 1 h 41 m became a real 1 h 06 m – 34 % less.

Only probe where you print

The octopus occupies 120 × 120 mm. The mesh probed 388 × 320 mm – a good ten times more area than is printed, with 169 points.

Klipper can derive this itself via BED_MESH_CALIBRATE ADAPTIVE=1, but only if the EXCLUDE_OBJECT_DEFINE lines appear in the G-code before the start macro. That could not be checked without downloading the entire G-code file, so I chose the approach that does not depend on the order: the slicer passes the footprint as a parameter, and the macro calculates area and point count from it.

yaml
# machine_start_gcode in OrcaSlicer
START_PRINT HOTEND=[nozzle_temperature_initial_layer] BED=[bed_temperature_initial_layer_single] \
  MESH_MIN={first_layer_print_min[0]},{first_layer_print_min[1]} \
  MESH_MAX={first_layer_print_max[0]},{first_layer_print_max[1]}

# im Makro: Rand, Klemmung auf den konfigurierten Bereich,
# Referenzpunkt immer enthalten, Punktzahl aus der Fläche
{% set m = 5.0 %}
{% set x0 = [[params.MESH_MIN.split(',')[0]|float - m, zx]|min, cfg.mesh_min[0]|float]|max %}
{% set x1 = [[params.MESH_MAX.split(',')[0]|float + m, zx]|max, cfg.mesh_max[0]|float]|min %}
{% set nx = [[(((x1 - x0) / 25.0)|round(0,'ceil')|int) + 1, 3]|max, cfg.probe_count[0]]|min %}
BED_MESH_CALIBRATE MESH_MIN={x0},{y0} MESH_MAX={x1},{y1} PROBE_COUNT={nx},{ny}

The important part is the one that is easily forgotten: setting only MESH_MIN/MESH_MAX achieves nothing – Klipper then still probes 13×13 points, just closer together. The point count has to change too.

CaseResultSafeguard
Object centred 7×7 = 49 points –
Part in the corner 9×6 = 54 area extended to the reference point
Part below mesh_min y 6×6 = 36 clamped to 84, no Klipper error
Part larger than the mesh 13×13 = 169 clamped to the configured limits
Parameters missing 13×13 = 169 plain BED_MESH_CALIBRATE

The reference point must lie within the probed area – otherwise the mesh has no valid zero reference.

And then it did nothing

The first test run with exactly the command the macro issues produced a mesh over the entire bed. 13×13 points, as always. No error, no warning, response ok.

The reason was in four lines that belong to the Eddy installation and override Klipper's original command:

yaml
[gcode_macro BED_MESH_CALIBRATE]
rename_existing: BTT_BED_MESH_CALIBRATE
gcode:
  BTT_BED_MESH_CALIBRATE METHOD=rapid_scan    # <- kein {rawparams}
Silently ineffective

The wrapper calls the original with a fixed parameter and passes nothing through. MESH_MIN, MESH_MAX and PROBE_COUNT were discarded – no error, no hint. The next print would simply have looked no different.

This is exactly what a test run protects against and a code review does not: the command was correct, the syntax was correct, the response was ok. Only the result was the old one. An error that reports itself as success can only be told apart from success by its result.

The fix passes everything through and keeps the original behaviour when nobody supplies their own METHOD:

yaml
{% if 'METHOD' in (rawparams|upper) %}
  BTT_BED_MESH_CALIBRATE {rawparams}
{% else %}
  BTT_BED_MESH_CALIBRATE METHOD=rapid_scan {rawparams}
{% endif %}

After the fix, the test run confirms what was intended: 49 points over 135–265 mm, mean +0.005 mm, 19 of the 49 points negative. So the zero reference holds within the partial area as well.

And the side effect is the actual gain: because the point count grows with the area, the point spacing drops from 32.3 to 21.6 mm. You probe less and finer at the same time.

Is a finer mesh worth it?

If adaptive probing already improves resolution – would even finer be even better? That can be measured rather than estimated, because 7×7, 5×5 and 3×3 are exact subsets of a 13×13 grid. You interpolate from the coarse points and compare with the actually measured fine ones.

GridPointsSpacingRMS errorMax error
7×7 49 64.7 mm 0.0067 mm 0.0130 mm
5×5 25 97.0 mm 0.0077 mm 0.0191 mm
4×4 16 129.3 mm 0.0122 mm 0.0223 mm
3×3 9 194.0 mm 0.0192 mm 0.0371 mm

Even with only every second point, the error is 6.7 µm. But that alone is not a sufficient argument: maybe there is real structure there that should be captured. Hence the second question – is this fine structure reproducible at all? Two independent surveys of the same bed, split into the part a coarse grid already sees and the rest:

ComponentMagnitude
Bed warp 108 µm
Stored offset 100 µm
Fine structure 5.5 µm
of which real 2.7 µm
Half of it is noise

Of the fine structure that a 13×13 grid captures in addition to 7×7, half is different from one measurement to the next. A finer mesh does not capture more bed shape but bakes a different noise pattern into the correction on every print.

Measured against a 0.32 mm first layer: bed warp accounts for 33.8 % – that is what the mesh is for, and a coarse grid captures it completely. The reproducible fine structure accounts for 0.9 %. A stored Z offset that sat unnoticed in the configuration at the time was, at 100 µm, thirty-seven times larger than that.

That is the real lesson of this calculation, and it applies far beyond bed meshes: before increasing the resolution of a measurement, it pays to check whether the biggest error is in that measurement at all. Here it was somewhere else – in a number that had sat unnoticed in a text file for months.

On top of that, mesh_pps: 5 inserts five interpolated points between every two measured ones anyway. The applied correction surface is already smooth and dense; more probe points only make it jumpier. If you really want to improve accuracy, increase samples – that fights the noise instead of sampling it.

Test setup and scope

Printer: HevORT, Klipper v0.13.0-743, BTT Eddy USB, Phaetus Rapido 2, 0.4 mm nozzle. Slicer: OrcaSlicer 2.4.2. Profile: PLA on PEI, 0.32/0.32 mm, 230/60 °C, 26 mm³/s, 8 % Lightning, Z-hop 0.2 mm Slope.

All numbers come from measurements on this machine. They apply to this printer and this model, not in general.

This series

One evening of troubleshooting a chain octopus produced four topics that can be read independently:

  1. Klipper Bed Mesh: A Missing Zero Reference Costs 0.14 mm
  2. Print-in-Place: Layer Height Decides Whether Joints Move
  3. OrcaSlicer & Klipper: 34 % Less Print Time – this article
  4. Seven Fallacies in Klipper Troubleshooting

// related posts

> echo "your thoughts" >> orcaslicer-klipper-34-percent-less-print-time.responses

Post your comment

Required for comment verification