Merge branch 'lm_wipe_tower_xl'

This commit is contained in:
Lukas Matena 2023-01-26 07:28:45 +01:00
commit 3558006a0b
14 changed files with 250 additions and 222 deletions

View File

@ -776,7 +776,7 @@ public:
static int nil_value() { return std::numeric_limits<int>::max(); } static int nil_value() { return std::numeric_limits<int>::max(); }
// A scalar is nil, or all values of a vector are nil. // A scalar is nil, or all values of a vector are nil.
bool is_nil() const override { for (auto v : this->values) if (v != nil_value()) return false; return true; } bool is_nil() const override { for (auto v : this->values) if (v != nil_value()) return false; return true; }
bool is_nil(size_t idx) const override { return this->values[idx] == nil_value(); } bool is_nil(size_t idx) const override { return values[idx < this->values.size() ? idx : 0] == nil_value(); }
std::string serialize() const override std::string serialize() const override
{ {

View File

@ -113,26 +113,19 @@ namespace Slic3r {
{ {
std::string gcode; std::string gcode;
// move to the nearest standby point unsigned int extruder_id = gcodegen.writer().extruder()->id();
if (!this->standby_points.empty()) { const ConfigOptionIntsNullable& filament_idle_temp = gcodegen.config().idle_temperature;
// get current position in print coordinates if (filament_idle_temp.is_nil(extruder_id)) {
Vec3d writer_pos = gcodegen.writer().get_position(); // There is no idle temperature defined in filament settings.
Point pos = Point::new_scale(writer_pos(0), writer_pos(1)); // Use the delta value from print config.
if (gcodegen.config().standby_temperature_delta.value != 0) {
// find standby point // we assume that heating is always slower than cooling, so no need to block
Point standby_point = nearest_point(this->standby_points, pos).first; gcode += gcodegen.writer().set_temperature
(this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, extruder_id);
/* We don't call gcodegen.travel_to() because we don't need retraction (it was already }
triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates } else {
of the destination point must not be transformed by origin nor current extruder offset. */ // Use the value from filament settings. That one is absolute, not delta.
gcode += gcodegen.writer().travel_to_xy(unscale(standby_point), gcode += gcodegen.writer().set_temperature(filament_idle_temp.get_at(extruder_id), false, extruder_id);
"move to standby position");
}
if (gcodegen.config().standby_temperature_delta.value != 0) {
// we assume that heating is always slower than cooling, so no need to block
gcode += gcodegen.writer().set_temperature
(this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, gcodegen.writer().extruder()->id());
} }
return gcode; return gcode;
@ -145,8 +138,7 @@ namespace Slic3r {
std::string(); std::string();
} }
int int OozePrevention::_get_temp(const GCode& gcodegen) const
OozePrevention::_get_temp(GCode& gcodegen)
{ {
return (gcodegen.layer() != nullptr && gcodegen.layer()->id() == 0) return (gcodegen.layer() != nullptr && gcodegen.layer()->id() == 0)
? gcodegen.config().first_layer_temperature.get_at(gcodegen.writer().extruder()->id()) ? gcodegen.config().first_layer_temperature.get_at(gcodegen.writer().extruder()->id())
@ -238,8 +230,16 @@ namespace Slic3r {
std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation); std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation);
if (! tcr.priming) { double current_z = gcodegen.writer().get_position().z();
// Move over the wipe tower. if (z == -1.) // in case no specific z was provided, print at current_z pos
z = current_z;
const bool needs_toolchange = gcodegen.writer().need_toolchange(new_extruder_id);
const bool will_go_down = ! is_approx(z, current_z);
if (! needs_toolchange || (gcodegen.config().single_extruder_multi_material && ! tcr.priming)) {
// Move over the wipe tower. If this is not single-extruder MM, the first wipe tower move following the
// toolchange will travel there anyway (if there is a toolchange).
gcode += gcodegen.retract(); gcode += gcodegen.retract();
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
gcode += gcodegen.travel_to( gcode += gcodegen.travel_to(
@ -248,82 +248,36 @@ namespace Slic3r {
"Travel to a Wipe Tower"); "Travel to a Wipe Tower");
gcode += gcodegen.unretract(); gcode += gcodegen.unretract();
} }
double current_z = gcodegen.writer().get_position().z(); if (will_go_down) {
if (z == -1.) // in case no specific z was provided, print at current_z pos
z = current_z;
if (! is_approx(z, current_z)) {
gcode += gcodegen.writer().retract(); gcode += gcodegen.writer().retract();
gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer."); gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer.");
gcode += gcodegen.writer().unretract(); gcode += gcodegen.writer().unretract();
} }
// Process the end filament gcode.
std::string end_filament_gcode_str;
if (gcodegen.writer().extruder() != nullptr) {
// Process the custom end_filament_gcode in case of single_extruder_multi_material.
unsigned int old_extruder_id = gcodegen.writer().extruder()->id();
const std::string& end_filament_gcode = gcodegen.config().end_filament_gcode.get_at(old_extruder_id);
if (gcodegen.writer().extruder() != nullptr && !end_filament_gcode.empty()) {
end_filament_gcode_str = gcodegen.placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id);
check_add_eol(end_filament_gcode_str);
}
}
// Process the custom toolchange_gcode. If it is empty, provide a simple Tn command to change the filament.
// Otherwise, leave control to the user completely.
std::string toolchange_gcode_str; std::string toolchange_gcode_str;
const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value; std::string deretraction_str;
if (! toolchange_gcode.empty()) { if (tcr.priming || (new_extruder_id >= 0 && needs_toolchange)) {
DynamicConfig config; if (gcodegen.config().single_extruder_multi_material)
int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1; gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines.
config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id)); toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z
config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id)); if (gcodegen.config().wipe_tower)
config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); deretraction_str = gcodegen.unretract();
config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z));
config.set_key_value("toolchange_z", new ConfigOptionFloat(z));
config.set_key_value("max_layer_z", new ConfigOptionFloat(gcodegen.m_max_layer_z));
toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config);
check_add_eol(toolchange_gcode_str);
} }
std::string toolchange_command;
if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)))
toolchange_command = gcodegen.writer().toolchange(new_extruder_id);
if (!custom_gcode_changes_tool(toolchange_gcode_str, gcodegen.writer().toolchange_prefix(), new_extruder_id))
toolchange_gcode_str += toolchange_command;
else {
// We have informed the m_writer about the current extruder_id, we can ignore the generated G-code.
}
gcodegen.placeholder_parser().set("current_extruder", new_extruder_id);
// Process the start filament gcode.
std::string start_filament_gcode_str;
const std::string& start_filament_gcode = gcodegen.config().start_filament_gcode.get_at(new_extruder_id);
if (!start_filament_gcode.empty()) {
// Process the start_filament_gcode for the active filament only.
DynamicConfig config;
config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index));
config.set_key_value("layer_z", new ConfigOptionFloat(gcodegen.writer().get_position()(2) - gcodegen.m_config.z_offset.value));
config.set_key_value("max_layer_z", new ConfigOptionFloat(gcodegen.m_max_layer_z));
config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_extruder_id));
start_filament_gcode_str = gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id, &config);
check_add_eol(start_filament_gcode_str);
}
// Insert the end filament, toolchange, and start filament gcode into the generated gcode. // Insert the toolchange and deretraction gcode into the generated gcode.
DynamicConfig config; DynamicConfig config;
config.set_key_value("end_filament_gcode", new ConfigOptionString(end_filament_gcode_str));
config.set_key_value("toolchange_gcode", new ConfigOptionString(toolchange_gcode_str)); config.set_key_value("toolchange_gcode", new ConfigOptionString(toolchange_gcode_str));
config.set_key_value("start_filament_gcode", new ConfigOptionString(start_filament_gcode_str)); config.set_key_value("deretraction_from_wipe_tower_generator", new ConfigOptionString(deretraction_str));
std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config); std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config);
unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode); unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode);
gcode += tcr_gcode; gcode += tcr_gcode;
check_add_eol(toolchange_gcode_str); check_add_eol(toolchange_gcode_str);
// A phony move to the end position at the wipe tower. // A phony move to the end position at the wipe tower.
gcodegen.writer().travel_to_xy(end_pos.cast<double>()); gcodegen.writer().travel_to_xy(end_pos.cast<double>());
gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos)); gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos));
@ -898,34 +852,7 @@ namespace DoExport {
static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention) static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention)
{ {
// Calculate wiping points if needed ooze_prevention.enable = print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material;
if (print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material) {
Points skirt_points;
for (const ExtrusionEntity *ee : print.skirt().entities)
for (const ExtrusionPath &path : dynamic_cast<const ExtrusionLoop*>(ee)->paths)
append(skirt_points, path.polyline.points);
if (! skirt_points.empty()) {
Polygon outer_skirt = Slic3r::Geometry::convex_hull(skirt_points);
Polygons skirts;
for (unsigned int extruder_id : print.extruders()) {
const Vec2d &extruder_offset = print.config().extruder_offset.get_at(extruder_id);
Polygon s(outer_skirt);
s.translate(Point::new_scale(-extruder_offset(0), -extruder_offset(1)));
skirts.emplace_back(std::move(s));
}
ooze_prevention.enable = true;
ooze_prevention.standby_points = offset(Slic3r::Geometry::convex_hull(skirts), float(scale_(3.))).front().equally_spaced_points(float(scale_(10.)));
#if 0
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"ooze_prevention.svg",
red_polygons => \@skirts,
polygons => [$outer_skirt],
points => $gcodegen->ooze_prevention->standby_points,
);
#endif
}
}
} }
// Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section. // Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section.
@ -1263,6 +1190,11 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
m_placeholder_parser.set("first_layer_print_min", new ConfigOptionFloats({ bbox.min.x(), bbox.min.y() })); m_placeholder_parser.set("first_layer_print_min", new ConfigOptionFloats({ bbox.min.x(), bbox.min.y() }));
m_placeholder_parser.set("first_layer_print_max", new ConfigOptionFloats({ bbox.max.x(), bbox.max.y() })); m_placeholder_parser.set("first_layer_print_max", new ConfigOptionFloats({ bbox.max.x(), bbox.max.y() }));
m_placeholder_parser.set("first_layer_print_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() })); m_placeholder_parser.set("first_layer_print_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() }));
std::vector<unsigned char> is_extruder_used(print.config().nozzle_diameter.size(), 0);
for (unsigned int extruder_id : print.extruders())
is_extruder_used[extruder_id] = true;
m_placeholder_parser.set("is_extruder_used", new ConfigOptionBools(is_extruder_used));
} }
std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config().start_gcode.value, initial_extruder_id); std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config().start_gcode.value, initial_extruder_id);
// Set bed temperature if the start G-code does not contain any bed temp control G-codes. // Set bed temperature if the start G-code does not contain any bed temp control G-codes.
@ -1282,8 +1214,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
// Set other general things. // Set other general things.
file.write(this->preamble()); file.write(this->preamble());
// Calculate wiping points if needed // Enable ooze prevention if configured so.
DoExport::init_ooze_prevention(print, m_ooze_prevention); DoExport::init_ooze_prevention(print, m_ooze_prevention);
print.throw_if_canceled(); print.throw_if_canceled();
// Collect custom seam data from all objects. // Collect custom seam data from all objects.
@ -1814,7 +1747,7 @@ void GCode::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Pr
m_writer.set_temperature(temp, wait, first_printing_extruder_id); m_writer.set_temperature(temp, wait, first_printing_extruder_id);
} else { } else {
// Custom G-code does not set the extruder temperature. Do it now. // Custom G-code does not set the extruder temperature. Do it now.
if (print.config().single_extruder_multi_material.value) { if (print.config().single_extruder_multi_material.value || m_ooze_prevention.enable) {
// Set temperature of the first printing extruder only. // Set temperature of the first printing extruder only.
int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id); int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id);
if (temp > 0) if (temp > 0)
@ -2136,11 +2069,14 @@ LayerResult GCode::process_layer(
// Transition from 1st to 2nd layer. Adjust nozzle temperatures as prescribed by the nozzle dependent // Transition from 1st to 2nd layer. Adjust nozzle temperatures as prescribed by the nozzle dependent
// first_layer_temperature vs. temperature settings. // first_layer_temperature vs. temperature settings.
for (const Extruder &extruder : m_writer.extruders()) { for (const Extruder &extruder : m_writer.extruders()) {
if (print.config().single_extruder_multi_material.value && extruder.id() != m_writer.extruder()->id()) if (print.config().single_extruder_multi_material.value || m_ooze_prevention.enable) {
// In single extruder multi material mode, set the temperature for the current extruder only. // In single extruder multi material mode, set the temperature for the current extruder only.
continue; // The same applies when ooze prevention is enabled.
if (extruder.id() != m_writer.extruder()->id())
continue;
}
int temperature = print.config().temperature.get_at(extruder.id()); int temperature = print.config().temperature.get_at(extruder.id());
if (temperature > 0 && temperature != print.config().first_layer_temperature.get_at(extruder.id())) if (temperature > 0 && (temperature != print.config().first_layer_temperature.get_at(extruder.id())))
gcode += m_writer.set_temperature(temperature, false, extruder.id()); gcode += m_writer.set_temperature(temperature, false, extruder.id());
} }
gcode += m_writer.set_bed_temperature(print.config().bed_temperature.get_at(first_extruder_id)); gcode += m_writer.set_bed_temperature(print.config().bed_temperature.get_at(first_extruder_id));
@ -3186,6 +3122,9 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
if (! start_filament_gcode.empty()) { if (! start_filament_gcode.empty()) {
// Process the start_filament_gcode for the filament. // Process the start_filament_gcode for the filament.
DynamicConfig config; DynamicConfig config;
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
config.set_key_value("layer_z", new ConfigOptionFloat(this->writer().get_position()(2) - m_config.z_offset.value));
config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(extruder_id))); config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(extruder_id)));
gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id, &config); gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id, &config);
check_add_eol(gcode); check_add_eol(gcode);
@ -3201,8 +3140,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
m_wipe.reset_path(); m_wipe.reset_path();
if (m_writer.extruder() != nullptr) { if (m_writer.extruder() != nullptr) {
// Process the custom end_filament_gcode. set_extruder() is only called if there is no wipe tower // Process the custom end_filament_gcode.
// so it should not be injected twice.
unsigned int old_extruder_id = m_writer.extruder()->id(); unsigned int old_extruder_id = m_writer.extruder()->id();
const std::string &end_filament_gcode = m_config.end_filament_gcode.get_at(old_extruder_id); const std::string &end_filament_gcode = m_config.end_filament_gcode.get_at(old_extruder_id);
if (! end_filament_gcode.empty()) { if (! end_filament_gcode.empty()) {
@ -3212,8 +3150,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
} }
// If ooze prevention is enabled, park current extruder in the nearest // If ooze prevention is enabled, set current extruder to the standby temperature.
// standby point and set it to the standby temperature.
if (m_ooze_prevention.enable && m_writer.extruder() != nullptr) if (m_ooze_prevention.enable && m_writer.extruder() != nullptr)
gcode += m_ooze_prevention.pre_toolchange(*this); gcode += m_ooze_prevention.pre_toolchange(*this);
@ -3257,6 +3194,9 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
if (! start_filament_gcode.empty()) { if (! start_filament_gcode.empty()) {
// Process the start_filament_gcode for the new filament. // Process the start_filament_gcode for the new filament.
DynamicConfig config; DynamicConfig config;
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
config.set_key_value("layer_z", new ConfigOptionFloat(this->writer().get_position()(2) - m_config.z_offset.value));
config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(extruder_id))); config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(extruder_id)));
gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id, &config); gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id, &config);
check_add_eol(gcode); check_add_eol(gcode);

