Welcome to mirror list, hosted at ThFree Co, Russian Federation.

PressureEqualizer.cpp « GCode « libslic3r « src - github.com/prusa3d/PrusaSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 3b2a58a884018ffa492ea02e63d7259e35287fe8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
#include <memory.h>
#include <string.h>
#include <float.h>

#include "../libslic3r.h"
#include "../PrintConfig.hpp"

#include "PressureEqualizer.hpp"

namespace Slic3r {

PressureEqualizer::PressureEqualizer(const Slic3r::GCodeConfig *config) : 
    m_config(config)
{
    reset();
}

PressureEqualizer::~PressureEqualizer()
{
}

void PressureEqualizer::reset()
{
    circular_buffer_pos     = 0;
    circular_buffer_size    = 100;
    circular_buffer_items   = 0;
    circular_buffer.assign(circular_buffer_size, GCodeLine());

    // Preallocate some data, so that output_buffer.data() will return an empty string.
    output_buffer.assign(32, 0);
    output_buffer_length    = 0;

    m_current_extruder = 0;
    // Zero the position of the XYZE axes + the current feed
    memset(m_current_pos, 0, sizeof(float) * 5);
    m_current_extrusion_role = erNone;
    // Expect the first command to fill the nozzle (deretract).
    m_retracted = true;

    // Calculate filamet crossections for the multiple extruders.
    m_filament_crossections.clear();
    for (size_t i = 0; i < m_config->filament_diameter.values.size(); ++ i) {
        double r = m_config->filament_diameter.values[i];
        double a = 0.25f*M_PI*r*r;
        m_filament_crossections.push_back(float(a));
    }

    m_max_segment_length = 20.f;
    // Volumetric rate of a 0.45mm x 0.2mm extrusion at 60mm/s XY movement: 0.45*0.2*60*60=5.4*60 = 324 mm^3/min
    // Volumetric rate of a 0.45mm x 0.2mm extrusion at 20mm/s XY movement: 0.45*0.2*20*60=1.8*60 = 108 mm^3/min
    // Slope of the volumetric rate, changing from 20mm/s to 60mm/s over 2 seconds: (5.4-1.8)*60*60/2=60*60*1.8 = 6480 mm^3/min^2 = 1.8 mm^3/s^2
    m_max_volumetric_extrusion_rate_slope_positive = (m_config == NULL) ? 6480.f :
        m_config->max_volumetric_extrusion_rate_slope_positive.value * 60.f * 60.f;
    m_max_volumetric_extrusion_rate_slope_negative = (m_config == NULL) ? 6480.f :
        m_config->max_volumetric_extrusion_rate_slope_negative.value * 60.f * 60.f;

    for (size_t i = 0; i < numExtrusionRoles; ++ i) {
        m_max_volumetric_extrusion_rate_slopes[i].negative = m_max_volumetric_extrusion_rate_slope_negative;
        m_max_volumetric_extrusion_rate_slopes[i].positive = m_max_volumetric_extrusion_rate_slope_positive;
    }

    // Don't regulate the pressure in infill.
    m_max_volumetric_extrusion_rate_slopes[erBridgeInfill].negative = 0;
    m_max_volumetric_extrusion_rate_slopes[erBridgeInfill].positive = 0;
    // Don't regulate the pressure in gap fill.
    m_max_volumetric_extrusion_rate_slopes[erGapFill].negative = 0;
    m_max_volumetric_extrusion_rate_slopes[erGapFill].positive = 0;

    m_stat.reset();
    line_idx = 0;
}

const char* PressureEqualizer::process(const char *szGCode, bool flush)
{
    // Reset length of the output_buffer.
    output_buffer_length = 0;

    if (szGCode != 0) {
        const char *p = szGCode;
        while (*p != 0) {
            // Find end of the line.
            const char *endl = p;
            // Slic3r always generates end of lines in a Unix style.
            for (; *endl != 0 && *endl != '\n'; ++ endl) ;
            if (circular_buffer_items == circular_buffer_size)
                // Buffer is full. Push out the oldest line.
                output_gcode_line(circular_buffer[circular_buffer_pos]);
            else
                ++ circular_buffer_items;
            // Process a G-code line, store it into the provided GCodeLine object.
            size_t idx_tail = circular_buffer_pos;
            circular_buffer_pos = circular_buffer_idx_next(circular_buffer_pos);
            if (! process_line(p, endl - p, circular_buffer[idx_tail])) {
                // The line has to be forgotten. It contains comment marks, which shall be
                // filtered out of the target g-code.
                circular_buffer_pos = idx_tail;
                -- circular_buffer_items;
            }
            p = endl;
            if (*p == '\n') 
                ++ p;
        }
    }

    if (flush) {
        // Flush the remaining valid lines of the circular buffer.
        for (size_t idx = circular_buffer_idx_head(); circular_buffer_items > 0; -- circular_buffer_items) {
            output_gcode_line(circular_buffer[idx]);
            if (++ idx == circular_buffer_size)
                idx = 0;
        }
        // Reset the index pointer.
        assert(circular_buffer_items == 0);
        circular_buffer_pos = 0;

#if 1 
        printf("Statistics: \n"); 
        printf("Minimum volumetric extrusion rate: %f\n", m_stat.volumetric_extrusion_rate_min);
        printf("Maximum volumetric extrusion rate: %f\n", m_stat.volumetric_extrusion_rate_max);
        if (m_stat.extrusion_length > 0)
            m_stat.volumetric_extrusion_rate_avg /= m_stat.extrusion_length;
        printf("Average volumetric extrusion rate: %f\n", m_stat.volumetric_extrusion_rate_avg);
        m_stat.reset();
#endif
    } 

    return output_buffer.data();
}

// Is a white space?
static inline bool is_ws(const char c) { return c == ' ' || c == '\t'; }
// Is it an end of line? Consider a comment to be an end of line as well.
static inline bool is_eol(const char c) { return c == 0 || c == '\r' || c == '\n' || c == ';'; };
// Is it a white space or end of line?
static inline bool is_ws_or_eol(const char c) { return is_ws(c) || is_eol(c); };

// Eat whitespaces.
static void eatws(const char *&line)
{
    while (is_ws(*line)) 
        ++ line;
}

// Parse an int starting at the current position of a line.
// If succeeded, the line pointer is advanced.
static inline int parse_int(const char *&line)
{
    char *endptr = NULL;
    long result = strtol(line, &endptr, 10);
    if (endptr == NULL || !is_ws_or_eol(*endptr))
        throw std::runtime_error("PressureEqualizer: Error parsing an int");
    line = endptr;
    return int(result);
};

// Parse an int starting at the current position of a line.
// If succeeded, the line pointer is advanced.
static inline float parse_float(const char *&line)
{
    char *endptr = NULL;
    float result = strtof(line, &endptr);
    if (endptr == NULL || !is_ws_or_eol(*endptr))
        throw std::runtime_error("PressureEqualizer: Error parsing a float");
    line = endptr;
    return result;
};

#define EXTRUSION_ROLE_TAG ";_EXTRUSION_ROLE:"
bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLine &buf)
{
    if (strncmp(line, EXTRUSION_ROLE_TAG, strlen(EXTRUSION_ROLE_TAG)) == 0) {
        line += strlen(EXTRUSION_ROLE_TAG);
        int role = atoi(line);
        m_current_extrusion_role = ExtrusionRole(role);
        ++ line_idx;
        return false;
    }

    // Set the type, copy the line to the buffer.
    buf.type = GCODELINETYPE_OTHER;
    buf.modified = false;
    if (buf.raw.size() < len + 1)
        buf.raw.assign(line, line + len + 1);
    else
        memcpy(buf.raw.data(), line, len);
    buf.raw[len] = 0;
    buf.raw_length = len;

    memcpy(buf.pos_start, m_current_pos, sizeof(float)*5);
    memcpy(buf.pos_end, m_current_pos, sizeof(float)*5);
    memset(buf.pos_provided, 0, 5);

    buf.volumetric_extrusion_rate = 0.f;
    buf.volumetric_extrusion_rate_start = 0.f;
    buf.volumetric_extrusion_rate_end = 0.f;
    buf.max_volumetric_extrusion_rate_slope_positive = 0.f;
    buf.max_volumetric_extrusion_rate_slope_negative = 0.f;
	buf.extrusion_role = m_current_extrusion_role;

    // Parse the G-code line, store the result into the buf.
    switch (toupper(*line ++)) {
    case 'G': {
        int gcode = parse_int(line);
        eatws(line);
        switch (gcode) {
        case 0:
        case 1:
        {
            // G0, G1: A FFF 3D printer does not make a difference between the two.
            float new_pos[5];
            memcpy(new_pos, m_current_pos, sizeof(float)*5);
            bool  changed[5] = { false, false, false, false, false };
            while (!is_eol(*line)) {
                char axis = toupper(*line++);
                int  i = -1;
                switch (axis) {
                case 'X':
                case 'Y':
                case 'Z':
                    i = axis - 'X';
                    break;
                case 'E':
                    i = 3;
                    break;
                case 'F':
                    i = 4;
                    break;
                default:
                    assert(false);
                }
                if (i == -1)
                    throw std::runtime_error(std::string("GCode::PressureEqualizer: Invalid axis for G0/G1: ") + axis);
                buf.pos_provided[i] = true;
                new_pos[i] = parse_float(line);
                if (i == 3 && m_config->use_relative_e_distances.value)
                    new_pos[i] += m_current_pos[i];
                changed[i] = new_pos[i] != m_current_pos[i];
                eatws(line);
            }
            if (changed[3]) {
                // Extrusion, retract or unretract.
                float diff = new_pos[3] - m_current_pos[3];
                if (diff < 0) {
                    buf.type = GCODELINETYPE_RETRACT;
                    m_retracted = true;
                } else if (! changed[0] && ! changed[1] && ! changed[2]) {
                    // assert(m_retracted);
                    buf.type = GCODELINETYPE_UNRETRACT;
                    m_retracted = false;
                } else {
                    assert(changed[0] || changed[1]);
                    // Moving in XY plane.
                    buf.type = GCODELINETYPE_EXTRUDE;
                    // Calculate the volumetric extrusion rate.
                    float diff[4];
                    for (size_t i = 0; i < 4; ++ i)
                        diff[i] = new_pos[i] - m_current_pos[i];
                    // volumetric extrusion rate = A_filament * F_xyz * L_e / L_xyz [mm^3/min]
                    float len2 = diff[0]*diff[0]+diff[1]*diff[1]+diff[2]*diff[2];
                    float rate = m_filament_crossections[m_current_extruder] * new_pos[4] * sqrt((diff[3]*diff[3])/len2);
                    buf.volumetric_extrusion_rate       = rate;
                    buf.volumetric_extrusion_rate_start = rate;
                    buf.volumetric_extrusion_rate_end   = rate;
                    m_stat.update(rate, sqrt(len2));
                    if (rate < 40.f) {
                    	printf("Extremely low flow rate: %f. Line %d, Length: %f, extrusion: %f Old position: (%f, %f, %f), new position: (%f, %f, %f)\n", 
                            rate, 
                            int(line_idx),
                            sqrt(len2), sqrt((diff[3]*diff[3])/len2),
                            m_current_pos[0], m_current_pos[1], m_current_pos[2],
                            new_pos[0], new_pos[1], new_pos[2]);
                    }
                }
            } else if (changed[0] || changed[1] || changed[2]) {
                // Moving without extrusion.
                buf.type = GCODELINETYPE_MOVE;
            }
            memcpy(m_current_pos, new_pos, sizeof(float) * 5);
            break;
        }
        case 92: 
        {
            // G92 : Set Position
            // Set a logical coordinate position to a new value without actually moving the machine motors.
            // Which axes to set?
            bool set = false;
            while (!is_eol(*line)) {
                char axis = toupper(*line++);
                switch (axis) {
                case 'X':
                case 'Y':
                case 'Z':
                    m_current_pos[axis - 'X'] = (!is_ws_or_eol(*line)) ? parse_float(line) : 0.f;
                    set = true;
                    break;
                case 'E':
                    m_current_pos[3] = (!is_ws_or_eol(*line)) ? parse_float(line) : 0.f;
                    set = true;
                    break;
                default:
                    throw std::runtime_error(std::string("GCode::PressureEqualizer: Incorrect axis in a G92 G-code: ") + axis);
                }
                eatws(line);
            }
            assert(set);
            break;
        }
        case 10:
        case 22:
            // Firmware retract.
            buf.type = GCODELINETYPE_RETRACT;
            m_retracted = true;
            break;
        case 11:
        case 23:
            // Firmware unretract.
            buf.type = GCODELINETYPE_UNRETRACT;
            m_retracted = false;
            break;
        default:
            // Ignore the rest.
        break;
        }
        break;
    }
    case 'M': {
        int mcode = parse_int(line);
        eatws(line);
        switch (mcode) {
        default:
            // Ignore the rest of the M-codes.
        break;
        }
        break;
    }
    case 'T':
    {
        // Activate an extruder head.
        int new_extruder = parse_int(line);
        if (new_extruder != m_current_extruder) {
            m_current_extruder = new_extruder;
            m_retracted = true;
            buf.type = GCODELINETYPE_TOOL_CHANGE;
        } else {
            buf.type = GCODELINETYPE_NOOP;
        }
        break;
    }
    }

    buf.extruder_id = m_current_extruder;
    memcpy(buf.pos_end, m_current_pos, sizeof(float)*5);

    adjust_volumetric_rate();
    ++ line_idx;
	return true;
}

