Extruder PA synchronization with input shaping

@Sineos, thanks. TBH,

This might come from the lower PA value alone. The stepper motor has to push plastic less aggressively (and retract it back), so it would need smaller duty cycles from the driver, and thus have, hopefully, less resistive losses. Though the extruder PA input shaping branch also tends to do less aggressive PA adjustments (at least in my dumps of the extruder steps), so it might additionally reduce the extruder load. Which might be nice too.

This does look interesting! I havenā€™t gotten a chance to look at your code, but hope to in a week or so.

Also, I like your idea for an alternate pressure advance test. I fear getting users to configure the slicer to emit changes in velocity will be too challenging though. Just thinking out loud, one possibility may be to create a Klipper ā€œgcode transformationā€, similar to tuning_tower, to inject speed changes into the g-code.

Cheers,
-Kevin

To be fair, it is not my idea, I came across it on the internet. There was also a gcode generator for this test. Alas, it has some outstanding bugs that are not fixed. So, I think there are basically two or three options. Also, I did not manage to achieve that velocity profile in PrusaSlicer/SuperSlicer, and I suspect it is not directly possible.

  1. Add a script to Klipper that would generate the test GCode. The main problem is the configuration input for such a script. It may read printer.cfg though, and maybe some configuration can be stored there.

  2. Add a new module and a gcode command to Klipper that would execute a test, e.g.

[pressure_advance_test]
x_size: 100
y_size: 50
slow_velocity: 20
fast_velocity: 100
walls: 2
...

which would enable a command such as TEST_PRESSURE_ADVANCE [EXTRUDER=<extruder>] NOZZLE=<nozzle_dia> TEMP=<temp> - basically, some settings could be pre-configured (and have defaults or inferred from the other printer.cfg settings), and some must be provided to avoid mistakes.

  1. PrusaSlicer/SuperSlicer(s) have calibration scripts, so add them there? Admittedly, chance of that going through may be low.

TBH, options 1 and 2 may open other possibilities, e.g. adding a better test for input shaping (without accelerometer). Right now that test modifies the velocity of both axes when the toolhead is moving. I did not find a good way to organize the walls (and toolhead path) such that the velocity of one axis would be stable. But by explicit GCode generation, that effect may be achieved.

FWIW, I was thinking of a simple ā€œhackā€. A module (auto-loaded) that registers a TEST_PRESSURE_ADVANCE command. That command registers a g-code transform that looks for extrude moves that are 60mm or longer, and replaces them with 3 moves with different speeds (eg, 20mm at full speed, 10mm at 8mm/s, 20mm at full speed). User then prints a ā€œboxā€ like object. A ā€œhackā€ for sure, but should certainly add a seam to a print!

Iā€™d recommend not ā€œgoing down that rabbit holeā€. Generating g-code requires a ton of config options (temperatures, nozzle size, bed size, bed orientation, speeds, homing routines, etc.). Itā€™s a ton of work for both developers and users.

-Kevin

In principle, I understand that. Fortunately, with the advance Klipper macro system, it might be not too bad, e.g. we can recommend for the users to define by default

[gcode_macro TEST_PRESSURE_ADVANCE]
gcode:
  ; Run start GCode, e.g.
  G28
  M190 S{params.BED_TEMP|default('60')}
  M109 S{params.TEMP}
  M106 S255
  TUNING_TOWER COMMAND=SET_PRESSURE_ADVANCE PARAMETER=ADVANCE START=0 FACTOR=.005
  RUN_PRESSURE_ADVANCE_TEST {rawparams}
  ; End GCode, e.g.
  M106 S0
  M104 S0                                       
  M140 S0                                 
  M84

[pressure_advance_test]
# Configure more permanent parameters

Then the user could just call, say, TEST_PRESSURE_ADVANCE NOZZLE=0.4 TEMP=210 and get the test run. So, it might not be too bad and wouldnā€™t require endless parameters and config options. And some options could be reasonably inferred from other options in printer.cfg, e.g. bed center.

FWIW, this would speak in favor of adding a module as a command rather than a script (where adding prolog/epilog gcode would be more troublesome). And TBH, I do not see other good options. Intercepting GCode commands may also work, but arguably is more hacky and prone to errors (e.g. if a user have to scale a model). But Iā€™m open to feedback and other suggestions. I think we can consider that option of intercepting and overriding GCode commands too, if the feedback for that option is positive.

