# Cartographer probe installation on vanilla Klipper

#### <span style="color: rgb(224, 62, 45);">**Disclamer**</span>

<p class="callout danger">All actions are at your own risk. Modifying the printer in this way will invalidate the warranty! None of us are responsible for any problems associated with this manual. If you do not feel comfortable with the steps shown on this page - **STOP**.</p>

<p class="callout danger">Before taking any step, common sense dictates that you read the **ENTIRE GUIDE** before making any changes.</p>


#### Abbreviations

- *configuration file* - /config/printer.cfg file
- *macros* - <span class="navigation-container" data-v-4b5d1549=""><span class="cursor-pointer navigation-segment" data-v-4b5d1549="" role="button" tabindex="0">/config</span></span><span class="navigation-container" data-v-4b5d1549=""><span class="navigation-divider text--disabled" data-v-4b5d1549="">/</span><span data-v-4b5d1549="">macros/macros.cfg file</span></span>
- *homing macro* - <span class="navigation-container" data-v-4b5d1549=""><span class="cursor-pointer navigation-segment" data-v-4b5d1549="" role="button" tabindex="0">/config</span></span><span class="navigation-container" data-v-4b5d1549=""><span class="navigation-divider text--disabled" data-v-4b5d1549="">/</span><span data-v-4b5d1549="">macros/sensorless\_homing\_override.cfg file</span></span>

#### <span class="navigation-container" data-v-4b5d1549=""><span data-v-4b5d1549="">Legend</span></span>

<span style="color: rgb(45, 194, 107);">**<span class="navigation-container" data-v-4b5d1549=""><span data-v-4b5d1549="">Line to be added.</span></span>**</span>

<span style="color: rgb(224, 62, 45);">**<span class="navigation-container" data-v-4b5d1549=""><span data-v-4b5d1549="">Line to be deleted.</span></span>**</span>

#### Introduction