void PressureEqualizer::output_gcode_line(GCodeLine &line)
{
    if (! line.modified) {
        push_to_output(line.raw.data(), line.raw_length, true);
        return;
    }

    // The line was modified.
    // Find the comment.
    const char *comment = line.raw.data();
    while (*comment != ';' && *comment != 0) ++comment;
    if (*comment != ';')
        comment = NULL;
    
    // Emit the line with lowered extrusion rates.
    float l2 = line.dist_xyz2();
    float l = sqrt(l2);
    size_t nSegments = size_t(ceil(l / m_max_segment_length));
    if (nSegments == 1) {
        // Just update this segment.
        push_line_to_output(line, line.feedrate() * line.volumetric_correction_avg(), comment);
    } else {
        bool accelerating = line.volumetric_extrusion_rate_start < line.volumetric_extrusion_rate_end;
        // Update the initial and final feed rate values.
        line.pos_start[4] = line.volumetric_extrusion_rate_start * line.pos_end[4] / line.volumetric_extrusion_rate;
        line.pos_end  [4] = line.volumetric_extrusion_rate_end   * line.pos_end[4] / line.volumetric_extrusion_rate;
        float feed_avg = 0.5f * (line.pos_start[4] + line.pos_end[4]);
        // Limiting volumetric extrusion rate slope for this segment.
        float max_volumetric_extrusion_rate_slope = accelerating ?
            line.max_volumetric_extrusion_rate_slope_positive : line.max_volumetric_extrusion_rate_slope_negative; 
        // Total time for the segment, corrected for the possibly lowered volumetric feed rate,
        // if accelerating / decelerating over the complete segment.
        float t_total = line.dist_xyz() / feed_avg;
        // Time of the acceleration / deceleration part of the segment, if accelerating / decelerating
        // with the maximum volumetric extrusion rate slope.
        float t_acc    = 0.5f * (line.volumetric_extrusion_rate_start + line.volumetric_extrusion_rate_end) / max_volumetric_extrusion_rate_slope;
        float l_acc    = l;
        float l_steady = 0.f;
        if (t_acc < t_total) {
            // One may achieve higher print speeds if part of the segment is not speed limited.
            float l_acc    = t_acc * feed_avg;
            float l_steady = l - l_acc;
            if (l_steady < 0.5f * m_max_segment_length) {
                l_acc    = l;
                l_steady = 0.f;
            } else
                nSegments = size_t(ceil(l_acc / m_max_segment_length));
        }
        float pos_start[5];
        float pos_end  [5];
        float pos_end2 [4];
        memcpy(pos_start, line.pos_start, sizeof(float)*5);
        memcpy(pos_end  , line.pos_end  , sizeof(float)*5);
        if (l_steady > 0.f) {
            // There will be a steady feed segment emitted.
            if (accelerating) {
                // Prepare the final steady feed rate segment.
                memcpy(pos_end2, pos_end, sizeof(float)*4);
                float t = l_acc / l;
                for (int i = 0; i < 4; ++ i) {
                    pos_end[i] = pos_start[i] + (pos_end[i] - pos_start[i]) * t;
                    line.pos_provided[i] = true;
                }
            } else {
                // Emit the steady feed rate segment.
                float t = l_steady / l;
                for (int i = 0; i < 4; ++ i) {
                    line.pos_end[i] = pos_start[i] + (pos_end[i] - pos_start[i]) * t;
                    line.pos_provided[i] = true;
                }
                push_line_to_output(line, pos_start[4], comment);
                comment = NULL;
                memcpy(line.pos_start, line.pos_end, sizeof(float)*5);
                memcpy(pos_start, line.pos_end, sizeof(float)*5);
            }
        }
        // Split the segment into pieces.
        for (size_t i = 1; i < nSegments; ++ i) {
            float t = float(i) / float(nSegments);
            for (size_t j = 0; j < 4; ++ j) {
                line.pos_end[j] = pos_start[j] + (pos_end[j] - pos_start[j]) * t;
                line.pos_provided[j] = true;
            } 
            // Interpolate the feed rate at the center of the segment.
            push_line_to_output(line, pos_start[4] + (pos_end[4] - pos_start[4]) * (float(i) - 0.5f) / float(nSegments), comment);
            comment = NULL;
            memcpy(line.pos_start, line.pos_end, sizeof(float)*5);
        }
		if (l_steady > 0.f && accelerating) {
            for (int i = 0; i < 4; ++ i) {
                line.pos_end[i] = pos_end2[i];
                line.pos_provided[i] = true;
            }
            push_line_to_output(line, pos_end[4], comment);
        }
    }
}