View File

@ -39,14 +39,13 @@ struct PrintInstance;
class OozePrevention { class OozePrevention {
public: public:
bool enable; bool enable;
Points standby_points;
OozePrevention() : enable(false) {} OozePrevention() : enable(false) {}
std::string pre_toolchange(GCode &gcodegen); std::string pre_toolchange(GCode &gcodegen);
std::string post_toolchange(GCode &gcodegen); std::string post_toolchange(GCode &gcodegen);
private: private:
int _get_temp(GCode &gcodegen); int _get_temp(const GCode &gcodegen) const;
}; };
class Wipe { class Wipe {

View File

@ -71,6 +71,8 @@ public:
return *this; return *this;
} }
WipeTowerWriter& set_position(const Vec2f &pos) { m_current_pos = pos; return *this; }
WipeTowerWriter& set_initial_tool(size_t tool) { m_current_tool = tool; return *this; } WipeTowerWriter& set_initial_tool(size_t tool) { m_current_tool = tool; return *this; }
WipeTowerWriter& set_z(float z) WipeTowerWriter& set_z(float z)
@ -802,22 +804,26 @@ void WipeTower::toolchange_Unload(
{ {
float xl = cleaning_box.ld.x() + 1.f * m_perimeter_width; float xl = cleaning_box.ld.x() + 1.f * m_perimeter_width;
float xr = cleaning_box.rd.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 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; // spacing between lines in mm
const Vec2f ramming_start_pos = Vec2f(xl, cleaning_box.ld.y() + m_depth_traversed + y_step/2.f);
writer.append("; CP TOOLCHANGE UNLOAD\n") writer.append("; CP TOOLCHANGE UNLOAD\n")
.change_analyzer_line_width(line_width); .change_analyzer_line_width(line_width);
unsigned i = 0; // iterates through ramming_speed unsigned i = 0; // iterates through ramming_speed
m_left_to_right = true; // current direction of ramming m_left_to_right = true; // current direction of ramming
float remaining = xr - xl ; // keeps track of distance to the next turnaround float remaining = xr - xl ; // keeps track of distance to the next turnaround
float e_done = 0; // measures E move done from each segment float e_done = 0; // measures E move done from each segment
writer.travel(xl, cleaning_box.ld.y() + m_depth_traversed + y_step/2.f ); // move to starting position
if (m_semm)
writer.travel(ramming_start_pos); // move to starting position
else
writer.set_position(ramming_start_pos);
// if the ending point of the ram would end up in mid air, align it with the end of the wipe tower: // if the ending point of the ram would end up in mid air, align it with the end of the wipe tower:
if (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion )) { if (m_semm && (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion ))) {
// this is y of the center of previous sparse infill border // this is y of the center of previous sparse infill border
float sparse_beginning_y = 0.f; float sparse_beginning_y = 0.f;
@ -849,7 +855,7 @@ void WipeTower::toolchange_Unload(
writer.disable_linear_advance(); writer.disable_linear_advance();
// now the ramming itself: // now the ramming itself:
while (i < m_filpar[m_current_tool].ramming_speed.size()) while (m_semm && i < m_filpar[m_current_tool].ramming_speed.size())
{ {
const float x = volume_to_length(m_filpar[m_current_tool].ramming_speed[i] * 0.25f, line_width, m_layer_height); const float x = volume_to_length(m_filpar[m_current_tool].ramming_speed[i] * 0.25f, line_width, m_layer_height);
const float e = m_filpar[m_current_tool].ramming_speed[i] * 0.25f / filament_area(); // transform volume per sec to E move; const float e = m_filpar[m_current_tool].ramming_speed[i] * 0.25f / filament_area(); // transform volume per sec to E move;
@ -898,7 +904,7 @@ void WipeTower::toolchange_Unload(
// Cooling: // Cooling:
const int& number_of_moves = m_filpar[m_current_tool].cooling_moves; const int& number_of_moves = m_filpar[m_current_tool].cooling_moves;
if (number_of_moves > 0) { if (m_semm && number_of_moves > 0) {
const float& initial_speed = m_filpar[m_current_tool].cooling_initial_speed; const float& initial_speed = m_filpar[m_current_tool].cooling_initial_speed;
const float& final_speed = m_filpar[m_current_tool].cooling_final_speed; const float& final_speed = m_filpar[m_current_tool].cooling_final_speed;
@ -916,14 +922,20 @@ void WipeTower::toolchange_Unload(
} }
} }
// let's wait is necessary: if (m_semm) {
writer.wait(m_filpar[m_current_tool].delay); // let's wait is necessary:
// we should be at the beginning of the cooling tube again - let's move to parking position: writer.wait(m_filpar[m_current_tool].delay);
writer.retract(-m_cooling_tube_length/2.f+m_parking_pos_retraction-m_cooling_tube_retraction, 2000); // we should be at the beginning of the cooling tube again - let's move to parking position:
writer.retract(-m_cooling_tube_length/2.f+m_parking_pos_retraction-m_cooling_tube_retraction, 2000);
}
// this is to align ramming and future wiping extrusions, so the future y-steps can be uniform from the start: // 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 // the perimeter_width will later be subtracted, it is there to not load while moving over just extruded material
writer.travel(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing-m_perimeter_width) / 2.f + m_perimeter_width, 2400.f); Vec2f pos = Vec2f(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing-m_perimeter_width) / 2.f + m_perimeter_width);
if (m_semm)
writer.travel(pos, 2400.f);
else
writer.set_position(pos);
writer.resume_preview() writer.resume_preview()
.flush_planner_queue(); .flush_planner_queue();
@ -941,7 +953,7 @@ void WipeTower::toolchange_Change(
// This is where we want to place the custom gcodes. We will use placeholders for this. // This is where we want to place the custom gcodes. We will use placeholders for this.
// These will be substituted by the actual gcodes when the gcode is generated. // These will be substituted by the actual gcodes when the gcode is generated.
writer.append("[end_filament_gcode]\n"); //writer.append("[end_filament_gcode]\n");
writer.append("[toolchange_gcode]\n"); writer.append("[toolchange_gcode]\n");
// Travel to where we assume we are. Custom toolchange or some special T code handling (parking extruder etc) // Travel to where we assume we are. Custom toolchange or some special T code handling (parking extruder etc)
@ -952,11 +964,12 @@ void WipeTower::toolchange_Change(
.append(std::string("G1 X") + Slic3r::float_to_string_decimal_point(current_pos.x()) .append(std::string("G1 X") + Slic3r::float_to_string_decimal_point(current_pos.x())
+ " Y" + Slic3r::float_to_string_decimal_point(current_pos.y()) + " Y" + Slic3r::float_to_string_decimal_point(current_pos.y())
+ never_skip_tag() + "\n"); + never_skip_tag() + "\n");
writer.append("[deretraction_from_wipe_tower_generator]");
// The toolchange Tn command will be inserted later, only in case that the user does // The toolchange Tn command will be inserted later, only in case that the user does
// not provide a custom toolchange gcode. // not provide a custom toolchange gcode.
writer.set_tool(new_tool); // This outputs nothing, the writer just needs to know the tool has changed. writer.set_tool(new_tool); // This outputs nothing, the writer just needs to know the tool has changed.
writer.append("[start_filament_gcode]\n"); //writer.append("[start_filament_gcode]\n");
writer.flush_planner_queue(); writer.flush_planner_queue();
m_current_tool = new_tool; m_current_tool = new_tool;

View File

@ -290,7 +290,6 @@ private:
// Extruder specific parameters. // Extruder specific parameters.
std::vector<FilamentParameters> m_filpar; std::vector<FilamentParameters> m_filpar;
// State of the wipe tower generator. // State of the wipe tower generator.
unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics. 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_num_tool_changes = 0; // Tool change change counter for the output statistics.

View File

@ -470,7 +470,7 @@ static std::vector<std::string> s_Preset_filament_options {
"extrusion_multiplier", "filament_density", "filament_cost", "filament_spool_weight", "filament_loading_speed", "filament_loading_speed_start", "filament_load_time", "extrusion_multiplier", "filament_density", "filament_cost", "filament_spool_weight", "filament_loading_speed", "filament_loading_speed_start", "filament_load_time",
"filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves", "filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves",
"filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower", "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower",
"temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "temperature", "idle_temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed",
"max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "full_fan_speed_layer", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "full_fan_speed_layer", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed",
"start_filament_gcode", "end_filament_gcode", "start_filament_gcode", "end_filament_gcode",
// Retract overrides // Retract overrides

View File

@ -191,6 +191,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "infill_first" || opt_key == "infill_first"
|| opt_key == "single_extruder_multi_material" || opt_key == "single_extruder_multi_material"
|| opt_key == "temperature" || opt_key == "temperature"
|| opt_key == "idle_temperature"
|| opt_key == "wipe_tower" || opt_key == "wipe_tower"
|| opt_key == "wipe_tower_width" || opt_key == "wipe_tower_width"
|| opt_key == "wipe_tower_brim_width" || opt_key == "wipe_tower_brim_width"
@ -347,7 +348,7 @@ std::vector<ObjectID> Print::print_object_ids() const
bool Print::has_infinite_skirt() const bool Print::has_infinite_skirt() const
{ {
return (m_config.draft_shield == dsEnabled && m_config.skirts > 0) || (m_config.ooze_prevention && this->extruders().size() > 1); return (m_config.draft_shield == dsEnabled && m_config.skirts > 0)/* || (m_config.ooze_prevention && this->extruders().size() > 1)*/;
} }
bool Print::has_skirt() const bool Print::has_skirt() const
@ -505,8 +506,8 @@ std::string Print::validate(std::string* warning) const
return L("The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter, RepRapFirmware and Repetier G-code flavors."); return L("The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter, RepRapFirmware and Repetier G-code flavors.");
if (! m_config.use_relative_e_distances) if (! m_config.use_relative_e_distances)
return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1)."); return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1).");
if (m_config.ooze_prevention) if (m_config.ooze_prevention && m_config.single_extruder_multi_material)
return L("Ooze prevention is currently not supported with the wipe tower enabled."); return L("Ooze prevention is only supported with the wipe tower when 'single_extruder_multi_material' is off.");
if (m_config.use_volumetric_e) if (m_config.use_volumetric_e)
return L("The Wipe Tower currently does not support volumetric E (use_volumetric_e=0)."); return L("The Wipe Tower currently does not support volumetric E (use_volumetric_e=0).");
if (m_config.complete_objects && extruders.size() > 1) if (m_config.complete_objects && extruders.size() > 1)

View File

@ -229,6 +229,11 @@ static void assign_printer_technology_to_unknown(t_optiondef_map &options, Print
kvp.second.printer_technology = printer_technology; kvp.second.printer_technology = printer_technology;
} }
// Maximum extruder temperature, bumped to 1500 to support printing of glass.
namespace {
const int max_temp = 1500;
};
PrintConfigDef::PrintConfigDef() PrintConfigDef::PrintConfigDef()
{ {
this->init_common_params(); this->init_common_params();
@ -1972,9 +1977,7 @@ void PrintConfigDef::init_fff_params()
def = this->add("ooze_prevention", coBool); def = this->add("ooze_prevention", coBool);
def->label = L("Enable"); def->label = L("Enable");
def->tooltip = L("This option will drop the temperature of the inactive extruders to prevent oozing. " def->tooltip = L("This option will drop the temperature of the inactive extruders to prevent oozing. ");
"It will enable a tall skirt automatically and move extruders outside such "
"skirt when changing temperatures.");
def->mode = comExpert; def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(false)); def->set_default_value(new ConfigOptionBool(false));
@ -2475,7 +2478,8 @@ void PrintConfigDef::init_fff_params()
def = this->add("standby_temperature_delta", coInt); def = this->add("standby_temperature_delta", coInt);
def->label = L("Temperature variation"); def->label = L("Temperature variation");
def->tooltip = L("Temperature difference to be applied when an extruder is not active. " def->tooltip = L("Temperature difference to be applied when an extruder is not active. "
"Enables a full-height \"sacrificial\" skirt on which the nozzles are periodically wiped."); "The value is not used when 'idle_temperature' in filament settings "
"is defined.");
def->sidetext = "∆°C"; def->sidetext = "∆°C";
def->min = -max_temp; def->min = -max_temp;
def->max = max_temp; def->max = max_temp;
@ -3731,6 +3735,15 @@ void PrintConfigDef::init_sla_params()
def->min = 0; def->min = 0;
def->set_default_value(new ConfigOptionFloat(0.3)); def->set_default_value(new ConfigOptionFloat(0.3));
def = this->add_nullable("idle_temperature", coInts);
def->label = L("Idle temperature");
def->tooltip = L("Nozzle temperature when the tool is currently not used in multi-tool setups."
"This is only used when 'Ooze prevention is active in Print Settings.'");
def->sidetext = L("°C");
def->min = 0;
def->max = max_temp;
def->set_default_value(new ConfigOptionIntsNullable { ConfigOptionIntsNullable::nil_value() });
def = this->add("bottle_volume", coFloat); def = this->add("bottle_volume", coFloat);
def->label = L("Bottle volume"); def->label = L("Bottle volume");
def->tooltip = L("Bottle volume"); def->tooltip = L("Bottle volume");
@ -4511,6 +4524,12 @@ std::string validate(const FullPrintConfig &cfg)
assert(opt != nullptr); assert(opt != nullptr);
const ConfigOptionDef *optdef = print_config_def.get(opt_key); const ConfigOptionDef *optdef = print_config_def.get(opt_key);
assert(optdef != nullptr); assert(optdef != nullptr);
if (opt->nullable() && opt->is_nil()) {
// Do not check nil values
continue;
}
bool out_of_range = false; bool out_of_range = false;
switch (opt->type()) { switch (opt->type()) {
case coFloat: case coFloat:

View File

@ -769,6 +769,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionFloatOrPercent, first_layer_height)) ((ConfigOptionFloatOrPercent, first_layer_height))
((ConfigOptionFloatOrPercent, first_layer_speed)) ((ConfigOptionFloatOrPercent, first_layer_speed))
((ConfigOptionInts, first_layer_temperature)) ((ConfigOptionInts, first_layer_temperature))
((ConfigOptionIntsNullable, idle_temperature))
((ConfigOptionInts, full_fan_speed_layer)) ((ConfigOptionInts, full_fan_speed_layer))
((ConfigOptionFloat, infill_acceleration)) ((ConfigOptionFloat, infill_acceleration))
((ConfigOptionBool, infill_first)) ((ConfigOptionBool, infill_first))

View File

@ -762,28 +762,26 @@ void SpinCtrl::BUILD() {
if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit); if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit);
wxString text_value = wxString(""); wxString text_value = wxString("");
int default_value = 0; int default_value = UNDEF_VALUE;
switch (m_opt.type) { switch (m_opt.type) {
case coInt: case coInt:
default_value = m_opt.default_value->getInt(); default_value = m_opt.default_value->getInt();
text_value = wxString::Format(_T("%i"), default_value);
break; break;
case coInts: case coInts:
{ {
const ConfigOptionInts *vec = m_opt.get_default_value<ConfigOptionInts>(); default_value = m_opt.get_default_value<ConfigOptionInts>()->get_at(m_opt_idx);
if (vec == nullptr || vec->empty()) break; if (m_opt.nullable)
for (size_t id = 0; id < vec->size(); ++id) m_last_meaningful_value = default_value == ConfigOptionIntsNullable::nil_value() ? static_cast<int>(m_opt.max) : default_value;
{
default_value = vec->get_at(id);
text_value += wxString::Format(_T("%i"), default_value);
}
break; break;
} }
default: default:
break; break;
} }
if (default_value != UNDEF_VALUE)
text_value = wxString::Format(_T("%i"), default_value);
const int min_val = m_opt.min == -FLT_MAX const int min_val = m_opt.min == -FLT_MAX
#ifdef __WXOSX__ #ifdef __WXOSX__
// We will forcibly set the input value for SpinControl, since the value // We will forcibly set the input value for SpinControl, since the value
@ -882,6 +880,50 @@ void SpinCtrl::BUILD() {
window = dynamic_cast<wxWindow*>(temp); window = dynamic_cast<wxWindow*>(temp);
} }
void SpinCtrl::set_value(const boost::any& value, bool change_event/* = false*/)
{
m_disable_change_event = !change_event;
tmp_value = boost::any_cast<int>(value);
m_value = value;
if (m_opt.nullable) {
const bool m_is_na_val = tmp_value == ConfigOptionIntsNullable::nil_value();
if (m_is_na_val)
dynamic_cast<wxSpinCtrl*>(window)->SetValue(na_value());
else {
m_last_meaningful_value = value;
dynamic_cast<wxSpinCtrl*>(window)->SetValue(tmp_value);
}
}
else
dynamic_cast<wxSpinCtrl*>(window)->SetValue(tmp_value);
m_disable_change_event = false;
}
void SpinCtrl::set_last_meaningful_value()
{
const int val = boost::any_cast<int>(m_last_meaningful_value);
dynamic_cast<wxSpinCtrl*>(window)->SetValue(val);
tmp_value = val;
propagate_value();
}
void SpinCtrl::set_na_value()
{
dynamic_cast<wxSpinCtrl*>(window)->SetValue(na_value());
m_value = ConfigOptionIntsNullable::nil_value();
propagate_value();
}
boost::any& SpinCtrl::get_value()
{
wxSpinCtrl* spin = static_cast<wxSpinCtrl*>(window);
if (spin->GetTextValue() == na_value())
return m_value;
int value = spin->GetValue();
return m_value = value;
}
void SpinCtrl::propagate_value() void SpinCtrl::propagate_value()
{ {
// check if value was really changed // check if value was really changed

View File

@ -330,24 +330,18 @@ public:
void BUILD() override; void BUILD() override;
/// Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER /// Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER
void propagate_value() ; void propagate_value() ;
/*
void set_value(const std::string& value, bool change_event = false) { void set_value(const std::string& value, bool change_event = false) {
m_disable_change_event = !change_event; m_disable_change_event = !change_event;
dynamic_cast<wxSpinCtrl*>(window)->SetValue(value); dynamic_cast<wxSpinCtrl*>(window)->SetValue(value);
m_disable_change_event = false; m_disable_change_event = false;
} }
void set_value(const boost::any& value, bool change_event = false) override { */
m_disable_change_event = !change_event; void set_value(const boost::any& value, bool change_event = false) override;
tmp_value = boost::any_cast<int>(value); void set_last_meaningful_value() override;
m_value = value; void set_na_value() override;
dynamic_cast<wxSpinCtrl*>(window)->SetValue(tmp_value);
m_disable_change_event = false;
}
boost::any& get_value() override { boost::any& get_value() override;
int value = static_cast<wxSpinCtrl*>(window)->GetValue();
return m_value = value;
}
void msw_rescale() override; void msw_rescale() override;

View File

@ -1826,45 +1826,59 @@ static void validate_custom_gcode_cb(Tab* tab, const wxString& title, const t_co
tab->on_value_change(opt_key, value); tab->on_value_change(opt_key, value);
} }
void TabFilament::create_line_with_near_label_widget(ConfigOptionsGroupShp optgroup, const std::string& opt_key, int opt_index/* = 0*/)
{
Line line {"",""};
if (opt_key == "filament_retract_lift_above" || opt_key == "filament_retract_lift_below") {
Option opt = optgroup->get_option(opt_key);
opt.opt.label = opt.opt.full_label;
line = optgroup->create_single_option_line(opt);
}
else
line = optgroup->create_single_option_line(optgroup->get_option(opt_key));
line.near_label_widget = [this, optgroup_wk = ConfigOptionsGroupWkp(optgroup), opt_key, opt_index](wxWindow* parent) {
wxCheckBox* check_box = new wxCheckBox(parent, wxID_ANY, "");
check_box->Bind(wxEVT_CHECKBOX, [optgroup_wk, opt_key, opt_index](wxCommandEvent& evt) {
const bool is_checked = evt.IsChecked();
if (auto optgroup_sh = optgroup_wk.lock(); optgroup_sh) {
if (Field *field = optgroup_sh->get_fieldc(opt_key, opt_index); field != nullptr) {
field->toggle(is_checked);
if (is_checked)
field->set_last_meaningful_value();
else
field->set_na_value();
}
}
}, check_box->GetId());
m_overrides_options[opt_key] = check_box;
return check_box;
};
optgroup->append_line(line);
}
void TabFilament::update_line_with_near_label_widget(ConfigOptionsGroupShp optgroup, const std::string& opt_key, int opt_index/* = 0*/, bool is_checked/* = true*/)
{
if (!m_overrides_options[opt_key])
return;
m_overrides_options[opt_key]->Enable(is_checked);
is_checked &= !m_config->option(opt_key)->is_nil();
m_overrides_options[opt_key]->SetValue(is_checked);
Field* field = optgroup->get_fieldc(opt_key, opt_index);
if (field != nullptr)
field->toggle(is_checked);
}
void TabFilament::add_filament_overrides_page() void TabFilament::add_filament_overrides_page()
{ {
PageShp page = add_options_page(L("Filament Overrides"), "wrench"); PageShp page = add_options_page(L("Filament Overrides"), "wrench");
ConfigOptionsGroupShp optgroup = page->new_optgroup(L("Retraction")); ConfigOptionsGroupShp optgroup = page->new_optgroup(L("Retraction"));
auto append_single_option_line = [optgroup, this](const std::string& opt_key, int opt_index)
{
Line line {"",""};
if (opt_key == "filament_retract_lift_above" || opt_key == "filament_retract_lift_below") {
Option opt = optgroup->get_option(opt_key);
opt.opt.label = opt.opt.full_label;
line = optgroup->create_single_option_line(opt);
}
else
line = optgroup->create_single_option_line(optgroup->get_option(opt_key));
line.near_label_widget = [this, optgroup_wk = ConfigOptionsGroupWkp(optgroup), opt_key, opt_index](wxWindow* parent) {
wxCheckBox* check_box = new wxCheckBox(parent, wxID_ANY, "");
check_box->Bind(wxEVT_CHECKBOX, [optgroup_wk, opt_key, opt_index](wxCommandEvent& evt) {
const bool is_checked = evt.IsChecked();
if (auto optgroup_sh = optgroup_wk.lock(); optgroup_sh) {
if (Field *field = optgroup_sh->get_fieldc(opt_key, opt_index); field != nullptr) {
field->toggle(is_checked);
if (is_checked)
field->set_last_meaningful_value();
else
field->set_na_value();
}
}
}, check_box->GetId());
m_overrides_options[opt_key] = check_box;
return check_box;
};
optgroup->append_line(line);
};
const int extruder_idx = 0; // #ys_FIXME const int extruder_idx = 0; // #ys_FIXME
for (const std::string opt_key : { "filament_retract_length", for (const std::string opt_key : { "filament_retract_length",
@ -1879,7 +1893,7 @@ void TabFilament::add_filament_overrides_page()
"filament_wipe", "filament_wipe",
"filament_retract_before_wipe" "filament_retract_before_wipe"
}) })
append_single_option_line(opt_key, extruder_idx); create_line_with_near_label_widget(optgroup, opt_key, extruder_idx);
} }
void TabFilament::update_filament_overrides_page() void TabFilament::update_filament_overrides_page()
@ -1914,14 +1928,7 @@ void TabFilament::update_filament_overrides_page()
for (const std::string& opt_key : opt_keys) for (const std::string& opt_key : opt_keys)
{ {
bool is_checked = opt_key=="filament_retract_length" ? true : have_retract_length; bool is_checked = opt_key=="filament_retract_length" ? true : have_retract_length;
m_overrides_options[opt_key]->Enable(is_checked); update_line_with_near_label_widget(optgroup, opt_key, extruder_idx, is_checked);
is_checked &= !m_config->option(opt_key)->is_nil();
m_overrides_options[opt_key]->SetValue(is_checked);
Field* field = optgroup->get_fieldc(opt_key, extruder_idx);
if (field != nullptr)
field->toggle(is_checked);
} }
} }
@ -1952,6 +1959,9 @@ void TabFilament::build()
}; };
optgroup = page->new_optgroup(L("Temperature")); optgroup = page->new_optgroup(L("Temperature"));
create_line_with_near_label_widget(optgroup, "idle_temperature");
Line line = { L("Nozzle"), "" }; Line line = { L("Nozzle"), "" };
line.append_option(optgroup->get_option("first_layer_temperature")); line.append_option(optgroup->get_option("first_layer_temperature"));
line.append_option(optgroup->get_option("temperature")); line.append_option(optgroup->get_option("temperature"));
@ -2142,6 +2152,14 @@ void TabFilament::toggle_options()
if (m_active_page->title() == "Filament Overrides") if (m_active_page->title() == "Filament Overrides")
update_filament_overrides_page(); update_filament_overrides_page();
if (m_active_page->title() == "Filament") {
Page* page = m_active_page;
const auto og_it = std::find_if(page->m_optgroups.begin(), page->m_optgroups.end(), [](const ConfigOptionsGroupShp og) { return og->title == "Temperature"; });
if (og_it != page->m_optgroups.end())
update_line_with_near_label_widget(*og_it, "idle_temperature");
}
} }
void TabFilament::update() void TabFilament::update()

