Skip to content

Conversation

@vovodroid
Copy link
Contributor

Following to #27935 (comment) FT_MOTION NONLINEAR_EXTRUSION implementation.

@vovodroid
Copy link
Contributor Author

vovodroid commented Dec 23, 2025

@dbuezas hi, I tried your suggestion

if (use_advance_lead) {
  // Don't apply LA or NLE to retract/unretract blocks
  const float e_rate = (traj_e - prev_traj_e) * (FTM_FS);
  
  #if ENABLED(NONLINEAR_EXTRUSION)
    const float v = abs(e_rate); // extruder velocity in mm/s
    const float multiplier = max(C, stepper.ne.settings.coeff.A * sq(v) + stepper.ne.settings.coeff.B * v + stepper.ne.settings.coeff.C);
    
    traj_coords.e += e_rate * advK * multiplier;
  #else
    traj_coords.e += e_rate * advK;
  #endif
}

but without effect. So I simplified code until

  const float traj_e = traj_coords.e;
  const float e_rate = (traj_e - prev_traj_e) * (FTM_FS);
  traj_coords.e += e_rate * 1.5;
  prev_traj_e = traj_e;

and printed this code:

G92 X0
G1 X100 E100 F600

Extrusion was 100mm, as if calc_traj_point were not running. Any chance for this?

@dbuezas
Copy link
Contributor

dbuezas commented Dec 23, 2025

Oh, I see the issue, we need it to accumulate. My original suggestion adds a constant term to position, but it doesn't accumulate, so it behaves like some weird non linear advance.

You could calculate the NLE term separately accumulate it in both startPos.E and endPos_prevBlock.E

@vovodroid
Copy link
Contributor Author

vovodroid commented Dec 23, 2025

Here?

    const float totalLength = current_block->millimeters;

    startPos = endPos_prevBlock;
    const xyze_pos_t& moveDist = current_block->dist_mm;
    ratio = moveDist / totalLength;

    // Plan the trajectory using the trajectory generator
    currentGenerator->plan(current_block->entry_speed, current_block->exit_speed, current_block->acceleration, current_block->nominal_speed, totalLength);

    endPos_prevBlock += moveDist;

Multiply moveDist.E (or current_block->dist_mm.E )by nle coefficient? Where can I get block E speed or at least time?

@dbuezas
Copy link
Contributor

dbuezas commented Dec 23, 2025

I mean to basically put

nle_term = ....
traj_coord.e += nle_term
startPos.e += nle_term
endPos_prevBlock.e += nle_term

In the original place i suggested (there with linear advance)

Just make sure to calculate the nle term as a term, not a factor

@vovodroid
Copy link
Contributor Author

nle term as a term, not a factor

As far as I understand current algorithm factor we apply to the E speed, while term to the distance, right?

If speed is const float e_rate = (traj_e - prev_traj_e) * (FTM_FS); distance would be float dist = traj_e - prev_traj_e? And term would be term = dist * (factor - 1)?

@dbuezas
Copy link
Contributor

dbuezas commented Dec 23, 2025

Yes, but not dist, it is speed. So use the original equation with the multiplier, and the term would be:

nle_term = e_rate * advK * (multiplier-1)

You can optimize the arithmetic but that would be the idea, yes.

@vovodroid
Copy link
Contributor Author

nle_term = e_rate * advK * (multiplier-1)

Should * advK be here? We want to increase original extrusion rate, not Linear advance addition.

@dbuezas
Copy link
Contributor

dbuezas commented Dec 23, 2025

What i mean is not to change the formula, but just to "extract" how much it offsets position so it can be accumulated. The point of accumulating it is so that the extra extrusion is not undone down the line.

I'm wondering now that the non linearity of extrusion is afaik because the "back pressure" makes the filament compress while the teeth push it (the teeth dents get closer).
That means that it is related to nozzle pressure, and we already know nozzle pressure from the linear advance term, which is how much extra filament needs to be pushed and won't be actually extruded. Therefore, a variation of NLE we could call could be the LA term times a single " teeth bite" constant.

@dbuezas
Copy link
Contributor

dbuezas commented Dec 23, 2025

Should * advK be here? We want to increase original extrusion rate, not Linear advance addition.

You may be right, I'd need to sit with a piece of paper (tomorrow)

@vovodroid
Copy link
Contributor Author

vovodroid commented Dec 23, 2025

non linearity of extrusion is afaik because the "back pressure" makes the filament compress

Not really. It's caused by teeth slippage, and depends on speed, but not speed change. It could be 5-10% or more at the high end. So if planned speed is 10mm/sec, we should command, for instance, 11 mm/sec.

@dbuezas
Copy link
Contributor

dbuezas commented Dec 23, 2025

I remember someone measuring the teeth mark distances and them being closer when extruding strongly.
Speed and pressure are equivalent after pressure equalizes anyway, but if it is purely speed related, then it should be applied including LA, even the rate calculation should include it

@dbuezas
Copy link
Contributor

dbuezas commented Dec 23, 2025

Not really. It's caused by teeth slippage, and depends on speed, but not speed change