void PressureEqualizer::adjust_volumetric_rate()
{
    if (circular_buffer_items < 2)
        return;

    // Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes.
    const size_t idx_head = circular_buffer_idx_head();
    const size_t idx_tail = circular_buffer_idx_prev(circular_buffer_idx_tail());
    size_t idx = idx_tail;
    if (idx == idx_head || ! circular_buffer[idx].extruding())
        // Nothing to do, the last move is not extruding.
        return;

    float feedrate_per_extrusion_role[numExtrusionRoles];
    for (size_t i = 0; i < numExtrusionRoles; ++ i)
        feedrate_per_extrusion_role[i] = FLT_MAX;
    feedrate_per_extrusion_role[circular_buffer[idx].extrusion_role] = circular_buffer[idx].volumetric_extrusion_rate_start;

    bool modified = true;
    while (modified && idx != idx_head) {
        size_t idx_prev = circular_buffer_idx_prev(idx);
        for (; ! circular_buffer[idx_prev].extruding() && idx_prev != idx_head; idx_prev = circular_buffer_idx_prev(idx_prev)) ;
        if (! circular_buffer[idx_prev].extruding())
        	break;
        // Volumetric extrusion rate at the start of the succeding segment.
        float rate_succ = circular_buffer[idx].volumetric_extrusion_rate_start;
        // What is the gradient of the extrusion rate between idx_prev and idx?
        idx = idx_prev;
        GCodeLine &line = circular_buffer[idx];
        for (size_t iRole = 1; iRole < numExtrusionRoles; ++ iRole) {
            float rate_slope = m_max_volumetric_extrusion_rate_slopes[iRole].negative;
            if (rate_slope == 0)
                // The negative rate is unlimited.
                continue;
            float rate_end = feedrate_per_extrusion_role[iRole];
            if (iRole == line.extrusion_role && rate_succ < rate_end)
                // Limit by the succeeding volumetric flow rate.
                rate_end = rate_succ;
            if (line.volumetric_extrusion_rate_end > rate_end) {
                line.volumetric_extrusion_rate_end = rate_end;
                line.modified = true;
            } else if (iRole == line.extrusion_role) {
                rate_end = line.volumetric_extrusion_rate_end;
            } else if (rate_end == FLT_MAX) {
                // The rate for ExtrusionRole iRole is unlimited.
                continue;
            } else {
                // Use the original, 'floating' extrusion rate as a starting point for the limiter.
            }
//            modified = false;
            float rate_start = rate_end + rate_slope * line.time_corrected();
            if (rate_start < line.volumetric_extrusion_rate_start) {
                // Limit the volumetric extrusion rate at the start of this segment due to a segment 
                // of ExtrusionType iRole, which will be extruded in the future.
                line.volumetric_extrusion_rate_start = rate_start;
                line.max_volumetric_extrusion_rate_slope_negative = rate_slope;
                line.modified = true;
//              modified = true;
            }
            feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_start : rate_start;
        }
    }

    // Go forward and adjust the feedrate to decrease the slope of the extrusion rate changes.
    for (size_t i = 0; i < numExtrusionRoles; ++ i)
        feedrate_per_extrusion_role[i] = FLT_MAX;
    feedrate_per_extrusion_role[circular_buffer[idx].extrusion_role] = circular_buffer[idx].volumetric_extrusion_rate_end;

    assert(circular_buffer[idx].extruding());
    while (idx != idx_tail) {
        size_t idx_next = circular_buffer_idx_next(idx);
        for (; ! circular_buffer[idx_next].extruding() && idx_next != idx_tail; idx_next = circular_buffer_idx_next(idx_next)) ;
        if (! circular_buffer[idx_next].extruding())
        	break;
        float rate_prec = circular_buffer[idx].volumetric_extrusion_rate_end;
        // What is the gradient of the extrusion rate between idx_prev and idx?
        idx = idx_next;
        GCodeLine &line = circular_buffer[idx];
        for (size_t iRole = 1; iRole < numExtrusionRoles; ++ iRole) {
            float rate_slope = m_max_volumetric_extrusion_rate_slopes[iRole].positive;
            if (rate_slope == 0)
                // The positive rate is unlimited.
                continue;
            float rate_start = feedrate_per_extrusion_role[iRole];
            if (iRole == line.extrusion_role && rate_prec < rate_start)
                rate_start = rate_prec;
            if (line.volumetric_extrusion_rate_start > rate_start) {
                line.volumetric_extrusion_rate_start = rate_start;
                line.modified = true;
            } else if (iRole == line.extrusion_role) {
                rate_start = line.volumetric_extrusion_rate_start;
            } else if (rate_start == FLT_MAX) {
                // The rate for ExtrusionRole iRole is unlimited.
                continue;
            } else {
                // Use the original, 'floating' extrusion rate as a starting point for the limiter.
            }
            float rate_end = (rate_slope == 0) ? FLT_MAX : rate_start + rate_slope * line.time_corrected();
            if (rate_end < line.volumetric_extrusion_rate_end) {
                // Limit the volumetric extrusion rate at the start of this segment due to a segment 
                // of ExtrusionType iRole, which was extruded before.
                line.volumetric_extrusion_rate_end = rate_end;
                line.max_volumetric_extrusion_rate_slope_positive = rate_slope;
                line.modified = true;
            }
            feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_end : rate_end;
        }
    }
}

