diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index ef3dc32c8..23be26ee7 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -776,7 +776,7 @@ public: static int nil_value() { return std::numeric_limits::max(); } // 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(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 { diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index ad0d3c43f..ffffd9d31 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -113,26 +113,19 @@ namespace Slic3r { { std::string gcode; - // move to the nearest standby point - if (!this->standby_points.empty()) { - // get current position in print coordinates - Vec3d writer_pos = gcodegen.writer().get_position(); - Point pos = Point::new_scale(writer_pos(0), writer_pos(1)); - - // find standby point - Point standby_point = nearest_point(this->standby_points, pos).first; - - /* 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 - of the destination point must not be transformed by origin nor current extruder offset. */ - gcode += gcodegen.writer().travel_to_xy(unscale(standby_point), - "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()); + unsigned int extruder_id = gcodegen.writer().extruder()->id(); + const ConfigOptionIntsNullable& filament_idle_temp = gcodegen.config().idle_temperature; + if (filament_idle_temp.is_nil(extruder_id)) { + // There is no idle temperature defined in filament settings. + // Use the delta value from print config. + 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, extruder_id); + } + } else { + // Use the value from filament settings. That one is absolute, not delta. + gcode += gcodegen.writer().set_temperature(filament_idle_temp.get_at(extruder_id), false, extruder_id); } return gcode; @@ -145,8 +138,7 @@ namespace Slic3r { std::string(); } - int - OozePrevention::_get_temp(GCode& gcodegen) + int OozePrevention::_get_temp(const GCode& gcodegen) const { return (gcodegen.layer() != nullptr && gcodegen.layer()->id() == 0) ? 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); - if (! tcr.priming) { - // Move over the wipe tower. + double current_z = gcodegen.writer().get_position().z(); + 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(); gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); gcode += gcodegen.travel_to( @@ -248,82 +248,36 @@ namespace Slic3r { "Travel to a Wipe Tower"); gcode += gcodegen.unretract(); } - - double current_z = gcodegen.writer().get_position().z(); - if (z == -1.) // in case no specific z was provided, print at current_z pos - z = current_z; - if (! is_approx(z, current_z)) { + + if (will_go_down) { gcode += gcodegen.writer().retract(); gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer."); 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; - const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value; - if (! toolchange_gcode.empty()) { - DynamicConfig config; - int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1; - config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id)); - config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id)); - config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); - 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 deretraction_str; + if (tcr.priming || (new_extruder_id >= 0 && needs_toolchange)) { + if (gcodegen.config().single_extruder_multi_material) + 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) + deretraction_str = gcodegen.unretract(); } - 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; - 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("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); unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode); gcode += tcr_gcode; check_add_eol(toolchange_gcode_str); - // A phony move to the end position at the wipe tower. gcodegen.writer().travel_to_xy(end_pos.cast()); 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) { - // Calculate wiping points if needed - 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(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 - } - } + ooze_prevention.enable = print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material; } // 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_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() })); + + std::vector 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); // 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. file.write(this->preamble()); - // Calculate wiping points if needed + // Enable ooze prevention if configured so. DoExport::init_ooze_prevention(print, m_ooze_prevention); + print.throw_if_canceled(); // 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); } else { // 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. int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id); 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 // first_layer_temperature vs. temperature settings. 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. - 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()); - 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_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()) { // Process the start_filament_gcode for the filament. 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))); gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id, &config); check_add_eol(gcode); @@ -3201,8 +3140,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) m_wipe.reset_path(); if (m_writer.extruder() != nullptr) { - // Process the custom end_filament_gcode. set_extruder() is only called if there is no wipe tower - // so it should not be injected twice. + // Process the custom end_filament_gcode. 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); 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 - // standby point and set it to the standby temperature. + // If ooze prevention is enabled, set current extruder to the standby temperature. if (m_ooze_prevention.enable && m_writer.extruder() != nullptr) 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()) { // Process the start_filament_gcode for the new filament. 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))); gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id, &config); check_add_eol(gcode); diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 0741d7e37..ee50aefcc 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -39,14 +39,13 @@ struct PrintInstance; class OozePrevention { public: bool enable; - Points standby_points; OozePrevention() : enable(false) {} std::string pre_toolchange(GCode &gcodegen); std::string post_toolchange(GCode &gcodegen); private: - int _get_temp(GCode &gcodegen); + int _get_temp(const GCode &gcodegen) const; }; class Wipe { diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index f24311a13..04fab3fcd 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -71,6 +71,8 @@ public: 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_z(float z) @@ -802,22 +804,26 @@ void WipeTower::toolchange_Unload( { 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 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 Vec2f ramming_start_pos = Vec2f(xl, cleaning_box.ld.y() + m_depth_traversed + y_step/2.f); + writer.append("; CP TOOLCHANGE UNLOAD\n") .change_analyzer_line_width(line_width); unsigned i = 0; // iterates through ramming_speed m_left_to_right = true; // current direction of ramming float remaining = xr - xl ; // keeps track of distance to the next turnaround - 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 + float e_done = 0; // measures E move done from each segment + 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 (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 float sparse_beginning_y = 0.f; @@ -849,7 +855,7 @@ void WipeTower::toolchange_Unload( writer.disable_linear_advance(); // 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 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: 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& final_speed = m_filpar[m_current_tool].cooling_final_speed; @@ -916,14 +922,20 @@ void WipeTower::toolchange_Unload( } } - // let's wait is necessary: - writer.wait(m_filpar[m_current_tool].delay); - // 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); + if (m_semm) { + // let's wait is necessary: + writer.wait(m_filpar[m_current_tool].delay); + // 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: // 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() .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. // 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"); // 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()) + " Y" + Slic3r::float_to_string_decimal_point(current_pos.y()) + 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 // 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.append("[start_filament_gcode]\n"); + //writer.append("[start_filament_gcode]\n"); writer.flush_planner_queue(); m_current_tool = new_tool; diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 397b5ab7d..ab3af507d 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -290,7 +290,6 @@ private: // Extruder specific parameters. std::vector m_filpar; - // State of the wipe tower generator. unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics. unsigned int m_num_tool_changes = 0; // Tool change change counter for the output statistics. diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index d2f5256f9..d6a4692f3 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -470,7 +470,7 @@ static std::vector s_Preset_filament_options { "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_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", "start_filament_gcode", "end_filament_gcode", // Retract overrides diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index e7305234f..055c6f730 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -191,6 +191,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "infill_first" || opt_key == "single_extruder_multi_material" || opt_key == "temperature" + || opt_key == "idle_temperature" || opt_key == "wipe_tower" || opt_key == "wipe_tower_width" || opt_key == "wipe_tower_brim_width" @@ -347,7 +348,7 @@ std::vector Print::print_object_ids() 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 @@ -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."); 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)."); - if (m_config.ooze_prevention) - return L("Ooze prevention is currently not supported with the wipe tower enabled."); + if (m_config.ooze_prevention && m_config.single_extruder_multi_material) + return L("Ooze prevention is only supported with the wipe tower when 'single_extruder_multi_material' is off."); if (m_config.use_volumetric_e) return L("The Wipe Tower currently does not support volumetric E (use_volumetric_e=0)."); if (m_config.complete_objects && extruders.size() > 1) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 4da75489c..1d0446ce2 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -229,6 +229,11 @@ static void assign_printer_technology_to_unknown(t_optiondef_map &options, Print 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() { this->init_common_params(); @@ -1972,9 +1977,7 @@ void PrintConfigDef::init_fff_params() def = this->add("ooze_prevention", coBool); def->label = L("Enable"); - 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->tooltip = L("This option will drop the temperature of the inactive extruders to prevent oozing. "); def->mode = comExpert; def->set_default_value(new ConfigOptionBool(false)); @@ -2475,7 +2478,8 @@ void PrintConfigDef::init_fff_params() def = this->add("standby_temperature_delta", coInt); def->label = L("Temperature variation"); 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->min = -max_temp; def->max = max_temp; @@ -3731,6 +3735,15 @@ void PrintConfigDef::init_sla_params() def->min = 0; 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->label = L("Bottle volume"); def->tooltip = L("Bottle volume"); @@ -4511,6 +4524,12 @@ std::string validate(const FullPrintConfig &cfg) assert(opt != nullptr); const ConfigOptionDef *optdef = print_config_def.get(opt_key); assert(optdef != nullptr); + + if (opt->nullable() && opt->is_nil()) { + // Do not check nil values + continue; + } + bool out_of_range = false; switch (opt->type()) { case coFloat: diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 42ac9c0e2..6bfc7723d 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -769,6 +769,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionFloatOrPercent, first_layer_height)) ((ConfigOptionFloatOrPercent, first_layer_speed)) ((ConfigOptionInts, first_layer_temperature)) + ((ConfigOptionIntsNullable, idle_temperature)) ((ConfigOptionInts, full_fan_speed_layer)) ((ConfigOptionFloat, infill_acceleration)) ((ConfigOptionBool, infill_first)) diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 419d48d5b..4e087c98f 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -762,28 +762,26 @@ void SpinCtrl::BUILD() { if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit); wxString text_value = wxString(""); - int default_value = 0; + int default_value = UNDEF_VALUE; switch (m_opt.type) { case coInt: default_value = m_opt.default_value->getInt(); - text_value = wxString::Format(_T("%i"), default_value); break; case coInts: { - const ConfigOptionInts *vec = m_opt.get_default_value(); - if (vec == nullptr || vec->empty()) break; - for (size_t id = 0; id < vec->size(); ++id) - { - default_value = vec->get_at(id); - text_value += wxString::Format(_T("%i"), default_value); - } + default_value = m_opt.get_default_value()->get_at(m_opt_idx); + if (m_opt.nullable) + m_last_meaningful_value = default_value == ConfigOptionIntsNullable::nil_value() ? static_cast(m_opt.max) : default_value; break; } default: break; } + if (default_value != UNDEF_VALUE) + text_value = wxString::Format(_T("%i"), default_value); + const int min_val = m_opt.min == -FLT_MAX #ifdef __WXOSX__ // We will forcibly set the input value for SpinControl, since the value @@ -882,6 +880,50 @@ void SpinCtrl::BUILD() { window = dynamic_cast(temp); } +void SpinCtrl::set_value(const boost::any& value, bool change_event/* = false*/) +{ + m_disable_change_event = !change_event; + tmp_value = boost::any_cast(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(window)->SetValue(na_value()); + else { + m_last_meaningful_value = value; + dynamic_cast(window)->SetValue(tmp_value); + } + } + else + dynamic_cast(window)->SetValue(tmp_value); + m_disable_change_event = false; +} + +void SpinCtrl::set_last_meaningful_value() +{ + const int val = boost::any_cast(m_last_meaningful_value); + dynamic_cast(window)->SetValue(val); + tmp_value = val; + propagate_value(); +} + +void SpinCtrl::set_na_value() +{ + dynamic_cast(window)->SetValue(na_value()); + m_value = ConfigOptionIntsNullable::nil_value(); + propagate_value(); +} + +boost::any& SpinCtrl::get_value() +{ + wxSpinCtrl* spin = static_cast(window); + if (spin->GetTextValue() == na_value()) + return m_value; + + int value = spin->GetValue(); + return m_value = value; +} + void SpinCtrl::propagate_value() { // check if value was really changed diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index eaa4fe481..73af0ef53 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -330,24 +330,18 @@ public: void BUILD() override; /// Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER void propagate_value() ; - +/* void set_value(const std::string& value, bool change_event = false) { m_disable_change_event = !change_event; dynamic_cast(window)->SetValue(value); m_disable_change_event = false; } - void set_value(const boost::any& value, bool change_event = false) override { - m_disable_change_event = !change_event; - tmp_value = boost::any_cast(value); - m_value = value; - dynamic_cast(window)->SetValue(tmp_value); - m_disable_change_event = false; - } +*/ + void set_value(const boost::any& value, bool change_event = false) override; + void set_last_meaningful_value() override; + void set_na_value() override; - boost::any& get_value() override { - int value = static_cast(window)->GetValue(); - return m_value = value; - } + boost::any& get_value() override; void msw_rescale() override; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 7d91f689d..3a69bcee1 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -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); } +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() { PageShp page = add_options_page(L("Filament Overrides"), "wrench"); 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 for (const std::string opt_key : { "filament_retract_length", @@ -1879,7 +1893,7 @@ void TabFilament::add_filament_overrides_page() "filament_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() @@ -1914,14 +1928,7 @@ void TabFilament::update_filament_overrides_page() for (const std::string& opt_key : opt_keys) { bool is_checked = opt_key=="filament_retract_length" ? true : have_retract_length; - 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, extruder_idx); - if (field != nullptr) - field->toggle(is_checked); + update_line_with_near_label_widget(optgroup, opt_key, extruder_idx, is_checked); } } @@ -1952,6 +1959,9 @@ void TabFilament::build() }; optgroup = page->new_optgroup(L("Temperature")); + + create_line_with_near_label_widget(optgroup, "idle_temperature"); + Line line = { L("Nozzle"), "" }; line.append_option(optgroup->get_option("first_layer_temperature")); line.append_option(optgroup->get_option("temperature")); @@ -2142,6 +2152,14 @@ void TabFilament::toggle_options() if (m_active_page->title() == "Filament Overrides") 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() diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index c060eb7fd..b131761e6 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -436,6 +436,8 @@ private: ogStaticText* m_volumetric_speed_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 update_filament_overrides_page(); void update_volumetric_flow_preset_hints(); diff --git a/tests/fff_print/test_multi.cpp b/tests/fff_print/test_multi.cpp index 03c7f644b..90f311c39 100644 --- a/tests/fff_print/test_multi.cpp +++ b/tests/fff_print/test_multi.cpp @@ -108,18 +108,18 @@ SCENARIO("Ooze prevention", "[Multi]") Polygon convex_hull = Geometry::convex_hull(extrusion_points); - THEN("all nozzles are outside skirt at toolchange") { - Points t; - sort_remove_duplicates(toolchange_points); - size_t inside = 0; - for (const auto &point : toolchange_points) - for (const Vec2d &offset : print_config.extruder_offset.values) { - Point p = point + scaled(offset); - if (convex_hull.contains(p)) - ++ inside; - } - REQUIRE(inside == 0); - } + // THEN("all nozzles are outside skirt at toolchange") { + // Points t; + // sort_remove_duplicates(toolchange_points); + // size_t inside = 0; + // for (const auto &point : toolchange_points) + // for (const Vec2d &offset : print_config.extruder_offset.values) { + // Point p = point + scaled(offset); + // if (convex_hull.contains(p)) + // ++ inside; + // } + // REQUIRE(inside == 0); + // } #if 0 require "Slic3r/SVG.pm";