mirror of
https://github.com/QIDITECH/QIDIStudio.git
synced 2026-02-07 12:21:50 +03:00
update libslic3r
This commit is contained in:
@@ -509,13 +509,14 @@ static float get_perimeter_spacing_external(const Layer &layer)
|
||||
|
||||
// Called by avoid_perimeters() and by simplify_travel_heuristics().
|
||||
static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &boundary,
|
||||
const Point &start,
|
||||
const Point &end,
|
||||
const Point &start_point,
|
||||
const Point &end_point,
|
||||
const Layer &layer,
|
||||
std::vector<TravelPoint> &result_out)
|
||||
{
|
||||
const Polygons &boundaries = boundary.boundaries;
|
||||
const EdgeGrid::Grid &edge_grid = boundary.grid;
|
||||
Point start = start_point, end = end_point;
|
||||
// Find all intersections between boundaries and the line segment, sort them along the line segment.
|
||||
std::vector<Intersection> intersections;
|
||||
{
|
||||
@@ -523,6 +524,29 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo
|
||||
AllIntersectionsVisitor visitor(edge_grid, intersections, Line(start, end));
|
||||
edge_grid.visit_cells_intersecting_line(start, end, visitor);
|
||||
Vec2d dir = (end - start).cast<double>();
|
||||
// if do not intersect due to the boundaries inner-offset, try to find the closest point to do intersect again!
|
||||
if (intersections.empty()) {
|
||||
// try to find the closest point on boundaries to start/end with distance less than extend_distance, which is noted as new start_point/end_point
|
||||
auto search_radius = 1.5 * get_perimeter_spacing(layer);
|
||||
const std::vector<ClosestLine> closest_line_to_start = get_closest_lines_in_radius(boundary.grid, start, search_radius);
|
||||
const std::vector<ClosestLine> closest_line_to_end = get_closest_lines_in_radius(boundary.grid, end, search_radius);
|
||||
if (!(closest_line_to_start.empty() && closest_line_to_end.empty())) {
|
||||
auto new_start_point = closest_line_to_start.empty() ? start : closest_line_to_start.front().point;
|
||||
auto new_end_point = closest_line_to_end.empty() ? end : closest_line_to_end.front().point;
|
||||
dir = (new_end_point - new_start_point).cast<double>();
|
||||
auto unit_direction = dir.normalized();
|
||||
// out-offset new_start_point/new_end_point epsilon along the Line(new_start_point, new_end_point) for right intersection!
|
||||
new_start_point = new_start_point - (unit_direction * double(coord_t(SCALED_EPSILON))).cast<coord_t>();
|
||||
new_end_point = new_end_point + (unit_direction * double(coord_t(SCALED_EPSILON))).cast<coord_t>();
|
||||
AllIntersectionsVisitor visitor(edge_grid, intersections, Line(new_start_point, new_end_point));
|
||||
edge_grid.visit_cells_intersecting_line(new_start_point, new_end_point, visitor);
|
||||
if (!intersections.empty()) {
|
||||
start = new_start_point;
|
||||
end = new_end_point;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Intersection &intersection : intersections) {
|
||||
float dist_from_line_begin = (intersection.point - boundary.boundaries[intersection.border_idx][intersection.line_idx]).cast<float>().norm();
|
||||
intersection.distance = boundary.boundaries_params[intersection.border_idx][intersection.line_idx] + dist_from_line_begin;
|
||||
@@ -598,6 +622,7 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo
|
||||
|
||||
result.push_back({end, -1});
|
||||
|
||||
|
||||
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
|
||||
{
|
||||
static int iRun = 0;
|
||||
@@ -632,6 +657,7 @@ static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &boundary
|
||||
size_t num_intersections = avoid_perimeters_inner(boundary, start, end, layer, path);
|
||||
result_out = to_polyline(path);
|
||||
|
||||
|
||||
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
|
||||
{
|
||||
static int iRun = 0;
|
||||
@@ -705,7 +731,7 @@ static bool need_wipe(const GCode &gcodegen,
|
||||
{
|
||||
const ExPolygons &lslices = gcodegen.layer()->lslices;
|
||||
const std::vector<BoundingBox> &lslices_bboxes = gcodegen.layer()->lslices_bboxes;
|
||||
bool z_lift_enabled = gcodegen.config().z_hop.get_at(gcodegen.writer().extruder()->id()) > 0.;
|
||||
bool z_lift_enabled = gcodegen.config().z_hop.get_at(gcodegen.writer().filament()->id()) > 0.;
|
||||
bool wipe_needed = false;
|
||||
|
||||
// If the original unmodified path doesn't have any intersection with boundary, then it is entirely inside the object otherwise is entirely
|
||||
@@ -1127,7 +1153,6 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &
|
||||
const Point start = gcodegen.last_pos() + scaled_origin;
|
||||
const Point end = point + scaled_origin;
|
||||
const Line travel(start, end);
|
||||
|
||||
Polyline result_pl;
|
||||
size_t travel_intersection_count = 0;
|
||||
Vec2d startf = start.cast<double>();
|
||||
|
||||
@@ -227,16 +227,16 @@ ConflictResultOpt ConflictChecker::find_inter_of_lines_in_diff_objs(PrintObjectP
|
||||
LinesBucketQueue conflictQueue;
|
||||
|
||||
if (wtdptr.has_value()) { // wipe tower at 0 by default
|
||||
auto wtpaths = wtdptr.value()->getFakeExtrusionPathsFromWipeTower();
|
||||
ExtrusionLayers wtels;
|
||||
wtels.type = ExtrusionLayersType::WIPE_TOWER;
|
||||
for (int i = 0; i < wtpaths.size(); ++i) { // assume that wipe tower always has same height
|
||||
ExtrusionLayer el;
|
||||
el.paths = wtpaths[i];
|
||||
el.bottom_z = wtpaths[i].front().height * (float) i;
|
||||
el.layer = nullptr;
|
||||
wtels.push_back(el);
|
||||
}
|
||||
//auto wtpaths = wtdptr.value()->getFakeExtrusionPathsFromWipeTower();
|
||||
ExtrusionLayers wtels = wtdptr.value()->getTrueExtrusionLayersFromWipeTower();
|
||||
//wtels.type = ExtrusionLayersType::WIPE_TOWER;
|
||||
//for (int i = 0; i < wtpaths.size(); ++i) { // assume that wipe tower always has same height
|
||||
// ExtrusionLayer el;
|
||||
// el.paths = wtpaths[i];
|
||||
// el.bottom_z = wtpaths[i].front().height * (float) i;
|
||||
// el.layer = nullptr;
|
||||
// wtels.push_back(el);
|
||||
//}
|
||||
conflictQueue.emplace_back_bucket(std::move(wtels), wtdptr.value(), {wtdptr.value()->plate_origin.x(), wtdptr.value()->plate_origin.y()});
|
||||
}
|
||||
for (PrintObject *obj : objs) {
|
||||
|
||||
@@ -33,8 +33,9 @@ struct ExtrusionLayer
|
||||
|
||||
enum class ExtrusionLayersType { INFILL, PERIMETERS, SUPPORT, WIPE_TOWER };
|
||||
|
||||
struct ExtrusionLayers : public std::vector<ExtrusionLayer>
|
||||
class ExtrusionLayers : public std::vector<ExtrusionLayer>
|
||||
{
|
||||
public:
|
||||
ExtrusionLayersType type;
|
||||
};
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,70 +1,21 @@
|
||||
#ifndef slic3r_CoolingBuffer_hpp_
|
||||
#define slic3r_CoolingBuffer_hpp_
|
||||
|
||||
#ifndef slic3r_CoolingProcess_hpp_
|
||||
#define slic3r_CoolingProcess_hpp_
|
||||
#include "../libslic3r.h"
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <cfloat>
|
||||
#include "GCodeEditor.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class GCode;
|
||||
class Layer;
|
||||
struct PerExtruderAdjustments;
|
||||
|
||||
// A standalone G-code filter, to control cooling of the print.
|
||||
// The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited
|
||||
// and the print is modified to stretch over a minimum layer time.
|
||||
//
|
||||
// The simple it sounds, the actual implementation is significantly more complex.
|
||||
// Namely, for a multi-extruder print, each material may require a different cooling logic.
|
||||
// For example, some materials may not like to print too slowly, while with some materials
|
||||
// we may slow down significantly.
|
||||
//
|
||||
class CoolingBuffer {
|
||||
public:
|
||||
CoolingBuffer(GCode &gcodegen);
|
||||
void reset(const Vec3d &position);
|
||||
void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; }
|
||||
std::string process_layer(std::string &&gcode, size_t layer_id, bool flush);
|
||||
|
||||
private:
|
||||
CoolingBuffer& operator=(const CoolingBuffer&) = delete;
|
||||
std::vector<PerExtruderAdjustments> parse_layer_gcode(const std::string &gcode, std::vector<float> ¤t_pos) const;
|
||||
float calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
|
||||
// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed.
|
||||
// Returns the adjusted G-code.
|
||||
std::string apply_layer_cooldown(const std::string &gcode, size_t layer_id, float layer_time, std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
|
||||
|
||||
// G-code snippet cached for the support layers preceding an object layer.
|
||||
std::string m_gcode;
|
||||
// Internal data.
|
||||
// QDS: 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;
|
||||
//QDS: current fan speed
|
||||
int m_current_fan_speed;
|
||||
//QDS:
|
||||
bool m_set_fan_changing_layer = false;
|
||||
bool m_set_addition_fan_changing_layer = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
class CoolingBuffer
|
||||
{
|
||||
public:
|
||||
CoolingBuffer(){};
|
||||
|
||||
float calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
|
||||
|
||||
private:
|
||||
// Old logic: proportional.
|
||||
bool m_cooling_logic_proportional = false;
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
636
src/libslic3r/GCode/GCodeEditor.cpp
Normal file
636
src/libslic3r/GCode/GCodeEditor.cpp
Normal file
@@ -0,0 +1,636 @@
|
||||
#include "../GCode.hpp"
|
||||
#include "GCodeEditor.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 {
|
||||
|
||||
GCodeEditor::GCodeEditor(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 GCodeEditor::reset(const Vec3d &position)
|
||||
{
|
||||
// QDS: 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.get_at(get_extruder_index(m_config, m_current_extruder)));
|
||||
m_fan_speed = -1;
|
||||
m_additional_fan_speed = -1;
|
||||
m_current_fan_speed = -1;
|
||||
}
|
||||
|
||||
static void record_wall_lines(bool &flag, int &line_idx, PerExtruderAdjustments *adjustment, const std::pair<int, int> &node_pos)
|
||||
{
|
||||
if (flag && line_idx < adjustment->lines.size()) {
|
||||
CoolingLine &ptr = adjustment->lines[line_idx];
|
||||
ptr.outwall_smooth_mark = true;
|
||||
ptr.object_id = node_pos.first;
|
||||
ptr.cooling_node_id = node_pos.second;
|
||||
flag = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void mark_node_pos(
|
||||
bool &flag, int &line_idx, std::pair<int, int> &node_pos, const std::vector<int> &object_label, int cooling_node_id, int object_id, PerExtruderAdjustments *adjustment)
|
||||
{
|
||||
for (size_t object_idx = 0; object_idx < object_label.size(); ++object_idx) {
|
||||
if (object_label[object_idx] == object_id) {
|
||||
if (cooling_node_id == -1) break;
|
||||
line_idx = adjustment->lines.size();
|
||||
flag = true;
|
||||
node_pos.first = object_idx;
|
||||
node_pos.second = cooling_node_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string GCodeEditor::process_layer(std::string && gcode,
|
||||
const size_t layer_id,
|
||||
std::vector<PerExtruderAdjustments> &per_extruder_adjustments,
|
||||
const std::vector<int> & object_label,
|
||||
const bool flush,
|
||||
const bool spiral_vase)
|
||||
{
|
||||
// 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.
|
||||
// record parse gcode info to per_extruder_adjustments
|
||||
per_extruder_adjustments = this->parse_layer_gcode(m_gcode, m_current_pos, object_label, spiral_vase, layer_id > 0);
|
||||
out = m_gcode;
|
||||
m_gcode.clear();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
//native-resource://sandbox_fs/webcontent/resource/assets/img/41ecc25c56.png
|
||||
// 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> GCodeEditor::parse_layer_gcode(
|
||||
const std::string &gcode,
|
||||
std::vector<float> & current_pos,
|
||||
const std::vector<int> & object_label,
|
||||
bool spiral_vase,
|
||||
bool join_z_smooth)
|
||||
{
|
||||
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));
|
||||
//w14
|
||||
adj.dont_slow_down_outer_wall = m_config.dont_slow_down_outer_wall.get_at(extruder_id);
|
||||
|
||||
map_extruder_to_per_extruder_adjustment[extruder_id] = i;
|
||||
}
|
||||
|
||||
unsigned int current_extruder = m_parse_gcode_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);
|
||||
int object_id = -1;
|
||||
int cooling_node_id = -1;
|
||||
std::string object_id_string = "; OBJECT_ID: ";
|
||||
std::string cooling_node_label = "; COOLING_NODE: ";
|
||||
bool append_wall_ptr = false;
|
||||
bool append_inner_wall_ptr = false;
|
||||
bool not_join_cooling = false;
|
||||
std::pair<int, int> node_pos;
|
||||
int line_idx = -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;
|
||||
//QDS: parse object id & node id
|
||||
else if (boost::starts_with(sline, object_id_string)) {
|
||||
std::string sub = sline.substr(object_id_string.size());
|
||||
object_id = std::stoi(sub);
|
||||
} else if (boost::starts_with(sline, cooling_node_label)) {
|
||||
std::string sub = sline.substr(cooling_node_label.size());
|
||||
cooling_node_id = std::stoi(sub);
|
||||
}
|
||||
|
||||
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
|
||||
//QDS: 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) {
|
||||
// QDS: 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");
|
||||
|
||||
record_wall_lines(append_inner_wall_ptr, line_idx, adjustment, node_pos);
|
||||
|
||||
if (wipe)
|
||||
line.type |= CoolingLine::TYPE_WIPE;
|
||||
|
||||
//w14
|
||||
bool adjust_external = true;
|
||||
if (adjustment->dont_slow_down_outer_wall && external_perimeter) adjust_external = false;
|
||||
|
||||
if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && !wipe && !not_join_cooling && adjust_external) {
|
||||
line.type |= CoolingLine::TYPE_ADJUSTABLE;
|
||||
active_speed_modifier = adjustment->lines.size();
|
||||
}
|
||||
|
||||
record_wall_lines(append_wall_ptr, line_idx, adjustment, node_pos);
|
||||
|
||||
if (external_perimeter) {
|
||||
line.type |= CoolingLine::TYPE_EXTERNAL_PERIMETER;
|
||||
if (line.type & CoolingLine::TYPE_ADJUSTABLE && join_z_smooth && !spiral_vase) {
|
||||
// QDS: collect outwall info
|
||||
mark_node_pos(append_wall_ptr, line_idx, node_pos, object_label, cooling_node_id, object_id, adjustment);
|
||||
}
|
||||
}
|
||||
|
||||
if ((line.type & CoolingLine::TYPE_G92) == 0) {
|
||||
//QDS: G0, G1, G2, G3. Calculate the duration.
|
||||
if (m_config.use_relative_e_distances.value)
|
||||
// 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;
|
||||
//QDS: 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];
|
||||
line.origin_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;
|
||||
|
||||
if (line.feedrate == 0)
|
||||
line.time = 0;
|
||||
|
||||
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);
|
||||
line.origin_time_max = line.time_max;
|
||||
// QDS: 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;
|
||||
|
||||
sm.origin_time_max = sm.time_max;
|
||||
}
|
||||
// Don't store this line.
|
||||
line.type = 0;
|
||||
}
|
||||
}
|
||||
current_pos = std::move(new_pos);
|
||||
} else if (boost::starts_with(sline, "; Slow Down Start")) {
|
||||
not_join_cooling = true;
|
||||
} else if (boost::starts_with(sline, "; Slow Down End")) {
|
||||
not_join_cooling = false;
|
||||
} 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.);
|
||||
line.origin_time_max = line.time_max;
|
||||
} else if (boost::starts_with(sline, ";_FORCE_RESUME_FAN_SPEED")) {
|
||||
line.type = CoolingLine::TYPE_FORCE_RESUME_FAN;
|
||||
} else if (boost::starts_with(sline, ";_SET_FAN_SPEED_CHANGING_LAYER")) {
|
||||
line.type = CoolingLine::TYPE_SET_FAN_CHANGING_LAYER;
|
||||
} else if (boost::starts_with(sline, "M624")) {
|
||||
line.type = CoolingLine::TYPE_OBJECT_START;
|
||||
} else if (boost::starts_with(sline, "M625")) {
|
||||
line.type = CoolingLine::TYPE_OBJECT_END;
|
||||
}
|
||||
if (line.type != 0)
|
||||
adjustment->lines.emplace_back(std::move(line));
|
||||
}
|
||||
m_parse_gcode_extruder = current_extruder;
|
||||
return 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 GCodeEditor::write_layer_gcode(
|
||||
// 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)
|
||||
{
|
||||
if (gcode.empty())
|
||||
return gcode;
|
||||
|
||||
// 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;
|
||||
float pre_start_overhang_fan_time = 0.f;
|
||||
|
||||
enum class SetFanType {
|
||||
sfChangingLayer = 0,
|
||||
sfChangingFilament,
|
||||
sfImmediatelyApply
|
||||
};
|
||||
|
||||
auto change_extruder_set_fan = [this, layer_id, layer_time, &new_gcode, &overhang_fan_control, &overhang_fan_speed, &pre_start_overhang_fan_time](SetFanType type) {
|
||||
#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;
|
||||
|
||||
//QDS
|
||||
//y58
|
||||
//int additional_fan_speed_new = EXTRUDER_CONFIG(additional_cooling_fan_speed);
|
||||
int additional_fan_speed_new;
|
||||
if(m_config.seal)
|
||||
additional_fan_speed_new = EXTRUDER_CONFIG(additional_cooling_fan_speed);
|
||||
else
|
||||
additional_fan_speed_new = EXTRUDER_CONFIG(additional_cooling_fan_speed_unseal);
|
||||
|
||||
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));
|
||||
//QDS: 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;
|
||||
//QDS
|
||||
m_current_fan_speed = fan_speed_new;
|
||||
if (type == SetFanType::sfImmediatelyApply)
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed);
|
||||
else if (type == SetFanType::sfChangingLayer)
|
||||
this->m_set_fan_changing_layer = true;
|
||||
//QDS: don't need to handle change filament, because we are always force to resume fan speed when filament change is finished
|
||||
}
|
||||
//QDS
|
||||
if (additional_fan_speed_new != m_additional_fan_speed) {
|
||||
m_additional_fan_speed = additional_fan_speed_new;
|
||||
if (type == SetFanType::sfImmediatelyApply)
|
||||
new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed);
|
||||
else if (type == SetFanType::sfChangingLayer)
|
||||
this->m_set_addition_fan_changing_layer = true;
|
||||
//QDS: don't need to handle change filament, because we are always force to resume fan speed when filament change is finished
|
||||
}
|
||||
//QDS: set fan pre start time value
|
||||
pre_start_overhang_fan_time = overhang_fan_control ? m_config.pre_start_fan_time.get_at(m_current_extruder) : 0.f;
|
||||
};
|
||||
|
||||
const char *pos = gcode.c_str();
|
||||
int current_feedrate = 0;
|
||||
//QDS
|
||||
m_set_fan_changing_layer = false;
|
||||
m_set_addition_fan_changing_layer = false;
|
||||
change_extruder_set_fan(SetFanType::sfChangingLayer);
|
||||
|
||||
//QDS: start the fan earlier for overhangs
|
||||
float cumulative_time = 0.f;
|
||||
float search_time = 0.f;
|
||||
|
||||
for (int i = 0,j = 0; i < lines.size(); i++) {
|
||||
const CoolingLine *line = lines[i];
|
||||
if (pre_start_overhang_fan_time > 0.f && overhang_fan_speed > m_fan_speed) {
|
||||
cumulative_time += line->time;
|
||||
j = j<i ? i : j;
|
||||
search_time = search_time<cumulative_time ? cumulative_time : search_time;
|
||||
// QDS: search for the next overhang line in xx seconds
|
||||
for (; search_time - cumulative_time < pre_start_overhang_fan_time && j < lines.size() && overhang_fan_control && m_current_fan_speed < overhang_fan_speed; j++) {
|
||||
const CoolingLine *line_iter = lines[j];
|
||||
//do not change fan speed for changing filament gcode
|
||||
if (line_iter->type & CoolingLine::TYPE_FORCE_RESUME_FAN) {
|
||||
//stop search when find a force resume fan command
|
||||
break;
|
||||
}
|
||||
search_time += line_iter->time;
|
||||
if (line_iter->type & CoolingLine::TYPE_OVERHANG_FAN_START) {
|
||||
m_current_fan_speed = overhang_fan_speed;
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, overhang_fan_speed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
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(SetFanType::sfChangingFilament); //QDS: will force to resume fan speed when filament change is finished
|
||||
cumulative_time = 0.f;
|
||||
search_time = 0.f;
|
||||
}
|
||||
new_gcode.append(line_start, line_end - line_start);
|
||||
} else if (line->type & CoolingLine::TYPE_OVERHANG_FAN_START) {
|
||||
if (overhang_fan_control && m_current_fan_speed < overhang_fan_speed) {
|
||||
//QDS
|
||||
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) {
|
||||
//QDS
|
||||
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) {
|
||||
//QDS: 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);
|
||||
if (m_additional_fan_speed != -1)
|
||||
new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed);
|
||||
} else if (line->type & CoolingLine::TYPE_SET_FAN_CHANGING_LAYER) {
|
||||
//QDS: check whether fan speed need to changed when change layer
|
||||
if (m_current_fan_speed != -1 && m_set_fan_changing_layer) {
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed);
|
||||
m_set_fan_changing_layer = false;
|
||||
}
|
||||
if (m_additional_fan_speed != -1 && m_set_addition_fan_changing_layer) {
|
||||
new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed);
|
||||
m_set_addition_fan_changing_layer = false;
|
||||
}
|
||||
}
|
||||
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')) {
|
||||
// QDS: 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 if (line->type & CoolingLine::TYPE_OBJECT_START) {
|
||||
new_gcode.append(line_start, line_end - line_start);
|
||||
if (pre_start_overhang_fan_time > 0.f && m_current_fan_speed > m_fan_speed)
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed);
|
||||
} else if (line->type & CoolingLine::TYPE_OBJECT_END) {
|
||||
if (pre_start_overhang_fan_time > 0.f && m_current_fan_speed > m_fan_speed)
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed);
|
||||
new_gcode.append(line_start, line_end - line_start);
|
||||
}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
|
||||
299
src/libslic3r/GCode/GCodeEditor.hpp
Normal file
299
src/libslic3r/GCode/GCodeEditor.hpp
Normal file
@@ -0,0 +1,299 @@
|
||||
#ifndef slic3r_GCodeEditer_hpp_
|
||||
#define slic3r_GCodeEditer_hpp_
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <cfloat>
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/Slicing.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class GCode;
|
||||
class Layer;
|
||||
|
||||
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,
|
||||
// QDS: add G2 G3 type
|
||||
TYPE_G2 = 1 << 12,
|
||||
TYPE_G3 = 1 << 13,
|
||||
TYPE_FORCE_RESUME_FAN = 1 << 14,
|
||||
TYPE_SET_FAN_CHANGING_LAYER = 1 << 15,
|
||||
TYPE_OBJECT_START = 1 << 16,
|
||||
TYPE_OBJECT_END = 1 << 17,
|
||||
};
|
||||
|
||||
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), origin_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;
|
||||
// Current feedrate, possibly adjusted.
|
||||
float origin_feedrate = 0;
|
||||
float origin_time_max = 0;
|
||||
// Current duration of this segment.
|
||||
//float origin_time;
|
||||
bool outwall_smooth_mark = false;
|
||||
int object_id = -1;
|
||||
int cooling_node_id = -1;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//collect lines time
|
||||
float collection_line_times_of_extruder() {
|
||||
float times = 0;
|
||||
for (const CoolingLine &line: lines) {
|
||||
times += line.time;
|
||||
}
|
||||
return times;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
//w14
|
||||
bool dont_slow_down_outer_wall = false;
|
||||
|
||||
// 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;
|
||||
};
|
||||
// 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 GCodeEditor {
|
||||
public:
|
||||
GCodeEditor(GCode &gcodegen);
|
||||
void reset(const Vec3d &position);
|
||||
void set_current_extruder(unsigned int extruder_id)
|
||||
{
|
||||
m_current_extruder = extruder_id;
|
||||
m_parse_gcode_extruder = extruder_id;
|
||||
}
|
||||
std::string process_layer(std::string && gcode,
|
||||
const size_t layer_id,
|
||||
std::vector<PerExtruderAdjustments> &per_extruder_adjustments,
|
||||
const std::vector<int> & object_label,
|
||||
const bool flush,
|
||||
const bool spiral_vase);
|
||||
|
||||
// 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 write_layer_gcode(const std::string &gcode, size_t layer_id, float layer_time, std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
|
||||
|
||||
private :
|
||||
GCodeEditor& operator=(const GCodeEditor&) = delete;
|
||||
std::vector<PerExtruderAdjustments> parse_layer_gcode(const std::string & gcode,
|
||||
std::vector<float> & current_pos,
|
||||
const std::vector<int> & object_label,
|
||||
bool spiral_vase,
|
||||
bool join_z_smooth);
|
||||
|
||||
// G-code snippet cached for the support layers preceding an object layer.
|
||||
std::string m_gcode;
|
||||
// Internal data.
|
||||
// QDS: 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;
|
||||
unsigned int m_parse_gcode_extruder;
|
||||
//QDS: current fan speed
|
||||
int m_current_fan_speed;
|
||||
//QDS:
|
||||
bool m_set_fan_changing_layer = false;
|
||||
bool m_set_addition_fan_changing_layer = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,7 @@ namespace Slic3r {
|
||||
#define BED_TEMP_TOO_HIGH_THAN_FILAMENT "bed_temperature_too_high_than_filament"
|
||||
#define NOT_SUPPORT_TRADITIONAL_TIMELAPSE "not_support_traditional_timelapse"
|
||||
#define NOT_GENERATE_TIMELAPSE "not_generate_timelapse"
|
||||
#define SMOOTH_TIMELAPSE_WITHOUT_PRIME_TOWER "smooth_timelapse_without_prime_tower"
|
||||
#define LONG_RETRACTION_WHEN_CUT "activate_long_retraction_when_cut"
|
||||
|
||||
enum class EMoveType : unsigned char
|
||||
@@ -41,6 +42,18 @@ namespace Slic3r {
|
||||
Count
|
||||
};
|
||||
|
||||
enum SkipType
|
||||
{
|
||||
stTimelapse,
|
||||
stHeadWrapDetect,
|
||||
stOther,
|
||||
stNone
|
||||
};
|
||||
|
||||
const std::unordered_map<std::string_view, SkipType> skip_type_map{
|
||||
{"timelapse", SkipType::stTimelapse},
|
||||
{"head_wrap_detect", SkipType::stHeadWrapDetect}
|
||||
};
|
||||
struct PrintEstimatedStatistics
|
||||
{
|
||||
enum class ETimeMode : unsigned char
|
||||
@@ -83,7 +96,8 @@ namespace Slic3r {
|
||||
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;
|
||||
unsigned int total_filament_changes;
|
||||
unsigned int total_extruder_changes;
|
||||
|
||||
PrintEstimatedStatistics() { reset(); }
|
||||
|
||||
@@ -99,7 +113,8 @@ namespace Slic3r {
|
||||
total_volumes_per_extruder.clear();
|
||||
flush_per_filament.clear();
|
||||
used_filaments_per_role.clear();
|
||||
total_filamentchanges = 0;
|
||||
total_filament_changes = 0;
|
||||
total_extruder_changes = 0;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -119,6 +134,18 @@ namespace Slic3r {
|
||||
|
||||
using ConflictResultOpt = std::optional<ConflictResult>;
|
||||
|
||||
struct GCodeCheckResult
|
||||
{
|
||||
int error_code = 0; // 0 means succeed, 0001 printable area error, 0010 printable height error
|
||||
std::map<int, std::vector<std::pair<int, int>>> print_area_error_infos; // printable_area extruder_id to <filament_id - object_label_id> which cannot printed in this extruder
|
||||
std::map<int, std::vector<std::pair<int, int>>> print_height_error_infos; // printable_height extruder_id to <filament_id - object_label_id> which cannot printed in this extruder
|
||||
void reset() {
|
||||
error_code = 0;
|
||||
print_area_error_infos.clear();
|
||||
print_height_error_infos.clear();
|
||||
}
|
||||
};
|
||||
|
||||
struct FilamentPrintableResult
|
||||
{
|
||||
std::vector<int> conflict_filament;
|
||||
@@ -132,7 +159,17 @@ namespace Slic3r {
|
||||
|
||||
struct GCodeProcessorResult
|
||||
{
|
||||
struct FilamentSequenceHash
|
||||
{
|
||||
uint64_t operator()(const std::vector<unsigned int>& layer_filament) const {
|
||||
uint64_t key = 0;
|
||||
for (auto& f : layer_filament)
|
||||
key |= (uint64_t(1) << f);
|
||||
return key;
|
||||
}
|
||||
};
|
||||
ConflictResultOpt conflict_result;
|
||||
GCodeCheckResult gcode_check_result;
|
||||
FilamentPrintableResult filament_printable_reuslt;
|
||||
|
||||
struct SettingsIds
|
||||
@@ -150,12 +187,14 @@ namespace Slic3r {
|
||||
|
||||
struct MoveVertex
|
||||
{
|
||||
unsigned int gcode_id{ 0 };
|
||||
EMoveType type{ EMoveType::Noop };
|
||||
ExtrusionRole extrusion_role{ erNone };
|
||||
//QDS: arc move related data
|
||||
EMovePathType move_path_type{ EMovePathType::Noop_move };
|
||||
unsigned char extruder_id{ 0 };
|
||||
unsigned char cp_color_id{ 0 };
|
||||
Vec3f position{ Vec3f::Zero() }; // mm
|
||||
|
||||
unsigned int gcode_id{ 0 };
|
||||
float delta_extruder{ 0.0f }; // mm
|
||||
float feedrate{ 0.0f }; // mm/s
|
||||
float width{ 0.0f }; // mm
|
||||
@@ -163,14 +202,15 @@ namespace Slic3r {
|
||||
float mm3_per_mm{ 0.0f };
|
||||
float fan_speed{ 0.0f }; // percentage
|
||||
float temperature{ 0.0f }; // Celsius degrees
|
||||
float time{ 0.0f }; // s
|
||||
float layer_duration{ 0.0f }; // s (layer id before finalize)
|
||||
|
||||
std::array<float, 2>time{ 0.f,0.f }; // prefix sum of time, assigned during finalize()
|
||||
|
||||
//QDS: arc move related data
|
||||
EMovePathType move_path_type{ EMovePathType::Noop_move };
|
||||
Vec3f position{ Vec3f::Zero() }; // mm
|
||||
Vec3f arc_center_position{ Vec3f::Zero() }; // mm
|
||||
std::vector<Vec3f> interpolation_points; // interpolation points of arc for drawing
|
||||
int object_label_id{-1};
|
||||
float print_z{0.0f};
|
||||
|
||||
float volumetric_rate() const { return feedrate * mm3_per_mm; }
|
||||
//QDS: new function to support arc move
|
||||
@@ -197,6 +237,8 @@ namespace Slic3r {
|
||||
Pointfs printable_area;
|
||||
//QDS: add bed exclude area
|
||||
Pointfs bed_exclude_area;
|
||||
std::vector<Pointfs> extruder_areas;
|
||||
std::vector<double> extruder_heights;
|
||||
//QDS: add toolpath_outside
|
||||
bool toolpath_outside;
|
||||
//QDS: add object_label_enabled
|
||||
@@ -207,19 +249,28 @@ namespace Slic3r {
|
||||
bool support_traditional_timelapse{true};
|
||||
float printable_height;
|
||||
SettingsIds settings_ids;
|
||||
size_t extruders_count;
|
||||
size_t filaments_count;
|
||||
std::vector<std::string> extruder_colors;
|
||||
std::vector<float> filament_diameters;
|
||||
std::vector<int> required_nozzle_HRC;
|
||||
std::vector<float> filament_densities;
|
||||
std::vector<float> filament_costs;
|
||||
std::vector<int> filament_vitrification_temperature;
|
||||
std::vector<int> filament_maps;
|
||||
std::vector<int> limit_filament_maps;
|
||||
PrintEstimatedStatistics print_statistics;
|
||||
std::vector<CustomGCode::Item> custom_gcode_per_print_z;
|
||||
std::vector<std::pair<float, std::pair<size_t, size_t>>> spiral_vase_layers;
|
||||
//QDS
|
||||
std::vector<SliceWarning> warnings;
|
||||
NozzleType nozzle_type;
|
||||
std::vector<NozzleType> nozzle_type;
|
||||
// first key stores filaments, second keys stores the layer ranges(enclosed) that use the filaments
|
||||
std::unordered_map<std::vector<unsigned int>, std::vector<std::pair<int, int>>,FilamentSequenceHash> layer_filaments;
|
||||
// first key stores `from` filament, second keys stores the `to` filament
|
||||
std::map<std::pair<int,int>, int > filament_change_count_map;
|
||||
|
||||
std::unordered_map<SkipType, float> skippable_part_time;
|
||||
|
||||
BedType bed_type = BedType::btCount;
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
int64_t time{ 0 };
|
||||
@@ -242,7 +293,7 @@ namespace Slic3r {
|
||||
timelapse_warning_code = other.timelapse_warning_code;
|
||||
printable_height = other.printable_height;
|
||||
settings_ids = other.settings_ids;
|
||||
extruders_count = other.extruders_count;
|
||||
filaments_count = other.filaments_count;
|
||||
extruder_colors = other.extruder_colors;
|
||||
filament_diameters = other.filament_diameters;
|
||||
filament_densities = other.filament_densities;
|
||||
@@ -252,7 +303,12 @@ namespace Slic3r {
|
||||
spiral_vase_layers = other.spiral_vase_layers;
|
||||
warnings = other.warnings;
|
||||
bed_type = other.bed_type;
|
||||
gcode_check_result = other.gcode_check_result;
|
||||
limit_filament_maps = other.limit_filament_maps;
|
||||
filament_printable_reuslt = other.filament_printable_reuslt;
|
||||
layer_filaments = other.layer_filaments;
|
||||
filament_change_count_map = other.filament_change_count_map;
|
||||
skippable_part_time = other.skippable_part_time;
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
time = other.time;
|
||||
#endif
|
||||
@@ -262,12 +318,85 @@ namespace Slic3r {
|
||||
void unlock() const { result_mutex.unlock(); }
|
||||
};
|
||||
|
||||
namespace ExtruderPreHeating
|
||||
{
|
||||
struct FilamentUsageBlock
|
||||
{
|
||||
int filament_id;
|
||||
unsigned int lower_gcode_id;
|
||||
unsigned int upper_gcode_id; // [lower_gcode_id,upper_gcode_id) uses current filament , upper gcode id will be set after finding next block
|
||||
FilamentUsageBlock(int filament_id_, unsigned int lower_gcode_id_, unsigned int upper_gcode_id_) :filament_id(filament_id_), lower_gcode_id(lower_gcode_id_), upper_gcode_id(upper_gcode_id_) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Describle the usage of a exturder in a section
|
||||
*
|
||||
* The strucutre stores the start and end lines of the sections as well as
|
||||
* the filament used at the beginning and end of the section.
|
||||
* Post extrusion means the final extrusion before switching to the next extruder.
|
||||
*
|
||||
* Simplified GCode Flow:
|
||||
* 1.Extruder Change Block (ext0 switch to ext1)
|
||||
* 2.Extruder Usage Block (use ext1 to print)
|
||||
* 3.Extruder Change Block (ext1 switch to ext0)
|
||||
* 4.Extruder Usage Block (use ext0 to print)
|
||||
* 5.Extruder Change Block (ext0 switch to ex1)
|
||||
* ...
|
||||
*
|
||||
* So the construct of extruder usage block relys on two extruder change block
|
||||
*/
|
||||
struct ExtruderUsageBlcok
|
||||
{
|
||||
int extruder_id = -1;
|
||||
unsigned int start_id = -1;
|
||||
unsigned int end_id = -1;
|
||||
int start_filament = -1;
|
||||
int end_filament = -1;
|
||||
unsigned int post_extrusion_start_id = -1;
|
||||
unsigned int post_extrusion_end_id = -1;
|
||||
|
||||
void initialize_step_1(int extruder_id_, int start_id_, int start_filament_) {
|
||||
extruder_id = extruder_id_;
|
||||
start_id = start_id_;
|
||||
start_filament = start_filament_;
|
||||
};
|
||||
void initialize_step_2(int post_extrusion_start_id_) {
|
||||
post_extrusion_start_id = post_extrusion_start_id_;
|
||||
}
|
||||
void initialize_step_3(int end_id_, int end_filament_, int post_extrusion_end_id_) {
|
||||
end_id = end_id_;
|
||||
end_filament = end_filament_;
|
||||
post_extrusion_end_id = post_extrusion_end_id_;
|
||||
}
|
||||
void reset() {
|
||||
*this = ExtruderUsageBlcok();
|
||||
}
|
||||
ExtruderUsageBlcok() = default;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
class CommandProcessor {
|
||||
public:
|
||||
using command_handler_t = std::function<void(const GCodeReader::GCodeLine& line)>;
|
||||
private:
|
||||
struct TrieNode {
|
||||
command_handler_t handler{ nullptr };
|
||||
std::unordered_map<char, std::unique_ptr<TrieNode>> children;
|
||||
bool early_quit{ false }; // stop matching, trigger handle imediately
|
||||
};
|
||||
public:
|
||||
CommandProcessor();
|
||||
void register_command(const std::string& str, command_handler_t handler,bool early_quit = false);
|
||||
bool process_comand(std::string_view cmd, const GCodeReader::GCodeLine& line);
|
||||
private:
|
||||
std::unique_ptr<TrieNode> root;
|
||||
};
|
||||
|
||||
class GCodeProcessor
|
||||
{
|
||||
static const std::vector<std::string> Reserved_Tags;
|
||||
static const std::string Flush_Start_Tag;
|
||||
static const std::string Flush_End_Tag;
|
||||
static const std::vector<std::string> ReservedTags;
|
||||
static const std::vector<std::string> CustomTags;
|
||||
public:
|
||||
enum class ETags : unsigned char
|
||||
{
|
||||
@@ -289,15 +418,32 @@ namespace Slic3r {
|
||||
//1.9.7.52
|
||||
Used_Filament_Weight_Placeholder,
|
||||
Used_Filament_Volume_Placeholder,
|
||||
Used_Filament_Length_Placeholder
|
||||
Used_Filament_Length_Placeholder,
|
||||
MachineStartGCodeEnd,
|
||||
MachineEndGCodeStart,
|
||||
NozzleChangeStart,
|
||||
NozzleChangeEnd
|
||||
};
|
||||
|
||||
static const std::string& reserved_tag(ETags tag) { return Reserved_Tags[static_cast<unsigned char>(tag)]; }
|
||||
enum class CustomETags : unsigned char
|
||||
{
|
||||
FLUSH_START,
|
||||
FLUSH_END,
|
||||
VFLUSH_START,
|
||||
VFLUSH_END,
|
||||
SKIPPABLE_START,
|
||||
SKIPPABLE_END,
|
||||
SKIPPABLE_TYPE
|
||||
};
|
||||
|
||||
static const std::string& reserved_tag(ETags tag) { return ReservedTags[static_cast<unsigned char>(tag)]; }
|
||||
static const std::string& custom_tags(CustomETags tag) { return CustomTags[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 bool get_last_position_from_gcode(const std::string &gcode_str, Vec3f &pos);
|
||||
|
||||
static int get_gcode_last_filament(const std::string &gcode_str);
|
||||
static bool get_last_z_from_gcode(const std::string& gcode_str, double& z);
|
||||
@@ -375,6 +521,8 @@ namespace Slic3r {
|
||||
|
||||
EMoveType move_type{ EMoveType::Noop };
|
||||
ExtrusionRole role{ erNone };
|
||||
SkipType skippable_type{ SkipType::stNone };
|
||||
unsigned int move_id{ 0 }; // index of the related move vertex, will be assigned duraing gcode process
|
||||
unsigned int g1_line_id{ 0 };
|
||||
unsigned int layer_id{ 0 };
|
||||
float distance{ 0.0f }; // mm
|
||||
@@ -458,28 +606,42 @@ namespace Slic3r {
|
||||
//QDS: prepare stage time before print model, including start gcode time and mostly same with start gcode time
|
||||
float prepare_time;
|
||||
|
||||
// accept the time block and total time
|
||||
using block_handler_t = std::function<void(const TimeBlock&, const float)>;
|
||||
using AdditionalBufferBlock = std::pair<ExtrusionRole,float>;
|
||||
using AdditionalBuffer = std::vector<AdditionalBufferBlock>;
|
||||
AdditionalBuffer m_additional_time_buffer;
|
||||
|
||||
AdditionalBuffer merge_adjacent_addtional_time_blocks(const AdditionalBuffer& buffer);
|
||||
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* @brief Simulates firmware st_synchronize() call
|
||||
*
|
||||
* Adding additional time to the specified extrusion role's time block.
|
||||
* Adding additional time to the specified extrusion role's time block.The provided block handler
|
||||
* can process the block and the corresponding time (usually assigned to the move of the block).
|
||||
*
|
||||
* @param additional_time Addtional time to calculate
|
||||
* @param target_role Target extrusion role for addtional time.Default is none,means any role is ok.
|
||||
* @param block_handler Handler to set the processing logic for the block and its corresponding time.
|
||||
*/
|
||||
void simulate_st_synchronize(float additional_time = 0.0f, ExtrusionRole target_role = ExtrusionRole::erNone);
|
||||
void simulate_st_synchronize(float additional_time = 0.0f, ExtrusionRole target_role = ExtrusionRole::erNone, block_handler_t block_handler = block_handler_t());
|
||||
|
||||
/**
|
||||
* @brief Calculates the time for all blocks
|
||||
*
|
||||
* Computes the time for all blocks.
|
||||
*
|
||||
* Computes the time for all blocks. The provided block handler can process each block and the
|
||||
* corresponding time (usually assigned to the move of the block).
|
||||
*
|
||||
* @param keep_last_n_blocks The number of last blocks to retain during calculation (default is 0).
|
||||
* @param additional_time Additional time to calculate.
|
||||
* @param target_role Target extrusion role for addtional time.Default is none, means any role is ok.
|
||||
* @param block_handler Handler to set the processing logic for each block and its corresponding time.
|
||||
*/
|
||||
void calculate_time(size_t keep_last_n_blocks = 0, float additional_time = 0.0f, ExtrusionRole target_role = ExtrusionRole::erNone);
|
||||
void calculate_time(size_t keep_last_n_blocks = 0, float additional_time = 0.0f, ExtrusionRole target_role = ExtrusionRole::erNone, block_handler_t block_handler = block_handler_t());
|
||||
|
||||
void handle_time_block(const TimeBlock& block, float time, int activate_machine_idx, GCodeProcessorResult& result);
|
||||
};
|
||||
|
||||
struct UsedFilaments // filaments per ColorChange
|
||||
@@ -488,19 +650,19 @@ namespace Slic3r {
|
||||
std::vector<double> volumes_per_color_change;
|
||||
|
||||
double model_extrude_cache;
|
||||
std::map<size_t, double> model_volumes_per_extruder;
|
||||
std::map<size_t, double> model_volumes_per_filament;
|
||||
|
||||
double wipe_tower_cache;
|
||||
std::map<size_t, double>wipe_tower_volumes_per_extruder;
|
||||
std::map<size_t, double>wipe_tower_volumes_per_filament;
|
||||
|
||||
double support_volume_cache;
|
||||
std::map<size_t, double>support_volumes_per_extruder;
|
||||
std::map<size_t, double>support_volumes_per_filament;
|
||||
|
||||
//QDS: the flush amount of every filament
|
||||
std::map<size_t, double> flush_per_filament;
|
||||
|
||||
double total_volume_cache;
|
||||
std::map<size_t, double>total_volumes_per_extruder;
|
||||
std::map<size_t, double>total_volumes_per_filament;
|
||||
|
||||
double role_cache;
|
||||
std::map<ExtrusionRole, std::pair<double, double>> filaments_per_role;
|
||||
@@ -527,17 +689,66 @@ namespace Slic3r {
|
||||
//1.9.7.52
|
||||
struct TimeProcessContext
|
||||
{
|
||||
size_t total_layer_num;
|
||||
UsedFilaments used_filaments; // stores the accurate filament usage info
|
||||
std::vector<Extruder> filament_lists;
|
||||
UsedFilaments used_filaments;
|
||||
TimeProcessContext( size_t total_layer_num_,
|
||||
std::vector<std::string> filament_types;
|
||||
std::vector<int> filament_maps; // map each filament to extruder
|
||||
std::vector<int> filament_nozzle_temp;
|
||||
std::vector<int> physical_extruder_map;
|
||||
|
||||
size_t total_layer_num;
|
||||
std::vector<double> cooling_rate{ 2.f }; // Celsius degree per second
|
||||
std::vector<double> heating_rate{ 2.f }; // Celsius degree per second
|
||||
std::vector<int> pre_cooling_temp{ 0 };
|
||||
float inject_time_threshold{ 30.f }; // only active pre cooling & heating if time gap is bigger than threshold
|
||||
bool enable_pre_heating{ false };
|
||||
|
||||
TimeProcessContext(
|
||||
const UsedFilaments& used_filaments_,
|
||||
const std::vector<Extruder>& filament_lists_,
|
||||
const UsedFilaments& used_filaments_)
|
||||
:total_layer_num(total_layer_num_), filament_lists(filament_lists_), used_filaments(used_filaments_) {}
|
||||
const std::vector<int>& filament_maps_,
|
||||
const std::vector<std::string>& filament_types_,
|
||||
const std::vector<int>& filament_nozzle_temp_,
|
||||
const std::vector<int>& physical_extruder_map_,
|
||||
const size_t total_layer_num_,
|
||||
const std::vector<double>& cooling_rate_,
|
||||
const std::vector<double>& heating_rate_,
|
||||
const std::vector<int>& pre_cooling_temp_,
|
||||
const float inject_time_threshold_,
|
||||
const bool enable_pre_heating_
|
||||
) :
|
||||
used_filaments(used_filaments_),
|
||||
filament_lists(filament_lists_),
|
||||
filament_maps(filament_maps_),
|
||||
filament_types(filament_types_),
|
||||
filament_nozzle_temp(filament_nozzle_temp_),
|
||||
physical_extruder_map(physical_extruder_map_),
|
||||
total_layer_num(total_layer_num_),
|
||||
cooling_rate(cooling_rate_),
|
||||
heating_rate(heating_rate_),
|
||||
pre_cooling_temp(pre_cooling_temp_),
|
||||
enable_pre_heating(enable_pre_heating_),
|
||||
inject_time_threshold(inject_time_threshold_)
|
||||
{
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct TimeProcessor
|
||||
{
|
||||
enum InsertLineType
|
||||
{
|
||||
PlaceholderReplace,
|
||||
TimePredict,
|
||||
FilamentChangePredict,
|
||||
ExtruderChangePredict,
|
||||
PreCooling,
|
||||
PreHeating,
|
||||
};
|
||||
|
||||
// first key is line id ,second key is content
|
||||
using InsertedLinesMap = std::map<unsigned int, std::vector<std::pair<std::string, InsertLineType>>>;
|
||||
|
||||
struct Planner
|
||||
{
|
||||
// Size of the firmware planner queue. The old 8-bit Marlins usually just managed 16 trapezoidal blocks.
|
||||
@@ -558,6 +769,7 @@ namespace Slic3r {
|
||||
// Additional load / unload times for a filament exchange sequence.
|
||||
float filament_load_times;
|
||||
float filament_unload_times;
|
||||
float extruder_change_times;
|
||||
|
||||
std::array<TimeMachine, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)> machines;
|
||||
|
||||
@@ -566,7 +778,99 @@ namespace Slic3r {
|
||||
// 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, const TimeProcessContext& context);
|
||||
private:
|
||||
void handle_offsets_of_first_process(
|
||||
const std::vector<std::pair<unsigned int, unsigned int>>& offsets,
|
||||
std::vector<GCodeProcessorResult::MoveVertex>& moves,
|
||||
std::vector<ExtruderPreHeating::FilamentUsageBlock>& filament_blocks,
|
||||
std::vector<ExtruderPreHeating::ExtruderUsageBlcok>& extruder_blocks,
|
||||
std::vector<std::pair<unsigned int, unsigned int>>& skippable_blocks,
|
||||
unsigned int& machine_start_gcode_end_line_id,
|
||||
unsigned int& machine_end_gcode_start_line_id
|
||||
);
|
||||
|
||||
void handle_offsets_of_second_process(
|
||||
const InsertedLinesMap& inserted_operation_lines,
|
||||
std::vector<GCodeProcessorResult::MoveVertex>& moves
|
||||
);
|
||||
};
|
||||
|
||||
class PreCoolingInjector {
|
||||
public:
|
||||
struct ExtruderFreeBlock {
|
||||
unsigned int free_lower_gcode_id;
|
||||
unsigned int free_upper_gcode_id;
|
||||
unsigned int partial_free_lower_id; // stores the range of extrusion in wipe tower. Without wipetower, partial free lower_id and upper id will be same as free lower id
|
||||
unsigned int partial_free_upper_id;
|
||||
int last_filament_id;
|
||||
int next_filament_id;
|
||||
int extruder_id;
|
||||
};
|
||||
|
||||
void process_pre_cooling_and_heating(TimeProcessor::InsertedLinesMap& inserted_operation_lines);
|
||||
void build_extruder_free_blocks(const std::vector<ExtruderPreHeating::FilamentUsageBlock>& filament_usage_blocks, const std::vector<ExtruderPreHeating::ExtruderUsageBlcok>& extruder_usage_blocks);
|
||||
|
||||
PreCoolingInjector(
|
||||
const std::vector<GCodeProcessorResult::MoveVertex>& moves_,
|
||||
const std::vector<std::string>& filament_types_,
|
||||
const std::vector<int>& filament_maps_,
|
||||
const std::vector<int>& filament_nozzle_temps_,
|
||||
const std::vector<int>& physical_extruder_map_,
|
||||
int valid_machine_id_,
|
||||
float inject_time_threshold_,
|
||||
const std::vector<int> & pre_cooling_temp_,
|
||||
const std::vector<double>& cooling_rate_,
|
||||
const std::vector<double>& heating_rate_,
|
||||
const std::vector<std::pair<unsigned int,unsigned int>>& skippable_blocks_,
|
||||
unsigned int machine_start_gcode_end_id_,
|
||||
unsigned int machine_end_gcode_start_id_
|
||||
) :
|
||||
moves(moves_),
|
||||
filament_types(filament_types_),
|
||||
filament_maps(filament_maps_),
|
||||
filament_nozzle_temps(filament_nozzle_temps_),
|
||||
physical_extruder_map(physical_extruder_map_),
|
||||
valid_machine_id(valid_machine_id_),
|
||||
inject_time_threshold(inject_time_threshold_),
|
||||
filament_pre_cooling_temps(pre_cooling_temp_),
|
||||
cooling_rate(cooling_rate_),
|
||||
heating_rate(heating_rate_),
|
||||
skippable_blocks(skippable_blocks_),
|
||||
machine_start_gcode_end_id(machine_start_gcode_end_id_),
|
||||
machine_end_gcode_start_id(machine_end_gcode_start_id_)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<ExtruderFreeBlock> m_extruder_free_blocks;
|
||||
const std::vector<GCodeProcessorResult::MoveVertex>& moves;
|
||||
const std::vector<std::string>& filament_types;
|
||||
const std::vector<int>& filament_maps;
|
||||
const std::vector<int>& filament_nozzle_temps;
|
||||
const std::vector<int>& physical_extruder_map;
|
||||
const int valid_machine_id;
|
||||
const float inject_time_threshold;
|
||||
const std::vector<double>& cooling_rate;
|
||||
const std::vector<double>& heating_rate;
|
||||
const std::vector<int>& filament_pre_cooling_temps; // target cooling temp during post extrusion
|
||||
const std::vector<std::pair<unsigned int, unsigned int>>& skippable_blocks;
|
||||
const unsigned int machine_start_gcode_end_id;
|
||||
const unsigned int machine_end_gcode_start_id;
|
||||
|
||||
void inject_cooling_heating_command(
|
||||
TimeProcessor::InsertedLinesMap& inserted_operation_lines,
|
||||
const ExtruderFreeBlock& free_block,
|
||||
float curr_temp,
|
||||
float target_temp,
|
||||
bool pre_cooling,
|
||||
bool pre_heating
|
||||
);
|
||||
|
||||
void build_by_filament_blocks(const std::vector<ExtruderPreHeating::FilamentUsageBlock>& filament_usage_blocks);
|
||||
void build_by_extruder_blocks(const std::vector<ExtruderPreHeating::ExtruderUsageBlcok>& extruder_usage_blocks);
|
||||
};
|
||||
|
||||
|
||||
public:
|
||||
class SeamsDetector
|
||||
{
|
||||
@@ -695,23 +999,36 @@ namespace Slic3r {
|
||||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
|
||||
private:
|
||||
CommandProcessor m_command_processor;
|
||||
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;
|
||||
std::vector<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;
|
||||
bool m_flushing; // mark a section with real flush
|
||||
bool m_virtual_flushing; // mark a section with virtual flush, only for statistics
|
||||
bool m_wipe_tower;
|
||||
float m_remaining_volume;
|
||||
bool m_skippable;
|
||||
SkipType m_skippable_type;
|
||||
int m_object_label_id{-1};
|
||||
float m_print_z{0.0f};
|
||||
std::vector<float> m_remaining_volume;
|
||||
//1.9.7.52
|
||||
std::vector<Extruder> m_filament_lists;
|
||||
std::vector<int> m_filament_nozzle_temp;
|
||||
std::vector<std::string> m_filament_types;
|
||||
std::vector<double> m_hotend_cooling_rate{ 2.f };
|
||||
std::vector<double> m_hotend_heating_rate{ 2.f };
|
||||
std::vector<int> m_filament_pre_cooling_temp{ 0 };
|
||||
float m_enable_pre_heating{ false };
|
||||
std::vector<int> m_physical_extruder_map;
|
||||
|
||||
//QDS: x, y offset for gcode generated
|
||||
double m_x_offset{ 0 };
|
||||
@@ -731,8 +1048,10 @@ namespace Slic3r {
|
||||
float m_mm3_per_mm;
|
||||
float m_fan_speed; // percentage
|
||||
ExtrusionRole m_extrusion_role;
|
||||
std::vector<int> m_filament_maps;
|
||||
std::vector<unsigned char> m_last_filament_id;
|
||||
std::vector<unsigned char> m_filament_id;
|
||||
unsigned char m_extruder_id;
|
||||
unsigned char m_last_extruder_id;
|
||||
ExtruderColors m_extruder_colors;
|
||||
ExtruderTemps m_extruder_temps;
|
||||
int m_highest_bed_temp;
|
||||
@@ -763,7 +1082,9 @@ namespace Slic3r {
|
||||
Simplify3D,
|
||||
CraftWare,
|
||||
ideaMaker,
|
||||
KissSlicer
|
||||
KissSlicer,
|
||||
QIDISlicer,
|
||||
BambuStudio
|
||||
};
|
||||
|
||||
static const std::vector<std::pair<GCodeProcessor::EProducer, std::string>> Producers;
|
||||
@@ -784,6 +1105,11 @@ namespace Slic3r {
|
||||
public:
|
||||
GCodeProcessor();
|
||||
|
||||
// check whether the gcode path meets the filament_map grouping requirements
|
||||
bool check_multi_extruder_gcode_valid(const std::vector<Polygons> &unprintable_areas,
|
||||
const std::vector<double> &printable_heights,
|
||||
const std::vector<int> &filament_map,
|
||||
const std::vector<std::set<int>>& unprintable_filament_types );
|
||||
void apply_config(const PrintConfig& config);
|
||||
//1.9.7.52
|
||||
void set_filaments(const std::vector<Extruder>&filament_lists) { m_filament_lists=filament_lists;}
|
||||
@@ -826,6 +1152,7 @@ namespace Slic3r {
|
||||
}
|
||||
|
||||
private:
|
||||
void register_commands();
|
||||
void apply_config(const DynamicPrintConfig& config);
|
||||
void apply_config_simplify3d(const std::string& filename);
|
||||
void apply_config_superslicer(const std::string& filename);
|
||||
@@ -848,6 +1175,8 @@ namespace Slic3r {
|
||||
void process_G1(const GCodeReader::GCodeLine& line);
|
||||
void process_G2_G3(const GCodeReader::GCodeLine& line);
|
||||
|
||||
void process_VG1(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// QDS: handle delay command
|
||||
void process_G4(const GCodeReader::GCodeLine& line);
|
||||
|
||||
@@ -896,6 +1225,12 @@ namespace Slic3r {
|
||||
// Set extruder temperature
|
||||
void process_M104(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Process virtual command of M104, in order to help gcodeviewer work
|
||||
void process_VM104(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Process virtual command of M109, in order to help gcodeviewer work
|
||||
void process_VM109(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set fan speed
|
||||
void process_M106(const GCodeReader::GCodeLine& line);
|
||||
|
||||
@@ -956,19 +1291,25 @@ namespace Slic3r {
|
||||
// Unload the current filament into the MK3 MMU2 unit at the end of print.
|
||||
void process_M702(const GCodeReader::GCodeLine& line);
|
||||
|
||||
void process_SYNC(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Processes T line (Select Tool)
|
||||
void process_T(const GCodeReader::GCodeLine& line);
|
||||
void process_T(const std::string_view command);
|
||||
|
||||
void process_M1020(const GCodeReader::GCodeLine &line);
|
||||
|
||||
void process_filament_change(int id);
|
||||
//QDS: 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);
|
||||
void set_skippable_type(const std::string_view type);
|
||||
|
||||
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_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis, int extruder_id) const;
|
||||
float get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis, int extruder_id) const;
|
||||
float get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
Vec3f get_xyz_max_jerk(PrintEstimatedStatistics::ETimeMode mode) const;
|
||||
float get_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode) const;
|
||||
@@ -979,6 +1320,7 @@ namespace Slic3r {
|
||||
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);
|
||||
float get_extruder_change_time(size_t extruder_id);
|
||||
int get_filament_vitrification_temperature(size_t extrude_id);
|
||||
void process_custom_gcode_time(CustomGCode::Type code);
|
||||
void process_filaments(CustomGCode::Type code);
|
||||
@@ -989,6 +1331,13 @@ namespace Slic3r {
|
||||
void update_estimated_times_stats();
|
||||
//QDS:
|
||||
void update_slice_warnings();
|
||||
|
||||
// get current used filament
|
||||
int get_filament_id(bool force_initialize = true) const;
|
||||
// get last used filament in the same extruder with current filament
|
||||
int get_last_filament_id(bool force_initialize = true) const;
|
||||
//get current used extruder
|
||||
int get_extruder_id(bool force_initialize = true)const;
|
||||
};
|
||||
|
||||
} /* namespace Slic3r */
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <SVG.hpp>
|
||||
#endif
|
||||
|
||||
static const int average_filter_window_size = 5;
|
||||
namespace Slic3r {
|
||||
|
||||
namespace SeamPlacerImpl {
|
||||
@@ -1125,15 +1126,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl::
|
||||
|
||||
// gather vector of all seams on the print_object - pair of layer_index and seam__index within that layer
|
||||
const std::vector<PrintObjectSeamData::LayerSeams> &layers = m_seam_per_object[po].layers;
|
||||
std::vector<std::pair<size_t, size_t>> seams;
|
||||
for (size_t layer_idx = 0; layer_idx < layers.size(); ++layer_idx) {
|
||||
const std::vector<SeamCandidate> &layer_perimeter_points = layers[layer_idx].points;
|
||||
size_t current_point_index = 0;
|
||||
while (current_point_index < layer_perimeter_points.size()) {
|
||||
seams.emplace_back(layer_idx, layer_perimeter_points[current_point_index].perimeter.seam_index);
|
||||
current_point_index = layer_perimeter_points[current_point_index].perimeter.end_index;
|
||||
}
|
||||
}
|
||||
std::vector<std::pair<size_t, size_t>> seams = gather_all_seams_of_object(layers);
|
||||
|
||||
// sort them before alignment. Alignment is sensitive to initializaion, this gives it better chance to choose something nice
|
||||
std::stable_sort(seams.begin(), seams.end(), [&comparator, &layers](const std::pair<size_t, size_t> &left, const std::pair<size_t, size_t> &right) {
|
||||
@@ -1257,6 +1250,93 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl::
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<std::pair<size_t, size_t>> SeamPlacer::gather_all_seams_of_object(const std::vector<PrintObjectSeamData::LayerSeams> &layers)
|
||||
{
|
||||
// gather vector of all seams on the print_object - pair of layer_index and seam__index within that layer
|
||||
std::vector<std::pair<size_t, size_t>> seams;
|
||||
for (size_t layer_idx = 0; layer_idx < layers.size(); ++layer_idx) {
|
||||
const std::vector<SeamPlacerImpl::SeamCandidate> &layer_perimeter_points = layers[layer_idx].points;
|
||||
size_t current_point_index = 0;
|
||||
while (current_point_index < layer_perimeter_points.size()) {
|
||||
seams.emplace_back(layer_idx, layer_perimeter_points[current_point_index].perimeter.seam_index);
|
||||
current_point_index = layer_perimeter_points[current_point_index].perimeter.end_index;
|
||||
}
|
||||
}
|
||||
return seams;
|
||||
}
|
||||
|
||||
void SeamPlacer::filter_scarf_seam_switch_by_angle(const float &angle, std::vector<PrintObjectSeamData::LayerSeams> &layers)
|
||||
{
|
||||
std::vector<std::pair<size_t, size_t>> seams = gather_all_seams_of_object(layers);
|
||||
|
||||
float max_distance = SeamPlacer::seam_align_tolerable_dist_factor * layers[seams[0].first].points[seams[0].second].perimeter.flow_width;
|
||||
|
||||
std::vector<int> seam_index_pos;
|
||||
std::vector<std::vector<int>> seam_index_group;
|
||||
//get each seam line group
|
||||
for (size_t seam_idx = 0; seam_idx < seams.size(); seam_idx++) {
|
||||
if (layers[seams[seam_idx].first].points[seams[seam_idx].second].is_grouped)
|
||||
continue;
|
||||
|
||||
layers[seams[seam_idx].first].points[seams[seam_idx].second].is_grouped = true;
|
||||
seam_index_pos.push_back(seam_idx);
|
||||
size_t prev_idx = seam_idx;
|
||||
size_t next_seam = seam_idx + 1;
|
||||
for (; next_seam < seams.size(); next_seam++) {
|
||||
if (layers[seams[next_seam].first].points[seams[next_seam].second].is_grouped || seams[prev_idx].first == seams[next_seam].first)
|
||||
continue;
|
||||
|
||||
// if the seam is not continous with prev layer, break
|
||||
if (seams[prev_idx].first + 1 != seams[next_seam].first)
|
||||
break;
|
||||
|
||||
if ((layers[seams[prev_idx].first].points[seams[prev_idx].second].position - layers[seams[next_seam].first].points[seams[next_seam].second].position).norm() <=
|
||||
max_distance) {
|
||||
|
||||
layers[seams[next_seam].first].points[seams[next_seam].second].is_grouped = true;
|
||||
|
||||
float next_seam_angle = layers[seams[next_seam].first].points[seams[next_seam].second].local_ccw_angle;
|
||||
|
||||
if (next_seam_angle < 0)
|
||||
next_seam_angle *= -1;
|
||||
|
||||
if (PI - angle > next_seam_angle) {
|
||||
layers[seams[next_seam].first].points[seams[next_seam].second].enable_scarf_seam = true;
|
||||
}
|
||||
|
||||
prev_idx = next_seam;
|
||||
seam_index_pos.push_back(next_seam);
|
||||
}
|
||||
}
|
||||
|
||||
seam_index_group.emplace_back(std::move(seam_index_pos));
|
||||
seam_index_pos.clear();
|
||||
}
|
||||
|
||||
// filter
|
||||
{
|
||||
for (size_t k = 0; k < seam_index_group.size(); k++) {
|
||||
std::vector<int> seam_group = seam_index_group[k];
|
||||
if (seam_group.size() <= 1) continue;
|
||||
int half_window = average_filter_window_size / 2;
|
||||
// average filter
|
||||
for (size_t idx = 0; idx < seam_group.size(); idx++) {
|
||||
double sum = 0;
|
||||
int count = 0;
|
||||
|
||||
for (int window_idx = -half_window; window_idx <= half_window; ++window_idx) {
|
||||
int index = idx + window_idx;
|
||||
if (index >= 0 && index < seam_group.size()) {
|
||||
sum += layers[seams[seam_group[index]].first].points[seams[seam_group[index]].second].enable_scarf_seam ? 1 : 0;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
layers[seams[seam_group[idx]].first].points[seams[seam_group[idx]].second].enable_scarf_seam = (sum / count) >= 0.5 ? true : false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SeamPlacer::init(const Print &print, std::function<void(void)> throw_if_canceled_func)
|
||||
{
|
||||
using namespace SeamPlacerImpl;
|
||||
@@ -1311,13 +1391,21 @@ void SeamPlacer::init(const Print &print, std::function<void(void)> throw_if_can
|
||||
BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: align_seam_points : end";
|
||||
}
|
||||
|
||||
//check if enable scarf seam for each seam point
|
||||
if (configured_seam_preference == spAligned || configured_seam_preference == spRear) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: check_enable_scarf_seam : start";
|
||||
//find seam lines and get angle imformation
|
||||
filter_scarf_seam_switch_by_angle(po->config().scarf_angle_threshold / 180.0f * PI, m_seam_per_object[po].layers);
|
||||
//filter scarf seam setting with gaussian filter
|
||||
BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: check_enable_scarf_seam : end";
|
||||
}
|
||||
#ifdef DEBUG_FILES
|
||||
debug_export_points(m_seam_per_object[po].layers, po->bounding_box(), comparator);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point &last_pos) const
|
||||
void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point &last_pos, bool &satisfy_angle_threshold) const
|
||||
{
|
||||
using namespace SeamPlacerImpl;
|
||||
const PrintObject *po = layer->object();
|
||||
@@ -1343,10 +1431,12 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern
|
||||
if (const Perimeter &perimeter = layer_perimeters.points[closest_perimeter_point_index].perimeter; perimeter.finalized) {
|
||||
seam_position = perimeter.final_seam_position;
|
||||
seam_index = perimeter.seam_index;
|
||||
satisfy_angle_threshold = layer_perimeters.points[seam_index].enable_scarf_seam;
|
||||
} else {
|
||||
seam_index = po->config().seam_position == spNearest ? pick_nearest_seam_point_index(layer_perimeters.points, perimeter.start_index, unscaled<float>(last_pos)) :
|
||||
perimeter.seam_index;
|
||||
seam_position = layer_perimeters.points[seam_index].position;
|
||||
satisfy_angle_threshold = layer_perimeters.points[seam_index].enable_scarf_seam;
|
||||
}
|
||||
|
||||
Point seam_point = Point::new_scale(seam_position.x(), seam_position.y());
|
||||
|
||||
@@ -69,7 +69,7 @@ struct Perimeter
|
||||
struct SeamCandidate
|
||||
{
|
||||
SeamCandidate(const Vec3f &pos, Perimeter &perimeter, float local_ccw_angle, EnforcedBlockedSeamPoint type)
|
||||
: position(pos), perimeter(perimeter), visibility(0.0f), overhang(0.0f), embedded_distance(0.0f), local_ccw_angle(local_ccw_angle), type(type), central_enforcer(false)
|
||||
: position(pos), perimeter(perimeter), visibility(0.0f), overhang(0.0f), embedded_distance(0.0f), local_ccw_angle(local_ccw_angle), type(type), central_enforcer(false), enable_scarf_seam(false),is_grouped(false)
|
||||
{}
|
||||
const Vec3f position;
|
||||
// pointer to Perimeter loop of this point. It is shared across all points of the loop
|
||||
@@ -82,6 +82,8 @@ struct SeamCandidate
|
||||
float local_ccw_angle;
|
||||
EnforcedBlockedSeamPoint type;
|
||||
bool central_enforcer; // marks this candidate as central point of enforced segment on the perimeter - important for alignment
|
||||
bool enable_scarf_seam; // marks this candidate as a candidate for scarf seam
|
||||
bool is_grouped;
|
||||
};
|
||||
|
||||
struct SeamCandidateCoordinateFunctor
|
||||
@@ -147,7 +149,7 @@ public:
|
||||
|
||||
void init(const Print &print, std::function<void(void)> throw_if_canceled_func);
|
||||
|
||||
void place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point &last_pos) const;
|
||||
void place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point &last_pos, bool &satisfy_angle_threshold) const;
|
||||
|
||||
private:
|
||||
void gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info, const SeamPosition configured_seam_preference);
|
||||
@@ -160,6 +162,8 @@ private:
|
||||
const size_t layer_idx,
|
||||
const float max_distance,
|
||||
const SeamPlacerImpl::SeamComparator & comparator) const;
|
||||
std::vector<std::pair<size_t, size_t>> gather_all_seams_of_object(const std::vector<PrintObjectSeamData::LayerSeams> &layers);
|
||||
void filter_scarf_seam_switch_by_angle(const float &angle, std::vector<PrintObjectSeamData::LayerSeams> &layers);
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
224
src/libslic3r/GCode/Smoothing.cpp
Normal file
224
src/libslic3r/GCode/Smoothing.cpp
Normal file
@@ -0,0 +1,224 @@
|
||||
#include "Smoothing.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void SmoothCalculator::build_node(std::vector<OutwallCollection> & wall_collection,
|
||||
const std::vector<int> & object_label,
|
||||
const std::vector<PerExtruderAdjustments> &per_extruder_adjustments)
|
||||
{
|
||||
if (per_extruder_adjustments.empty())
|
||||
return;
|
||||
// QDS: update outwall feedrate
|
||||
// update feedrate of outwall after initial cooling process
|
||||
// initial and arrange node collection seq
|
||||
for (size_t object_idx = 0; object_idx < object_label.size(); ++object_idx) {
|
||||
OutwallCollection object_level;
|
||||
object_level.object_id = object_label[object_idx];
|
||||
wall_collection.push_back(object_level);
|
||||
}
|
||||
|
||||
for (size_t extruder_idx = 0; extruder_idx < per_extruder_adjustments.size(); ++extruder_idx) {
|
||||
const PerExtruderAdjustments &extruder_adjustments = per_extruder_adjustments[extruder_idx];
|
||||
for (size_t line_idx = 0; line_idx < extruder_adjustments.lines.size(); ++line_idx) {
|
||||
const CoolingLine &line = extruder_adjustments.lines[line_idx];
|
||||
if (line.outwall_smooth_mark) {
|
||||
// search node id
|
||||
if (wall_collection[line.object_id].cooling_nodes.count(line.cooling_node_id) == 0) {
|
||||
CoolingNode node;
|
||||
wall_collection[line.object_id].cooling_nodes.emplace(line.cooling_node_id, node);
|
||||
}
|
||||
|
||||
CoolingNode &node = wall_collection[line.object_id].cooling_nodes[line.cooling_node_id];
|
||||
if (line.type & CoolingLine::TYPE_EXTERNAL_PERIMETER) {
|
||||
node.outwall_line.emplace_back(line_idx, extruder_idx);
|
||||
if (node.max_feedrate < line.feedrate) {
|
||||
node.max_feedrate = line.feedrate;
|
||||
node.filter_feedrate = node.max_feedrate;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void exclude_participate_in_speed_slowdown(std::vector<std::pair<int, int>> & lines,
|
||||
std::vector<PerExtruderAdjustments> &per_extruder_adjustments,
|
||||
CoolingNode & node)
|
||||
{
|
||||
// QDS: add protect, feedrate will be 0 if the outwall is overhang. just apply not adjust flage
|
||||
bool apply_speed = node.max_feedrate > 0 && node.filter_feedrate > 0;
|
||||
if (apply_speed) node.rate = node.filter_feedrate / node.max_feedrate;
|
||||
|
||||
for (std::pair<int, int> line_pos : lines) {
|
||||
CoolingLine &line = per_extruder_adjustments[line_pos.second].lines[line_pos.first];
|
||||
if (apply_speed && line.feedrate > node.filter_feedrate) {
|
||||
line.feedrate = node.filter_feedrate;
|
||||
line.slowdown = true;
|
||||
}
|
||||
|
||||
// not adjust outwal line speed
|
||||
line.type = line.type & (~CoolingLine::TYPE_ADJUSTABLE);
|
||||
// update time cost
|
||||
if (line.feedrate == 0 || line.length == 0)
|
||||
line.time = 0;
|
||||
else
|
||||
line.time = line.length / line.feedrate;
|
||||
}
|
||||
}
|
||||
|
||||
float SmoothCalculator::recaculate_layer_time(int layer_id, std::vector<PerExtruderAdjustments> &extruder_adjustments)
|
||||
{
|
||||
// rewrite feedrate
|
||||
for (size_t obj_id = 0; obj_id < layers_wall_collection[layer_id].size(); ++obj_id) {
|
||||
for (size_t node_id = 0; node_id < layers_wall_collection[layer_id][obj_id].cooling_nodes.size(); ++node_id) {
|
||||
CoolingNode &node = layers_wall_collection[layer_id][obj_id].cooling_nodes[node_id];
|
||||
// set outwall speed
|
||||
exclude_participate_in_speed_slowdown(node.outwall_line, extruder_adjustments, node);
|
||||
}
|
||||
}
|
||||
|
||||
float layer_time = 0;
|
||||
for (PerExtruderAdjustments extruder : extruder_adjustments) {
|
||||
layer_time += extruder.collection_line_times_of_extruder();
|
||||
}
|
||||
|
||||
return layer_time;
|
||||
};
|
||||
|
||||
void SmoothCalculator::init_object_node_range()
|
||||
{
|
||||
for (size_t object_id = 0; object_id < objects_node_range.size(); ++object_id) {
|
||||
for (size_t layer_id = 1; layer_id < layers_wall_collection.size(); ++layer_id) {
|
||||
const OutwallCollection &each_object = layers_wall_collection[layer_id][object_id];
|
||||
auto it = each_object.cooling_nodes.begin();
|
||||
while (it != each_object.cooling_nodes.end()) {
|
||||
if (objects_node_range[object_id].count(it->first) == 0) {
|
||||
objects_node_range[object_id].emplace(it->first, std::pair<int, int>(layer_id, layer_id));
|
||||
} else {
|
||||
objects_node_range[object_id][it->first].second = layer_id;
|
||||
}
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SmoothCalculator::smooth_layer_speed()
|
||||
{
|
||||
init_object_node_range();
|
||||
|
||||
for (size_t obj_id = 0; obj_id < objects_node_range.size(); ++obj_id) {
|
||||
auto it = objects_node_range[obj_id].begin();
|
||||
while (it != objects_node_range[obj_id].end()) {
|
||||
int step_count = 0;
|
||||
while (step_count < max_steps_count && speed_filter_continue(obj_id, it->first)) {
|
||||
step_count++;
|
||||
layer_speed_filter(obj_id, it->first);
|
||||
}
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SmoothCalculator::layer_speed_filter(const int object_id, const int node_id)
|
||||
{
|
||||
int start_pos = guassian_filter.size() / 2;
|
||||
// first layer don't need to be smoothed
|
||||
int layer_start = objects_node_range[object_id][node_id].first;
|
||||
int layer_end = objects_node_range[object_id][node_id].second;
|
||||
|
||||
// QDS: some layers may empty as the support has indenpendent layer
|
||||
for (int layer_id = layer_start; layer_id <= layer_end; ++layer_id) {
|
||||
if (layers_wall_collection[layer_id].empty()) continue;
|
||||
|
||||
if (layers_wall_collection[layer_id][object_id].cooling_nodes.count(node_id) == 0) break;
|
||||
|
||||
CoolingNode &node = layers_wall_collection[layer_id][object_id].cooling_nodes[node_id];
|
||||
|
||||
if (node.outwall_line.empty()) continue;
|
||||
|
||||
double conv_sum = 0;
|
||||
for (int filter_pos_idx = 0; filter_pos_idx < guassian_filter.size(); ++filter_pos_idx) {
|
||||
int remap_data_pos = layer_id - start_pos + filter_pos_idx;
|
||||
|
||||
if (remap_data_pos < layer_start)
|
||||
remap_data_pos = layer_start;
|
||||
else if (remap_data_pos > layer_end)
|
||||
remap_data_pos = layer_end;
|
||||
|
||||
// some node may not start at layer 1
|
||||
double remap_data = node.filter_feedrate;
|
||||
if (!layers_wall_collection[remap_data_pos][object_id].cooling_nodes[node_id].outwall_line.empty())
|
||||
remap_data = layers_wall_collection[remap_data_pos][object_id].cooling_nodes[node_id].filter_feedrate;
|
||||
|
||||
conv_sum += guassian_filter[filter_pos_idx] * remap_data;
|
||||
}
|
||||
double filter_res = conv_sum / filter_sum;
|
||||
if (filter_res < node.filter_feedrate) node.filter_feedrate = filter_res;
|
||||
}
|
||||
}
|
||||
|
||||
bool SmoothCalculator::speed_filter_continue(const int object_id, const int node_id)
|
||||
{
|
||||
int layer_id = objects_node_range[object_id][node_id].first;
|
||||
int layer_end = objects_node_range[object_id][node_id].second;
|
||||
|
||||
// QDS: some layers may empty as the support has indenpendent layer
|
||||
for (; layer_id < layer_end; ++layer_id) {
|
||||
if (std::abs(layers_wall_collection[layer_id + 1][object_id].cooling_nodes[node_id].filter_feedrate -
|
||||
layers_wall_collection[layer_id][object_id].cooling_nodes[node_id].filter_feedrate) > guassian_stop_threshold)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SmoothCalculator::filter_layer_time()
|
||||
{
|
||||
int start_pos = guassian_filter.size() / 2;
|
||||
// first layer don't need to be smoothed
|
||||
for (int layer_id = 1; layer_id < layers_cooling_time.size(); ++layer_id) {
|
||||
if (layers_cooling_time[layer_id] > layer_time_smoothing_threshold) continue;
|
||||
|
||||
double conv_sum = 0;
|
||||
for (int filter_pos_idx = 0; filter_pos_idx < guassian_filter.size(); ++filter_pos_idx) {
|
||||
int remap_data_pos = layer_id - start_pos + filter_pos_idx;
|
||||
|
||||
if (remap_data_pos < 1)
|
||||
remap_data_pos = 1;
|
||||
else if (remap_data_pos > layers_cooling_time.size() - 1)
|
||||
remap_data_pos = layers_cooling_time.size() - 1;
|
||||
|
||||
// if the layer time big enough, surface defact will disappear
|
||||
double data_temp = layers_cooling_time[remap_data_pos] > layer_time_smoothing_threshold ? layer_time_smoothing_threshold : layers_cooling_time[remap_data_pos];
|
||||
|
||||
conv_sum += guassian_filter[filter_pos_idx] * data_temp;
|
||||
}
|
||||
double filter_res = conv_sum / filter_sum;
|
||||
filter_res = filter_res > layer_time_smoothing_threshold ? layer_time_smoothing_threshold : filter_res;
|
||||
if (filter_res > layers_cooling_time[layer_id]) layers_cooling_time[layer_id] = filter_res;
|
||||
}
|
||||
}
|
||||
|
||||
bool SmoothCalculator::layer_time_filter_continue()
|
||||
{
|
||||
for (int layer_id = 1; layer_id < layers_cooling_time.size() - 1; ++layer_id) {
|
||||
double layer_time = layers_cooling_time[layer_id] > layer_time_smoothing_threshold ? layer_time_smoothing_threshold : layers_cooling_time[layer_id];
|
||||
double layer_time_cmp = layers_cooling_time[layer_id + 1] > layer_time_smoothing_threshold ? layer_time_smoothing_threshold : layers_cooling_time[layer_id + 1];
|
||||
|
||||
if (std::abs(layer_time - layer_time_cmp) > guassian_layer_time_stop_threshold) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SmoothCalculator::smooth_layer_time()
|
||||
{
|
||||
int step_count = 0;
|
||||
while (step_count < max_steps_count && layer_time_filter_continue()) {
|
||||
step_count++;
|
||||
filter_layer_time();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
103
src/libslic3r/GCode/Smoothing.hpp
Normal file
103
src/libslic3r/GCode/Smoothing.hpp
Normal file
@@ -0,0 +1,103 @@
|
||||
#ifndef slic3r_Smoothing_hpp_
|
||||
#define slic3r_Smoothing_hpp_
|
||||
#include "../libslic3r.h"
|
||||
#include <libslic3r/GCode/GCodeEditor.hpp>
|
||||
#include <math.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
static const int guassian_window_size = 11;
|
||||
static const int guassian_r = 2;
|
||||
static const int guassian_stop_threshold = 5;
|
||||
static const float guassian_layer_time_stop_threshold = 3.0;
|
||||
static const int max_steps_count = 1000;
|
||||
|
||||
struct CoolingNode
|
||||
{
|
||||
// extruder pos, line pos;
|
||||
std::vector<std::pair<int, int>> outwall_line;
|
||||
float max_feedrate = 0;
|
||||
float filter_feedrate = 0;
|
||||
double rate = 1;
|
||||
};
|
||||
|
||||
struct OutwallCollection
|
||||
{
|
||||
int object_id;
|
||||
std::map<int, CoolingNode> cooling_nodes;
|
||||
};
|
||||
|
||||
class SmoothCalculator
|
||||
{
|
||||
|
||||
public:
|
||||
std::vector<std::map<int, std::pair<int, int>>> objects_node_range;
|
||||
std::vector<std::vector<OutwallCollection>> layers_wall_collection;
|
||||
std::vector<float> layers_cooling_time;
|
||||
|
||||
SmoothCalculator(const int objects_size, const double gap_limit) : layer_time_smoothing_threshold(gap_limit)
|
||||
{
|
||||
guassian_filter_generator();
|
||||
objects_node_range.resize(objects_size);
|
||||
}
|
||||
|
||||
SmoothCalculator(const int objects_size)
|
||||
{
|
||||
guassian_filter_generator();
|
||||
objects_node_range.resize(objects_size);
|
||||
}
|
||||
|
||||
void append_data(const std::vector<OutwallCollection> &wall_collection, float cooling_time)
|
||||
{
|
||||
layers_wall_collection.push_back(wall_collection);
|
||||
layers_cooling_time.push_back(cooling_time);
|
||||
}
|
||||
|
||||
void append_data(const std::vector<OutwallCollection> &wall_collection)
|
||||
{
|
||||
layers_wall_collection.push_back(wall_collection);
|
||||
}
|
||||
|
||||
void build_node(std::vector<OutwallCollection> &wall_collection, const std::vector<int> &object_label, const std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
|
||||
|
||||
float recaculate_layer_time(int layer_id, std::vector<PerExtruderAdjustments> &extruder_adjustments);
|
||||
|
||||
void smooth_layer_speed();
|
||||
|
||||
private:
|
||||
// guassian filter
|
||||
double guassian_function(double x, double r) {
|
||||
return exp(-x * x / (2 * r * r)) / (r * sqrt(2 * PI));
|
||||
}
|
||||
|
||||
void guassian_filter_generator() {
|
||||
double r = guassian_r;
|
||||
int half_win_size = guassian_window_size / 2;
|
||||
for (int start = -half_win_size; start <= half_win_size; ++start) {
|
||||
double y = guassian_function(start, r);
|
||||
filter_sum += y;
|
||||
guassian_filter.push_back(y);
|
||||
}
|
||||
}
|
||||
|
||||
void init_object_node_range();
|
||||
|
||||
// filter the data
|
||||
void layer_speed_filter(const int object_id, const int node_id);
|
||||
|
||||
bool speed_filter_continue(const int object_id, const int node_id);
|
||||
|
||||
// filter the data
|
||||
void filter_layer_time();
|
||||
|
||||
bool layer_time_filter_continue();
|
||||
void smooth_layer_time();
|
||||
|
||||
std::vector<double> guassian_filter;
|
||||
double filter_sum = .0f;
|
||||
float layer_time_smoothing_threshold = 30.0f;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
@@ -38,6 +38,9 @@ struct ThumbnailsParams
|
||||
bool show_bed;
|
||||
bool transparent_background;
|
||||
int plate_id;
|
||||
bool use_plate_box{true};
|
||||
bool post_processing_enabled{ false };
|
||||
Vec4f background_color{ 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
};
|
||||
|
||||
typedef std::function<ThumbnailsList(const ThumbnailsParams&)> ThumbnailsGeneratorCallback;
|
||||
|
||||
759
src/libslic3r/GCode/ToolOrderUtils.cpp
Normal file
759
src/libslic3r/GCode/ToolOrderUtils.cpp
Normal file
@@ -0,0 +1,759 @@
|
||||
#include "ToolOrderUtils.hpp"
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <cmath>
|
||||
#include <boost/multiprecision/cpp_int.hpp>
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
struct MinCostMaxFlow {
|
||||
public:
|
||||
struct Edge {
|
||||
int from, to, capacity, cost, flow;
|
||||
Edge(int u, int v, int cap, int cst) : from(u), to(v), capacity(cap), cost(cst), flow(0) {}
|
||||
};
|
||||
|
||||
std::vector<int> solve();
|
||||
void add_edge(int from, int to, int capacity, int cost);
|
||||
bool spfa(int source, int sink);
|
||||
int get_distance(int idx_in_left, int idx_in_right);
|
||||
|
||||
std::vector<std::vector<float>> matrix;
|
||||
std::vector<int> l_nodes;
|
||||
std::vector<int> r_nodes;
|
||||
std::vector<Edge> edges;
|
||||
std::vector<std::vector<int>> adj;
|
||||
|
||||
int total_nodes{ -1 };
|
||||
int source_id{ -1 };
|
||||
int sink_id{ -1 };
|
||||
};
|
||||
|
||||
std::vector<int> MinCostMaxFlow::solve()
|
||||
{
|
||||
while (spfa(source_id, sink_id));
|
||||
|
||||
std::vector<int>matching(l_nodes.size(), MaxFlowGraph::INVALID_ID);
|
||||
// to get the match info, just traverse the left nodes and
|
||||
// check the edges with flow > 0 and linked to right nodes
|
||||
for (int u = 0; u < l_nodes.size(); ++u) {
|
||||
for (int eid : adj[u]) {
|
||||
Edge& e = edges[eid];
|
||||
if (e.flow > 0 && e.to >= l_nodes.size() && e.to < l_nodes.size() + r_nodes.size())
|
||||
matching[e.from] = r_nodes[e.to - l_nodes.size()];
|
||||
}
|
||||
}
|
||||
|
||||
return matching;
|
||||
}
|
||||
|
||||
void MinCostMaxFlow::add_edge(int from, int to, int capacity, int cost)
|
||||
{
|
||||
adj[from].emplace_back(edges.size());
|
||||
edges.emplace_back(from, to, capacity, cost);
|
||||
//also add reverse edge ,set capacity to zero,cost to negative
|
||||
adj[to].emplace_back(edges.size());
|
||||
edges.emplace_back(to, from, 0, -cost);
|
||||
}
|
||||
|
||||
bool MinCostMaxFlow::spfa(int source, int sink)
|
||||
{
|
||||
std::vector<int>dist(total_nodes, MaxFlowGraph::INF);
|
||||
std::vector<bool>in_queue(total_nodes, false);
|
||||
std::vector<int>flow(total_nodes, MaxFlowGraph::INF);
|
||||
std::vector<int>prev(total_nodes, 0);
|
||||
|
||||
std::queue<int>q;
|
||||
q.push(source);
|
||||
in_queue[source] = true;
|
||||
dist[source] = 0;
|
||||
|
||||
while (!q.empty()) {
|
||||
int now_at = q.front();
|
||||
q.pop();
|
||||
in_queue[now_at] = false;
|
||||
|
||||
for (auto eid : adj[now_at]) //traverse all linked edges
|
||||
{
|
||||
Edge& e = edges[eid];
|
||||
if (e.flow<e.capacity && dist[e.to]>dist[now_at] + e.cost) {
|
||||
dist[e.to] = dist[now_at] + e.cost;
|
||||
prev[e.to] = eid;
|
||||
flow[e.to] = std::min(flow[now_at], e.capacity - e.flow);
|
||||
if (!in_queue[e.to]) {
|
||||
q.push(e.to);
|
||||
in_queue[e.to] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dist[sink] == MaxFlowGraph::INF)
|
||||
return false;
|
||||
|
||||
int now_at = sink;
|
||||
while (now_at != source) {
|
||||
int prev_edge = prev[now_at];
|
||||
edges[prev_edge].flow += flow[sink];
|
||||
edges[prev_edge ^ 1].flow -= flow[sink];
|
||||
now_at = edges[prev_edge].from;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int MinCostMaxFlow::get_distance(int idx_in_left, int idx_in_right)
|
||||
{
|
||||
if (l_nodes[idx_in_left] == -1) {
|
||||
return 0;
|
||||
//TODO: test more here
|
||||
int sum = 0;
|
||||
for (int i = 0; i < matrix.size(); ++i)
|
||||
sum += matrix[i][idx_in_right];
|
||||
sum /= matrix.size();
|
||||
return -sum;
|
||||
}
|
||||
|
||||
return matrix[l_nodes[idx_in_left]][r_nodes[idx_in_right]];
|
||||
}
|
||||
|
||||
|
||||
MaxFlowSolver::MaxFlowSolver(const std::vector<int>& u_nodes, const std::vector<int>& v_nodes,
|
||||
const std::unordered_map<int, std::vector<int>>& uv_link_limits,
|
||||
const std::unordered_map<int, std::vector<int>>& uv_unlink_limits,
|
||||
const std::vector<int>& u_capacity,
|
||||
const std::vector<int>& v_capacity)
|
||||
{
|
||||
assert(u_capacity.empty() || u_capacity.size() == u_nodes.size());
|
||||
assert(v_capacity.empty() || v_capacity.size() == v_nodes.size());
|
||||
l_nodes = u_nodes;
|
||||
r_nodes = v_nodes;
|
||||
total_nodes = u_nodes.size() + v_nodes.size() + 2;
|
||||
source_id = total_nodes - 2;
|
||||
sink_id = total_nodes - 1;
|
||||
|
||||
adj.resize(total_nodes);
|
||||
|
||||
// add edge from source to left nodes
|
||||
for (int idx = 0; idx < l_nodes.size(); ++idx) {
|
||||
int capacity = u_capacity.empty() ? 1 : u_capacity[idx];
|
||||
add_edge(source_id, idx, capacity);
|
||||
}
|
||||
// add edge from right nodes to sink node
|
||||
for (int idx = 0; idx < r_nodes.size(); ++idx) {
|
||||
int capacity = v_capacity.empty() ? 1 : v_capacity[idx];
|
||||
add_edge(l_nodes.size() + idx, sink_id, capacity);
|
||||
}
|
||||
|
||||
// add edge from left nodes to right nodes
|
||||
for (int i = 0; i < l_nodes.size(); ++i) {
|
||||
int from_idx = i;
|
||||
// process link limits , i can only link to uv_link_limits
|
||||
if (auto iter = uv_link_limits.find(i); iter != uv_link_limits.end()) {
|
||||
for (auto r_id : iter->second)
|
||||
add_edge(from_idx, l_nodes.size() + r_id, 1);
|
||||
continue;
|
||||
}
|
||||
// process unlink limits
|
||||
std::optional<std::vector<int>> unlink_limits;
|
||||
if (auto iter = uv_unlink_limits.find(i); iter != uv_unlink_limits.end())
|
||||
unlink_limits = iter->second;
|
||||
|
||||
for (int j = 0; j < r_nodes.size(); ++j) {
|
||||
// check whether i can link to j
|
||||
if (unlink_limits.has_value() && std::find(unlink_limits->begin(), unlink_limits->end(), j) != unlink_limits->end())
|
||||
continue;
|
||||
add_edge(from_idx, l_nodes.size() + j, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MaxFlowSolver::add_edge(int from, int to, int capacity)
|
||||
{
|
||||
adj[from].emplace_back(edges.size());
|
||||
edges.emplace_back(from, to, capacity);
|
||||
//also add reverse edge ,set capacity to zero
|
||||
adj[to].emplace_back(edges.size());
|
||||
edges.emplace_back(to, from, 0);
|
||||
}
|
||||
|
||||
std::vector<int> MaxFlowSolver::solve() {
|
||||
std::vector<int> augment;
|
||||
std::vector<int> previous(total_nodes, 0);
|
||||
while (1) {
|
||||
std::vector<int>(total_nodes, 0).swap(augment);
|
||||
std::queue<int> travel;
|
||||
travel.push(source_id);
|
||||
augment[source_id] = MaxFlowGraph::INF;
|
||||
while (!travel.empty()) {
|
||||
int from = travel.front();
|
||||
travel.pop();
|
||||
|
||||
// traverse all linked edges
|
||||
for (int i = 0; i < adj[from].size(); ++i) {
|
||||
int eid = adj[from][i];
|
||||
Edge& tmp = edges[eid];
|
||||
if (augment[tmp.to] == 0 && tmp.capacity > tmp.flow) {
|
||||
previous[tmp.to] = eid;
|
||||
augment[tmp.to] = std::min(augment[from], tmp.capacity - tmp.flow);
|
||||
travel.push(tmp.to);
|
||||
}
|
||||
}
|
||||
|
||||
// already find an extend path, stop and do update
|
||||
if (augment[sink_id] != 0)
|
||||
break;
|
||||
}
|
||||
// no longer have extend path
|
||||
if (augment[sink_id] == 0)
|
||||
break;
|
||||
|
||||
for (int i = sink_id; i != source_id; i = edges[previous[i]].from) {
|
||||
edges[previous[i]].flow += augment[sink_id];
|
||||
edges[previous[i] ^ 1].flow -= augment[sink_id];
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> matching(l_nodes.size(), MaxFlowGraph::INVALID_ID);
|
||||
// to get the match info, just traverse the left nodes and
|
||||
// check the edge with flow > 0 and linked to right nodes
|
||||
for (int u = 0; u < l_nodes.size(); ++u) {
|
||||
for (int eid : adj[u]) {
|
||||
Edge& e = edges[eid];
|
||||
if (e.flow > 0 && e.to >= l_nodes.size() && e.to < l_nodes.size() + r_nodes.size())
|
||||
matching[e.from] = r_nodes[e.to - l_nodes.size()];
|
||||
}
|
||||
}
|
||||
return matching;
|
||||
}
|
||||
|
||||
GeneralMinCostSolver::~GeneralMinCostSolver()
|
||||
{
|
||||
}
|
||||
|
||||
GeneralMinCostSolver::GeneralMinCostSolver(const std::vector<std::vector<float>>& matrix_, const std::vector<int>& u_nodes, const std::vector<int>& v_nodes)
|
||||
{
|
||||
m_solver = std::make_unique<MinCostMaxFlow>();
|
||||
m_solver->matrix = matrix_;;
|
||||
m_solver->l_nodes = u_nodes;
|
||||
m_solver->r_nodes = v_nodes;
|
||||
|
||||
m_solver->total_nodes = u_nodes.size() + v_nodes.size() + 2;
|
||||
|
||||
m_solver->source_id =m_solver->total_nodes - 2;
|
||||
m_solver->sink_id = m_solver->total_nodes - 1;
|
||||
|
||||
m_solver->adj.resize(m_solver->total_nodes);
|
||||
|
||||
|
||||
// add edge from source to left nodes,cost to 0
|
||||
for (int i = 0; i < m_solver->l_nodes.size(); ++i)
|
||||
m_solver->add_edge(m_solver->source_id, i, 1, 0);
|
||||
|
||||
// add edge from right nodes to sink,cost to 0
|
||||
for (int i = 0; i < m_solver->r_nodes.size(); ++i)
|
||||
m_solver->add_edge(m_solver->l_nodes.size() + i, m_solver->sink_id, 1, 0);
|
||||
|
||||
// add edge from left node to right nodes
|
||||
for (int i = 0; i < m_solver->l_nodes.size(); ++i) {
|
||||
int from_idx = i;
|
||||
for (int j = 0; j < m_solver->r_nodes.size(); ++j) {
|
||||
int to_idx = m_solver->l_nodes.size() + j;
|
||||
m_solver->add_edge(from_idx, to_idx, 1, m_solver->get_distance(i, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> GeneralMinCostSolver::solve() {
|
||||
return m_solver->solve();
|
||||
}
|
||||
|
||||
MinFlushFlowSolver::~MinFlushFlowSolver()
|
||||
{
|
||||
}
|
||||
|
||||
MinFlushFlowSolver::MinFlushFlowSolver(const std::vector<std::vector<float>>& matrix_, const std::vector<int>& u_nodes, const std::vector<int>& v_nodes,
|
||||
const std::unordered_map<int, std::vector<int>>& uv_link_limits,
|
||||
const std::unordered_map<int, std::vector<int>>& uv_unlink_limits,
|
||||
const std::vector<int>& u_capacity,
|
||||
const std::vector<int>& v_capacity)
|
||||
{
|
||||
assert(u_capacity.empty() || u_capacity.size() == u_nodes.size());
|
||||
assert(v_capacity.empty() || v_capacity.size() == v_nodes.size());
|
||||
m_solver = std::make_unique<MinCostMaxFlow>();
|
||||
m_solver->matrix = matrix_;;
|
||||
m_solver->l_nodes = u_nodes;
|
||||
m_solver->r_nodes = v_nodes;
|
||||
|
||||
m_solver->total_nodes = u_nodes.size() + v_nodes.size() + 2;
|
||||
|
||||
m_solver->source_id =m_solver->total_nodes - 2;
|
||||
m_solver->sink_id = m_solver->total_nodes - 1;
|
||||
|
||||
m_solver->adj.resize(m_solver->total_nodes);
|
||||
|
||||
// add edge from source to left nodes,cost to 0
|
||||
for (int i = 0; i < m_solver->l_nodes.size(); ++i) {
|
||||
int capacity = u_capacity.empty() ? 1 : u_capacity[i];
|
||||
m_solver->add_edge(m_solver->source_id, i, capacity, 0);
|
||||
}
|
||||
// add edge from right nodes to sink,cost to 0
|
||||
for (int i = 0; i < m_solver->r_nodes.size(); ++i) {
|
||||
int capacity = v_capacity.empty() ? 1 : v_capacity[i];
|
||||
m_solver->add_edge(m_solver->l_nodes.size() + i, m_solver->sink_id, capacity, 0);
|
||||
}
|
||||
// add edge from left node to right nodes
|
||||
for (int i = 0; i < m_solver->l_nodes.size(); ++i) {
|
||||
int from_idx = i;
|
||||
// process link limits, i can only link to link_limits
|
||||
if (auto iter = uv_link_limits.find(i); iter != uv_link_limits.end()) {
|
||||
for (auto r_id : iter->second)
|
||||
m_solver->add_edge(from_idx, m_solver->l_nodes.size() + r_id, 1, m_solver->get_distance(i, r_id));
|
||||
continue;
|
||||
}
|
||||
|
||||
// process unlink limits, check whether i can link to j
|
||||
std::optional<std::vector<int>> unlink_limits;
|
||||
if (auto iter = uv_unlink_limits.find(i); iter != uv_unlink_limits.end())
|
||||
unlink_limits = iter->second;
|
||||
for (int j = 0; j < m_solver->r_nodes.size(); ++j) {
|
||||
if (unlink_limits.has_value() && std::find(unlink_limits->begin(), unlink_limits->end(), j) != unlink_limits->end())
|
||||
continue;
|
||||
m_solver->add_edge(from_idx, m_solver->l_nodes.size() + j, 1, m_solver->get_distance(i, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> MinFlushFlowSolver::solve() {
|
||||
return m_solver->solve();
|
||||
}
|
||||
|
||||
MatchModeGroupSolver::~MatchModeGroupSolver()
|
||||
{
|
||||
}
|
||||
|
||||
MatchModeGroupSolver::MatchModeGroupSolver(const std::vector<std::vector<float>>& matrix_, const std::vector<int>& u_nodes, const std::vector<int>& v_nodes, const std::vector<int>& v_capacity, const std::unordered_map<int, std::vector<int>>& uv_unlink_limits)
|
||||
{
|
||||
assert(v_nodes.size() == v_capacity.size());
|
||||
m_solver = std::make_unique<MinCostMaxFlow>();
|
||||
m_solver->matrix = matrix_;;
|
||||
m_solver->l_nodes = u_nodes;
|
||||
m_solver->r_nodes = v_nodes;
|
||||
|
||||
m_solver->total_nodes = u_nodes.size() + v_nodes.size() + 2;
|
||||
|
||||
m_solver->source_id = m_solver->total_nodes - 2;
|
||||
m_solver->sink_id = m_solver->total_nodes - 1;
|
||||
|
||||
m_solver->adj.resize(m_solver->total_nodes);
|
||||
|
||||
|
||||
// add edge from source to left nodes,cost to 0
|
||||
for (int i = 0; i < m_solver->l_nodes.size(); ++i)
|
||||
m_solver->add_edge(m_solver->source_id, i, 1, 0);
|
||||
|
||||
// add edge from right nodes to sink,cost to 0
|
||||
for (int i = 0; i < m_solver->r_nodes.size(); ++i)
|
||||
m_solver->add_edge(m_solver->l_nodes.size() + i, m_solver->sink_id, v_capacity[i], 0);
|
||||
|
||||
// add edge from left node to right nodes
|
||||
for (int i = 0; i < m_solver->l_nodes.size(); ++i) {
|
||||
int from_idx = i;
|
||||
|
||||
// process unlink limits, check whether i can link to j
|
||||
std::optional<std::vector<int>> unlink_limits;
|
||||
if (auto iter = uv_unlink_limits.find(i); iter != uv_unlink_limits.end())
|
||||
unlink_limits = iter->second;
|
||||
for (int j = 0; j < m_solver->r_nodes.size(); ++j) {
|
||||
if (unlink_limits.has_value() && std::find(unlink_limits->begin(), unlink_limits->end(), j) != unlink_limits->end())
|
||||
continue;
|
||||
m_solver->add_edge(from_idx, m_solver->l_nodes.size() + j, 1, m_solver->get_distance(i, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> MatchModeGroupSolver::solve() {
|
||||
return m_solver->solve();
|
||||
}
|
||||
|
||||
//solve the problem by searching the least flush of current filament
|
||||
static std::vector<unsigned int> solve_extruder_order_with_greedy(const std::vector<std::vector<float>>& wipe_volumes,
|
||||
const std::vector<unsigned int> curr_layer_extruders,
|
||||
const std::optional<unsigned int>& start_extruder_id,
|
||||
float* min_cost)
|
||||
{
|
||||
float cost = 0;
|
||||
std::vector<unsigned int> best_seq;
|
||||
std::vector<bool>is_visited(curr_layer_extruders.size(), false);
|
||||
std::optional<unsigned int>prev_filament = start_extruder_id;
|
||||
int idx = curr_layer_extruders.size();
|
||||
while (idx > 0) {
|
||||
if (!prev_filament) {
|
||||
auto iter = std::find_if(is_visited.begin(), is_visited.end(), [](auto item) {return item == 0; });
|
||||
assert(iter != is_visited.end());
|
||||
prev_filament = curr_layer_extruders[iter - is_visited.begin()];
|
||||
}
|
||||
int target_idx = -1;
|
||||
int target_cost = std::numeric_limits<int>::max();
|
||||
for (size_t k = 0; k < is_visited.size(); ++k) {
|
||||
if (!is_visited[k]) {
|
||||
if (wipe_volumes[*prev_filament][curr_layer_extruders[k]] < target_cost) {
|
||||
target_idx = k;
|
||||
target_cost = wipe_volumes[*prev_filament][curr_layer_extruders[k]];
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(target_idx != -1);
|
||||
cost += target_cost;
|
||||
best_seq.emplace_back(curr_layer_extruders[target_idx]);
|
||||
prev_filament = curr_layer_extruders[target_idx];
|
||||
is_visited[target_idx] = true;
|
||||
idx -= 1;
|
||||
}
|
||||
if (min_cost)
|
||||
*min_cost = cost;
|
||||
return best_seq;
|
||||
}
|
||||
|
||||
//solve the problem by forcasting one layer
|
||||
static std::vector<unsigned int> solve_extruder_order_with_forcast(const std::vector<std::vector<float>>& wipe_volumes,
|
||||
std::vector<unsigned int> curr_layer_extruders,
|
||||
std::vector<unsigned int> next_layer_extruders,
|
||||
const std::optional<unsigned int>& start_extruder_id,
|
||||
float* min_cost)
|
||||
{
|
||||
std::sort(curr_layer_extruders.begin(), curr_layer_extruders.end());
|
||||
std::sort(next_layer_extruders.begin(), next_layer_extruders.end());
|
||||
float best_cost = std::numeric_limits<float>::max();
|
||||
std::vector<unsigned int>best_seq;
|
||||
|
||||
do {
|
||||
std::optional<unsigned int>prev_extruder_1 = start_extruder_id;
|
||||
float curr_layer_cost = 0;
|
||||
for (size_t idx = 0; idx < curr_layer_extruders.size(); ++idx) {
|
||||
if (prev_extruder_1)
|
||||
curr_layer_cost += wipe_volumes[*prev_extruder_1][curr_layer_extruders[idx]];
|
||||
prev_extruder_1 = curr_layer_extruders[idx];
|
||||
}
|
||||
if (curr_layer_cost > best_cost)
|
||||
continue;
|
||||
do {
|
||||
std::optional<unsigned int>prev_extruder_2 = prev_extruder_1;
|
||||
float total_cost = curr_layer_cost;
|
||||
|
||||
for (size_t idx = 0; idx < next_layer_extruders.size(); ++idx) {
|
||||
if (prev_extruder_2)
|
||||
total_cost += wipe_volumes[*prev_extruder_2][next_layer_extruders[idx]];
|
||||
prev_extruder_2 = next_layer_extruders[idx];
|
||||
}
|
||||
|
||||
if (total_cost < best_cost) {
|
||||
best_cost = total_cost;
|
||||
best_seq = curr_layer_extruders;
|
||||
}
|
||||
} while (std::next_permutation(next_layer_extruders.begin(), next_layer_extruders.end()));
|
||||
} while (std::next_permutation(curr_layer_extruders.begin(), curr_layer_extruders.end()));
|
||||
|
||||
if (min_cost) {
|
||||
float real_cost = 0;
|
||||
std::optional<unsigned int>prev_extruder = start_extruder_id;
|
||||
for (size_t idx = 0; idx < best_seq.size(); ++idx) {
|
||||
if (prev_extruder)
|
||||
real_cost += wipe_volumes[*prev_extruder][best_seq[idx]];
|
||||
prev_extruder = best_seq[idx];
|
||||
}
|
||||
*min_cost = real_cost;
|
||||
}
|
||||
return best_seq;
|
||||
}
|
||||
|
||||
// Shortest hamilton path problem
|
||||
static std::vector<unsigned int> solve_extruder_order(const std::vector<std::vector<float>>& wipe_volumes,
|
||||
std::vector<unsigned int> all_extruders,
|
||||
std::optional<unsigned int> start_extruder_id,
|
||||
float* min_cost)
|
||||
{
|
||||
bool add_start_extruder_flag = false;
|
||||
|
||||
if (start_extruder_id) {
|
||||
auto start_iter = std::find(all_extruders.begin(), all_extruders.end(), start_extruder_id);
|
||||
if (start_iter == all_extruders.end())
|
||||
all_extruders.insert(all_extruders.begin(), *start_extruder_id), add_start_extruder_flag = true;
|
||||
else
|
||||
std::swap(*all_extruders.begin(), *start_iter);
|
||||
}
|
||||
else {
|
||||
start_extruder_id = all_extruders.front();
|
||||
}
|
||||
|
||||
unsigned int iterations = (1 << all_extruders.size());
|
||||
unsigned int final_state = iterations - 1;
|
||||
std::vector<std::vector<float>>cache(iterations, std::vector<float>(all_extruders.size(), 0x7fffffff));
|
||||
std::vector<std::vector<int>>prev(iterations, std::vector<int>(all_extruders.size(), -1));
|
||||
cache[1][0] = 0.;
|
||||
for (unsigned int state = 0; state < iterations; ++state) {
|
||||
if (state & 1) {
|
||||
for (unsigned int target = 0; target < all_extruders.size(); ++target) {
|
||||
if (state >> target & 1) {
|
||||
for (unsigned int mid_point = 0; mid_point < all_extruders.size(); ++mid_point) {
|
||||
if (state >> mid_point & 1) {
|
||||
auto tmp = cache[state - (1 << target)][mid_point] + wipe_volumes[all_extruders[mid_point]][all_extruders[target]];
|
||||
if (cache[state][target] > tmp) {
|
||||
cache[state][target] = tmp;
|
||||
prev[state][target] = mid_point;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//get res
|
||||
float cost = std::numeric_limits<float>::max();
|
||||
int final_dst = 0;
|
||||
for (unsigned int dst = 0; dst < all_extruders.size(); ++dst) {
|
||||
if (all_extruders[dst] != start_extruder_id && cost > cache[final_state][dst]) {
|
||||
cost = cache[final_state][dst];
|
||||
if (min_cost)
|
||||
*min_cost = cost;
|
||||
final_dst = dst;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<unsigned int>path;
|
||||
unsigned int curr_state = final_state;
|
||||
int curr_point = final_dst;
|
||||
while (curr_point != -1) {
|
||||
path.emplace_back(all_extruders[curr_point]);
|
||||
auto mid_point = prev[curr_state][curr_point];
|
||||
curr_state -= (1 << curr_point);
|
||||
curr_point = mid_point;
|
||||
};
|
||||
|
||||
if (add_start_extruder_flag)
|
||||
path.pop_back();
|
||||
|
||||
std::reverse(path.begin(), path.end());
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
|
||||
template<class T>
|
||||
static std::vector<T> collect_filaments_in_groups(const std::unordered_set<unsigned int>& group, const std::vector<unsigned int>& filament_list) {
|
||||
std::vector<T>ret;
|
||||
ret.reserve(group.size());
|
||||
for (auto& f : filament_list) {
|
||||
if (auto iter = group.find(f); iter != group.end())
|
||||
ret.emplace_back(static_cast<T>(f));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// get best filament order of single nozzle
|
||||
std::vector<unsigned int> get_extruders_order(const std::vector<std::vector<float>>& wipe_volumes,
|
||||
const std::vector<unsigned int>& curr_layer_extruders,
|
||||
const std::vector<unsigned int>& next_layer_extruders,
|
||||
const std::optional<unsigned int>& start_extruder_id,
|
||||
bool use_forcast,
|
||||
float* cost)
|
||||
{
|
||||
if (curr_layer_extruders.empty()) {
|
||||
if (cost)
|
||||
*cost = 0;
|
||||
return curr_layer_extruders;
|
||||
}
|
||||
if (curr_layer_extruders.size() == 1) {
|
||||
if (cost) {
|
||||
*cost = 0;
|
||||
if (start_extruder_id)
|
||||
*cost = wipe_volumes[*start_extruder_id][curr_layer_extruders[0]];
|
||||
}
|
||||
return curr_layer_extruders;
|
||||
}
|
||||
|
||||
if (use_forcast)
|
||||
return solve_extruder_order_with_forcast(wipe_volumes, curr_layer_extruders, next_layer_extruders, start_extruder_id, cost);
|
||||
else if (curr_layer_extruders.size() <= 20)
|
||||
return solve_extruder_order(wipe_volumes, curr_layer_extruders, start_extruder_id, cost);
|
||||
else
|
||||
return solve_extruder_order_with_greedy(wipe_volumes, curr_layer_extruders, start_extruder_id, cost);
|
||||
}
|
||||
|
||||
|
||||
int reorder_filaments_for_minimum_flush_volume(const std::vector<unsigned int>& filament_lists,
|
||||
const std::vector<int>& filament_maps,
|
||||
const std::vector<std::vector<unsigned int>>& layer_filaments,
|
||||
const std::vector<FlushMatrix>& flush_matrix,
|
||||
std::optional<std::function<bool(int, std::vector<int>&)>> get_custom_seq,
|
||||
std::vector<std::vector<unsigned int>>* filament_sequences)
|
||||
{
|
||||
//only when layer filament num <= 5,we do forcast
|
||||
constexpr int max_n_with_forcast = 5;
|
||||
int cost = 0;
|
||||
std::vector<std::unordered_set<unsigned int>>groups(2); //save the grouped filaments
|
||||
std::vector<std::vector<std::vector<unsigned int>>> layer_sequences(2); //save the reordered filament sequence by group
|
||||
std::map<size_t, std::vector<unsigned int>> custom_layer_sequence_map; // save the filament sequences of custom layer
|
||||
|
||||
// group the filament
|
||||
for (int i = 0; i < filament_maps.size(); ++i) {
|
||||
if (filament_maps[i] == 0)
|
||||
groups[0].insert(filament_lists[i]);
|
||||
if (filament_maps[i] == 1)
|
||||
groups[1].insert(filament_lists[i]);
|
||||
}
|
||||
|
||||
// store custom layer sequence
|
||||
for (size_t layer = 0; layer < layer_filaments.size(); ++layer) {
|
||||
const auto& curr_lf = layer_filaments[layer];
|
||||
|
||||
std::vector<int>custom_filament_seq;
|
||||
if (get_custom_seq && (*get_custom_seq)(layer, custom_filament_seq) && !custom_filament_seq.empty()) {
|
||||
std::vector<unsigned int> unsign_custom_extruder_seq;
|
||||
for (int extruder : custom_filament_seq) {
|
||||
unsigned int unsign_extruder = static_cast<unsigned int>(extruder) - 1;
|
||||
auto it = std::find(curr_lf.begin(), curr_lf.end(), unsign_extruder);
|
||||
if (it != curr_lf.end())
|
||||
unsign_custom_extruder_seq.emplace_back(unsign_extruder);
|
||||
}
|
||||
assert(curr_lf.size() == unsign_custom_extruder_seq.size());
|
||||
|
||||
custom_layer_sequence_map[layer] = unsign_custom_extruder_seq;
|
||||
}
|
||||
}
|
||||
using uint128_t = boost::multiprecision::uint128_t;
|
||||
auto extruders_to_hash_key = [](const std::vector<unsigned int>& curr_layer_extruders,
|
||||
const std::vector<unsigned int>& next_layer_extruders,
|
||||
const std::optional<unsigned int>& prev_extruder,
|
||||
bool use_forcast)->uint128_t
|
||||
{
|
||||
uint128_t hash_key = 0;
|
||||
//31-0 bit define current layer extruder,63-32 bit define next layer extruder,95~64 define prev extruder
|
||||
if (prev_extruder)
|
||||
hash_key |= (uint128_t(1) << (64 + *prev_extruder));
|
||||
|
||||
if (use_forcast) {
|
||||
for (auto item : next_layer_extruders)
|
||||
hash_key |= (uint128_t(1) << (32 + item));
|
||||
}
|
||||
|
||||
for (auto item : curr_layer_extruders)
|
||||
hash_key |= (uint128_t(1) << item);
|
||||
return hash_key;
|
||||
};
|
||||
|
||||
|
||||
// get best layer sequence by group
|
||||
for (size_t idx = 0; idx < groups.size(); ++idx) {
|
||||
// case with one group
|
||||
if (groups[idx].empty())
|
||||
continue;
|
||||
std::optional<unsigned int>current_extruder_id;
|
||||
|
||||
std::unordered_map<uint128_t, std::pair<float, std::vector<unsigned int>>> caches;
|
||||
|
||||
for (size_t layer = 0; layer < layer_filaments.size(); ++layer) {
|
||||
const auto& curr_lf = layer_filaments[layer];
|
||||
|
||||
if (auto iter = custom_layer_sequence_map.find(layer); iter != custom_layer_sequence_map.end()) {
|
||||
auto sequence_in_group = collect_filaments_in_groups<unsigned int>(groups[idx], iter->second);
|
||||
|
||||
float tmp_cost = 0;
|
||||
std::optional<unsigned int>prev = current_extruder_id;
|
||||
for (auto& f : sequence_in_group) {
|
||||
if (prev) { tmp_cost += flush_matrix[idx][*prev][f]; }
|
||||
prev = f;
|
||||
}
|
||||
cost += tmp_cost;
|
||||
|
||||
if (!sequence_in_group.empty())
|
||||
current_extruder_id = sequence_in_group.back();
|
||||
//insert an empty array
|
||||
if (filament_sequences)
|
||||
layer_sequences[idx].emplace_back(std::vector<unsigned int>());
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<unsigned int>filament_used_in_group = collect_filaments_in_groups<unsigned int>(groups[idx], curr_lf);
|
||||
|
||||
std::vector<unsigned int>next_lf;
|
||||
if (layer + 1 < layer_filaments.size())
|
||||
next_lf = layer_filaments[layer + 1];
|
||||
std::vector<unsigned int>filament_used_in_group_next_layer = collect_filaments_in_groups<unsigned int>(groups[idx], next_lf);
|
||||
|
||||
bool use_forcast = (filament_used_in_group.size() <= max_n_with_forcast && filament_used_in_group_next_layer.size() <= max_n_with_forcast);
|
||||
float tmp_cost = 0;
|
||||
std::vector<unsigned int>sequence;
|
||||
uint128_t hash_key = extruders_to_hash_key(filament_used_in_group, filament_used_in_group_next_layer, current_extruder_id, use_forcast);
|
||||
if (auto iter = caches.find(hash_key); iter != caches.end()) {
|
||||
tmp_cost = iter->second.first;
|
||||
sequence = iter->second.second;
|
||||
}
|
||||
else {
|
||||
sequence = get_extruders_order(flush_matrix[idx], filament_used_in_group, filament_used_in_group_next_layer, current_extruder_id, use_forcast, &tmp_cost);
|
||||
caches[hash_key] = { tmp_cost,sequence };
|
||||
}
|
||||
|
||||
assert(sequence.size() == filament_used_in_group.size());
|
||||
|
||||
if (filament_sequences)
|
||||
layer_sequences[idx].emplace_back(sequence);
|
||||
|
||||
if (!sequence.empty())
|
||||
current_extruder_id = sequence.back();
|
||||
cost += tmp_cost;
|
||||
}
|
||||
}
|
||||
|
||||
// get the final layer sequences
|
||||
// if only have one group,we need to check whether layer sequence[idx] is valid
|
||||
if (filament_sequences) {
|
||||
filament_sequences->clear();
|
||||
filament_sequences->resize(layer_filaments.size());
|
||||
int last_group_id = 0;
|
||||
//if last_group == 0,print group 0 first ,else print group 1 first
|
||||
if (!custom_layer_sequence_map.empty()) {
|
||||
const auto& first_layer = custom_layer_sequence_map.begin()->first;
|
||||
const auto& first_layer_filaments = custom_layer_sequence_map.begin()->second;
|
||||
assert(!first_layer_filaments.empty());
|
||||
|
||||
bool first_group = groups[0].count(first_layer_filaments.front()) ? 0 : 1;
|
||||
last_group_id = (first_layer & 1) ? !first_group : first_group;
|
||||
}
|
||||
|
||||
for (size_t layer = 0; layer < layer_filaments.size(); ++layer) {
|
||||
auto& curr_layer_seq = (*filament_sequences)[layer];
|
||||
if (custom_layer_sequence_map.find(layer) != custom_layer_sequence_map.end()) {
|
||||
curr_layer_seq = custom_layer_sequence_map[layer];
|
||||
if (!curr_layer_seq.empty()) {
|
||||
last_group_id = groups[0].count(curr_layer_seq.back()) ? 0 : 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (last_group_id == 1) {
|
||||
// try reuse the last group
|
||||
if (!layer_sequences[1].empty() && !layer_sequences[1][layer].empty())
|
||||
curr_layer_seq.insert(curr_layer_seq.end(), layer_sequences[1][layer].begin(), layer_sequences[1][layer].end());
|
||||
if (!layer_sequences[0].empty() && !layer_sequences[0][layer].empty()) {
|
||||
curr_layer_seq.insert(curr_layer_seq.end(), layer_sequences[0][layer].begin(), layer_sequences[0][layer].end());
|
||||
last_group_id = 0; // update last group id
|
||||
}
|
||||
}
|
||||
else if(last_group_id == 0) {
|
||||
if (!layer_sequences[0].empty() && !layer_sequences[0][layer].empty()) {
|
||||
curr_layer_seq.insert(curr_layer_seq.end(), layer_sequences[0][layer].begin(), layer_sequences[0][layer].end());
|
||||
}
|
||||
if (!layer_sequences[1].empty() && !layer_sequences[1][layer].empty()) {
|
||||
curr_layer_seq.insert(curr_layer_seq.end(), layer_sequences[1][layer].begin(), layer_sequences[1][layer].end());
|
||||
last_group_id = 1; // update last group id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cost;
|
||||
}
|
||||
}
|
||||
114
src/libslic3r/GCode/ToolOrderUtils.hpp
Normal file
114
src/libslic3r/GCode/ToolOrderUtils.hpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#ifndef TOOL_ORDER_UTILS_HPP
|
||||
#define TOOL_ORDER_UTILS_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
using FlushMatrix = std::vector<std::vector<float>>;
|
||||
|
||||
namespace MaxFlowGraph {
|
||||
const int INF = std::numeric_limits<int>::max();
|
||||
const int INVALID_ID = -1;
|
||||
}
|
||||
|
||||
class MaxFlowSolver
|
||||
{
|
||||
private:
|
||||
struct Edge {
|
||||
int from, to, capacity, flow;
|
||||
Edge(int u, int v, int cap) :from(u), to(v), capacity(cap), flow(0) {}
|
||||
};
|
||||
public:
|
||||
MaxFlowSolver(const std::vector<int>& u_nodes, const std::vector<int>& v_nodes,
|
||||
const std::unordered_map<int, std::vector<int>>& uv_link_limits = {},
|
||||
const std::unordered_map<int, std::vector<int>>& uv_unlink_limits = {},
|
||||
const std::vector<int>& u_capacity = {},
|
||||
const std::vector<int>& v_capacity = {}
|
||||
);
|
||||
std::vector<int> solve();
|
||||
|
||||
private:
|
||||
void add_edge(int from, int to, int capacity);
|
||||
|
||||
int total_nodes;
|
||||
int source_id;
|
||||
int sink_id;
|
||||
std::vector<Edge>edges;
|
||||
std::vector<int>l_nodes;
|
||||
std::vector<int>r_nodes;
|
||||
std::vector<std::vector<int>>adj;
|
||||
};
|
||||
|
||||
|
||||
struct MinCostMaxFlow;
|
||||
|
||||
class GeneralMinCostSolver
|
||||
{
|
||||
public:
|
||||
GeneralMinCostSolver(const std::vector<std::vector<float>>& matrix_,
|
||||
const std::vector<int>& u_nodes,
|
||||
const std::vector<int>& v_nodes);
|
||||
|
||||
std::vector<int> solve();
|
||||
~GeneralMinCostSolver();
|
||||
private:
|
||||
std::unique_ptr<MinCostMaxFlow> m_solver;
|
||||
};
|
||||
|
||||
|
||||
class MinFlushFlowSolver
|
||||
{
|
||||
public:
|
||||
MinFlushFlowSolver(const std::vector<std::vector<float>>& matrix_,
|
||||
const std::vector<int>& u_nodes,
|
||||
const std::vector<int>& v_nodes,
|
||||
const std::unordered_map<int, std::vector<int>>& uv_link_limits = {},
|
||||
const std::unordered_map<int, std::vector<int>>& uv_unlink_limits = {},
|
||||
const std::vector<int>& u_capacity = {},
|
||||
const std::vector<int>& v_capacity = {}
|
||||
);
|
||||
std::vector<int> solve();
|
||||
~MinFlushFlowSolver();
|
||||
private:
|
||||
std::unique_ptr<MinCostMaxFlow> m_solver;
|
||||
};
|
||||
|
||||
|
||||
class MatchModeGroupSolver
|
||||
{
|
||||
public:
|
||||
MatchModeGroupSolver(const std::vector<std::vector<float>>& matrix_,
|
||||
const std::vector<int>& u_nodes,
|
||||
const std::vector<int>& v_nodes,
|
||||
const std::vector<int>& v_capacity,
|
||||
const std::unordered_map<int, std::vector<int>>& uv_unlink_limits = {});
|
||||
|
||||
std::vector<int> solve();
|
||||
~MatchModeGroupSolver();
|
||||
private:
|
||||
std::unique_ptr<MinCostMaxFlow> m_solver;
|
||||
};
|
||||
|
||||
|
||||
std::vector<unsigned int> get_extruders_order(const std::vector<std::vector<float>> &wipe_volumes,
|
||||
const std::vector<unsigned int> &curr_layer_extruders,
|
||||
const std::vector<unsigned int> &next_layer_extruders,
|
||||
const std::optional<unsigned int> &start_extruder_id,
|
||||
bool use_forcast = false,
|
||||
float *cost = nullptr);
|
||||
|
||||
int reorder_filaments_for_minimum_flush_volume(const std::vector<unsigned int> &filament_lists,
|
||||
const std::vector<int> &filament_maps,
|
||||
const std::vector<std::vector<unsigned int>> &layer_filaments,
|
||||
const std::vector<FlushMatrix> &flush_matrix,
|
||||
std::optional<std::function<bool(int, std::vector<int> &)>> get_custom_seq,
|
||||
std::vector<std::vector<unsigned int>> *filament_sequences);
|
||||
|
||||
}
|
||||
#endif // !TOOL_ORDER_UTILS_HPP
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,9 @@
|
||||
#include <utility>
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include "../FilamentGroup.hpp"
|
||||
#include "../ExtrusionEntity.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -91,6 +94,37 @@ private:
|
||||
const LayerTools* m_layer_tools = nullptr; // so we know which LayerTools object this belongs to
|
||||
};
|
||||
|
||||
|
||||
struct FilamentChangeStats
|
||||
{
|
||||
int filament_flush_weight{0};
|
||||
int filament_change_count{0};
|
||||
int extruder_change_count{0};
|
||||
|
||||
void clear(){
|
||||
filament_flush_weight = 0;
|
||||
filament_change_count = 0;
|
||||
extruder_change_count = 0;
|
||||
}
|
||||
|
||||
FilamentChangeStats& operator+=(const FilamentChangeStats& other) {
|
||||
this->filament_flush_weight += other.filament_flush_weight;
|
||||
this->filament_change_count += other.filament_change_count;
|
||||
this->extruder_change_count += other.extruder_change_count;
|
||||
return *this;
|
||||
}
|
||||
|
||||
FilamentChangeStats operator+(const FilamentChangeStats& other){
|
||||
FilamentChangeStats ret;
|
||||
ret.filament_flush_weight = this->filament_flush_weight + other.filament_flush_weight;
|
||||
ret.filament_change_count = this->filament_change_count + other.filament_change_count;
|
||||
ret.extruder_change_count = this->extruder_change_count + other.extruder_change_count;
|
||||
return ret;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
class LayerTools
|
||||
{
|
||||
public:
|
||||
@@ -146,6 +180,11 @@ private:
|
||||
class ToolOrdering
|
||||
{
|
||||
public:
|
||||
enum FilamentChangeMode {
|
||||
SingleExt,
|
||||
MultiExtBest,
|
||||
MultiExtCurr
|
||||
};
|
||||
ToolOrdering() = default;
|
||||
|
||||
// For the use case when each object is printed separately
|
||||
@@ -156,8 +195,17 @@ public:
|
||||
// (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(); m_tool_order_cache.clear();
|
||||
void handle_dontcare_extruder(const std::vector<unsigned int>& first_layer_tool_order);
|
||||
void handle_dontcare_extruder(unsigned int first_extruder);
|
||||
|
||||
void sort_and_build_data(const PrintObject &object, unsigned int first_extruder, bool prime_multi_material = false);
|
||||
void sort_and_build_data(const Print& print, unsigned int first_extruder, bool prime_multi_material = false);
|
||||
|
||||
void clear() {
|
||||
m_layer_tools.clear();
|
||||
m_stats_by_single_extruder.clear();
|
||||
m_stats_by_multi_extruder_best.clear();
|
||||
m_stats_by_multi_extruder_curr.clear();
|
||||
}
|
||||
|
||||
// Only valid for non-sequential print:
|
||||
@@ -187,16 +235,33 @@ public:
|
||||
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().has_wipe_tower; }
|
||||
|
||||
int get_most_used_extruder() const { return most_used_extruder; }
|
||||
/*
|
||||
* called in single extruder mode, the value in map are all 0
|
||||
* called in dual extruder mode, the value in map will be 0 or 1
|
||||
* 0 based group id
|
||||
*/
|
||||
static std::vector<int> get_recommended_filament_maps(const std::vector<std::vector<unsigned int>>& layer_filaments, const Print* print,const FilamentMapMode mode, const std::vector<std::set<int>>& physical_unprintables, const std::vector<std::set<int>>& geometric_unprintables);
|
||||
|
||||
static bool check_tpu_group(const std::vector<unsigned int>&used_filaments,const std::vector<int>& filament_maps,const PrintConfig* config);
|
||||
|
||||
// should be called after doing reorder
|
||||
FilamentChangeStats get_filament_change_stats(FilamentChangeMode mode);
|
||||
void cal_most_used_extruder(const PrintConfig &config);
|
||||
bool cal_non_support_filaments(const PrintConfig &config,
|
||||
unsigned int & first_non_support_filament,
|
||||
std::vector<int> & initial_non_support_filaments,
|
||||
std::vector<int> & initial_filaments);
|
||||
|
||||
bool has_non_support_filament(const PrintConfig &config);
|
||||
|
||||
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);
|
||||
// QDS
|
||||
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);
|
||||
void reorder_extruders_for_minimum_flush_volume();
|
||||
void reorder_extruders_for_minimum_flush_volume(bool reorder_first_layer);
|
||||
|
||||
// QDS
|
||||
std::vector<unsigned int> generate_first_layer_tool_order(const Print& print);
|
||||
@@ -209,9 +274,16 @@ private:
|
||||
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;
|
||||
std::unordered_map<uint32_t, std::vector<uint8_t>> m_tool_order_cache;
|
||||
const PrintConfig* m_print_config_ptr = nullptr;
|
||||
const PrintObject* m_print_object_ptr = nullptr;
|
||||
Print* m_print;
|
||||
bool m_sorted = false;
|
||||
|
||||
FilamentChangeStats m_stats_by_single_extruder;
|
||||
FilamentChangeStats m_stats_by_multi_extruder_curr;
|
||||
FilamentChangeStats m_stats_by_multi_extruder_best;
|
||||
|
||||
int most_used_extruder;
|
||||
};
|
||||
|
||||
} // namespace SLic3r
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,10 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
@@ -17,15 +21,21 @@ class PrintConfig;
|
||||
enum GCodeFlavor : unsigned char;
|
||||
|
||||
|
||||
|
||||
class WipeTower
|
||||
{
|
||||
public:
|
||||
friend class WipeTowerWriter;
|
||||
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;
|
||||
|
||||
static float get_limit_depth_by_height(float max_height);
|
||||
static float get_auto_brim_by_height(float max_height);
|
||||
static TriangleMesh its_make_rib_tower(float width, float depth, float height, float rib_length, float rib_width, bool fillet_wall);
|
||||
static TriangleMesh its_make_rib_brim(const Polygon& brim, float layer_height);
|
||||
static Polygon rib_section(float width, float depth, float rib_length, float rib_width, bool fillet_wall);
|
||||
static Vec2f move_box_inside_box(const BoundingBox &box1, const BoundingBox &box2, int offset = 0);
|
||||
static Polygon rounding_polygon(Polygon &polygon, double rounding = 2., double angle_tol = 30. / 180. * PI);
|
||||
struct Extrusion
|
||||
{
|
||||
Extrusion(const Vec2f &pos, float width, unsigned int tool) : pos(pos), width(width), tool(tool) {}
|
||||
@@ -38,6 +48,18 @@ public:
|
||||
unsigned int tool;
|
||||
};
|
||||
|
||||
struct NozzleChangeResult
|
||||
{
|
||||
std::string gcode;
|
||||
|
||||
Vec2f start_pos; // rotated
|
||||
Vec2f end_pos;
|
||||
|
||||
Vec2f origin_start_pos; // not rotated
|
||||
|
||||
std::vector<Vec2f> wipe_path;
|
||||
};
|
||||
|
||||
struct ToolChangeResult
|
||||
{
|
||||
// Print heigh of this tool change.
|
||||
@@ -60,6 +82,9 @@ public:
|
||||
// Is this a priming extrusion? (If so, the wipe tower rotation & translation will not be applied later)
|
||||
bool priming;
|
||||
|
||||
bool is_tool_change{false};
|
||||
Vec2f tool_change_start_pos;
|
||||
|
||||
// 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
|
||||
@@ -81,6 +106,8 @@ public:
|
||||
// executing the gcode finish_layer_tcr.
|
||||
bool is_finish_first = false;
|
||||
|
||||
NozzleChangeResult nozzle_change_result;
|
||||
|
||||
// Sum the total length of the extrusion.
|
||||
float total_extrusion_length_in_plane() {
|
||||
float e_length = 0.f;
|
||||
@@ -132,14 +159,22 @@ public:
|
||||
bool priming,
|
||||
size_t old_tool,
|
||||
bool is_finish,
|
||||
bool is_tool_change,
|
||||
float purge_volume) const;
|
||||
|
||||
ToolChangeResult construct_block_tcr(WipeTowerWriter& writer,
|
||||
bool priming,
|
||||
size_t filament_id,
|
||||
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
|
||||
// QDS: 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);
|
||||
WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origin, size_t initial_tool, const float wipe_tower_height);
|
||||
|
||||
|
||||
// Set the extruder properties.
|
||||
@@ -152,12 +187,27 @@ public:
|
||||
// Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result"
|
||||
void generate(std::vector<std::vector<ToolChangeResult>> &result);
|
||||
|
||||
WipeTower::ToolChangeResult only_generate_out_wall();
|
||||
WipeTower::ToolChangeResult only_generate_out_wall(bool is_new_mode = false);
|
||||
Polygon generate_support_wall(WipeTowerWriter &writer, const box_coordinates &wt_box, double feedrate, bool first_layer);
|
||||
Polygon generate_support_wall_new(WipeTowerWriter &writer, const box_coordinates &wt_box, double feedrate, bool first_layer,bool rib_wall, bool extrude_perimeter, bool skip_points);
|
||||
|
||||
Polygon generate_rib_polygon(const box_coordinates &wt_box);
|
||||
float get_depth() const { return m_wipe_tower_depth; }
|
||||
float get_brim_width() const { return m_wipe_tower_brim_width_real; }
|
||||
BoundingBoxf get_bbx() const {
|
||||
if (m_outer_wall.empty()) return BoundingBoxf({Vec2d(0,0)});
|
||||
BoundingBox box = get_extents(m_outer_wall.begin()->second);
|
||||
BoundingBoxf res = BoundingBoxf(unscale(box.min), unscale(box.max));
|
||||
return res;
|
||||
}
|
||||
std::map<float, Polylines> get_outer_wall() const
|
||||
{
|
||||
return m_outer_wall;
|
||||
}
|
||||
float get_height() const { return m_wipe_tower_height; }
|
||||
float get_layer_height() const { return m_layer_height; }
|
||||
float get_rib_length() const { return m_rib_length; }
|
||||
float get_rib_width() const { return m_rib_width; }
|
||||
|
||||
void set_last_layer_extruder_fill(bool extruder_fill) {
|
||||
if (!m_plan.empty()) {
|
||||
@@ -190,10 +240,9 @@ public:
|
||||
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;
|
||||
@@ -221,6 +270,8 @@ public:
|
||||
// QDS
|
||||
ToolChangeResult tool_change(size_t new_tool, bool extrude_perimeter = false, bool first_toolchange_to_nonsoluble = false);
|
||||
|
||||
NozzleChangeResult nozzle_change(int old_filament_id, int new_filament_id);
|
||||
|
||||
// Fill the unfilled space with a sparse infill.
|
||||
// Call this method only if layer_finished() is false.
|
||||
ToolChangeResult finish_layer(bool extruder_perimeter = true, bool extruder_fill = true);
|
||||
@@ -231,6 +282,12 @@ public:
|
||||
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();
|
||||
}
|
||||
float nozzle_change_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_nozzle_change_perimeter_width - layer_height * (1.f - float(M_PI) / 4.f)) / filament_area();
|
||||
}
|
||||
|
||||
bool get_floating_area(float& start_pos_y, float& end_pos_y) const;
|
||||
bool need_thick_bridge_flow(float pos_y) const;
|
||||
@@ -244,8 +301,23 @@ public:
|
||||
std::vector<float> get_used_filament() const { return m_used_filament_length; }
|
||||
int get_number_of_toolchanges() const { return m_num_tool_changes; }
|
||||
|
||||
void set_filament_map(const std::vector<int> &filament_map) { m_filament_map = filament_map; }
|
||||
|
||||
void set_has_tpu_filament(bool has_tpu) { m_has_tpu_filament = has_tpu; }
|
||||
void set_need_reverse_travel(const std::vector<unsigned int> & used_extruders)
|
||||
{
|
||||
for (unsigned int filament_id : used_extruders) {
|
||||
if (m_filpar[filament_id].ramming_travel_time > EPSILON) {
|
||||
m_need_reverse_travel = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
bool has_tpu_filament() const { return m_has_tpu_filament; }
|
||||
|
||||
struct FilamentParameters {
|
||||
std::string material = "PLA";
|
||||
int category;
|
||||
bool is_soluble = false;
|
||||
// QDS
|
||||
bool is_support = false;
|
||||
@@ -266,8 +338,70 @@ public:
|
||||
std::vector<float> ramming_speed;
|
||||
float nozzle_diameter;
|
||||
float filament_area;
|
||||
float retract_length;
|
||||
float retract_speed;
|
||||
float wipe_dist;
|
||||
float max_e_ramming_speed = 0.f;
|
||||
float ramming_travel_time=0.f; //Travel time after ramming
|
||||
std::vector<float> precool_t;//Pre-cooling time, set to 0 to ensure the ramming speed is controlled solely by ramming volumetric speed.
|
||||
std::vector<float> precool_t_first_layer;
|
||||
};
|
||||
|
||||
|
||||
void set_used_filament_ids(const std::vector<int> &used_filament_ids) { m_used_filament_ids = used_filament_ids; };
|
||||
void set_filament_categories(const std::vector<int> & filament_categories) { m_filament_categories = filament_categories;};
|
||||
std::vector<int> m_used_filament_ids;
|
||||
std::vector<int> m_filament_categories;
|
||||
|
||||
struct WipeTowerBlock
|
||||
{
|
||||
int block_id{0};
|
||||
int filament_adhesiveness_category{0};
|
||||
std::vector<float> layer_depths;
|
||||
std::vector<bool> solid_infill;
|
||||
std::vector<float> finish_depth{0}; // the start pos of finish frame for every layer
|
||||
float depth{0};
|
||||
float start_depth{0};
|
||||
float cur_depth{0};
|
||||
int last_filament_change_id{-1};
|
||||
int last_nozzle_change_id{-1};
|
||||
};
|
||||
|
||||
struct BlockDepthInfo
|
||||
{
|
||||
int category{-1};
|
||||
float depth{0};
|
||||
float nozzle_change_depth{0};
|
||||
};
|
||||
|
||||
std::vector<std::vector<BlockDepthInfo>> m_all_layers_depth;
|
||||
std::vector<WipeTowerBlock> m_wipe_tower_blocks;
|
||||
int m_last_block_id;
|
||||
WipeTowerBlock* m_cur_block{nullptr};
|
||||
|
||||
// help function
|
||||
WipeTowerBlock* get_block_by_category(int filament_adhesiveness_category, bool create);
|
||||
void add_depth_to_block(int filament_id, int filament_adhesiveness_category, float depth, bool is_nozzle_change = false);
|
||||
int get_filament_category(int filament_id);
|
||||
bool is_in_same_extruder(int filament_id_1, int filament_id_2);
|
||||
void reset_block_status();
|
||||
int get_wall_filament_for_all_layer();
|
||||
// for generate new wipe tower
|
||||
void generate_new(std::vector<std::vector<WipeTower::ToolChangeResult>> &result);
|
||||
|
||||
void plan_tower_new();
|
||||
void generate_wipe_tower_blocks();
|
||||
void update_all_layer_depth(float wipe_tower_depth);
|
||||
void set_nozzle_last_layer_id();
|
||||
|
||||
ToolChangeResult tool_change_new(size_t new_tool, bool solid_change = false, bool solid_nozzlechange=false);
|
||||
NozzleChangeResult nozzle_change_new(int old_filament_id, int new_filament_id, bool solid_change = false);
|
||||
ToolChangeResult finish_layer_new(bool extrude_perimeter = true, bool extrude_fill = true, bool extrude_fill_wall = true);
|
||||
ToolChangeResult finish_block(const WipeTowerBlock &block, int filament_id, bool extrude_fill = true);
|
||||
ToolChangeResult finish_block_solid(const WipeTowerBlock &block, int filament_id, bool extrude_fill = true ,bool interface_solid =false);
|
||||
void toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinates &cleaning_box, float wipe_length,bool solid_toolchange=false);
|
||||
Vec2f get_rib_offset() const { return m_rib_offset; }
|
||||
|
||||
private:
|
||||
enum wipe_shape // A fill-in direction
|
||||
{
|
||||
@@ -300,7 +434,23 @@ private:
|
||||
float m_travel_speed = 0.f;
|
||||
float m_first_layer_speed = 0.f;
|
||||
size_t m_first_layer_idx = size_t(-1);
|
||||
size_t m_cur_layer_id;
|
||||
std::vector<int> m_last_layer_id;
|
||||
std::vector<double> m_filaments_change_length;
|
||||
size_t m_cur_layer_id;
|
||||
NozzleChangeResult m_nozzle_change_result;
|
||||
std::vector<int> m_filament_map;
|
||||
bool m_has_tpu_filament{false};
|
||||
bool m_is_multi_extruder{false};
|
||||
bool m_use_gap_wall{false};
|
||||
bool m_use_rib_wall{false};
|
||||
float m_rib_length=0.f;
|
||||
float m_rib_width=0.f;
|
||||
float m_extra_rib_length=0.f;
|
||||
bool m_used_fillet{false};
|
||||
Vec2f m_rib_offset{Vec2f(0.f, 0.f)};
|
||||
bool m_tower_framework{false};
|
||||
bool m_need_reverse_travel{false};
|
||||
|
||||
// G-code generator parameters.
|
||||
// QDS: remove useless config
|
||||
//float m_cooling_tube_retraction = 0.f;
|
||||
@@ -314,6 +464,14 @@ private:
|
||||
bool m_adhesion = true;
|
||||
GCodeFlavor m_gcode_flavor;
|
||||
|
||||
std::vector<unsigned int> m_normal_accels;
|
||||
std::vector<unsigned int> m_first_layer_normal_accels;
|
||||
std::vector<unsigned int> m_travel_accels;
|
||||
std::vector<unsigned int> m_first_layer_travel_accels;
|
||||
unsigned int m_max_accels;
|
||||
bool m_accel_to_decel_enable;
|
||||
float m_accel_to_decel_factor;
|
||||
|
||||
// Bed properties
|
||||
enum {
|
||||
RectangularBed,
|
||||
@@ -324,6 +482,7 @@ private:
|
||||
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_nozzle_change_perimeter_width = 0.4f * Width_To_Nozzle_Ratio;
|
||||
float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter.
|
||||
|
||||
// Extruder specific parameters.
|
||||
@@ -340,14 +499,18 @@ private:
|
||||
size_t m_current_tool = 0;
|
||||
// QDS
|
||||
//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;
|
||||
|
||||
float m_tpu_fixed_spacing = 2;
|
||||
float m_max_speed = 5400.f; // the maximum printing speed on the prime tower.
|
||||
std::vector<Vec2f> m_wall_skip_points;
|
||||
std::map<float,Polylines> m_outer_wall;
|
||||
std::vector<double> m_printable_height;
|
||||
bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; }
|
||||
bool is_valid_last_layer(int tool) const;
|
||||
|
||||
// Calculates length of extrusion line to extrude given volume
|
||||
float volume_to_length(float volume, float line_width, float layer_height) const {
|
||||
@@ -360,12 +523,17 @@ private:
|
||||
// Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental
|
||||
void make_wipe_tower_square();
|
||||
|
||||
Vec2f get_next_pos(const WipeTower::box_coordinates &cleaning_box, float wipe_length);
|
||||
|
||||
// Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe
|
||||
void save_on_last_wipe();
|
||||
|
||||
bool is_tpu_filament(int filament_id) const;
|
||||
bool is_need_reverse_travel(int filament) const;
|
||||
// QDS
|
||||
box_coordinates align_perimeter(const box_coordinates& perimeter_box);
|
||||
|
||||
void set_for_wipe_tower_writer(WipeTowerWriter &writer);
|
||||
|
||||
// to store information about tool changes for a given layer
|
||||
struct WipeTowerInfo{
|
||||
@@ -377,6 +545,7 @@ private:
|
||||
float first_wipe_line;
|
||||
float wipe_volume;
|
||||
float wipe_length;
|
||||
float nozzle_change_depth{0};
|
||||
// QDS
|
||||
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)
|
||||
@@ -409,7 +578,7 @@ private:
|
||||
|
||||
void toolchange_Unload(
|
||||
WipeTowerWriter &writer,
|
||||
const box_coordinates &cleaning_box,
|
||||
const box_coordinates &cleaning_box,
|
||||
const std::string& current_material,
|
||||
const int new_temperature);
|
||||
|
||||
@@ -417,15 +586,17 @@ private:
|
||||
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);
|
||||
void get_wall_skip_points(const WipeTowerInfo &layer);
|
||||
ToolChangeResult merge_tcr(ToolChangeResult &first, ToolChangeResult &second);
|
||||
};
|
||||
|
||||
|
||||
@@ -433,4 +604,4 @@ private:
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // WipeTowerPrusaMM_hpp_
|
||||
#endif // WipeTowerPrusaMM_hpp_
|
||||
|
||||
Reference in New Issue
Block a user