mirror of
https://github.com/QIDITECH/QIDISlicer.git
synced 2026-02-02 17:08:42 +03:00
Prusa 2.7.3
This commit is contained in:
@@ -1171,7 +1171,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCodeGenerator &gcodegen, cons
|
||||
{
|
||||
// If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset).
|
||||
// Otherwise perform the path planning in the coordinate system of the active object.
|
||||
bool use_external = m_use_external_mp || m_use_external_mp_once;
|
||||
bool use_external = m_use_external_mp || use_external_mp_once;
|
||||
Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0);
|
||||
const Point start = *gcodegen.last_position + scaled_origin;
|
||||
const Point end = point + scaled_origin;
|
||||
|
||||
@@ -17,11 +17,10 @@ class AvoidCrossingPerimeters
|
||||
public:
|
||||
// Routing around the objects vs. inside a single object.
|
||||
void use_external_mp(bool use = true) { m_use_external_mp = use; };
|
||||
void use_external_mp_once() { m_use_external_mp_once = true; }
|
||||
bool used_external_mp_once() { return m_use_external_mp_once; }
|
||||
bool used_external_mp_once() { return use_external_mp_once; }
|
||||
void disable_once() { m_disabled_once = true; }
|
||||
bool disabled_once() const { return m_disabled_once; }
|
||||
void reset_once_modifiers() { m_use_external_mp_once = false; m_disabled_once = false; }
|
||||
void reset_once_modifiers() { use_external_mp_once = false; m_disabled_once = false; }
|
||||
|
||||
void init_layer(const Layer &layer);
|
||||
|
||||
@@ -50,10 +49,10 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
// just for the next travel move
|
||||
bool use_external_mp_once { false };
|
||||
private:
|
||||
bool m_use_external_mp { false };
|
||||
// just for the next travel move
|
||||
bool m_use_external_mp_once { false };
|
||||
// this flag disables avoid_crossing_perimeters just for the next travel move
|
||||
// we enable it by default for the first travel move in print
|
||||
bool m_disabled_once { true };
|
||||
|
||||
@@ -3806,7 +3806,13 @@ void GCodeProcessor::post_process()
|
||||
struct LineData
|
||||
{
|
||||
std::string line;
|
||||
float time;
|
||||
std::array<float, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)> times{ 0.0f, 0.0f };
|
||||
};
|
||||
|
||||
enum ETimeMode
|
||||
{
|
||||
Normal = static_cast<int>(PrintEstimatedStatistics::ETimeMode::Normal),
|
||||
Stealth = static_cast<int>(PrintEstimatedStatistics::ETimeMode::Stealth)
|
||||
};
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -3836,10 +3842,10 @@ void GCodeProcessor::post_process()
|
||||
#endif // NDEBUG
|
||||
|
||||
EWriteType m_write_type{ EWriteType::BySize };
|
||||
// Time machine containing g1 times cache
|
||||
TimeMachine& m_machine;
|
||||
// Time machines containing g1 times cache
|
||||
const std::array<TimeMachine, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)>& m_machines;
|
||||
// Current time
|
||||
float m_time{ 0.0f };
|
||||
std::array<float, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)> m_times{ 0.0f, 0.0f };
|
||||
// Current size in bytes
|
||||
size_t m_size{ 0 };
|
||||
|
||||
@@ -3855,11 +3861,12 @@ void GCodeProcessor::post_process()
|
||||
|
||||
bgcode::binarize::Binarizer& m_binarizer;
|
||||
public:
|
||||
ExportLines(bgcode::binarize::Binarizer& binarizer, EWriteType type, TimeMachine& machine)
|
||||
ExportLines(bgcode::binarize::Binarizer& binarizer, EWriteType type,
|
||||
const std::array<TimeMachine, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)>& machines)
|
||||
#ifndef NDEBUG
|
||||
: m_statistics(*this), m_binarizer(binarizer), m_write_type(type), m_machine(machine) {}
|
||||
: m_statistics(*this), m_binarizer(binarizer), m_write_type(type), m_machines(machines) {}
|
||||
#else
|
||||
: m_binarizer(binarizer), m_write_type(type), m_machine(machine) {}
|
||||
: m_binarizer(binarizer), m_write_type(type), m_machines(machines) {}
|
||||
#endif // NDEBUG
|
||||
|
||||
// return: number of internal G1 lines (from G2/G3 splitting) processed
|
||||
@@ -3876,9 +3883,9 @@ void GCodeProcessor::post_process()
|
||||
else
|
||||
return ret;
|
||||
|
||||
auto init_it = m_machine.g1_times_cache.begin() + m_times_cache_id;
|
||||
auto init_it = m_machines[Normal].g1_times_cache.begin() + m_times_cache_id;
|
||||
auto it = init_it;
|
||||
while (it != m_machine.g1_times_cache.end() && it->id < g1_lines_counter) {
|
||||
while (it != m_machines[Normal].g1_times_cache.end() && it->id < g1_lines_counter) {
|
||||
++it;
|
||||
++m_times_cache_id;
|
||||
}
|
||||
@@ -3888,7 +3895,7 @@ void GCodeProcessor::post_process()
|
||||
|
||||
// search for internal G1 lines
|
||||
if (GCodeReader::GCodeLine::cmd_is(line, "G2") || GCodeReader::GCodeLine::cmd_is(line, "G3")) {
|
||||
while (it != m_machine.g1_times_cache.end() && it->remaining_internal_g1_lines > 0) {
|
||||
while (it != m_machines[Normal].g1_times_cache.end() && it->remaining_internal_g1_lines > 0) {
|
||||
++it;
|
||||
++m_times_cache_id;
|
||||
++g1_lines_counter;
|
||||
@@ -3896,14 +3903,17 @@ void GCodeProcessor::post_process()
|
||||
}
|
||||
}
|
||||
|
||||
if (it != m_machine.g1_times_cache.end() && it->id == g1_lines_counter)
|
||||
m_time = it->elapsed_time;
|
||||
if (it != m_machines[Normal].g1_times_cache.end() && it->id == g1_lines_counter) {
|
||||
m_times[Normal] = it->elapsed_time;
|
||||
if (!m_machines[Stealth].g1_times_cache.empty())
|
||||
m_times[Stealth] = (m_machines[Stealth].g1_times_cache.begin() + std::distance(m_machines[Normal].g1_times_cache.begin(), it))->elapsed_time;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// add the given gcode line to the cache
|
||||
void append_line(const std::string& line) {
|
||||
m_lines.push_back({ line, m_time });
|
||||
m_lines.push_back({ line, m_times });
|
||||
#ifndef NDEBUG
|
||||
m_statistics.add_line(line.length());
|
||||
#endif // NDEBUG
|
||||
@@ -3914,7 +3924,8 @@ void GCodeProcessor::post_process()
|
||||
}
|
||||
|
||||
// Insert the gcode lines required by the command cmd by backtracing into the cache
|
||||
void insert_lines(const Backtrace& backtrace, const std::string& cmd, std::function<std::string(unsigned int, float, float)> line_inserter,
|
||||
void insert_lines(const Backtrace& backtrace, const std::string& cmd,
|
||||
std::function<std::string(unsigned int, const std::vector<float>&)> line_inserter,
|
||||
std::function<std::string(const std::string&)> line_replacer) {
|
||||
assert(!m_lines.empty());
|
||||
const float time_step = backtrace.time_step();
|
||||
@@ -3922,13 +3933,13 @@ void GCodeProcessor::post_process()
|
||||
float last_time_insertion = 0.0f; // used to avoid inserting two lines at the same time
|
||||
for (unsigned int i = 0; i < backtrace.steps; ++i) {
|
||||
const float backtrace_time_i = (i + 1) * time_step;
|
||||
const float time_threshold_i = m_time - backtrace_time_i;
|
||||
const float time_threshold_i = m_times[Normal] - backtrace_time_i;
|
||||
auto rev_it = m_lines.rbegin() + rev_it_dist;
|
||||
auto start_rev_it = rev_it;
|
||||
|
||||
std::string curr_cmd = GCodeReader::GCodeLine::extract_cmd(rev_it->line);
|
||||
// backtrace into the cache to find the place where to insert the line
|
||||
while (rev_it != m_lines.rend() && rev_it->time > time_threshold_i && curr_cmd != cmd && curr_cmd != "G28" && curr_cmd != "G29") {
|
||||
while (rev_it != m_lines.rend() && rev_it->times[Normal] > time_threshold_i && curr_cmd != cmd && curr_cmd != "G28" && curr_cmd != "G29") {
|
||||
rev_it->line = line_replacer(rev_it->line);
|
||||
++rev_it;
|
||||
if (rev_it != m_lines.rend())
|
||||
@@ -3940,11 +3951,15 @@ void GCodeProcessor::post_process()
|
||||
break;
|
||||
|
||||
// insert the line for the current step
|
||||
if (rev_it != m_lines.rend() && rev_it != start_rev_it && rev_it->time != last_time_insertion) {
|
||||
last_time_insertion = rev_it->time;
|
||||
const std::string out_line = line_inserter(i + 1, last_time_insertion, m_time - last_time_insertion);
|
||||
if (rev_it != m_lines.rend() && rev_it != start_rev_it && rev_it->times[Normal] != last_time_insertion) {
|
||||
last_time_insertion = rev_it->times[Normal];
|
||||
std::vector<float> time_diffs;
|
||||
time_diffs.push_back(m_times[Normal] - last_time_insertion);
|
||||
if (!m_machines[Stealth].g1_times_cache.empty())
|
||||
time_diffs.push_back(m_times[Stealth] - rev_it->times[Stealth]);
|
||||
const std::string out_line = line_inserter(i + 1, time_diffs);
|
||||
rev_it_dist = std::distance(m_lines.rbegin(), rev_it) + 1;
|
||||
m_lines.insert(rev_it.base(), { out_line, rev_it->time });
|
||||
m_lines.insert(rev_it.base(), { out_line, rev_it->times });
|
||||
#ifndef NDEBUG
|
||||
m_statistics.add_line(out_line.length());
|
||||
#endif // NDEBUG
|
||||
@@ -3970,7 +3985,7 @@ void GCodeProcessor::post_process()
|
||||
std::string out_string;
|
||||
if (!m_lines.empty()) {
|
||||
if (m_write_type == EWriteType::ByTime) {
|
||||
while (m_lines.front().time < m_time - backtrace_time) {
|
||||
while (m_lines.front().times[Normal] < m_times[Normal] - backtrace_time) {
|
||||
const LineData& data = m_lines.front();
|
||||
out_string += data.line;
|
||||
m_size -= data.line.length();
|
||||
@@ -4055,7 +4070,8 @@ void GCodeProcessor::post_process()
|
||||
}
|
||||
};
|
||||
|
||||
ExportLines export_lines(m_binarizer, m_result.backtrace_enabled ? ExportLines::EWriteType::ByTime : ExportLines::EWriteType::BySize, m_time_processor.machines[0]);
|
||||
ExportLines export_lines(m_binarizer, m_result.backtrace_enabled ? ExportLines::EWriteType::ByTime : ExportLines::EWriteType::BySize,
|
||||
m_time_processor.machines);
|
||||
|
||||
// replace placeholder lines with the proper final value
|
||||
// gcode_line is in/out parameter, to reduce expensive memory allocation
|
||||
@@ -4259,9 +4275,14 @@ void GCodeProcessor::post_process()
|
||||
}
|
||||
export_lines.insert_lines(backtrace, cmd,
|
||||
// line inserter
|
||||
[tool_number, this](unsigned int id, float time, float time_diff) {
|
||||
int temperature = int( m_layer_id != 1 ? m_extruder_temps_config[tool_number] : m_extruder_temps_first_layer_config[tool_number]);
|
||||
const std::string out = "M104 T" + std::to_string(tool_number) + " P" + std::to_string(int(std::round(time_diff))) + " S" + std::to_string(temperature) + "\n";
|
||||
[tool_number, this](unsigned int id, const std::vector<float>& time_diffs) {
|
||||
const int temperature = int(m_layer_id != 1 ? m_extruder_temps_config[tool_number] : m_extruder_temps_first_layer_config[tool_number]);
|
||||
std::string out = "M104.1 T" + std::to_string(tool_number);
|
||||
if (time_diffs.size() > 0)
|
||||
out += " P" + std::to_string(int(std::round(time_diffs[0])));
|
||||
if (time_diffs.size() > 1)
|
||||
out += " Q" + std::to_string(int(std::round(time_diffs[1])));
|
||||
out += " S" + std::to_string(temperature) + "\n";
|
||||
return out;
|
||||
},
|
||||
// line replacer
|
||||
|
||||
@@ -198,6 +198,8 @@ public:
|
||||
{ return { quantize(pt.x(), XYZF_EXPORT_DIGITS), quantize(pt.y(), XYZF_EXPORT_DIGITS) }; }
|
||||
static Vec3d quantize(const Vec3d &pt)
|
||||
{ return { quantize(pt.x(), XYZF_EXPORT_DIGITS), quantize(pt.y(), XYZF_EXPORT_DIGITS), quantize(pt.z(), XYZF_EXPORT_DIGITS) }; }
|
||||
static Vec2d quantize(const Vec2f &pt)
|
||||
{ return { quantize(double(pt.x()), XYZF_EXPORT_DIGITS), quantize(double(pt.y()), XYZF_EXPORT_DIGITS) }; }
|
||||
|
||||
void emit_axis(const char axis, const double v, size_t digits);
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include "LabelObjects.hpp"
|
||||
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "GCode/GCodeWriter.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "TriangleMeshSlicer.hpp"
|
||||
|
||||
#include "boost/algorithm/string/replace.hpp"
|
||||
|
||||
namespace Slic3r::GCode {
|
||||
|
||||
@@ -39,10 +41,10 @@ Polygon instance_outline(const PrintInstance* pi)
|
||||
}; // anonymous namespace
|
||||
|
||||
|
||||
void LabelObjects::init(const Print& print)
|
||||
void LabelObjects::init(const SpanOfConstPtrs<PrintObject>& objects, LabelObjectsStyle label_object_style, GCodeFlavor gcode_flavor)
|
||||
{
|
||||
m_label_objects_style = print.config().gcode_label_objects;
|
||||
m_flavor = print.config().gcode_flavor;
|
||||
m_label_objects_style = label_object_style;
|
||||
m_flavor = gcode_flavor;
|
||||
|
||||
if (m_label_objects_style == LabelObjectsStyle::Disabled)
|
||||
return;
|
||||
@@ -51,7 +53,7 @@ void LabelObjects::init(const Print& print)
|
||||
|
||||
// Iterate over all PrintObjects and their PrintInstances, collect PrintInstances which
|
||||
// belong to the same ModelObject.
|
||||
for (const PrintObject* po : print.objects())
|
||||
for (const PrintObject* po : objects)
|
||||
for (const PrintInstance& pi : po->instances())
|
||||
model_object_to_print_instances[pi.model_instance->get_object()].emplace_back(&pi);
|
||||
|
||||
@@ -87,13 +89,69 @@ void LabelObjects::init(const Print& print)
|
||||
}
|
||||
}
|
||||
|
||||
m_label_data.emplace(pi, LabelData{name, unique_id});
|
||||
// Now calculate the polygon and center for Cancel Object (this is not always used).
|
||||
Polygon outline = instance_outline(pi);
|
||||
assert(! outline.empty());
|
||||
outline.douglas_peucker(50000.f);
|
||||
Point center = outline.centroid();
|
||||
char buffer[64];
|
||||
std::snprintf(buffer, sizeof(buffer) - 1, "%.3f,%.3f", unscale<float>(center[0]), unscale<float>(center[1]));
|
||||
std::string center_str(buffer);
|
||||
std::string polygon_str = std::string("[");
|
||||
for (const Point& point : outline) {
|
||||
std::snprintf(buffer, sizeof(buffer) - 1, "[%.3f,%.3f],", unscale<float>(point[0]), unscale<float>(point[1]));
|
||||
polygon_str += buffer;
|
||||
}
|
||||
polygon_str.pop_back();
|
||||
polygon_str += "]";
|
||||
|
||||
m_label_data.emplace_back(LabelData{pi, name, center_str, polygon_str, unique_id});
|
||||
++unique_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool LabelObjects::update(const PrintInstance *instance) {
|
||||
if (this->last_operation_instance == instance) {
|
||||
return false;
|
||||
}
|
||||
this->last_operation_instance = instance;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string LabelObjects::maybe_start_instance(GCodeWriter& writer) {
|
||||
if (current_instance == nullptr && last_operation_instance != nullptr) {
|
||||
current_instance = last_operation_instance;
|
||||
|
||||
std::string result{this->start_object(*current_instance, LabelObjects::IncludeName::No)};
|
||||
result += writer.reset_e(true);
|
||||
return result;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string LabelObjects::maybe_stop_instance() {
|
||||
if (current_instance != nullptr) {
|
||||
const std::string result{this->stop_object(*current_instance)};
|
||||
current_instance = nullptr;
|
||||
return result;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string LabelObjects::maybe_change_instance(GCodeWriter& writer) {
|
||||
if (last_operation_instance != current_instance) {
|
||||
const std::string stop_instance_gcode{this->maybe_stop_instance()};
|
||||
// Be carefull with refactoring: this->maybe_stop_instance() + this->maybe_start_instance()
|
||||
// may not be evaluated in order. The order is indeed undefined!
|
||||
return stop_instance_gcode + this->maybe_start_instance(writer);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
bool LabelObjects::has_active_instance() {
|
||||
return this->current_instance != nullptr;
|
||||
}
|
||||
|
||||
std::string LabelObjects::all_objects_header() const
|
||||
{
|
||||
@@ -102,38 +160,34 @@ std::string LabelObjects::all_objects_header() const
|
||||
|
||||
std::string out;
|
||||
|
||||
// Let's sort the values according to unique_id so they are in the same order in which they were added.
|
||||
std::vector<std::pair<const PrintInstance*, LabelData>> label_data_sorted;
|
||||
for (const auto& pi_and_label : m_label_data)
|
||||
label_data_sorted.emplace_back(pi_and_label);
|
||||
std::sort(label_data_sorted.begin(), label_data_sorted.end(), [](const auto& ld1, const auto& ld2) { return ld1.second.unique_id < ld2.second.unique_id; });
|
||||
|
||||
out += "\n";
|
||||
for (const auto& [print_instance, label] : label_data_sorted) {
|
||||
if (m_label_objects_style == LabelObjectsStyle::Firmware && m_flavor == gcfKlipper) {
|
||||
char buffer[64];
|
||||
out += "EXCLUDE_OBJECT_DEFINE NAME='" + label.name + "'";
|
||||
Polygon outline = instance_outline(print_instance);
|
||||
assert(! outline.empty());
|
||||
outline.douglas_peucker(50000.f);
|
||||
Point center = outline.centroid();
|
||||
std::snprintf(buffer, sizeof(buffer) - 1, " CENTER=%.3f,%.3f", unscale<float>(center[0]), unscale<float>(center[1]));
|
||||
out += buffer + std::string(" POLYGON=[");
|
||||
for (const Point& point : outline) {
|
||||
std::snprintf(buffer, sizeof(buffer) - 1, "[%.3f,%.3f],", unscale<float>(point[0]), unscale<float>(point[1]));
|
||||
out += buffer;
|
||||
}
|
||||
out.pop_back();
|
||||
out += "]\n";
|
||||
} else {
|
||||
out += start_object(*print_instance, IncludeName::Yes);
|
||||
out += stop_object(*print_instance);
|
||||
for (const LabelData& label : m_label_data) {
|
||||
if (m_label_objects_style == LabelObjectsStyle::Firmware && m_flavor == gcfKlipper)
|
||||
out += "EXCLUDE_OBJECT_DEFINE NAME='" + label.name + "' CENTER=" + label.center + " POLYGON=" + label.polygon + "\n";
|
||||
else {
|
||||
out += start_object(*label.pi, IncludeName::Yes);
|
||||
out += stop_object(*label.pi);
|
||||
}
|
||||
}
|
||||
out += "\n";
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string LabelObjects::all_objects_header_singleline_json() const
|
||||
{
|
||||
std::string out;
|
||||
out = "{\"objects\":[";
|
||||
for (size_t i=0; i<m_label_data.size(); ++i) {
|
||||
const LabelData& label = m_label_data[i];
|
||||
out += std::string("{\"name\":\"") + label.name + "\",";
|
||||
out += "\"polygon\":" + label.polygon + "}";
|
||||
if (i != m_label_data.size() - 1)
|
||||
out += ",";
|
||||
}
|
||||
out += "]}";
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
std::string LabelObjects::start_object(const PrintInstance& print_instance, IncludeName include_name) const
|
||||
@@ -141,7 +195,7 @@ std::string LabelObjects::start_object(const PrintInstance& print_instance, Incl
|
||||
if (m_label_objects_style == LabelObjectsStyle::Disabled)
|
||||
return std::string();
|
||||
|
||||
const LabelData& label = m_label_data.at(&print_instance);
|
||||
const LabelData& label = *std::find_if(m_label_data.begin(), m_label_data.end(), [&print_instance](const LabelData& ld) { return ld.pi == &print_instance; });
|
||||
|
||||
std::string out;
|
||||
if (m_label_objects_style == LabelObjectsStyle::Octoprint)
|
||||
@@ -170,7 +224,7 @@ std::string LabelObjects::stop_object(const PrintInstance& print_instance) const
|
||||
if (m_label_objects_style == LabelObjectsStyle::Disabled)
|
||||
return std::string();
|
||||
|
||||
const LabelData& label = m_label_data.at(&print_instance);
|
||||
const LabelData& label = *std::find_if(m_label_data.begin(), m_label_data.end(), [&print_instance](const LabelData& ld) { return ld.pi == &print_instance; });
|
||||
|
||||
std::string out;
|
||||
if (m_label_objects_style == LabelObjectsStyle::Octoprint)
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
#define slic3r_GCode_LabelObjects_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "libslic3r/Print.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -11,30 +13,49 @@ enum class LabelObjectsStyle;
|
||||
struct PrintInstance;
|
||||
class Print;
|
||||
|
||||
class GCodeWriter;
|
||||
|
||||
namespace GCode {
|
||||
|
||||
|
||||
class LabelObjects {
|
||||
class LabelObjects
|
||||
{
|
||||
public:
|
||||
void init(const SpanOfConstPtrs<PrintObject>& objects, LabelObjectsStyle label_object_style, GCodeFlavor gcode_flavor);
|
||||
std::string all_objects_header() const;
|
||||
std::string all_objects_header_singleline_json() const;
|
||||
|
||||
bool update(const PrintInstance *instance);
|
||||
|
||||
std::string maybe_start_instance(GCodeWriter& writer);
|
||||
|
||||
std::string maybe_stop_instance();
|
||||
|
||||
std::string maybe_change_instance(GCodeWriter& writer);
|
||||
|
||||
bool has_active_instance();
|
||||
|
||||
private:
|
||||
struct LabelData
|
||||
{
|
||||
const PrintInstance* pi;
|
||||
std::string name;
|
||||
std::string center;
|
||||
std::string polygon;
|
||||
int unique_id;
|
||||
};
|
||||
enum class IncludeName {
|
||||
No,
|
||||
Yes
|
||||
};
|
||||
void init(const Print& print);
|
||||
std::string all_objects_header() const;
|
||||
std::string start_object(const PrintInstance& print_instance, IncludeName include_name) const;
|
||||
std::string stop_object(const PrintInstance& print_instance) const;
|
||||
|
||||
private:
|
||||
struct LabelData {
|
||||
std::string name;
|
||||
int unique_id;
|
||||
};
|
||||
const PrintInstance* current_instance{nullptr};
|
||||
const PrintInstance* last_operation_instance{nullptr};
|
||||
|
||||
LabelObjectsStyle m_label_objects_style;
|
||||
GCodeFlavor m_flavor;
|
||||
std::unordered_map<const PrintInstance*, LabelData> m_label_data;
|
||||
std::vector<LabelData> m_label_data;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <memory.h>
|
||||
#include <cstring>
|
||||
#include <cfloat>
|
||||
#include <algorithm>
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../PrintConfig.hpp"
|
||||
@@ -27,6 +28,11 @@ static constexpr float max_segment_length = 5.f;
|
||||
// affect how distant will be propagated a flow rate adjustment.
|
||||
static constexpr int max_look_back_limit = 128;
|
||||
|
||||
// Max non-extruding XY distance (travel move) in mm between two continuous extrusions where we pretend
|
||||
// it's all one continuous extrusion line. Above this distance, we assume extruder pressure hits 0
|
||||
// This exists because often there are tiny travel moves between stuff like infill.
|
||||
// Lines where some extruder pressure will remain (so we should equalize between these small travels).
|
||||
static constexpr double max_ignored_gap_between_extruding_segments = 3.;
|
||||
PressureEqualizer::PressureEqualizer(const Slic3r::GCodeConfig &config) : m_use_relative_e_distances(config.use_relative_e_distances.value)
|
||||
{
|
||||
// Preallocate some data, so that output_buffer.data() will return an empty string.
|
||||
@@ -59,8 +65,8 @@ PressureEqualizer::PressureEqualizer(const Slic3r::GCodeConfig &config) : m_use_
|
||||
extrusion_rate_slope.positive = m_max_volumetric_extrusion_rate_slope_positive;
|
||||
}
|
||||
|
||||
// Don't regulate the pressure before and after gap-fill and ironing.
|
||||
for (const GCodeExtrusionRole er : {GCodeExtrusionRole::GapFill, GCodeExtrusionRole::Ironing}) {
|
||||
// Don't regulate the pressure before and after ironing.
|
||||
for (const GCodeExtrusionRole er : {GCodeExtrusionRole::Ironing}) {
|
||||
m_max_volumetric_extrusion_rate_slopes[size_t(er)].negative = 0;
|
||||
m_max_volumetric_extrusion_rate_slopes[size_t(er)].positive = 0;
|
||||
}
|
||||
@@ -97,6 +103,72 @@ void PressureEqualizer::process_layer(const std::string &gcode)
|
||||
}
|
||||
assert(!this->opened_extrude_set_speed_block);
|
||||
}
|
||||
// At this point, we have an entire layer of gcode lines loaded into m_gcode_lines.
|
||||
// Now, we will split the mix of travels and extrusions into segments of continuous extrusions and process them.
|
||||
// We skip over large travels, and pretend that small ones are part of a continuous extrusion segment.
|
||||
for (auto current_extrusion_end_it = m_gcode_lines.cbegin(); current_extrusion_end_it != m_gcode_lines.cend();) {
|
||||
// Find beginning of next extrusion segment from current position.
|
||||
const auto current_extrusion_begin_it = std::find_if(current_extrusion_end_it, m_gcode_lines.cend(), [](const GCodeLine &line) {
|
||||
return line.extruding();
|
||||
});
|
||||
|
||||
// We start with extrusion length of zero.
|
||||
current_extrusion_end_it = current_extrusion_begin_it;
|
||||
|
||||
// Inner loop extends the extrusion segment over small travel moves.
|
||||
while (current_extrusion_end_it != m_gcode_lines.cend()) {
|
||||
// Find the end of the current extrusion segment.
|
||||
const auto travel_begin_it = std::find_if(std::next(current_extrusion_end_it), m_gcode_lines.cend(), [](const GCodeLine &line) {
|
||||
return !line.extruding();
|
||||
});
|
||||
|
||||
current_extrusion_end_it = std::prev(travel_begin_it);
|
||||
|
||||
const auto next_extrusion_segment_it = advance_segment_beyond_small_gap(current_extrusion_end_it);
|
||||
if (std::distance(current_extrusion_end_it, next_extrusion_segment_it) > 0) {
|
||||
// Extend the continuous line over the small gap.
|
||||
current_extrusion_end_it = next_extrusion_segment_it;
|
||||
continue; // Keep going, loop again to find the new end of extrusion segment.
|
||||
} else {
|
||||
break; // Gap to next extrude is too big, stop looking forward. We've found the end of this segment.
|
||||
}
|
||||
}
|
||||
|
||||
// Now, run the pressure equalizer across the segment like a streamroller.
|
||||
// It operates on a sliding window that moves forward across gcode line by line.
|
||||
const std::ptrdiff_t current_extrusion_begin_idx = std::distance(m_gcode_lines.cbegin(), current_extrusion_begin_it);
|
||||
for (auto current_line_it = current_extrusion_begin_it; current_line_it != current_extrusion_end_it; ++current_line_it) {
|
||||
const std::ptrdiff_t current_line_idx = std::distance(m_gcode_lines.cbegin(), current_line_it);
|
||||
|
||||
// Feed pressure equalizer past lines, going back to max_look_back_limit (or start of segment).
|
||||
const size_t start_idx = size_t(std::max<std::ptrdiff_t>(current_extrusion_begin_idx, current_line_idx - max_look_back_limit));
|
||||
adjust_volumetric_rate(start_idx, size_t(current_line_idx));
|
||||
}
|
||||
|
||||
// Current extrusion is all done processing so advance beyond it for the next loop.
|
||||
if (current_extrusion_end_it != m_gcode_lines.cend())
|
||||
++current_extrusion_end_it;
|
||||
}
|
||||
}
|
||||
|
||||
PressureEqualizer::GCodeLinesConstIt PressureEqualizer::advance_segment_beyond_small_gap(const GCodeLinesConstIt &last_extruding_line_it) const {
|
||||
// This should only be run on the last extruding line before a gap.
|
||||
assert(last_extruding_line_it != m_gcode_lines.cend() && last_extruding_line_it->extruding());
|
||||
double travel_distance = 0.;
|
||||
// Start at the beginning of a gap, advance till extrusion found or gap too big.
|
||||
for (auto current_line_it = std::next(last_extruding_line_it); current_line_it != m_gcode_lines.cend(); ++current_line_it) {
|
||||
// Started extruding again! Return segment extension.
|
||||
if (current_line_it->extruding())
|
||||
return current_line_it;
|
||||
|
||||
travel_distance += current_line_it->dist_xy();
|
||||
// Gap too big, don't extend segment.
|
||||
if (travel_distance > max_ignored_gap_between_extruding_segments)
|
||||
return last_extruding_line_it;
|
||||
}
|
||||
|
||||
// Looped until the end of the layer and couldn't extend extrusion.
|
||||
return last_extruding_line_it;
|
||||
}
|
||||
|
||||
LayerResult PressureEqualizer::process_layer(LayerResult &&input)
|
||||
@@ -391,7 +463,6 @@ bool PressureEqualizer::process_line(const char *line, const char *line_end, GCo
|
||||
buf.extruder_id = m_current_extruder;
|
||||
memcpy(buf.pos_end, m_current_pos, sizeof(float)*5);
|
||||
|
||||
adjust_volumetric_rate();
|
||||
#ifdef PRESSURE_EQUALIZER_DEBUG
|
||||
++line_idx;
|
||||
#endif
|
||||
@@ -506,16 +577,14 @@ void PressureEqualizer::output_gcode_line(const size_t line_idx)
|
||||
}
|
||||
}
|
||||
|
||||
void PressureEqualizer::adjust_volumetric_rate()
|
||||
void PressureEqualizer::adjust_volumetric_rate(const size_t first_line_idx, const size_t last_line_idx)
|
||||
{
|
||||
if (m_gcode_lines.size() < 2)
|
||||
// Don't bother adjusting volumetric rate if there's no gcode to adjust.
|
||||
if (last_line_idx <= first_line_idx || last_line_idx - first_line_idx < 2)
|
||||
return;
|
||||
|
||||
// Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes.
|
||||
size_t fist_line_idx = size_t(std::max<int>(0, int(m_gcode_lines.size()) - max_look_back_limit));
|
||||
const size_t last_line_idx = m_gcode_lines.size() - 1;
|
||||
size_t line_idx = last_line_idx;
|
||||
if (line_idx == fist_line_idx || !m_gcode_lines[line_idx].extruding())
|
||||
if (line_idx == first_line_idx || !m_gcode_lines[line_idx].extruding())
|
||||
// Nothing to do, the last move is not extruding.
|
||||
return;
|
||||
|
||||
@@ -523,13 +592,13 @@ void PressureEqualizer::adjust_volumetric_rate()
|
||||
feedrate_per_extrusion_role.fill(std::numeric_limits<float>::max());
|
||||
feedrate_per_extrusion_role[int(m_gcode_lines[line_idx].extrusion_role)] = m_gcode_lines[line_idx].volumetric_extrusion_rate_start;
|
||||
|
||||
while (line_idx != fist_line_idx) {
|
||||
while (line_idx != first_line_idx) {
|
||||
size_t idx_prev = line_idx - 1;
|
||||
for (; !m_gcode_lines[idx_prev].extruding() && idx_prev != fist_line_idx; --idx_prev);
|
||||
for (; !m_gcode_lines[idx_prev].extruding() && idx_prev != first_line_idx; --idx_prev);
|
||||
if (!m_gcode_lines[idx_prev].extruding())
|
||||
break;
|
||||
// Don't decelerate before ironing and gap-fill.
|
||||
if (m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::Ironing || m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::GapFill) {
|
||||
// Don't decelerate before ironing.
|
||||
if (m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::Ironing) {
|
||||
line_idx = idx_prev;
|
||||
continue;
|
||||
}
|
||||
@@ -549,7 +618,8 @@ void PressureEqualizer::adjust_volumetric_rate()
|
||||
// Limit by the succeeding volumetric flow rate.
|
||||
rate_end = rate_succ;
|
||||
|
||||
if (!line.adjustable_flow || line.extrusion_role == GCodeExtrusionRole::ExternalPerimeter || line.extrusion_role == GCodeExtrusionRole::GapFill || line.extrusion_role == GCodeExtrusionRole::BridgeInfill || line.extrusion_role == GCodeExtrusionRole::Ironing) {
|
||||
// Don't alter the flow rate for these extrusion types.
|
||||
if (!line.adjustable_flow || line.extrusion_role == GCodeExtrusionRole::BridgeInfill || line.extrusion_role == GCodeExtrusionRole::Ironing) {
|
||||
rate_end = line.volumetric_extrusion_rate_end;
|
||||
} else if (line.volumetric_extrusion_rate_end > rate_end) {
|
||||
line.volumetric_extrusion_rate_end = rate_end;
|
||||
@@ -571,9 +641,8 @@ void PressureEqualizer::adjust_volumetric_rate()
|
||||
line.modified = true;
|
||||
}
|
||||
}
|
||||
// feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_start : rate_start;
|
||||
// Don't store feed rate for ironing and gap-fill.
|
||||
if (line.extrusion_role != GCodeExtrusionRole::Ironing && line.extrusion_role != GCodeExtrusionRole::GapFill)
|
||||
// Don't store feed rate for ironing.
|
||||
if (line.extrusion_role != GCodeExtrusionRole::Ironing)
|
||||
feedrate_per_extrusion_role[iRole] = line.volumetric_extrusion_rate_start;
|
||||
}
|
||||
}
|
||||
@@ -587,8 +656,8 @@ void PressureEqualizer::adjust_volumetric_rate()
|
||||
for (; !m_gcode_lines[idx_next].extruding() && idx_next != last_line_idx; ++idx_next);
|
||||
if (!m_gcode_lines[idx_next].extruding())
|
||||
break;
|
||||
// Don't accelerate after ironing and gap-fill.
|
||||
if (m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::Ironing || m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::GapFill) {
|
||||
// Don't accelerate after ironing.
|
||||
if (m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::Ironing) {
|
||||
line_idx = idx_next;
|
||||
continue;
|
||||
}
|
||||
@@ -603,7 +672,8 @@ void PressureEqualizer::adjust_volumetric_rate()
|
||||
continue; // The positive rate is unlimited or the rate for GCodeExtrusionRole iRole is unlimited.
|
||||
|
||||
float rate_start = feedrate_per_extrusion_role[iRole];
|
||||
if (!line.adjustable_flow || line.extrusion_role == GCodeExtrusionRole::ExternalPerimeter || line.extrusion_role == GCodeExtrusionRole::GapFill || line.extrusion_role == GCodeExtrusionRole::BridgeInfill || line.extrusion_role == GCodeExtrusionRole::Ironing) {
|
||||
// Don't alter the flow rate for these extrusion types.
|
||||
if (!line.adjustable_flow || line.extrusion_role == GCodeExtrusionRole::BridgeInfill || line.extrusion_role == GCodeExtrusionRole::Ironing) {
|
||||
rate_start = line.volumetric_extrusion_rate_start;
|
||||
} else if (iRole == size_t(line.extrusion_role) && rate_prec < rate_start)
|
||||
rate_start = rate_prec;
|
||||
@@ -627,9 +697,8 @@ void PressureEqualizer::adjust_volumetric_rate()
|
||||
line.modified = true;
|
||||
}
|
||||
}
|
||||
// feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_end : rate_end;
|
||||
// Don't store feed rate for ironing and gap-fill.
|
||||
if (line.extrusion_role != GCodeExtrusionRole::Ironing && line.extrusion_role != GCodeExtrusionRole::GapFill)
|
||||
// Don't store feed rate for ironing
|
||||
if (line.extrusion_role != GCodeExtrusionRole::Ironing)
|
||||
feedrate_per_extrusion_role[iRole] = line.volumetric_extrusion_rate_end;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +169,8 @@ private:
|
||||
bool extrude_end_tag = false;
|
||||
};
|
||||
|
||||
using GCodeLines = std::vector<GCodeLine>;
|
||||
using GCodeLinesConstIt = GCodeLines::const_iterator;
|
||||
// Output buffer will only grow. It will not be reallocated over and over.
|
||||
std::vector<char> output_buffer;
|
||||
size_t output_buffer_length;
|
||||
@@ -182,9 +184,10 @@ private:
|
||||
bool process_line(const char *line, const char *line_end, GCodeLine &buf);
|
||||
void output_gcode_line(size_t line_idx);
|
||||
|
||||
GCodeLinesConstIt advance_segment_beyond_small_gap(const GCodeLinesConstIt &last_extruding_line_it) const;
|
||||
// Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes.
|
||||
// Then go forward and adjust the feedrate to decrease the slope of the extrusion rate changes.
|
||||
void adjust_volumetric_rate();
|
||||
void adjust_volumetric_rate(size_t first_line_idx, size_t last_line_idx);
|
||||
|
||||
// Push the text to the end of the output_buffer.
|
||||
inline void push_to_output(GCodeG1Formatter &formatter);
|
||||
|
||||
@@ -133,6 +133,11 @@ double clip_end(SmoothPath &path, double distance, double min_point_distance_thr
|
||||
return distance;
|
||||
}
|
||||
|
||||
void reverse(SmoothPath &path) {
|
||||
std::reverse(path.begin(), path.end());
|
||||
for (SmoothPathElement &path_element : path)
|
||||
Geometry::ArcWelder::reverse(path_element.path);
|
||||
}
|
||||
void SmoothPathCache::interpolate_add(const ExtrusionPath &path, const InterpolationParameters ¶ms)
|
||||
{
|
||||
double tolerance = params.tolerance;
|
||||
|
||||
@@ -33,6 +33,7 @@ std::optional<Point> sample_path_point_at_distance_from_end(const SmoothPath &pa
|
||||
// rather discard such a degenerate segment.
|
||||
double clip_end(SmoothPath &path, double distance, double min_point_distance_threshold);
|
||||
|
||||
void reverse(SmoothPath &path);
|
||||
class SmoothPathCache
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
#include "SpiralVase.hpp"
|
||||
#include "GCode.hpp"
|
||||
#include <sstream>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
std::string SpiralVase::process_layer(const std::string &gcode)
|
||||
static AABBTreeLines::LinesDistancer<Linef> get_layer_distancer(const std::vector<Vec2f> &layer_points)
|
||||
{
|
||||
Linesf lines;
|
||||
for (size_t idx = 1; idx < layer_points.size(); ++idx)
|
||||
lines.emplace_back(layer_points[idx - 1].cast<double>(), layer_points[idx].cast<double>());
|
||||
|
||||
return AABBTreeLines::LinesDistancer{std::move(lines)};
|
||||
}
|
||||
|
||||
std::string SpiralVase::process_layer(const std::string &gcode, bool last_layer)
|
||||
{
|
||||
/* This post-processor relies on several assumptions:
|
||||
- all layers are processed through it, including those that are not supposed
|
||||
@@ -22,8 +33,8 @@ std::string SpiralVase::process_layer(const std::string &gcode)
|
||||
}
|
||||
|
||||
// Get total XY length for this layer by summing all extrusion moves.
|
||||
float total_layer_length = 0;
|
||||
float layer_height = 0;
|
||||
float total_layer_length = 0.f;
|
||||
float layer_height = 0.f;
|
||||
float z = 0.f;
|
||||
|
||||
{
|
||||
@@ -49,15 +60,21 @@ std::string SpiralVase::process_layer(const std::string &gcode)
|
||||
// Remove layer height from initial Z.
|
||||
z -= layer_height;
|
||||
|
||||
std::string new_gcode;
|
||||
//FIXME Tapering of the transition layer only works reliably with relative extruder distances.
|
||||
// FIXME Tapering of the transition layer and smoothing only works reliably with relative extruder distances.
|
||||
// For absolute extruder distances it will be switched off.
|
||||
// Tapering the absolute extruder distances requires to process every extrusion value after the first transition
|
||||
// layer.
|
||||
bool transition = m_transition_layer && m_config.use_relative_e_distances.value;
|
||||
float layer_height_factor = layer_height / total_layer_length;
|
||||
const bool transition_in = m_transition_layer && m_config.use_relative_e_distances.value;
|
||||
const bool transition_out = last_layer && m_config.use_relative_e_distances.value;
|
||||
const bool smooth_spiral = m_smooth_spiral && m_config.use_relative_e_distances.value;
|
||||
|
||||
const AABBTreeLines::LinesDistancer previous_layer_distancer = get_layer_distancer(m_previous_layer);
|
||||
Vec2f last_point = m_previous_layer.empty() ? Vec2f::Zero() : m_previous_layer.back();
|
||||
float len = 0.f;
|
||||
m_reader.parse_buffer(gcode, [&new_gcode, &z, total_layer_length, layer_height_factor, transition, &len]
|
||||
std::string new_gcode, transition_gcode;
|
||||
std::vector<Vec2f> current_layer;
|
||||
m_reader.parse_buffer(gcode, [z, total_layer_length, layer_height, transition_in, transition_out, smooth_spiral, max_xy_smoothing = m_max_xy_smoothing,
|
||||
&len, &last_point, &new_gcode, &transition_gcode, ¤t_layer, &previous_layer_distancer]
|
||||
(GCodeReader &reader, GCodeReader::GCodeLine line) {
|
||||
if (line.cmd_is("G1")) {
|
||||
if (line.has_z()) {
|
||||
@@ -66,16 +83,52 @@ std::string SpiralVase::process_layer(const std::string &gcode)
|
||||
line.set(reader, Z, z);
|
||||
new_gcode += line.raw() + '\n';
|
||||
return;
|
||||
} else if (line.has_x() || line.has_y()) { // Sometimes lines have X/Y but the move is to the last position.
|
||||
if (const float dist_XY = line.dist_XY(reader); dist_XY > 0 && line.extruding(reader)) { // Exclude wipe and retract
|
||||
len += dist_XY;
|
||||
const float factor = len / total_layer_length;
|
||||
if (transition_in)
|
||||
// Transition layer, interpolate the amount of extrusion from zero to the final value.
|
||||
line.set(reader, E, line.e() * factor, 5);
|
||||
else if (transition_out) {
|
||||
// We want the last layer to ramp down extrusion, but without changing z height!
|
||||
// So clone the line before we mess with its Z and duplicate it into a new layer that ramps down E
|
||||
// We add this new layer at the very end
|
||||
GCodeReader::GCodeLine transition_line(line);
|
||||
transition_line.set(reader, E, line.e() * (1.f - factor), 5);
|
||||
transition_gcode += transition_line.raw() + '\n';
|
||||
}
|
||||
|
||||
// This line is the core of Spiral Vase mode, ramp up the Z smoothly
|
||||
line.set(reader, Z, z + factor * layer_height);
|
||||
|
||||
bool emit_gcode_line = true;
|
||||
if (smooth_spiral) {
|
||||
// Now we also need to try to interpolate X and Y
|
||||
Vec2f p(line.x(), line.y()); // Get current x/y coordinates
|
||||
current_layer.emplace_back(p); // Store that point for later use on the next layer
|
||||
|
||||
auto [nearest_distance, idx, nearest_pt] = previous_layer_distancer.distance_from_lines_extra<false>(p.cast<double>());
|
||||
if (nearest_distance < max_xy_smoothing) {
|
||||
// Interpolate between the point on this layer and the point on the previous layer
|
||||
Vec2f target = nearest_pt.cast<float>() * (1.f - factor) + p * factor;
|
||||
|
||||
// We will emit a new g-code line only when XYZ positions differ from the previous g-code line.
|
||||
emit_gcode_line = GCodeFormatter::quantize(last_point) != GCodeFormatter::quantize(target);
|
||||
|
||||
line.set(reader, X, target.x());
|
||||
line.set(reader, Y, target.y());
|
||||
// We need to figure out the distance of this new line!
|
||||
float modified_dist_XY = (last_point - target).norm();
|
||||
// Scale the extrusion amount according to change in length
|
||||
line.set(reader, E, line.e() * modified_dist_XY / dist_XY, 5);
|
||||
last_point = target;
|
||||
} else {
|
||||
float dist_XY = line.dist_XY(reader);
|
||||
if (dist_XY > 0) {
|
||||
// horizontal move
|
||||
if (line.extruding(reader)) {
|
||||
len += dist_XY;
|
||||
line.set(reader, Z, z + len * layer_height_factor);
|
||||
if (transition && line.has(E))
|
||||
// Transition layer, modulate the amount of extrusion from zero to the final value.
|
||||
line.set(reader, E, line.value(E) * len / total_layer_length);
|
||||
last_point = p;
|
||||
}
|
||||
}
|
||||
|
||||
if (emit_gcode_line)
|
||||
new_gcode += line.raw() + '\n';
|
||||
}
|
||||
return;
|
||||
@@ -84,14 +137,18 @@ std::string SpiralVase::process_layer(const std::string &gcode)
|
||||
cause a visible seam when loops are not aligned in XY; by skipping
|
||||
it we blend the first loop move in the XY plane (although the smoothness
|
||||
of such blend depend on how long the first segment is; maybe we should
|
||||
enforce some minimum length?). */
|
||||
enforce some minimum length?).
|
||||
When smooth_spiral is enabled, we're gonna end up exactly where the next layer should
|
||||
start anyway, so we don't need the travel move */
|
||||
}
|
||||
}
|
||||
}
|
||||
new_gcode += line.raw() + '\n';
|
||||
if (transition_out)
|
||||
transition_gcode += line.raw() + '\n';
|
||||
});
|
||||
|
||||
return new_gcode;
|
||||
m_previous_layer = std::move(current_layer);
|
||||
return new_gcode + transition_gcode;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,28 +6,38 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class SpiralVase {
|
||||
class SpiralVase
|
||||
{
|
||||
public:
|
||||
SpiralVase(const PrintConfig &config) : m_config(config)
|
||||
SpiralVase() = delete;
|
||||
|
||||
explicit SpiralVase(const PrintConfig &config) : m_config(config)
|
||||
{
|
||||
m_reader.z() = (float)m_config.z_offset;
|
||||
m_reader.apply_config(m_config);
|
||||
const double max_nozzle_diameter = *std::max_element(config.nozzle_diameter.values.begin(), config.nozzle_diameter.values.end());
|
||||
m_max_xy_smoothing = float(2. * max_nozzle_diameter);
|
||||
};
|
||||
|
||||
void enable(bool en) {
|
||||
m_transition_layer = en && ! m_enabled;
|
||||
m_enabled = en;
|
||||
void enable(bool enable)
|
||||
{
|
||||
m_transition_layer = enable && !m_enabled;
|
||||
m_enabled = enable;
|
||||
}
|
||||
|
||||
std::string process_layer(const std::string &gcode);
|
||||
std::string process_layer(const std::string &gcode, bool last_layer);
|
||||
|
||||
private:
|
||||
const PrintConfig &m_config;
|
||||
GCodeReader m_reader;
|
||||
float m_max_xy_smoothing = 0.f;
|
||||
|
||||
bool m_enabled = false;
|
||||
// First spiral vase layer. Layer height has to be ramped up from zero to the target layer height.
|
||||
bool m_transition_layer = false;
|
||||
// Whether to interpolate XY coordinates with the previous layer. Results in no seam at layer changes
|
||||
bool m_smooth_spiral = true;
|
||||
std::vector<Vec2f> m_previous_layer;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -32,27 +32,11 @@ void Wipe::init(const PrintConfig &config, const std::vector<unsigned int> &extr
|
||||
this->enable(wipe_xy);
|
||||
}
|
||||
|
||||
void Wipe::set_path(SmoothPath &&path, bool reversed)
|
||||
{
|
||||
void Wipe::set_path(SmoothPath &&path) {
|
||||
this->reset_path();
|
||||
|
||||
if (this->enabled() && ! path.empty()) {
|
||||
if (coord_t wipe_len_max_scaled = scaled(m_wipe_len_max); reversed) {
|
||||
m_path = std::move(path.back().path);
|
||||
Geometry::ArcWelder::reverse(m_path);
|
||||
int64_t len = Geometry::ArcWelder::estimate_path_length(m_path);
|
||||
for (auto it = std::next(path.rbegin()); len < wipe_len_max_scaled && it != path.rend(); ++ it) {
|
||||
if (it->path_attributes.role.is_bridge())
|
||||
break; // Do not perform a wipe on bridges.
|
||||
assert(it->path.size() >= 2);
|
||||
assert(m_path.back().point == it->path.back().point);
|
||||
if (m_path.back().point != it->path.back().point)
|
||||
// ExtrusionMultiPath is interrupted in some place. This should not really happen.
|
||||
break;
|
||||
len += Geometry::ArcWelder::estimate_path_length(it->path);
|
||||
m_path.insert(m_path.end(), it->path.rbegin() + 1, it->path.rend());
|
||||
}
|
||||
} else {
|
||||
const coord_t wipe_len_max_scaled = scaled(m_wipe_len_max);
|
||||
m_path = std::move(path.front().path);
|
||||
int64_t len = Geometry::ArcWelder::estimate_path_length(m_path);
|
||||
for (auto it = std::next(path.begin()); len < wipe_len_max_scaled && it != path.end(); ++ it) {
|
||||
@@ -67,7 +51,6 @@ void Wipe::set_path(SmoothPath &&path, bool reversed)
|
||||
m_path.insert(m_path.end(), it->path.begin() + 1, it->path.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(m_path.empty() || m_path.size() > 1);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public:
|
||||
if (this->enabled() && path.size() > 1)
|
||||
m_path = std::move(path);
|
||||
}
|
||||
void set_path(SmoothPath &&path, bool reversed);
|
||||
void set_path(SmoothPath &&path);
|
||||
void offset_path(const Point &v) { m_offset += v; }
|
||||
|
||||
std::string wipe(GCodeGenerator &gcodegen, bool toolchange);
|
||||
|
||||
@@ -22,6 +22,16 @@
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
// Calculates length of extrusion line to extrude given volume
|
||||
static float volume_to_length(float volume, float line_width, float layer_height)
|
||||
{
|
||||
return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))));
|
||||
}
|
||||
|
||||
static float length_to_volume(float length, float line_width, float layer_height)
|
||||
{
|
||||
return std::max(0.f, length * layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f)));
|
||||
}
|
||||
class WipeTowerWriter
|
||||
{
|
||||
public:
|
||||
@@ -102,6 +112,10 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
WipeTowerWriter& switch_filament_monitoring(bool enable) {
|
||||
m_gcode += std::string("G4 S0\n") + "M591 " + (enable ? "R" : "S0") + "\n";
|
||||
return *this;
|
||||
}
|
||||
// Suppress / resume G-code preview in Slic3r. Slic3r will have difficulty to differentiate the various
|
||||
// filament loading and cooling moves from normal extrusion moves. Therefore the writer
|
||||
// is asked to suppres output of some lines, which look like extrusions.
|
||||
@@ -284,6 +298,24 @@ public:
|
||||
return extrude_explicit(end_point, y(), loading_dist, x_speed * 60.f, false, false);
|
||||
}
|
||||
|
||||
// Loads filament while also moving towards given point in x-axis. Unlike the previous function, this one respects
|
||||
// both the loading_speed and x_speed. Can shorten the move.
|
||||
WipeTowerWriter& load_move_x_advanced_there_and_back(float farthest_x, float e_dist, float e_speed, float x_speed)
|
||||
{
|
||||
float old_x = x();
|
||||
float time = std::abs(e_dist / e_speed); // time that the whole move must take
|
||||
float x_max_dist = std::abs(farthest_x - x()); // max x-distance that we can travel
|
||||
float x_dist = x_speed * time; // totel x-distance to travel during the move
|
||||
int n = int(x_dist / (2*x_max_dist) + 1.f); // how many there and back moves should we do
|
||||
float r = 2*n*x_max_dist / x_dist; // actual/required dist if the move is not shortened
|
||||
|
||||
float end_point = x() + (farthest_x > x() ? 1.f : -1.f) * x_max_dist / r;
|
||||
for (int i=0; i<n; ++i) {
|
||||
extrude_explicit(end_point, y(), e_dist/(2.f*n), x_speed * 60.f, false, false);
|
||||
extrude_explicit(old_x, y(), e_dist/(2.f*n), x_speed * 60.f, false, false);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
// Elevate the extruder head above the current print_z position.
|
||||
WipeTowerWriter& z_hop(float hop, float f = 0.f)
|
||||
{
|
||||
@@ -326,6 +358,7 @@ public:
|
||||
// Set extruder temperature, don't wait by default.
|
||||
WipeTowerWriter& set_extruder_temp(int temperature, bool wait = false)
|
||||
{
|
||||
m_gcode += "G4 S0\n"; // to flush planner queue
|
||||
m_gcode += "M" + std::to_string(wait ? 109 : 104) + " S" + std::to_string(temperature) + "\n";
|
||||
return *this;
|
||||
}
|
||||
@@ -523,7 +556,9 @@ WipeTower::WipeTower(const PrintConfig& config, const PrintRegionConfig& default
|
||||
m_wipe_tower_rotation_angle(float(config.wipe_tower_rotation_angle)),
|
||||
m_wipe_tower_brim_width(float(config.wipe_tower_brim_width)),
|
||||
m_wipe_tower_cone_angle(float(config.wipe_tower_cone_angle)),
|
||||
m_extra_spacing(float(config.wipe_tower_extra_spacing/100.)),
|
||||
m_extra_flow(float(config.wipe_tower_extra_flow/100.)),
|
||||
m_extra_spacing_wipe(float(config.wipe_tower_extra_spacing/100. * config.wipe_tower_extra_flow/100.)),
|
||||
m_extra_spacing_ramming(float(config.wipe_tower_extra_spacing/100.)),
|
||||
m_y_shift(0.f),
|
||||
m_z_pos(0.f),
|
||||
m_bridging(float(config.wipe_tower_bridging)),
|
||||
@@ -560,6 +595,7 @@ WipeTower::WipeTower(const PrintConfig& config, const PrintRegionConfig& default
|
||||
m_set_extruder_trimpot = config.high_current_on_filament_swap;
|
||||
}
|
||||
|
||||
m_is_mk4mmu3 = boost::icontains(config.printer_notes.value, "PRINTER_MODEL_MK4") && boost::icontains(config.printer_notes.value, "MMU");
|
||||
// Calculate where the priming lines should be - very naive test not detecting parallelograms etc.
|
||||
const std::vector<Vec2d>& bed_points = config.bed_shape.values;
|
||||
BoundingBoxf bb(bed_points);
|
||||
@@ -594,6 +630,7 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config)
|
||||
m_filpar[idx].is_soluble = config.wipe_tower_extruder == 0 ? config.filament_soluble.get_at(idx) : (idx != size_t(config.wipe_tower_extruder - 1));
|
||||
m_filpar[idx].temperature = config.temperature.get_at(idx);
|
||||
m_filpar[idx].first_layer_temperature = config.first_layer_temperature.get_at(idx);
|
||||
m_filpar[idx].filament_minimal_purge_on_wipe_tower = config.filament_minimal_purge_on_wipe_tower.get_at(idx);
|
||||
|
||||
// If this is a single extruder MM printer, we will use all the SE-specific config values.
|
||||
// Otherwise, the defaults will be used to turn off the SE stuff.
|
||||
@@ -606,6 +643,8 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config)
|
||||
m_filpar[idx].cooling_moves = config.filament_cooling_moves.get_at(idx);
|
||||
m_filpar[idx].cooling_initial_speed = float(config.filament_cooling_initial_speed.get_at(idx));
|
||||
m_filpar[idx].cooling_final_speed = float(config.filament_cooling_final_speed.get_at(idx));
|
||||
m_filpar[idx].filament_stamping_loading_speed = float(config.filament_stamping_loading_speed.get_at(idx));
|
||||
m_filpar[idx].filament_stamping_distance = float(config.filament_stamping_distance.get_at(idx));
|
||||
}
|
||||
|
||||
m_filpar[idx].filament_area = float((M_PI/4.f) * pow(config.filament_diameter.get_at(idx), 2)); // all extruders are assumed to have the same filament diameter at this point
|
||||
@@ -719,7 +758,7 @@ std::vector<WipeTower::ToolChangeResult> WipeTower::prime(
|
||||
toolchange_Wipe(writer, cleaning_box , 20.f);
|
||||
box_coordinates box = cleaning_box;
|
||||
box.translate(0.f, writer.y() - cleaning_box.ld.y() + m_perimeter_width);
|
||||
toolchange_Unload(writer, box , m_filpar[m_current_tool].material, m_filpar[tools[idx_tool + 1]].first_layer_temperature);
|
||||
toolchange_Unload(writer, box , m_filpar[m_current_tool].material, m_filpar[m_current_tool].first_layer_temperature, m_filpar[tools[idx_tool + 1]].first_layer_temperature);
|
||||
cleaning_box.translate(prime_section_width, 0.f);
|
||||
writer.travel(cleaning_box.ld, 7200);
|
||||
}
|
||||
@@ -766,7 +805,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool)
|
||||
for (const auto &b : m_layer_info->tool_changes)
|
||||
if ( b.new_tool == tool ) {
|
||||
wipe_volume = b.wipe_volume;
|
||||
wipe_area = b.required_depth * m_layer_info->extra_spacing;
|
||||
wipe_area = b.required_depth;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -807,14 +846,15 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool)
|
||||
// Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool.
|
||||
if (tool != (unsigned int)-1){ // This is not the last change.
|
||||
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material,
|
||||
is_first_layer() ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature);
|
||||
(is_first_layer() ? m_filpar[m_current_tool].first_layer_temperature : m_filpar[m_current_tool].temperature),
|
||||
(is_first_layer() ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature));
|
||||
toolchange_Change(writer, tool, m_filpar[tool].material); // Change the tool, set a speed override for soluble and flex materials.
|
||||
toolchange_Load(writer, cleaning_box);
|
||||
writer.travel(writer.x(), writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road
|
||||
toolchange_Wipe(writer, cleaning_box, wipe_volume); // Wipe the newly loaded filament until the end of the assigned wipe area.
|
||||
++ m_num_tool_changes;
|
||||
} else
|
||||
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature);
|
||||
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature, m_filpar[m_current_tool].temperature);
|
||||
|
||||
m_depth_traversed += wipe_area;
|
||||
|
||||
@@ -841,13 +881,14 @@ void WipeTower::toolchange_Unload(
|
||||
WipeTowerWriter &writer,
|
||||
const box_coordinates &cleaning_box,
|
||||
const std::string& current_material,
|
||||
const int old_temperature,
|
||||
const int new_temperature)
|
||||
{
|
||||
float xl = cleaning_box.ld.x() + 1.f * m_perimeter_width;
|
||||
float xr = cleaning_box.rd.x() - 1.f * m_perimeter_width;
|
||||
|
||||
const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness
|
||||
const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing; // spacing between lines in mm
|
||||
const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing_ramming; // spacing between lines in mm
|
||||
|
||||
const Vec2f ramming_start_pos = Vec2f(xl, cleaning_box.ld.y() + m_depth_traversed + y_step/2.f);
|
||||
|
||||
@@ -860,10 +901,14 @@ void WipeTower::toolchange_Unload(
|
||||
float e_done = 0; // measures E move done from each segment
|
||||
|
||||
const bool do_ramming = m_semm || m_filpar[m_current_tool].multitool_ramming;
|
||||
const bool cold_ramming = m_is_mk4mmu3;
|
||||
|
||||
if (do_ramming) {
|
||||
writer.travel(ramming_start_pos); // move to starting position
|
||||
if (! m_is_mk4mmu3)
|
||||
writer.disable_linear_advance();
|
||||
if (cold_ramming)
|
||||
writer.set_extruder_temp(old_temperature - 20);
|
||||
}
|
||||
else
|
||||
writer.set_position(ramming_start_pos);
|
||||
@@ -884,7 +929,7 @@ void WipeTower::toolchange_Unload(
|
||||
if (tch.old_tool == m_current_tool) {
|
||||
sum_of_depths += tch.ramming_depth;
|
||||
float ramming_end_y = sum_of_depths;
|
||||
ramming_end_y -= (y_step/m_extra_spacing-m_perimeter_width) / 2.f; // center of final ramming line
|
||||
ramming_end_y -= (y_step/m_extra_spacing_ramming-m_perimeter_width) / 2.f; // center of final ramming line
|
||||
|
||||
if ( (m_current_shape == SHAPE_REVERSED && ramming_end_y < sparse_beginning_y - 0.5f*m_perimeter_width ) ||
|
||||
(m_current_shape == SHAPE_NORMAL && ramming_end_y > sparse_beginning_y + 0.5f*m_perimeter_width ) )
|
||||
@@ -898,6 +943,10 @@ void WipeTower::toolchange_Unload(
|
||||
}
|
||||
}
|
||||
|
||||
if (m_is_mk4mmu3) {
|
||||
writer.switch_filament_monitoring(false);
|
||||
writer.wait(1.5f);
|
||||
}
|
||||
|
||||
// now the ramming itself:
|
||||
while (do_ramming && i < m_filpar[m_current_tool].ramming_speed.size())
|
||||
@@ -938,31 +987,66 @@ void WipeTower::toolchange_Unload(
|
||||
.retract(0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed * 60.f)
|
||||
.resume_preview();
|
||||
}
|
||||
const int& number_of_cooling_moves = m_filpar[m_current_tool].cooling_moves;
|
||||
const bool cooling_will_happen = m_semm && number_of_cooling_moves > 0;
|
||||
bool change_temp_later = false;
|
||||
// Wipe tower should only change temperature with single extruder MM. Otherwise, all temperatures should
|
||||
// be already set and there is no need to change anything. Also, the temperature could be changed
|
||||
// for wrong extruder.
|
||||
if (m_semm) {
|
||||
if (new_temperature != 0 && (new_temperature != m_old_temperature || is_first_layer()) ) { // Set the extruder temperature, but don't wait.
|
||||
if (new_temperature != 0 && (new_temperature != m_old_temperature || is_first_layer() || cold_ramming) ) { // Set the extruder temperature, but don't wait.
|
||||
// If the required temperature is the same as last time, don't emit the M104 again (if user adjusted the value, it would be reset)
|
||||
// However, always change temperatures on the first layer (this is to avoid issues with priming lines turned off).
|
||||
if (cold_ramming && cooling_will_happen)
|
||||
change_temp_later = true;
|
||||
else
|
||||
writer.set_extruder_temp(new_temperature, false);
|
||||
m_old_temperature = new_temperature;
|
||||
}
|
||||
}
|
||||
|
||||
// Cooling:
|
||||
const int& number_of_moves = m_filpar[m_current_tool].cooling_moves;
|
||||
if (m_semm && number_of_moves > 0) {
|
||||
if (cooling_will_happen) {
|
||||
const float& initial_speed = m_filpar[m_current_tool].cooling_initial_speed;
|
||||
const float& final_speed = m_filpar[m_current_tool].cooling_final_speed;
|
||||
|
||||
float speed_inc = (final_speed - initial_speed) / (2.f * number_of_moves - 1.f);
|
||||
float speed_inc = (final_speed - initial_speed) / (2.f * number_of_cooling_moves - 1.f);
|
||||
|
||||
if (m_is_mk4mmu3)
|
||||
writer.disable_linear_advance();
|
||||
|
||||
writer.suppress_preview()
|
||||
.travel(writer.x(), writer.y() + y_step);
|
||||
old_x = writer.x();
|
||||
turning_point = xr-old_x > old_x-xl ? xr : xl;
|
||||
for (int i=0; i<number_of_moves; ++i) {
|
||||
float stamping_dist_e = m_filpar[m_current_tool].filament_stamping_distance + m_cooling_tube_length / 2.f;
|
||||
|
||||
for (int i=0; i<number_of_cooling_moves; ++i) {
|
||||
|
||||
// Stamping - happens after every cooling move except for the last one.
|
||||
if (i>0 && m_filpar[m_current_tool].filament_stamping_distance != 0) {
|
||||
|
||||
// Stamping turning point shall be no farther than 20mm from the current nozzle position:
|
||||
float stamping_turning_point = std::clamp(old_x + 20.f * (turning_point - old_x > 0.f ? 1.f : -1.f), xl, xr);
|
||||
|
||||
// Only last 5mm will be done with the fast x travel. The point is to spread possible blobs
|
||||
// along the whole wipe tower.
|
||||
if (stamping_dist_e > 5) {
|
||||
float cent = writer.x();
|
||||
writer.load_move_x_advanced(stamping_turning_point, (stamping_dist_e - 5), m_filpar[m_current_tool].filament_stamping_loading_speed, 200);
|
||||
writer.load_move_x_advanced(cent, 5, m_filpar[m_current_tool].filament_stamping_loading_speed, m_travel_speed);
|
||||
writer.travel(cent, writer.y());
|
||||
} else
|
||||
writer.load_move_x_advanced_there_and_back(stamping_turning_point, stamping_dist_e, m_filpar[m_current_tool].filament_stamping_loading_speed, m_travel_speed);
|
||||
|
||||
// Retract while the print head is stationary, so if there is a blob, it is not dragged along.
|
||||
writer.retract(stamping_dist_e, m_filpar[m_current_tool].unloading_speed * 60.f);
|
||||
}
|
||||
|
||||
if (i == number_of_cooling_moves - 1 && change_temp_later) {
|
||||
// If cold_ramming, the temperature change should be done before the last cooling move.
|
||||
writer.set_extruder_temp(new_temperature, false);
|
||||
}
|
||||
float speed = initial_speed + speed_inc * 2*i;
|
||||
writer.load_move_x_advanced(turning_point, m_cooling_tube_length, speed);
|
||||
speed += speed_inc;
|
||||
@@ -979,7 +1063,7 @@ void WipeTower::toolchange_Unload(
|
||||
|
||||
// this is to align ramming and future wiping extrusions, so the future y-steps can be uniform from the start:
|
||||
// the perimeter_width will later be subtracted, it is there to not load while moving over just extruded material
|
||||
Vec2f pos = Vec2f(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing-m_perimeter_width) / 2.f + m_perimeter_width);
|
||||
Vec2f pos = Vec2f(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing_ramming-m_perimeter_width) / 2.f + m_perimeter_width);
|
||||
if (do_ramming)
|
||||
writer.travel(pos, 2400.f);
|
||||
else
|
||||
@@ -1004,6 +1088,8 @@ void WipeTower::toolchange_Change(
|
||||
//writer.append("[end_filament_gcode]\n");
|
||||
writer.append("[toolchange_gcode_from_wipe_tower_generator]\n");
|
||||
|
||||
if (m_is_mk4mmu3)
|
||||
writer.switch_filament_monitoring(true);
|
||||
// Travel to where we assume we are. Custom toolchange or some special T code handling (parking extruder etc)
|
||||
// gcode could have left the extruder somewhere, we cannot just start extruding. We should also inform the
|
||||
// postprocessor that we absolutely want to have this in the gcode, even if it thought it is the same as before.
|
||||
@@ -1064,20 +1150,23 @@ void WipeTower::toolchange_Wipe(
|
||||
const float& xl = cleaning_box.ld.x();
|
||||
const float& xr = cleaning_box.rd.x();
|
||||
|
||||
writer.set_extrusion_flow(m_extrusion_flow * m_extra_flow);
|
||||
const float line_width = m_perimeter_width * m_extra_flow;
|
||||
writer.change_analyzer_line_width(line_width);
|
||||
// Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least
|
||||
// the ordered volume, even if it means violating the box. This can later be removed and simply
|
||||
// wipe until the end of the assigned area.
|
||||
|
||||
float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height) * (is_first_layer() ? m_extra_spacing : 1.f);
|
||||
float dy = (is_first_layer() ? 1.f : m_extra_spacing) * m_perimeter_width; // Don't use the extra spacing for the first layer.
|
||||
float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height) / m_extra_flow;
|
||||
float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width; // Don't use the extra spacing for the first layer, but do use the spacing resulting from increased flow.
|
||||
// All the calculations in all other places take the spacing into account for all the layers.
|
||||
|
||||
const float target_speed = is_first_layer() ? m_first_layer_speed * 60.f : m_infill_speed * 60.f;
|
||||
float wipe_speed = 0.33f * target_speed;
|
||||
|
||||
// if there is less than 2.5*m_perimeter_width to the edge, advance straightaway (there is likely a blob anyway)
|
||||
if ((m_left_to_right ? xr-writer.x() : writer.x()-xl) < 2.5f*m_perimeter_width) {
|
||||
writer.travel((m_left_to_right ? xr-m_perimeter_width : xl+m_perimeter_width),writer.y()+dy);
|
||||
// if there is less than 2.5*line_width to the edge, advance straightaway (there is likely a blob anyway)
|
||||
if ((m_left_to_right ? xr-writer.x() : writer.x()-xl) < 2.5f*line_width) {
|
||||
writer.travel((m_left_to_right ? xr-line_width : xl+line_width),writer.y()+dy);
|
||||
m_left_to_right = !m_left_to_right;
|
||||
}
|
||||
|
||||
@@ -1092,21 +1181,21 @@ void WipeTower::toolchange_Wipe(
|
||||
|
||||
float traversed_x = writer.x();
|
||||
if (m_left_to_right)
|
||||
writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5f*m_perimeter_width), writer.y(), wipe_speed);
|
||||
writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5f*line_width), writer.y(), wipe_speed);
|
||||
else
|
||||
writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5f*m_perimeter_width), writer.y(), wipe_speed);
|
||||
writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5f*line_width), writer.y(), wipe_speed);
|
||||
|
||||
if (writer.y()+float(EPSILON) > cleaning_box.lu.y()-0.5f*m_perimeter_width)
|
||||
if (writer.y()+float(EPSILON) > cleaning_box.lu.y()-0.5f*line_width)
|
||||
break; // in case next line would not fit
|
||||
|
||||
traversed_x -= writer.x();
|
||||
x_to_wipe -= std::abs(traversed_x);
|
||||
if (x_to_wipe < WT_EPSILON) {
|
||||
writer.travel(m_left_to_right ? xl + 1.5f*m_perimeter_width : xr - 1.5f*m_perimeter_width, writer.y(), 7200);
|
||||
writer.travel(m_left_to_right ? xl + 1.5f*line_width : xr - 1.5f*line_width, writer.y(), 7200);
|
||||
break;
|
||||
}
|
||||
// stepping to the next line:
|
||||
writer.extrude(writer.x() + (i % 4 == 0 ? -1.f : (i % 4 == 1 ? 1.f : 0.f)) * 1.5f*m_perimeter_width, writer.y() + dy);
|
||||
writer.extrude(writer.x() + (i % 4 == 0 ? -1.f : (i % 4 == 1 ? 1.f : 0.f)) * 1.5f*line_width, writer.y() + dy);
|
||||
m_left_to_right = !m_left_to_right;
|
||||
}
|
||||
|
||||
@@ -1120,6 +1209,7 @@ void WipeTower::toolchange_Wipe(
|
||||
m_left_to_right = !m_left_to_right;
|
||||
|
||||
writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow.
|
||||
writer.change_analyzer_line_width(m_perimeter_width);
|
||||
}
|
||||
|
||||
|
||||
@@ -1399,9 +1489,19 @@ std::vector<std::vector<float>> WipeTower::extract_wipe_volumes(const PrintConfi
|
||||
// Extract purging volumes for each extruder pair:
|
||||
std::vector<std::vector<float>> wipe_volumes;
|
||||
const unsigned int number_of_extruders = (unsigned int)(sqrt(wiping_matrix.size())+EPSILON);
|
||||
for (unsigned int i = 0; i<number_of_extruders; ++i)
|
||||
for (size_t i = 0; i<number_of_extruders; ++i)
|
||||
wipe_volumes.push_back(std::vector<float>(wiping_matrix.begin()+i*number_of_extruders, wiping_matrix.begin()+(i+1)*number_of_extruders));
|
||||
|
||||
// For SEMM printers, the project can be configured to use defaults from configuration,
|
||||
// in which case the custom matrix shall be ignored. We will overwrite the values.
|
||||
if (config.single_extruder_multi_material && ! config.wiping_volumes_use_custom_matrix) {
|
||||
for (size_t i = 0; i < number_of_extruders; ++i) {
|
||||
for (size_t j = 0; j < number_of_extruders; ++j) {
|
||||
if (i != j)
|
||||
wipe_volumes[i][j] = (i == j ? 0.f : config.multimaterial_purging.value * config.filament_purge_multiplier.get_at(j) / 100.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Also include filament_minimal_purge_on_wipe_tower. This is needed for the preview.
|
||||
for (unsigned int i = 0; i<number_of_extruders; ++i)
|
||||
for (unsigned int j = 0; j<number_of_extruders; ++j)
|
||||
@@ -1410,6 +1510,13 @@ std::vector<std::vector<float>> WipeTower::extract_wipe_volumes(const PrintConfi
|
||||
return wipe_volumes;
|
||||
}
|
||||
|
||||
static float get_wipe_depth(float volume, float layer_height, float perimeter_width, float extra_flow, float extra_spacing, float width)
|
||||
{
|
||||
float length_to_extrude = (volume_to_length(volume, perimeter_width, layer_height)) / extra_flow;
|
||||
length_to_extrude = std::max(length_to_extrude,0.f);
|
||||
|
||||
return (int(length_to_extrude / width) + 1) * perimeter_width * extra_spacing;
|
||||
}
|
||||
// Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box
|
||||
void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool,
|
||||
unsigned int new_tool, float wipe_volume)
|
||||
@@ -1426,22 +1533,17 @@ void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned in
|
||||
return;
|
||||
|
||||
// this is an actual toolchange - let's calculate depth to reserve on the wipe tower
|
||||
float depth = 0.f;
|
||||
float width = m_wipe_tower_width - 3*m_perimeter_width;
|
||||
float length_to_extrude = volume_to_length(0.25f * std::accumulate(m_filpar[old_tool].ramming_speed.begin(), m_filpar[old_tool].ramming_speed.end(), 0.f),
|
||||
m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator,
|
||||
layer_height_par);
|
||||
depth = (int(length_to_extrude / width) + 1) * (m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator * m_filpar[old_tool].ramming_step_multiplicator);
|
||||
float ramming_depth = depth;
|
||||
length_to_extrude = width*((length_to_extrude / width)-int(length_to_extrude / width)) - width;
|
||||
float first_wipe_line = -length_to_extrude;
|
||||
length_to_extrude += volume_to_length(wipe_volume, m_perimeter_width, layer_height_par);
|
||||
length_to_extrude = std::max(length_to_extrude,0.f);
|
||||
float ramming_depth = (int(length_to_extrude / width) + 1) * (m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator * m_filpar[old_tool].ramming_step_multiplicator) * m_extra_spacing_ramming;
|
||||
float first_wipe_line = - (width*((length_to_extrude / width)-int(length_to_extrude / width)) - width);
|
||||
|
||||
depth += (int(length_to_extrude / width) + 1) * m_perimeter_width;
|
||||
depth *= m_extra_spacing;
|
||||
float first_wipe_volume = length_to_volume(first_wipe_line, m_perimeter_width * m_extra_flow, layer_height_par);
|
||||
float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height_par, m_perimeter_width, m_extra_flow, m_extra_spacing_wipe, width);
|
||||
|
||||
m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool, depth, ramming_depth, first_wipe_line, wipe_volume));
|
||||
m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume));
|
||||
}
|
||||
|
||||
|
||||
@@ -1491,14 +1593,14 @@ void WipeTower::save_on_last_wipe()
|
||||
|
||||
if (i == idx) {
|
||||
float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into
|
||||
float length_to_save = finish_layer().total_extrusion_length_in_plane();
|
||||
float length_to_wipe = volume_to_length(toolchange.wipe_volume,
|
||||
m_perimeter_width, m_layer_info->height) - toolchange.first_wipe_line - length_to_save;
|
||||
|
||||
length_to_wipe = std::max(length_to_wipe,0.f);
|
||||
float depth_to_wipe = m_perimeter_width * (std::floor(length_to_wipe/width) + ( length_to_wipe > 0.f ? 1.f : 0.f ) );
|
||||
float volume_to_save = length_to_volume(finish_layer().total_extrusion_length_in_plane(), m_perimeter_width, m_layer_info->height);
|
||||
float volume_left_to_wipe = std::max(m_filpar[toolchange.new_tool].filament_minimal_purge_on_wipe_tower, toolchange.wipe_volume_total - volume_to_save);
|
||||
float volume_we_need_depth_for = std::max(0.f, volume_left_to_wipe - length_to_volume(toolchange.first_wipe_line, m_perimeter_width*m_extra_flow, m_layer_info->height));
|
||||
float depth_to_wipe = get_wipe_depth(volume_we_need_depth_for, m_layer_info->height, m_perimeter_width, m_extra_flow, m_extra_spacing_wipe, width);
|
||||
|
||||
toolchange.required_depth = (toolchange.ramming_depth + depth_to_wipe) * m_extra_spacing;
|
||||
toolchange.required_depth = toolchange.ramming_depth + depth_to_wipe;
|
||||
toolchange.wipe_volume = volume_left_to_wipe;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#ifndef WipeTower_
|
||||
#define WipeTower_
|
||||
|
||||
#ifndef slic3r_GCode_WipeTower_hpp_
|
||||
#define slic3r_GCode_WipeTower_hpp_
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
@@ -232,6 +231,8 @@ public:
|
||||
float unloading_speed = 0.f;
|
||||
float unloading_speed_start = 0.f;
|
||||
float delay = 0.f ;
|
||||
float filament_stamping_loading_speed = 0.f;
|
||||
float filament_stamping_distance = 0.f;
|
||||
int cooling_moves = 0;
|
||||
float cooling_initial_speed = 0.f;
|
||||
float cooling_final_speed = 0.f;
|
||||
@@ -243,6 +244,7 @@ public:
|
||||
float filament_area;
|
||||
bool multitool_ramming;
|
||||
float multitool_ramming_time = 0.f;
|
||||
float filament_minimal_purge_on_wipe_tower = 0.f;
|
||||
};
|
||||
|
||||
private:
|
||||
@@ -260,6 +262,7 @@ private:
|
||||
|
||||
|
||||
bool m_semm = true; // Are we using a single extruder multimaterial printer?
|
||||
bool m_is_mk4mmu3 = false;
|
||||
Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm.
|
||||
float m_wipe_tower_width; // Width of the wipe tower.
|
||||
float m_wipe_tower_depth = 0.f; // Depth of the wipe tower
|
||||
@@ -309,8 +312,6 @@ private:
|
||||
// State of the wipe tower generator.
|
||||
unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics.
|
||||
unsigned int m_num_tool_changes = 0; // Tool change change counter for the output statistics.
|
||||
///unsigned int m_idx_tool_change_in_layer = 0; // Layer change counter in this layer. Counting up to m_max_color_changes.
|
||||
bool m_print_brim = true;
|
||||
// A fill-in direction (positive Y, negative Y) alternates with each layer.
|
||||
wipe_shape m_current_shape = SHAPE_NORMAL;
|
||||
size_t m_current_tool = 0;
|
||||
@@ -319,7 +320,9 @@ private:
|
||||
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_extra_flow = 1.f;
|
||||
float m_extra_spacing_wipe = 1.f;
|
||||
float m_extra_spacing_ramming = 1.f;
|
||||
|
||||
bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; }
|
||||
|
||||
@@ -331,16 +334,10 @@ private:
|
||||
return layer_height * ( m_perimeter_width - layer_height * (1.f-float(M_PI)/4.f)) / filament_area();
|
||||
}
|
||||
|
||||
// Calculates length of extrusion line to extrude given volume
|
||||
float volume_to_length(float volume, float line_width, float layer_height) const {
|
||||
return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))));
|
||||
}
|
||||
|
||||
// Calculates depth for all layers and propagates them downwards
|
||||
void plan_tower();
|
||||
|
||||
// Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental
|
||||
void make_wipe_tower_square();
|
||||
|
||||
// Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe
|
||||
void save_on_last_wipe();
|
||||
@@ -355,19 +352,19 @@ private:
|
||||
float ramming_depth;
|
||||
float first_wipe_line;
|
||||
float wipe_volume;
|
||||
float wipe_volume_total;
|
||||
ToolChange(size_t old, size_t newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f)
|
||||
: old_tool{old}, new_tool{newtool}, required_depth{depth}, ramming_depth{ramming_depth}, first_wipe_line{fwl}, wipe_volume{wv} {}
|
||||
: old_tool{old}, new_tool{newtool}, required_depth{depth}, ramming_depth{ramming_depth}, first_wipe_line{fwl}, wipe_volume{wv}, wipe_volume_total{wv} {}
|
||||
};
|
||||
float z; // z position of the layer
|
||||
float height; // layer height
|
||||
float depth; // depth of the layer based on all layers above
|
||||
float extra_spacing;
|
||||
float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; }
|
||||
|
||||
std::vector<ToolChange> tool_changes;
|
||||
|
||||
WipeTowerInfo(float z_par, float layer_height_par)
|
||||
: z{z_par}, height{layer_height_par}, depth{0}, extra_spacing{1.f} {}
|
||||
: z{z_par}, height{layer_height_par}, depth{0} {}
|
||||
};
|
||||
|
||||
std::vector<WipeTowerInfo> m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...))
|
||||
@@ -390,6 +387,7 @@ private:
|
||||
WipeTowerWriter &writer,
|
||||
const box_coordinates &cleaning_box,
|
||||
const std::string& current_material,
|
||||
const int old_temperature,
|
||||
const int new_temperature);
|
||||
|
||||
void toolchange_Change(
|
||||
@@ -412,4 +410,4 @@ private:
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // WipeTowerQIDIMM_hpp_
|
||||
#endif // slic3r_GCode_WipeTower_hpp_
|
||||
|
||||
@@ -58,13 +58,14 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
|
||||
|| will_go_down); // don't dig into the print
|
||||
if (should_travel_to_tower) {
|
||||
const Point xy_point = wipe_tower_point_to_object_point(gcodegen, start_pos);
|
||||
gcode += gcodegen.m_label_objects.maybe_stop_instance();
|
||||
gcode += gcodegen.retract_and_wipe();
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
|
||||
const std::string comment{"Travel to a Wipe Tower"};
|
||||
if (gcodegen.m_current_layer_first_position) {
|
||||
if (gcodegen.last_position) {
|
||||
gcode += gcodegen.travel_to(
|
||||
*gcodegen.last_position, xy_point, ExtrusionRole::Mixed, comment
|
||||
*gcodegen.last_position, xy_point, ExtrusionRole::Mixed, comment, [](){return "";}
|
||||
);
|
||||
} else {
|
||||
gcode += gcodegen.writer().travel_to_xy(gcodegen.point_to_gcode(xy_point), comment);
|
||||
@@ -72,7 +73,7 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
|
||||
}
|
||||
} else {
|
||||
const Vec3crd point = to_3d(xy_point, scaled(z));
|
||||
gcode += gcodegen.travel_to_first_position(point, current_z);
|
||||
gcode += gcodegen.travel_to_first_position(point, current_z, ExtrusionRole::Mixed, [](){return "";});
|
||||
}
|
||||
gcode += gcodegen.unretract();
|
||||
} else {
|
||||
@@ -93,12 +94,10 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
|
||||
gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines.
|
||||
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z
|
||||
if (gcodegen.config().wipe_tower) {
|
||||
if (tcr.priming) {
|
||||
const double return_to_z{tcr.print_z + gcodegen.config().z_offset.value};
|
||||
deretraction_str += gcodegen.writer().get_travel_to_z_gcode(return_to_z, "set priming layer Z");
|
||||
} else {
|
||||
deretraction_str += gcodegen.writer().travel_to_z(z, "restore layer Z");
|
||||
}
|
||||
deretraction_str += gcodegen.writer().get_travel_to_z_gcode(z, "restore layer Z");
|
||||
Vec3d position{gcodegen.writer().get_position()};
|
||||
position.z() = z;
|
||||
gcodegen.writer().update_position(position);
|
||||
deretraction_str += gcodegen.unretract();
|
||||
|
||||
}
|
||||
@@ -111,7 +110,10 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
|
||||
boost::replace_first(tcr_rotated_gcode, "[deretraction_from_wipe_tower_generator]", deretraction_str);
|
||||
std::string tcr_gcode;
|
||||
unescape_string_cstyle(tcr_rotated_gcode, tcr_gcode);
|
||||
if (gcodegen.config().default_acceleration > 0)
|
||||
gcode += gcodegen.writer().set_print_acceleration(fast_round_up<unsigned int>(gcodegen.config().wipe_tower_acceleration.value));
|
||||
gcode += tcr_gcode;
|
||||
gcode += gcodegen.writer().set_print_acceleration(fast_round_up<unsigned int>(gcodegen.config().default_acceleration.value));
|
||||
|
||||
// A phony move to the end position at the wipe tower.
|
||||
gcodegen.writer().travel_to_xy(end_pos.cast<double>());
|
||||
@@ -136,7 +138,7 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
|
||||
}
|
||||
|
||||
// Let the planner know we are traveling between objects.
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
|
||||
return gcode;
|
||||
}
|
||||
|
||||
@@ -262,10 +264,11 @@ std::string WipeTowerIntegration::tool_change(GCodeGenerator &gcodegen, int extr
|
||||
std::string WipeTowerIntegration::finalize(GCodeGenerator &gcodegen)
|
||||
{
|
||||
std::string gcode;
|
||||
if (std::abs(gcodegen.writer().get_position().z() - m_final_purge.print_z) > EPSILON)
|
||||
const double purge_z{m_final_purge.print_z + gcodegen.config().z_offset.value};
|
||||
if (std::abs(gcodegen.writer().get_position().z() - purge_z) > EPSILON)
|
||||
gcode += gcodegen.generate_travel_gcode(
|
||||
{{gcodegen.last_position->x(), gcodegen.last_position->y(), scaled(m_final_purge.print_z)}},
|
||||
"move to safe place for purging"
|
||||
{{gcodegen.last_position->x(), gcodegen.last_position->y(), scaled(purge_z)}},
|
||||
"move to safe place for purging", [](){return "";}
|
||||
);
|
||||
gcode += append_tcr(gcodegen, m_final_purge, -1);
|
||||
return gcode;
|
||||
|
||||
Reference in New Issue
Block a user