A while ago, I was browsing the internet, checking out electronics prices. That rabbit hole led me to 3D printer boards, which led to 3D printers, and then I noticed something interesting.

There was a printer priced way below its competitors that caught my eye. I started digging into it: the Artillery X4 Pro. The reviews, comments, and user thoughts were polarized opinions were either "it's amazing" or "it's a disaster." I even found reports on Reddit of printers literally catching fire.

Naturally, I had to buy it to see for myself. Since it was Klipper-based with an MKS Pi clone board, I had high hopes that I could turn this questionable machine into a mainline Klipper beast. (Spoiler alert: I did).

1. The Immediate Disaster

When it arrived, I unboxed it and went through the standard calibration steps. I am not kidding when I say this: it immediately bricked.

I emailed support, and they told me they needed to mail me a new EMMC module. I thought, "What do you mean send a new part? This is a brand new printer; it shouldn't be that easy to brick!"

Turns out, it was. I spent 3–4 hours troubleshooting and trying different things. Finally, by pure luck, I wondered if this printer had a serial connection like the original MKS Pi boards. I grabbed a USB-C cable, tried a couple of settings in TeraTerm, and finally I was in the system terminal.

From there, I connected the printer to the internet via the terminal, used Fluidd to correct the Klipper config to the official one, and it started working. I quickly updated the software to ensure I’d never live through that nightmare again. Minutes later, I printed my first Benchy.

2. Early Hardware Failures

In its first week, both fans the hotend fan and the part cooling fan failed perfectly. I replaced them, but otherwise, the printer was behaving. I was happy for the moment.

For a couple of months, I experimented with new things, writing my own macros for this "pile of junk," and it served me well while building my Thor robot arm. But then, the nightmares began.

The Z-offset was simply not working correctly. I tried everything to keep it stock (at least for a while), but the inductive sensor Artillery put on this thing was just too inaccurate.

3. Solving the Z-Offset Nightmare

After a month of troubleshooting, I looked into community mods and decided to give a few of them a go. The priority was replacing that terrible inductive sensor with a BIQU MicroProbe to improve probing accuracy.

I printed a custom baseplate (using my P1S), connected the sensor and hotend to it, and opened up the back of the printer. The board looked very similar to other MKS Pi clones. I soldered pins to the servo connector on the board, removed the old inductive sensor's pins, and utilized the old probe pin and the servo connector. I connected the BIQU MicroProbe (following the manual rigorously), booted it up, and updated the macros to support it.

Note: The specific macro configuration and base model for the MicroProbe was adapted from community resources.

It worked correctly with no issues for a while. However, the mods were tempting, and a bigger issue had emerged: the Z-axis was still quite off, sometimes hitting the print.

4. Mechanical Overkill

Troubleshooting filament settings didn't help. I decided the issue was the Z-wobble "fix" Artillery installed—they recommended sticking a piece of plastic in there to increase friction. Bruh.

The only logical solution was to upgrade it further. I decided to perform a linear rail mod and a spring-loaded T8 anti-backlash nut mod. The community had already created the models, so why not?

To be honest, I’m pretty sure just changing the T8 screws to spring-loaded ones would have worked, but I wanted to do this so bad.

I disassembled the printer (and while I was at it, added heatsinks to the motors), installed the rails, oiled them to spec, installed the T8 screws with springs, and reassembled the gantry. I also removed the upper belt as it was only causing synchronization issues.

5. Quality of Life Upgrades

At this point, accuracy had improved massively. But there were still issues bugging me:

  1. Old Klipper bugs were still present.
  2. The filament sensor and roll placement were on top (terrible balance).
  3. Temperatures were unstable, and the bottom cooling fan was noisy.
  4. The nozzle wasn't accessible enough, and the heater lacked a safety groove for electrical shorts (which makes me wonder if this caused those fires I read about).

I started with the easiest fix: the bottom fan. There was a community mod to replace it with an 80x10mm fan. I printed the bracket, soldered the connectors, and installed it. Silence achieved, and temperatures dropped.

Next was the filament placement. I couldn't find a mod I liked, so I modeled one myself to mount the sensor on the upper side corner. I placed the filament in a dryer next to the printer, connected PTFE tubes from the dryer to the sensor, and then to the hotend. Now, the filament is loaded from the side, fully encapsulated in PTFE to protect it from moisture.

Then came the hotend. There was a mod to use Bambu Lab hotends (I already had one, so it was a no-brainer). However, the original mod was for an inductive sensor, making the hotend too short. I remixed the model 4 or 5 times, disassembled and reassembled it until the length was perfect. After calibration, the print quality got a massive uplift. I was impressed.

6. The Firmware Deep Dive

Finally, the moment I had been waiting for since I bought this printer: porting it to mainline Klipper.

I searched online and found a few people who had done this. Many were disabling the screen and using plain MainsailOS or Armbian. I decided to go with Armbian-MKSPI since the printer effectively runs on an MKS Pi clone.

During my research, I found the Tortillery Project. It was a disk image with no source code, which meant trusting a total stranger with my printer. But since they had already done the linear rail and probe mods, I decided to give it a try.

