A Software Solution to Eliminate Ringing?
-
@DigitalVision interesting. Thanks for the explanation.
That makes sense. Instead of the G1 curvature, it might be G2 or G3?
I'm not as smart as you two when it comes to these algorithms, so I'll keep to myself now. I just get worried when there is talk of the firmware injecting intentional deviation into the stream. I'll have to wait and see results, I guess!
[Edit: Just to clarify, the Klipper method does not actually deviate from the path -- it is only a virtual deviation to calculate the speed change possible.]
-
@dc42 said in A Software Solution to Eliminate Ringing?:
What I have in mind is that when the GCode involves a change in direction between adjacent printing moves, RRF will insert a joining move between them. This joining move would be a quadratic or cubic in shape, and would deviate from the path specified in the GCode by no more than a user-set deviation parameter. So an internal corner would be cut by no more than this amount. Different motors would be executing trajectories of different shapes during this joining move.
In order to satisfy a smooth curvature (G2 continuity) at both the start end end of such a joining segment, I you either need a higher order polynomial than quadratic or cubic or you need to piecewise combine multiple polynomials. An easy way to think about it is that a cubic polynomial only has one point where the second derivative is zero and you need two. For a single polynomial to describe a symmetric transition curve, I think you need a 4th degree (quartic) polynomial, and for a generic transition between two arbitrary points the degree increases to 5 (quintic?).
Another important property that we want for a smoothing method is to allow for constant arc-length speed. I.e. the print head should be able to move through a circle made up of line segments at constant extrusion speed. This means that ideally you want the curve to be parametrized based on arc length. For a circular arc this is trivial, but it's not so easy for a polynomial curve shape. That's the reason I suggested a second inverse-arc-length approximating polynomial.
In the extension though, if you implement say a spline of 3rd order polynomials to support joining moves you've basically done 90 % of the job of supporting a general spline motion profile. And going all the way has some very nice properties. The slicer could now output perfect arc and elliptical shapes, and replicate more generic curve shapes out of most CAD software perfectly. I believe this is where the industry is going for advanced CNC control paths.
-
@bot said in A Software Solution to Eliminate Ringing?:
@DigitalVision interesting. Thanks for the explanation.
That makes sense. Instead of the G1 curvature, it might be G2 or G3?
That's right. In the end, we actually need C2 continuity, but if you start with G2 you can reparametrize it to achieve C2.
I'm not as smart as you two when it comes to these algorithms, so I'll keep to myself now. I just get worried when there is talk of the firmware injecting intentional deviation into the stream. I'll have to wait and see results, I guess!
You bring up some good questions. The intentional deviation may sound controversial, but it's actually the reality already. I don't know of any high-end professional CNC machine that doesn't do this (since it significantly increases the motion speed), and that's generally an application with much tighter tolerance requirements. In the case of joining line segments and doing spring-damper compensation the outcome is actually a more faithful reconstruction of the original curve.
You could make a valid argument that the smoothing should be done by the slicer instead of the firmware. I don't have a strong opinion either way. I think the reality is that a lot of the way our current pipelines are constructed just kind of happened and making a local change is much easier than driving a change across softwares. For example there is no good reason a slicer should need to know what your filament diameter is.
If smoothing were implemented in firmware, I would expect it to be controlled by a cordial error tolerance parameter that you could set to zero to disable the behavior.
[Edit: Just to clarify, the Klipper method does not actually deviate from the path -- it is only a virtual deviation to calculate the speed change possible.]
Thanks, that makes sense.
-
I've been modifying PrusaSlicer for the past few months as a way to slowly learn and get used to how it works under the hood. My end goal is to make sure the slicer and firmware are playing nicely together. If you want to try and put some of this smart stuff into PrusaSlicer, maybe just for testing, I'd be glad to lend a hand with the tedious bits or pointing you around the codebase as best as I can.
I do get worried at the thought of the firmware having more calculation overhead. Once we solve the "jerk" problem, and we can round corners faster, then we solve the ringing problem, we'll quickly encounter the next problem: too many segments too fast.
-
Great thread!
I am very interested to see this progress.
-
@DigitalVision said
You could make a valid argument that the smoothing should be done by the slicer instead of the firmware. I don't have a strong opinion either way. I think the reality is that a lot of the way our current pipelines are constructed just kind of happened and making a local change is much easier than driving a change across softwares.
The only argument I can see for accepting implementation in the slicer is if the processing overhead is so huge that it cannot be done in firmware.
Adding it to the firmware keeps the firmware slicer agnostic.
It also means people using RRF for non-FFF projects can use the functionality.
And just like bed-leveling, its a per-machine setting. If you are running a printfarm, its a nightmare to have to deal with machine specific slicing profiles.For example there is no good reason a slicer should need to know what your filament diameter is.
Very true. I think that's mostly due to legacy slicing function. I know there's no firmware reason to keep working like that.
-
A short update.
I managed to get some hours to spend on this today, so I rewrote the test code from before mostly from scratch and turned it into a G-code filter. I can now take a regular slicer output file, pass it through the filter and feed the result to the printer. This makes it easier to do realistic testing. The code only really supports linear paths to far, so curves with high tessellation counts will end up looking terrible, but it should work OK for calibration cubes and similar objects. In the process of rewriting it I also implemented some improved machine limits so I won’t have to worry about it trying to move the Z-axis on my cartesian printer at 120 mm/sec.
Here are some quick-and-dirty results.
speed: 80 mm/s, accel: 10 m/s² (10,000 mm/s²), jerk: 2 km/s³ (2,000,000 mm/s³)I'm actually pretty happy with that given that this is a 490 g heavy end effector on 375 mm diagonals. For reference, here's what it looks like without any spring-damper compensation:
speed: 80 mm/s, accel: 5 m/s² (5,000 mm/s²)Pushing it a bit faster.
speed: 120 mm/s, accel: 20 m/s² (20,000 mm/s²), jerk: 2 km/s³ (2,000,000 mm/s³)
Some extrusion glitches, but not much ringing.I’ll share the code once I’ve written a little documentation on how to use it as I’d love to get some more testing and learnings.
-
@DigitalVision said in A Software Solution to Eliminate Ringing?:
A short update.
I managed to get some hours to spend on this today, so I rewrote the test code from before mostly from scratch and turned it into a G-code filter. I can now take a regular slicer output file, pass it through the filter and feed the result to the printer. This makes it easier to do realistic testing. The code only really supports linear paths to far, so curves with high tessellation counts will end up looking terrible, but it should work OK for calibration cubes and similar objects. In the process of rewriting it I also implemented some improved machine limits so I won’t have to worry about it trying to move the Z-axis on my cartesian printer at 120 mm/sec.
Here are some quick-and-dirty results.
speed: 80 mm/s, accel: 10 m/s² (10,000 mm/s²), jerk: 2 km/s³ (2,000,000 mm/s³)That is a beautiful print!
-
Only a quick thought. I really like the idea of a slicer setting for that.
I am using KISSlicer alot with the PreloadVE feature. In the end the machine do not know how the 3D model look. So i think the best approached is a hybrid one! -
@PCR
Its always going to be hybrid.I would make the case that this specific thing (software compensation for position error) is about getting the printer to actually do what you told it to do. If the printer was more rigid, the function would not be needed. Therefore, it falls into the same category as basic things like steps per mm.
And it has applicability for any motion control done with RRF/Duet, not just FFF.Functions like Preload in KISSlicer (not that I have used it), are material specific, not just machine specific. So it has to be in the slicer to work correctly.
And its a function that only works for FFF.@PCR said in A Software Solution to Eliminate Ringing?:
Only a quick thought. I really like the idea of a slicer setting for that.
For what?
If you mean filament diameter, my point was that we could be using a volumetric value, people don't because there's no real advantage to it and we all got into the habit of futzing with the slicer setting to get certain effects... But if most printers had an accurate device for measuring filament diameter as it went into the extruder, it would produce more accurate prints.
Not to a degree that I think most people would notice though. Which is why its not a thing. -
Another update.
Before sharing the python code, I wanted to test it on at least one other printer. This time on a cartesian (CoreXY) printer with CPC linear rails. The printer x-axis is made up of one single 15mm rail with a sole runner block holding the hotend and extruder assembly. The rail itself is very rigid. The weak point is instead the x-axis carrier/runner block that has a bit of yield to its roll axis (resulting in ringing along the y axis).
In getting the calibration done for this printer I learned a number of new things. First, and very importantly there are actually two modes of ringing. The first one is the one we classicality think of as ringing. It occurs after the printer has decelerates down to a full stop and the end effector oscillates. The photo below illustrates this.
The second type of ringing was something I hadn’t considered before. It’s ringing that happens during the acceleration phase. If you look carefully you can see it happening in the simulation in the first figure in https://www.digitalvision.blog/spring-damper-control. The effect may look subtle in that animation, but the result on an actual print is much more substantial. The effect is that the extrusion bead gets stretched and compressed periodically.
The effect of this is what I suspect often prevents getting consistent extrusion when printing at high speeds. The extrusion gets ripped apart in the stretching phase. After correcting for this ringing I’ve been able to print consistently even at 150 mm/s with 10 m/s² accelerations, something that I’ve never managed before.
When observing ringing on a print, you will often see a compound effect of both of these two modes of ringing, with the acceleration ringing from e.g. the x-axis compounding with the post-deceleration ringing from the y-axis. When the two axes have different natural spring frequencies the two axis parameters can be hard to tease apart. For that reason, I came up with the strategy of isolating one axis at a time by setting the acceleration limits of the other axis to a very low value. E.g. 10 m/s² acceleration on the y axis with 0.5 m/s² acceleration on the x.
My final issue with calibrating this printer was that I was unable to find a single setting that eliminated the ringing on both the left and right side of the print. The left side had a ringing frequency of ~52 Hz with the right side closer to 60 Hz. It seems like the ringing had different frequencies when moving in the positive y direction vs the negative. Thinking a bit about this I came up with a model that makes sense. The print head is not perfectly balanced under the axis with the center of gravity being offset to one side. This effectively means that gravity is applying pre-tension on the spring biasing the spring effect towards one direction. To test this hypothesis I implemented an asymmetric spring model and plugging in 52 and 60 Hz respectively for the two axes did the trick.
-
Here's the python test code if anyone wants to give this a shot.
https://github.com/OskarLinde/dynamic-motion
The link contains some preliminary instructions for how to use and calibrate the method. You need at least RRF 2.03 since without the
M566 P1
jerk policy the G-Code doesn't execute reliably. I was able to run this on an unmodified firmware for simple objects, but found that I could increase the discretization points and run more complex objects if I made a minor firmware change: I disabled the motion planner (look_ahead) if the current move speeds were below the current instantaneous velocity change settings. With this, the extrusion issues in the 120 mm/s print went away. Oh, I also implemented pressure advance correction the script since the firmware one doesn't work with the type of G-Code the script outputs.While there are many limitations as described above I'd love to get some testing of this approach: Were you able to get it to work? Were you able to see any improvements in ringing or improvements in print speeds? What type of printer and what calibration values did you land on?
-
Despite the fact my machine is half disassembled, and the fact I recently swapped out extruder motors without much 'calibration' this has been pretty cool to work with. I don't think I have anything workable yet, but still neat to play with. Once I get my machine put back together, hopefully I'll be able able to work through the parameters more methodically.
A quick note - I'm thinking the z_max (in mm) parameter is a bit wonky, initially I did some thin wall prints that were 100mm tall, and could only see variation (both in Craftware for previewing) and in the print for the first 20mm or so - even if I set z_max to 100. When I set it to 20mm (for a 20mm tall print) it also looks like the variation occurs in the first 5-8mm or so.
Ok, testing was done on an IDEX machine (only with T0). Fairly heavy Y gantry that carries the T0 and T1 print heads, plus X and U steppers. A quite large print bed, so I modified the python files to eliminate changes to the Z axis (had some issues during my first few tests). I get some significant defects due to the bed oscillating on layer changes. Some potentially useful information from the config.g
M92 X100.00 Y96.00 Z1280.00 U100.00 E415.00 ; Set steps per mm M566 X1000.00 Y1000.00 Z500.00 U1000.00 E36000.00 ; Set maximum instantaneous speed changes (mm/min) M566 P1 M203 X18000.00 Y18000.00 Z1320.00 U18000.00 E3000.00 ; Set maximum speeds (mm/min M201 X4000.00 Y4000.00 Z450.00 U4000.00 E3000.00 ; Set accelerations (mm/s^2) M906 X1800.00 Y2400.00 Z2400.00 U1800.00 E1960.00:500.00 I30 ; Set motor currents (mA) and motor idle factor in per cent
The unmodified print file: JP_talltestPart2.gcode
Short video of unmodified print - https://www.youtube.com/watch?v=3PDYEEwW7_I
First I ran a print of the above file - shown as "F" in pictures below ("F" for front of build plate haha). Printed with HIPS at somewhat high temperatures, no part cooling, 100mm/s. In the FRONT view, the printhead moves left to right along the X axis. In the Right view, the nozzle moves left to right along the Y axis... etc. DAA, PA, etc, all were turned off for the reference print. The reference print is stacked on top of the modified gcode print. For "Fx" I ran the script targeting x f_n, ranging from 5 to 75 across Z=20 (see note above). Yes, the X axis did a good amount of shaking.
Video - https://www.youtube.com/watch?v=6TJ2uW1rX_E
File - x-5-75.gcodeimport move # EXAMPLE CONFIGURATION, PLEASE EDIT BEFORE USING class motion_parameters: dynamic_model = [ move.spring_damper_parameters(f_n = 0, zeta = 0), # x move.spring_damper_parameters(f_n = 0, zeta = 0), # y #move.asymmetric_spring_damper_parameters(f_n_negative = 52.5, f_n_positive = 56.9, zeta = 0.02), # y move.spring_damper_parameters(f_n = 0, zeta = 0.0), # z move.pressure_advance_parameters(k = 0.00) # e ] max_speed = 300 accel = 12000 jerk = 2000000 non_motion_max_speed = 300 # i.e. extrusion-only moves non_motion_accel = 12000 non_motion_jerk = 2000000 axis_limits = [ move.axis_limits(speed = 300), # x move.axis_limits(speed = 300, accel=500), # y move.axis_limits(speed = 22, accel = 1200, jerk = 1000), # z None, #e ] # adjust a parameter dynamically based on the z height def calibration_adjustment(z): # example value_min = 5 value_max = 75 z_max = 20 # mm value = z / z_max * (value_max - value_min) + value_min #motion_parameters.dynamic_model[0].zeta = value # adjust x zeta #motion_parameters.dynamic_model[1].zeta = value # adjust y zeta motion_parameters.dynamic_model[0].f_n = value # adjust x f_n #motion_parameters.dynamic_model[1].f_n = value # adjust y f_n #motion_parameters.dynamic_model[1].f_n_negative = value # adjust y f_n #motion_parameters.dynamic_model[1].f_n_positive = value # adjust y f_n #motion_parameters.dynamic_model[3].k = value # adjust pressure advance # adjust these to adjust the linear interpolation and coarseness of the generated output # reducing the tolerances will increase the size of the output file (and more likely cause the printer motion planner to choke) class curve_parameters: tolerances = [ 0.002, # x (2µm) 0.002, # y (2µm) 0.050, # z (50µm) 0.100, # e (0.1mm) # low tolerance to prevent pressure advance from adding nodes at the potential detriment of the xy spring-damper control ] min_dt = 1/200 def disable_acceleration_control(): print('M566 P1') # RRF Jerk Policy 1 print('M572 D0 S0') # dis.able pressure advance #print('M201 X100000 Y100000 Z100000 E100000') # disable acceleration print('M201 X100000 Y100000 E100000') # disable acceleration #print('M205 X10000 Y10000 Z10000 E10000') # infinite instantaneous velocity change print('M205 X10000 Y10000 E10000') # infinite instantaneous velocity change def restore_acceleration_control(): print('M201 X4000 Y4000 Z1200 E3000') # reset acceleration #print('M205 X10 Y10 Z10 E10000') # reset instantaneous velocity change print('M205 X10 Y10 E10000') # reset instantaneous velocity change
For the image below, same notes - targeted Y axis. I read your warning on going below 20Hz and promptly ignored it hahahahaha
Video - https://www.youtube.com/watch?v=Yc9EpRiLeGE
y-5-75.gcode
import move # EXAMPLE CONFIGURATION, PLEASE EDIT BEFORE USING class motion_parameters: dynamic_model = [ move.spring_damper_parameters(f_n = 0, zeta = 0), # x move.spring_damper_parameters(f_n = 0, zeta = 0), # y #move.asymmetric_spring_damper_parameters(f_n_negative = 52.5, f_n_positive = 56.9, zeta = 0.02), # y move.spring_damper_parameters(f_n = 0, zeta = 0.0), # z move.pressure_advance_parameters(k = 0.00) # e ] max_speed = 300 accel = 12000 jerk = 2000000 non_motion_max_speed = 300 # i.e. extrusion-only moves non_motion_accel = 12000 non_motion_jerk = 2000000 axis_limits = [ move.axis_limits(speed = 300, accel=500), # x move.axis_limits(speed = 300), # y move.axis_limits(speed = 22, accel = 1200, jerk = 1000), # z None, #e ] # adjust a parameter dynamically based on the z height def calibration_adjustment(z): # example value_min = 5 value_max = 75 z_max = 20 # mm value = z / z_max * (value_max - value_min) + value_min #motion_parameters.dynamic_model[0].zeta = value # adjust x zeta #motion_parameters.dynamic_model[1].zeta = value # adjust y zeta #motion_parameters.dynamic_model[0].f_n = value # adjust x f_n motion_parameters.dynamic_model[1].f_n = value # adjust y f_n #motion_parameters.dynamic_model[1].f_n_negative = value # adjust y f_n #motion_parameters.dynamic_model[1].f_n_positive = value # adjust y f_n #motion_parameters.dynamic_model[3].k = value # adjust pressure advance # adjust these to adjust the linear interpolation and coarseness of the generated output # reducing the tolerances will increase the size of the output file (and more likely cause the printer motion planner to choke) class curve_parameters: tolerances = [ 0.002, # x (2µm) 0.002, # y (2µm) 0.050, # z (50µm) 0.100, # e (0.1mm) # low tolerance to prevent pressure advance from adding nodes at the potential detriment of the xy spring-damper control ] min_dt = 1/200 def disable_acceleration_control(): print('M566 P1') # RRF Jerk Policy 1 print('M572 D0 S0') # dis.able pressure advance #print('M201 X100000 Y100000 Z100000 E100000') # disable acceleration print('M201 X100000 Y100000 E100000') # disable acceleration #print('M205 X10000 Y10000 Z10000 E10000') # infinite instantaneous velocity change print('M205 X10000 Y10000 E10000') # infinite instantaneous velocity change def restore_acceleration_control(): print('M201 X4000 Y4000 Z1200 E3000') # reset acceleration #print('M205 X10 Y10 Z10 E10000') # reset instantaneous velocity change print('M205 X10 Y10 E10000') # reset instantaneous velocity change
Fun to watch and futz with (I did many more prints prior to this), but unfortunately nothing conclusive. What I did do was measure the ringing in the reference print on each side, and then ran the print again only targeting those values (no value hunting). I threw in X=25Hz, and (the reason escapes me now) Y=30Hz. The amplitude of the vibrations are much lower (read: surface is smoother) than the reference print (I also ran a print swapping the X and Y values, where in the inverted prints the results were not as good). Unfortunately the picture doesn't quite paint a clear picture.
Video - https://www.youtube.com/watch?v=P6JSEvgVD6A
File - x-25-y-30.gcodeimport move # EXAMPLE CONFIGURATION, PLEASE EDIT BEFORE USING class motion_parameters: dynamic_model = [ move.spring_damper_parameters(f_n = 25, zeta = 0), # x move.spring_damper_parameters(f_n = 30, zeta = 0), # y #move.asymmetric_spring_damper_parameters(f_n_negative = 52.5, f_n_positive = 56.9, zeta = 0.02), # y move.spring_damper_parameters(f_n = 0, zeta = 0.0), # z move.pressure_advance_parameters(k = 0.00) # e ] max_speed = 300 accel = 12000 jerk = 2000000 non_motion_max_speed = 300 # i.e. extrusion-only moves non_motion_accel = 12000 non_motion_jerk = 2000000 axis_limits = [ move.axis_limits(speed = 300), # x move.axis_limits(speed = 300), # y move.axis_limits(speed = 22, accel = 1200, jerk = 1000), # z None, #e ] # adjust a parameter dynamically based on the z height def calibration_adjustment(z): # example value_min = 5 value_max = 75 z_max = 20 # mm value = z / z_max * (value_max - value_min) + value_min #motion_parameters.dynamic_model[0].zeta = value # adjust x zeta #motion_parameters.dynamic_model[1].zeta = value # adjust y zeta #motion_parameters.dynamic_model[0].f_n = value # adjust x f_n #motion_parameters.dynamic_model[1].f_n = value # adjust y f_n #motion_parameters.dynamic_model[1].f_n_negative = value # adjust y f_n #motion_parameters.dynamic_model[1].f_n_positive = value # adjust y f_n #motion_parameters.dynamic_model[3].k = value # adjust pressure advance # adjust these to adjust the linear interpolation and coarseness of the generated output # reducing the tolerances will increase the size of the output file (and more likely cause the printer motion planner to choke) class curve_parameters: tolerances = [ 0.002, # x (2µm) 0.002, # y (2µm) 0.050, # z (50µm) 0.100, # e (0.1mm) # low tolerance to prevent pressure advance from adding nodes at the potential detriment of the xy spring-damper control ] min_dt = 1/200 def disable_acceleration_control(): print('M566 P1') # RRF Jerk Policy 1 print('M572 D0 S0') # dis.able pressure advance #print('M201 X100000 Y100000 Z100000 E100000') # disable acceleration print('M201 X100000 Y100000 E100000') # disable acceleration #print('M205 X10000 Y10000 Z10000 E10000') # infinite instantaneous velocity change print('M205 X10000 Y10000 E10000') # infinite instantaneous velocity change def restore_acceleration_control(): print('M201 X4000 Y4000 Z1200 E3000') # reset acceleration #print('M205 X10 Y10 Z10 E10000') # reset instantaneous velocity change print('M205 X10 Y10 E10000') # reset instantaneous velocity change
-
@sebkritikel said in A Software Solution to Eliminate Ringing?:
Despite the fact my machine is half disassembled, and the fact I recently swapped out extruder motors without much 'calibration' this has been pretty cool to work with. I don't think I have anything workable yet, but still neat to play with. Once I get my machine put back together, hopefully I'll be able able to work through the parameters more methodically.
Thanks for testing! These are very interesting results.
A quick note - I'm thinking the z_max (in mm) parameter is a bit wonky, initially I did some thin wall prints that were 100mm tall, and could only see variation (both in Craftware for previewing) and in the print for the first 20mm or so - even if I set z_max to 100. When I set it to 20mm (for a 20mm tall print) it also looks like the variation occurs in the first 5-8mm or so.
I don't think the parameter is broken – it's more the consequence to the correction magnitude of increasing f_n. The higher the number the smaller the correction, asymptotically approaching zero. The correction magnitude is proportional one over the square of the f_n number, so at say 50 Hz you will get 4x less correction than at 25 Hz, and at 5 Hz (your lowest number, the correction is 100x higher.
I modified the python files to eliminate changes to the Z axis (had some issues during my first few tests).
I get some significant defects due to the bed oscillating on layer changes. Some potentially useful information from the config.gLooking at your g-code, your slicer is not spiralizing the contour, so I think your modifications are ok. You should be able to achieve the same effect by reducing the accel setting in the z axis limits though.
The unmodified print file: JP_talltestPart2.gcode
Short video of unmodified print - https://www.youtube.com/watch?v=3PDYEEwW7_I
First I ran a print of the above file - shown as "F" in pictures below ("F" for front of build plate haha). Printed with HIPS at somewhat high temperatures, no part cooling, 100mm/s. In the FRONT view, the printhead moves left to right along the X axis. In the Right view, the nozzle moves left to right along the Y axis... etc. DAA, PA, etc, all were turned off for the reference print. The reference print is stacked on top of the modified gcode print. For "Fx" I ran the script targeting x f_n, ranging from 5 to 75 across Z=20 (see note above). Yes, the X axis did a good amount of shaking.
Video - https://www.youtube.com/watch?v=6TJ2uW1rX_E
File - x-5-75.gcodeOn the front view, you definitely see a significant improvement around one third up corresponding to roughly 25~30 Hz. I don't quite understand what's happening on the back side though. I think maybe the lack of cooling keeps the filament from achieving good adherence to the layer below.
axis_limits = [ move.axis_limits(speed = 300), # x move.axis_limits(speed = 300, accel=500), # y move.axis_limits(speed = 22, accel = 1200, jerk = 1000), # z
With such a low jerk value, I'm surprised you have issues with the z axis.
For the image below, same notes - targeted Y axis. I read your warning on going below 20Hz and promptly ignored it hahahahaha
Video - https://www.youtube.com/watch?v=Yc9EpRiLeGE
y-5-75.gcode
You should definitely increase the low end of the range from 5Hz to something higher (15 Hz maybe?). The poor extrusion at the bottom is making reading these quite hard. Another thing that helps is plugging in the ~28 Hz value from the X-axis calibration above here to minimize the cross-talk. That being said, you can definitely see a phase transitioning happening in the range of 25-50 % of the height. I've generally been successful picking the middle point of the phase transition.
Fun to watch and futz with (I did many more prints prior to this), but unfortunately nothing conclusive. What I did do was measure the ringing in the reference print on each side, and then ran the print again only targeting those values (no value hunting). I threw in X=25Hz, and (the reason escapes me now) Y=30Hz. The amplitude of the vibrations are much lower (read: surface is smoother) than the reference print (I also ran a print swapping the X and Y values, where in the inverted prints the results were not as good). Unfortunately the picture doesn't quite paint a clear picture.
I think this is a good start. It always took me a few iterations to get it tuned, you should zoom in on the parameters a bit more (e.g try 20–35 Hz). Zeta can also be critical to calibrate (depending on your printer). It's also possible that doing 12000 mm/s² acceleration is aiming too high and you start hitting non-linear spring behaviors. My CoreXY printer has ringing in the range of 52-60 Hz which is almost exactly 2x of your values. Since that means 4x as much spring compensation I'd not be surprised if you need to back off on the acceleration setting.
The thing about photos of shiny filament is that they amplify the curvature changes over the magnitude – you see similar specularities even after reducing the ringing magnitude by a significant factor. You probably want to get your f_n estimates to within 2–5% or so to start seeing more dramatic improvements.
Finding my most similar silver filament, here an "after" print I made on my CoreXY:
80 mm/s, 10,000 mm/s², 2 km/s³ jerkI didn't have enough filament to finish a "before" shot. This was as far as I got:
(identical printing parameters)Ignore the horizontal lines in the middle of the silver zone – that's when I fed in the gold filament.
-
@DigitalVision does your script support arc moves, i.e. G2/G3 moves? I am asking because I sometimes use ArcWelder as a postprocessor to turn segmented curves back into real curves (mostly to compress the gcode)...
-
@DigitalVision Many thanks for the response! Appreciate all of your comments.
Many thanks for your comments! Great insight all around.
Spiral mode... should have started with that!
Not quite there yet, but narrowing it down some - the after here is taken at f_n X to 21Hz, f_n Y to 30Hz (X picked after running from 19-26, Y a number of times from 15-50).
The Y axis was quite interesting to watch and tune for. When running from 15-100 Hz, you could see the initial set of ripples fan out some, and then a second set begin to develop. Image below is likely from 15-50Hz.
For the iterations above, I dropped accelerations to 8000mm/s^2, and jerk to 1,000,000, but have started dropping each of those lowers. I ran one print at 4000mm/s^2 and something like 80,000 (typo), which naturally took quite long, but looked great.
The acceleration values in my config.g (specifically for X, U, and Y), are not picked for print quality, rather they are derived from stepper motor torque, gantry mass, and any gearing present. Some more investigation and test needed on my end there with respect to acceleration and jerk values.
-
@sebkritikel said in A Software Solution to Eliminate Ringing?:
Spiral mode... should have started with that!
Just be careful with your hack to let the machine do Z acceleration ramping in that case. It breaks the time-critical assumption baked into the constant velocity segments that get output.
I also found that Slic3r generate a tiny extra-move at one of the corners in spiral mode which is not ideal with the script. Maybe Cura does spirals better.
Not quite there yet, but narrowing it down some - the after here is taken at f_n X to 21Hz, f_n Y to 30Hz (X picked after running from 19-26, Y a number of times from 15-50).
I curious how you end up with such low frequencies and what's dominating the X-spring in your system. Are your belts fairly loose? How long is the X travel?
The left/right asymmetry is very interesting. You may be seeing a higher frequency of ringing when the printer is accelerating in the negative Y than positive. I had something similar myself but not at all as extreme, which is why I had to implement the asymmetric spring function. Here are my settings:
dynamic_model = [ move.spring_damper_parameters(f_n = 60.5, zeta = 0), # x move.asymmetric_spring_damper_parameters(f_n_negative = 52.5, f_n_positive = 56.9, zeta = 0.02), # y move.spring_damper_parameters(f_n = 0, zeta = 0.0), # z move.pressure_advance_parameters(k = 0.08) # e ]
The Y axis was quite interesting to watch and tune for. When running from 15-100 Hz, you could see the initial set of ripples fan out some, and then a second set begin to develop. Image below is likely from 15-50Hz.
I found it easiest to calibrate the axes when looking at the acceleration ringing rather than the post-deceleration ringing, and decoupling them required setting a low acceleration (500 in my case) on the other axis.
From your image here it does look like there is possibly a bit of post-deceleration ringing at a low frequency on top (the wavy pattern) that may be creating the interference pattern with a higher frequency acceleration ringing on the bottom. You may need to go even lower/more extreme than 500 mm/s² on the X axis to get a clearer read on Y.
There could also be a non-linear spring response – or you simply need to calibrate zeta. On my delta, I did see a similar phase change pattern, so I picked the middle of the transition zone for f_n and swept zeta from 0-0.5 and picked 0.10. The effect from zeta is much more subtle, but allowed the two phases of the interference pattern to separate when re-running a f_n sweep.
For the iterations above, I dropped accelerations to 8000mm/s^2, and jerk to 1,000,000, but have started dropping each of those lowers. I ran one print at 4000mm/s^2 and something like 80,000 (typo), which naturally took quite long, but looked great.
The acceleration values in my config.g (specifically for X, U, and Y), are not picked for print quality, rather they are derived from stepper motor torque, gantry mass, and any gearing present. Some more investigation and test needed on my end there with respect to acceleration and jerk values.
-
@DigitalVision said in A Software Solution to Eliminate Ringing?:
@sebkritikel said in A Software Solution to Eliminate Ringing?:
Not quite there yet, but narrowing it down some - the after here is taken at f_n X to 21Hz, f_n Y to 30Hz (X picked after running from 19-26, Y a number of times from 15-50).
I curious how you end up with such low frequencies and what's dominating the X-spring in your system. Are your belts fairly loose? How long is the X travel?
A quick theoretical calculation on the timing belt contribution:
From Gates' datasheet, a GT3 2mm pitch, 6mm wide belt has an elongation of 0.1% @ 16.9 N of force. If we assume that the extruder is 500 g and running at 10,000 mm/s² accelerations we get 5 N of force. That would correspond to an elongation of 0.03 % or 0.3 mm/m. For a belt that is is 1 m, this corresponds to a spring constant of k=17000 N/m. The natural frequency is sqrt(k/m)/(2π) = 29.3 Hz. A 500 mm belt would have a natural frequency of 41.5 Hz.
I have a CoreXY so there are two belts pulling the extruder. My shorter x belt length from extruder to the motor pulley is ~300 mm at the center and the longer ~1050 mm.
The long belt will have a spring constant of k=15.9 N/mm and the short one k=55.6 N/mm. Since the two springs work in parallel we can simply add the two spring constants getting k=71.5 N/mm.
Assuming 500g of extruder weight we get f_n = sqrt(71.5 * 1000 / 0.500) / (2*π) = 60.2 Hz. That's remarkably close to the 60.5 Hz number I got from tuning.
-
Just a closing thought on the above. Not entirely surprising it seems the belts have the potential to explain almost all of the spring dynamics on my printer. The belt lengths are a critical parameter though, and they will vary significantly across the print envelope, so a single f_n parameter will probably not work across all of it. Luckily it's very easy to calculate the belt lengths at any given point, so it should be easy to build a simple dynamic model to correct for this.
-
@DigitalVision The X and U axis belts are each 1256mm in total length (GT2, 2mm pitch, 9mm width, through SDP/SI (GT3 was not available in that length at the time of purchasing)), so about 600mm center to center (motor to idler). Unfortunately the shoulder bolts I configured through Misumi for the idlers ended up being equivalent to partially threaded screws instead - I set the thread diameter equal to the shoulder diameter, eliminating the shoulder.... As such the X and U idler stackup is quite loose (to allow the bearings to rotate freely), and the belt tension is fairly low as well. I have plans to correct this in the new year (fingers crossed).
The Y axis belts - GT3, 3mm pitch, 15mm width, total length ~ 1235, 580mm center to center - are significantly tighter, although I have my doubts that they are tightened to spec.