Preface: the post got actually too large. So, Iā€™d recommend reading the principles of extruder input shaping and then skip the justification part and take a look at the experiment results because there are some interesting findings there, in case you want to save some time.

From the implementation point of view, the exruder input shaping is done as follows:

  • extruder motion is split into 3 ā€˜fakeā€™ axis E_x, E_y, E_z in certain proportion for each move;
  • the configured input shapers are applied to E_x and E_y motion;
  • the pressure advance calculation (with smoothing) is applied on top of that (in fact, integrals and sums can be swapped in this case, so it is actually implemented in reverse: IS_x(PA(E_x)), IS_y(PA(E_y)), E_z (no PA or IS for z, retractions/deretractions are also implemented as a motion over this ā€˜fakeā€™ z axis);
  • the final extruder position E is a sum of per-axis motion, i.e. E = IS_x(PA(E_x)) + IS_y(PA(E_y)) + E_z.

If you are curious, hereā€™s some justification on why this might even make sense at all, and how the moves are split into E_x, E_y, E_z.

An infinitesimal extruder motion dE can be represented as

dE = r_e * sqrt(dx^2 + dy^2 + dz^2) = r_e * dt * sqrt(v_x(t)^2 + v_y(t)^2 + v_z(t)^2)

This is a precise representation, sadly, it would require integration to go directly with it to input shaping (and symbolic integration is not likely to succeed, so a numeric one would be required). Instead, weā€™ll use some tricks. Letā€™s define

v_x(t) = V(t) * r_x(t) + Dv_x(t)
v_y(t) = V(t) * r_y(t) + Dv_y(t)
v_z(t) = V(t) * r_z(t) + Dv_z(t)
r_x(t) = V_x(t) / V(t)
r_y(t) = V_y(t) / V(t)
r_z(t) = V_z(t) / V(t)

Basically, we represent v_x(t), v_y(t) and v_z(t) as some ā€˜averageā€™ motion \vec V(t) plus some small (compared to V(t)) corrections Dv_x(t), Dv_y(t), Dv_z(t). Additionally, r_x(t)^2 + r_y(t)^2 + r_z(t)^2 = 1. Then,

dE = r_e * dt * sqrt((V(t) * r_x(t) + Dv_x(t))^2 +
                     (V(t) * r_y(t) + Dv_y(t))^2 +
                     (V(t) * r_z(t) + Dv_z(t))^2) =
   = r_e * V(t) * dt * sqrt(r_x(t)^2 * (1 + Dv_x(t) / (V(t) * r_x(t)))^2 +
                            r_y(t)^2 * (1 + Dv_y(t) / (V(t) * r_y(t)))^2 +
                            r_z(t)^2 * (1 + Dv_z(t) / (V(t) * r_z(t)))^2)

Now we can use (1 + x)^a ~= 1 + a * x when x is small:

dE ~= r_e * V(t) * dt * sqrt(r_x(t)^2 * (1 + 2 * Dv_x(t) / (V(t) * r_x(t))) +
                             r_y(t)^2 * (1 + 2 * Dv_y(t) / (V(t) * r_y(t))) +
                             r_z(t)^2 * (1 + 2 * Dv_z(t) / (V(t) * r_z(t)))) =
    = r_e * V(t) * dt * sqrt(r_x(t)^2 + r_y(t)^2 + r_z(t)^2 +
                             2 * (Dv_x(t) * r_x(t) + Dv_y(t) * r_y(t) +
                                  Dv_z(t) * r_z(t)) / V(t))

We can use the property of sum of squares of r_?s and then that approximate equality again to get

dE ~= r_e * V(t) * dt * sqrt(1 + 2 * (Dv_x(t) * r_x(t) + Dv_y(t) * r_y(t) +
                                      Dv_z(t) * r_z(t)) / V(t)) ~=
   ~= r_e * V(t) * dt * (1 + (Dv_x(t) * r_x(t) + Dv_y(t) * r_y(t) +
                             Dv_z(t) * r_z(t)) / V(t)) =
    = r_e * dt * (V(t) + Dv_x(t) * r_x(t) + Dv_y(t) * r_y(t) + Dv_z(t) * r_z(t))

Now we can substitute Dv_x(t) = v_x(t) - V(t) * r_x(t), Dv_y(t)=... and Dv_z(t)=... from above to get

dE ~= r_e * dt * (V(t) + (v_x(t) - V(t) * r_x(t)) * r_x(t) +
                         (v_y(t) - V(t) * r_y(t)) * r_y(t) +
                         (v_z(t) - V(t) * r_z(t)) * r_z(t)) =
    = r_e * dt * (V(t) * (1 - r_x(t)^2 - r_y(t)^2 - r_z(t)^2) +
                  v_x(t) * r_x(t) + v_y(t) * r_y(t) + v_z(t) * r_z(t)) =
    = r_e * dt * (v_x(t) * r_x(t) + v_y(t) * r_y(t) + v_z(t) * r_z(t))

So, we eliminated V(t), and dE(t)/dt is (approximately) proportional to the original toolhead motion v_x(t), v_y(t) and v_z(t) with some some direction coefficients representing some ā€˜averagedā€™ motion that must be r_x(t)^2 + r_y(t)^2 + r_z(t)^2 = 1. FWIW, depending on the choice of that direction vector, a proper computation of E(t) may still require integration.

Instead, I went about it this way (though it is a lot more hand-wavy at this point). With input shaping, velocities are computed as follows:

v_x(t)  = sum(A_{x,m} * v_m(t - t_{x,m}) * r_{x,m})
v_y(t)  = sum(A_{y,m} * v_m(t - t_{y,m}) * r_{y,m})
v_z(t) = v(t) * r_z

with A_{x,m}, t_{x,m}, A_{y,m} and t_{y,m} being corresponding input shaper constants, summation running over the input shaper pulses potentially spanning multiple moves, and v_m(t) and r_{x,m} and r_{y,m} being velocity and x,y-axis direction of a corresponding move m. Now, here be dragons:

dE(t) / dt = sum(A_{x,m} * r_{e,m} * v_m(t - t_{x,m}) * r_{x,m}^2) +
             sum(A_{y,m} * r_{e,m} * v_m(t - t_{y,m}) * r_{y,m}^2) +
             r_e * v(t) * r_z^2

So, it is sorta r_z(t) = r_z, r_x(t) ~ r_{x, m}, r_y(t) ~ r{y, m}, but not really. Still, arguably, this formula may correspond to a certain choice of r_x(t) and r_y(t). And, conveniently, it does not require any complicated integration. Then, for a move m

E_{x,m}(t) = E_{x,m,offst} + r_{e,m} * (v * t + a * t^2 / 2) * r_{x, m}^2
E_{y,m}(t) = E_{y,m,offst} + r_{e,m} * (v * t + a * t^2 / 2) * r_{y, m}^2
E_{z,m}(t) = E_{z,m,offst} + r_{e,m} * (v * t + a * t^2 / 2) * r_{z, m}^2
E_{a,m+1,offst} = E_{a,m}(t_{end,m})

with E_{a,m,offst} being an offset of a beginning of a move on an axis a. Afterwards, the procedure to apply input shaping and pressure advance is as described in the beginning.

In principle, there could be other choices of r_x(t), r_y(t) and r_z(t) than this. Maybe Iā€™ll explore other options as well. But this one is simple enough and, in my opinion, give acceptable results.

FWIW, while I was preparing this explanation, I realized that I made a small mistake in my initial implementation. Namely, I instead used different coefficients

E_{x,m}(t) = E_{x,m,offst} + r_{e,m} * (v * t + a * t^2 / 2) * |r_{x, m}| / R_m
E_{y,m}(t) = E_{y,m,offst} + r_{e,m} * (v * t + a * t^2 / 2) * |r_{y, m}| / R_m
E_{z,m}(t) = E_{z,m,offst} + r_{e,m} * (v * t + a * t^2 / 2) * |r_{z, m}| / R_m
R_m = |r_{x, m}| + |r_{y, m}| +  |r_{z, m}|

I couldnā€™t trace back where exactly I got this in the first place (perhaps I made a mistake or took some different approximation :confused:), so, I pushed a fix and ran some tests to compare the old and the new behaviors.


Left to right are current Klipper PA code (with PA=0.04 and smooth_time=0.02), original extruder input shaping code (with PA=0.04 and smooth_time=0.01) and the fixed code (the same PA=0.04 and smooth_time=0.01). Input shaping in all cases is 3hump_ei at 70 Hz for X axis and 2hump_ei at 52.6 Hz on Y axis. The extruder shaping differs from mainline substantially, but the fix itself is pretty cosmetic.

Another example of PA behavior from the same test run (just a different GCode location):


This one is pretty interesting actually. Again, the extruder shaping differs from mainline substantially, but the fix itself is pretty cosmetic, though this time there are a bit more noticeable differences.

Specifically, thereā€™s this jitter with both extruder input shaping versions at the range of supposedly constant extruder velocity. So, I tried to trace it down to see if itā€™s some kind of fluke. In GCode, this corresponds to the part of 3D Benchy perimeter that faces outwards including the Benchy nose (basically that velocity dip in the middle of velocity chart is the Benchyā€™s nose):


So, while cruising velocity stays constant, the motion on X and Y axes isnā€™t - the toolheat prints a smooth curve and turns all the time. I wanted to check the actual toolhead velocity from the steppers, so I wrote and additional motan data analyzer that can be used as ["norm2(derivative(stepq(stepper_x)),derivative(stepq(stepper_y)))"] to compute (v_x^2+v_y^2)^(1/2). Hereā€™s the result for that part:

test_3db_extr_is_stepcompr_2_tv2

So, while the velocity of stepper_x and stepper_y is smooth and all, the resulting instantaneous toolhead velocity (after input shaping) is not constant, there are some velocity oscillations. And for better or worse, the extruder input shaping code can now pick up those oscillations and adjust the PA for the extruder accordingly. Also, comparing the two versions, extruder input shaping after the fix seems to better match the amplitude of the velocity oscillations to the PA adjustments: it is stronger where the actual amplitude of velocity oscillations is larger. Prior to fix, the amplitude of PA oscillations was more consistent across the whole curve, even though the actual toolhead velocity oscillations are much smaller on the sides of the Benchy far away from its nose. Whether the whole thing actually makes sense is another question, though in many of my performed prints I didnā€™t observe any degradation of the surface finish on smooth curves.

2 Likes

OK, so I put together a module to print that PA tower from within Klipper. It works as follows. You can add the following configuration to printer.cfg

[pa_test]
# size_x: 100
# size_y: 50
# height: 50
# origin_x: #bed_center_x
# origin_y: #bed_center_y
# layer_height: 0.2
# first_layer_height: 0.3
# perimeters: 2
# brim_width: 10
# slow_velocity: 20
# fast_velocity: 80
# filament_diameter: 1.75

Basically, as you can see, all parameters have their default values, so just adding [pa_test] may suffice. However, automatic tower placement (and determining max size) on some exotic kinematics like polar, delta, etc. may not work very well, in which case it may be necessary to manually specify origin_x, origin_y (and perhaps size_x and size_y). Then you can initiate a tower print by issuing a PRINT_PA_TOWER command. It also lets you override any of the parameters from printer.cfg (e.g. as PRINT_PA_TOWER BRIM_WIDTH=5), and it also has 2 other required parameters NOZZLE and TARGET_TEMP (e.g. PRINT_PA_TOWER NOZZLE=0.4 TARGET_TEMP=205) that cannot be set in the config. This is because both have critical impact on PA testing, so you must make sure that you are providing the correct values. However, PRINT_PA_TOWER does not heat the extruder, and TARGET_TEMP is only used for sanity-checking the currently configured extruder temp; you must pre-heat the extruder separately.

Now, to the GCode prologue/epilogue and other features. I do not recommend using PRINT_PA_TOWER directly. Instead, I suggest to add the following macros to your printer.cfg:

[delayed_gcode start_pa_test]
gcode:
    {% set vars = printer["gcode_macro RUN_PA_TEST"] %}
    ; Add your start GCode here, for example:
    G28
    M190 S{vars.bed_temp}
    M109 S{vars.hotend_temp}
    {% set flow_percent = vars.flow_rate|float * 100.0 %}
    {% if flow_percent > 0 %}
    M221 S{flow_percent}
    {% endif %}
    TUNING_TOWER COMMAND=SET_PRESSURE_ADVANCE PARAMETER=ADVANCE START=0 FACTOR=.005
    ; PRINT_PA_TOWER must be the last command in the start_pa_test script:
    ; it starts a print and then immediately returns without waiting for the print to finish
    PRINT_PA_TOWER {vars.rawparams} FINAL_GCODE_ID=end_pa_test

[delayed_gcode end_pa_test]
gcode:
    ; Add your end GCode here, for example:
    M104 S0 ; turn off temperature
    M140 S0 ; turn off heatbed
    M107 ; turn off fan
    G91 ; relative positioning
    G1 Z5 ; raise Z
    G90 ; absolute positioning
    G1 X0 Y200 ; present print
    M84 ; disable steppers
    RESTORE_GCODE_STATE NAME=PA_TEST_STATE

[gcode_macro RUN_PA_TEST]
variable_bed_temp: -1
variable_hotend_temp: -1
variable_flow_rate: -1
variable_rawparams: ''
gcode:
    # Fail early if the required parameters are not provided
    {% if params.NOZZLE is not defined %}
    {action_raise_error('NOZZLE= parameter must be provided')}
    {% endif %}
    {% if params.TARGET_TEMP is not defined %}
    {action_raise_error('TARGET_TEMP= parameter must be provided')}
    {% endif %}
    SET_GCODE_VARIABLE MACRO=RUN_PA_TEST VARIABLE=bed_temp VALUE={params.BED_TEMP|default(60)}
    SET_GCODE_VARIABLE MACRO=RUN_PA_TEST VARIABLE=hotend_temp VALUE={params.TARGET_TEMP}
    SET_GCODE_VARIABLE MACRO=RUN_PA_TEST VARIABLE=flow_rate VALUE={params.FLOW_RATE|default(-1)}
    SET_GCODE_VARIABLE MACRO=RUN_PA_TEST VARIABLE=rawparams VALUE="'{rawparams}'"
    SAVE_GCODE_STATE NAME=PA_TEST_STATE
    UPDATE_DELAYED_GCODE ID=start_pa_test DURATION=0.01

Of course, you can adjust the scripts to your liking and add more parameters, for example. Then you can just call as RUN_PA_TEST NOZZLE=0.4 TARGET_TEMP=205 [other params as necessary].

Note the use of FINAL_GCODE_ID parameter of PRINT_PA_TOWER to trigger a delayed_gcode upon print completion, and also passing all parameters as-is to it via {vars.rawparams}. The latter uses the fact that Klipper ignores unknown GCode command parameters, so you can safely pass additional parameters to RUN_PA_TEST (e.g. FLOW_RATE) that are only handled by the macro itself and which are not recognized by PRINT_PA_TOWER.

For this to work nicely I also modified virtual_sdcard module to support printing from other GCode sources rather than the virtual sdcard itself. So, the PA tower prints somewhat similar to as if it was printed from sdcard, e.g. there is progress tracking and you can call PAUSE/RESUME and CANCEL_PRINT if you enable [pause_resume] module to pause/unpause and cancel the print. Alas, I could have broken [virtual_sdcard] module in some non-obvious way, so if you run into issues, please report.

If you wish to test just this new script with the current Klipper PA implementation, you can use this branch. Otherwise, Iā€™ve updated the extruder pa input shaping branches to include this patch too.

I am trying to give the PA Test ago but as soon as I enable the macros I get

Unhandled exception during run
Traceback (most recent call last):
  File "/home/pi/klipper/klippy/klippy.py", line 217, in run
    self.reactor.run()
  File "/home/pi/klipper/klippy/reactor.py", line 292, in run
    g_next.switch()
  File "/home/pi/klipper/klippy/reactor.py", line 340, in _dispatch_loop
    timeout = self._check_timers(eventtime, busy)
  File "/home/pi/klipper/klippy/reactor.py", line 158, in _check_timers
    t.waketime = waketime = t.callback(eventtime)
  File "/home/pi/klipper/klippy/webhooks.py", line 485, in _do_query
    res = query[obj_name] = po.get_status(eventtime)
  File "/home/pi/klipper/klippy/extras/pa_test.py", line 84, in get_status
    'progress': self.progress,
AttributeError: PATest instance has no attribute 'progress'
Transition to shutdown state: Unhandled exception during run

Oh, Iā€™m not using Moonraker and so missed that possibility. It should be fixed now. But let me know if more problems remain.

Wow what a work, thanks.
About the tuning tower, would it be easier to use the flat test common in Marlin, where lines with only linear acceleration are used without corners?
I found they make it quite easy to spot the optimal value and they are straight moves soneasy even to generate without slicer.

The new suggested tuning tower achieves a similar effect as a Marlin test. However, the potential issue with the Marlin test specifically is that it is quite sensitive for perfectly tuned first layer. In case of a warped bed or the first layer somewhat off, one may see it difficult to find the correct PA value. Also, the number of tested PA values is more limited. And with the tower test, you can fine-tune the PA value better because you can achieve a smooth transition of PA from layer to layer. But if a Marlin test works for you, thereā€™s nothing wrong to keep using it. Iā€™m just saying that some people find it more difficult and fiddly to use.

Iā€™ve just written a new PA calibration tool that might be handy for your testing here. I deprecated the old marlin tool (at least in my guide, there are some other versions of it out there.)
You can find it here. Pretty much just prints a bunch of cube corners in one go (using slic3r flow math at its core), and has proven to be quite accurate to real life results.
Iā€™ve been running your branch of a while and been having great results. It seems like Iā€™m getting more consistent pressure advance at a wider range of speeds, but I need to do some more direct comparison, right now itā€™s mostly anecdotal. Just wanted to put in my kudos here, very cool stuff.
The only reason I swapped back for now is that Mainsail/Moonraker disables features because it thinks itā€™s on an old Klipper version. Would love to see this taken into consideration for mainline Klipper.

4 Likes

I just came across this topicā€¦very interesting and great work!

What I was wondering: does anybody know this PA Tool script?

It is very convenient and FAST, as it only prints 1 layer. And the results are very easy to compare.
BTW it also gave me smaller PA values than the original tower.

In case this is considered ā€œbadā€ or useless/wrong - sorry for the distraction :wink:

Indeed this tool can be used. A ā€œdrawbackā€ is that the results can vary depending on your first layer quality. The larger prints are considered more robust and precise.

Just getting my toes wet with klipper. Is there info somewhere on how to implement this from start to finish?

Hello @conekiller !

You mean this?

https://www.klipper3d.org/Pressure_Advance.html

When running some simulations, I observed something interesting.

I have a hypothesis that this is more beneficial to longer duration input shaper but may actually be detrimental to shorter duration shapers. The extruder movement is calculated based on ideal toolhead states (position and velocity that klipper commands before input shaper and gantry dynamics), so the closer our estimate to the actual toolhead position, including gantry dynamics, the better.

With long duration input shaper such as EI, 2HEI, 3HEI, the actual toolhead states are closer to the shaped commands. But for well tuned shorter duration shapers at high acceleration, the actual toolhead states after input shaper and gantry dynamics can be closer to ideal toolhead states than the toolhead states with input shapers without gantry dynamics.

I ran a some simulations to compare the effects in different configuration and the results appear so support it. I have not run physical test since Iā€™m still working out the math for another effect that I suspect to be significant. Inspecting the graphs visually, it looks like the effect is about equal around MZV, at least for the configurations that I tested. Acceleration, target velocity and the system parameters appear to have effects as well.

Note that my simulation assumes second order underdamped dynamics, which is a good estimate for my printer since the resonance measurement shows one sharp peak. I have also measured and verified the damping factor which I will use here.

The simulations are one axis, constant acceleration from dead stop to a target velocity.


Fig 1: Acceleration response for ZV shaper for reference

Fig 2: ZV shaper, note the shaped response is closer to ideal (unshaped)

Fig 3: ZV shaper, actual velocity - planned velocity vs position, as percentage of planned velocity. I reason that this deviation is what causes over/under-extrusion which may be somewhat compensated by PA.

Fig 4: Same as Fig 3 but for MZV.

Fig 5: Same as Fig 3 but for EI. Only half acceleration to prevent smoothing

Fig 6: Same as Fig 5 but at 10k acceleration. We can see slight ringing.

What Fig 3 to Fig 6 have in common, is that we are switching from slightly overextruding to slightly underextruding. Actual toolhead velocity lags unshaped modeI and leads shaped model for a given position. Lagging toolhead velocity for the same extrusion rate means overextrusion, in some cases up to at least 5% over 1mm distance. I suspect this may be one reason why the PA value needed to be re-tuned.

From these graphs, if we assume that PA perfectly compensates for extruder dynamics, there is still a quite significant under/over-extrusion that is mostly due to the toolhead dynamics. This deviation is dependant on the toolheadā€™s resonance frequency, damping factor, input shaper used, acceleration and target velocity. When printing at high acceleration and speed, a common recommendation is to use constant acceleration and velocity for all features and tune PA to that condition, a practice that works well in my experience. However, it makes me think that we are perhaps using PA to compensate for toolhead dynamics on top of extruder dynamics.

Let me know if thereā€™s any issue with this line of thought.

Thanks for reading

2 Likes