Warning: Installing random images from the internet is dangerous. I checked the entire disk and services after flashing, but do this at your own risk!

I flashed the image, isolated the printer from the internet, installed the EMMC module, and booted it up. The first thing I noticed was that Klipper updated exactly like it does on official firmware.

I dug through services, files, and packages nothing malicious found. I realized something cool about the MCU update process: the Pi is connected to the internal ports of the MCU. When an update file is found on USB, it triggers an internal connection, puts the MCU into DFU mode, and updates it. Genius.

Even the screen works because the developer ported the Elegoo Neptune 3 Pro's KlipperLCD mechanism (same screen, different firmware KlipperLCD for Elegoo Neptune 3 Pro available on github). I still plan to build the image myself eventually, but for now, I'm happy.

7. Advanced Macros: KAMP

Now, into the software customization. I wanted Klipper Adaptive Meshing and Purging (KAMP). Native Klipper recently implemented adaptive leveling, so I wrote a macro to utilize it.

Here is my START_PRINT and KAMP setup:

[gcode_macro START_PRINT]
description: Start the printing.
gcode:
    RESPOND TYPE=command MSG='Start START_PRINT'
    GANTRY_LIGHT_ON                                              # Turn on Bridge LEDs
    SET_TEMPERATURE_FAN_TARGET TEMPERATURE_FAN=Board TARGET=30
    G90                                                          # Absolute Positioning.
    {% set BED_TEMP = params.BED_TEMP|default(65)|float %}       # Set the bed temperature.
    {% set EXTRUDER_TEMP = params.EXTRUDER_TEMP|default(220)|float %} # Set the extruder temp.
    M190 S{BED_TEMP}
    M109 S140
    G28                                                          # Home all axes.
    G28
    {% set X = params.X|float %}
    {% set Y = params.Y|float %}
    
    # Adaptive Meshing Logic
    M420 # Load bed mesh (or KAMP logic takes over here via Exclude Objects)
    
    M220 S100                                                   # Reset Speed factor
    M221 S100                                                   # Reset Extrusion factor
    DRIP_OUTSIDE                                                # Move to purge bucket
    M140 S{BED_TEMP}
    M109 S{EXTRUDER_TEMP}
    M190 S{BED_TEMP}
    
    NOZZLE_WIPE
    PURGE_PATTERN X={X} Y={Y}                                   # Adaptive Purge
    G1 E1 F2400
    RESPOND TYPE=command MSG='End START_PRINT'

[gcode_macro KAMP]
description: Klipper Adaptive Meshing.
gcode:
    RESPOND TYPE=command MSG='Start KAMP'
    {% set MG = params.MG|default(0) %}
    GANTRY_LIGHT_ON
    G28
    # ... (Heating logic) ...
    G28
    {% if MG == '0' %}                                   
        BED_MESH_CALIBRATE PROFILE=adaptive ADAPTIVE=1
    {% else %}  
        BED_MESH_CALIBRATE PROFILE=adaptive ADAPTIVE=1 ADAPTIVE_MARGIN={MARGIN}
    {% endif %}
    RESPOND TYPE=command MSG='End KAMP'

Slicer Configuration

To make adaptive meshing work, the slicer needs to tell the printer where the object is. In your slicer's Start G-code section, you need to modify the call to START_PRINT to pass the print area dimensions.

For example, in Orca Slicer or Prusa Slicer, you would verify that "Exclude Objects" is enabled in your firmware settings, and update your start G-code to look something like this:

M104 S0 ; Stops slicer from forcing temps
M140 S0
START_PRINT BED_TEMP=[first_layer_bed_temperature] EXTRUDER_TEMP=[first_layer_temperature] X={first_layer_print_min[0]} Y={first_layer_print_min[1]}

This passes the X and Y coordinates of the print, allowing the macro to mesh only the area needed. With this, adaptive meshing started working wonderfully I haven't had a single failed print since.

8. Final Tuning

I also adjusted other aspects of the printer, including modifying Z-motor currents for better stability and implementing Screws Tilt Adjust for easier bed leveling.

I created a macro to help calculate the probe Z-offset accurately, incorporating a heat soak to ensure thermal expansion is accounted for:

[gcode_macro CALCULATE_PROBE_Z_OFFSET]
description: Calculates the probe z_offset from a set of measurements.
gcode:
    RESPOND TYPE=command MSG='Start CALCULATE_PROBE_Z_OFFSET'
    {% set DELAY = params.DELAY|default(30)|int * 1000 %}
    {% set BED_TEMP = params.BED_TEMP|default(65)|float %}
    {% set NOZZLE_TEMP = params.NOZZLE_TEMP|default(140)|float %}
    
    # Heat up and wait
    M190 S{BED_TEMP}
    G4 P{DELAY}
    M109 S{NOZZLE_TEMP}
    
    G28
    G28
    PROBE_CALIBRATE
    RESPOND TYPE=command MSG='End CALCULATE_PROBE_Z_OFFSET'

Final Words

This journey was wild. From a bricked unit in the first hour to a heavily modified, mainline Klipper beast running on custom firmware and linear rails. I learned so much while tinkering with this printer.

It’s now isolated, stable, and churning out prints with incredible accuracy. Happy 3D printing!