mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-21 21:58:03 -06:00
Add the full source of BambuStudio
using version 1.0.10
This commit is contained in:
parent
30bcadab3e
commit
1555904bef
3771 changed files with 1251328 additions and 0 deletions
1599
src/libslic3r/GCode/AvoidCrossingPerimeters.cpp
Normal file
1599
src/libslic3r/GCode/AvoidCrossingPerimeters.cpp
Normal file
File diff suppressed because it is too large
Load diff
71
src/libslic3r/GCode/AvoidCrossingPerimeters.hpp
Normal file
71
src/libslic3r/GCode/AvoidCrossingPerimeters.hpp
Normal file
|
@ -0,0 +1,71 @@
|
|||
#ifndef slic3r_AvoidCrossingPerimeters_hpp_
|
||||
#define slic3r_AvoidCrossingPerimeters_hpp_
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../ExPolygon.hpp"
|
||||
#include "../EdgeGrid.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Forward declarations.
|
||||
class GCode;
|
||||
class Layer;
|
||||
class Point;
|
||||
|
||||
class AvoidCrossingPerimeters
|
||||
{
|
||||
public:
|
||||
// Routing around the objects vs. inside a single object.
|
||||
void use_external_mp(bool use = true) { m_use_external_mp = use; };
|
||||
void use_external_mp_once() { m_use_external_mp_once = true; }
|
||||
bool used_external_mp_once() { return m_use_external_mp_once; }
|
||||
void disable_once() { m_disabled_once = true; }
|
||||
bool disabled_once() const { return m_disabled_once; }
|
||||
void reset_once_modifiers() { m_use_external_mp_once = false; m_disabled_once = false; }
|
||||
|
||||
void init_layer(const Layer &layer);
|
||||
|
||||
Polyline travel_to(const GCode& gcodegen, const Point& point)
|
||||
{
|
||||
bool could_be_wipe_disabled;
|
||||
return this->travel_to(gcodegen, point, &could_be_wipe_disabled);
|
||||
}
|
||||
|
||||
Polyline travel_to(const GCode& gcodegen, const Point& point, bool* could_be_wipe_disabled);
|
||||
|
||||
struct Boundary {
|
||||
// Collection of boundaries used for detection of crossing perimeters for travels
|
||||
Polygons boundaries;
|
||||
// Bounding box of boundaries
|
||||
BoundingBoxf bbox;
|
||||
// Precomputed distances of all points in boundaries
|
||||
std::vector<std::vector<float>> boundaries_params;
|
||||
// Used for detection of intersection between line and any polygon from boundaries
|
||||
EdgeGrid::Grid grid;
|
||||
|
||||
void clear()
|
||||
{
|
||||
boundaries.clear();
|
||||
boundaries_params.clear();
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
bool m_use_external_mp { false };
|
||||
// just for the next travel move
|
||||
bool m_use_external_mp_once { false };
|
||||
// this flag disables reduce_crossing_wall just for the next travel move
|
||||
// we enable it by default for the first travel move in print
|
||||
bool m_disabled_once { true };
|
||||
|
||||
// Used for detection of line or polyline is inside of any polygon.
|
||||
EdgeGrid::Grid m_grid_lslice;
|
||||
// Store all needed data for travels inside object
|
||||
Boundary m_internal;
|
||||
// Store all needed data for travels outside object
|
||||
Boundary m_external;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_AvoidCrossingPerimeters_hpp_
|
908
src/libslic3r/GCode/CoolingBuffer.cpp
Normal file
908
src/libslic3r/GCode/CoolingBuffer.cpp
Normal file
|
@ -0,0 +1,908 @@
|
|||
#include "../GCode.hpp"
|
||||
#include "CoolingBuffer.hpp"
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <iostream>
|
||||
#include <float.h>
|
||||
|
||||
#if 0
|
||||
#define DEBUG
|
||||
#define _DEBUG
|
||||
#undef NDEBUG
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0)
|
||||
{
|
||||
this->reset(gcodegen.writer().get_position());
|
||||
|
||||
const std::vector<Extruder> &extruders = gcodegen.writer().extruders();
|
||||
m_extruder_ids.reserve(extruders.size());
|
||||
for (const Extruder &ex : extruders) {
|
||||
m_num_extruders = std::max(ex.id() + 1, m_num_extruders);
|
||||
m_extruder_ids.emplace_back(ex.id());
|
||||
}
|
||||
}
|
||||
|
||||
void CoolingBuffer::reset(const Vec3d &position)
|
||||
{
|
||||
// BBS: add I and J axis to store center of arc
|
||||
m_current_pos.assign(7, 0.f);
|
||||
m_current_pos[0] = float(position.x());
|
||||
m_current_pos[1] = float(position.y());
|
||||
m_current_pos[2] = float(position.z());
|
||||
m_current_pos[4] = float(m_config.travel_speed.value);
|
||||
m_fan_speed = -1;
|
||||
m_additional_fan_speed = -1;
|
||||
m_current_fan_speed = -1;
|
||||
}
|
||||
|
||||
struct CoolingLine
|
||||
{
|
||||
enum Type {
|
||||
TYPE_SET_TOOL = 1 << 0,
|
||||
TYPE_EXTRUDE_END = 1 << 1,
|
||||
TYPE_OVERHANG_FAN_START = 1 << 2,
|
||||
TYPE_OVERHANG_FAN_END = 1 << 3,
|
||||
TYPE_G0 = 1 << 4,
|
||||
TYPE_G1 = 1 << 5,
|
||||
TYPE_ADJUSTABLE = 1 << 6,
|
||||
TYPE_EXTERNAL_PERIMETER = 1 << 7,
|
||||
// The line sets a feedrate.
|
||||
TYPE_HAS_F = 1 << 8,
|
||||
TYPE_WIPE = 1 << 9,
|
||||
TYPE_G4 = 1 << 10,
|
||||
TYPE_G92 = 1 << 11,
|
||||
//BBS: add G2 G3 type
|
||||
TYPE_G2 = 1 << 12,
|
||||
TYPE_G3 = 1 << 13,
|
||||
TYPE_FORCE_RESUME_FAN = 1 << 14,
|
||||
};
|
||||
|
||||
CoolingLine(unsigned int type, size_t line_start, size_t line_end) :
|
||||
type(type), line_start(line_start), line_end(line_end),
|
||||
length(0.f), feedrate(0.f), time(0.f), time_max(0.f), slowdown(false) {}
|
||||
|
||||
bool adjustable(bool slowdown_external_perimeters) const {
|
||||
return (this->type & TYPE_ADJUSTABLE) &&
|
||||
(! (this->type & TYPE_EXTERNAL_PERIMETER) || slowdown_external_perimeters) &&
|
||||
this->time < this->time_max;
|
||||
}
|
||||
|
||||
bool adjustable() const {
|
||||
return (this->type & TYPE_ADJUSTABLE) && this->time < this->time_max;
|
||||
}
|
||||
|
||||
size_t type;
|
||||
// Start of this line at the G-code snippet.
|
||||
size_t line_start;
|
||||
// End of this line at the G-code snippet.
|
||||
size_t line_end;
|
||||
// XY Euclidian length of this segment.
|
||||
float length;
|
||||
// Current feedrate, possibly adjusted.
|
||||
float feedrate;
|
||||
// Current duration of this segment.
|
||||
float time;
|
||||
// Maximum duration of this segment.
|
||||
float time_max;
|
||||
// If marked with the "slowdown" flag, the line has been slowed down.
|
||||
bool slowdown;
|
||||
};
|
||||
|
||||
// Calculate the required per extruder time stretches.
|
||||
struct PerExtruderAdjustments
|
||||
{
|
||||
// Calculate the total elapsed time per this extruder, adjusted for the slowdown.
|
||||
float elapsed_time_total() const {
|
||||
float time_total = 0.f;
|
||||
for (const CoolingLine &line : lines)
|
||||
time_total += line.time;
|
||||
return time_total;
|
||||
}
|
||||
// Calculate the total elapsed time when slowing down
|
||||
// to the minimum extrusion feed rate defined for the current material.
|
||||
float maximum_time_after_slowdown(bool slowdown_external_perimeters) const {
|
||||
float time_total = 0.f;
|
||||
for (const CoolingLine &line : lines)
|
||||
if (line.adjustable(slowdown_external_perimeters)) {
|
||||
if (line.time_max == FLT_MAX)
|
||||
return FLT_MAX;
|
||||
else
|
||||
time_total += line.time_max;
|
||||
} else
|
||||
time_total += line.time;
|
||||
return time_total;
|
||||
}
|
||||
// Calculate the adjustable part of the total time.
|
||||
float adjustable_time(bool slowdown_external_perimeters) const {
|
||||
float time_total = 0.f;
|
||||
for (const CoolingLine &line : lines)
|
||||
if (line.adjustable(slowdown_external_perimeters))
|
||||
time_total += line.time;
|
||||
return time_total;
|
||||
}
|
||||
// Calculate the non-adjustable part of the total time.
|
||||
float non_adjustable_time(bool slowdown_external_perimeters) const {
|
||||
float time_total = 0.f;
|
||||
for (const CoolingLine &line : lines)
|
||||
if (! line.adjustable(slowdown_external_perimeters))
|
||||
time_total += line.time;
|
||||
return time_total;
|
||||
}
|
||||
// Slow down the adjustable extrusions to the minimum feedrate allowed for the current extruder material.
|
||||
// Used by both proportional and non-proportional slow down.
|
||||
float slowdown_to_minimum_feedrate(bool slowdown_external_perimeters) {
|
||||
float time_total = 0.f;
|
||||
for (CoolingLine &line : lines) {
|
||||
if (line.adjustable(slowdown_external_perimeters)) {
|
||||
assert(line.time_max >= 0.f && line.time_max < FLT_MAX);
|
||||
line.slowdown = true;
|
||||
line.time = line.time_max;
|
||||
line.feedrate = line.length / line.time;
|
||||
}
|
||||
time_total += line.time;
|
||||
}
|
||||
return time_total;
|
||||
}
|
||||
// Slow down each adjustable G-code line proportionally by a factor.
|
||||
// Used by the proportional slow down.
|
||||
float slow_down_proportional(float factor, bool slowdown_external_perimeters) {
|
||||
assert(factor >= 1.f);
|
||||
float time_total = 0.f;
|
||||
for (CoolingLine &line : lines) {
|
||||
if (line.adjustable(slowdown_external_perimeters)) {
|
||||
line.slowdown = true;
|
||||
line.time = std::min(line.time_max, line.time * factor);
|
||||
line.feedrate = line.length / line.time;
|
||||
}
|
||||
time_total += line.time;
|
||||
}
|
||||
return time_total;
|
||||
}
|
||||
|
||||
// Sort the lines, adjustable first, higher feedrate first.
|
||||
// Used by non-proportional slow down.
|
||||
void sort_lines_by_decreasing_feedrate() {
|
||||
std::sort(lines.begin(), lines.end(), [](const CoolingLine &l1, const CoolingLine &l2) {
|
||||
bool adj1 = l1.adjustable();
|
||||
bool adj2 = l2.adjustable();
|
||||
return (adj1 == adj2) ? l1.feedrate > l2.feedrate : adj1;
|
||||
});
|
||||
for (n_lines_adjustable = 0;
|
||||
n_lines_adjustable < lines.size() && this->lines[n_lines_adjustable].adjustable();
|
||||
++ n_lines_adjustable);
|
||||
time_non_adjustable = 0.f;
|
||||
for (size_t i = n_lines_adjustable; i < lines.size(); ++ i)
|
||||
time_non_adjustable += lines[i].time;
|
||||
}
|
||||
|
||||
// Calculate the maximum time stretch when slowing down to min_feedrate.
|
||||
// Slowdown to min_feedrate shall be allowed for this extruder's material.
|
||||
// Used by non-proportional slow down.
|
||||
float time_stretch_when_slowing_down_to_feedrate(float min_feedrate) const {
|
||||
float time_stretch = 0.f;
|
||||
assert(this->slow_down_min_speed < min_feedrate + EPSILON);
|
||||
for (size_t i = 0; i < n_lines_adjustable; ++ i) {
|
||||
const CoolingLine &line = lines[i];
|
||||
if (line.feedrate > min_feedrate)
|
||||
time_stretch += line.time * (line.feedrate / min_feedrate - 1.f);
|
||||
}
|
||||
return time_stretch;
|
||||
}
|
||||
|
||||
// Slow down all adjustable lines down to min_feedrate.
|
||||
// Slowdown to min_feedrate shall be allowed for this extruder's material.
|
||||
// Used by non-proportional slow down.
|
||||
void slow_down_to_feedrate(float min_feedrate) {
|
||||
assert(this->slow_down_min_speed < min_feedrate + EPSILON);
|
||||
for (size_t i = 0; i < n_lines_adjustable; ++ i) {
|
||||
CoolingLine &line = lines[i];
|
||||
if (line.feedrate > min_feedrate) {
|
||||
line.time *= std::max(1.f, line.feedrate / min_feedrate);
|
||||
line.feedrate = min_feedrate;
|
||||
line.slowdown = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extruder, for which the G-code will be adjusted.
|
||||
unsigned int extruder_id = 0;
|
||||
// Is the cooling slow down logic enabled for this extruder's material?
|
||||
bool cooling_slow_down_enabled = false;
|
||||
// Slow down the print down to slow_down_min_speed if the total layer time is below slow_down_layer_time.
|
||||
float slow_down_layer_time = 0.f;
|
||||
// Minimum print speed allowed for this extruder.
|
||||
float slow_down_min_speed = 0.f;
|
||||
|
||||
// Parsed lines.
|
||||
std::vector<CoolingLine> lines;
|
||||
// The following two values are set by sort_lines_by_decreasing_feedrate():
|
||||
// Number of adjustable lines, at the start of lines.
|
||||
size_t n_lines_adjustable = 0;
|
||||
// Non-adjustable time of lines starting with n_lines_adjustable.
|
||||
float time_non_adjustable = 0;
|
||||
// Current total time for this extruder.
|
||||
float time_total = 0;
|
||||
// Maximum time for this extruder, when the maximum slow down is applied.
|
||||
float time_maximum = 0;
|
||||
|
||||
// Temporaries for processing the slow down. Both thresholds go from 0 to n_lines_adjustable.
|
||||
size_t idx_line_begin = 0;
|
||||
size_t idx_line_end = 0;
|
||||
};
|
||||
|
||||
// Calculate a new feedrate when slowing down by time_stretch for segments faster than min_feedrate.
|
||||
// Used by non-proportional slow down.
|
||||
float new_feedrate_to_reach_time_stretch(
|
||||
std::vector<PerExtruderAdjustments*>::const_iterator it_begin, std::vector<PerExtruderAdjustments*>::const_iterator it_end,
|
||||
float min_feedrate, float time_stretch, size_t max_iter = 20)
|
||||
{
|
||||
float new_feedrate = min_feedrate;
|
||||
for (size_t iter = 0; iter < max_iter; ++ iter) {
|
||||
float nomin = 0;
|
||||
float denom = time_stretch;
|
||||
for (auto it = it_begin; it != it_end; ++ it) {
|
||||
assert((*it)->slow_down_min_speed < min_feedrate + EPSILON);
|
||||
for (size_t i = 0; i < (*it)->n_lines_adjustable; ++i) {
|
||||
const CoolingLine &line = (*it)->lines[i];
|
||||
if (line.feedrate > min_feedrate) {
|
||||
nomin += line.time * line.feedrate;
|
||||
denom += line.time;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(denom > 0);
|
||||
if (denom < 0)
|
||||
return min_feedrate;
|
||||
new_feedrate = nomin / denom;
|
||||
assert(new_feedrate > min_feedrate - EPSILON);
|
||||
if (new_feedrate < min_feedrate + EPSILON)
|
||||
goto finished;
|
||||
for (auto it = it_begin; it != it_end; ++ it)
|
||||
for (size_t i = 0; i < (*it)->n_lines_adjustable; ++i) {
|
||||
const CoolingLine &line = (*it)->lines[i];
|
||||
if (line.feedrate > min_feedrate && line.feedrate < new_feedrate)
|
||||
// Some of the line segments taken into account in the calculation of nomin / denom are now slower than new_feedrate,
|
||||
// which makes the new_feedrate lower than it should be.
|
||||
// Re-run the calculation with a new min_feedrate limit, so that the segments with current feedrate lower than new_feedrate
|
||||
// are not taken into account.
|
||||
goto not_finished_yet;
|
||||
}
|
||||
goto finished;
|
||||
not_finished_yet:
|
||||
min_feedrate = new_feedrate;
|
||||
}
|
||||
// Failed to find the new feedrate for the time_stretch.
|
||||
|
||||
finished:
|
||||
// Test whether the time_stretch was achieved.
|
||||
#ifndef NDEBUG
|
||||
{
|
||||
float time_stretch_final = 0.f;
|
||||
for (auto it = it_begin; it != it_end; ++ it)
|
||||
time_stretch_final += (*it)->time_stretch_when_slowing_down_to_feedrate(new_feedrate);
|
||||
assert(std::abs(time_stretch - time_stretch_final) < EPSILON);
|
||||
}
|
||||
#endif /* NDEBUG */
|
||||
|
||||
return new_feedrate;
|
||||
}
|
||||
|
||||
std::string CoolingBuffer::process_layer(std::string &&gcode, size_t layer_id, bool flush)
|
||||
{
|
||||
// Cache the input G-code.
|
||||
if (m_gcode.empty())
|
||||
m_gcode = std::move(gcode);
|
||||
else
|
||||
m_gcode += gcode;
|
||||
|
||||
std::string out;
|
||||
if (flush) {
|
||||
// This is either an object layer or the very last print layer. Calculate cool down over the collected support layers
|
||||
// and one object layer.
|
||||
std::vector<PerExtruderAdjustments> per_extruder_adjustments = this->parse_layer_gcode(m_gcode, m_current_pos);
|
||||
float layer_time_stretched = this->calculate_layer_slowdown(per_extruder_adjustments);
|
||||
out = this->apply_layer_cooldown(m_gcode, layer_id, layer_time_stretched, per_extruder_adjustments);
|
||||
m_gcode.clear();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Parse the layer G-code for the moves, which could be adjusted.
|
||||
// Return the list of parsed lines, bucketed by an extruder.
|
||||
std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::vector<float> ¤t_pos) const
|
||||
{
|
||||
std::vector<PerExtruderAdjustments> per_extruder_adjustments(m_extruder_ids.size());
|
||||
std::vector<size_t> map_extruder_to_per_extruder_adjustment(m_num_extruders, 0);
|
||||
for (size_t i = 0; i < m_extruder_ids.size(); ++ i) {
|
||||
PerExtruderAdjustments &adj = per_extruder_adjustments[i];
|
||||
unsigned int extruder_id = m_extruder_ids[i];
|
||||
adj.extruder_id = extruder_id;
|
||||
adj.cooling_slow_down_enabled = m_config.slow_down_for_layer_cooling.get_at(extruder_id);
|
||||
adj.slow_down_layer_time = float(m_config.slow_down_layer_time.get_at(extruder_id));
|
||||
adj.slow_down_min_speed = float(m_config.slow_down_min_speed.get_at(extruder_id));
|
||||
map_extruder_to_per_extruder_adjustment[extruder_id] = i;
|
||||
}
|
||||
|
||||
unsigned int current_extruder = m_current_extruder;
|
||||
PerExtruderAdjustments *adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]];
|
||||
const char *line_start = gcode.c_str();
|
||||
const char *line_end = line_start;
|
||||
// Index of an existing CoolingLine of the current adjustment, which holds the feedrate setting command
|
||||
// for a sequence of extrusion moves.
|
||||
size_t active_speed_modifier = size_t(-1);
|
||||
|
||||
for (; *line_start != 0; line_start = line_end)
|
||||
{
|
||||
while (*line_end != '\n' && *line_end != 0)
|
||||
++ line_end;
|
||||
// sline will not contain the trailing '\n'.
|
||||
std::string sline(line_start, line_end);
|
||||
// CoolingLine will contain the trailing '\n'.
|
||||
if (*line_end == '\n')
|
||||
++ line_end;
|
||||
CoolingLine line(0, line_start - gcode.c_str(), line_end - gcode.c_str());
|
||||
if (boost::starts_with(sline, "G0 "))
|
||||
line.type = CoolingLine::TYPE_G0;
|
||||
else if (boost::starts_with(sline, "G1 "))
|
||||
line.type = CoolingLine::TYPE_G1;
|
||||
else if (boost::starts_with(sline, "G92 "))
|
||||
line.type = CoolingLine::TYPE_G92;
|
||||
else if (boost::starts_with(sline, "G2 "))
|
||||
line.type = CoolingLine::TYPE_G2;
|
||||
else if (boost::starts_with(sline, "G3 "))
|
||||
line.type = CoolingLine::TYPE_G3;
|
||||
if (line.type) {
|
||||
// G0, G1 or G92
|
||||
// Parse the G-code line.
|
||||
std::vector<float> new_pos(current_pos);
|
||||
const char *c = sline.data() + 3;
|
||||
for (;;) {
|
||||
// Skip whitespaces.
|
||||
for (; *c == ' ' || *c == '\t'; ++ c);
|
||||
if (*c == 0 || *c == ';')
|
||||
break;
|
||||
|
||||
assert(is_decimal_separator_point()); // for atof
|
||||
//BBS: Parse the axis.
|
||||
size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') :
|
||||
(*c == 'E') ? 3 : (*c == 'F') ? 4 :
|
||||
(*c == 'I') ? 5 : (*c == 'J') ? 6 : size_t(-1);
|
||||
if (axis != size_t(-1)) {
|
||||
new_pos[axis] = float(atof(++c));
|
||||
if (axis == 4) {
|
||||
// Convert mm/min to mm/sec.
|
||||
new_pos[4] /= 60.f;
|
||||
if ((line.type & CoolingLine::TYPE_G92) == 0)
|
||||
// This is G0 or G1 line and it sets the feedrate. This mark is used for reducing the duplicate F calls.
|
||||
line.type |= CoolingLine::TYPE_HAS_F;
|
||||
} else if (axis == 5 || axis == 6) {
|
||||
// BBS: get position of arc center
|
||||
new_pos[axis] += current_pos[axis - 5];
|
||||
}
|
||||
}
|
||||
// Skip this word.
|
||||
for (; *c != ' ' && *c != '\t' && *c != 0; ++ c);
|
||||
}
|
||||
bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER");
|
||||
bool wipe = boost::contains(sline, ";_WIPE");
|
||||
if (external_perimeter)
|
||||
line.type |= CoolingLine::TYPE_EXTERNAL_PERIMETER;
|
||||
if (wipe)
|
||||
line.type |= CoolingLine::TYPE_WIPE;
|
||||
if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && ! wipe) {
|
||||
line.type |= CoolingLine::TYPE_ADJUSTABLE;
|
||||
active_speed_modifier = adjustment->lines.size();
|
||||
}
|
||||
if ((line.type & CoolingLine::TYPE_G92) == 0) {
|
||||
//BBS: G0, G1, G2, G3. Calculate the duration.
|
||||
if (RELATIVE_E_AXIS)
|
||||
// Reset extruder accumulator.
|
||||
current_pos[3] = 0.f;
|
||||
float dif[4];
|
||||
for (size_t i = 0; i < 4; ++ i)
|
||||
dif[i] = new_pos[i] - current_pos[i];
|
||||
float dxy2 = 0;
|
||||
//BBS: support to calculate length of arc
|
||||
if (line.type & CoolingLine::TYPE_G2 || line.type & CoolingLine::TYPE_G3) {
|
||||
Vec3f start(current_pos[0], current_pos[1], 0);
|
||||
Vec3f end(new_pos[0], new_pos[1], 0);
|
||||
Vec3f center(new_pos[5], new_pos[6], 0);
|
||||
bool is_ccw = line.type & CoolingLine::TYPE_G3;
|
||||
float dxy = ArcSegment::calc_arc_length(start, end, center, is_ccw);
|
||||
dxy2 = dxy * dxy;
|
||||
} else {
|
||||
dxy2 = dif[0] * dif[0] + dif[1] * dif[1];
|
||||
}
|
||||
float dxyz2 = dxy2 + dif[2] * dif[2];
|
||||
if (dxyz2 > 0.f) {
|
||||
// Movement in xyz, calculate time from the xyz Euclidian distance.
|
||||
line.length = sqrt(dxyz2);
|
||||
} else if (std::abs(dif[3]) > 0.f) {
|
||||
// Movement in the extruder axis.
|
||||
line.length = std::abs(dif[3]);
|
||||
}
|
||||
line.feedrate = new_pos[4];
|
||||
assert((line.type & CoolingLine::TYPE_ADJUSTABLE) == 0 || line.feedrate > 0.f);
|
||||
if (line.length > 0)
|
||||
line.time = line.length / line.feedrate;
|
||||
line.time_max = line.time;
|
||||
if ((line.type & CoolingLine::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1))
|
||||
line.time_max = (adjustment->slow_down_min_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / adjustment->slow_down_min_speed);
|
||||
// BBS: add G2 and G3 support
|
||||
if (active_speed_modifier < adjustment->lines.size() && ((line.type & CoolingLine::TYPE_G1) ||
|
||||
(line.type & CoolingLine::TYPE_G2) ||
|
||||
(line.type & CoolingLine::TYPE_G3))) {
|
||||
// Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry.
|
||||
assert((line.type & CoolingLine::TYPE_HAS_F) == 0);
|
||||
CoolingLine &sm = adjustment->lines[active_speed_modifier];
|
||||
assert(sm.feedrate > 0.f);
|
||||
sm.length += line.length;
|
||||
sm.time += line.time;
|
||||
if (sm.time_max != FLT_MAX) {
|
||||
if (line.time_max == FLT_MAX)
|
||||
sm.time_max = FLT_MAX;
|
||||
else
|
||||
sm.time_max += line.time_max;
|
||||
}
|
||||
// Don't store this line.
|
||||
line.type = 0;
|
||||
}
|
||||
}
|
||||
current_pos = std::move(new_pos);
|
||||
} else if (boost::starts_with(sline, ";_EXTRUDE_END")) {
|
||||
line.type = CoolingLine::TYPE_EXTRUDE_END;
|
||||
active_speed_modifier = size_t(-1);
|
||||
} else if (boost::starts_with(sline, m_toolchange_prefix)) {
|
||||
unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + m_toolchange_prefix.size());
|
||||
// Only change extruder in case the number is meaningful. User could provide an out-of-range index through custom gcodes - those shall be ignored.
|
||||
if (new_extruder < map_extruder_to_per_extruder_adjustment.size()) {
|
||||
if (new_extruder != current_extruder) {
|
||||
// Switch the tool.
|
||||
line.type = CoolingLine::TYPE_SET_TOOL;
|
||||
current_extruder = new_extruder;
|
||||
adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]];
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Only log the error in case of MM printer. Single extruder printers likely ignore any T anyway.
|
||||
if (map_extruder_to_per_extruder_adjustment.size() > 1)
|
||||
BOOST_LOG_TRIVIAL(error) << "CoolingBuffer encountered an invalid toolchange, maybe from a custom gcode: " << sline;
|
||||
}
|
||||
|
||||
} else if (boost::starts_with(sline, ";_OVERHANG_FAN_START")) {
|
||||
line.type = CoolingLine::TYPE_OVERHANG_FAN_START;
|
||||
} else if (boost::starts_with(sline, ";_OVERHANG_FAN_END")) {
|
||||
line.type = CoolingLine::TYPE_OVERHANG_FAN_END;
|
||||
} else if (boost::starts_with(sline, "G4 ")) {
|
||||
// Parse the wait time.
|
||||
line.type = CoolingLine::TYPE_G4;
|
||||
size_t pos_S = sline.find('S', 3);
|
||||
size_t pos_P = sline.find('P', 3);
|
||||
assert(is_decimal_separator_point()); // for atof
|
||||
line.time = line.time_max = float(
|
||||
(pos_S > 0) ? atof(sline.c_str() + pos_S + 1) :
|
||||
(pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.);
|
||||
} else if (boost::starts_with(sline, ";_FORCE_RESUME_FAN_SPEED")) {
|
||||
line.type = CoolingLine::TYPE_FORCE_RESUME_FAN;
|
||||
}
|
||||
if (line.type != 0)
|
||||
adjustment->lines.emplace_back(std::move(line));
|
||||
}
|
||||
|
||||
return per_extruder_adjustments;
|
||||
}
|
||||
|
||||
// Slow down an extruder range proportionally down to slow_down_layer_time.
|
||||
// Return the total time for the complete layer.
|
||||
static inline float extruder_range_slow_down_proportional(
|
||||
std::vector<PerExtruderAdjustments*>::iterator it_begin,
|
||||
std::vector<PerExtruderAdjustments*>::iterator it_end,
|
||||
// Elapsed time for the extruders already processed.
|
||||
float elapsed_time_total0,
|
||||
// Initial total elapsed time before slow down.
|
||||
float elapsed_time_before_slowdown,
|
||||
// Target time for the complete layer (all extruders applied).
|
||||
float slow_down_layer_time)
|
||||
{
|
||||
// Total layer time after the slow down has been applied.
|
||||
float total_after_slowdown = elapsed_time_before_slowdown;
|
||||
// Now decide, whether the external perimeters shall be slowed down as well.
|
||||
float max_time_nep = elapsed_time_total0;
|
||||
for (auto it = it_begin; it != it_end; ++ it)
|
||||
max_time_nep += (*it)->maximum_time_after_slowdown(false);
|
||||
if (max_time_nep > slow_down_layer_time) {
|
||||
// It is sufficient to slow down the non-external perimeter moves to reach the target layer time.
|
||||
// Slow down the non-external perimeters proportionally.
|
||||
float non_adjustable_time = elapsed_time_total0;
|
||||
for (auto it = it_begin; it != it_end; ++ it)
|
||||
non_adjustable_time += (*it)->non_adjustable_time(false);
|
||||
// The following step is a linear programming task due to the minimum movement speeds of the print moves.
|
||||
// Run maximum 5 iterations until a good enough approximation is reached.
|
||||
for (size_t iter = 0; iter < 5; ++ iter) {
|
||||
float factor = (slow_down_layer_time - non_adjustable_time) / (total_after_slowdown - non_adjustable_time);
|
||||
assert(factor > 1.f);
|
||||
total_after_slowdown = elapsed_time_total0;
|
||||
for (auto it = it_begin; it != it_end; ++ it)
|
||||
total_after_slowdown += (*it)->slow_down_proportional(factor, false);
|
||||
if (total_after_slowdown > 0.95f * slow_down_layer_time)
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Slow down everything. First slow down the non-external perimeters to maximum.
|
||||
for (auto it = it_begin; it != it_end; ++ it)
|
||||
(*it)->slowdown_to_minimum_feedrate(false);
|
||||
// Slow down the external perimeters proportionally.
|
||||
float non_adjustable_time = elapsed_time_total0;
|
||||
for (auto it = it_begin; it != it_end; ++ it)
|
||||
non_adjustable_time += (*it)->non_adjustable_time(true);
|
||||
for (size_t iter = 0; iter < 5; ++ iter) {
|
||||
float factor = (slow_down_layer_time - non_adjustable_time) / (total_after_slowdown - non_adjustable_time);
|
||||
assert(factor > 1.f);
|
||||
total_after_slowdown = elapsed_time_total0;
|
||||
for (auto it = it_begin; it != it_end; ++ it)
|
||||
total_after_slowdown += (*it)->slow_down_proportional(factor, true);
|
||||
if (total_after_slowdown > 0.95f * slow_down_layer_time)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return total_after_slowdown;
|
||||
}
|
||||
|
||||
// Slow down an extruder range to slow_down_layer_time.
|
||||
// Return the total time for the complete layer.
|
||||
static inline void extruder_range_slow_down_non_proportional(
|
||||
std::vector<PerExtruderAdjustments*>::iterator it_begin,
|
||||
std::vector<PerExtruderAdjustments*>::iterator it_end,
|
||||
float time_stretch)
|
||||
{
|
||||
// Slow down. Try to equalize the feedrates.
|
||||
std::vector<PerExtruderAdjustments*> by_min_print_speed(it_begin, it_end);
|
||||
// Find the next highest adjustable feedrate among the extruders.
|
||||
float feedrate = 0;
|
||||
for (PerExtruderAdjustments *adj : by_min_print_speed) {
|
||||
adj->idx_line_begin = 0;
|
||||
adj->idx_line_end = 0;
|
||||
assert(adj->idx_line_begin < adj->n_lines_adjustable);
|
||||
if (adj->lines[adj->idx_line_begin].feedrate > feedrate)
|
||||
feedrate = adj->lines[adj->idx_line_begin].feedrate;
|
||||
}
|
||||
assert(feedrate > 0.f);
|
||||
// Sort by slow_down_min_speed, maximum speed first.
|
||||
std::sort(by_min_print_speed.begin(), by_min_print_speed.end(),
|
||||
[](const PerExtruderAdjustments *p1, const PerExtruderAdjustments *p2){ return p1->slow_down_min_speed > p2->slow_down_min_speed; });
|
||||
// Slow down, fast moves first.
|
||||
for (;;) {
|
||||
// For each extruder, find the span of lines with a feedrate close to feedrate.
|
||||
for (PerExtruderAdjustments *adj : by_min_print_speed) {
|
||||
for (adj->idx_line_end = adj->idx_line_begin;
|
||||
adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate - EPSILON;
|
||||
++ adj->idx_line_end) ;
|
||||
}
|
||||
// Find the next highest adjustable feedrate among the extruders.
|
||||
float feedrate_next = 0.f;
|
||||
for (PerExtruderAdjustments *adj : by_min_print_speed)
|
||||
if (adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate_next)
|
||||
feedrate_next = adj->lines[adj->idx_line_end].feedrate;
|
||||
// Slow down, limited by max(feedrate_next, slow_down_min_speed).
|
||||
for (auto adj = by_min_print_speed.begin(); adj != by_min_print_speed.end();) {
|
||||
// Slow down at most by time_stretch.
|
||||
if ((*adj)->slow_down_min_speed == 0.f) {
|
||||
// All the adjustable speeds are now lowered to the same speed,
|
||||
// and the minimum speed is set to zero.
|
||||
float time_adjustable = 0.f;
|
||||
for (auto it = adj; it != by_min_print_speed.end(); ++ it)
|
||||
time_adjustable += (*it)->adjustable_time(true);
|
||||
float rate = (time_adjustable + time_stretch) / time_adjustable;
|
||||
for (auto it = adj; it != by_min_print_speed.end(); ++ it)
|
||||
(*it)->slow_down_proportional(rate, true);
|
||||
return;
|
||||
} else {
|
||||
float feedrate_limit = std::max(feedrate_next, (*adj)->slow_down_min_speed);
|
||||
bool done = false;
|
||||
float time_stretch_max = 0.f;
|
||||
for (auto it = adj; it != by_min_print_speed.end(); ++ it)
|
||||
time_stretch_max += (*it)->time_stretch_when_slowing_down_to_feedrate(feedrate_limit);
|
||||
if (time_stretch_max >= time_stretch) {
|
||||
feedrate_limit = new_feedrate_to_reach_time_stretch(adj, by_min_print_speed.end(), feedrate_limit, time_stretch, 20);
|
||||
done = true;
|
||||
} else
|
||||
time_stretch -= time_stretch_max;
|
||||
for (auto it = adj; it != by_min_print_speed.end(); ++ it)
|
||||
(*it)->slow_down_to_feedrate(feedrate_limit);
|
||||
if (done)
|
||||
return;
|
||||
}
|
||||
// Skip the other extruders with nearly the same slow_down_min_speed, as they have been processed already.
|
||||
auto next = adj;
|
||||
for (++ next; next != by_min_print_speed.end() && (*next)->slow_down_min_speed > (*adj)->slow_down_min_speed - EPSILON; ++ next);
|
||||
adj = next;
|
||||
}
|
||||
if (feedrate_next == 0.f)
|
||||
// There are no other extrusions available for slow down.
|
||||
break;
|
||||
for (PerExtruderAdjustments *adj : by_min_print_speed) {
|
||||
adj->idx_line_begin = adj->idx_line_end;
|
||||
feedrate = feedrate_next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate slow down for all the extruders.
|
||||
float CoolingBuffer::calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments)
|
||||
{
|
||||
// Sort the extruders by an increasing slow_down_layer_time.
|
||||
// The layers with a lower slow_down_layer_time are slowed down
|
||||
// together with all the other layers with slow_down_layer_time above.
|
||||
std::vector<PerExtruderAdjustments*> by_slowdown_time;
|
||||
by_slowdown_time.reserve(per_extruder_adjustments.size());
|
||||
// Only insert entries, which are adjustable (have cooling enabled and non-zero stretchable time).
|
||||
// Collect total print time of non-adjustable extruders.
|
||||
float elapsed_time_total0 = 0.f;
|
||||
for (PerExtruderAdjustments &adj : per_extruder_adjustments) {
|
||||
// Curren total time for this extruder.
|
||||
adj.time_total = adj.elapsed_time_total();
|
||||
// Maximum time for this extruder, when all extrusion moves are slowed down to min_extrusion_speed.
|
||||
adj.time_maximum = adj.maximum_time_after_slowdown(true);
|
||||
if (adj.cooling_slow_down_enabled && adj.lines.size() > 0) {
|
||||
by_slowdown_time.emplace_back(&adj);
|
||||
if (! m_cooling_logic_proportional)
|
||||
// sorts the lines, also sets adj.time_non_adjustable
|
||||
adj.sort_lines_by_decreasing_feedrate();
|
||||
} else
|
||||
elapsed_time_total0 += adj.elapsed_time_total();
|
||||
}
|
||||
std::sort(by_slowdown_time.begin(), by_slowdown_time.end(),
|
||||
[](const PerExtruderAdjustments *adj1, const PerExtruderAdjustments *adj2)
|
||||
{ return adj1->slow_down_layer_time < adj2->slow_down_layer_time; });
|
||||
|
||||
for (auto cur_begin = by_slowdown_time.begin(); cur_begin != by_slowdown_time.end(); ++ cur_begin) {
|
||||
PerExtruderAdjustments &adj = *(*cur_begin);
|
||||
// Calculate the current adjusted elapsed_time_total over the non-finalized extruders.
|
||||
float total = elapsed_time_total0;
|
||||
for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it)
|
||||
total += (*it)->time_total;
|
||||
float slow_down_layer_time = adj.slow_down_layer_time * 1.001f;
|
||||
if (total > slow_down_layer_time) {
|
||||
// The current total time is above the minimum threshold of the rest of the extruders, don't adjust anything.
|
||||
} else {
|
||||
// Adjust this and all the following (higher m_config.slow_down_layer_time) extruders.
|
||||
// Sum maximum slow down time as if everything was slowed down including the external perimeters.
|
||||
float max_time = elapsed_time_total0;
|
||||
for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it)
|
||||
max_time += (*it)->time_maximum;
|
||||
if (max_time > slow_down_layer_time) {
|
||||
if (m_cooling_logic_proportional)
|
||||
extruder_range_slow_down_proportional(cur_begin, by_slowdown_time.end(), elapsed_time_total0, total, slow_down_layer_time);
|
||||
else
|
||||
extruder_range_slow_down_non_proportional(cur_begin, by_slowdown_time.end(), slow_down_layer_time - total);
|
||||
} else {
|
||||
// Slow down to maximum possible.
|
||||
for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it)
|
||||
(*it)->slowdown_to_minimum_feedrate(true);
|
||||
}
|
||||
}
|
||||
elapsed_time_total0 += adj.elapsed_time_total();
|
||||
}
|
||||
|
||||
return elapsed_time_total0;
|
||||
}
|
||||
|
||||
// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed.
|
||||
// Returns the adjusted G-code.
|
||||
std::string CoolingBuffer::apply_layer_cooldown(
|
||||
// Source G-code for the current layer.
|
||||
const std::string &gcode,
|
||||
// ID of the current layer, used to disable fan for the first n layers.
|
||||
size_t layer_id,
|
||||
// Total time of this layer after slow down, used to control the fan.
|
||||
float layer_time,
|
||||
// Per extruder list of G-code lines and their cool down attributes.
|
||||
std::vector<PerExtruderAdjustments> &per_extruder_adjustments)
|
||||
{
|
||||
// First sort the adjustment lines by of multiple extruders by their position in the source G-code.
|
||||
std::vector<const CoolingLine*> lines;
|
||||
{
|
||||
size_t n_lines = 0;
|
||||
for (const PerExtruderAdjustments &adj : per_extruder_adjustments)
|
||||
n_lines += adj.lines.size();
|
||||
lines.reserve(n_lines);
|
||||
for (const PerExtruderAdjustments &adj : per_extruder_adjustments)
|
||||
for (const CoolingLine &line : adj.lines)
|
||||
lines.emplace_back(&line);
|
||||
std::sort(lines.begin(), lines.end(), [](const CoolingLine *ln1, const CoolingLine *ln2) { return ln1->line_start < ln2->line_start; } );
|
||||
}
|
||||
// Second generate the adjusted G-code.
|
||||
std::string new_gcode;
|
||||
new_gcode.reserve(gcode.size() * 2);
|
||||
bool overhang_fan_control= false;
|
||||
int overhang_fan_speed = 0;
|
||||
auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &overhang_fan_control, &overhang_fan_speed]() {
|
||||
#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder)
|
||||
int fan_min_speed = EXTRUDER_CONFIG(fan_min_speed);
|
||||
int fan_speed_new = EXTRUDER_CONFIG(reduce_fan_stop_start_freq) ? fan_min_speed : 0;
|
||||
//BBS
|
||||
int additional_fan_speed_new = EXTRUDER_CONFIG(additional_cooling_fan_speed);
|
||||
int close_fan_the_first_x_layers = EXTRUDER_CONFIG(close_fan_the_first_x_layers);
|
||||
// Is the fan speed ramp enabled?
|
||||
int full_fan_speed_layer = EXTRUDER_CONFIG(full_fan_speed_layer);
|
||||
if (close_fan_the_first_x_layers <= 0 && full_fan_speed_layer > 0) {
|
||||
// When ramping up fan speed from close_fan_the_first_x_layers to full_fan_speed_layer, force close_fan_the_first_x_layers above zero,
|
||||
// so there will be a zero fan speed at least at the 1st layer.
|
||||
close_fan_the_first_x_layers = 1;
|
||||
}
|
||||
if (int(layer_id) >= close_fan_the_first_x_layers) {
|
||||
int fan_max_speed = EXTRUDER_CONFIG(fan_max_speed);
|
||||
float slow_down_layer_time = float(EXTRUDER_CONFIG(slow_down_layer_time));
|
||||
float fan_cooling_layer_time = float(EXTRUDER_CONFIG(fan_cooling_layer_time));
|
||||
//BBS: always enable the fan speed interpolation according to layer time
|
||||
//if (EXTRUDER_CONFIG(cooling)) {
|
||||
if (layer_time < slow_down_layer_time) {
|
||||
// Layer time very short. Enable the fan to a full throttle.
|
||||
fan_speed_new = fan_max_speed;
|
||||
} else if (layer_time < fan_cooling_layer_time) {
|
||||
// Layer time quite short. Enable the fan proportionally according to the current layer time.
|
||||
assert(layer_time >= slow_down_layer_time);
|
||||
double t = (layer_time - slow_down_layer_time) / (fan_cooling_layer_time - slow_down_layer_time);
|
||||
fan_speed_new = int(floor(t * fan_min_speed + (1. - t) * fan_max_speed) + 0.5);
|
||||
}
|
||||
//}
|
||||
overhang_fan_speed = EXTRUDER_CONFIG(overhang_fan_speed);
|
||||
if (int(layer_id) >= close_fan_the_first_x_layers && int(layer_id) + 1 < full_fan_speed_layer) {
|
||||
// Ramp up the fan speed from close_fan_the_first_x_layers to full_fan_speed_layer.
|
||||
float factor = float(int(layer_id + 1) - close_fan_the_first_x_layers) / float(full_fan_speed_layer - close_fan_the_first_x_layers);
|
||||
fan_speed_new = std::clamp(int(float(fan_speed_new) * factor + 0.5f), 0, 255);
|
||||
overhang_fan_speed = std::clamp(int(float(overhang_fan_speed) * factor + 0.5f), 0, 255);
|
||||
}
|
||||
#undef EXTRUDER_CONFIG
|
||||
overhang_fan_control= overhang_fan_speed > fan_speed_new;
|
||||
} else {
|
||||
overhang_fan_control= false;
|
||||
overhang_fan_speed = 0;
|
||||
fan_speed_new = 0;
|
||||
additional_fan_speed_new = 0;
|
||||
}
|
||||
if (fan_speed_new != m_fan_speed) {
|
||||
m_fan_speed = fan_speed_new;
|
||||
//BBS
|
||||
m_current_fan_speed = fan_speed_new;
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed);
|
||||
}
|
||||
//BBS
|
||||
if (additional_fan_speed_new != m_additional_fan_speed) {
|
||||
m_additional_fan_speed = additional_fan_speed_new;
|
||||
new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed);
|
||||
}
|
||||
};
|
||||
|
||||
const char *pos = gcode.c_str();
|
||||
int current_feedrate = 0;
|
||||
change_extruder_set_fan();
|
||||
for (const CoolingLine *line : lines) {
|
||||
const char *line_start = gcode.c_str() + line->line_start;
|
||||
const char *line_end = gcode.c_str() + line->line_end;
|
||||
if (line_start > pos)
|
||||
new_gcode.append(pos, line_start - pos);
|
||||
if (line->type & CoolingLine::TYPE_SET_TOOL) {
|
||||
unsigned int new_extruder = (unsigned int)atoi(line_start + m_toolchange_prefix.size());
|
||||
if (new_extruder != m_current_extruder) {
|
||||
m_current_extruder = new_extruder;
|
||||
change_extruder_set_fan();
|
||||
}
|
||||
new_gcode.append(line_start, line_end - line_start);
|
||||
} else if (line->type & CoolingLine::TYPE_OVERHANG_FAN_START) {
|
||||
if (overhang_fan_control) {
|
||||
//BBS
|
||||
m_current_fan_speed = overhang_fan_speed;
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, overhang_fan_speed);
|
||||
}
|
||||
} else if (line->type & CoolingLine::TYPE_OVERHANG_FAN_END) {
|
||||
if (overhang_fan_control) {
|
||||
//BBS
|
||||
m_current_fan_speed = m_fan_speed;
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed);
|
||||
}
|
||||
} else if (line->type & CoolingLine::TYPE_FORCE_RESUME_FAN) {
|
||||
//BBS: force to write a fan speed command again
|
||||
if (m_current_fan_speed != -1)
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed);
|
||||
}
|
||||
else if (line->type & CoolingLine::TYPE_EXTRUDE_END) {
|
||||
// Just remove this comment.
|
||||
} else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) {
|
||||
// Find the start of a comment, or roll to the end of line.
|
||||
const char *end = line_start;
|
||||
for (; end < line_end && *end != ';'; ++ end);
|
||||
// Find the 'F' word.
|
||||
const char *fpos = strstr(line_start + 2, " F") + 2;
|
||||
int new_feedrate = current_feedrate;
|
||||
// Modify the F word of the current G-code line.
|
||||
bool modify = false;
|
||||
// Remove the F word from the current G-code line.
|
||||
bool remove = false;
|
||||
assert(fpos != nullptr);
|
||||
new_feedrate = line->slowdown ? int(floor(60. * line->feedrate + 0.5)) : atoi(fpos);
|
||||
if (new_feedrate == current_feedrate) {
|
||||
// No need to change the F value.
|
||||
if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.)
|
||||
// Feedrate does not change and this line does not move the print head. Skip the complete G-code line including the G-code comment.
|
||||
end = line_end;
|
||||
else
|
||||
// Remove the feedrate from the G0/G1 line. The G-code line may become empty!
|
||||
remove = true;
|
||||
} else if (line->slowdown) {
|
||||
// The F value will be overwritten.
|
||||
modify = true;
|
||||
} else {
|
||||
// The F value is different from current_feedrate, but not slowed down, thus the G-code line will not be modified.
|
||||
// Emit the line without the comment.
|
||||
new_gcode.append(line_start, end - line_start);
|
||||
current_feedrate = new_feedrate;
|
||||
}
|
||||
if (modify || remove) {
|
||||
if (modify) {
|
||||
// Replace the feedrate.
|
||||
new_gcode.append(line_start, fpos - line_start);
|
||||
current_feedrate = new_feedrate;
|
||||
char buf[64];
|
||||
sprintf(buf, "%d", int(current_feedrate));
|
||||
new_gcode += buf;
|
||||
} else {
|
||||
// Remove the feedrate word.
|
||||
const char *f = fpos;
|
||||
// Roll the pointer before the 'F' word.
|
||||
for (f -= 2; f > line_start && (*f == ' ' || *f == '\t'); -- f);
|
||||
|
||||
if ((f - line_start == 1) && *line_start == 'G' && (*f == '1' || *f == '0')) {
|
||||
// BBS: only remain "G1" or "G0" of this line after remove 'F' part, don't save
|
||||
} else {
|
||||
// Append up to the F word, without the trailing whitespace.
|
||||
new_gcode.append(line_start, f - line_start + 1);
|
||||
}
|
||||
}
|
||||
// Skip the non-whitespaces of the F parameter up the comment or end of line.
|
||||
for (; fpos != end && *fpos != ' ' && *fpos != ';' && *fpos != '\n'; ++ fpos);
|
||||
// Append the rest of the line without the comment.
|
||||
if (fpos < end)
|
||||
// The G-code line is not empty yet. Emit the rest of it.
|
||||
new_gcode.append(fpos, end - fpos);
|
||||
else if (remove && new_gcode == "G1") {
|
||||
// The G-code line only contained the F word, now it is empty. Remove it completely including the comments.
|
||||
new_gcode.resize(new_gcode.size() - 2);
|
||||
end = line_end;
|
||||
}
|
||||
}
|
||||
// Process the rest of the line.
|
||||
if (end < line_end) {
|
||||
if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) {
|
||||
// Process comments, remove ";_EXTRUDE_SET_SPEED", ";_EXTERNAL_PERIMETER", ";_WIPE"
|
||||
std::string comment(end, line_end);
|
||||
boost::replace_all(comment, ";_EXTRUDE_SET_SPEED", "");
|
||||
if (line->type & CoolingLine::TYPE_EXTERNAL_PERIMETER)
|
||||
boost::replace_all(comment, ";_EXTERNAL_PERIMETER", "");
|
||||
if (line->type & CoolingLine::TYPE_WIPE)
|
||||
boost::replace_all(comment, ";_WIPE", "");
|
||||
new_gcode += comment;
|
||||
} else {
|
||||
// Just attach the rest of the source line.
|
||||
new_gcode.append(end, line_end - end);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
new_gcode.append(line_start, line_end - line_start);
|
||||
}
|
||||
pos = line_end;
|
||||
}
|
||||
const char *gcode_end = gcode.c_str() + gcode.size();
|
||||
if (pos < gcode_end)
|
||||
new_gcode.append(pos, gcode_end - pos);
|
||||
|
||||
return new_gcode;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
66
src/libslic3r/GCode/CoolingBuffer.hpp
Normal file
66
src/libslic3r/GCode/CoolingBuffer.hpp
Normal file
|
@ -0,0 +1,66 @@
|
|||
#ifndef slic3r_CoolingBuffer_hpp_
|
||||
#define slic3r_CoolingBuffer_hpp_
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class GCode;
|
||||
class Layer;
|
||||
struct PerExtruderAdjustments;
|
||||
|
||||
// A standalone G-code filter, to control cooling of the print.
|
||||
// The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited
|
||||
// and the print is modified to stretch over a minimum layer time.
|
||||
//
|
||||
// The simple it sounds, the actual implementation is significantly more complex.
|
||||
// Namely, for a multi-extruder print, each material may require a different cooling logic.
|
||||
// For example, some materials may not like to print too slowly, while with some materials
|
||||
// we may slow down significantly.
|
||||
//
|
||||
class CoolingBuffer {
|
||||
public:
|
||||
CoolingBuffer(GCode &gcodegen);
|
||||
void reset(const Vec3d &position);
|
||||
void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; }
|
||||
std::string process_layer(std::string &&gcode, size_t layer_id, bool flush);
|
||||
|
||||
private:
|
||||
CoolingBuffer& operator=(const CoolingBuffer&) = delete;
|
||||
std::vector<PerExtruderAdjustments> parse_layer_gcode(const std::string &gcode, std::vector<float> ¤t_pos) const;
|
||||
float calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
|
||||
// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed.
|
||||
// Returns the adjusted G-code.
|
||||
std::string apply_layer_cooldown(const std::string &gcode, size_t layer_id, float layer_time, std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
|
||||
|
||||
// G-code snippet cached for the support layers preceding an object layer.
|
||||
std::string m_gcode;
|
||||
// Internal data.
|
||||
// BBS: X,Y,Z,E,F,I,J
|
||||
std::vector<char> m_axis;
|
||||
std::vector<float> m_current_pos;
|
||||
// Current known fan speed or -1 if not known yet.
|
||||
int m_fan_speed;
|
||||
int m_additional_fan_speed;
|
||||
// Cached from GCodeWriter.
|
||||
// Printing extruder IDs, zero based.
|
||||
std::vector<unsigned int> m_extruder_ids;
|
||||
// Highest of m_extruder_ids plus 1.
|
||||
unsigned int m_num_extruders { 0 };
|
||||
const std::string m_toolchange_prefix;
|
||||
// Referencs GCode::m_config, which is FullPrintConfig. While the PrintObjectConfig slice of FullPrintConfig is being modified,
|
||||
// the PrintConfig slice of FullPrintConfig is constant, thus no thread synchronization is required.
|
||||
const PrintConfig &m_config;
|
||||
unsigned int m_current_extruder;
|
||||
|
||||
// Old logic: proportional.
|
||||
bool m_cooling_logic_proportional = false;
|
||||
//BBS: current fan speed
|
||||
int m_current_fan_speed;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
3906
src/libslic3r/GCode/GCodeProcessor.cpp
Normal file
3906
src/libslic3r/GCode/GCodeProcessor.cpp
Normal file
File diff suppressed because it is too large
Load diff
827
src/libslic3r/GCode/GCodeProcessor.hpp
Normal file
827
src/libslic3r/GCode/GCodeProcessor.hpp
Normal file
|
@ -0,0 +1,827 @@
|
|||
#ifndef slic3r_GCodeProcessor_hpp_
|
||||
#define slic3r_GCodeProcessor_hpp_
|
||||
|
||||
#include "libslic3r/GCodeReader.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/ExtrusionEntity.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/CustomGCode.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <optional>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum class EMoveType : unsigned char
|
||||
{
|
||||
Noop,
|
||||
Retract,
|
||||
Unretract,
|
||||
Seam,
|
||||
Tool_change,
|
||||
Color_change,
|
||||
Pause_Print,
|
||||
Custom_GCode,
|
||||
Travel,
|
||||
Wipe,
|
||||
Extrude,
|
||||
Count
|
||||
};
|
||||
|
||||
struct PrintEstimatedStatistics
|
||||
{
|
||||
enum class ETimeMode : unsigned char
|
||||
{
|
||||
Normal,
|
||||
Stealth,
|
||||
Count
|
||||
};
|
||||
|
||||
struct Mode
|
||||
{
|
||||
float time;
|
||||
std::vector<std::pair<CustomGCode::Type, std::pair<float, float>>> custom_gcode_times;
|
||||
std::vector<std::pair<EMoveType, float>> moves_times;
|
||||
std::vector<std::pair<ExtrusionRole, float>> roles_times;
|
||||
std::vector<float> layers_times;
|
||||
|
||||
void reset() {
|
||||
time = 0.0f;
|
||||
custom_gcode_times.clear();
|
||||
moves_times.clear();
|
||||
roles_times.clear();
|
||||
layers_times.clear();
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<double> volumes_per_color_change;
|
||||
std::map<size_t, double> volumes_per_extruder;
|
||||
//BBS: the flush amount of every filament
|
||||
std::map<size_t, double> flush_per_filament;
|
||||
std::map<ExtrusionRole, std::pair<double, double>> used_filaments_per_role;
|
||||
|
||||
std::array<Mode, static_cast<size_t>(ETimeMode::Count)> modes;
|
||||
unsigned int total_filamentchanges;
|
||||
|
||||
PrintEstimatedStatistics() { reset(); }
|
||||
|
||||
void reset() {
|
||||
for (auto m : modes) {
|
||||
m.reset();
|
||||
}
|
||||
volumes_per_color_change.clear();
|
||||
volumes_per_extruder.clear();
|
||||
flush_per_filament.clear();
|
||||
used_filaments_per_role.clear();
|
||||
total_filamentchanges = 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct GCodeProcessorResult
|
||||
{
|
||||
struct SettingsIds
|
||||
{
|
||||
std::string print;
|
||||
std::vector<std::string> filament;
|
||||
std::string printer;
|
||||
|
||||
void reset() {
|
||||
print.clear();
|
||||
filament.clear();
|
||||
printer.clear();
|
||||
}
|
||||
};
|
||||
|
||||
struct MoveVertex
|
||||
{
|
||||
unsigned int gcode_id{ 0 };
|
||||
EMoveType type{ EMoveType::Noop };
|
||||
ExtrusionRole extrusion_role{ erNone };
|
||||
unsigned char extruder_id{ 0 };
|
||||
unsigned char cp_color_id{ 0 };
|
||||
Vec3f position{ Vec3f::Zero() }; // mm
|
||||
float delta_extruder{ 0.0f }; // mm
|
||||
float feedrate{ 0.0f }; // mm/s
|
||||
float width{ 0.0f }; // mm
|
||||
float height{ 0.0f }; // mm
|
||||
float mm3_per_mm{ 0.0f };
|
||||
float fan_speed{ 0.0f }; // percentage
|
||||
float temperature{ 0.0f }; // Celsius degrees
|
||||
float time{ 0.0f }; // s
|
||||
|
||||
//BBS: arc move related data
|
||||
EMovePathType move_path_type{ EMovePathType::Noop_move };
|
||||
Vec3f arc_center_position{ Vec3f::Zero() }; // mm
|
||||
std::vector<Vec3f> interpolation_points; // interpolation points of arc for drawing
|
||||
|
||||
float volumetric_rate() const { return feedrate * mm3_per_mm; }
|
||||
//BBS: new function to support arc move
|
||||
bool is_arc_move_with_interpolation_points() const {
|
||||
return (move_path_type == EMovePathType::Arc_move_ccw || move_path_type == EMovePathType::Arc_move_cw) && interpolation_points.size();
|
||||
}
|
||||
bool is_arc_move() const {
|
||||
return move_path_type == EMovePathType::Arc_move_ccw || move_path_type == EMovePathType::Arc_move_cw;
|
||||
}
|
||||
};
|
||||
|
||||
std::string filename;
|
||||
unsigned int id;
|
||||
std::vector<MoveVertex> moves;
|
||||
// Positions of ends of lines of the final G-code this->filename after TimeProcessor::post_process() finalizes the G-code.
|
||||
std::vector<size_t> lines_ends;
|
||||
Pointfs printable_area;
|
||||
//BBS: add bed exclude area
|
||||
Pointfs bed_exclude_area;
|
||||
//BBS: add toolpath_outside
|
||||
bool toolpath_outside;
|
||||
float printable_height;
|
||||
SettingsIds settings_ids;
|
||||
size_t extruders_count;
|
||||
std::vector<std::string> extruder_colors;
|
||||
std::vector<float> filament_diameters;
|
||||
std::vector<float> filament_densities;
|
||||
PrintEstimatedStatistics print_statistics;
|
||||
std::vector<CustomGCode::Item> custom_gcode_per_print_z;
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
int64_t time{ 0 };
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
void reset();
|
||||
|
||||
//BBS: add mutex for protection of gcode result
|
||||
mutable std::mutex result_mutex;
|
||||
GCodeProcessorResult& operator=(const GCodeProcessorResult &other)
|
||||
{
|
||||
filename = other.filename;
|
||||
id = other.id;
|
||||
moves = other.moves;
|
||||
lines_ends = other.lines_ends;
|
||||
printable_area = other.printable_area;
|
||||
bed_exclude_area = other.bed_exclude_area;
|
||||
toolpath_outside = other.toolpath_outside;
|
||||
printable_height = other.printable_height;
|
||||
settings_ids = other.settings_ids;
|
||||
extruders_count = other.extruders_count;
|
||||
extruder_colors = other.extruder_colors;
|
||||
filament_diameters = other.filament_diameters;
|
||||
filament_densities = other.filament_densities;
|
||||
print_statistics = other.print_statistics;
|
||||
custom_gcode_per_print_z = other.custom_gcode_per_print_z;
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
time = other.time;
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
void lock() const { result_mutex.lock(); }
|
||||
void unlock() const { result_mutex.unlock(); }
|
||||
};
|
||||
|
||||
|
||||
class GCodeProcessor
|
||||
{
|
||||
static const std::vector<std::string> Reserved_Tags;
|
||||
static const std::string Flush_Start_Tag;
|
||||
static const std::string Flush_End_Tag;
|
||||
|
||||
public:
|
||||
enum class ETags : unsigned char
|
||||
{
|
||||
Role,
|
||||
Wipe_Start,
|
||||
Wipe_End,
|
||||
Height,
|
||||
Width,
|
||||
Layer_Change,
|
||||
Color_Change,
|
||||
Pause_Print,
|
||||
Custom_Code,
|
||||
First_Line_M73_Placeholder,
|
||||
Last_Line_M73_Placeholder,
|
||||
Estimated_Printing_Time_Placeholder
|
||||
};
|
||||
|
||||
static const std::string& reserved_tag(ETags tag) { return Reserved_Tags[static_cast<unsigned char>(tag)]; }
|
||||
// checks the given gcode for reserved tags and returns true when finding the 1st (which is returned into found_tag)
|
||||
static bool contains_reserved_tag(const std::string& gcode, std::string& found_tag);
|
||||
// checks the given gcode for reserved tags and returns true when finding any
|
||||
// (the first max_count found tags are returned into found_tag)
|
||||
static bool contains_reserved_tags(const std::string& gcode, unsigned int max_count, std::vector<std::string>& found_tag);
|
||||
|
||||
static const float Wipe_Width;
|
||||
static const float Wipe_Height;
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
static const std::string Mm3_Per_Mm_Tag;
|
||||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
|
||||
private:
|
||||
using AxisCoords = std::array<float, 4>;
|
||||
using ExtruderColors = std::vector<unsigned char>;
|
||||
using ExtruderTemps = std::vector<float>;
|
||||
|
||||
enum class EUnits : unsigned char
|
||||
{
|
||||
Millimeters,
|
||||
Inches
|
||||
};
|
||||
|
||||
enum class EPositioningType : unsigned char
|
||||
{
|
||||
Absolute,
|
||||
Relative
|
||||
};
|
||||
|
||||
struct CachedPosition
|
||||
{
|
||||
AxisCoords position; // mm
|
||||
float feedrate; // mm/s
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
struct CpColor
|
||||
{
|
||||
unsigned char counter;
|
||||
unsigned char current;
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
public:
|
||||
struct FeedrateProfile
|
||||
{
|
||||
float entry{ 0.0f }; // mm/s
|
||||
float cruise{ 0.0f }; // mm/s
|
||||
float exit{ 0.0f }; // mm/s
|
||||
};
|
||||
|
||||
struct Trapezoid
|
||||
{
|
||||
float accelerate_until{ 0.0f }; // mm
|
||||
float decelerate_after{ 0.0f }; // mm
|
||||
float cruise_feedrate{ 0.0f }; // mm/sec
|
||||
|
||||
float acceleration_time(float entry_feedrate, float acceleration) const;
|
||||
float cruise_time() const;
|
||||
float deceleration_time(float distance, float acceleration) const;
|
||||
float cruise_distance() const;
|
||||
};
|
||||
|
||||
struct TimeBlock
|
||||
{
|
||||
struct Flags
|
||||
{
|
||||
bool recalculate{ false };
|
||||
bool nominal_length{ false };
|
||||
};
|
||||
|
||||
EMoveType move_type{ EMoveType::Noop };
|
||||
ExtrusionRole role{ erNone };
|
||||
unsigned int g1_line_id{ 0 };
|
||||
unsigned int layer_id{ 0 };
|
||||
float distance{ 0.0f }; // mm
|
||||
float acceleration{ 0.0f }; // mm/s^2
|
||||
float max_entry_speed{ 0.0f }; // mm/s
|
||||
float safe_feedrate{ 0.0f }; // mm/s
|
||||
Flags flags;
|
||||
FeedrateProfile feedrate_profile;
|
||||
Trapezoid trapezoid;
|
||||
|
||||
// Calculates this block's trapezoid
|
||||
void calculate_trapezoid();
|
||||
|
||||
float time() const;
|
||||
};
|
||||
|
||||
private:
|
||||
struct TimeMachine
|
||||
{
|
||||
struct State
|
||||
{
|
||||
float feedrate; // mm/s
|
||||
float safe_feedrate; // mm/s
|
||||
//BBS: feedrate of X-Y-Z-E axis. But when the move is G2 and G3, X-Y will be
|
||||
//same value which means feedrate in X-Y plane.
|
||||
AxisCoords axis_feedrate; // mm/s
|
||||
AxisCoords abs_axis_feedrate; // mm/s
|
||||
|
||||
//BBS: unit vector of enter speed and exit speed in x-y-z space.
|
||||
//For line move, there are same. For arc move, there are different.
|
||||
Vec3f enter_direction;
|
||||
Vec3f exit_direction;
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
struct CustomGCodeTime
|
||||
{
|
||||
bool needed;
|
||||
float cache;
|
||||
std::vector<std::pair<CustomGCode::Type, float>> times;
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
struct G1LinesCacheItem
|
||||
{
|
||||
unsigned int id;
|
||||
float elapsed_time;
|
||||
};
|
||||
|
||||
bool enabled;
|
||||
float acceleration; // mm/s^2
|
||||
// hard limit for the acceleration, to which the firmware will clamp.
|
||||
float max_acceleration; // mm/s^2
|
||||
float retract_acceleration; // mm/s^2
|
||||
// hard limit for the acceleration, to which the firmware will clamp.
|
||||
float max_retract_acceleration; // mm/s^2
|
||||
float travel_acceleration; // mm/s^2
|
||||
// hard limit for the travel acceleration, to which the firmware will clamp.
|
||||
float max_travel_acceleration; // mm/s^2
|
||||
float extrude_factor_override_percentage;
|
||||
float time; // s
|
||||
struct StopTime
|
||||
{
|
||||
unsigned int g1_line_id;
|
||||
float elapsed_time;
|
||||
};
|
||||
std::vector<StopTime> stop_times;
|
||||
std::string line_m73_main_mask;
|
||||
std::string line_m73_stop_mask;
|
||||
State curr;
|
||||
State prev;
|
||||
CustomGCodeTime gcode_time;
|
||||
std::vector<TimeBlock> blocks;
|
||||
std::vector<G1LinesCacheItem> g1_times_cache;
|
||||
std::array<float, static_cast<size_t>(EMoveType::Count)> moves_time;
|
||||
std::array<float, static_cast<size_t>(ExtrusionRole::erCount)> roles_time;
|
||||
std::vector<float> layers_time;
|
||||
|
||||
void reset();
|
||||
|
||||
// Simulates firmware st_synchronize() call
|
||||
void simulate_st_synchronize(float additional_time = 0.0f);
|
||||
void calculate_time(size_t keep_last_n_blocks = 0, float additional_time = 0.0f);
|
||||
};
|
||||
|
||||
struct TimeProcessor
|
||||
{
|
||||
struct Planner
|
||||
{
|
||||
// Size of the firmware planner queue. The old 8-bit Marlins usually just managed 16 trapezoidal blocks.
|
||||
// Let's be conservative and plan for newer boards with more memory.
|
||||
static constexpr size_t queue_size = 64;
|
||||
// The firmware recalculates last planner_queue_size trapezoidal blocks each time a new block is added.
|
||||
// We are not simulating the firmware exactly, we calculate a sequence of blocks once a reasonable number of blocks accumulate.
|
||||
static constexpr size_t refresh_threshold = queue_size * 4;
|
||||
};
|
||||
|
||||
// extruder_id is currently used to correctly calculate filament load / unload times into the total print time.
|
||||
// This is currently only really used by the MK3 MMU2:
|
||||
// extruder_unloaded = true means no filament is loaded yet, all the filaments are parked in the MK3 MMU2 unit.
|
||||
bool extruder_unloaded;
|
||||
// allow to skip the lines M201/M203/M204/M205 generated by GCode::print_machine_envelope() for non-Normal time estimate mode
|
||||
bool machine_envelope_processing_enabled;
|
||||
MachineEnvelopeConfig machine_limits;
|
||||
// Additional load / unload times for a filament exchange sequence.
|
||||
float filament_load_times;
|
||||
float filament_unload_times;
|
||||
std::array<TimeMachine, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)> machines;
|
||||
|
||||
void reset();
|
||||
|
||||
// post process the file with the given filename to add remaining time lines M73
|
||||
// and updates moves' gcode ids accordingly
|
||||
void post_process(const std::string& filename, std::vector<GCodeProcessorResult::MoveVertex>& moves, std::vector<size_t>& lines_ends);
|
||||
};
|
||||
|
||||
struct UsedFilaments // filaments per ColorChange
|
||||
{
|
||||
double color_change_cache;
|
||||
std::vector<double> volumes_per_color_change;
|
||||
|
||||
double tool_change_cache;
|
||||
std::map<size_t, double> volumes_per_extruder;
|
||||
|
||||
//BBS: the flush amount of every filament
|
||||
std::map<size_t, double> flush_per_filament;
|
||||
|
||||
double role_cache;
|
||||
std::map<ExtrusionRole, std::pair<double, double>> filaments_per_role;
|
||||
|
||||
void reset();
|
||||
|
||||
void increase_caches(double extruded_volume);
|
||||
|
||||
void process_color_change_cache();
|
||||
void process_extruder_cache(GCodeProcessor* processor);
|
||||
void update_flush_per_filament(size_t extrude_id, float flush_length);
|
||||
void process_role_cache(GCodeProcessor* processor);
|
||||
void process_caches(GCodeProcessor* processor);
|
||||
|
||||
friend class GCodeProcessor;
|
||||
};
|
||||
|
||||
public:
|
||||
class SeamsDetector
|
||||
{
|
||||
bool m_active{ false };
|
||||
std::optional<Vec3f> m_first_vertex;
|
||||
|
||||
public:
|
||||
void activate(bool active) {
|
||||
if (m_active != active) {
|
||||
m_active = active;
|
||||
if (m_active)
|
||||
m_first_vertex.reset();
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<Vec3f> get_first_vertex() const { return m_first_vertex; }
|
||||
void set_first_vertex(const Vec3f& vertex) { m_first_vertex = vertex; }
|
||||
|
||||
bool is_active() const { return m_active; }
|
||||
bool has_first_vertex() const { return m_first_vertex.has_value(); }
|
||||
};
|
||||
|
||||
// Helper class used to fix the z for color change, pause print and
|
||||
// custom gcode markes
|
||||
class OptionsZCorrector
|
||||
{
|
||||
GCodeProcessorResult& m_result;
|
||||
std::optional<size_t> m_move_id;
|
||||
std::optional<size_t> m_custom_gcode_per_print_z_id;
|
||||
|
||||
public:
|
||||
explicit OptionsZCorrector(GCodeProcessorResult& result) : m_result(result) {
|
||||
}
|
||||
|
||||
void set() {
|
||||
m_move_id = m_result.moves.size() - 1;
|
||||
m_custom_gcode_per_print_z_id = m_result.custom_gcode_per_print_z.size() - 1;
|
||||
}
|
||||
|
||||
void update(float height) {
|
||||
if (!m_move_id.has_value() || !m_custom_gcode_per_print_z_id.has_value())
|
||||
return;
|
||||
|
||||
const Vec3f position = m_result.moves.back().position;
|
||||
|
||||
GCodeProcessorResult::MoveVertex& move = m_result.moves.emplace_back(m_result.moves[*m_move_id]);
|
||||
move.position = position;
|
||||
move.height = height;
|
||||
m_result.moves.erase(m_result.moves.begin() + *m_move_id);
|
||||
m_result.custom_gcode_per_print_z[*m_custom_gcode_per_print_z_id].print_z = position.z();
|
||||
reset();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
m_move_id.reset();
|
||||
m_custom_gcode_per_print_z_id.reset();
|
||||
}
|
||||
};
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
struct DataChecker
|
||||
{
|
||||
struct Error
|
||||
{
|
||||
float value;
|
||||
float tag_value;
|
||||
ExtrusionRole role;
|
||||
};
|
||||
|
||||
std::string type;
|
||||
float threshold{ 0.01f };
|
||||
float last_tag_value{ 0.0f };
|
||||
unsigned int count{ 0 };
|
||||
std::vector<Error> errors;
|
||||
|
||||
DataChecker(const std::string& type, float threshold)
|
||||
: type(type), threshold(threshold)
|
||||
{}
|
||||
|
||||
void update(float value, ExtrusionRole role) {
|
||||
if (role != erCustom) {
|
||||
++count;
|
||||
if (last_tag_value != 0.0f) {
|
||||
if (std::abs(value - last_tag_value) / last_tag_value > threshold)
|
||||
errors.push_back({ value, last_tag_value, role });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void reset() { last_tag_value = 0.0f; errors.clear(); count = 0; }
|
||||
|
||||
std::pair<float, float> get_min() const {
|
||||
float delta_min = FLT_MAX;
|
||||
float perc_min = 0.0f;
|
||||
for (const Error& e : errors) {
|
||||
if (delta_min > e.value - e.tag_value) {
|
||||
delta_min = e.value - e.tag_value;
|
||||
perc_min = 100.0f * delta_min / e.tag_value;
|
||||
}
|
||||
}
|
||||
return { delta_min, perc_min };
|
||||
}
|
||||
|
||||
std::pair<float, float> get_max() const {
|
||||
float delta_max = -FLT_MAX;
|
||||
float perc_max = 0.0f;
|
||||
for (const Error& e : errors) {
|
||||
if (delta_max < e.value - e.tag_value) {
|
||||
delta_max = e.value - e.tag_value;
|
||||
perc_max = 100.0f * delta_max / e.tag_value;
|
||||
}
|
||||
}
|
||||
return { delta_max, perc_max };
|
||||
}
|
||||
|
||||
void output() const {
|
||||
if (!errors.empty()) {
|
||||
std::cout << type << ":\n";
|
||||
std::cout << "Errors: " << errors.size() << " (" << 100.0f * float(errors.size()) / float(count) << "%)\n";
|
||||
auto [min, perc_min] = get_min();
|
||||
auto [max, perc_max] = get_max();
|
||||
std::cout << "min: " << min << "(" << perc_min << "%) - max: " << max << "(" << perc_max << "%)\n";
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
|
||||
private:
|
||||
GCodeReader m_parser;
|
||||
|
||||
EUnits m_units;
|
||||
EPositioningType m_global_positioning_type;
|
||||
EPositioningType m_e_local_positioning_type;
|
||||
std::vector<Vec3f> m_extruder_offsets;
|
||||
GCodeFlavor m_flavor;
|
||||
float m_nozzle_volume;
|
||||
|
||||
AxisCoords m_start_position; // mm
|
||||
AxisCoords m_end_position; // mm
|
||||
AxisCoords m_origin; // mm
|
||||
CachedPosition m_cached_position;
|
||||
bool m_wiping;
|
||||
bool m_flushing;
|
||||
float m_remaining_volume;
|
||||
|
||||
//BBS: x, y offset for gcode generated
|
||||
double m_x_offset{ 0 };
|
||||
double m_y_offset{ 0 };
|
||||
//BBS: arc move related data
|
||||
EMovePathType m_move_path_type{ EMovePathType::Noop_move };
|
||||
Vec3f m_arc_center{ Vec3f::Zero() }; // mm
|
||||
std::vector<Vec3f> m_interpolation_points;
|
||||
|
||||
unsigned int m_line_id;
|
||||
unsigned int m_last_line_id;
|
||||
float m_feedrate; // mm/s
|
||||
float m_width; // mm
|
||||
float m_height; // mm
|
||||
float m_forced_width; // mm
|
||||
float m_forced_height; // mm
|
||||
float m_mm3_per_mm;
|
||||
float m_fan_speed; // percentage
|
||||
ExtrusionRole m_extrusion_role;
|
||||
unsigned char m_extruder_id;
|
||||
unsigned char m_last_extruder_id;
|
||||
ExtruderColors m_extruder_colors;
|
||||
ExtruderTemps m_extruder_temps;
|
||||
float m_extruded_last_z;
|
||||
float m_first_layer_height; // mm
|
||||
bool m_processing_start_custom_gcode;
|
||||
unsigned int m_g1_line_id;
|
||||
unsigned int m_layer_id;
|
||||
CpColor m_cp_color;
|
||||
SeamsDetector m_seams_detector;
|
||||
OptionsZCorrector m_options_z_corrector;
|
||||
size_t m_last_default_color_id;
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> m_start_time;
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
|
||||
enum class EProducer
|
||||
{
|
||||
Unknown,
|
||||
BambuStudio,
|
||||
Slic3rPE,
|
||||
Slic3r,
|
||||
SuperSlicer,
|
||||
Cura,
|
||||
Simplify3D,
|
||||
CraftWare,
|
||||
ideaMaker,
|
||||
KissSlicer
|
||||
};
|
||||
|
||||
static const std::vector<std::pair<GCodeProcessor::EProducer, std::string>> Producers;
|
||||
EProducer m_producer;
|
||||
|
||||
TimeProcessor m_time_processor;
|
||||
UsedFilaments m_used_filaments;
|
||||
|
||||
GCodeProcessorResult m_result;
|
||||
static unsigned int s_result_id;
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
DataChecker m_mm3_per_mm_compare{ "mm3_per_mm", 0.01f };
|
||||
DataChecker m_height_compare{ "height", 0.01f };
|
||||
DataChecker m_width_compare{ "width", 0.01f };
|
||||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
|
||||
public:
|
||||
GCodeProcessor();
|
||||
|
||||
void apply_config(const PrintConfig& config);
|
||||
void enable_stealth_time_estimator(bool enabled);
|
||||
bool is_stealth_time_estimator_enabled() const {
|
||||
return m_time_processor.machines[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled;
|
||||
}
|
||||
void enable_machine_envelope_processing(bool enabled) { m_time_processor.machine_envelope_processing_enabled = enabled; }
|
||||
void reset();
|
||||
|
||||
const GCodeProcessorResult& get_result() const { return m_result; }
|
||||
GCodeProcessorResult&& extract_result() { return std::move(m_result); }
|
||||
|
||||
// Load a G-code into a stand-alone G-code viewer.
|
||||
// throws CanceledException through print->throw_if_canceled() (sent by the caller as callback).
|
||||
void process_file(const std::string& filename, std::function<void()> cancel_callback = nullptr);
|
||||
|
||||
// Streaming interface, for processing G-codes just generated by PrusaSlicer in a pipelined fashion.
|
||||
void initialize(const std::string& filename);
|
||||
void process_buffer(const std::string& buffer);
|
||||
void finalize(bool post_process);
|
||||
|
||||
float get_time(PrintEstimatedStatistics::ETimeMode mode) const;
|
||||
std::string get_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const;
|
||||
std::vector<std::pair<CustomGCode::Type, std::pair<float, float>>> get_custom_gcode_times(PrintEstimatedStatistics::ETimeMode mode, bool include_remaining) const;
|
||||
|
||||
std::vector<std::pair<EMoveType, float>> get_moves_time(PrintEstimatedStatistics::ETimeMode mode) const;
|
||||
std::vector<std::pair<ExtrusionRole, float>> get_roles_time(PrintEstimatedStatistics::ETimeMode mode) const;
|
||||
std::vector<float> get_layers_time(PrintEstimatedStatistics::ETimeMode mode) const;
|
||||
|
||||
//BBS: set offset for gcode writer
|
||||
void set_xy_offset(double x, double y) { m_x_offset = x; m_y_offset = y; }
|
||||
|
||||
private:
|
||||
void apply_config(const DynamicPrintConfig& config);
|
||||
void apply_config_simplify3d(const std::string& filename);
|
||||
void apply_config_superslicer(const std::string& filename);
|
||||
void process_gcode_line(const GCodeReader::GCodeLine& line, bool producers_enabled);
|
||||
|
||||
// Process tags embedded into comments
|
||||
void process_tags(const std::string_view comment, bool producers_enabled);
|
||||
bool process_producers_tags(const std::string_view comment);
|
||||
bool process_bambuslicer_tags(const std::string_view comment);
|
||||
bool process_cura_tags(const std::string_view comment);
|
||||
bool process_simplify3d_tags(const std::string_view comment);
|
||||
bool process_craftware_tags(const std::string_view comment);
|
||||
bool process_ideamaker_tags(const std::string_view comment);
|
||||
bool process_kissslicer_tags(const std::string_view comment);
|
||||
|
||||
bool detect_producer(const std::string_view comment);
|
||||
|
||||
// Move
|
||||
void process_G0(const GCodeReader::GCodeLine& line);
|
||||
void process_G1(const GCodeReader::GCodeLine& line);
|
||||
void process_G2_G3(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// BBS: handle delay command
|
||||
void process_G4(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Retract
|
||||
void process_G10(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Unretract
|
||||
void process_G11(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set Units to Inches
|
||||
void process_G20(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set Units to Millimeters
|
||||
void process_G21(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Firmware controlled Retract
|
||||
void process_G22(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Firmware controlled Unretract
|
||||
void process_G23(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Move to origin
|
||||
void process_G28(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// BBS
|
||||
void process_G29(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set to Absolute Positioning
|
||||
void process_G90(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set to Relative Positioning
|
||||
void process_G91(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set Position
|
||||
void process_G92(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Sleep or Conditional stop
|
||||
void process_M1(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set extruder to absolute mode
|
||||
void process_M82(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set extruder to relative mode
|
||||
void process_M83(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set extruder temperature
|
||||
void process_M104(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set fan speed
|
||||
void process_M106(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Disable fan
|
||||
void process_M107(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set tool (Sailfish)
|
||||
void process_M108(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set extruder temperature and wait
|
||||
void process_M109(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Recall stored home offsets
|
||||
void process_M132(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set tool (MakerWare)
|
||||
void process_M135(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set max printing acceleration
|
||||
void process_M201(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set maximum feedrate
|
||||
void process_M203(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set default acceleration
|
||||
void process_M204(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Advanced settings
|
||||
void process_M205(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set extrude factor override percentage
|
||||
void process_M221(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// BBS: handle delay command. M400 is defined by BBL only
|
||||
void process_M400(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Repetier: Store x, y and z position
|
||||
void process_M401(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Repetier: Go to stored position
|
||||
void process_M402(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set allowable instantaneous speed change
|
||||
void process_M566(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Unload the current filament into the MK3 MMU2 unit at the end of print.
|
||||
void process_M702(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Processes T line (Select Tool)
|
||||
void process_T(const GCodeReader::GCodeLine& line);
|
||||
void process_T(const std::string_view command);
|
||||
|
||||
//BBS: different path_type is only used for arc move
|
||||
void store_move_vertex(EMoveType type, EMovePathType path_type = EMovePathType::Noop_move);
|
||||
|
||||
void set_extrusion_role(ExtrusionRole role);
|
||||
|
||||
float minimum_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const;
|
||||
float minimum_travel_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const;
|
||||
float get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
float get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
float get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
float get_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode) const;
|
||||
void set_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value);
|
||||
float get_acceleration(PrintEstimatedStatistics::ETimeMode mode) const;
|
||||
void set_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value);
|
||||
float get_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode) const;
|
||||
void set_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value);
|
||||
float get_filament_load_time(size_t extruder_id);
|
||||
float get_filament_unload_time(size_t extruder_id);
|
||||
|
||||
void process_custom_gcode_time(CustomGCode::Type code);
|
||||
void process_filaments(CustomGCode::Type code);
|
||||
|
||||
// Simulates firmware st_synchronize() call
|
||||
void simulate_st_synchronize(float additional_time = 0.0f);
|
||||
|
||||
void update_estimated_times_stats();
|
||||
};
|
||||
|
||||
} /* namespace Slic3r */
|
||||
|
||||
#endif /* slic3r_GCodeProcessor_hpp_ */
|
||||
|
||||
|
379
src/libslic3r/GCode/PostProcessor.cpp
Normal file
379
src/libslic3r/GCode/PostProcessor.cpp
Normal file
|
@ -0,0 +1,379 @@
|
|||
#include "PostProcessor.hpp"
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/format.hpp"
|
||||
#include "libslic3r/I18N.hpp"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
#include <boost/nowide/cenv.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
|
||||
// BBS
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
// The standard Windows includes.
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <Windows.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
// https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
|
||||
// This routine appends the given argument to a command line such that CommandLineToArgvW will return the argument string unchanged.
|
||||
// Arguments in a command line should be separated by spaces; this function does not add these spaces.
|
||||
// Argument - Supplies the argument to encode.
|
||||
// CommandLine - Supplies the command line to which we append the encoded argument string.
|
||||
static void quote_argv_winapi(const std::wstring &argument, std::wstring &commmand_line_out)
|
||||
{
|
||||
// Don't quote unless we actually need to do so --- hopefully avoid problems if programs won't parse quotes properly.
|
||||
if (argument.empty() == false && argument.find_first_of(L" \t\n\v\"") == argument.npos)
|
||||
commmand_line_out.append(argument);
|
||||
else {
|
||||
commmand_line_out.push_back(L'"');
|
||||
for (auto it = argument.begin(); ; ++ it) {
|
||||
unsigned number_backslashes = 0;
|
||||
while (it != argument.end() && *it == L'\\') {
|
||||
++ it;
|
||||
++ number_backslashes;
|
||||
}
|
||||
if (it == argument.end()) {
|
||||
// Escape all backslashes, but let the terminating double quotation mark we add below be interpreted as a metacharacter.
|
||||
commmand_line_out.append(number_backslashes * 2, L'\\');
|
||||
break;
|
||||
} else if (*it == L'"') {
|
||||
// Escape all backslashes and the following double quotation mark.
|
||||
commmand_line_out.append(number_backslashes * 2 + 1, L'\\');
|
||||
commmand_line_out.push_back(*it);
|
||||
} else {
|
||||
// Backslashes aren't special here.
|
||||
commmand_line_out.append(number_backslashes, L'\\');
|
||||
commmand_line_out.push_back(*it);
|
||||
}
|
||||
}
|
||||
commmand_line_out.push_back(L'"');
|
||||
}
|
||||
}
|
||||
|
||||
static DWORD execute_process_winapi(const std::wstring &command_line)
|
||||
{
|
||||
// Extract the current environment to be passed to the child process.
|
||||
std::wstring envstr;
|
||||
{
|
||||
wchar_t *env = GetEnvironmentStrings();
|
||||
assert(env != nullptr);
|
||||
const wchar_t* var = env;
|
||||
size_t totallen = 0;
|
||||
size_t len;
|
||||
while ((len = wcslen(var)) > 0) {
|
||||
totallen += len + 1;
|
||||
var += len + 1;
|
||||
}
|
||||
envstr = std::wstring(env, totallen);
|
||||
FreeEnvironmentStrings(env);
|
||||
}
|
||||
|
||||
STARTUPINFOW startup_info;
|
||||
memset(&startup_info, 0, sizeof(startup_info));
|
||||
startup_info.cb = sizeof(STARTUPINFO);
|
||||
#if 0
|
||||
startup_info.dwFlags = STARTF_USESHOWWINDOW;
|
||||
startup_info.wShowWindow = SW_HIDE;
|
||||
#endif
|
||||
PROCESS_INFORMATION process_info;
|
||||
if (! ::CreateProcessW(
|
||||
nullptr /* lpApplicationName */, (LPWSTR)command_line.c_str(), nullptr /* lpProcessAttributes */, nullptr /* lpThreadAttributes */, false /* bInheritHandles */,
|
||||
CREATE_UNICODE_ENVIRONMENT /* | CREATE_NEW_CONSOLE */ /* dwCreationFlags */, (LPVOID)envstr.c_str(), nullptr /* lpCurrentDirectory */, &startup_info, &process_info))
|
||||
throw Slic3r::RuntimeError(std::string("Failed starting the script ") + boost::nowide::narrow(command_line) + ", Win32 error: " + std::to_string(int(::GetLastError())));
|
||||
::WaitForSingleObject(process_info.hProcess, INFINITE);
|
||||
ULONG rc = 0;
|
||||
::GetExitCodeProcess(process_info.hProcess, &rc);
|
||||
::CloseHandle(process_info.hThread);
|
||||
::CloseHandle(process_info.hProcess);
|
||||
return rc;
|
||||
}
|
||||
|
||||
// Run the script. If it is a perl script, run it through the bundled perl interpreter.
|
||||
// If it is a batch file, run it through the cmd.exe.
|
||||
// Otherwise run it directly.
|
||||
static int run_script(const std::string &script, const std::string &gcode, std::string &/*std_err*/)
|
||||
{
|
||||
// Unpack the argument list provided by the user.
|
||||
int nArgs;
|
||||
LPWSTR *szArglist = CommandLineToArgvW(boost::nowide::widen(script).c_str(), &nArgs);
|
||||
if (szArglist == nullptr || nArgs <= 0) {
|
||||
// CommandLineToArgvW failed. Maybe the command line escapment is invalid?
|
||||
throw Slic3r::RuntimeError(std::string("Post processing script ") + script + " on file " + gcode + " failed. CommandLineToArgvW() refused to parse the command line path.");
|
||||
}
|
||||
|
||||
std::wstring command_line;
|
||||
std::wstring command = szArglist[0];
|
||||
if (! boost::filesystem::exists(boost::filesystem::path(command)))
|
||||
throw Slic3r::RuntimeError(std::string("The configured post-processing script does not exist: ") + boost::nowide::narrow(command));
|
||||
if (boost::iends_with(command, L".pl")) {
|
||||
// This is a perl script. Run it through the perl interpreter.
|
||||
// The current process may be slic3r.exe or slic3r-console.exe.
|
||||
// Find the path of the process:
|
||||
wchar_t wpath_exe[_MAX_PATH + 1];
|
||||
::GetModuleFileNameW(nullptr, wpath_exe, _MAX_PATH);
|
||||
boost::filesystem::path path_exe(wpath_exe);
|
||||
boost::filesystem::path path_perl = path_exe.parent_path() / "perl" / "perl.exe";
|
||||
if (! boost::filesystem::exists(path_perl)) {
|
||||
LocalFree(szArglist);
|
||||
throw Slic3r::RuntimeError(std::string("Perl interpreter ") + path_perl.string() + " does not exist.");
|
||||
}
|
||||
// Replace it with the current perl interpreter.
|
||||
quote_argv_winapi(boost::nowide::widen(path_perl.string()), command_line);
|
||||
command_line += L" ";
|
||||
} else if (boost::iends_with(command, ".bat")) {
|
||||
// Run a batch file through the command line interpreter.
|
||||
command_line = L"cmd.exe /C ";
|
||||
}
|
||||
|
||||
for (int i = 0; i < nArgs; ++ i) {
|
||||
quote_argv_winapi(szArglist[i], command_line);
|
||||
command_line += L" ";
|
||||
}
|
||||
LocalFree(szArglist);
|
||||
quote_argv_winapi(boost::nowide::widen(gcode), command_line);
|
||||
return (int)execute_process_winapi(command_line);
|
||||
}
|
||||
|
||||
#else
|
||||
// POSIX
|
||||
|
||||
#include <cstdlib> // getenv()
|
||||
#include <sstream>
|
||||
#include <boost/process.hpp>
|
||||
|
||||
namespace process = boost::process;
|
||||
|
||||
static int run_script(const std::string &script, const std::string &gcode, std::string &std_err)
|
||||
{
|
||||
// Try to obtain user's default shell
|
||||
const char *shell = ::getenv("SHELL");
|
||||
if (shell == nullptr) { shell = "sh"; }
|
||||
|
||||
// Quote and escape the gcode path argument
|
||||
std::string command { script };
|
||||
command.append(" '");
|
||||
for (char c : gcode) {
|
||||
if (c == '\'') { command.append("'\\''"); }
|
||||
else { command.push_back(c); }
|
||||
}
|
||||
command.push_back('\'');
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("Executing script, shell: %1%, command: %2%") % shell % command;
|
||||
|
||||
process::ipstream istd_err;
|
||||
process::child child(shell, "-c", command, process::std_err > istd_err);
|
||||
|
||||
std_err.clear();
|
||||
std::string line;
|
||||
|
||||
while (child.running() && std::getline(istd_err, line)) {
|
||||
std_err.append(line);
|
||||
std_err.push_back('\n');
|
||||
}
|
||||
|
||||
child.wait();
|
||||
return child.exit_code();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
//! macro used to mark string used at localization,
|
||||
//! return same string
|
||||
#define L(s) (s)
|
||||
#define _(s) Slic3r::I18N::translate(s)
|
||||
|
||||
// BBS
|
||||
void gcode_add_line_number(const std::string& path, const DynamicPrintConfig& config)
|
||||
{
|
||||
const ConfigOptionBool* opt = config.opt<ConfigOptionBool>("gcode_add_line_number");
|
||||
if (!opt->getBool())
|
||||
return;
|
||||
|
||||
auto gcode_file = boost::filesystem::path(path);
|
||||
if (!boost::filesystem::exists(gcode_file))
|
||||
return;
|
||||
|
||||
std::fstream fs;
|
||||
std::string new_gcode;
|
||||
fs.open(gcode_file.c_str(), std::fstream::in | std::fstream::out);
|
||||
|
||||
size_t line_number = 1;
|
||||
std::string gcode_line;
|
||||
while (std::getline(fs, gcode_line)) {
|
||||
char num_str[128];
|
||||
memset(num_str, 0, sizeof(num_str));
|
||||
snprintf(num_str, sizeof(num_str), "%d", line_number);
|
||||
new_gcode += std::string("N") + num_str + " " + gcode_line + "\n";
|
||||
line_number++;
|
||||
}
|
||||
|
||||
fs.clear();
|
||||
fs.seekp(0, std::ios_base::beg);
|
||||
fs.write(new_gcode.c_str(), new_gcode.length());
|
||||
fs.close();
|
||||
}
|
||||
|
||||
// Run post processing script / scripts if defined.
|
||||
// Returns true if a post-processing script was executed.
|
||||
// Returns false if no post-processing script was defined.
|
||||
// Throws an exception on error.
|
||||
// host is one of "File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...
|
||||
// For a "File" target, a temp file will be created for src_path by adding a ".pp" suffix and src_path will be updated.
|
||||
// In that case the caller is responsible to delete the temp file created.
|
||||
// output_name is the final name of the G-code on SD card or when uploaded to PrusaLink or OctoPrint.
|
||||
// If uploading to PrusaLink or OctoPrint, then the file will be renamed to output_name first on the target host.
|
||||
// The post-processing script may change the output_name.
|
||||
bool run_post_process_scripts(std::string &src_path, bool make_copy, const std::string &host, std::string &output_name, const DynamicPrintConfig &config)
|
||||
{
|
||||
const auto *post_process = config.opt<ConfigOptionStrings>("post_process");
|
||||
if (// likely running in SLA mode
|
||||
post_process == nullptr ||
|
||||
// no post-processing script
|
||||
post_process->values.empty())
|
||||
return false;
|
||||
|
||||
std::string path;
|
||||
if (make_copy) {
|
||||
// Don't run the post-processing script on the input file, it will be memory mapped by the G-code viewer.
|
||||
// Make a copy.
|
||||
path = src_path + ".pp";
|
||||
// First delete an old file if it exists.
|
||||
try {
|
||||
if (boost::filesystem::exists(path))
|
||||
boost::filesystem::remove(path);
|
||||
} catch (const std::exception &err) {
|
||||
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting an old temporary file %1% before running a post-processing script: %2%", path, err.what());
|
||||
}
|
||||
// Second make a copy.
|
||||
std::string error_message;
|
||||
if (copy_file(src_path, path, error_message, false) != SUCCESS)
|
||||
throw Slic3r::RuntimeError(Slic3r::format("Failed making a temporary copy of G-code file %1% before running a post-processing script: %2%", src_path, error_message));
|
||||
} else {
|
||||
// Don't make a copy of the G-code before running the post-processing script.
|
||||
path = src_path;
|
||||
}
|
||||
|
||||
auto delete_copy = [&path, &src_path, make_copy]() {
|
||||
if (make_copy)
|
||||
try {
|
||||
if (boost::filesystem::exists(path))
|
||||
boost::filesystem::remove(path);
|
||||
} catch (const std::exception &err) {
|
||||
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting a temporary copy %1% of a G-code file %2% : %3%", path, src_path, err.what());
|
||||
}
|
||||
};
|
||||
|
||||
auto gcode_file = boost::filesystem::path(path);
|
||||
if (! boost::filesystem::exists(gcode_file))
|
||||
throw Slic3r::RuntimeError(std::string("Post-processor can't find exported gcode file"));
|
||||
|
||||
// Store print configuration into environment variables.
|
||||
config.setenv_();
|
||||
// Let the post-processing script know the target host ("File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...)
|
||||
boost::nowide::setenv("SLIC3R_PP_HOST", host.c_str(), 1);
|
||||
// Let the post-processing script know the final file name. For "File" host, it is a full path of the target file name and its location, for example pointing to an SD card.
|
||||
// For "PrusaLink" or "OctoPrint", it is a file name optionally with a directory on the target host.
|
||||
boost::nowide::setenv("SLIC3R_PP_OUTPUT_NAME", output_name.c_str(), 1);
|
||||
|
||||
// Path to an optional file that the post-processing script may create and populate it with a single line containing the output_name replacement.
|
||||
std::string path_output_name = path + ".output_name";
|
||||
auto remove_output_name_file = [&path_output_name, &src_path]() {
|
||||
try {
|
||||
if (boost::filesystem::exists(path_output_name))
|
||||
boost::filesystem::remove(path_output_name);
|
||||
} catch (const std::exception &err) {
|
||||
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting a file %1% carrying the final name / path of a G-code file %2%: %3%", path_output_name, src_path, err.what());
|
||||
}
|
||||
};
|
||||
// Remove possible stalled path_output_name of the previous run.
|
||||
remove_output_name_file();
|
||||
|
||||
try {
|
||||
for (const std::string &scripts : post_process->values) {
|
||||
std::vector<std::string> lines;
|
||||
boost::split(lines, scripts, boost::is_any_of("\r\n"));
|
||||
for (std::string script : lines) {
|
||||
// Ignore empty post processing script lines.
|
||||
boost::trim(script);
|
||||
if (script.empty())
|
||||
continue;
|
||||
BOOST_LOG_TRIVIAL(info) << "Executing script " << script << " on file " << path;
|
||||
std::string std_err;
|
||||
const int result = run_script(script, gcode_file.string(), std_err);
|
||||
if (result != 0) {
|
||||
const std::string msg = std_err.empty() ? (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%") % script % path % result).str()
|
||||
: (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%\nOutput:\n%4%") % script % path % result % std_err).str();
|
||||
BOOST_LOG_TRIVIAL(error) << msg;
|
||||
delete_copy();
|
||||
throw Slic3r::RuntimeError(msg);
|
||||
}
|
||||
if (! boost::filesystem::exists(gcode_file)) {
|
||||
const std::string msg = (boost::format(_(L(
|
||||
"Post-processing script %1% failed.\n\n"
|
||||
"The post-processing script is expected to change the G-code file %2% in place, but the G-code file was deleted and likely saved under a new name.\n"
|
||||
"Please adjust the post-processing script to change the G-code in place and consult the manual on how to optionally rename the post-processed G-code file.\n")))
|
||||
% script % path).str();
|
||||
BOOST_LOG_TRIVIAL(error) << msg;
|
||||
throw Slic3r::RuntimeError(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (boost::filesystem::exists(path_output_name)) {
|
||||
try {
|
||||
// Read a single line from path_output_name, which should contain the new output name of the post-processed G-code.
|
||||
boost::nowide::fstream f;
|
||||
f.open(path_output_name, std::ios::in);
|
||||
std::string new_output_name;
|
||||
std::getline(f, new_output_name);
|
||||
f.close();
|
||||
|
||||
if (host == "File") {
|
||||
namespace fs = boost::filesystem;
|
||||
fs::path op(new_output_name);
|
||||
if (op.is_relative() && op.has_filename() && op.parent_path().empty()) {
|
||||
// Is this just a filename? Make it an absolute path.
|
||||
auto outpath = fs::path(output_name).parent_path();
|
||||
outpath /= op.string();
|
||||
new_output_name = outpath.string();
|
||||
}
|
||||
else {
|
||||
if (! op.is_absolute() || ! op.has_filename())
|
||||
throw Slic3r::RuntimeError("Unable to parse desired new path from output name file");
|
||||
}
|
||||
if (! fs::exists(fs::path(new_output_name).parent_path()))
|
||||
throw Slic3r::RuntimeError(Slic3r::format("Output directory does not exist: %1%",
|
||||
fs::path(new_output_name).parent_path().string()));
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Post-processing script changed the file name from " << output_name << " to " << new_output_name;
|
||||
output_name = new_output_name;
|
||||
} catch (const std::exception &err) {
|
||||
throw Slic3r::RuntimeError(Slic3r::format("run_post_process_scripts: Failed reading a file %1% "
|
||||
"carrying the final name / path of a G-code file: %2%",
|
||||
path_output_name, err.what()));
|
||||
}
|
||||
remove_output_name_file();
|
||||
}
|
||||
} catch (...) {
|
||||
remove_output_name_file();
|
||||
delete_copy();
|
||||
throw;
|
||||
}
|
||||
|
||||
src_path = std::move(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
34
src/libslic3r/GCode/PostProcessor.hpp
Normal file
34
src/libslic3r/GCode/PostProcessor.hpp
Normal file
|
@ -0,0 +1,34 @@
|
|||
#ifndef slic3r_GCode_PostProcessor_hpp_
|
||||
#define slic3r_GCode_PostProcessor_hpp_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Run post processing script / scripts if defined.
|
||||
// Returns true if a post-processing script was executed.
|
||||
// Returns false if no post-processing script was defined.
|
||||
// Throws an exception on error.
|
||||
// host is one of "File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...
|
||||
// If make_copy, then a temp file will be created for src_path by adding a ".pp" suffix and src_path will be updated.
|
||||
// In that case the caller is responsible to delete the temp file created.
|
||||
// output_name is the final name of the G-code on SD card or when uploaded to PrusaLink or OctoPrint.
|
||||
// If uploading to PrusaLink or OctoPrint, then the file will be renamed to output_name first on the target host.
|
||||
// The post-processing script may change the output_name.
|
||||
extern bool run_post_process_scripts(std::string &src_path, bool make_copy, const std::string &host, std::string &output_name, const DynamicPrintConfig &config);
|
||||
|
||||
inline bool run_post_process_scripts(std::string &src_path, const DynamicPrintConfig &config)
|
||||
{
|
||||
std::string src_path_name = src_path;
|
||||
return run_post_process_scripts(src_path, false, "File", src_path_name, config);
|
||||
}
|
||||
|
||||
// BBS
|
||||
extern void gcode_add_line_number(const std::string &path, const DynamicPrintConfig &config);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_GCode_PostProcessor_hpp_ */
|
623
src/libslic3r/GCode/PressureEqualizer.cpp
Normal file
623
src/libslic3r/GCode/PressureEqualizer.cpp
Normal file
|
@ -0,0 +1,623 @@
|
|||
#include <memory.h>
|
||||
#include <string.h>
|
||||
#include <float.h>
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../PrintConfig.hpp"
|
||||
#include "../LocalesUtils.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 Slic3r::RuntimeError("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 = string_to_double_decimal_point(line, &endptr);
|
||||
if (endptr == NULL || !is_ws_or_eol(*endptr))
|
||||
throw Slic3r::RuntimeError("PressureEqualizer: Error parsing a float");
|
||||
line = endptr;
|
||||
return result;
|
||||
};
|
||||
|
||||
bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLine &buf)
|
||||
{
|
||||
static constexpr const char *EXTRUSION_ROLE_TAG = ";_EXTRUSION_ROLE:";
|
||||
|
||||
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 Slic3r::RuntimeError(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 Slic3r::RuntimeError(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
|
212
src/libslic3r/GCode/PressureEqualizer.hpp
Normal file
212
src/libslic3r/GCode/PressureEqualizer.hpp
Normal file
|
@ -0,0 +1,212 @@
|
|||
#ifndef slic3r_GCode_PressureEqualizer_hpp_
|
||||
#define slic3r_GCode_PressureEqualizer_hpp_
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../PrintConfig.hpp"
|
||||
#include "../ExtrusionEntity.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Processes a G-code. Finds changes in the volumetric extrusion speed and adjusts the transitions
|
||||
// between these paths to limit fast changes in the volumetric extrusion speed.
|
||||
class PressureEqualizer
|
||||
{
|
||||
public:
|
||||
PressureEqualizer(const Slic3r::GCodeConfig *config);
|
||||
~PressureEqualizer();
|
||||
|
||||
void reset();
|
||||
|
||||
// Process a next batch of G-code lines. Flush the internal buffers if asked for.
|
||||
const char* process(const char *szGCode, bool flush);
|
||||
|
||||
size_t get_output_buffer_length() const { return output_buffer_length; }
|
||||
|
||||
private:
|
||||
struct Statistics
|
||||
{
|
||||
void reset() {
|
||||
volumetric_extrusion_rate_min = std::numeric_limits<float>::max();
|
||||
volumetric_extrusion_rate_max = 0.f;
|
||||
volumetric_extrusion_rate_avg = 0.f;
|
||||
extrusion_length = 0.f;
|
||||
}
|
||||
void update(float volumetric_extrusion_rate, float length) {
|
||||
volumetric_extrusion_rate_min = std::min(volumetric_extrusion_rate_min, volumetric_extrusion_rate);
|
||||
volumetric_extrusion_rate_max = std::max(volumetric_extrusion_rate_max, volumetric_extrusion_rate);
|
||||
volumetric_extrusion_rate_avg += volumetric_extrusion_rate * length;
|
||||
extrusion_length += length;
|
||||
}
|
||||
float volumetric_extrusion_rate_min;
|
||||
float volumetric_extrusion_rate_max;
|
||||
float volumetric_extrusion_rate_avg;
|
||||
float extrusion_length;
|
||||
};
|
||||
|
||||
struct Statistics m_stat;
|
||||
|
||||
// Keeps the reference, does not own the config.
|
||||
const Slic3r::GCodeConfig *m_config;
|
||||
|
||||
// Private configuration values
|
||||
// How fast could the volumetric extrusion rate increase / decrase? mm^3/sec^2
|
||||
struct ExtrusionRateSlope {
|
||||
float positive;
|
||||
float negative;
|
||||
};
|
||||
enum { numExtrusionRoles = erSupportMaterialInterface + 1 };
|
||||
ExtrusionRateSlope m_max_volumetric_extrusion_rate_slopes[numExtrusionRoles];
|
||||
float m_max_volumetric_extrusion_rate_slope_positive;
|
||||
float m_max_volumetric_extrusion_rate_slope_negative;
|
||||
// Maximum segment length to split a long segment, if the initial and the final flow rate differ.
|
||||
float m_max_segment_length;
|
||||
|
||||
// Configuration extracted from config.
|
||||
// Area of the crossestion of each filament. Necessary to calculate the volumetric flow rate.
|
||||
std::vector<float> m_filament_crossections;
|
||||
|
||||
// Internal data.
|
||||
// X,Y,Z,E,F
|
||||
float m_current_pos[5];
|
||||
size_t m_current_extruder;
|
||||
ExtrusionRole m_current_extrusion_role;
|
||||
bool m_retracted;
|
||||
|
||||
enum GCodeLineType
|
||||
{
|
||||
GCODELINETYPE_INVALID,
|
||||
GCODELINETYPE_NOOP,
|
||||
GCODELINETYPE_OTHER,
|
||||
GCODELINETYPE_RETRACT,
|
||||
GCODELINETYPE_UNRETRACT,
|
||||
GCODELINETYPE_TOOL_CHANGE,
|
||||
GCODELINETYPE_MOVE,
|
||||
GCODELINETYPE_EXTRUDE,
|
||||
};
|
||||
|
||||
struct GCodeLine
|
||||
{
|
||||
GCodeLine() :
|
||||
type(GCODELINETYPE_INVALID),
|
||||
raw_length(0),
|
||||
modified(false),
|
||||
extruder_id(0),
|
||||
volumetric_extrusion_rate(0.f),
|
||||
volumetric_extrusion_rate_start(0.f),
|
||||
volumetric_extrusion_rate_end(0.f)
|
||||
{}
|
||||
|
||||
bool moving_xy() const { return fabs(pos_end[0] - pos_start[0]) > 0.f || fabs(pos_end[1] - pos_start[1]) > 0.f; }
|
||||
bool moving_z () const { return fabs(pos_end[2] - pos_start[2]) > 0.f; }
|
||||
bool extruding() const { return moving_xy() && pos_end[3] > pos_start[3]; }
|
||||
bool retracting() const { return pos_end[3] < pos_start[3]; }
|
||||
bool deretracting() const { return ! moving_xy() && pos_end[3] > pos_start[3]; }
|
||||
|
||||
float dist_xy2() const { return (pos_end[0] - pos_start[0]) * (pos_end[0] - pos_start[0]) + (pos_end[1] - pos_start[1]) * (pos_end[1] - pos_start[1]); }
|
||||
float dist_xyz2() const { return (pos_end[0] - pos_start[0]) * (pos_end[0] - pos_start[0]) + (pos_end[1] - pos_start[1]) * (pos_end[1] - pos_start[1]) + (pos_end[2] - pos_start[2]) * (pos_end[2] - pos_start[2]); }
|
||||
float dist_xy() const { return sqrt(dist_xy2()); }
|
||||
float dist_xyz() const { return sqrt(dist_xyz2()); }
|
||||
float dist_e() const { return fabs(pos_end[3] - pos_start[3]); }
|
||||
|
||||
float feedrate() const { return pos_end[4]; }
|
||||
float time() const { return dist_xyz() / feedrate(); }
|
||||
float time_inv() const { return feedrate() / dist_xyz(); }
|
||||
float volumetric_correction_avg() const {
|
||||
float avg_correction = 0.5f * (volumetric_extrusion_rate_start + volumetric_extrusion_rate_end) / volumetric_extrusion_rate;
|
||||
assert(avg_correction > 0.f);
|
||||
assert(avg_correction <= 1.00000001f);
|
||||
return avg_correction;
|
||||
}
|
||||
float time_corrected() const { return time() * volumetric_correction_avg(); }
|
||||
|
||||
GCodeLineType type;
|
||||
|
||||
// We try to keep the string buffer once it has been allocated, so it will not be reallocated over and over.
|
||||
std::vector<char> raw;
|
||||
size_t raw_length;
|
||||
// If modified, the raw text has to be adapted by the new extrusion rate,
|
||||
// or maybe the line needs to be split into multiple lines.
|
||||
bool modified;
|
||||
|
||||
// float timeStart;
|
||||
// float timeEnd;
|
||||
// X,Y,Z,E,F. Storing the state of the currently active extruder only.
|
||||
float pos_start[5];
|
||||
float pos_end[5];
|
||||
// Was the axis found on the G-code line? X,Y,Z,F
|
||||
bool pos_provided[5];
|
||||
|
||||
// Index of the active extruder.
|
||||
size_t extruder_id;
|
||||
// Extrusion role of this segment.
|
||||
ExtrusionRole extrusion_role;
|
||||
|
||||
// Current volumetric extrusion rate.
|
||||
float volumetric_extrusion_rate;
|
||||
// Volumetric extrusion rate at the start of this segment.
|
||||
float volumetric_extrusion_rate_start;
|
||||
// Volumetric extrusion rate at the end of this segment.
|
||||
float volumetric_extrusion_rate_end;
|
||||
|
||||
// Volumetric extrusion rate slope limiting this segment.
|
||||
// If set to zero, the slope is unlimited.
|
||||
float max_volumetric_extrusion_rate_slope_positive;
|
||||
float max_volumetric_extrusion_rate_slope_negative;
|
||||
};
|
||||
|
||||
// Circular buffer of GCode lines. The circular buffer size will be limited to circular_buffer_size.
|
||||
std::vector<GCodeLine> circular_buffer;
|
||||
// Current position of the circular buffer (index, where to write the next line to, the line has to be pushed out before it is overwritten).
|
||||
size_t circular_buffer_pos;
|
||||
// Circular buffer size, configuration value.
|
||||
size_t circular_buffer_size;
|
||||
// Number of valid lines in the circular buffer. Lower or equal to circular_buffer_size.
|
||||
size_t circular_buffer_items;
|
||||
|
||||
// Output buffer will only grow. It will not be reallocated over and over.
|
||||
std::vector<char> output_buffer;
|
||||
size_t output_buffer_length;
|
||||
|
||||
// For debugging purposes. Index of the G-code line processed.
|
||||
size_t line_idx;
|
||||
|
||||
bool process_line(const char *line, const size_t len, GCodeLine &buf);
|
||||
void output_gcode_line(GCodeLine &buf);
|
||||
|
||||
// Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes.
|
||||
// Then go forward and adjust the feedrate to decrease the slope of the extrusion rate changes.
|
||||
void adjust_volumetric_rate();
|
||||
|
||||
// Push the text to the end of the output_buffer.
|
||||
void push_to_output(const char *text, const size_t len, bool add_eol = true);
|
||||
// Push an axis assignment to the end of the output buffer.
|
||||
void push_axis_to_output(const char axis, const float value, bool add_eol = false);
|
||||
// Push a G-code line to the output,
|
||||
void push_line_to_output(const GCodeLine &line, const float new_feedrate, const char *comment);
|
||||
|
||||
size_t circular_buffer_idx_head() const {
|
||||
size_t idx = circular_buffer_pos + circular_buffer_size - circular_buffer_items;
|
||||
if (idx >= circular_buffer_size)
|
||||
idx -= circular_buffer_size;
|
||||
return idx;
|
||||
}
|
||||
|
||||
size_t circular_buffer_idx_tail() const { return circular_buffer_pos; }
|
||||
|
||||
size_t circular_buffer_idx_prev(size_t idx) const {
|
||||
idx += circular_buffer_size - 1;
|
||||
if (idx >= circular_buffer_size)
|
||||
idx -= circular_buffer_size;
|
||||
return idx;
|
||||
}
|
||||
|
||||
size_t circular_buffer_idx_next(size_t idx) const {
|
||||
if (++ idx >= circular_buffer_size)
|
||||
idx -= circular_buffer_size;
|
||||
return idx;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_GCode_PressureEqualizer_hpp_ */
|
192
src/libslic3r/GCode/PrintExtents.cpp
Normal file
192
src/libslic3r/GCode/PrintExtents.cpp
Normal file
|
@ -0,0 +1,192 @@
|
|||
// Calculate extents of the extrusions assigned to Print / PrintObject.
|
||||
// The extents are used for assessing collisions of the print with the priming towers,
|
||||
// to decide whether to pause the print after the priming towers are extruded
|
||||
// to let the operator remove them from the print bed.
|
||||
|
||||
#include "../BoundingBox.hpp"
|
||||
#include "../ExtrusionEntity.hpp"
|
||||
#include "../ExtrusionEntityCollection.hpp"
|
||||
#include "../Layer.hpp"
|
||||
#include "../Print.hpp"
|
||||
|
||||
#include "PrintExtents.hpp"
|
||||
#include "WipeTower.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
static inline BoundingBox extrusion_polyline_extents(const Polyline &polyline, const coord_t radius)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
if (! polyline.points.empty())
|
||||
bbox.merge(polyline.points.front());
|
||||
for (const Point &pt : polyline.points) {
|
||||
bbox.min(0) = std::min(bbox.min(0), pt(0) - radius);
|
||||
bbox.min(1) = std::min(bbox.min(1), pt(1) - radius);
|
||||
bbox.max(0) = std::max(bbox.max(0), pt(0) + radius);
|
||||
bbox.max(1) = std::max(bbox.max(1), pt(1) + radius);
|
||||
}
|
||||
return bbox;
|
||||
}
|
||||
|
||||
static inline BoundingBoxf extrusionentity_extents(const ExtrusionPath &extrusion_path)
|
||||
{
|
||||
BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width)));
|
||||
BoundingBoxf bboxf;
|
||||
if (! empty(bbox)) {
|
||||
bboxf.min = unscale(bbox.min);
|
||||
bboxf.max = unscale(bbox.max);
|
||||
bboxf.defined = true;
|
||||
}
|
||||
return bboxf;
|
||||
}
|
||||
|
||||
static inline BoundingBoxf extrusionentity_extents(const ExtrusionLoop &extrusion_loop)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const ExtrusionPath &extrusion_path : extrusion_loop.paths)
|
||||
bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width))));
|
||||
BoundingBoxf bboxf;
|
||||
if (! empty(bbox)) {
|
||||
bboxf.min = unscale(bbox.min);
|
||||
bboxf.max = unscale(bbox.max);
|
||||
bboxf.defined = true;
|
||||
}
|
||||
return bboxf;
|
||||
}
|
||||
|
||||
static inline BoundingBoxf extrusionentity_extents(const ExtrusionMultiPath &extrusion_multi_path)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const ExtrusionPath &extrusion_path : extrusion_multi_path.paths)
|
||||
bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width))));
|
||||
BoundingBoxf bboxf;
|
||||
if (! empty(bbox)) {
|
||||
bboxf.min = unscale(bbox.min);
|
||||
bboxf.max = unscale(bbox.max);
|
||||
bboxf.defined = true;
|
||||
}
|
||||
return bboxf;
|
||||
}
|
||||
|
||||
static BoundingBoxf extrusionentity_extents(const ExtrusionEntity *extrusion_entity);
|
||||
|
||||
static inline BoundingBoxf extrusionentity_extents(const ExtrusionEntityCollection &extrusion_entity_collection)
|
||||
{
|
||||
BoundingBoxf bbox;
|
||||
for (const ExtrusionEntity *extrusion_entity : extrusion_entity_collection.entities)
|
||||
bbox.merge(extrusionentity_extents(extrusion_entity));
|
||||
return bbox;
|
||||
}
|
||||
|
||||
static BoundingBoxf extrusionentity_extents(const ExtrusionEntity *extrusion_entity)
|
||||
{
|
||||
if (extrusion_entity == nullptr)
|
||||
return BoundingBoxf();
|
||||
auto *extrusion_path = dynamic_cast<const ExtrusionPath*>(extrusion_entity);
|
||||
if (extrusion_path != nullptr)
|
||||
return extrusionentity_extents(*extrusion_path);
|
||||
auto *extrusion_loop = dynamic_cast<const ExtrusionLoop*>(extrusion_entity);
|
||||
if (extrusion_loop != nullptr)
|
||||
return extrusionentity_extents(*extrusion_loop);
|
||||
auto *extrusion_multi_path = dynamic_cast<const ExtrusionMultiPath*>(extrusion_entity);
|
||||
if (extrusion_multi_path != nullptr)
|
||||
return extrusionentity_extents(*extrusion_multi_path);
|
||||
auto *extrusion_entity_collection = dynamic_cast<const ExtrusionEntityCollection*>(extrusion_entity);
|
||||
if (extrusion_entity_collection != nullptr)
|
||||
return extrusionentity_extents(*extrusion_entity_collection);
|
||||
throw Slic3r::RuntimeError("Unexpected extrusion_entity type in extrusionentity_extents()");
|
||||
return BoundingBoxf();
|
||||
}
|
||||
|
||||
BoundingBoxf get_print_extrusions_extents(const Print &print)
|
||||
{
|
||||
//BBS: usage of m_brim are deleted, the bbx of skrit is always larger than that of brim
|
||||
BoundingBoxf bbox(extrusionentity_extents(print.skirt()));
|
||||
return bbox;
|
||||
}
|
||||
|
||||
BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object, const coordf_t max_print_z)
|
||||
{
|
||||
BoundingBoxf bbox;
|
||||
for (const Layer *layer : print_object.layers()) {
|
||||
if (layer->print_z > max_print_z)
|
||||
break;
|
||||
BoundingBoxf bbox_this;
|
||||
for (const LayerRegion *layerm : layer->regions()) {
|
||||
bbox_this.merge(extrusionentity_extents(layerm->perimeters));
|
||||
for (const ExtrusionEntity *ee : layerm->fills.entities)
|
||||
// fill represents infill extrusions of a single island.
|
||||
bbox_this.merge(extrusionentity_extents(*dynamic_cast<const ExtrusionEntityCollection*>(ee)));
|
||||
}
|
||||
const SupportLayer *support_layer = dynamic_cast<const SupportLayer*>(layer);
|
||||
if (support_layer)
|
||||
for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities)
|
||||
bbox_this.merge(extrusionentity_extents(extrusion_entity));
|
||||
for (const PrintInstance &instance : print_object.instances()) {
|
||||
BoundingBoxf bbox_translated(bbox_this);
|
||||
bbox_translated.translate(unscale(instance.shift));
|
||||
bbox.merge(bbox_translated);
|
||||
}
|
||||
}
|
||||
return bbox;
|
||||
}
|
||||
|
||||
// Returns a bounding box of a projection of the wipe tower for the layers <= max_print_z.
|
||||
// The projection does not contain the priming regions.
|
||||
BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_t max_print_z)
|
||||
{
|
||||
// Wipe tower extrusions are saved as if the tower was at the origin with no rotation
|
||||
// We need to get position and angle of the wipe tower to transform them to actual position.
|
||||
int plate_idx = print.get_plate_index();
|
||||
Vec3d plate_origin = print.get_plate_origin();
|
||||
double wipe_tower_x = print.config().wipe_tower_x.get_at(plate_idx) + plate_origin(0);
|
||||
double wipe_tower_y = print.config().wipe_tower_y.get_at(plate_idx) + plate_origin(1);
|
||||
Transform2d trafo =
|
||||
Eigen::Translation2d(wipe_tower_x, wipe_tower_y) *
|
||||
Eigen::Rotation2Dd(Geometry::deg2rad(print.config().wipe_tower_rotation_angle.value));
|
||||
|
||||
BoundingBoxf bbox;
|
||||
for (const std::vector<WipeTower::ToolChangeResult> &tool_changes : print.wipe_tower_data().tool_changes) {
|
||||
if (! tool_changes.empty() && tool_changes.front().print_z > max_print_z)
|
||||
break;
|
||||
for (const WipeTower::ToolChangeResult &tcr : tool_changes) {
|
||||
for (size_t i = 1; i < tcr.extrusions.size(); ++ i) {
|
||||
const WipeTower::Extrusion &e = tcr.extrusions[i];
|
||||
if (e.width > 0) {
|
||||
Vec2d delta = 0.5 * Vec2d(e.width, e.width);
|
||||
Vec2d p1 = trafo * (&e - 1)->pos.cast<double>();
|
||||
Vec2d p2 = trafo * e.pos.cast<double>();
|
||||
bbox.merge(p1.cwiseMin(p2) - delta);
|
||||
bbox.merge(p1.cwiseMax(p2) + delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return bbox;
|
||||
}
|
||||
|
||||
// Returns a bounding box of the wipe tower priming extrusions.
|
||||
BoundingBoxf get_wipe_tower_priming_extrusions_extents(const Print &print)
|
||||
{
|
||||
BoundingBoxf bbox;
|
||||
if (print.wipe_tower_data().priming != nullptr) {
|
||||
for (const WipeTower::ToolChangeResult &tcr : *print.wipe_tower_data().priming) {
|
||||
for (size_t i = 1; i < tcr.extrusions.size(); ++ i) {
|
||||
const WipeTower::Extrusion &e = tcr.extrusions[i];
|
||||
if (e.width > 0) {
|
||||
const Vec2d& p1 = (&e - 1)->pos.cast<double>();
|
||||
const Vec2d& p2 = e.pos.cast<double>();
|
||||
bbox.merge(p1);
|
||||
coordf_t radius = 0.5 * e.width;
|
||||
bbox.min(0) = std::min(bbox.min(0), std::min(p1(0), p2(0)) - radius);
|
||||
bbox.min(1) = std::min(bbox.min(1), std::min(p1(1), p2(1)) - radius);
|
||||
bbox.max(0) = std::max(bbox.max(0), std::max(p1(0), p2(0)) + radius);
|
||||
bbox.max(1) = std::max(bbox.max(1), std::max(p1(1), p2(1)) + radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return bbox;
|
||||
}
|
||||
|
||||
}
|
30
src/libslic3r/GCode/PrintExtents.hpp
Normal file
30
src/libslic3r/GCode/PrintExtents.hpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Measure extents of the planned extrusions.
|
||||
// To be used for collision reporting.
|
||||
|
||||
#ifndef slic3r_PrintExtents_hpp_
|
||||
#define slic3r_PrintExtents_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Print;
|
||||
class PrintObject;
|
||||
class BoundingBoxf;
|
||||
|
||||
// Returns a bounding box of a projection of the brim and skirt.
|
||||
BoundingBoxf get_print_extrusions_extents(const Print &print);
|
||||
|
||||
// Returns a bounding box of a projection of the object extrusions at z <= max_print_z.
|
||||
BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object, const coordf_t max_print_z);
|
||||
|
||||
// Returns a bounding box of a projection of the wipe tower for the layers <= max_print_z.
|
||||
// The projection does not contain the priming regions.
|
||||
BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_t max_print_z);
|
||||
|
||||
// Returns a bounding box of the wipe tower priming extrusions.
|
||||
BoundingBoxf get_wipe_tower_priming_extrusions_extents(const Print &print);
|
||||
|
||||
};
|
||||
|
||||
#endif /* slic3r_PrintExtents_hpp_ */
|
987
src/libslic3r/GCode/SeamPlacer.cpp
Normal file
987
src/libslic3r/GCode/SeamPlacer.cpp
Normal file
|
@ -0,0 +1,987 @@
|
|||
#include "SeamPlacer.hpp"
|
||||
|
||||
#include "libslic3r/ExtrusionEntity.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/EdgeGrid.hpp"
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/SVG.hpp"
|
||||
#include "libslic3r/Layer.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// This penalty is added to all points inside custom blockers (subtracted from pts inside enforcers).
|
||||
static constexpr float ENFORCER_BLOCKER_PENALTY = 100;
|
||||
|
||||
// In case there are custom enforcers/blockers, the loop polygon shall always have
|
||||
// sides smaller than this (so it isn't limited to original resolution).
|
||||
static constexpr float MINIMAL_POLYGON_SIDE = scaled<float>(0.2f);
|
||||
|
||||
// When spAligned is active and there is a support enforcer,
|
||||
// add this penalty to its center.
|
||||
static constexpr float ENFORCER_CENTER_PENALTY = -10.f;
|
||||
|
||||
|
||||
|
||||
|
||||
static float extrudate_overlap_penalty(float nozzle_r, float weight_zero, float overlap_distance)
|
||||
{
|
||||
// The extrudate is not fully supported by the lower layer. Fit a polynomial penalty curve.
|
||||
// Solved by sympy package:
|
||||
/*
|
||||
from sympy import *
|
||||
(x,a,b,c,d,r,z)=symbols('x a b c d r z')
|
||||
p = a + b*x + c*x*x + d*x*x*x
|
||||
p2 = p.subs(solve([p.subs(x, -r), p.diff(x).subs(x, -r), p.diff(x,x).subs(x, -r), p.subs(x, 0)-z], [a, b, c, d]))
|
||||
from sympy.plotting import plot
|
||||
plot(p2.subs(r,0.2).subs(z,1.), (x, -1, 3), adaptive=False, nb_of_points=400)
|
||||
*/
|
||||
if (overlap_distance < - nozzle_r) {
|
||||
// The extrudate is fully supported by the lower layer. This is the ideal case, therefore zero penalty.
|
||||
return 0.f;
|
||||
} else {
|
||||
float x = overlap_distance / nozzle_r;
|
||||
float x2 = x * x;
|
||||
float x3 = x2 * x;
|
||||
return weight_zero * (1.f + 3.f * x + 3.f * x2 + x3);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Return a value in <0, 1> of a cubic B-spline kernel centered around zero.
|
||||
// The B-spline is re-scaled so it has value 1 at zero.
|
||||
static inline float bspline_kernel(float x)
|
||||
{
|
||||
x = std::abs(x);
|
||||
if (x < 1.f) {
|
||||
return 1.f - (3.f / 2.f) * x * x + (3.f / 4.f) * x * x * x;
|
||||
}
|
||||
else if (x < 2.f) {
|
||||
x -= 1.f;
|
||||
float x2 = x * x;
|
||||
float x3 = x2 * x;
|
||||
return (1.f / 4.f) - (3.f / 4.f) * x + (3.f / 4.f) * x2 - (1.f / 4.f) * x3;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static Points::const_iterator project_point_to_polygon_and_insert(Polygon &polygon, const Point &pt, double eps)
|
||||
{
|
||||
assert(polygon.points.size() >= 2);
|
||||
if (polygon.points.size() <= 1)
|
||||
if (polygon.points.size() == 1)
|
||||
return polygon.points.begin();
|
||||
|
||||
Point pt_min;
|
||||
double d_min = std::numeric_limits<double>::max();
|
||||
size_t i_min = size_t(-1);
|
||||
|
||||
for (size_t i = 0; i < polygon.points.size(); ++ i) {
|
||||
size_t j = i + 1;
|
||||
if (j == polygon.points.size())
|
||||
j = 0;
|
||||
const Point &p1 = polygon.points[i];
|
||||
const Point &p2 = polygon.points[j];
|
||||
const Slic3r::Point v_seg = p2 - p1;
|
||||
const Slic3r::Point v_pt = pt - p1;
|
||||
const int64_t l2_seg = int64_t(v_seg(0)) * int64_t(v_seg(0)) + int64_t(v_seg(1)) * int64_t(v_seg(1));
|
||||
int64_t t_pt = int64_t(v_seg(0)) * int64_t(v_pt(0)) + int64_t(v_seg(1)) * int64_t(v_pt(1));
|
||||
if (t_pt < 0) {
|
||||
// Closest to p1.
|
||||
double dabs = sqrt(int64_t(v_pt(0)) * int64_t(v_pt(0)) + int64_t(v_pt(1)) * int64_t(v_pt(1)));
|
||||
if (dabs < d_min) {
|
||||
d_min = dabs;
|
||||
i_min = i;
|
||||
pt_min = p1;
|
||||
}
|
||||
}
|
||||
else if (t_pt > l2_seg) {
|
||||
// Closest to p2. Then p2 is the starting point of another segment, which shall be discovered in the next step.
|
||||
continue;
|
||||
} else {
|
||||
// Closest to the segment.
|
||||
assert(t_pt >= 0 && t_pt <= l2_seg);
|
||||
int64_t d_seg = int64_t(v_seg(1)) * int64_t(v_pt(0)) - int64_t(v_seg(0)) * int64_t(v_pt(1));
|
||||
double d = double(d_seg) / sqrt(double(l2_seg));
|
||||
double dabs = std::abs(d);
|
||||
if (dabs < d_min) {
|
||||
d_min = dabs;
|
||||
i_min = i;
|
||||
// Evaluate the foot point.
|
||||
pt_min = p1;
|
||||
double linv = double(d_seg) / double(l2_seg);
|
||||
pt_min(0) = pt(0) - coord_t(floor(double(v_seg(1)) * linv + 0.5));
|
||||
pt_min(1) = pt(1) + coord_t(floor(double(v_seg(0)) * linv + 0.5));
|
||||
assert(Line(p1, p2).distance_to(pt_min) < scale_(1e-5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(i_min != size_t(-1));
|
||||
if ((pt_min - polygon.points[i_min]).cast<double>().norm() > eps) {
|
||||
// Insert a new point on the segment i_min, i_min+1.
|
||||
return polygon.points.insert(polygon.points.begin() + (i_min + 1), pt_min);
|
||||
}
|
||||
return polygon.points.begin() + i_min;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static std::vector<float> polygon_angles_at_vertices(const Polygon &polygon, const std::vector<float> &lengths, float min_arm_length)
|
||||
{
|
||||
assert(polygon.points.size() + 1 == lengths.size());
|
||||
if (min_arm_length > 0.25f * lengths.back())
|
||||
min_arm_length = 0.25f * lengths.back();
|
||||
|
||||
// Find the initial prev / next point span.
|
||||
size_t idx_prev = polygon.points.size();
|
||||
size_t idx_curr = 0;
|
||||
size_t idx_next = 1;
|
||||
while (idx_prev > idx_curr && lengths.back() - lengths[idx_prev] < min_arm_length)
|
||||
-- idx_prev;
|
||||
while (idx_next < idx_prev && lengths[idx_next] < min_arm_length)
|
||||
++ idx_next;
|
||||
|
||||
std::vector<float> angles(polygon.points.size(), 0.f);
|
||||
for (; idx_curr < polygon.points.size(); ++ idx_curr) {
|
||||
// Move idx_prev up until the distance between idx_prev and idx_curr is lower than min_arm_length.
|
||||
if (idx_prev >= idx_curr) {
|
||||
while (idx_prev < polygon.points.size() && lengths.back() - lengths[idx_prev] + lengths[idx_curr] > min_arm_length)
|
||||
++ idx_prev;
|
||||
if (idx_prev == polygon.points.size())
|
||||
idx_prev = 0;
|
||||
}
|
||||
while (idx_prev < idx_curr && lengths[idx_curr] - lengths[idx_prev] > min_arm_length)
|
||||
++ idx_prev;
|
||||
// Move idx_prev one step back.
|
||||
if (idx_prev == 0)
|
||||
idx_prev = polygon.points.size() - 1;
|
||||
else
|
||||
-- idx_prev;
|
||||
// Move idx_next up until the distance between idx_curr and idx_next is greater than min_arm_length.
|
||||
if (idx_curr <= idx_next) {
|
||||
while (idx_next < polygon.points.size() && lengths[idx_next] - lengths[idx_curr] < min_arm_length)
|
||||
++ idx_next;
|
||||
if (idx_next == polygon.points.size())
|
||||
idx_next = 0;
|
||||
}
|
||||
while (idx_next < idx_curr && lengths.back() - lengths[idx_curr] + lengths[idx_next] < min_arm_length)
|
||||
++ idx_next;
|
||||
// Calculate angle between idx_prev, idx_curr, idx_next.
|
||||
const Point &p0 = polygon.points[idx_prev];
|
||||
const Point &p1 = polygon.points[idx_curr];
|
||||
const Point &p2 = polygon.points[idx_next];
|
||||
const Point v1 = p1 - p0;
|
||||
const Point v2 = p2 - p1;
|
||||
int64_t dot = int64_t(v1(0))*int64_t(v2(0)) + int64_t(v1(1))*int64_t(v2(1));
|
||||
int64_t cross = int64_t(v1(0))*int64_t(v2(1)) - int64_t(v1(1))*int64_t(v2(0));
|
||||
float angle = float(atan2(double(cross), double(dot)));
|
||||
angles[idx_curr] = angle;
|
||||
}
|
||||
|
||||
return angles;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void SeamPlacer::init(const Print& print)
|
||||
{
|
||||
m_enforcers.clear();
|
||||
m_blockers.clear();
|
||||
m_seam_history.clear();
|
||||
m_po_list.clear();
|
||||
|
||||
const std::vector<double>& nozzle_dmrs = print.config().nozzle_diameter.values;
|
||||
float max_nozzle_dmr = *std::max_element(nozzle_dmrs.begin(), nozzle_dmrs.end());
|
||||
|
||||
|
||||
std::vector<ExPolygons> temp_enf;
|
||||
std::vector<ExPolygons> temp_blk;
|
||||
std::vector<Polygons> temp_polygons;
|
||||
|
||||
for (const PrintObject* po : print.objects()) {
|
||||
|
||||
auto merge_and_offset = [po, &temp_polygons, max_nozzle_dmr](EnforcerBlockerType type, std::vector<ExPolygons>& out) {
|
||||
// Offset the triangles out slightly.
|
||||
auto offset_out = [](Polygon& input, float offset) -> ExPolygons {
|
||||
ClipperLib::Paths out(1);
|
||||
std::vector<float> deltas(input.points.size(), offset);
|
||||
input.make_counter_clockwise();
|
||||
out.front() = mittered_offset_path_scaled(input.points, deltas, 3.);
|
||||
return ClipperPaths_to_Slic3rExPolygons(out, true); // perform union
|
||||
};
|
||||
|
||||
|
||||
temp_polygons.clear();
|
||||
po->project_and_append_custom_facets(true, type, temp_polygons);
|
||||
out.clear();
|
||||
out.reserve(temp_polygons.size());
|
||||
float offset = scale_(max_nozzle_dmr + po->config().elefant_foot_compensation);
|
||||
for (Polygons &src : temp_polygons) {
|
||||
out.emplace_back(ExPolygons());
|
||||
for (Polygon& plg : src) {
|
||||
ExPolygons offset_explg = offset_out(plg, offset);
|
||||
if (! offset_explg.empty())
|
||||
out.back().emplace_back(std::move(offset_explg.front()));
|
||||
}
|
||||
|
||||
offset = scale_(max_nozzle_dmr);
|
||||
}
|
||||
};
|
||||
merge_and_offset(EnforcerBlockerType::BLOCKER, temp_blk);
|
||||
merge_and_offset(EnforcerBlockerType::ENFORCER, temp_enf);
|
||||
|
||||
// Remember this PrintObject and initialize a store of enforcers and blockers for it.
|
||||
m_po_list.push_back(po);
|
||||
size_t po_idx = m_po_list.size() - 1;
|
||||
m_enforcers.emplace_back(std::vector<CustomTrianglesPerLayer>(temp_enf.size()));
|
||||
m_blockers.emplace_back(std::vector<CustomTrianglesPerLayer>(temp_blk.size()));
|
||||
|
||||
// A helper class to store data to build the AABB tree from.
|
||||
class CustomTriangleRef {
|
||||
public:
|
||||
CustomTriangleRef(size_t idx,
|
||||
Point&& centroid,
|
||||
BoundingBox&& bb)
|
||||
: m_idx{idx}, m_centroid{centroid},
|
||||
m_bbox{AlignedBoxType(bb.min, bb.max)}
|
||||
{}
|
||||
size_t idx() const { return m_idx; }
|
||||
const Point& centroid() const { return m_centroid; }
|
||||
const TreeType::BoundingBox& bbox() const { return m_bbox; }
|
||||
|
||||
private:
|
||||
size_t m_idx;
|
||||
Point m_centroid;
|
||||
AlignedBoxType m_bbox;
|
||||
};
|
||||
|
||||
// A lambda to extract the ExPolygons and save them into the member AABB tree.
|
||||
// Will be called for enforcers and blockers separately.
|
||||
auto add_custom = [](std::vector<ExPolygons>& src, std::vector<CustomTrianglesPerLayer>& dest) {
|
||||
// Go layer by layer, and append all the ExPolygons into the AABB tree.
|
||||
size_t layer_idx = 0;
|
||||
for (ExPolygons& expolys_on_layer : src) {
|
||||
CustomTrianglesPerLayer& layer_data = dest[layer_idx];
|
||||
std::vector<CustomTriangleRef> triangles_data;
|
||||
layer_data.polys.reserve(expolys_on_layer.size());
|
||||
triangles_data.reserve(expolys_on_layer.size());
|
||||
|
||||
for (ExPolygon& expoly : expolys_on_layer) {
|
||||
if (expoly.empty())
|
||||
continue;
|
||||
layer_data.polys.emplace_back(std::move(expoly));
|
||||
triangles_data.emplace_back(layer_data.polys.size() - 1,
|
||||
layer_data.polys.back().centroid(),
|
||||
layer_data.polys.back().bounding_box());
|
||||
}
|
||||
// All polygons are saved, build the AABB tree for them.
|
||||
layer_data.tree.build(std::move(triangles_data));
|
||||
++layer_idx;
|
||||
}
|
||||
};
|
||||
|
||||
add_custom(temp_enf, m_enforcers.at(po_idx));
|
||||
add_custom(temp_blk, m_blockers.at(po_idx));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void SeamPlacer::plan_perimeters(const std::vector<const ExtrusionEntity*> perimeters,
|
||||
const Layer& layer, SeamPosition seam_position,
|
||||
Point last_pos, coordf_t nozzle_dmr, const PrintObject* po,
|
||||
const EdgeGrid::Grid* lower_layer_edge_grid)
|
||||
{
|
||||
// When printing the perimeters, we want the seams on external and internal perimeters to match.
|
||||
// We have a list of perimeters in the order to be printed. Each internal perimeter must inherit
|
||||
// the seam from the previous external perimeter.
|
||||
|
||||
m_plan.clear();
|
||||
m_plan_idx = 0;
|
||||
|
||||
if (perimeters.empty() || ! po)
|
||||
return;
|
||||
|
||||
m_plan.resize(perimeters.size());
|
||||
|
||||
for (int i = 0; i < int(perimeters.size()); ++i) {
|
||||
if (perimeters[i]->role() == erExternalPerimeter && perimeters[i]->is_loop()) {
|
||||
last_pos = this->calculate_seam(
|
||||
layer, seam_position, *dynamic_cast<const ExtrusionLoop*>(perimeters[i]), nozzle_dmr,
|
||||
po, lower_layer_edge_grid, last_pos);
|
||||
m_plan[i].external = true;
|
||||
m_plan[i].seam_position = seam_position;
|
||||
m_plan[i].layer = &layer;
|
||||
m_plan[i].po = po;
|
||||
}
|
||||
m_plan[i].pt = last_pos;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SeamPlacer::place_seam(ExtrusionLoop& loop, const Point& last_pos, bool external_first, double nozzle_diameter,
|
||||
const EdgeGrid::Grid* lower_layer_edge_grid)
|
||||
{
|
||||
const double seam_offset = nozzle_diameter;
|
||||
|
||||
Point seam = last_pos;
|
||||
if (! m_plan.empty() && m_plan_idx < m_plan.size()) {
|
||||
if (m_plan[m_plan_idx].external) {
|
||||
seam = m_plan[m_plan_idx].pt;
|
||||
// One more heuristics: if the seam is too far from current nozzle position,
|
||||
// try to place it again. This can happen in cases where the external perimeter
|
||||
// does not belong to the preceding ones and they are ordered so they end up
|
||||
// far from each other.
|
||||
if ((seam.cast<double>() - last_pos.cast<double>()).squaredNorm() > std::pow(scale_(5.*nozzle_diameter), 2.))
|
||||
seam = this->calculate_seam(*m_plan[m_plan_idx].layer, m_plan[m_plan_idx].seam_position, loop, nozzle_diameter,
|
||||
m_plan[m_plan_idx].po, lower_layer_edge_grid, last_pos);
|
||||
}
|
||||
else if (! external_first) {
|
||||
// Internal perimeter printed before the external.
|
||||
// First get list of external seams.
|
||||
std::vector<size_t> ext_seams;
|
||||
for (size_t i = 0; i < m_plan.size(); ++i) {
|
||||
if (m_plan[i].external)
|
||||
ext_seams.emplace_back(i);
|
||||
}
|
||||
|
||||
if (! ext_seams.empty()) {
|
||||
// First find the line segment closest to an external seam:
|
||||
int path_idx = 0;
|
||||
int line_idx = 0;
|
||||
size_t ext_seam_idx = size_t(-1);
|
||||
double min_dist_sqr = std::numeric_limits<double>::max();
|
||||
std::vector<Lines> lines_vect;
|
||||
for (int i = 0; i < int(loop.paths.size()); ++i) {
|
||||
lines_vect.emplace_back(loop.paths[i].polyline.lines());
|
||||
const Lines& lines = lines_vect.back();
|
||||
for (int j = 0; j < int(lines.size()); ++j) {
|
||||
for (size_t k : ext_seams) {
|
||||
double d_sqr = lines[j].distance_to_squared(m_plan[k].pt);
|
||||
if (d_sqr < min_dist_sqr) {
|
||||
path_idx = i;
|
||||
line_idx = j;
|
||||
ext_seam_idx = k;
|
||||
min_dist_sqr = d_sqr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only accept seam that is reasonably close.
|
||||
double limit_dist_sqr = std::pow(double(scale_((ext_seam_idx - m_plan_idx) * nozzle_diameter * 2.)), 2.);
|
||||
if (ext_seam_idx != size_t(-1) && min_dist_sqr < limit_dist_sqr) {
|
||||
// Now find a projection of the external seam
|
||||
const Lines& lines = lines_vect[path_idx];
|
||||
Point closest = m_plan[ext_seam_idx].pt.projection_onto(lines[line_idx]);
|
||||
double dist = (closest.cast<double>() - lines[line_idx].b.cast<double>()).norm();
|
||||
|
||||
// And walk along the perimeter until we make enough space for
|
||||
// seams of all perimeters beforethe external one.
|
||||
double offset = (ext_seam_idx - m_plan_idx) * scale_(seam_offset);
|
||||
double last_offset = offset;
|
||||
offset -= dist;
|
||||
const Point* a = &closest;
|
||||
const Point* b = &lines[line_idx].b;
|
||||
while (++line_idx < int(lines.size()) && offset > 0.) {
|
||||
last_offset = offset;
|
||||
offset -= lines[line_idx].length();
|
||||
a = &lines[line_idx].a;
|
||||
b = &lines[line_idx].b;
|
||||
}
|
||||
|
||||
// We have walked far enough, too far maybe. Interpolate on the
|
||||
// last segment to find the end precisely.
|
||||
offset = std::min(0., offset); // In case that offset is still positive (we may have "wrapped around")
|
||||
double ratio = last_offset / (last_offset - offset);
|
||||
seam = (a->cast<double>() + ((b->cast<double>() - a->cast<double>()) * ratio)).cast<coord_t>();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// We should have a candidate ready from before. If not, use last_pos.
|
||||
if (m_plan_idx > 0 && m_plan[m_plan_idx - 1].precalculated)
|
||||
seam = m_plan[m_plan_idx - 1].pt;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Split the loop at the point with a minium penalty.
|
||||
if (!loop.split_at_vertex(seam))
|
||||
// The point is not in the original loop. Insert it.
|
||||
loop.split_at(seam, true);
|
||||
|
||||
if (external_first && m_plan_idx+1<m_plan.size() && ! m_plan[m_plan_idx+1].external) {
|
||||
// Next perimeter should start near this one.
|
||||
const double dist_sqr = std::pow(double(scale_(seam_offset)), 2.);
|
||||
double running_sqr = 0.;
|
||||
double running_sqr_last = 0.;
|
||||
if (!loop.paths.empty() && loop.paths.back().polyline.points.size() > 1) {
|
||||
const ExtrusionPath& last = loop.paths.back();
|
||||
auto it = last.polyline.points.crbegin() + 1;
|
||||
for (; it != last.polyline.points.crend(); ++it) {
|
||||
running_sqr += (it->cast<double>() - (it - 1)->cast<double>()).squaredNorm();
|
||||
if (running_sqr > dist_sqr)
|
||||
break;
|
||||
running_sqr_last = running_sqr;
|
||||
}
|
||||
if (running_sqr <= dist_sqr)
|
||||
it = last.polyline.points.crend() - 1;
|
||||
// Now interpolate.
|
||||
double ratio = (std::sqrt(dist_sqr) - std::sqrt(running_sqr_last)) / (std::sqrt(running_sqr) - std::sqrt(running_sqr_last));
|
||||
m_plan[m_plan_idx + 1].pt = ((it - 1)->cast<double>() + (it->cast<double>() - (it - 1)->cast<double>()) * std::min(ratio, 1.)).cast<coord_t>();
|
||||
m_plan[m_plan_idx + 1].precalculated = true;
|
||||
}
|
||||
}
|
||||
|
||||
++m_plan_idx;
|
||||
}
|
||||
|
||||
constexpr float CLOSE_TO_LAST_SEAM_THRESHOLD = 5;
|
||||
|
||||
// Returns a seam for an EXTERNAL perimeter.
|
||||
Point SeamPlacer::calculate_seam(const Layer& layer, const SeamPosition seam_position,
|
||||
const ExtrusionLoop& loop, coordf_t nozzle_dmr, const PrintObject* po,
|
||||
const EdgeGrid::Grid* lower_layer_edge_grid, Point last_pos)
|
||||
{
|
||||
assert(loop.role() == erExternalPerimeter);
|
||||
Polygon polygon = loop.polygon();
|
||||
bool was_clockwise = polygon.make_counter_clockwise();
|
||||
BoundingBox polygon_bb = polygon.bounding_box();
|
||||
const coord_t nozzle_r = coord_t(scale_(0.5 * nozzle_dmr) + 0.5);
|
||||
|
||||
size_t po_idx = std::find(m_po_list.begin(), m_po_list.end(), po) - m_po_list.begin();
|
||||
|
||||
// Find current layer in respective PrintObject. Cache the result so the
|
||||
// lookup is only done once per layer, not for each loop.
|
||||
const Layer* layer_po = nullptr;
|
||||
if (po == m_last_po && layer.print_z == m_last_print_z)
|
||||
layer_po = m_last_layer_po;
|
||||
else {
|
||||
layer_po = po->get_layer_at_printz(layer.print_z);
|
||||
m_last_po = po;
|
||||
m_last_print_z = layer.print_z;
|
||||
m_last_layer_po = layer_po;
|
||||
}
|
||||
if (! layer_po)
|
||||
return last_pos;
|
||||
|
||||
// Index of this layer in the respective PrintObject.
|
||||
size_t layer_idx = layer_po->id() - po->layers().front()->id(); // raft layers
|
||||
|
||||
assert(layer_idx < po->layer_count());
|
||||
|
||||
if (this->is_custom_seam_on_layer(layer_idx, po_idx)) {
|
||||
// Seam enf/blockers can begin and end in between the original vertices.
|
||||
// Let add extra points in between and update the leghths.
|
||||
polygon.densify(MINIMAL_POLYGON_SIDE);
|
||||
}
|
||||
|
||||
if (seam_position != spRandom) {
|
||||
// Retrieve the last start position for this object.
|
||||
float last_pos_weight = 1.f;
|
||||
|
||||
if (seam_position == spAligned) {
|
||||
// Seam is aligned to the seam at the preceding layer.
|
||||
if (po != nullptr) {
|
||||
std::optional<Point> pos = m_seam_history.get_last_seam(m_po_list[po_idx], layer_idx, polygon_bb);
|
||||
if (pos.has_value()) {
|
||||
last_pos = *pos;
|
||||
last_pos_weight = is_custom_enforcer_on_layer(layer_idx, po_idx) ? 0.f : 1.f;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (seam_position == spRear) {
|
||||
// Object is centered around (0,0) in its current coordinate system.
|
||||
last_pos.x() = 0;
|
||||
last_pos.y() = coord_t(3. * po->bounding_box().radius());
|
||||
last_pos_weight = 5.f;
|
||||
} if (seam_position == spNearest) {
|
||||
// last_pos already contains current nozzle position
|
||||
|
||||
// BBS: if the project point of current nozzle position is close to the last seam of this object
|
||||
// then we think the current nozzle position is almost same with last same.
|
||||
// So that last seam can be treat as one factor even in cost based strategy to make seam more posible to be aligned
|
||||
if (po != nullptr) {
|
||||
std::optional<Point> pos = m_seam_history.get_last_seam(m_po_list[po_idx], layer_idx, polygon_bb);
|
||||
if (pos.has_value()) {
|
||||
Point project_point = polygon.point_projection(last_pos);
|
||||
if ((pos->cast<double>() - project_point.cast<double>()).squaredNorm() < std::pow(scale_(CLOSE_TO_LAST_SEAM_THRESHOLD), 2.))
|
||||
last_pos = *pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Insert a projection of last_pos into the polygon.
|
||||
size_t last_pos_proj_idx;
|
||||
{
|
||||
auto it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r);
|
||||
last_pos_proj_idx = it - polygon.points.begin();
|
||||
}
|
||||
|
||||
// Parametrize the polygon by its length.
|
||||
std::vector<float> lengths = polygon.parameter_by_length();
|
||||
|
||||
// For each polygon point, store a penalty.
|
||||
// First calculate the angles, store them as penalties. The angles are caluculated over a minimum arm length of nozzle_r.
|
||||
std::vector<float> penalties = polygon_angles_at_vertices(polygon, lengths, float(nozzle_r));
|
||||
// No penalty for reflex points, slight penalty for convex points, high penalty for flat surfaces.
|
||||
const float penaltyConvexVertex = 1.f;
|
||||
const float penaltyFlatSurface = 5.f;
|
||||
const float penaltyOverhangHalf = 10.f;
|
||||
// Penalty for visible seams.
|
||||
for (size_t i = 0; i < polygon.points.size(); ++ i) {
|
||||
float ccwAngle = penalties[i];
|
||||
if (was_clockwise)
|
||||
ccwAngle = - ccwAngle;
|
||||
float penalty = 0;
|
||||
if (ccwAngle <- float(0.6 * PI))
|
||||
// Sharp reflex vertex. We love that, it hides the seam perfectly.
|
||||
penalty = 0.f;
|
||||
else if (ccwAngle > float(0.6 * PI))
|
||||
// Seams on sharp convex vertices are more visible than on reflex vertices.
|
||||
penalty = penaltyConvexVertex;
|
||||
else if (ccwAngle < 0.f) {
|
||||
// Interpolate penalty between maximum and zero.
|
||||
penalty = penaltyFlatSurface * bspline_kernel(ccwAngle * float(PI * 2. / 3.));
|
||||
} else {
|
||||
assert(ccwAngle >= 0.f);
|
||||
// Interpolate penalty between maximum and the penalty for a convex vertex.
|
||||
penalty = penaltyConvexVertex + (penaltyFlatSurface - penaltyConvexVertex) * bspline_kernel(ccwAngle * float(PI * 2. / 3.));
|
||||
}
|
||||
// Give a negative penalty for points close to the last point or the prefered seam location.
|
||||
float dist_to_last_pos_proj = (i < last_pos_proj_idx) ?
|
||||
std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) :
|
||||
std::min(lengths[i] - lengths[last_pos_proj_idx], lengths.back() - lengths[i] + lengths[last_pos_proj_idx]);
|
||||
float dist_max = 0.1f * lengths.back(); // 5.f * nozzle_dmr
|
||||
penalty -= last_pos_weight * bspline_kernel(dist_to_last_pos_proj / dist_max);
|
||||
penalties[i] = std::max(0.f, penalty);
|
||||
}
|
||||
|
||||
// Penalty for overhangs.
|
||||
if (lower_layer_edge_grid) {
|
||||
// Use the edge grid distance field structure over the lower layer to calculate overhangs.
|
||||
coord_t nozzle_r = coord_t(std::floor(scale_(0.5 * nozzle_dmr) + 0.5));
|
||||
coord_t search_r = coord_t(std::floor(scale_(0.8 * nozzle_dmr) + 0.5));
|
||||
for (size_t i = 0; i < polygon.points.size(); ++ i) {
|
||||
const Point &p = polygon.points[i];
|
||||
coordf_t dist;
|
||||
// Signed distance is positive outside the object, negative inside the object.
|
||||
// The point is considered at an overhang, if it is more than nozzle radius
|
||||
// outside of the lower layer contour.
|
||||
[[maybe_unused]] bool found = lower_layer_edge_grid->signed_distance(p, search_r, dist);
|
||||
// If the approximate Signed Distance Field was initialized over lower_layer_edge_grid,
|
||||
// then the signed distnace shall always be known.
|
||||
assert(found);
|
||||
penalties[i] += extrudate_overlap_penalty(float(nozzle_r), penaltyOverhangHalf, float(dist));
|
||||
}
|
||||
}
|
||||
|
||||
// Custom seam. Huge (negative) constant penalty is applied inside
|
||||
// blockers (enforcers) to rule out points that should not win.
|
||||
this->apply_custom_seam(polygon, po_idx, penalties, lengths, layer_idx, seam_position);
|
||||
|
||||
// Find a point with a minimum penalty.
|
||||
size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin();
|
||||
|
||||
if (seam_position != spAligned || ! is_custom_enforcer_on_layer(layer_idx, po_idx)) {
|
||||
// Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx.
|
||||
// In that case use last_pos_proj_idx instead.
|
||||
float penalty_aligned = penalties[last_pos_proj_idx];
|
||||
float penalty_min = penalties[idx_min];
|
||||
float penalty_diff_abs = std::abs(penalty_min - penalty_aligned);
|
||||
float penalty_max = std::max(std::abs(penalty_min), std::abs(penalty_aligned));
|
||||
float penalty_diff_rel = (penalty_max == 0.f) ? 0.f : penalty_diff_abs / penalty_max;
|
||||
// printf("Align seams, penalty aligned: %f, min: %f, diff abs: %f, diff rel: %f\n", penalty_aligned, penalty_min, penalty_diff_abs, penalty_diff_rel);
|
||||
if (std::abs(penalty_diff_rel) < 0.05) {
|
||||
// Penalty of the aligned point is very close to the minimum penalty.
|
||||
// Align the seams as accurately as possible.
|
||||
idx_min = last_pos_proj_idx;
|
||||
}
|
||||
}
|
||||
|
||||
if (seam_position == spAligned || seam_position == spNearest)
|
||||
m_seam_history.add_seam(po, polygon.points[idx_min], polygon_bb);
|
||||
|
||||
|
||||
// Export the contour into a SVG file.
|
||||
#if 0
|
||||
{
|
||||
static int iRun = 0;
|
||||
SVG svg(debug_out_path("GCode_extrude_loop-%d.svg", iRun ++));
|
||||
if (m_layer->lower_layer != NULL)
|
||||
svg.draw(m_layer->lower_layer->slices);
|
||||
for (size_t i = 0; i < loop.paths.size(); ++ i)
|
||||
svg.draw(loop.paths[i].as_polyline(), "red");
|
||||
Polylines polylines;
|
||||
for (size_t i = 0; i < loop.paths.size(); ++ i)
|
||||
polylines.push_back(loop.paths[i].as_polyline());
|
||||
Slic3r::Polygons polygons;
|
||||
coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter);
|
||||
coord_t delta = scale_(0.5*nozzle_dmr);
|
||||
Slic3r::offset(polylines, &polygons, delta);
|
||||
// for (size_t i = 0; i < polygons.size(); ++ i) svg.draw((Polyline)polygons[i], "blue");
|
||||
svg.draw(last_pos, "green", 3);
|
||||
svg.draw(polygon.points[idx_min], "yellow", 3);
|
||||
svg.Close();
|
||||
}
|
||||
#endif
|
||||
return polygon.points[idx_min];
|
||||
|
||||
} else
|
||||
return this->get_random_seam(layer_idx, polygon, po_idx);
|
||||
}
|
||||
|
||||
|
||||
Point SeamPlacer::get_random_seam(size_t layer_idx, const Polygon& polygon, size_t po_idx,
|
||||
bool* saw_custom) const
|
||||
{
|
||||
// Parametrize the polygon by its length.
|
||||
const std::vector<float> lengths = polygon.parameter_by_length();
|
||||
|
||||
// Which of the points are inside enforcers/blockers?
|
||||
std::vector<size_t> enforcers_idxs;
|
||||
std::vector<size_t> blockers_idxs;
|
||||
this->get_enforcers_and_blockers(layer_idx, polygon, po_idx, enforcers_idxs, blockers_idxs);
|
||||
|
||||
bool has_enforcers = ! enforcers_idxs.empty();
|
||||
bool has_blockers = ! blockers_idxs.empty();
|
||||
if (saw_custom)
|
||||
*saw_custom = has_enforcers || has_blockers;
|
||||
|
||||
assert(std::is_sorted(enforcers_idxs.begin(), enforcers_idxs.end()));
|
||||
assert(std::is_sorted(blockers_idxs.begin(), blockers_idxs.end()));
|
||||
std::vector<float> edges;
|
||||
|
||||
// Lambda to calculate lengths of all edges of interest. Last parameter
|
||||
// decides whether to measure edges inside or outside idxs.
|
||||
// Negative number = not an edge of interest.
|
||||
auto get_valid_length = [&lengths](const std::vector<size_t>& idxs,
|
||||
std::vector<float>& edges,
|
||||
bool measure_inside_edges) -> float
|
||||
{
|
||||
// First mark edges we are interested in by assigning a positive number.
|
||||
edges.assign(lengths.size()-1, measure_inside_edges ? -1.f : 1.f);
|
||||
for (size_t i=0; i<idxs.size(); ++i) {
|
||||
size_t this_pt_idx = idxs[i];
|
||||
// Two concurrent indices in the list -> the edge between them is the enforcer/blocker.
|
||||
bool inside_edge = ((i != idxs.size()-1 && idxs[i+1] == this_pt_idx + 1)
|
||||
|| (i == idxs.size()-1 && idxs.back() == lengths.size()-2 && idxs[0] == 0));
|
||||
if (inside_edge)
|
||||
edges[this_pt_idx] = measure_inside_edges ? 1.f : -1.f;
|
||||
}
|
||||
// Now measure them.
|
||||
float running_total = 0.f;
|
||||
for (size_t i=0; i<edges.size(); ++i) {
|
||||
if (edges[i] > 0.f) {
|
||||
edges[i] = lengths[i+1] - lengths[i];
|
||||
running_total += edges[i];
|
||||
}
|
||||
}
|
||||
return running_total;
|
||||
};
|
||||
|
||||
// Find all seam candidate edges and their lengths.
|
||||
float valid_length = 0.f;
|
||||
if (has_enforcers)
|
||||
valid_length = get_valid_length(enforcers_idxs, edges, true);
|
||||
|
||||
if (! has_enforcers || valid_length == 0.f) {
|
||||
// Second condition covers case with isolated enf points. Given how the painted
|
||||
// triangles are projected, this should not happen. Stay on the safe side though.
|
||||
if (has_blockers)
|
||||
valid_length = get_valid_length(blockers_idxs, edges, false);
|
||||
if (valid_length == 0.f) // No blockers or everything blocked - use the whole polygon.
|
||||
valid_length = lengths.back();
|
||||
}
|
||||
assert(valid_length != 0.f);
|
||||
// Now generate a random length and find the respective edge.
|
||||
float rand_len = valid_length * (rand()/float(RAND_MAX));
|
||||
size_t pt_idx = 0; // Index of the edge where to put the seam.
|
||||
if (valid_length == lengths.back()) {
|
||||
// Whole polygon is used for placing the seam.
|
||||
auto it = std::lower_bound(lengths.begin(), lengths.end(), rand_len);
|
||||
pt_idx = it == lengths.begin() ? 0 : (it-lengths.begin()-1); // this takes care of a corner case where rand() returns 0
|
||||
} else {
|
||||
float running = 0.f;
|
||||
for (size_t i=0; i<edges.size(); ++i) {
|
||||
running += edges[i] > 0.f ? edges[i] : 0.f;
|
||||
if (running >= rand_len) {
|
||||
pt_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! has_enforcers && ! has_blockers) {
|
||||
// The polygon may be too coarse, calculate the point exactly.
|
||||
assert(valid_length == lengths.back());
|
||||
bool last_seg = pt_idx == polygon.points.size()-1;
|
||||
size_t next_idx = last_seg ? 0 : pt_idx+1;
|
||||
const Point& prev = polygon.points[pt_idx];
|
||||
const Point& next = polygon.points[next_idx];
|
||||
assert(next_idx == 0 || pt_idx+1 == next_idx);
|
||||
coordf_t diff_x = next.x() - prev.x();
|
||||
coordf_t diff_y = next.y() - prev.y();
|
||||
coordf_t dist = lengths[last_seg ? pt_idx+1 : next_idx] - lengths[pt_idx];
|
||||
return Point(prev.x() + (rand_len - lengths[pt_idx]) * (diff_x/dist),
|
||||
prev.y() + (rand_len - lengths[pt_idx]) * (diff_y/dist));
|
||||
|
||||
} else {
|
||||
// The polygon should be dense enough.
|
||||
return polygon.points[pt_idx];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void SeamPlacer::get_enforcers_and_blockers(size_t layer_id,
|
||||
const Polygon& polygon,
|
||||
size_t po_idx,
|
||||
std::vector<size_t>& enforcers_idxs,
|
||||
std::vector<size_t>& blockers_idxs) const
|
||||
{
|
||||
enforcers_idxs.clear();
|
||||
blockers_idxs.clear();
|
||||
|
||||
auto is_inside = [](const Point& pt,
|
||||
const CustomTrianglesPerLayer& custom_data) -> bool {
|
||||
assert(! custom_data.polys.empty());
|
||||
// Now ask the AABB tree which polygons we should check and check them.
|
||||
std::vector<size_t> candidates;
|
||||
AABBTreeIndirect::get_candidate_idxs(custom_data.tree, pt, candidates);
|
||||
if (! candidates.empty())
|
||||
for (size_t idx : candidates)
|
||||
if (custom_data.polys[idx].contains(pt))
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
if (! m_enforcers[po_idx].empty()) {
|
||||
const CustomTrianglesPerLayer& enforcers = m_enforcers[po_idx][layer_id];
|
||||
if (! enforcers.polys.empty()) {
|
||||
for (size_t i=0; i<polygon.points.size(); ++i) {
|
||||
if (is_inside(polygon.points[i], enforcers))
|
||||
enforcers_idxs.emplace_back(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! m_blockers[po_idx].empty()) {
|
||||
const CustomTrianglesPerLayer& blockers = m_blockers[po_idx][layer_id];
|
||||
if (! blockers.polys.empty()) {
|
||||
for (size_t i=0; i<polygon.points.size(); ++i) {
|
||||
if (is_inside(polygon.points[i], blockers))
|
||||
blockers_idxs.emplace_back(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Go through the polygon, identify points inside support enforcers and return
|
||||
// indices of points in the middle of each enforcer (measured along the contour).
|
||||
static std::vector<size_t> find_enforcer_centers(const Polygon& polygon,
|
||||
const std::vector<float>& lengths,
|
||||
const std::vector<size_t>& enforcers_idxs)
|
||||
{
|
||||
std::vector<size_t> out;
|
||||
assert(polygon.points.size()+1 == lengths.size());
|
||||
assert(std::is_sorted(enforcers_idxs.begin(), enforcers_idxs.end()));
|
||||
if (polygon.size() < 2 || enforcers_idxs.empty())
|
||||
return out;
|
||||
|
||||
auto get_center_idx = [&lengths](size_t start_idx, size_t end_idx) -> size_t {
|
||||
assert(end_idx >= start_idx);
|
||||
if (start_idx == end_idx)
|
||||
return start_idx;
|
||||
float t_c = lengths[start_idx] + 0.5f * (lengths[end_idx] - lengths[start_idx]);
|
||||
auto it = std::lower_bound(lengths.begin() + start_idx, lengths.begin() + end_idx, t_c);
|
||||
int ret = it - lengths.begin();
|
||||
return ret;
|
||||
};
|
||||
|
||||
int last_enforcer_start_idx = enforcers_idxs.front();
|
||||
bool first_pt_in_list = enforcers_idxs.front() != 0;
|
||||
bool last_pt_in_list = enforcers_idxs.back() == polygon.points.size() - 1;
|
||||
bool wrap_around = last_pt_in_list && first_pt_in_list;
|
||||
|
||||
for (size_t i=0; i<enforcers_idxs.size(); ++i) {
|
||||
if (i != enforcers_idxs.size() - 1) {
|
||||
if (enforcers_idxs[i+1] != enforcers_idxs[i] + 1) {
|
||||
// i is last point of current enforcer
|
||||
out.push_back(get_center_idx(last_enforcer_start_idx, enforcers_idxs[i]));
|
||||
last_enforcer_start_idx = enforcers_idxs[i+1];
|
||||
}
|
||||
} else {
|
||||
if (! wrap_around) {
|
||||
// we can safely use the last enforcer point.
|
||||
out.push_back(get_center_idx(last_enforcer_start_idx, enforcers_idxs[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wrap_around) {
|
||||
// Update first center already found.
|
||||
if (out.empty()) {
|
||||
// Probably an enforcer around the whole contour. Return nothing.
|
||||
return out;
|
||||
}
|
||||
|
||||
// find last point of the enforcer at the beginning:
|
||||
size_t idx = 0;
|
||||
while (enforcers_idxs[idx]+1 == enforcers_idxs[idx+1])
|
||||
++idx;
|
||||
|
||||
float t_s = lengths[last_enforcer_start_idx];
|
||||
float t_e = lengths[idx];
|
||||
float half_dist = 0.5f * (t_e + lengths.back() - t_s);
|
||||
float t_c = (half_dist > t_e) ? t_s + half_dist : t_e - half_dist;
|
||||
|
||||
auto it = std::lower_bound(lengths.begin(), lengths.end(), t_c);
|
||||
out[0] = it - lengths.begin();
|
||||
if (out[0] == lengths.size() - 1)
|
||||
--out[0];
|
||||
assert(out[0] < lengths.size() - 1);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void SeamPlacer::apply_custom_seam(const Polygon& polygon, size_t po_idx,
|
||||
std::vector<float>& penalties,
|
||||
const std::vector<float>& lengths,
|
||||
int layer_id, SeamPosition seam_position) const
|
||||
{
|
||||
if (! is_custom_seam_on_layer(layer_id, po_idx))
|
||||
return;
|
||||
|
||||
std::vector<size_t> enforcers_idxs;
|
||||
std::vector<size_t> blockers_idxs;
|
||||
this->get_enforcers_and_blockers(layer_id, polygon, po_idx, enforcers_idxs, blockers_idxs);
|
||||
|
||||
for (size_t i : enforcers_idxs) {
|
||||
assert(i < penalties.size());
|
||||
penalties[i] -= float(ENFORCER_BLOCKER_PENALTY);
|
||||
}
|
||||
for (size_t i : blockers_idxs) {
|
||||
assert(i < penalties.size());
|
||||
penalties[i] += float(ENFORCER_BLOCKER_PENALTY);
|
||||
}
|
||||
if (seam_position == spAligned) {
|
||||
std::vector<size_t> enf_centers = find_enforcer_centers(polygon, lengths, enforcers_idxs);
|
||||
for (size_t idx : enf_centers) {
|
||||
assert(idx < penalties.size());
|
||||
penalties[idx] += ENFORCER_CENTER_PENALTY;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
std::ostringstream os;
|
||||
os << std::setw(3) << std::setfill('0') << layer_id;
|
||||
int a = scale_(30.);
|
||||
SVG svg("custom_seam" + os.str() + ".svg", BoundingBox(Point(-a, -a), Point(a, a)));
|
||||
if (! m_enforcers[po_idx].empty())
|
||||
svg.draw(m_enforcers[po_idx][layer_id].polys, "blue");
|
||||
if (! m_blockers[po_idx].empty())
|
||||
svg.draw(m_blockers[po_idx][layer_id].polys, "red");
|
||||
|
||||
if (! blockers_idxs.empty()) {
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
size_t min_idx = std::min_element(penalties.begin(), penalties.end()) - penalties.begin();
|
||||
|
||||
for (size_t i=0; i<polygon.points.size(); ++i) {
|
||||
std::string fill;
|
||||
coord_t size = 5e5;
|
||||
if (min_idx == i)
|
||||
fill = "yellow";
|
||||
else
|
||||
fill = (std::find(blockers_idxs.begin(), blockers_idxs.end(), i) != blockers_idxs.end() ? "green" : "black");
|
||||
if (i != 0)
|
||||
svg.draw(polygon.points[i], fill, size);
|
||||
else
|
||||
svg.draw(polygon.points[i], "red", 5e5);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::optional<Point> SeamHistory::get_last_seam(const PrintObject* po, size_t layer_id, const BoundingBox& island_bb)
|
||||
{
|
||||
assert(layer_id >= m_layer_id || layer_id == 0);
|
||||
if (layer_id != m_layer_id) {
|
||||
// Get seam was called for different layer than last time.
|
||||
if (layer_id == 0) // seq printing
|
||||
m_data_this_layer.clear();
|
||||
m_data_last_layer = m_data_this_layer;
|
||||
m_data_this_layer.clear();
|
||||
m_layer_id = layer_id;
|
||||
}
|
||||
|
||||
std::optional<Point> out;
|
||||
|
||||
auto seams_it = m_data_last_layer.find(po);
|
||||
if (seams_it == m_data_last_layer.end())
|
||||
return out;
|
||||
|
||||
const std::vector<SeamPoint>& seam_data_po = seams_it->second;
|
||||
|
||||
// Find a bounding-box on the last layer that is close to one we see now.
|
||||
double min_score = std::numeric_limits<double>::max();
|
||||
for (const SeamPoint& sp : seam_data_po) {
|
||||
const BoundingBox& bb = sp.m_island_bb;
|
||||
|
||||
if (! bb.overlap(island_bb)) {
|
||||
// This bb does not even overlap. It is likely unrelated.
|
||||
continue;
|
||||
}
|
||||
|
||||
double score = std::pow(bb.min(0) - island_bb.min(0), 2.)
|
||||
+ std::pow(bb.min(1) - island_bb.min(1), 2.)
|
||||
+ std::pow(bb.max(0) - island_bb.max(0), 2.)
|
||||
+ std::pow(bb.max(1) - island_bb.max(1), 2.);
|
||||
|
||||
if (score < min_score) {
|
||||
min_score = score;
|
||||
out = sp.m_pos;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void SeamHistory::add_seam(const PrintObject* po, const Point& pos, const BoundingBox& island_bb)
|
||||
{
|
||||
m_data_this_layer[po].push_back({pos, island_bb});;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void SeamHistory::clear()
|
||||
{
|
||||
m_layer_id = 0;
|
||||
m_data_last_layer.clear();
|
||||
m_data_this_layer.clear();
|
||||
}
|
||||
|
||||
|
||||
}
|
134
src/libslic3r/GCode/SeamPlacer.hpp
Normal file
134
src/libslic3r/GCode/SeamPlacer.hpp
Normal file
|
@ -0,0 +1,134 @@
|
|||
#ifndef libslic3r_SeamPlacer_hpp_
|
||||
#define libslic3r_SeamPlacer_hpp_
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "libslic3r/ExtrusionEntity.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/AABBTreeIndirect.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class PrintObject;
|
||||
class ExtrusionLoop;
|
||||
class Print;
|
||||
class Layer;
|
||||
namespace EdgeGrid { class Grid; }
|
||||
|
||||
|
||||
class SeamHistory {
|
||||
public:
|
||||
SeamHistory() { clear(); }
|
||||
std::optional<Point> get_last_seam(const PrintObject* po, size_t layer_id, const BoundingBox& island_bb);
|
||||
void add_seam(const PrintObject* po, const Point& pos, const BoundingBox& island_bb);
|
||||
void clear();
|
||||
|
||||
private:
|
||||
struct SeamPoint {
|
||||
Point m_pos;
|
||||
BoundingBox m_island_bb;
|
||||
};
|
||||
|
||||
std::map<const PrintObject*, std::vector<SeamPoint>> m_data_last_layer;
|
||||
std::map<const PrintObject*, std::vector<SeamPoint>> m_data_this_layer;
|
||||
size_t m_layer_id;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class SeamPlacer {
|
||||
public:
|
||||
void init(const Print& print);
|
||||
|
||||
// When perimeters are printed, first call this function with the respective
|
||||
// external perimeter. SeamPlacer will find a location for its seam and remember it.
|
||||
// Subsequent calls to get_seam will return this position.
|
||||
|
||||
|
||||
void plan_perimeters(const std::vector<const ExtrusionEntity*> perimeters,
|
||||
const Layer& layer, SeamPosition seam_position,
|
||||
Point last_pos, coordf_t nozzle_dmr, const PrintObject* po,
|
||||
const EdgeGrid::Grid* lower_layer_edge_grid);
|
||||
|
||||
void place_seam(ExtrusionLoop& loop, const Point& last_pos, bool external_first, double nozzle_diameter,
|
||||
const EdgeGrid::Grid* lower_layer_edge_grid);
|
||||
|
||||
|
||||
using TreeType = AABBTreeIndirect::Tree<2, coord_t>;
|
||||
using AlignedBoxType = Eigen::AlignedBox<TreeType::CoordType, TreeType::NumDimensions>;
|
||||
|
||||
private:
|
||||
|
||||
// When given an external perimeter (!), returns the seam.
|
||||
Point calculate_seam(const Layer& layer, const SeamPosition seam_position,
|
||||
const ExtrusionLoop& loop, coordf_t nozzle_dmr, const PrintObject* po,
|
||||
const EdgeGrid::Grid* lower_layer_edge_grid, Point last_pos);
|
||||
|
||||
struct CustomTrianglesPerLayer {
|
||||
Polygons polys;
|
||||
TreeType tree;
|
||||
};
|
||||
|
||||
// Just a cache to save some lookups.
|
||||
const Layer* m_last_layer_po = nullptr;
|
||||
coordf_t m_last_print_z = -1.;
|
||||
const PrintObject* m_last_po = nullptr;
|
||||
|
||||
struct SeamPoint {
|
||||
Point pt;
|
||||
bool precalculated = false;
|
||||
bool external = false;
|
||||
const Layer* layer = nullptr;
|
||||
SeamPosition seam_position;
|
||||
const PrintObject* po = nullptr;
|
||||
};
|
||||
std::vector<SeamPoint> m_plan;
|
||||
size_t m_plan_idx;
|
||||
|
||||
std::vector<std::vector<CustomTrianglesPerLayer>> m_enforcers;
|
||||
std::vector<std::vector<CustomTrianglesPerLayer>> m_blockers;
|
||||
std::vector<const PrintObject*> m_po_list;
|
||||
|
||||
//std::map<const PrintObject*, Point> m_last_seam_position;
|
||||
SeamHistory m_seam_history;
|
||||
|
||||
// Get indices of points inside enforcers and blockers.
|
||||
void get_enforcers_and_blockers(size_t layer_id,
|
||||
const Polygon& polygon,
|
||||
size_t po_id,
|
||||
std::vector<size_t>& enforcers_idxs,
|
||||
std::vector<size_t>& blockers_idxs) const;
|
||||
|
||||
// Apply penalties to points inside enforcers/blockers.
|
||||
void apply_custom_seam(const Polygon& polygon, size_t po_id,
|
||||
std::vector<float>& penalties,
|
||||
const std::vector<float>& lengths,
|
||||
int layer_id, SeamPosition seam_position) const;
|
||||
|
||||
// Return random point of a polygon. The distribution will be uniform
|
||||
// along the contour and account for enforcers and blockers.
|
||||
Point get_random_seam(size_t layer_idx, const Polygon& polygon, size_t po_id,
|
||||
bool* saw_custom = nullptr) const;
|
||||
|
||||
// Is there any enforcer/blocker on this layer?
|
||||
bool is_custom_seam_on_layer(size_t layer_id, size_t po_idx) const {
|
||||
return is_custom_enforcer_on_layer(layer_id, po_idx)
|
||||
|| is_custom_blocker_on_layer(layer_id, po_idx);
|
||||
}
|
||||
|
||||
bool is_custom_enforcer_on_layer(size_t layer_id, size_t po_idx) const {
|
||||
return (! m_enforcers.at(po_idx).empty() && ! m_enforcers.at(po_idx)[layer_id].polys.empty());
|
||||
}
|
||||
|
||||
bool is_custom_blocker_on_layer(size_t layer_id, size_t po_idx) const {
|
||||
return (! m_blockers.at(po_idx).empty() && ! m_blockers.at(po_idx)[layer_id].polys.empty());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif // libslic3r_SeamPlacer_hpp_
|
50
src/libslic3r/GCode/SpeedGenerator.cpp
Normal file
50
src/libslic3r/GCode/SpeedGenerator.cpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
#include "SpeedGenerator.hpp"
|
||||
#include "libslic3r/ExtrusionEntity.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
#define BOOST_SPIRIT_THREADSAFE
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/date_time.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
SpeedGenerator::SpeedGenerator() {
|
||||
// default is 100 speed
|
||||
for (int i = 0; i < 6; i++) {
|
||||
speed_table[i] = 100;
|
||||
}
|
||||
std::string config_file = resources_dir() + "/PerimeterSpeedConfig.json";
|
||||
std::string encoded_path = encode_path(config_file.c_str());
|
||||
boost::property_tree::read_json<boost::property_tree::ptree>(encoded_path, root);
|
||||
if (root.count("speed_table")) {
|
||||
int i = 0;
|
||||
auto array6 = root.get_child("speed_table");
|
||||
for (auto pos = array6.begin(); pos != array6.end() && i < 6; pos++, i++)
|
||||
speed_table[i] = pos->second.get_value<int>();
|
||||
}
|
||||
}
|
||||
|
||||
double SpeedGenerator::calculate_speed(const ExtrusionPath& path, double max_speed, double min_speed) {
|
||||
// limit the speed in case of F0 generated in gcode when user set 0 speed in UI
|
||||
// which cause printer stopped. 1mm/s is slow enough and can make printer not really stopped.
|
||||
max_speed = max_speed < 1 ? 1 : max_speed;
|
||||
min_speed = min_speed < 1 ? 1 : min_speed;
|
||||
// switch min and max speed if user set the max speed to be slower than min_speed
|
||||
if (max_speed < min_speed) {
|
||||
double temp = max_speed;
|
||||
max_speed = min_speed;
|
||||
min_speed = temp;
|
||||
}
|
||||
speed_table[0] = max_speed;
|
||||
|
||||
int overhang_degree = path.get_overhang_degree();
|
||||
assert(overhang_degree >= 0 && overhang_degree <= 6);
|
||||
double speed = (double)speed_table[overhang_degree];
|
||||
speed = std::max(speed, min_speed);
|
||||
speed = std::min(speed, max_speed);
|
||||
return speed;
|
||||
}
|
||||
|
||||
}
|
22
src/libslic3r/GCode/SpeedGenerator.hpp
Normal file
22
src/libslic3r/GCode/SpeedGenerator.hpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
#ifndef libslic3r_SpeedGenerator_hpp_
|
||||
#define libslic3r_SpeedGenerator_hpp_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExtrusionPath;
|
||||
|
||||
class SpeedGenerator {
|
||||
public:
|
||||
SpeedGenerator();
|
||||
double calculate_speed(const ExtrusionPath& path, double max_speed, double min_speed);
|
||||
|
||||
private:
|
||||
boost::property_tree::ptree root;
|
||||
int speed_table[6];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // libslic3r_SpeedGenerator_hpp_
|
97
src/libslic3r/GCode/SpiralVase.cpp
Normal file
97
src/libslic3r/GCode/SpiralVase.cpp
Normal file
|
@ -0,0 +1,97 @@
|
|||
#include "SpiralVase.hpp"
|
||||
#include "GCode.hpp"
|
||||
#include <sstream>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
std::string SpiralVase::process_layer(const std::string &gcode)
|
||||
{
|
||||
/* This post-processor relies on several assumptions:
|
||||
- all layers are processed through it, including those that are not supposed
|
||||
to be transformed, in order to update the reader with the XY positions
|
||||
- each call to this method includes a full layer, with a single Z move
|
||||
at the beginning
|
||||
- each layer is composed by suitable geometry (i.e. a single complete loop)
|
||||
- loops were not clipped before calling this method */
|
||||
|
||||
// If we're not going to modify G-code, just feed it to the reader
|
||||
// in order to update positions.
|
||||
if (! m_enabled) {
|
||||
m_reader.parse_buffer(gcode);
|
||||
return gcode;
|
||||
}
|
||||
|
||||
// Get total XY length for this layer by summing all extrusion moves.
|
||||
float total_layer_length = 0;
|
||||
float layer_height = 0;
|
||||
float z = 0.f;
|
||||
|
||||
{
|
||||
//FIXME Performance warning: This copies the GCodeConfig of the reader.
|
||||
GCodeReader r = m_reader; // clone
|
||||
bool set_z = false;
|
||||
r.parse_buffer(gcode, [&total_layer_length, &layer_height, &z, &set_z]
|
||||
(GCodeReader &reader, const GCodeReader::GCodeLine &line) {
|
||||
if (line.cmd_is("G1")) {
|
||||
if (line.extruding(reader)) {
|
||||
total_layer_length += line.dist_XY(reader);
|
||||
} else if (line.has(Z)) {
|
||||
layer_height += line.dist_Z(reader);
|
||||
if (!set_z) {
|
||||
z = line.new_Z(reader);
|
||||
set_z = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Remove layer height from initial Z.
|
||||
z -= layer_height;
|
||||
|
||||
std::string new_gcode;
|
||||
//FIXME Tapering of the transition layer only works reliably with relative extruder distances.
|
||||
// For absolute extruder distances it will be switched off.
|
||||
// Tapering the absolute extruder distances requires to process every extrusion value after the first transition
|
||||
// layer.
|
||||
bool transition = m_transition_layer && RELATIVE_E_AXIS;
|
||||
float layer_height_factor = layer_height / total_layer_length;
|
||||
float len = 0.f;
|
||||
m_reader.parse_buffer(gcode, [&new_gcode, &z, total_layer_length, layer_height_factor, transition, &len]
|
||||
(GCodeReader &reader, GCodeReader::GCodeLine line) {
|
||||
if (line.cmd_is("G1")) {
|
||||
if (line.has_z()) {
|
||||
// If this is the initial Z move of the layer, replace it with a
|
||||
// (redundant) move to the last Z of previous layer.
|
||||
line.set(reader, Z, z);
|
||||
new_gcode += line.raw() + '\n';
|
||||
return;
|
||||
} else {
|
||||
float dist_XY = line.dist_XY(reader);
|
||||
if (dist_XY > 0) {
|
||||
// horizontal move
|
||||
if (line.extruding(reader)) {
|
||||
len += dist_XY;
|
||||
line.set(reader, Z, z + len * layer_height_factor);
|
||||
if (transition && line.has(E))
|
||||
// Transition layer, modulate the amount of extrusion from zero to the final value.
|
||||
line.set(reader, E, line.value(E) * len / total_layer_length);
|
||||
new_gcode += line.raw() + '\n';
|
||||
}
|
||||
return;
|
||||
|
||||
/* Skip travel moves: the move to first perimeter point will
|
||||
cause a visible seam when loops are not aligned in XY; by skipping
|
||||
it we blend the first loop move in the XY plane (although the smoothness
|
||||
of such blend depend on how long the first segment is; maybe we should
|
||||
enforce some minimum length?). */
|
||||
}
|
||||
}
|
||||
}
|
||||
new_gcode += line.raw() + '\n';
|
||||
});
|
||||
|
||||
return new_gcode;
|
||||
}
|
||||
|
||||
}
|
37
src/libslic3r/GCode/SpiralVase.hpp
Normal file
37
src/libslic3r/GCode/SpiralVase.hpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
#ifndef slic3r_SpiralVase_hpp_
|
||||
#define slic3r_SpiralVase_hpp_
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../GCodeReader.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class SpiralVase {
|
||||
public:
|
||||
SpiralVase(const PrintConfig &config) : m_config(config)
|
||||
{
|
||||
//BBS
|
||||
//m_reader.z() = (float)m_config.z_offset;
|
||||
m_reader.z() = 0.0f;
|
||||
m_reader.apply_config(m_config);
|
||||
};
|
||||
|
||||
void enable(bool en) {
|
||||
m_transition_layer = en && ! m_enabled;
|
||||
m_enabled = en;
|
||||
}
|
||||
|
||||
std::string process_layer(const std::string &gcode);
|
||||
|
||||
private:
|
||||
const PrintConfig &m_config;
|
||||
GCodeReader m_reader;
|
||||
|
||||
bool m_enabled = false;
|
||||
// First spiral vase layer. Layer height has to be ramped up from zero to the target layer height.
|
||||
bool m_transition_layer = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // slic3r_SpiralVase_hpp_
|
31
src/libslic3r/GCode/ThumbnailData.cpp
Normal file
31
src/libslic3r/GCode/ThumbnailData.cpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
#include "ThumbnailData.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void ThumbnailData::set(unsigned int w, unsigned int h)
|
||||
{
|
||||
if ((w == 0) || (h == 0))
|
||||
return;
|
||||
|
||||
if ((width != w) || (height != h))
|
||||
{
|
||||
width = w;
|
||||
height = h;
|
||||
// defaults to white texture
|
||||
pixels = std::vector<unsigned char>(width * height * 4, 255);
|
||||
}
|
||||
}
|
||||
|
||||
void ThumbnailData::reset()
|
||||
{
|
||||
width = 0;
|
||||
height = 0;
|
||||
pixels.clear();
|
||||
}
|
||||
|
||||
bool ThumbnailData::is_valid() const
|
||||
{
|
||||
return (width != 0) && (height != 0) && ((unsigned int)pixels.size() == 4 * width * height);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
118
src/libslic3r/GCode/ThumbnailData.hpp
Normal file
118
src/libslic3r/GCode/ThumbnailData.hpp
Normal file
|
@ -0,0 +1,118 @@
|
|||
#ifndef slic3r_ThumbnailData_hpp_
|
||||
#define slic3r_ThumbnailData_hpp_
|
||||
|
||||
#include <vector>
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
//BBS: thumbnail_size in gcode file
|
||||
static std::vector<Vec2d> THUMBNAIL_SIZE = { Vec2d(50, 50) };
|
||||
|
||||
struct ThumbnailData
|
||||
{
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
std::vector<unsigned char> pixels;
|
||||
|
||||
ThumbnailData() { reset(); }
|
||||
void set(unsigned int w, unsigned int h);
|
||||
void reset();
|
||||
|
||||
bool is_valid() const;
|
||||
void load_from(ThumbnailData &data) {
|
||||
this->set(data.width, data.height);
|
||||
pixels = data.pixels;
|
||||
}
|
||||
};
|
||||
|
||||
//BBS: add plate id into thumbnail render logic
|
||||
using ThumbnailsList = std::vector<ThumbnailData>;
|
||||
|
||||
struct ThumbnailsParams
|
||||
{
|
||||
const Vec2ds sizes;
|
||||
bool printable_only;
|
||||
bool parts_only;
|
||||
bool show_bed;
|
||||
bool transparent_background;
|
||||
int plate_id;
|
||||
};
|
||||
|
||||
typedef std::function<ThumbnailsList(const ThumbnailsParams&)> ThumbnailsGeneratorCallback;
|
||||
|
||||
struct BBoxData
|
||||
{
|
||||
int id; // object id
|
||||
std::vector<coordf_t> bbox; // first layer bounding box: min.{x,y}, max.{x,y}
|
||||
float area; // first layer area
|
||||
float layer_height;
|
||||
std::string name;
|
||||
void to_json(nlohmann::json& j) const{
|
||||
j = nlohmann::json{
|
||||
{"id",id},
|
||||
{"bbox",bbox},
|
||||
{"area",area},
|
||||
{"layer_height",layer_height},
|
||||
{"name",name}
|
||||
};
|
||||
}
|
||||
void from_json(const nlohmann::json& j) {
|
||||
j.at("id").get_to(id);
|
||||
j.at("bbox").get_to(bbox);
|
||||
j.at("area").get_to(area);
|
||||
j.at("layer_height").get_to(layer_height);
|
||||
j.at("name").get_to(name);
|
||||
}
|
||||
};
|
||||
|
||||
struct PlateBBoxData
|
||||
{
|
||||
std::vector<coordf_t> bbox_all; // total bounding box of all objects including brim
|
||||
std::vector<BBoxData> bbox_objs; // BBoxData of seperate object
|
||||
std::vector<int> filament_ids; // filament id used in curr plate
|
||||
std::vector<std::string> filament_colors;
|
||||
bool is_seq_print = false;
|
||||
int first_extruder = 0;
|
||||
float nozzle_diameter = 0.4;
|
||||
// version 1: use view type ColorPrint (filament color)
|
||||
// version 2: use view type FilamentId (filament id)
|
||||
int version = 2;
|
||||
|
||||
void to_json(nlohmann::json& j) const{
|
||||
j = nlohmann::json{ {"bbox_all",bbox_all} };
|
||||
j["filament_ids"] = filament_ids;
|
||||
j["filament_colors"] = filament_colors;
|
||||
j["is_seq_print"] = is_seq_print;
|
||||
j["first_extruder"] = first_extruder;
|
||||
j["nozzle_diameter"] = nozzle_diameter;
|
||||
j["version"] = version;
|
||||
for (const auto& bbox : bbox_objs) {
|
||||
nlohmann::json j_bbox;
|
||||
bbox.to_json(j_bbox);
|
||||
j["bbox_objects"].push_back(j_bbox);
|
||||
}
|
||||
}
|
||||
void from_json(const nlohmann::json& j) {
|
||||
j.at("bbox_all").get_to(bbox_all);
|
||||
j.at("filament_ids").get_to(filament_ids);
|
||||
j.at("filament_colors").get_to(filament_colors);
|
||||
j.at("is_seq_print").get_to(is_seq_print);
|
||||
j.at("first_extruder").get_to(first_extruder);
|
||||
j.at("nozzle_diameter").get_to(nozzle_diameter);
|
||||
j.at("version").get_to(version);
|
||||
for (auto& bbox_j : j.at("bbox_objects")) {
|
||||
BBoxData bbox_data;
|
||||
bbox_data.from_json(bbox_j);
|
||||
bbox_objs.push_back(bbox_data);
|
||||
}
|
||||
}
|
||||
bool is_valid() const {
|
||||
return !bbox_objs.empty();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_ThumbnailData_hpp_
|
1013
src/libslic3r/GCode/ToolOrdering.cpp
Normal file
1013
src/libslic3r/GCode/ToolOrdering.cpp
Normal file
File diff suppressed because it is too large
Load diff
191
src/libslic3r/GCode/ToolOrdering.hpp
Normal file
191
src/libslic3r/GCode/ToolOrdering.hpp
Normal file
|
@ -0,0 +1,191 @@
|
|||
// Ordering of the tools to minimize tool switches.
|
||||
|
||||
#ifndef slic3r_ToolOrdering_hpp_
|
||||
#define slic3r_ToolOrdering_hpp_
|
||||
|
||||
#include "../libslic3r.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Print;
|
||||
class PrintObject;
|
||||
class LayerTools;
|
||||
namespace CustomGCode { struct Item; }
|
||||
class PrintRegion;
|
||||
|
||||
// Object of this class holds information about whether an extrusion is printed immediately
|
||||
// after a toolchange (as part of infill/perimeter wiping) or not. One extrusion can be a part
|
||||
// of several copies - this has to be taken into account.
|
||||
class WipingExtrusions
|
||||
{
|
||||
public:
|
||||
bool is_anything_overridden() const { // if there are no overrides, all the agenda can be skipped - this function can tell us if that's the case
|
||||
return something_overridden;
|
||||
}
|
||||
|
||||
// When allocating extruder overrides of an object's ExtrusionEntity, overrides for maximum 3 copies are allocated in place.
|
||||
typedef boost::container::small_vector<int32_t, 3> ExtruderPerCopy;
|
||||
|
||||
// This is called from GCode::process_layer - see implementation for further comments:
|
||||
const ExtruderPerCopy* get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies);
|
||||
|
||||
// This function goes through all infill entities, decides which ones will be used for wiping and
|
||||
// marks them by the extruder id. Returns volume that remains to be wiped on the wipe tower:
|
||||
float mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe);
|
||||
|
||||
void ensure_perimeters_infills_order(const Print& print);
|
||||
|
||||
bool is_overriddable(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const;
|
||||
bool is_overriddable_and_mark(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) {
|
||||
bool out = this->is_overriddable(ee, print_config, object, region);
|
||||
this->something_overridable |= out;
|
||||
return out;
|
||||
}
|
||||
|
||||
void set_layer_tools_ptr(const LayerTools* lt) { m_layer_tools = lt; }
|
||||
|
||||
private:
|
||||
int first_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const;
|
||||
int last_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const;
|
||||
|
||||
// This function is called from mark_wiping_extrusions and sets extruder that it should be printed with (-1 .. as usual)
|
||||
void set_extruder_override(const ExtrusionEntity* entity, size_t copy_id, int extruder, size_t num_of_copies);
|
||||
|
||||
// Returns true in case that entity is not printed with its usual extruder for a given copy:
|
||||
bool is_entity_overridden(const ExtrusionEntity* entity, size_t copy_id) const {
|
||||
auto it = entity_map.find(entity);
|
||||
return it == entity_map.end() ? false : it->second[copy_id] != -1;
|
||||
}
|
||||
|
||||
std::map<const ExtrusionEntity*, ExtruderPerCopy> entity_map; // to keep track of who prints what
|
||||
bool something_overridable = false;
|
||||
bool something_overridden = false;
|
||||
const LayerTools* m_layer_tools = nullptr; // so we know which LayerTools object this belongs to
|
||||
};
|
||||
|
||||
class LayerTools
|
||||
{
|
||||
public:
|
||||
LayerTools(const coordf_t z) : print_z(z) {}
|
||||
|
||||
// Changing these operators to epsilon version can make a problem in cases where support and object layers get close to each other.
|
||||
// In case someone tries to do it, make sure you know what you're doing and test it properly (slice multiple objects at once with supports).
|
||||
bool operator< (const LayerTools &rhs) const { return print_z < rhs.print_z; }
|
||||
bool operator==(const LayerTools &rhs) const { return print_z == rhs.print_z; }
|
||||
|
||||
bool is_extruder_order(unsigned int a, unsigned int b) const;
|
||||
bool has_extruder(unsigned int extruder) const { return std::find(this->extruders.begin(), this->extruders.end(), extruder) != this->extruders.end(); }
|
||||
|
||||
// Return a zero based extruder from the region, or extruder_override if overriden.
|
||||
unsigned int wall_filament(const PrintRegion ®ion) const;
|
||||
unsigned int sparse_infill_filament(const PrintRegion ®ion) const;
|
||||
unsigned int solid_infill_filament(const PrintRegion ®ion) const;
|
||||
// Returns a zero based extruder this eec should be printed with, according to PrintRegion config or extruder_override if overriden.
|
||||
unsigned int extruder(const ExtrusionEntityCollection &extrusions, const PrintRegion ®ion) const;
|
||||
|
||||
coordf_t print_z = 0.;
|
||||
bool has_object = false;
|
||||
bool has_support = false;
|
||||
// Zero based extruder IDs, ordered to minimize tool switches.
|
||||
std::vector<unsigned int> extruders;
|
||||
// If per layer extruder switches are inserted by the G-code preview slider, this value contains the new (1 based) extruder, with which the whole object layer is being printed with.
|
||||
// If not overriden, it is set to 0.
|
||||
unsigned int extruder_override = 0;
|
||||
// Should a skirt be printed at this layer?
|
||||
// Layers are marked for infinite skirt aka draft shield. Not all the layers have to be printed.
|
||||
bool has_skirt = false;
|
||||
// Will there be anything extruded on this layer for the wipe tower?
|
||||
// Due to the support layers possibly interleaving the object layers,
|
||||
// wipe tower will be disabled for some support only layers.
|
||||
bool has_wipe_tower = false;
|
||||
// Number of wipe tower partitions to support the required number of tool switches
|
||||
// and to support the wipe tower partitions above this one.
|
||||
size_t wipe_tower_partitions = 0;
|
||||
coordf_t wipe_tower_layer_height = 0.;
|
||||
// Custom G-code (color change, extruder switch, pause) to be performed before this layer starts to print.
|
||||
const CustomGCode::Item *custom_gcode = nullptr;
|
||||
|
||||
WipingExtrusions& wiping_extrusions() {
|
||||
m_wiping_extrusions.set_layer_tools_ptr(this);
|
||||
return m_wiping_extrusions;
|
||||
}
|
||||
|
||||
private:
|
||||
// This object holds list of extrusion that will be used for extruder wiping
|
||||
WipingExtrusions m_wiping_extrusions;
|
||||
};
|
||||
|
||||
class ToolOrdering
|
||||
{
|
||||
public:
|
||||
ToolOrdering() = default;
|
||||
|
||||
// For the use case when each object is printed separately
|
||||
// (print->config().print_sequence == PrintSequence::ByObject is true).
|
||||
ToolOrdering(const PrintObject &object, unsigned int first_extruder, bool prime_multi_material = false);
|
||||
|
||||
// For the use case when all objects are printed at once.
|
||||
// (print->config().print_sequence == PrintSequence::ByObject is false).
|
||||
ToolOrdering(const Print& print, unsigned int first_extruder, bool prime_multi_material = false);
|
||||
|
||||
void clear() { m_layer_tools.clear(); }
|
||||
|
||||
// Only valid for non-sequential print:
|
||||
// Assign a pointer to a custom G-code to the respective ToolOrdering::LayerTools.
|
||||
// Ignore color changes, which are performed on a layer and for such an extruder, that the extruder will not be printing above that layer.
|
||||
// If multiple events are planned over a span of a single layer, use the last one.
|
||||
void assign_custom_gcodes(const Print &print);
|
||||
|
||||
// Get the first extruder printing, including the extruder priming areas, returns -1 if there is no layer printed.
|
||||
unsigned int first_extruder() const { return m_first_printing_extruder; }
|
||||
|
||||
// Get the first extruder printing the layer_tools, returns -1 if there is no layer printed.
|
||||
unsigned int last_extruder() const { return m_last_printing_extruder; }
|
||||
|
||||
// For a multi-material print, the printing extruders are ordered in the order they shall be primed.
|
||||
const std::vector<unsigned int>& all_extruders() const { return m_all_printing_extruders; }
|
||||
|
||||
// Find LayerTools with the closest print_z.
|
||||
const LayerTools& tools_for_layer(coordf_t print_z) const;
|
||||
LayerTools& tools_for_layer(coordf_t print_z) { return const_cast<LayerTools&>(std::as_const(*this).tools_for_layer(print_z)); }
|
||||
|
||||
const LayerTools& front() const { return m_layer_tools.front(); }
|
||||
const LayerTools& back() const { return m_layer_tools.back(); }
|
||||
std::vector<LayerTools>::const_iterator begin() const { return m_layer_tools.begin(); }
|
||||
std::vector<LayerTools>::const_iterator end() const { return m_layer_tools.end(); }
|
||||
bool empty() const { return m_layer_tools.empty(); }
|
||||
std::vector<LayerTools>& layer_tools() { return m_layer_tools; }
|
||||
bool has_wipe_tower() const { return ! m_layer_tools.empty() && m_first_printing_extruder != (unsigned int)-1 && m_layer_tools.front().wipe_tower_partitions > 0; }
|
||||
|
||||
private:
|
||||
void initialize_layers(std::vector<coordf_t> &zs);
|
||||
void collect_extruders(const PrintObject &object, const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches);
|
||||
void reorder_extruders(unsigned int last_extruder_id);
|
||||
// BBS
|
||||
void reorder_extruders(std::vector<unsigned int> tool_order_layer0);
|
||||
void fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z, coordf_t max_layer_height);
|
||||
void mark_skirt_layers(const PrintConfig &config, coordf_t max_layer_height);
|
||||
void collect_extruder_statistics(bool prime_multi_material);
|
||||
|
||||
// BBS
|
||||
std::vector<unsigned int> generate_first_layer_tool_order(const Print& print);
|
||||
std::vector<unsigned int> generate_first_layer_tool_order(const PrintObject& object);
|
||||
|
||||
std::vector<LayerTools> m_layer_tools;
|
||||
// First printing extruder, including the multi-material priming sequence.
|
||||
unsigned int m_first_printing_extruder = (unsigned int)-1;
|
||||
// Final printing extruder.
|
||||
unsigned int m_last_printing_extruder = (unsigned int)-1;
|
||||
// All extruders, which extrude some material over m_layer_tools.
|
||||
std::vector<unsigned int> m_all_printing_extruders;
|
||||
|
||||
const PrintConfig* m_print_config_ptr = nullptr;
|
||||
};
|
||||
|
||||
} // namespace SLic3r
|
||||
|
||||
#endif /* slic3r_ToolOrdering_hpp_ */
|
1511
src/libslic3r/GCode/WipeTower.cpp
Normal file
1511
src/libslic3r/GCode/WipeTower.cpp
Normal file
File diff suppressed because it is too large
Load diff
425
src/libslic3r/GCode/WipeTower.hpp
Normal file
425
src/libslic3r/GCode/WipeTower.hpp
Normal file
|
@ -0,0 +1,425 @@
|
|||
#ifndef WipeTower_
|
||||
#define WipeTower_
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
class WipeTowerWriter;
|
||||
class PrintConfig;
|
||||
enum GCodeFlavor : unsigned char;
|
||||
|
||||
|
||||
|
||||
class WipeTower
|
||||
{
|
||||
public:
|
||||
static const std::string never_skip_tag() { return "_GCODE_WIPE_TOWER_NEVER_SKIP_TAG"; }
|
||||
|
||||
// WipeTower height to minimum depth map
|
||||
static const std::map<float, float> min_depth_per_height;
|
||||
|
||||
struct Extrusion
|
||||
{
|
||||
Extrusion(const Vec2f &pos, float width, unsigned int tool) : pos(pos), width(width), tool(tool) {}
|
||||
// End position of this extrusion.
|
||||
Vec2f pos;
|
||||
// Width of a squished extrusion, corrected for the roundings of the squished extrusions.
|
||||
// This is left zero if it is a travel move.
|
||||
float width;
|
||||
// Current extruder index.
|
||||
unsigned int tool;
|
||||
};
|
||||
|
||||
struct ToolChangeResult
|
||||
{
|
||||
// Print heigh of this tool change.
|
||||
float print_z;
|
||||
float layer_height;
|
||||
// G-code section to be directly included into the output G-code.
|
||||
std::string gcode;
|
||||
// For path preview.
|
||||
std::vector<Extrusion> extrusions;
|
||||
// Initial position, at which the wipe tower starts its action.
|
||||
// At this position the extruder is loaded and there is no Z-hop applied.
|
||||
Vec2f start_pos;
|
||||
// Last point, at which the normal G-code generator of Slic3r shall continue.
|
||||
// At this position the extruder is loaded and there is no Z-hop applied.
|
||||
Vec2f end_pos;
|
||||
// Time elapsed over this tool change.
|
||||
// This is useful not only for the print time estimation, but also for the control of layer cooling.
|
||||
float elapsed_time;
|
||||
|
||||
// Is this a priming extrusion? (If so, the wipe tower rotation & translation will not be applied later)
|
||||
bool priming;
|
||||
|
||||
// Pass a polyline so that normal G-code generator can do a wipe for us.
|
||||
// The wipe cannot be done by the wipe tower because it has to pass back
|
||||
// a loaded extruder, so it would have to either do a wipe with no retraction
|
||||
// (leading to https://github.com/prusa3d/PrusaSlicer/issues/2834) or do
|
||||
// an extra retraction-unretraction pair.
|
||||
std::vector<Vec2f> wipe_path;
|
||||
|
||||
// BBS
|
||||
float purge_volume = 0.f;
|
||||
|
||||
// Initial tool
|
||||
int initial_tool;
|
||||
|
||||
// New tool
|
||||
int new_tool;
|
||||
|
||||
// BBS: in bbl filament_change_gcode, toolhead will be moved to the wipe tower automatically.
|
||||
// But if finish_layer_tcr is before tool_change_tcr, we have to travel to the wipe tower before
|
||||
// executing the gcode finish_layer_tcr.
|
||||
bool is_finish_first = false;
|
||||
|
||||
// Sum the total length of the extrusion.
|
||||
float total_extrusion_length_in_plane() {
|
||||
float e_length = 0.f;
|
||||
for (size_t i = 1; i < this->extrusions.size(); ++ i) {
|
||||
const Extrusion &e = this->extrusions[i];
|
||||
if (e.width > 0) {
|
||||
Vec2f v = e.pos - (&e - 1)->pos;
|
||||
e_length += v.norm();
|
||||
}
|
||||
}
|
||||
return e_length;
|
||||
}
|
||||
};
|
||||
|
||||
struct box_coordinates
|
||||
{
|
||||
box_coordinates(float left, float bottom, float width, float height) :
|
||||
ld(left , bottom ),
|
||||
lu(left , bottom + height),
|
||||
rd(left + width, bottom ),
|
||||
ru(left + width, bottom + height) {}
|
||||
box_coordinates(const Vec2f &pos, float width, float height) : box_coordinates(pos(0), pos(1), width, height) {}
|
||||
void translate(const Vec2f &shift) {
|
||||
ld += shift; lu += shift;
|
||||
rd += shift; ru += shift;
|
||||
}
|
||||
void translate(const float dx, const float dy) { translate(Vec2f(dx, dy)); }
|
||||
void expand(const float offset) {
|
||||
ld += Vec2f(- offset, - offset);
|
||||
lu += Vec2f(- offset, offset);
|
||||
rd += Vec2f( offset, - offset);
|
||||
ru += Vec2f( offset, offset);
|
||||
}
|
||||
void expand(const float offset_x, const float offset_y) {
|
||||
ld += Vec2f(- offset_x, - offset_y);
|
||||
lu += Vec2f(- offset_x, offset_y);
|
||||
rd += Vec2f( offset_x, - offset_y);
|
||||
ru += Vec2f( offset_x, offset_y);
|
||||
}
|
||||
Vec2f ld; // left down
|
||||
Vec2f lu; // left upper
|
||||
Vec2f rd; // right lower
|
||||
Vec2f ru; // right upper
|
||||
};
|
||||
|
||||
// Construct ToolChangeResult from current state of WipeTower and WipeTowerWriter.
|
||||
// WipeTowerWriter is moved from !
|
||||
ToolChangeResult construct_tcr(WipeTowerWriter& writer,
|
||||
bool priming,
|
||||
size_t old_tool,
|
||||
bool is_finish,
|
||||
float purge_volume) const;
|
||||
|
||||
// x -- x coordinates of wipe tower in mm ( left bottom corner )
|
||||
// y -- y coordinates of wipe tower in mm ( left bottom corner )
|
||||
// width -- width of wipe tower in mm ( default 60 mm - leave as it is )
|
||||
// wipe_area -- space available for one toolchange in mm
|
||||
// BBS: add partplate logic
|
||||
WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origin, const float wipe_volume, size_t initial_tool, const float wipe_tower_height);
|
||||
|
||||
|
||||
// Set the extruder properties.
|
||||
void set_extruder(size_t idx, const PrintConfig& config);
|
||||
|
||||
// Appends into internal structure m_plan containing info about the future wipe tower
|
||||
// to be used before building begins. The entries must be added ordered in z.
|
||||
void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume = 0.f, float prime_volume = 0.f);
|
||||
|
||||
// Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result"
|
||||
void generate(std::vector<std::vector<ToolChangeResult>> &result);
|
||||
|
||||
float get_depth() const { return m_wipe_tower_depth; }
|
||||
float get_brim_width() const { return m_wipe_tower_brim_width_real; }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Switch to a next layer.
|
||||
void set_layer(
|
||||
// Print height of this layer.
|
||||
float print_z,
|
||||
// Layer height, used to calculate extrusion the rate.
|
||||
float layer_height,
|
||||
// Maximum number of tool changes on this layer or the layers below.
|
||||
size_t max_tool_changes,
|
||||
// Is this the first layer of the print? In that case print the brim first.
|
||||
bool is_first_layer,
|
||||
// Is this the last layer of the waste tower?
|
||||
bool is_last_layer)
|
||||
{
|
||||
m_z_pos = print_z;
|
||||
m_layer_height = layer_height;
|
||||
m_depth_traversed = 0.f;
|
||||
m_current_layer_finished = false;
|
||||
//m_current_shape = (! is_first_layer && m_current_shape == SHAPE_NORMAL) ? SHAPE_REVERSED : SHAPE_NORMAL;
|
||||
m_current_shape = SHAPE_NORMAL;
|
||||
if (is_first_layer) {
|
||||
m_num_layer_changes = 0;
|
||||
m_num_tool_changes = 0;
|
||||
} else
|
||||
++ m_num_layer_changes;
|
||||
|
||||
// Calculate extrusion flow from desired line width, nozzle diameter, filament diameter and layer_height:
|
||||
m_extrusion_flow = extrusion_flow(layer_height);
|
||||
|
||||
// Advance m_layer_info iterator, making sure we got it right
|
||||
while (!m_plan.empty() && m_layer_info->z < print_z - WT_EPSILON && m_layer_info+1 != m_plan.end())
|
||||
++m_layer_info;
|
||||
}
|
||||
|
||||
// Return the wipe tower position.
|
||||
const Vec2f& position() const { return m_wipe_tower_pos; }
|
||||
// Return the wipe tower width.
|
||||
float width() const { return m_wipe_tower_width; }
|
||||
// The wipe tower is finished, there should be no more tool changes or wipe tower prints.
|
||||
bool finished() const { return m_max_color_changes == 0; }
|
||||
|
||||
// Returns gcode to prime the nozzles at the front edge of the print bed.
|
||||
std::vector<ToolChangeResult> prime(
|
||||
// print_z of the first layer.
|
||||
float initial_layer_print_height,
|
||||
// Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object.
|
||||
const std::vector<unsigned int> &tools,
|
||||
// If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower.
|
||||
// If false, the last priming are will be large enough to wipe the last extruder sufficiently.
|
||||
bool last_wipe_inside_wipe_tower);
|
||||
|
||||
// Returns gcode for a toolchange and a final print head position.
|
||||
// On the first layer, extrude a brim around the future wipe tower first.
|
||||
// BBS
|
||||
ToolChangeResult tool_change(size_t new_tool, bool extrude_perimeter = false);
|
||||
|
||||
// Fill the unfilled space with a sparse infill.
|
||||
// Call this method only if layer_finished() is false.
|
||||
ToolChangeResult finish_layer(bool extruder_perimeter = true);
|
||||
|
||||
// Is the current layer finished?
|
||||
bool layer_finished() const {
|
||||
return m_current_layer_finished;
|
||||
}
|
||||
|
||||
std::vector<float> get_used_filament() const { return m_used_filament_length; }
|
||||
int get_number_of_toolchanges() const { return m_num_tool_changes; }
|
||||
|
||||
struct FilamentParameters {
|
||||
std::string material = "PLA";
|
||||
bool is_soluble = false;
|
||||
// BBS
|
||||
bool is_support = false;
|
||||
int nozzle_temperature = 0;
|
||||
int nozzle_temperature_initial_layer = 0;
|
||||
// BBS: remove useless config
|
||||
//float loading_speed = 0.f;
|
||||
//float loading_speed_start = 0.f;
|
||||
//float unloading_speed = 0.f;
|
||||
//float unloading_speed_start = 0.f;
|
||||
//float delay = 0.f ;
|
||||
//int cooling_moves = 0;
|
||||
//float cooling_initial_speed = 0.f;
|
||||
//float cooling_final_speed = 0.f;
|
||||
float ramming_line_width_multiplicator = 1.f;
|
||||
float ramming_step_multiplicator = 1.f;
|
||||
float max_e_speed = std::numeric_limits<float>::max();
|
||||
std::vector<float> ramming_speed;
|
||||
float nozzle_diameter;
|
||||
float filament_area;
|
||||
};
|
||||
|
||||
private:
|
||||
enum wipe_shape // A fill-in direction
|
||||
{
|
||||
SHAPE_NORMAL = 1,
|
||||
SHAPE_REVERSED = -1
|
||||
};
|
||||
|
||||
const float Width_To_Nozzle_Ratio = 1.25f; // desired line width (oval) in multiples of nozzle diameter - may not be actually neccessary to adjust
|
||||
const float WT_EPSILON = 1e-3f;
|
||||
float filament_area() const {
|
||||
return m_filpar[0].filament_area; // all extruders are assumed to have the same filament diameter at this point
|
||||
}
|
||||
|
||||
|
||||
bool m_semm = true; // Are we using a single extruder multimaterial printer?
|
||||
Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm.
|
||||
float m_wipe_tower_width; // Width of the wipe tower.
|
||||
float m_wipe_tower_depth = 0.f; // Depth of the wipe tower
|
||||
// BBS
|
||||
float m_wipe_tower_height = 0.f;
|
||||
float m_wipe_tower_brim_width = 0.f; // Width of brim (mm) from config
|
||||
float m_wipe_tower_brim_width_real = 0.f; // Width of brim (mm) after generation
|
||||
float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis)
|
||||
float m_internal_rotation = 0.f;
|
||||
float m_y_shift = 0.f; // y shift passed to writer
|
||||
float m_z_pos = 0.f; // Current Z position.
|
||||
float m_layer_height = 0.f; // Current layer height.
|
||||
size_t m_max_color_changes = 0; // Maximum number of color changes per layer.
|
||||
int m_old_temperature = -1; // To keep track of what was the last temp that we set (so we don't issue the command when not neccessary)
|
||||
float m_travel_speed = 0.f;
|
||||
float m_first_layer_speed = 0.f;
|
||||
size_t m_first_layer_idx = size_t(-1);
|
||||
|
||||
// G-code generator parameters.
|
||||
// BBS: remove useless config
|
||||
//float m_cooling_tube_retraction = 0.f;
|
||||
//float m_cooling_tube_length = 0.f;
|
||||
//float m_parking_pos_retraction = 0.f;
|
||||
//float m_extra_loading_move = 0.f;
|
||||
float m_bridging = 0.f;
|
||||
bool m_no_sparse_layers = false;
|
||||
// BBS: remove useless config
|
||||
//bool m_set_extruder_trimpot = false;
|
||||
bool m_adhesion = true;
|
||||
GCodeFlavor m_gcode_flavor;
|
||||
|
||||
// Bed properties
|
||||
enum {
|
||||
RectangularBed,
|
||||
CircularBed,
|
||||
CustomBed
|
||||
} m_bed_shape;
|
||||
float m_bed_width; // width of the bed bounding box
|
||||
Vec2f m_bed_bottom_left; // bottom-left corner coordinates (for rectangular beds)
|
||||
|
||||
float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill.
|
||||
float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter.
|
||||
|
||||
// Extruder specific parameters.
|
||||
std::vector<FilamentParameters> m_filpar;
|
||||
|
||||
|
||||
// State of the wipe tower generator.
|
||||
unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics.
|
||||
unsigned int m_num_tool_changes = 0; // Tool change change counter for the output statistics.
|
||||
///unsigned int m_idx_tool_change_in_layer = 0; // Layer change counter in this layer. Counting up to m_max_color_changes.
|
||||
bool m_print_brim = true;
|
||||
// A fill-in direction (positive Y, negative Y) alternates with each layer.
|
||||
wipe_shape m_current_shape = SHAPE_NORMAL;
|
||||
size_t m_current_tool = 0;
|
||||
// BBS
|
||||
//const std::vector<std::vector<float>> wipe_volumes;
|
||||
const float m_wipe_volume;
|
||||
|
||||
float m_depth_traversed = 0.f; // Current y position at the wipe tower.
|
||||
bool m_current_layer_finished = false;
|
||||
bool m_left_to_right = true;
|
||||
float m_extra_spacing = 1.f;
|
||||
|
||||
bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; }
|
||||
|
||||
// Calculates extrusion flow needed to produce required line width for given layer height
|
||||
float extrusion_flow(float layer_height = -1.f) const // negative layer_height - return current m_extrusion_flow
|
||||
{
|
||||
if ( layer_height < 0 )
|
||||
return m_extrusion_flow;
|
||||
return layer_height * ( m_perimeter_width - layer_height * (1.f-float(M_PI)/4.f)) / filament_area();
|
||||
}
|
||||
|
||||
// Calculates length of extrusion line to extrude given volume
|
||||
float volume_to_length(float volume, float line_width, float layer_height) const {
|
||||
return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))));
|
||||
}
|
||||
|
||||
// Calculates depth for all layers and propagates them downwards
|
||||
void plan_tower();
|
||||
|
||||
// Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental
|
||||
void make_wipe_tower_square();
|
||||
|
||||
// Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe
|
||||
void save_on_last_wipe();
|
||||
|
||||
// BBS
|
||||
box_coordinates align_perimeter(const box_coordinates& perimeter_box);
|
||||
|
||||
|
||||
// to store information about tool changes for a given layer
|
||||
struct WipeTowerInfo{
|
||||
struct ToolChange {
|
||||
size_t old_tool;
|
||||
size_t new_tool;
|
||||
float required_depth;
|
||||
float ramming_depth;
|
||||
float first_wipe_line;
|
||||
float wipe_volume;
|
||||
float wipe_length;
|
||||
// BBS
|
||||
float purge_volume;
|
||||
ToolChange(size_t old, size_t newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f, float wl = 0, float pv = 0)
|
||||
: old_tool{ old }, new_tool{ newtool }, required_depth{ depth }, ramming_depth{ ramming_depth }, first_wipe_line{ fwl }, wipe_volume{ wv }, wipe_length{ wl }, purge_volume{ pv } {}
|
||||
};
|
||||
float z; // z position of the layer
|
||||
float height; // layer height
|
||||
float depth; // depth of the layer based on all layers above
|
||||
float extra_spacing;
|
||||
float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; }
|
||||
|
||||
std::vector<ToolChange> tool_changes;
|
||||
|
||||
WipeTowerInfo(float z_par, float layer_height_par)
|
||||
: z{z_par}, height{layer_height_par}, depth{0}, extra_spacing{1.f} {}
|
||||
};
|
||||
|
||||
std::vector<WipeTowerInfo> m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...))
|
||||
std::vector<WipeTowerInfo>::iterator m_layer_info = m_plan.end();
|
||||
|
||||
// Stores information about used filament length per extruder:
|
||||
std::vector<float> m_used_filament_length;
|
||||
|
||||
// BBS: consider both soluable and support properties
|
||||
// Return index of first toolchange that switches to non-soluble extruder
|
||||
// ot -1 if there is no such toolchange.
|
||||
int first_toolchange_to_nonsoluble_nonsupport(
|
||||
const std::vector<WipeTowerInfo::ToolChange>& tool_changes) const;
|
||||
|
||||
void toolchange_Unload(
|
||||
WipeTowerWriter &writer,
|
||||
const box_coordinates &cleaning_box,
|
||||
const std::string& current_material,
|
||||
const int new_temperature);
|
||||
|
||||
void toolchange_Change(
|
||||
WipeTowerWriter &writer,
|
||||
const size_t new_tool,
|
||||
const std::string& new_material);
|
||||
|
||||
void toolchange_Load(
|
||||
WipeTowerWriter &writer,
|
||||
const box_coordinates &cleaning_box);
|
||||
|
||||
void toolchange_Wipe(
|
||||
WipeTowerWriter &writer,
|
||||
const box_coordinates &cleaning_box,
|
||||
float wipe_volume);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // WipeTowerPrusaMM_hpp_
|
Loading…
Add table
Add a link
Reference in a new issue