void PressureEqualizer::push_axis_to_output(const char axis, const float value, bool add_eol)
{
    char buf[2048];
    int len = sprintf(buf, 
        (axis == 'E') ? " %c%.3f" : " %c%.5f",
        axis, value);
    push_to_output(buf, len, add_eol);
}

void PressureEqualizer::push_to_output(const char *text, const size_t len, bool add_eol)
{
    // New length of the output buffer content.
    size_t len_new = output_buffer_length + len + 1;
    if (add_eol)
        ++ len_new;

    // Resize the output buffer to a power of 2 higher than the required memory.
    if (output_buffer.size() < len_new) {
        size_t v = len_new;
        // Compute the next highest power of 2 of 32-bit v
        // http://graphics.stanford.edu/~seander/bithacks.html
        v--;
        v |= v >> 1;
        v |= v >> 2;
        v |= v >> 4;
        v |= v >> 8;
        v |= v >> 16;
        v++;
        output_buffer.resize(v);
    }

    // Copy the text to the output.
    if (len != 0) {
        memcpy(output_buffer.data() + output_buffer_length, text, len);
        output_buffer_length += len;
    }
    if (add_eol)
        output_buffer[output_buffer_length ++] = '\n';
    output_buffer[output_buffer_length] = 0;
}

void PressureEqualizer::push_line_to_output(const GCodeLine &line, const float new_feedrate, const char *comment)
{
    push_to_output("G1", 2, false);
    for (char i = 0; i < 3; ++ i)
        if (line.pos_provided[i])
            push_axis_to_output('X'+i, line.pos_end[i]);
    push_axis_to_output('E', m_config->use_relative_e_distances.value ? (line.pos_end[3] - line.pos_start[3]) : line.pos_end[3]);
//    if (line.pos_provided[4] || fabs(line.feedrate() - new_feedrate) > 1e-5)
        push_axis_to_output('F', new_feedrate);
    // output comment and EOL
    push_to_output(comment, (comment == NULL) ? 0 : strlen(comment), true);
} 

} // namespace Slic3r