View File

@ -436,6 +436,8 @@ private:
ogStaticText* m_volumetric_speed_description_line {nullptr}; ogStaticText* m_volumetric_speed_description_line {nullptr};
ogStaticText* m_cooling_description_line {nullptr}; ogStaticText* m_cooling_description_line {nullptr};
void create_line_with_near_label_widget(ConfigOptionsGroupShp optgroup, const std::string &opt_key, int opt_index = 0);
void update_line_with_near_label_widget(ConfigOptionsGroupShp optgroup, const std::string &opt_key, int opt_index = 0, bool is_checked = true);
void add_filament_overrides_page(); void add_filament_overrides_page();
void update_filament_overrides_page(); void update_filament_overrides_page();
void update_volumetric_flow_preset_hints(); void update_volumetric_flow_preset_hints();

View File

@ -108,18 +108,18 @@ SCENARIO("Ooze prevention", "[Multi]")
Polygon convex_hull = Geometry::convex_hull(extrusion_points); Polygon convex_hull = Geometry::convex_hull(extrusion_points);
THEN("all nozzles are outside skirt at toolchange") { // THEN("all nozzles are outside skirt at toolchange") {
Points t; // Points t;
sort_remove_duplicates(toolchange_points); // sort_remove_duplicates(toolchange_points);
size_t inside = 0; // size_t inside = 0;
for (const auto &point : toolchange_points) // for (const auto &point : toolchange_points)
for (const Vec2d &offset : print_config.extruder_offset.values) { // for (const Vec2d &offset : print_config.extruder_offset.values) {
Point p = point + scaled<coord_t>(offset); // Point p = point + scaled<coord_t>(offset);
if (convex_hull.contains(p)) // if (convex_hull.contains(p))
++ inside; // ++ inside;
} // }
REQUIRE(inside == 0); // REQUIRE(inside == 0);
} // }
#if 0 #if 0
require "Slic3r/SVG.pm"; require "Slic3r/SVG.pm";