I'm not saying it depends on speed change, back pressure doesn't either. In fact if you look at how I implemented LA in ftmotion, it multiplies k by rate, not acceleration.

The standard motion implementation does rate += accel*k. But that's not pressure, that's it's derivative. Both implementations are mathematically equivalent, but ftmotion's doesn't lose track of the integration constant during discontinuities at the beginning of blocks (jerk)

@vovodroid
Copy link
Contributor Author

if you look at how I implemented LA in ftmotion, it multiplies k by rate, not acceleration.

Even for long constant speed movements? Actually that's exactly what NLE is supposed to do, not LA. Or do I miss something?

@dbuezas
Copy link
Contributor

dbuezas commented Dec 24, 2025

It's just the derivative:

Final_extrusion_rate(t) = planned_extrusion_vel(t)+ planned_extrusion_accel(t) * k

If you integrate both sides you get

final_extruder_position(t) = planned_extruder_position + planned_extruder_vel(t) *k

My point is that pressure is planned_extruder_vel(k) *k
It is the amount of extruded filament which doesn't leave the nozzle, it's just there to pressurise, so that the planned part is what leaves the nozzle.

If the non linearity comes from pressure (teeth grabbing closer or slippage, or whatever), then we could use that known pressure to compute it: slippage=vel*k * nle_factor, then accumulate that slippage (since it's effect isn't transient like linear advance is).

Anyway it would be a different model than normal nle

@vovodroid
Copy link
Contributor Author

planned_extruder_vel(k)

You mean planned_extruder_vel(t)?

@dbuezas
Copy link
Contributor

dbuezas commented Dec 24, 2025

Corrected

@dbuezas
Copy link
Contributor

dbuezas commented Dec 24, 2025

Ok, took a look, you were right, NLE should be calculated from the rate, but applied to the extruder delta. And applied independently of LA.

I think this should work:

if (use_advance_lead) {
  // Don't apply LA or NLE to retract/unretract blocks
  const float e_rate = (traj_e - prev_traj_e) * (FTM_FS);
  traj_coords.e += e_rate * advK;
  
  #if ENABLED(NONLINEAR_EXTRUSION)
    const float v = abs(e_rate); // extruder velocity in mm/s
    const float multiplier = max(C, stepper.ne.settings.coeff.A * sq(v) + stepper.ne.settings.coeff.B * v + stepper.ne.settings.coeff.C);
    const float nle_term = (traj_e - prev_traj_e) * (multiplier - 1);
    traj_coord.e += nle_term;
    startPos.e += nle_term;
    endPos_prevBlock.e += nle_term;
  #endif
}

@dbuezas
Copy link
Contributor

dbuezas commented Dec 24, 2025

The different instantaneous pressure based model would be like this:

if (use_advance_lead) {
  // Don't apply LA or NLE to retract/unretract blocks
  const float e_rate = (traj_e - prev_traj_e) * (FTM_FS);
  const float pressure = e_rate * advK;
  traj_coords.e += pressure;
  
  #if ENABLED(PRESSURE_DEPENDENT_EXTRUSION)
    const float multiplier = pressure * stepper.pressure_dependent_extrussion.settings.constant;
    const float nle_term = (traj_e - prev_traj_e) * (multiplier - 1);
    traj_coord.e += nle_term;
    startPos.e += nle_term;
    endPos_prevBlock.e += nle_term;
  #endif
}

@vovodroid
Copy link
Contributor Author

vovodroid commented Dec 25, 2025

I've tested (with extra ABS and hardcoded multiplier to be on the safe side)

  #if ENABLED(NONLINEAR_EXTRUSION)
    const float v = abs(e_rate); // extruder velocity in mm/s
    const float multiplier = 1.5;
    const float nle_term = abs(traj_e - prev_traj_e) * (multiplier - 1);
    traj_coord.e += nle_term;
    startPos.e += nle_term;
    endPos_prevBlock.e += nle_term;
  #endif

and this

G92 X0
G1 X100 E100 F600

coded resulted in 200mm extrusion instead of 150.

For whatever reason max(stepper.ne.settings.coeff.C, stepper.ne.settings.coeff.A * sq(v) + stepper.ne.settings.coeff.B * v + stepper.ne.settings.coeff.C) with M592 A0 B0.05 C1 stopped extrusion at all, but it would be next stage.

@dbuezas
Copy link
Contributor

dbuezas commented Dec 25, 2025

The abs in the delta shouldn't be necessary (arguably wrong if nle were to ever see retractions)
Try
prev_traj_e = traj_e +nle
So the rates are correct. You can surely follow the idea and find cleaner alternatives

@vovodroid
Copy link
Contributor Author

vovodroid commented Dec 25, 2025

Still 200mm.

const float v = abs(e_rate); // extruder velocity in mm/s
const float multiplier = 1.5;//max(stepper.ne.settings.coeff.C, stepper.ne.settings.coeff.A * sq(v) + stepper.ne.settings.coeff.B * v + stepper.ne.settings.coeff.C);
const float nle_term = abs(traj_e - prev_traj_e) * (multiplier - 1);
traj_coords.e += nle_term;
prev_traj_e = traj_e + nle_term;
startPos.e += nle_term;
endPos_prevBlock.e += nle_term;

