Camera-assisted XY (and optional Z) tool alignment for multi-tool 3D printers running the Klipper Tool Changer plugin. No manual math — align each tool over the crosshair and Axiscope calculates the offset for you.
Axiscope provides:
- Manual XY calibration using live camera feedback
- Quick T0 → Tn alignment, tool by tool
- Automatic offset calculation — just align and capture, no math required
- Optional automatic Z calibration via a fixed or portable endstop switch
- Optional camera LED control (Ember Prototype CXC V2 camera)
| Option | Details |
|---|---|
| DIY | XY Nozzle Alignment Camera (3D printed parts) + OV9726 camera module + 4x 5V 3mm round white (6000-6500K) LEDs |
| Pre-assembled | Ember Prototypes CXC — long USB cable, ready to mount, portable z endstop |
The CXC V2 camera from Ember Prototypes has an onboard PWM-controlled LED that Axiscope can drive directly from a spare MCU PWM pin. See LED Control.
To use automatic Z calibration you need:
- An endstop switch mounted at a known (or portable) position
- The corresponding config added to
printer.cfg— see Configuration
Prerequisites:
- Klipper installed and running
- Klipper Tool Changer
- Moonraker configured
- Camera set up in Crowsnest & Mainsail
- SSH access to your printer
Install:
curl -sSL https://raw.githubusercontent.com/nic335/Axiscope/refs/heads/main/install.sh | bashThis script will:
- Create a Python virtual environment
- Install required dependencies
- Set up the systemd service
- Configure Moonraker integration
Start the service:
- Open Mainsail
- Go to Klipper → Service → Host Control and find
axiscope - Use the Start/Stop button
Add an [axiscope] section to printer.cfg. Only pin and the three zswitch_*_pos values are required (and only if you want automatic Z calibration):
[axiscope]
pin: !PG15 # Endstop pin
zswitch_x_pos: 226.71 # REQUIRED - X position of the endstop switch
zswitch_y_pos: -18.46 # REQUIRED - Y position of the endstop switch
zswitch_z_pos: 7.8 # REQUIRED - Z position + clearance of the endstop switch
lift_z: 1 # OPTIONAL - Z lift before XY moves (default: 1)
move_speed: 60 # OPTIONAL - XY move speed, mm/s (default: 60)
z_move_speed: 10 # OPTIONAL - Z move speed, mm/s (default: 10)
led_pwm_pin: PB3 # OPTIONAL - PWM pin driving the camera LED
led_pwm_cycle_time: 0.010 # OPTIONAL - PWM cycle time, s (default: 0.010)
led_pwm_initial_value: 0 # OPTIONAL - Initial LED brightness 0.0-1.0 (default: 0)
start_gcode:
{% set tools = printer.toolchanger.tool_numbers %}
{% for tool in tools %}
M104 T{tool} S150
{% endfor %}
before_pickup_gcode: M118 pickup_gcode
after_pickup_gcode: M118 after_pickup_gcode
finish_gcode: M118 Calibration complete
{% set tools = printer.toolchanger.tool_numbers %}
{% for tool in tools %}
M104 T{tool} S0
{% endfor %}
T0| Option | Required | Default | Description |
|---|---|---|---|
pin |
Yes* | — | Endstop pin used for Z calibration |
zswitch_x_pos |
Yes* | — | X position of the endstop switch |
zswitch_y_pos |
Yes* | — | Y position of the endstop switch |
zswitch_z_pos |
Yes* | — | Z position + clearance of the endstop switch |
lift_z |
No | 1 |
Z lift (mm) before XY moves |
move_speed |
No | 60 |
XY move speed (mm/s) |
z_move_speed |
No | 10 |
Z move speed (mm/s) |
led_pwm_pin |
No | — | MCU PWM pin driving the camera LED |
led_pwm_cycle_time |
No | 0.010 |
LED PWM cycle time (s) |
led_pwm_initial_value |
No | 0 |
Initial LED brightness (0.0-1.0) |
*Only required if using automatic Z calibration.
- Home the printer with
T0selected - Jog the nozzle so it's centered directly over the endstop pin
- Note the current X, Y, Z position from your interface
- Use X and Y directly for
zswitch_x_pos/zswitch_y_pos - Add ~3mm clearance to Z for
zswitch_z_pos(more if hotends vary in length)
Example: readings of X:226.71 Y:-18.46 Z:4.8 become:
zswitch_x_pos: 226.71
zswitch_y_pos: -18.46
zswitch_z_pos: 7.8 # 4.8 + 3mm clearanceIf you use a portable endstop (e.g. the CXC V2 camera), you don't need to manually edit zswitch_*_pos and restart Klipper every time you reposition it — update it live instead:
- Home the printer and jog the nozzle 1-2mm above the endstop switch
- In the Axiscope UI, click Set Endstop Position in the positioning panel
- Axiscope immediately updates its in-memory X/Y/Z endstop position to the toolhead's current position
Equivalent G-code:
AXISCOPE_SET_ENDSTOP_POSITION CURRENT=1 # X, Y, Z = current toolhead position
AXISCOPE_SET_ENDSTOP_POSITION X=150.0 # Only X
AXISCOPE_SET_ENDSTOP_POSITION X=150.0 Y=200.0 CURRENT=1 # X, Y explicit; Z from current positionNote: This updates the in-memory position for the current session only. To persist it across restarts, copy the reported values into
zswitch_x_pos,zswitch_y_pos, andzswitch_z_posin the[axiscope]section ofprinter.cfg.
If your camera has an onboard LED (e.g. Ember Prototype V2 / CXC), Axiscope can drive it via PWM directly from the printer's MCU:
- Wire the LED PWM input to a spare PWM-capable MCU pin
- Set
led_pwm_pinin[axiscope](see Basic Setup) - Restart Klipper
Once configured, a brightness slider (light bulb icon) automatically appears in the Axiscope camera panel — drag it to set brightness 0-100%. It stays hidden if led_pwm_pin isn't set.
Control it directly from the console or a macro:
AXISCOPE_SET_LED VALUE=0.5 # 0.0 (off) to 1.0 (full brightness)Axiscope supports templated G-code macros with full Jinja support:
| Macro | Runs |
|---|---|
start_gcode |
At the start of calibration |
before_pickup_gcode |
Before each tool change |
after_pickup_gcode |
After each tool change |
finish_gcode |
After calibration completes |
If you connect using a hostname (e.g. voron.local:3000), add *.local:* to moonraker.conf:
[authorization]
trusted_clients:
192.168.0.0/16
10.0.0.0/8
127.0.0.0/8
169.254.0.0/16
172.16.0.0/12
FE80::/10
::1/128
cors_domains:
*.lan
*.local
*.local:*
*://localhost
*://localhost:*
*://my.mainsail.xyz
*://app.fluidd.xyz- Open
http://<your-printer-ip>:3000 - Select the printer to calibrate (autofill support coming soon)
- Select the camera to use
- Align
T0perfectly to the crosshair, then Capture Position - Switch to
Tn, re-align to center, then press X and Y in the side navigator to compute the offset
Make sure the camera is configured in both Crowsnest and Mainsail:
Some cameras aren't auto-detected by Crowsnest if plugged in after boot. Restart the Crowsnest service instead of rebooting the whole printer.
Cause: Both the toolchanger's calibrate_offsets.cfg and Axiscope are configured, and both use the probe_multi_axis chip name.
Fix:
- Remove/comment out the
calibrate_offsets.cfginclude inprinter.cfg, or - Only use one of the two calibration methods, not both
Only Axiscope should own the probe_multi_axis module.
MIT License — see LICENSE or the full text below.
MIT License text
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.





