Add the full source of BambuStudio

using version 1.0.10
This commit is contained in:
lane.wei 2022-07-15 23:37:19 +08:00 committed by Lane.Wei
parent 30bcadab3e
commit 1555904bef
3771 changed files with 1251328 additions and 0 deletions

File diff suppressed because it is too large Load diff

View 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_

View 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> &current_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

View 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> &current_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

File diff suppressed because it is too large Load diff

View 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_ */

View 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

View 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_ */

View 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

View 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_ */

View 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;
}
}

View 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_ */

View 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();
}
}

View 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_

View 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;
}
}

View 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_

View 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;
}
}

View 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_

View 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

View 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_

File diff suppressed because it is too large Load diff

View 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 &region) const;
unsigned int sparse_infill_filament(const PrintRegion &region) const;
unsigned int solid_infill_filament(const PrintRegion &region) 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 &region) 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_ */

File diff suppressed because it is too large Load diff

View 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_