You can surely follow the idea

Well, I'm not familiar with FTM algorithm and code.

@vovodroid
Copy link
Contributor Author

vovodroid commented Dec 25, 2025

I've put multiplier = 1.25, and got 135mm extrusion. Does it point to something?

By a way, this code is not ISR, isn't it? So can we use DEBUG?

@dbuezas
Copy link
Contributor

dbuezas commented Dec 25, 2025

The code doesn't work in the isr, but it runs at 1khz (on average) so if you put a serial print, make sure to not call it on every iteration.
The basics of ftmotion are that blocks come in, and calculate_traj is called every ms. It returns the coordinate of each axis of that moment in time

@vovodroid
Copy link
Contributor Author

vovodroid commented Dec 26, 2025

I added

      static uint64_t ticks = 0;
      if (ticks++ % 1000 == 0) {
        SERIAL_ECHOLNPGM("traj_e ", traj_e, ", prev_traj_e", prev_traj_e, ", traj_delta ", traj_delta, ", e_rate ", e_rate, ", nle_term ", nle_term);
        SERIAL_ECHOLNPGM("startPos.e ", startPos.e, ", endPos_prevBlock.e ", endPos_prevBlock.e);
      }

but got only one output:

12:01:32.504 File opened: nle~1.gco Size: 61
12:01:32.504 File selected
12:01:32.520 traj_e 0.0032, prev_traj_e0.0000, traj_delta 0.0032, e_rate 3.2277, nle_term 0.0016
12:01:32.521 startPos.e 0.0000, endPos_prevBlock.e 100.0000
12:01:34.520 echo:busy: processing
12:01:36.520 echo:busy: processing
12:01:38.520 echo:busy: processing
12:01:40.520 echo:busy: processing
12:01:42.520 echo:busy: processing
12:01:42.564 Done printing file

Does printing block SERIAL_ECHOLNPGM? Anyway, could we learn something? Is it correct that startPos.e is 0, while endPos_prevBlock.e is 100?

@dbuezas
Copy link
Contributor

dbuezas commented Dec 26, 2025

i can take a look at the code if you commit it

@vovodroid
Copy link
Contributor Author

Committed with your remarks.

e_rate * advK added twice

could it explain extrusion of 200 instead of 150? K is 0.02, assume it's 0.04. Still it has very little effect on 100mm constant speed extrusion.

@vovodroid
Copy link
Contributor Author

It seems to work now, thanks for help @dbuezas!

I combined checks for Advance K and NLE to remove duplicates.

If it looks good for you I'll mark PR as ready for review.

@vovodroid
Copy link
Contributor Author

By a way, abs(...) not needed, extruder always advance. I've got logs issuing command from terminal instead of printing.

20:14:48.295 mult 1.4990 A 0.0000, B 0.0500, C 1.0000, e_rate 9.9792, nle_term 0.0050
20:14:48.296 traj_e 293.8622, prev_traj_e 293.8522, traj_delta 0.0100, e_rate 9.9792, nle_term 0.0050

20:14:48.297 startPos.e 264.6396, endPos_prevBlock.e 364.6437
20:14:49.293 mult 1.4990 A 0.0000, B 0.0500, C 1.0000, e_rate 9.9792, nle_term 0.0050

20:14:49.297 traj_e 308.8568, prev_traj_e 308.8468, traj_delta 0.0100, e_rate 9.9792, nle_term 0.0050
20:14:49.298 startPos.e 269.6349, endPos_prevBlock.e 369.6390

@vovodroid
Copy link
Contributor Author

Also currently #error "NONLINEAR_EXTRUSION requires a 32-bit CPU.". FTM runs also for 8 bits, and is much more heavy than NLE. Could we remove such limitation?

@thinkyhead
Copy link
Member

Also currently #error "NONLINEAR_EXTRUSION requires a 32-bit CPU.". FTM runs also for 8 bits, and is much more heavy than NLE. Could we remove such limitation?

Non-linear extrusion adds a call to calc_nonlinear_e during the block phase, and if SMOOTH_LIN_ADVANCE is enabled there's some additional calculation in the block phase and in the smooth_lin_adv_isr. These do some 32 and 64 bit maths with multiplication. If these don't add too much to ISR processing and the machine has the available resources then it might be possible to use NONLINEAR_EXTRUSION on AVR. It would be good to have actual data on that.

@vovodroid
Copy link
Contributor Author

Non-linear extrusion adds a call to calc_nonlinear_e during the block phase

Ok, but could we use it on 8bit for FTM?

@dbuezas
Copy link
Contributor

dbuezas commented Dec 26, 2025

By a way, abs(...) not needed, extruder always advance.

Worth a code comment there, something like "use_advance_lead guarantees delta is positive" should suffice

@vovodroid vovodroid marked this pull request as ready for review December 27, 2025 08:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants