I've created a conditional G-code macro that prints a model to help tune pressure advance. It's based on the model used in this post, which I happen to like very much. It's a fast and native solution, starts from your printer directly and requires no slicer or other software.
The macro is a work-in-progress, but offers a lot of configurability already. I will probably make a number of improvements as I use it myself, but I wanted to share this with you at this stage for feedback and educational purposes (i.e. my education).
I can't guarantee that this code will work properly on your machine. I have a simple cartesian model with XYZE axes. Also, I've tested this on my printer with a self-compiled post-beta 5 firmware 3.4.0, since I ran into some rounding issues in the output of earlier RRF firmwares, though this has been fixed by @dc42 and I've seen mentioned that this fix is part of the beta 6 release.
I've kept the variable names as descriptive as possible. However, let me know if you need explanation on how to use this:
; This macro generates a test print to determine the best pressure advance factor (M572 S-parameter)
; It uses the test print described in this forum post:
; https://forum.duet3d.com/topic/6698/pressure-advance-calibration
; Author: Schmart
; WARNING: all dimensions, size units and speeds are in mm and mm/s
; Reference for conditional G-code:
; - https://duet3d.dozuki.com/Wiki/GCode_Meta_Commands
; Wishes for conditional G-code:
; - Canceling or stopping running conditional G-code
; - Simulation; logging what commands would be executed/generated without actually executing them
; - Dumping the variables (names and values) in a macro, perhaps also treating variable in a meta-way, e.g. {#var} for count, {var[0].name} and {var['layer_height'].value}
; - Assigning an object or array to a variable. E.g. var axis = move.axes[0]; echo {var.axis.letter}
; - Custom subroutine/function definition, e.g. def print_line(var a, var b)
; - ceiling(), pow() and sq() functions
; TODO:
; - Many optimizations still remaining
; - Perhaps the most important variables (bed and print temperature, filament diameter, extrusion multiplier, PA stepping)
; can be made into macro parameters, e.g. M98 P"0:/macros/4 - Print Tuning/Pressure Advance" B40 P204 D1.75 X0.95 S0.02 P0.002
; Starting value for pressure advance
var pa_start = 0.0
; Pressure advance increment for each (whole) millimeter print height
; e.g. if var.height = 20, var.pa_start = 0.1 and var.pa_stepping = 0.004, then
; the PA test range is from 0.1 to (0.1 + 20 * 0.004) => 0.1 to 0.18.
var pa_stepping = 0.01
; The extruder to apply the pressure advance factor to
var pa_extruder = 0
; These values specify the center of the print bed
; The default bed center is at (0,0)
var x_center = 0
var y_center = 0
; Print temperatures
var print_temperature = 205
var standby_temperature = 120
var bed_temperature = 40
; Height of the test print
var height = 20
var layer_height = 0.20
; Number of fast segments
var fast_segments = 3
; Slow segments are at the beginning and the end, and in between fast segments, e.g. SLOW FAST SLOW FAST SLOW
var slow_segment_length = 10
var fast_segment_length = 20
; Definition of speeds
var first_layer_speed = 20
var travel_speed = 80
var fast_segment_speed = 60
var slow_segment_speed = 10
; Number of perimeters around the object as a stable base
var skirt_loops = 6
; Height of the skirt in layers
var skirt_layers = 2
; The tool number to print with
var tool_number = 0
; Extrusion width is calculated here, but can also be set with a literal value
; Note that 1.05 and 1.125 are common factors that result in 0.42mm or 0.45mm width respectively
var nozzle_bore_diameter = 0.40
var extrusion_width = {var.nozzle_bore_diameter * 1.125}
var filament_diameter = 1.78
var extrusion_multiplier = 0.93
; Firmware retraction settings
var retract_length = 0.5
var retract_restart_length = 0
var retract_speed = 40
var deretract_speed = 40
var retract_z_lift = 0
; Flow math
var filament_flow = {pi * var.filament_diameter * var.filament_diameter / 4}
var regular_flow = {(var.extrusion_width - var.layer_height) * var.layer_height + pi * var.layer_height * var.layer_height / 4}
var bridge_flow = {pi * var.nozzle_bore_diameter * var.nozzle_bore_diameter / 4}
var line_spacing = {var.extrusion_width - var.layer_height * (1 - pi / 4)}
var regular_flow_ratio = { var.extrusion_multiplier * var.regular_flow / var.filament_flow}
var purge_line_flow_ratio = { 2.0 * var.regular_flow_ratio }
echo "extrusion_width: " ^ var.extrusion_width
echo "layer_height: " ^ var.layer_height
echo "filament_flow: " ^ var.filament_flow
echo "bridge_flow: " ^ var.bridge_flow
echo "regular_flow: " ^ var.regular_flow
echo "line_spacing: " ^ var.line_spacing
echo "regular_flow_ratio: " ^ var.regular_flow_ratio
;M37 S1 ; Enter simulation mode
; Set firmware retraction
M207 S{var.retract_length} R{var.retract_restart_length} F{60 * var.retract_speed} T{60 * var.deretract_speed} Z{var.retract_z_lift}
T{var.tool_number} ; Select tool
M106 S0 ; Turn off part cooling fan
M568 P{var.tool_number} S{var.print_temperature} R{var.standby_temperature} A1 ; Set tool to standby temperature
M190 S{var.bed_temperature} ; Wait for bed temperature to reach setpoint
M116 P{var.tool_number} ; Wait for temperatures associated with the selected tool to be reached
; Make an inventory of axes that have not yet been homed
var axes = ""
echo "Total number of axes: " ^ {#move.axes}
while {iterations < #move.axes}
if {!move.axes[iterations].homed}
set var.axes = {var.axes ^ move.axes[iterations].letter}
; Home applicable axes
echo "Axes to be homed: " ^ var.axes
G28 {var.axes}
;G28 XYZ ; Home the X, Y and Z axes
;G28 XY ; Home the X and Y axes
;G28 Z ; Home the Z axis
G21 ; Set units to millimeters
M83 ; Use relative distances for extrusion
; Calculate object width
var width = {var.fast_segments * var.fast_segment_length + (1 + var.fast_segments) * var.slow_segment_length}
; Calculate starting coordinates and other constant(s)
var x_start = {var.x_center - 0.5 * var.width + var.skirt_loops * var.line_spacing}
var y_start = {var.y_center - 0.5 * var.line_spacing + var.skirt_loops * var.line_spacing}
var travel_feedrate = {60 * var.travel_speed}
var first_layer_feedrate = {60 * var.first_layer_speed}
; Absolute position for purge line in X and Y space, 50 mm behind model
G90 ; Use absolute coordinates
G1 X{var.x_start} Y{var.y_start + 50} F{var.travel_feedrate}
; Set heater to final temperature and wait
M568 A2
M116 P{var.tool_number}
; Absolute position of nozzle at first layer height
G1 Z{var.layer_height} F{var.travel_feedrate}
; Relatively print two fat purge lines
G91 ; Switch to relative coordinates
G1 X{var.width} E{var.width * var.purge_line_flow_ratio} F{var.first_layer_feedrate}
G1 Y{-(2 * var.line_spacing)} F{var.travel_feedrate}
G1 X{-var.width} E{var.width * var.purge_line_flow_ratio} F{var.first_layer_feedrate}
G10 ; Retract to prevent oozing
; Move to the start of the model in X, Y and Z space
; The skirt code also moves to the start, but the skirt can be disabled.
; Also, the skirt code does not set Z, and there may be no purge line for which Z is set. Safety first.
G90 ; Use absolute coordinates
G1 X{var.x_start} Y{var.y_start} Z{var.layer_height} F{var.travel_feedrate}
G91 ; Switch to relative coordinates
G11 ; Advance/unretract/deretract in preparation to print
; Routine for printing the test object
var layers = {floor(var.height / var.layer_height)}
echo "Total number of layers: " ^ var.layers
while {iterations < var.layers}
; Track current layer
var layer = {iterations + 1}
; Current height in mm
var z = {var.layer * var.layer_height}
; Calculate pressure advance factor
var pa = {var.pa_start + floor(var.z) * var.pa_stepping}
; Set pressure advance
M572 D{var.pa_extruder} S{var.pa}
; Output some statistics while printing
echo "Layer " ^ iterations ^ " (" ^ {iterations + 1} ^ " of " ^ {var.layers} ^ " at " ^ {var.z} ^ "mm)"
echo "Pressure advance: " ^ {var.pa}
; Pre-calculate feedrates for first layer and other layers
var slow_segment_feedrate = {60 * (var.layer == 1 ? var.first_layer_speed : var.slow_segment_speed)}
var fast_segment_feedrate = {60 * (var.layer == 1 ? var.first_layer_speed : var.fast_segment_speed)}
; Print skirt
if {iterations < var.skirt_layers}
G90 ; Use absolute coordinates
; Move to absolute XY start coordinates
G1 X{var.x_start} Y{var.y_start} F{var.travel_feedrate}
G91 ; Switch to relative coordinates
; Print all loops of the skirt
while {iterations < var.skirt_loops}
var skirt_loop = {var.skirt_loops - iterations}
var x = {var.width + 2 * var.skirt_loop * var.line_spacing}
var y = {var.line_spacing + 2 * var.skirt_loop * var.line_spacing}
; Print one full skirt loop
while iterations < 2
var direction = {iterations == 0 ? 1 : -1}
G1 X{var.direction * var.x} E{var.x * var.regular_flow_ratio} F{var.fast_segment_feedrate}
G1 Y{var.direction * var.y} E{var.y * var.regular_flow_ratio} F{var.fast_segment_feedrate}
; Travel to the start of the next skirt loop
G1 X{var.line_spacing} Y{var.line_spacing} F{var.travel_feedrate}
; Print two perimeters back and forth of alternating slow and fast segments
while iterations < 2
var direction = {iterations == 0 ? 1 : -1}
; Slow starting segment (X)
G1 X{var.direction * var.slow_segment_length} E{var.slow_segment_length * var.regular_flow_ratio} F{var.slow_segment_feedrate}
; Remaining fast and slow segments (X)
while iterations < var.fast_segments
G1 X{var.direction * var.fast_segment_length} E{var.fast_segment_length * var.regular_flow_ratio} F{var.fast_segment_feedrate}
G1 X{var.direction * var.slow_segment_length} E{var.slow_segment_length * var.regular_flow_ratio} F{var.slow_segment_feedrate}
; Print the side perimeter (Y)
G1 Y{var.direction * var.line_spacing} E{var.line_spacing * var.regular_flow_ratio} F{var.slow_segment_feedrate}
; Move one layer up
G1 Z{var.layer_height} F{var.travel_feedrate}
G10 ; Retract
G91 ; Relative positioning
G1 F3000 Z20 ; Move gantry up 20mm
G90 ; Absolute positioning
G28 X ; Home X axis
;M104 S0 ; Turn off nozzle heat block
M568 P{var.tool_number} S0 R0 A2 ; Set required heater temperature off
M140 S0 ; Turn off bed
M106 S0 ; Turn off part cooling fan
M18 ; Disable stepper motors
;M37 S0 ; Leave simulation mode
Photo of one print in progress and one done:
Lastly, I'm so happy with the concept and speed, that I'm now toying with the idea to make macros for:
- Tuning bridge parameters (with flow, temperature and part cooling as variables)
- Layer adhesion (with temperature and part cooling are variable)
- Tuning the extrusion multiplier
EDIT: The code didn't look right when it contains words like "doesn't" and "haven't" in comments. This results in random "{0}" inserted and illegal G-code.