From the moment I discovered Cartographer, I wanted to use it all the time. After waiting for the order and finally being at my desk, I could start the installation and configuration on my QIDI X-Max 3 with Vanilla Klipper \[[1](https://github.com/leadustin/QIDI-up2date-english/blob/main/Klipper-Update/update+upgrade.md)\], because it's always cool to have up to date software and use whatever extension/functionality you want (Spoolman! - from this point on I have a database of my stock). It was not easy, but with the help of McSneaky (he is a f\*cking guru) I was able to say "It's alive! I've included all the sources at the bottom of the page if you want to dig deeper (do it - I encourage you NOT to trust me and check every point, even though the whole setup works).

##### To do

- set the position of the nozzle when homing to the centre of the bed (there is no effect on the bed mesh or the probe behaviour).

##### TL;DR

Configuration files (<span style="color: rgb(35, 111, 161);">**blue pill**</span>).

Use my configuration file and the macros \[[2](https://drive.google.com/file/d/15s56RrGnDsqp3EQyPislcNXO5nyOK20N/view?usp=drive_link)\] that works with the printer. Note that you'll have defined all the plugins I use in my setup (Spoolman, Crownest, etc). I encourage you to modify your printer files to match your setup. You have been warned.

#### Hardware

This article is not intended to guide you through the full installation process on the printer itself, but in a nutshell; download the probe holder model provided by McSneaky \[[3](https://www.printables.com/pl/model/692991-qidi-x3-series-beacon-cartographer-probe-low-profi)\] (make sure you print in min. ABS/ASA - my suggestion - PC).

#### Software

(<span style="color: rgb(186, 55, 42);">**red pill**</span>)

##### Installation

Follow the official Cartographer wiki page \[[4](https://docs.cartographer3d.com/cartographer-probe/installation-and-setup/klipper-setup)\]. This is straightforward and requires no change in procedure.

##### Configuration

This is the heart of this guide, as several things need to be modified. At first I followed the official wiki \[[5](https://docs.cartographer3d.com/cartographer-probe/installation-and-setup/cartographer-with-input-shaping-v2-and-v3-hybrid)\], but then I discovered that a few things need to be done differently.

Check your UUID (CAN version) or serial port (USB version):

USB

<div class="group/codeblock grid grid-flow-col max-w-3xl w-full mx-auto decoration-primary/6 page-api-block:ml-0" id="bkmrk-ls-%2Fdev%2Fserial%2Fby-id"><div class="flex items-center justify-start [grid-area:1/1] text-sm gap-2">`ls /dev/serial/by-id/`</div><button class="group-hover/codeblock:opacity-[1] transition-opacity duration-75 opacity-0 text-xs [grid-area:2/1] z-[2] justify-self-end backdrop-blur-md leading-none self-start ring-1 ring-dark/2 text-dark/7 bg-transparent rounded-md mr-2 mt-2 p-1 hover:ring-dark/3 dark:ring-light/2 dark:text-light/7 dark:hover:ring-light/3 print:hidden">  
</button></div>CAN

<div class="group/codeblock grid grid-flow-col max-w-3xl w-full mx-auto decoration-primary/6 page-api-block:ml-0" id="bkmrk-%7E%2Fklippy-env%2Fbin%2Fpyt"><div class="flex items-center justify-start [grid-area:1/1] text-sm gap-2">`~/klippy-env/bin/python ~/klipper/scripts/canbus_query.py can0`</div></div><div class="flex items-center justify-start [grid-area:1/1] text-sm gap-2" id="bkmrk-"></div><div class="flex items-center justify-start [grid-area:1/1] text-sm gap-2" id="bkmrk--1"></div><div class="flex items-center justify-start [grid-area:1/1] text-sm gap-2" id="bkmrk-now-we-need-to-set-c">Now we need to set the Cartographer configuration in the *configuration file*:</div><div class="flex items-center justify-start [grid-area:1/1] text-sm gap-2" id="bkmrk--2"></div><div class="flex items-center justify-start [grid-area:1/1] text-sm gap-2" id="bkmrk--3"></div>> <div class="flex items-center justify-start [grid-area:1/1] text-sm gap-2" id="bkmrk-%5Bcartographer%5Dserial-1">[cartographer]  
> serial:  
> \# Path to the serial port for the Cartographer device. Typically has the form  
> \# /dev/serial/by-id/usb-cartographer_cartographer_...  
> \#   
> \# If you are using the CAN Bus version, replace serial: with canbus_uuid: and add the UUID.  
> \# Example: canbus_uuid: 1283as878a9sd  
> \#  
> speed: 40.  
> \# Z probing dive speed.  
> lift_speed: 5.0  
> \# Z probing lift speed.  
> backlash_comp: 0.5  
> \# Backlash compensation distance for removing Z backlash before measuring  
> \# the sensor response.  
> \#   
> \# Offsets are measured from the centre of your coil, to the tip of your nozzle   
> \# on a level axis. It is vital that this is accurate.   
> \#  
> x_offset: 0.0  
> \# X offset of cartographer from the nozzle.  
> y_offset: 21.1  
> \# Y offset of cartographer from the nozzle.  
> trigger_distance: 2.0  
> \# cartographer trigger distance for homing.  
> trigger_dive_threshold: 1.5  
> \# Threshold for range vs dive mode probing. Beyond `trigger_distance +  
> \# trigger_dive_threshold` a dive will be used.  
> trigger_hysteresis: 0.006  
> \# Hysteresis on trigger threshold for untriggering, as a percentage of the  
> \# trigger threshold.  
> cal_nozzle_z: 0.1  
> \# Expected nozzle offset after completing manual Z offset calibration.  
> cal_floor: 0.1  
> \# Minimum z bound on sensor response measurement.  
> cal_ceil: 5.0  
> \# Maximum z bound on sensor response measurement.  
> cal_speed: 1.0  
> \# Speed while measuring response curve.  
> cal_move_speed: 10.0  
> \# Speed while moving to position for response curve measurement.  
> default_model_name: default  
> \# Name of default cartographer model to load.  
> mesh_main_direction: x  
> \# Primary travel direction during mesh measurement.  
> \#mesh_overscan: -1  
> \# Distance to use for direction changes at mesh line ends. Omit this setting  
> \# and a default will be calculated from line spacing and available travel.  
> mesh_cluster_size: 1  
> \# Radius of mesh grid point clusters.  
> mesh_runs: 2  
> \# Number of passes to make during mesh scan.</div>

<div class="flex items-center justify-start [grid-area:1/1] text-sm gap-2" id="bkmrk--4"></div>Since X-Max 3 has adxl345, I'll ignore the Input Shaper part of the configuration. The next step is to delete the `[probe]` section in the *configuration file:*

> **<span style="color: rgb(224, 62, 45);">\[probe\]</span>**  
> **<span style="color: rgb(224, 62, 45);">pin: ^!MKS\_THR:gpio21</span>**  
> **<span style="color: rgb(224, 62, 45);">x\_offset: 28</span>**  
> **<span style="color: rgb(224, 62, 45);">y\_offset: 4.4</span>**  
> **<span style="color: rgb(224, 62, 45);">\#z\_offset: 0.0</span>**  
> **<span style="color: rgb(224, 62, 45);">speed: 5</span>**  
> **<span style="color: rgb(224, 62, 45);">samples: 2</span>**  
> **<span style="color: rgb(224, 62, 45);">samples\_result: average</span>**  
> **<span style="color: rgb(224, 62, 45);">sample\_retract\_dist: 3.0</span>**  
> **<span style="color: rgb(224, 62, 45);">samples\_tolerance: 0.08</span>**  
> **<span style="color: rgb(224, 62, 45);">samples\_tolerance\_retries:3</span>**

Do NOT add `[safe_z_home]` as suggested in the Cartho wiki. We will use the `[homing_override]` in *homing macro*. It is more elegant.<button class="group-hover/codeblock:opacity-[1] transition-opacity duration-75 opacity-0 text-xs [grid-area:2/1] z-[2] justify-self-end backdrop-blur-md leading-none self-start ring-1 ring-dark/2 text-dark/7 bg-transparent rounded-md mr-2 mt-2 p-1 hover:ring-dark/3 dark:ring-light/2 dark:text-light/7 dark:hover:ring-light/3 print:hidden"></button>

> \[homing\_override\]  
> axes: xyz  
> set\_position\_z: 0  
> gcode:
> 
>  {% set home\_all = 'X' not in params and 'Y' not in params and 'Z' not in params %} #  
>  {% set z\_hop\_speed = (printer.configfile.settings\['stepper\_z'\].homing\_speed \* 30) | float %} #  
>  {% set travel\_speed = (printer.toolhead.max\_velocity \* 30) | float %} #  
>  {% set sensorless\_variables = printer\["gcode\_macro \_Sensorless\_Homing\_Variables"\] %} #  
>  {% set z\_hop\_distance = sensorless\_variables.z\_hop\_distance | float %} # Collect all variables needed for sensorless homing  
>  {% set first\_homed\_axis = sensorless\_variables.first\_homed\_axis | string %} # from machine config file and \_Sensorless\_Homing\_Variables  
>  {% set second\_homed\_axis = sensorless\_variables.second\_homed\_axis | string %} #  
>  {% set safe\_x = sensorless\_variables.safe\_x | float %} #  
>  {% set safe\_y = sensorless\_variables.safe\_y | float %} #  
>  {% set safe\_z = sensorless\_variables.safe\_z\_enable | abs %} #
> 
> <span style="color: rgb(45, 194, 107);"> **{% if printer.configfile.settings.cartographer is defined %} # Check if a third-party \[cartographer\] definiton is used**</span>  
> <span style="color: rgb(45, 194, 107);"> **{% set probe\_name = printer.configfile.settings.cartographer %} # If \[cartographer\] is found in config, set 'probe\_name' as \[cartographer\] config string**</span>
> 
> <span style="color: rgb(224, 62, 45);">**{% if printer.configfile.settings.beacon is defined %} # Check if a third-party \[probe\] definiton is used**</span>  
>  {% elif printer.configfile.settings.beacon is defined %} #   
>  {% set probe\_name = printer.configfile.settings.beacon %} # If \[beacon\] is found in config, set 'probe\_name' as \[beacon\] config string  
>  {% elif printer.configfile.settings.probe is defined %} #  
>  {% set probe\_name = printer.configfile.settings.probe %} # If \[probe\] is found in config, set 'probe\_name' as \[probe\] config string  
>  {% elif printer.configfile.settings.dockable\_probe is defined %} #  
>  {% set probe\_name = printer.configfile.settings.dockable\_probe %} # If \[dockable\_probe\] is found in config, set 'probe\_name' as \[dockable\_probe\] config string  
>  {% elif printer.configfile.settings.bltouch is defined %} #  
>  {% set probe\_name = printer.configfile.settings.bltouch %} # If \[bltouch\] is found in config, set 'probe\_name' as \[bltouch\] config string  
>  {% endif %} #
> 
>  {% if 'probe' in printer.configfile.settings.stepper\_z.endstop\_pin %} # Check if Z is configured to home with a probe and pull config values for  
>  {% set probe\_x\_offset = probe\_name.x\_offset | float %} # X and Y offsets  
>  {% set probe\_y\_offset = probe\_name.y\_offset | float %} #  
>  {% else %} #  
>  {% set probe\_x\_offset = 0 | float %} #  
>  {% set probe\_y\_offset = 0 | float %} # If Z if not homed with a probe, set offsets to 0 (do not apply an offset)  
>  {% endif %} #
> 
>  {% if safe\_x == -128 %} #  
>  {% set safe\_x = (printer.configfile.settings.stepper\_x.position\_max) /2 %} # If safe\_x is '-128', set safe\_x to the center of the X axis  
>  {% endif %} #
> 
>  {% if probe\_x\_offset &lt; 0 %} #  
>  {% set safe\_x = safe\_x + probe\_x\_offset %} #  
>  {% elif probe\_x\_offset &gt; 0 %} # Depending on if probe\_x\_offset is a positive or negative value, adjust safe\_x  
>  {% set safe\_x = safe\_x - probe\_x\_offset %} # If the machine does not home Z with a probe, an offset is not applied.  
>  {% endif %} #
> 
>  {% if safe\_y == -128 %} #   
>  {% set safe\_y = (printer.configfile.settings.stepper\_y.position\_max) /2 %} # If safe\_y is '-128', set safe\_y to the center of the Y axis  
>  {% endif %} #
> 
>  {% if probe\_y\_offset &lt; 0 %} #  
>  {% set safe\_y = safe\_y + probe\_y\_offset %} #  
>  {% elif probe\_y\_offset &gt; 0 %} # Depending on if probe\_y\_offset is a positive or negative value, adjust safe\_y  
>  {% set safe\_y = safe\_y - probe\_y\_offset %} # If the machine does not home Z with a probe, an offset is not applied.  
>  {% endif %} #
> 
>  {% if z\_hop\_distance &gt; 0 %} # Check if z\_hop\_distance is greater than zero  
>  {% if 'x' not in printer.toolhead.homed\_axes and 'y' not in printer.toolhead.homed\_axes %} # If X and Y are not homed, move Z to z\_hop\_distance  
>  {% if first\_homed\_axis != 'Z' %}  
>  G0 Z{z\_hop\_distance} F{z\_hop\_speed} #  
>  {% endif %}  
>  {% endif %} #  
>  {% endif %} #
> 
>  {% if first\_homed\_axis == 'X' %} # If first\_homed\_axis is 'X', begin G28 param check  
>  {% if home\_all or 'X' in params %} #  
>  \_HOME\_X # If home\_all or 'X' is in params, home X  
>  {% endif %} #  
>  {% if home\_all or 'Y' in params %} # If home\_all or 'Y' in params, home Y  
>  \_HOME\_Y #  
>  {% endif %} #  
>  {% endif %} #
> 
>  {% if first\_homed\_axis == 'Y' %} # If first\_homed\_axis is 'Y', begin G28 param check  
>  {% if home\_all or 'Y' in params %} #  
>  \_HOME\_Y # if home\_all or 'Y' is in params, home Y  
>  {% endif %} #  
>  {% if home\_all or 'X' in params %} # If home\_all or 'X' in params, home X  
>  \_HOME\_X #  
>  {% endif %} #  
>  {% endif %} #
> 
>  {% if first\_homed\_axis == 'Z' %}  
>  {% if home\_all or 'Z' in params %}  
>  \_HOME\_Z  
>  {% endif %}  
>  {% if second\_homed\_axis == 'X' %}  
>  {% if home\_all or 'X' in params %}  
>  \_HOME\_X  
>  {% endif %}  
>  {% if home\_all or 'Y' in params %}  
>  \_HOME\_Y  
>  {% endif %}  
>  {% endif %}  
>  {% if second\_homed\_axis == 'Y' %}  
>  {% if home\_all or 'Y' in params %}  
>  \_HOME\_Y  
>  {% endif %}  
>  {% if home\_all or 'X' in params %}  
>  \_HOME\_X  
>  {% endif %}  
>  {% endif %}  
>  {% endif %}
> 
>  {% if safe\_z == True and (home\_all or 'Z' in params) and first\_homed\_axis != 'Z' %} # If safe\_z is true and home\_all or 'Z' is in params,  
>  G0 X{safe\_x} Y{safe\_y} F{travel\_speed} # Move to the defined safe XY location  
>  {% endif %} #
> 
>  {% if home\_all or 'Z' in params %} #  
>  {% if first\_homed\_axis != 'Z'%}  
>  \_HOME\_Z  
>  {% endif %}  
>  {% endif %} #

Now let's start modifying the *configuration file*.

> \[bed\_mesh\]  
> <span style="color: rgb(224, 62, 45);">**speed: 150 # Speed**</span>
> 
> <span style="color: rgb(45, 194, 107);">**speed: 500 # Speed**</span>  
> horizontal\_move\_z: 10 # Z-axis height  
> <span style="color: rgb(224, 62, 45);">**mesh\_min: 25,10 # Minimum position of the detection point**</span>
> 
> <span style="color: rgb(45, 194, 107);">**mesh\_min: 25,20 # Minimum position of the detection point**</span>  
> mesh\_max: 315,315 # Maximum position of the detection point  
> <span style="color: rgb(224, 62, 45);">**probe\_count: 9,9 # Number of measuring points - 6x6 - 7x7 etc.**</span>
> 
> <span style="color: rgb(45, 194, 107);">**probe\_count: 15,15 # Number of measuring points - 6x6 - 7x7 etc.**</span>  
> algorithm: bicubic  
> bicubic\_tension: 0.2  
> mesh\_pps: 4, 4

When configuring the Z axis, make sure that you are using the virtual end stop:

> \[stepper\_z\]  
> endstop\_pin: probe:z\_virtual\_endstop # use cartographer as virtual endstop  
> homing\_retract\_dist: 0 # cartographer needs this to be set to 0

We're almost done with the configuration part, you're one step away from success.

Let's add a `[PROBE_CALIBRATE]` macro in *macros* to make sure Klipper uses the correct calibration routine:

> \[gcode\_macro PROBE\_CALIBRATE\]  
> gcode:  
>  CARTOGRAPHER\_CALIBRATE

Bravo! Configuration is completed, we can now calibrate our brand new Cartographer probe!

#### Calibration

As the official calibration is not accurate in our case, we need to do some workarounds.

Home the printer using `G28 X Y`.

Move to the centre of the build plate with `G0 X162.5 Y162.5`.

At this point we cannot follow the Cartographer wiki, as our printer slides the bed all the way down (when using `G28`). There is no way to compensate for this with probe calibration. We're going to trick the printer and force it to move the bed all the way down until it touches the nozzle.

First we need to enable the `force_move` macro in *macros*.

> \[force\_move\]
> 
> <span style="color: rgb(224, 62, 45);">**enable\_force\_move: False**</span>
> 
> <span style="color: rgb(45, 194, 107);">**enable\_force\_move: True**</span>

Now we are ready to `SAVE_CONFIG` and restart the printer.

<span style="text-decoration: underline; color: rgb(186, 55, 42);">***NOTE***</span>

<p class="callout danger">Now all safety limits in the Klipper configuration are **DISABLED**. Be aware that you need to be careful and do things slower rather than faster and for God's sake, observe the behaviour of your printer and make sure you have easy access to the OFF button on the back of the X-Max 3.</p>

Going back to the calibration procedure, we are now forcing the printer to move \[[6](https://www.klipper3d.org/Config_Reference.html?h=pixel#force_move)\] and Klipper will not be aware of the physical location of the nozzle. This is fine for now and we will correct this in a few ticks.

According to the official Klipper documentation \[[7](https://www.klipper3d.org/G-Codes.html#force_move_1)\], you must use the following syntax to force the printer to move:

`FORCE_MOVE STEPPER=<config_name> DISTANCE=<value> VELOCITY=<value> [ACCEL=<value>]`

My example:

`FORCE_MOVE STEPPER=stepper_z DISTANCE=-1 VELOCITY=30 [ACCEL=30]`

This will move Z up one millimetre. Slowly move to your Z0. `G28` is moving to Z10, so you need to force the Z axis to 0, then move 10 mm down with `FORCE_MOVE STEPPER=stepper_z DISTANCE=10 VELOCITY=30 [ACCEL=30]` and finally Klipper will align with the physical position of your nozzle.

We can go back to the original probe calibration process.

`CARTOGRAPHER_CALIBRATE`

This is the time to calibrate the height between the nozzle and the bed with your favourite method (the paper supplied by QIDI is fine - it works). Then `SAVE_CONFIG` and reboot the printer.

For safety reasons go to *macros*, set `enable_force_move: False` then `SAVE_CONFIG` and restart the printer.

That is all, you can start inital tests \[[8](https://docs.cartographer3d.com/cartographer-probe/installation-and-setup/cartographer-with-input-shaper-v2-and-v3-hybrid#initial-tests)\].

#### Sources

\[1\] [https://github.com/leadustin/QIDI-up2date-english/blob/main/Klipper-Update/update+upgrade.md](https://github.com/leadustin/QIDI-up2date-english/blob/main/Klipper-Update/update+upgrade.md)

\[2\] [https://drive.google.com/file/d/15s56RrGnDsqp3EQyPislcNXO5nyOK20N/view?usp=drive\_link](https://drive.google.com/file/d/15s56RrGnDsqp3EQyPislcNXO5nyOK20N/view?usp=drive_link)

\[3\] [https://www.printables.com/pl/model/692991-qidi-x3-series-beacon-cartographer-probe-low-profi](https://www.printables.com/pl/model/692991-qidi-x3-series-beacon-cartographer-probe-low-profi)

\[4\] [https://docs.cartographer3d.com/cartographer-probe/installation-and-setup/klipper-setup](https://docs.cartographer3d.com/cartographer-probe/installation-and-setup/klipper-setup)

\[5\] [https://docs.cartographer3d.com/cartographer-probe/installation-and-setup/cartographer-with-input-shaping-v2-and-v3-hybrid](https://docs.cartographer3d.com/cartographer-probe/installation-and-setup/cartographer-with-input-shaping-v2-and-v3-hybrid)

\[6\] [https://www.klipper3d.org/Config\_Reference.html?h=pixel#force\_move](https://www.klipper3d.org/Config_Reference.html?h=pixel#force_move)

\[7\] [https://www.klipper3d.org/G-Codes.html#force\_move\_1](https://www.klipper3d.org/G-Codes.html#force_move_1)

\[8\] [https://docs.cartographer3d.com/cartographer-probe/installation-and-setup/cartographer-with-input-shaper-v2-and-v3-hybrid#initial-tests](https://docs.cartographer3d.com/cartographer-probe/installation-and-setup/cartographer-with-input-shaper-v2-and-v3-hybrid#initial-tests)