From fdbe1196580e2ecdb61543c79e4119e856f15b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Wed, 21 Feb 2024 17:56:55 +0100 Subject: [PATCH 01/11] Make LabelObjects hold state. * This enables setting the object flags anywhere in gcode. * The flags are then set in such a way that cancel object behaves as expected. --- src/libslic3r/GCode.cpp | 44 +++++++++++++------- src/libslic3r/GCode.hpp | 11 +++-- src/libslic3r/GCode/LabelObjects.cpp | 30 +++++++++++++ src/libslic3r/GCode/LabelObjects.hpp | 43 ++++++++++++------- src/libslic3r/GCode/WipeTowerIntegration.cpp | 8 ++-- 5 files changed, 96 insertions(+), 40 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 6a0ed586ca..7507c0d74e 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -576,7 +576,6 @@ GCodeGenerator::GCodeGenerator(const Print* print) : m_brim_done(false), m_second_layer_things_done(false), m_silent_time_estimator_enabled(false), - m_current_instance({nullptr, -1}), m_print(print) {} @@ -1248,7 +1247,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer m_avoid_crossing_perimeters.use_external_mp_once(); file.write(this->retract_and_wipe()); - file.write(this->travel_to(*this->last_position, Point(0, 0), ExtrusionRole::None, "move to origin position for next object")); + file.write(this->travel_to(*this->last_position, Point(0, 0), ExtrusionRole::None, "move to origin position for next object", "")); m_enable_cooling_markers = true; // Disable motion planner when traveling to first object point. m_avoid_crossing_perimeters.disable_once(); @@ -1333,6 +1332,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail // and export G-code into file. this->process_layers(print, tool_ordering, print_object_instances_ordering, layers_to_print, smooth_path_cache_global, file); + file.write(m_label_objects.maybe_stop_instance()); if (m_wipe_tower) // Purge the extruder, pull out the active filament. file.write(m_wipe_tower->finalize(*this)); @@ -2518,12 +2518,11 @@ void GCodeGenerator::process_layer_single_object( m_avoid_crossing_perimeters.init_layer(*m_layer); // When starting a new object, use the external motion planner for the first travel move. const Point &offset = print_object.instances()[print_instance.instance_id].shift; - GCode::PrintObjectInstance next_instance = {&print_object, int(print_instance.instance_id)}; - if (m_current_instance != next_instance) + + const bool updated{m_label_objects.update(print_instance.print_object.instances()[print_instance.instance_id])}; + if (updated) m_avoid_crossing_perimeters.use_external_mp_once(); - m_current_instance = next_instance; this->set_origin(unscale(offset)); - gcode += m_label_objects.start_object(print_instance.print_object.instances()[print_instance.instance_id], GCode::LabelObjects::IncludeName::No); } }; @@ -2703,8 +2702,6 @@ void GCodeGenerator::process_layer_single_object( } } } - if (! first) - gcode += m_label_objects.stop_object(print_instance.print_object.instances()[print_instance.instance_id]); } void GCodeGenerator::apply_print_config(const PrintConfig &print_config) @@ -3069,7 +3066,7 @@ void GCodeGenerator::GCodeOutputStream::write_format(const char* format, ...) va_end(args); } -std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const double from_z) { +std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const double from_z, const std::string& gcode_to_insert) { std::string gcode; const Vec3d gcode_point = to_3d(this->point_to_gcode(point.head<2>()), unscaled(point.z())); @@ -3093,6 +3090,8 @@ std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const this->last_position = point.head<2>(); this->writer().update_position(gcode_point); + gcode += gcode_to_insert; + std::string comment{"move to first layer point"}; gcode += this->writer().get_travel_to_xy_gcode(gcode_point.head<2>(), comment); gcode += this->writer().get_travel_to_z_gcode(gcode_point.z(), comment); @@ -3126,13 +3125,14 @@ std::string GCodeGenerator::_extrude( if (!m_current_layer_first_position) { const Vec3crd point = to_3d(path.front().point, scaled(this->m_last_layer_z)); - gcode += this->travel_to_first_position(point, unscaled(point.z())); + gcode += this->travel_to_first_position(point, unscaled(point.z()), this->m_label_objects.maybe_change_instance()); } else { // go to first point of extrusion path if (!this->last_position) { const double z = this->m_last_layer_z; const std::string comment{"move to print after unknown position"}; gcode += this->retract_and_wipe(); + gcode += m_label_objects.maybe_change_instance(); gcode += this->m_writer.travel_to_xy(this->point_to_gcode(path.front().point), comment); gcode += this->m_writer.get_travel_to_z_gcode(z, comment); } else if ( this->last_position != path.front().point) { @@ -3140,7 +3140,7 @@ std::string GCodeGenerator::_extrude( comment += description; comment += description_bridge; comment += " point"; - const std::string travel_gcode{this->travel_to(*this->last_position, path.front().point, path_attr.role, comment)}; + const std::string travel_gcode{this->travel_to(*this->last_position, path.front().point, path_attr.role, comment, this->m_label_objects.maybe_change_instance())}; gcode += travel_gcode; } } @@ -3366,7 +3366,8 @@ std::string GCodeGenerator::_extrude( std::string GCodeGenerator::generate_travel_gcode( const Points3& travel, - const std::string& comment + const std::string& comment, + const std::string& insert_before_end ) { std::string gcode; @@ -3381,9 +3382,16 @@ std::string GCodeGenerator::generate_travel_gcode( gcode += this->m_writer.set_travel_acceleration(acceleration); Vec3d previous_point{this->point_to_gcode(travel.front())}; - for (const Vec3crd& point : travel) { + bool already_inserted{false}; + for (std::size_t i{0}; i < travel.size(); ++i) { + const Vec3crd& point{travel[i]}; const Vec3d gcode_point{this->point_to_gcode(point)}; + if (travel.size() - i <= 2 && !already_inserted) { + gcode += insert_before_end; + already_inserted = true; + } + gcode += this->m_writer.travel_to_xyz(previous_point, gcode_point, comment); this->last_position = point.head<2>(); previous_point = gcode_point; @@ -3479,7 +3487,11 @@ Polyline GCodeGenerator::generate_travel_xy_path( // This method accepts &point in print coordinates. std::string GCodeGenerator::travel_to( - const Point &start_point, const Point &end_point, ExtrusionRole role, const std::string &comment + const Point &start_point, + const Point &end_point, + ExtrusionRole role, + const std::string &comment, + const std::string &gcode_to_insert ) { // check whether a straight travel move would need retraction @@ -3537,7 +3549,7 @@ std::string GCodeGenerator::travel_to( ) ); - return wipe_retract_gcode + generate_travel_gcode(travel, comment); + return wipe_retract_gcode + generate_travel_gcode(travel, comment, gcode_to_insert); } std::string GCodeGenerator::retract_and_wipe(bool toolchange) @@ -3592,6 +3604,8 @@ std::string GCodeGenerator::set_extruder(unsigned int extruder_id, double print_ // prepend retraction on the current extruder std::string gcode = this->retract_and_wipe(true); + gcode += this->m_label_objects.maybe_stop_instance(); + // Always reset the extrusion path, even if the tool change retract is set to zero. m_wipe.reset_path(); diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 7800103b9b..9a96bc72c0 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -319,7 +319,8 @@ private: std::string extrude_support(const ExtrusionEntityReferences &support_fills, const GCode::SmoothPathCache &smooth_path_cache); std::string generate_travel_gcode( const Points3& travel, - const std::string& comment + const std::string& comment, + const std::string& insert_before_end ); Polyline generate_travel_xy_path( const Point& start, @@ -331,10 +332,11 @@ private: const Point &start_point, const Point &end_point, ExtrusionRole role, - const std::string &comment + const std::string &comment, + const std::string &gcode_to_insert ); - std::string travel_to_first_position(const Vec3crd& point, const double from_z); + std::string travel_to_first_position(const Vec3crd& point, const double from_z, const std::string& gcode_to_insert); bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None); @@ -444,9 +446,6 @@ private: bool m_second_layer_things_done; // G-code that is due to be written before the next extrusion std::string m_pending_pre_extrusion_gcode; - // Pointer to currently exporting PrintObject and instance index. - GCode::PrintObjectInstance m_current_instance; - bool m_silent_time_estimator_enabled; // Processor diff --git a/src/libslic3r/GCode/LabelObjects.cpp b/src/libslic3r/GCode/LabelObjects.cpp index aae9f09fb9..240e6860d8 100644 --- a/src/libslic3r/GCode/LabelObjects.cpp +++ b/src/libslic3r/GCode/LabelObjects.cpp @@ -111,7 +111,37 @@ void LabelObjects::init(const SpanOfConstPtrs& objects, LabelObject } } +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() { + if (current_instance == nullptr && last_operation_instance != nullptr) { + current_instance = last_operation_instance; + return this->start_object(*current_instance, LabelObjects::IncludeName::No); + } + 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() { + if (last_operation_instance != current_instance) { + return this->maybe_stop_instance() + this->maybe_start_instance(); + } + return ""; +} std::string LabelObjects::all_objects_header() const { diff --git a/src/libslic3r/GCode/LabelObjects.hpp b/src/libslic3r/GCode/LabelObjects.hpp index 9002655afd..0ef8acb104 100644 --- a/src/libslic3r/GCode/LabelObjects.hpp +++ b/src/libslic3r/GCode/LabelObjects.hpp @@ -16,35 +16,46 @@ class Print; namespace GCode { - -class LabelObjects { +class LabelObjects +{ public: + void init(const SpanOfConstPtrs& 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(); + + std::string maybe_stop_instance(); + + std::string maybe_change_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 SpanOfConstPtrs& objects, LabelObjectsStyle label_object_style, GCodeFlavor gcode_flavor); - std::string all_objects_header() const; - std::string all_objects_header_singleline_json() 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 { - const PrintInstance* pi; - std::string name; - std::string center; - std::string polygon; - int unique_id; - }; + const PrintInstance* current_instance{nullptr}; + const PrintInstance* last_operation_instance{nullptr}; LabelObjectsStyle m_label_objects_style; GCodeFlavor m_flavor; std::vector m_label_data; - }; - - } // namespace GCode } // namespace Slic3r diff --git a/src/libslic3r/GCode/WipeTowerIntegration.cpp b/src/libslic3r/GCode/WipeTowerIntegration.cpp index 49d523be11..3d00a12eae 100644 --- a/src/libslic3r/GCode/WipeTowerIntegration.cpp +++ b/src/libslic3r/GCode/WipeTowerIntegration.cpp @@ -64,15 +64,17 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip 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, + gcodegen.m_label_objects.maybe_stop_instance() ); } else { + gcode += gcodegen.m_label_objects.maybe_stop_instance(); gcode += gcodegen.writer().travel_to_xy(gcodegen.point_to_gcode(xy_point), comment); gcode += gcodegen.writer().get_travel_to_z_gcode(z, comment); } } 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, gcodegen.m_label_objects.maybe_stop_instance()); } gcode += gcodegen.unretract(); } else { @@ -268,7 +270,7 @@ std::string WipeTowerIntegration::finalize(GCodeGenerator &gcodegen) if (std::abs(gcodegen.writer().get_position().z() - m_final_purge.print_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" + "move to safe place for purging", "" ); gcode += append_tcr(gcodegen, m_final_purge, -1); return gcode; From 28d935b0b322b34ea3943bbe3f1318ea0867ccc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Fri, 23 Feb 2024 16:07:06 +0100 Subject: [PATCH 02/11] Improve cancel object label placement. * Make LabelObjects hold more state * Place cancel object labels at proper positions in the gcode --- src/libslic3r/GCode.cpp | 21 ++- src/libslic3r/GCode/LabelObjects.cpp | 2 +- tests/fff_print/CMakeLists.txt | 1 + tests/fff_print/test_cancel_object.cpp | 249 +++++++++++++++++++++++++ 4 files changed, 265 insertions(+), 8 deletions(-) create mode 100644 tests/fff_print/test_cancel_object.cpp diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 7507c0d74e..bb789ce3a2 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2507,7 +2507,7 @@ void GCodeGenerator::process_layer_single_object( { bool first = true; // Delay layer initialization as many layers may not print with all extruders. - auto init_layer_delayed = [this, &print_instance, &layer_to_print, &first, &gcode]() { + auto init_layer_delayed = [this, &print_instance, &layer_to_print, &first]() { if (first) { first = false; const PrintObject &print_object = print_instance.print_object; @@ -3123,16 +3123,18 @@ std::string GCodeGenerator::_extrude( std::string gcode; const std::string_view description_bridge = path_attr.role.is_bridge() ? " (bridge)"sv : ""sv; + const std::string instance_change_gcode{this->m_label_objects.maybe_change_instance()}; + std::string travel_instance_change_gcode = m_writer.multiple_extruders ? "" : instance_change_gcode; if (!m_current_layer_first_position) { const Vec3crd point = to_3d(path.front().point, scaled(this->m_last_layer_z)); - gcode += this->travel_to_first_position(point, unscaled(point.z()), this->m_label_objects.maybe_change_instance()); + gcode += this->travel_to_first_position(point, unscaled(point.z()), travel_instance_change_gcode); } else { // go to first point of extrusion path if (!this->last_position) { const double z = this->m_last_layer_z; const std::string comment{"move to print after unknown position"}; gcode += this->retract_and_wipe(); - gcode += m_label_objects.maybe_change_instance(); + gcode += travel_instance_change_gcode; gcode += this->m_writer.travel_to_xy(this->point_to_gcode(path.front().point), comment); gcode += this->m_writer.get_travel_to_z_gcode(z, comment); } else if ( this->last_position != path.front().point) { @@ -3140,8 +3142,10 @@ std::string GCodeGenerator::_extrude( comment += description; comment += description_bridge; comment += " point"; - const std::string travel_gcode{this->travel_to(*this->last_position, path.front().point, path_attr.role, comment, this->m_label_objects.maybe_change_instance())}; + const std::string travel_gcode{this->travel_to(*this->last_position, path.front().point, path_attr.role, comment, travel_instance_change_gcode)}; gcode += travel_gcode; + } else { + travel_instance_change_gcode = ""; } } @@ -3152,6 +3156,9 @@ std::string GCodeGenerator::_extrude( this->m_already_unretracted = true; gcode += "FIRST_UNRETRACT" + this->unretract(); } + if (travel_instance_change_gcode.empty()) { + gcode += instance_change_gcode; + } if (!m_pending_pre_extrusion_gcode.empty()) { // There is G-Code that is due to be inserted before an extrusion starts. Insert it. @@ -3601,10 +3608,10 @@ std::string GCodeGenerator::set_extruder(unsigned int extruder_id, double print_ return gcode; } - // prepend retraction on the current extruder - std::string gcode = this->retract_and_wipe(true); + std::string gcode{this->m_label_objects.maybe_stop_instance()}; - gcode += this->m_label_objects.maybe_stop_instance(); + // prepend retraction on the current extruder + gcode += this->retract_and_wipe(true); // Always reset the extrusion path, even if the tool change retract is set to zero. m_wipe.reset_path(); diff --git a/src/libslic3r/GCode/LabelObjects.cpp b/src/libslic3r/GCode/LabelObjects.cpp index 240e6860d8..caf461a0b0 100644 --- a/src/libslic3r/GCode/LabelObjects.cpp +++ b/src/libslic3r/GCode/LabelObjects.cpp @@ -56,7 +56,7 @@ void LabelObjects::init(const SpanOfConstPtrs& objects, LabelObject for (const PrintObject* po : objects) for (const PrintInstance& pi : po->instances()) model_object_to_print_instances[pi.model_instance->get_object()].emplace_back(&pi); - + // Now go through the map, assign a unique_id to each of the PrintInstances and get the indices of the // respective ModelObject and ModelInstance so we can use them in the tags. This will maintain // indices even in case that some instances are rotated (those end up in different PrintObjects) diff --git a/tests/fff_print/CMakeLists.txt b/tests/fff_print/CMakeLists.txt index 23be4ddedc..da1d8b54c6 100644 --- a/tests/fff_print/CMakeLists.txt +++ b/tests/fff_print/CMakeLists.txt @@ -16,6 +16,7 @@ add_executable(${_TEST_NAME}_tests test_gcode_travels.cpp test_gcodefindreplace.cpp test_gcodewriter.cpp + test_cancel_object.cpp test_layers.cpp test_model.cpp test_multi.cpp diff --git a/tests/fff_print/test_cancel_object.cpp b/tests/fff_print/test_cancel_object.cpp new file mode 100644 index 0000000000..f8c7c72cfb --- /dev/null +++ b/tests/fff_print/test_cancel_object.cpp @@ -0,0 +1,249 @@ +#include +#include +#include + +#include "libslic3r/GCode.hpp" +#include "test_data.hpp" + +using namespace Slic3r; +using namespace Test; + +constexpr bool debug_files{false}; + +std::string remove_object(const std::string &gcode, const int id) { + std::string result{gcode}; + std::string start_token{"M486 S" + std::to_string(id) + "\n"}; + std::string end_token{"M486 S-1\n"}; + + std::size_t start{result.find(start_token)}; + + while (start != std::string::npos) { + std::size_t end_token_start{result.find(end_token, start)}; + std::size_t end{end_token_start + end_token.size()}; + result.replace(start, end - start, ""); + start = result.find(start_token); + } + return result; +} + +TEST_CASE("Remove object sanity check", "[CancelObject]") { + // clang-format off + const std::string gcode{ + "the\n" + "M486 S2\n" + "to delete\n" + "M486 S-1\n" + "kept\n" + "M486 S2\n" + "to also delete\n" + "M486 S-1\n" + "lines\n" + }; + // clang-format on + + const std::string result{remove_object(gcode, 2)}; + + // clang-format off + CHECK(result == std::string{ + "the\n" + "kept\n" + "lines\n" + }); + // clang-format on +} + +void check_retraction(const std::string &gcode, double offset = 0.0) { + GCodeReader parser; + std::map retracted; + unsigned count{0}; + std::set there_is_unretract; + int extruder_id{0}; + + parser.parse_buffer( + gcode, + [&](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + INFO("Line number: " + std::to_string(++count)); + INFO("Extruder id: " + std::to_string(extruder_id)); + if (!line.raw().empty() && line.raw().front() == 'T') { + extruder_id = std::stoi(std::string{line.raw().back()}); + } + if (line.dist_XY(self) < std::numeric_limits::epsilon()) { + if (line.has_e() && line.e() < 0) { + retracted[extruder_id] += line.e(); + } + if (line.has_e() && line.e() > 0) { + INFO("Line: " + line.raw()); + if (there_is_unretract.count(extruder_id) == 0) { + there_is_unretract.insert(extruder_id); + REQUIRE(retracted[extruder_id] + offset + line.e() == Approx(0.0)); + } else { + REQUIRE(retracted[extruder_id] + line.e() == Approx(0.0)); + } + retracted[extruder_id] = 0.0; + } + } + } + ); +} + +void add_object( + Model &model, const std::string &name, const int extruder, const Vec3d &offset = Vec3d::Zero() +) { + std::string extruder_id{std::to_string(extruder)}; + ModelObject *object = model.add_object(); + object->name = name; + ModelVolume *volume = object->add_volume(Test::mesh(Test::TestMesh::cube_20x20x20)); + volume->set_material_id("material" + extruder_id); + volume->translate(offset); + DynamicPrintConfig config; + config.set_deserialize_strict({ + {"extruder", extruder_id}, + }); + volume->config.assign_config(config); + object->add_instance(); + object->ensure_on_bed(); +} + +class CancelObjectFixture +{ +public: + CancelObjectFixture() { + config.set_deserialize_strict({ + {"gcode_flavor", "marlin2"}, + {"gcode_label_objects", "firmware"}, + {"gcode_comments", "1"}, + {"use_relative_e_distances", "1"}, + {"wipe", "0"}, + {"skirts", "0"}, + }); + + add_object(two_cubes, "no_offset_cube", 0); + add_object(two_cubes, "offset_cube", 0, {30.0, 0.0, 0.0}); + + add_object(multimaterial_cubes, "no_offset_cube", 1); + add_object(multimaterial_cubes, "offset_cube", 2, {30.0, 0.0, 0.0}); + + retract_length = config.option("retract_length")->get_at(0); + retract_length_toolchange = config.option("retract_length_toolchange") + ->get_at(0); + } + + DynamicPrintConfig config{Slic3r::DynamicPrintConfig::full_print_config()}; + + Model two_cubes; + Model multimaterial_cubes; + + double retract_length{}; + double retract_length_toolchange{}; +}; + +TEST_CASE_METHOD(CancelObjectFixture, "Single extruder", "[CancelObject]") { + Print print; + print.apply(two_cubes, config); + print.validate(); + const std::string gcode{Test::gcode(print)}; + + if constexpr (debug_files) { + std::ofstream output{"single_extruder_two.gcode"}; + output << gcode; + } + + SECTION("One remaining") { + const std::string removed_object_gcode{remove_object(gcode, 0)}; + REQUIRE(removed_object_gcode.find("M486 S1\n") != std::string::npos); + if constexpr (debug_files) { + std::ofstream output{"single_extruder_one.gcode"}; + output << removed_object_gcode; + } + + check_retraction(removed_object_gcode); + } + + SECTION("All cancelled") { + const std::string removed_all_gcode{remove_object(remove_object(gcode, 0), 1)}; + + // First retraction is not compensated - set offset. + check_retraction(removed_all_gcode, retract_length); + } +} + +TEST_CASE_METHOD(CancelObjectFixture, "Multiple extruders", "[CancelObject]") { + auto wipe_tower = GENERATE(0, 1); + INFO("With wipe tower: " + std::string{wipe_tower == 0 ? "false" : "true"}); + config.set_deserialize_strict( + {{"nozzle_diameter", "0.4,0.4"}, + {"toolchange_gcode", "T[next_extruder]"}, + {"wipe_tower", wipe_tower}, + {"wipe_tower_x", "50.0"}, + {"wipe_tower_y", "50.0"}} + ); + + Print print; + print.apply(multimaterial_cubes, config); + print.validate(); + + const std::string gcode{Test::gcode(print)}; + + if constexpr (debug_files) { + const std::string prefix = wipe_tower == 1 ? "wipe_tower_" : ""; + std::ofstream output{prefix + "multi_extruder_two.gcode"}; + output << gcode; + } + + REQUIRE(gcode.find("T1\n") != std::string::npos); + + SECTION("One remaining") { + const std::string removed_object_gcode{remove_object(gcode, 0)}; + REQUIRE(removed_object_gcode.find("M486 S1\n") != std::string::npos); + if constexpr (debug_files) { + const std::string prefix = wipe_tower == 1 ? "wipe_tower_" : ""; + std::ofstream output{prefix + "multi_extruder_one.gcode"}; + output << removed_object_gcode; + } + + check_retraction(removed_object_gcode); + } + + SECTION("All cancelled") { + const std::string removed_all_gcode{remove_object(remove_object(gcode, 0), 1)}; + if constexpr (debug_files) { + const std::string prefix = wipe_tower == 1 ? "wipe_tower_" : ""; + std::ofstream output{prefix + "multi_extruder_none.gcode"}; + output << removed_all_gcode; + } + + check_retraction(removed_all_gcode); + } +} + +TEST_CASE_METHOD(CancelObjectFixture, "Sequential print", "[CancelObject]") { + config.set_deserialize_strict({{"complete_objects", 1}}); + + Print print; + print.apply(two_cubes, config); + print.validate(); + const std::string gcode{Test::gcode(print)}; + + if constexpr (debug_files) { + std::ofstream output{"sequential_print_two.gcode"}; + output << gcode; + } + + SECTION("One remaining") { + const std::string removed_object_gcode{remove_object(gcode, 0)}; + REQUIRE(removed_object_gcode.find("M486 S1\n") != std::string::npos); + if constexpr (debug_files) { + std::ofstream output{"sequential_print_one.gcode"}; + output << removed_object_gcode; + } + + check_retraction(removed_object_gcode); + } + + SECTION("All cancelled") { + const std::string removed_all_gcode{remove_object(remove_object(gcode, 0), 1)}; + + // First retraction is not compensated - set offset. + check_retraction(removed_all_gcode, retract_length); + } +} From 0207819b57379af565b2891ac001171e85484439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Wed, 28 Feb 2024 16:47:40 +0100 Subject: [PATCH 03/11] Split objects in the middle of travel. This works only for single extruder setups (no toolchanges), but is a step forward --- src/libslic3r/GCode.cpp | 12 ++----- tests/fff_print/test_cancel_object.cpp | 49 -------------------------- 2 files changed, 3 insertions(+), 58 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index bb789ce3a2..3a5fbd5250 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -3124,17 +3124,16 @@ std::string GCodeGenerator::_extrude( const std::string_view description_bridge = path_attr.role.is_bridge() ? " (bridge)"sv : ""sv; const std::string instance_change_gcode{this->m_label_objects.maybe_change_instance()}; - std::string travel_instance_change_gcode = m_writer.multiple_extruders ? "" : instance_change_gcode; if (!m_current_layer_first_position) { const Vec3crd point = to_3d(path.front().point, scaled(this->m_last_layer_z)); - gcode += this->travel_to_first_position(point, unscaled(point.z()), travel_instance_change_gcode); + gcode += this->travel_to_first_position(point, unscaled(point.z()), instance_change_gcode); } else { // go to first point of extrusion path if (!this->last_position) { const double z = this->m_last_layer_z; const std::string comment{"move to print after unknown position"}; gcode += this->retract_and_wipe(); - gcode += travel_instance_change_gcode; + gcode += instance_change_gcode; gcode += this->m_writer.travel_to_xy(this->point_to_gcode(path.front().point), comment); gcode += this->m_writer.get_travel_to_z_gcode(z, comment); } else if ( this->last_position != path.front().point) { @@ -3142,10 +3141,8 @@ std::string GCodeGenerator::_extrude( comment += description; comment += description_bridge; comment += " point"; - const std::string travel_gcode{this->travel_to(*this->last_position, path.front().point, path_attr.role, comment, travel_instance_change_gcode)}; + const std::string travel_gcode{this->travel_to(*this->last_position, path.front().point, path_attr.role, comment, instance_change_gcode)}; gcode += travel_gcode; - } else { - travel_instance_change_gcode = ""; } } @@ -3156,9 +3153,6 @@ std::string GCodeGenerator::_extrude( this->m_already_unretracted = true; gcode += "FIRST_UNRETRACT" + this->unretract(); } - if (travel_instance_change_gcode.empty()) { - gcode += instance_change_gcode; - } if (!m_pending_pre_extrusion_gcode.empty()) { // There is G-Code that is due to be inserted before an extrusion starts. Insert it. diff --git a/tests/fff_print/test_cancel_object.cpp b/tests/fff_print/test_cancel_object.cpp index f8c7c72cfb..3e0b0e03ac 100644 --- a/tests/fff_print/test_cancel_object.cpp +++ b/tests/fff_print/test_cancel_object.cpp @@ -167,55 +167,6 @@ TEST_CASE_METHOD(CancelObjectFixture, "Single extruder", "[CancelObject]") { } } -TEST_CASE_METHOD(CancelObjectFixture, "Multiple extruders", "[CancelObject]") { - auto wipe_tower = GENERATE(0, 1); - INFO("With wipe tower: " + std::string{wipe_tower == 0 ? "false" : "true"}); - config.set_deserialize_strict( - {{"nozzle_diameter", "0.4,0.4"}, - {"toolchange_gcode", "T[next_extruder]"}, - {"wipe_tower", wipe_tower}, - {"wipe_tower_x", "50.0"}, - {"wipe_tower_y", "50.0"}} - ); - - Print print; - print.apply(multimaterial_cubes, config); - print.validate(); - - const std::string gcode{Test::gcode(print)}; - - if constexpr (debug_files) { - const std::string prefix = wipe_tower == 1 ? "wipe_tower_" : ""; - std::ofstream output{prefix + "multi_extruder_two.gcode"}; - output << gcode; - } - - REQUIRE(gcode.find("T1\n") != std::string::npos); - - SECTION("One remaining") { - const std::string removed_object_gcode{remove_object(gcode, 0)}; - REQUIRE(removed_object_gcode.find("M486 S1\n") != std::string::npos); - if constexpr (debug_files) { - const std::string prefix = wipe_tower == 1 ? "wipe_tower_" : ""; - std::ofstream output{prefix + "multi_extruder_one.gcode"}; - output << removed_object_gcode; - } - - check_retraction(removed_object_gcode); - } - - SECTION("All cancelled") { - const std::string removed_all_gcode{remove_object(remove_object(gcode, 0), 1)}; - if constexpr (debug_files) { - const std::string prefix = wipe_tower == 1 ? "wipe_tower_" : ""; - std::ofstream output{prefix + "multi_extruder_none.gcode"}; - output << removed_all_gcode; - } - - check_retraction(removed_all_gcode); - } -} - TEST_CASE_METHOD(CancelObjectFixture, "Sequential print", "[CancelObject]") { config.set_deserialize_strict({{"complete_objects", 1}}); From 8119bb2925a7a9c622c855b92c25baf55d7a1ce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Thu, 29 Feb 2024 13:05:08 +0100 Subject: [PATCH 04/11] Implement cancel object for toolchanges. Toolchages (wipe tower/no wipe tower) are always kept and if there are any toolchanges, the objects are split at the begining of each instance, while the first travel is always part of the "next" instance. --- src/libslic3r/GCode.cpp | 28 +++++++++++++++++--- src/libslic3r/GCode/LabelObjects.cpp | 4 +++ src/libslic3r/GCode/LabelObjects.hpp | 2 ++ src/libslic3r/GCode/WipeTowerIntegration.cpp | 7 +++-- 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 3a5fbd5250..8534d37625 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2181,6 +2181,12 @@ LayerResult GCodeGenerator::process_layer( // Either printing all copies of all objects, or just a single copy of a single object. assert(single_object_instance_idx == size_t(-1) || layers.size() == 1); + const std::vector instances_to_print{sort_print_object_instances(layers, ordering, single_object_instance_idx)}; + const PrintInstance* first_instance{instances_to_print.empty() ? nullptr : &instances_to_print.front().print_object.instances()[instances_to_print.front().instance_id]}; + if (first_instance != nullptr) { + m_label_objects.update(*first_instance); + } + // First object, support and raft layer, if available. const Layer *object_layer = nullptr; const SupportLayer *support_layer = nullptr; @@ -2383,8 +2389,6 @@ LayerResult GCodeGenerator::process_layer( m_avoid_crossing_perimeters.disable_once(); } - std::vector instances_to_print = sort_print_object_instances(layers, ordering, single_object_instance_idx); - // We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature): bool is_anything_overridden = layer_tools.wiping_extrusions().is_anything_overridden(); if (is_anything_overridden) { @@ -2785,6 +2789,10 @@ std::string GCodeGenerator::change_layer( // Increment a progress bar indicator. gcode += m_writer.update_progress(++ m_layer_index, m_layer_count); + if (m_writer.multiple_extruders) { + gcode += m_label_objects.maybe_change_instance(); + } + if (!EXTRUDER_CONFIG(travel_ramping_lift) && EXTRUDER_CONFIG(retract_layer_change)) { gcode += this->retract_and_wipe(); } else if (EXTRUDER_CONFIG(travel_ramping_lift) && !vase_mode){ @@ -3123,7 +3131,17 @@ std::string GCodeGenerator::_extrude( std::string gcode; const std::string_view description_bridge = path_attr.role.is_bridge() ? " (bridge)"sv : ""sv; - const std::string instance_change_gcode{this->m_label_objects.maybe_change_instance()}; + const bool has_active_instance{m_label_objects.has_active_instance()}; + if (m_writer.multiple_extruders && has_active_instance) { + gcode += m_label_objects.maybe_change_instance(); + } + + std::string instance_change_gcode{this->m_label_objects.maybe_change_instance()}; + const std::string delayed_instance_change_gcode{instance_change_gcode}; + if (m_writer.multiple_extruders) { + instance_change_gcode = ""; + } + if (!m_current_layer_first_position) { const Vec3crd point = to_3d(path.front().point, scaled(this->m_last_layer_z)); gcode += this->travel_to_first_position(point, unscaled(point.z()), instance_change_gcode); @@ -3154,6 +3172,10 @@ std::string GCodeGenerator::_extrude( gcode += "FIRST_UNRETRACT" + this->unretract(); } + if (m_writer.multiple_extruders && !has_active_instance) { + gcode += delayed_instance_change_gcode; + } + if (!m_pending_pre_extrusion_gcode.empty()) { // There is G-Code that is due to be inserted before an extrusion starts. Insert it. gcode += m_pending_pre_extrusion_gcode; diff --git a/src/libslic3r/GCode/LabelObjects.cpp b/src/libslic3r/GCode/LabelObjects.cpp index caf461a0b0..dee3759417 100644 --- a/src/libslic3r/GCode/LabelObjects.cpp +++ b/src/libslic3r/GCode/LabelObjects.cpp @@ -143,6 +143,10 @@ std::string LabelObjects::maybe_change_instance() { return ""; } +bool LabelObjects::has_active_instance() { + return this->current_instance != nullptr; +} + std::string LabelObjects::all_objects_header() const { if (m_label_objects_style == LabelObjectsStyle::Disabled) diff --git a/src/libslic3r/GCode/LabelObjects.hpp b/src/libslic3r/GCode/LabelObjects.hpp index 0ef8acb104..a9461ec649 100644 --- a/src/libslic3r/GCode/LabelObjects.hpp +++ b/src/libslic3r/GCode/LabelObjects.hpp @@ -31,6 +31,8 @@ public: std::string maybe_change_instance(); + bool has_active_instance(); + private: struct LabelData { diff --git a/src/libslic3r/GCode/WipeTowerIntegration.cpp b/src/libslic3r/GCode/WipeTowerIntegration.cpp index 3d00a12eae..4172547712 100644 --- a/src/libslic3r/GCode/WipeTowerIntegration.cpp +++ b/src/libslic3r/GCode/WipeTowerIntegration.cpp @@ -58,23 +58,22 @@ 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(); 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.m_label_objects.maybe_stop_instance() + *gcodegen.last_position, xy_point, ExtrusionRole::Mixed, comment, "" ); } else { - gcode += gcodegen.m_label_objects.maybe_stop_instance(); gcode += gcodegen.writer().travel_to_xy(gcodegen.point_to_gcode(xy_point), comment); gcode += gcodegen.writer().get_travel_to_z_gcode(z, comment); } } else { const Vec3crd point = to_3d(xy_point, scaled(z)); - gcode += gcodegen.travel_to_first_position(point, current_z, gcodegen.m_label_objects.maybe_stop_instance()); + gcode += gcodegen.travel_to_first_position(point, current_z, ""); } gcode += gcodegen.unretract(); } else { From 08fa362abdec8c7c89c0c7253efe68d9662255ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Mon, 4 Mar 2024 18:15:32 +0100 Subject: [PATCH 05/11] Fix undefined behaviour in maybe change instance --- src/libslic3r/GCode/LabelObjects.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/GCode/LabelObjects.cpp b/src/libslic3r/GCode/LabelObjects.cpp index dee3759417..a3409819e7 100644 --- a/src/libslic3r/GCode/LabelObjects.cpp +++ b/src/libslic3r/GCode/LabelObjects.cpp @@ -138,7 +138,10 @@ std::string LabelObjects::maybe_stop_instance() { std::string LabelObjects::maybe_change_instance() { if (last_operation_instance != current_instance) { - return this->maybe_stop_instance() + this->maybe_start_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(); } return ""; } From d15e82135e62b9c5389bfb3b49971711c13d6a21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Tue, 5 Mar 2024 12:06:35 +0100 Subject: [PATCH 06/11] Exclude skirt and brim from 'instance' annotation --- src/libslic3r/GCode.cpp | 20 +++++++++++++------- src/libslic3r/GCode/LabelObjects.cpp | 6 +++--- src/libslic3r/GCode/LabelObjects.hpp | 2 +- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 8534d37625..cebf0a59d0 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2181,12 +2181,6 @@ LayerResult GCodeGenerator::process_layer( // Either printing all copies of all objects, or just a single copy of a single object. assert(single_object_instance_idx == size_t(-1) || layers.size() == 1); - const std::vector instances_to_print{sort_print_object_instances(layers, ordering, single_object_instance_idx)}; - const PrintInstance* first_instance{instances_to_print.empty() ? nullptr : &instances_to_print.front().print_object.instances()[instances_to_print.front().instance_id]}; - if (first_instance != nullptr) { - m_label_objects.update(*first_instance); - } - // First object, support and raft layer, if available. const Layer *object_layer = nullptr; const SupportLayer *support_layer = nullptr; @@ -2212,6 +2206,11 @@ LayerResult GCodeGenerator::process_layer( bool first_layer = layer.id() == 0; unsigned int first_extruder_id = layer_tools.extruders.front(); + const std::vector instances_to_print{sort_print_object_instances(layers, ordering, single_object_instance_idx)}; + const PrintInstance* first_instance{instances_to_print.empty() ? nullptr : &instances_to_print.front().print_object.instances()[instances_to_print.front().instance_id]}; + m_label_objects.update(first_instance); + + // Initialize config with the 1st object to be printed at this layer. m_config.apply(layer.object()->config(), true); @@ -2358,6 +2357,9 @@ LayerResult GCodeGenerator::process_layer( } if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) { + gcode += this->m_label_objects.maybe_stop_instance(); + this->m_label_objects.update(nullptr); + const std::pair loops = loops_it->second; this->set_origin(0., 0.); m_avoid_crossing_perimeters.use_external_mp(); @@ -2379,6 +2381,9 @@ LayerResult GCodeGenerator::process_layer( // Extrude brim with the extruder of the 1st region. if (! m_brim_done) { + gcode += this->m_label_objects.maybe_stop_instance(); + this->m_label_objects.update(nullptr); + this->set_origin(0., 0.); m_avoid_crossing_perimeters.use_external_mp(); for (const ExtrusionEntity *ee : print.brim().entities) @@ -2388,6 +2393,7 @@ LayerResult GCodeGenerator::process_layer( // Allow a straight travel move to the first object point. m_avoid_crossing_perimeters.disable_once(); } + this->m_label_objects.update(first_instance); // We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature): bool is_anything_overridden = layer_tools.wiping_extrusions().is_anything_overridden(); @@ -2523,7 +2529,7 @@ void GCodeGenerator::process_layer_single_object( // When starting a new object, use the external motion planner for the first travel move. const Point &offset = print_object.instances()[print_instance.instance_id].shift; - const bool updated{m_label_objects.update(print_instance.print_object.instances()[print_instance.instance_id])}; + const bool updated{m_label_objects.update(&print_instance.print_object.instances()[print_instance.instance_id])}; if (updated) m_avoid_crossing_perimeters.use_external_mp_once(); this->set_origin(unscale(offset)); diff --git a/src/libslic3r/GCode/LabelObjects.cpp b/src/libslic3r/GCode/LabelObjects.cpp index a3409819e7..92e3b1e29c 100644 --- a/src/libslic3r/GCode/LabelObjects.cpp +++ b/src/libslic3r/GCode/LabelObjects.cpp @@ -111,11 +111,11 @@ void LabelObjects::init(const SpanOfConstPtrs& objects, LabelObject } } -bool LabelObjects::update(const PrintInstance &instance) { - if (this->last_operation_instance == &instance) { +bool LabelObjects::update(const PrintInstance *instance) { + if (this->last_operation_instance == instance) { return false; } - this->last_operation_instance = &instance; + this->last_operation_instance = instance; return true; } diff --git a/src/libslic3r/GCode/LabelObjects.hpp b/src/libslic3r/GCode/LabelObjects.hpp index a9461ec649..e8eb2f21a4 100644 --- a/src/libslic3r/GCode/LabelObjects.hpp +++ b/src/libslic3r/GCode/LabelObjects.hpp @@ -23,7 +23,7 @@ public: std::string all_objects_header() const; std::string all_objects_header_singleline_json() const; - bool update(const PrintInstance &instance); + bool update(const PrintInstance *instance); std::string maybe_start_instance(); From cb9aaf8b585766c9b75d617d15d13fd170821ded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Tue, 5 Mar 2024 14:21:27 +0100 Subject: [PATCH 07/11] Allow cancelling toolchanges in multitool sequential print --- src/libslic3r/GCode.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index cebf0a59d0..1fd6506b3b 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1276,6 +1276,8 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail m_second_layer_things_done = false; prev_object = &object; } + + file.write(m_label_objects.maybe_stop_instance()); } else { // Sort layers by Z. // All extrusion moves with the same top layer height are extruded uninterrupted. @@ -2357,7 +2359,9 @@ LayerResult GCodeGenerator::process_layer( } if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) { - gcode += this->m_label_objects.maybe_stop_instance(); + if (!this->m_config.complete_objects.value) { + gcode += this->m_label_objects.maybe_stop_instance(); + } this->m_label_objects.update(nullptr); const std::pair loops = loops_it->second; @@ -2381,7 +2385,10 @@ LayerResult GCodeGenerator::process_layer( // Extrude brim with the extruder of the 1st region. if (! m_brim_done) { - gcode += this->m_label_objects.maybe_stop_instance(); + + if (!this->m_config.complete_objects.value) { + gcode += this->m_label_objects.maybe_stop_instance(); + } this->m_label_objects.update(nullptr); this->set_origin(0., 0.); @@ -3630,7 +3637,10 @@ std::string GCodeGenerator::set_extruder(unsigned int extruder_id, double print_ return gcode; } - std::string gcode{this->m_label_objects.maybe_stop_instance()}; + std::string gcode{}; + if (!this->m_config.complete_objects.value) { + gcode += this->m_label_objects.maybe_stop_instance(); + } // prepend retraction on the current extruder gcode += this->retract_and_wipe(true); From f850433e2bfadcac1ca3cf9115961320c5378006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Tue, 12 Mar 2024 10:39:35 +0100 Subject: [PATCH 08/11] Cancel instance before moving to a next object in sequential print --- src/libslic3r/GCode.cpp | 3 +++ tests/fff_print/test_gcode.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 1fd6506b3b..6697876087 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1247,6 +1247,9 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer m_avoid_crossing_perimeters.use_external_mp_once(); file.write(this->retract_and_wipe()); + file.write(m_label_objects.maybe_stop_instance()); + const double last_z{this->writer().get_position().z()}; + file.write(this->writer().get_travel_to_z_gcode(last_z, "ensure z position")); file.write(this->travel_to(*this->last_position, Point(0, 0), ExtrusionRole::None, "move to origin position for next object", "")); m_enable_cooling_markers = true; // Disable motion planner when traveling to first object point. diff --git a/tests/fff_print/test_gcode.cpp b/tests/fff_print/test_gcode.cpp index a87e6dc317..fc693b29d4 100644 --- a/tests/fff_print/test_gcode.cpp +++ b/tests/fff_print/test_gcode.cpp @@ -149,7 +149,7 @@ TEST_CASE("Extrusion, travels, temeperatures", "[GCode]") { INFO("Unexpected E argument"); CHECK(!line.has_e()); - if (line.has_z()) { + if (line.has_z() && std::abs(line.dist_Z(self)) > 0) { z_moves.emplace_back(line.z()); } if (line.has_x() || line.has_y()) { From 0894ac6d7265e84af567755375e9af65cd4a5655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Wed, 6 Mar 2024 18:03:29 +0100 Subject: [PATCH 09/11] Evaluate maybe change instance lazily --- src/libslic3r/GCode.cpp | 32 +++++++++----------- src/libslic3r/GCode.hpp | 6 ++-- src/libslic3r/GCode/WipeTowerIntegration.cpp | 6 ++-- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 6697876087..df690c0410 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1250,7 +1250,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail file.write(m_label_objects.maybe_stop_instance()); const double last_z{this->writer().get_position().z()}; file.write(this->writer().get_travel_to_z_gcode(last_z, "ensure z position")); - file.write(this->travel_to(*this->last_position, Point(0, 0), ExtrusionRole::None, "move to origin position for next object", "")); + file.write(this->travel_to(*this->last_position, Point(0, 0), ExtrusionRole::None, "move to origin position for next object", [](){return "";})); m_enable_cooling_markers = true; // Disable motion planner when traveling to first object point. m_avoid_crossing_perimeters.disable_once(); @@ -3090,7 +3090,7 @@ void GCodeGenerator::GCodeOutputStream::write_format(const char* format, ...) va_end(args); } -std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const double from_z, const std::string& gcode_to_insert) { +std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const double from_z, const std::function& insert_gcode) { std::string gcode; const Vec3d gcode_point = to_3d(this->point_to_gcode(point.head<2>()), unscaled(point.z())); @@ -3114,7 +3114,7 @@ std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const this->last_position = point.head<2>(); this->writer().update_position(gcode_point); - gcode += gcode_to_insert; + gcode += insert_gcode(); std::string comment{"move to first layer point"}; gcode += this->writer().get_travel_to_xy_gcode(gcode_point.head<2>(), comment); @@ -3152,22 +3152,18 @@ std::string GCodeGenerator::_extrude( gcode += m_label_objects.maybe_change_instance(); } - std::string instance_change_gcode{this->m_label_objects.maybe_change_instance()}; - const std::string delayed_instance_change_gcode{instance_change_gcode}; - if (m_writer.multiple_extruders) { - instance_change_gcode = ""; - } - if (!m_current_layer_first_position) { const Vec3crd point = to_3d(path.front().point, scaled(this->m_last_layer_z)); - gcode += this->travel_to_first_position(point, unscaled(point.z()), instance_change_gcode); + gcode += this->travel_to_first_position(point, unscaled(point.z()), [&](){ + return m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(); + }); } else { // go to first point of extrusion path if (!this->last_position) { const double z = this->m_last_layer_z; const std::string comment{"move to print after unknown position"}; gcode += this->retract_and_wipe(); - gcode += instance_change_gcode; + gcode += m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(); gcode += this->m_writer.travel_to_xy(this->point_to_gcode(path.front().point), comment); gcode += this->m_writer.get_travel_to_z_gcode(z, comment); } else if ( this->last_position != path.front().point) { @@ -3175,7 +3171,9 @@ std::string GCodeGenerator::_extrude( comment += description; comment += description_bridge; comment += " point"; - const std::string travel_gcode{this->travel_to(*this->last_position, path.front().point, path_attr.role, comment, instance_change_gcode)}; + const std::string travel_gcode{this->travel_to(*this->last_position, path.front().point, path_attr.role, comment, [&](){ + return m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(); + })}; gcode += travel_gcode; } } @@ -3189,7 +3187,7 @@ std::string GCodeGenerator::_extrude( } if (m_writer.multiple_extruders && !has_active_instance) { - gcode += delayed_instance_change_gcode; + gcode += m_label_objects.maybe_change_instance(); } if (!m_pending_pre_extrusion_gcode.empty()) { @@ -3406,7 +3404,7 @@ std::string GCodeGenerator::_extrude( std::string GCodeGenerator::generate_travel_gcode( const Points3& travel, const std::string& comment, - const std::string& insert_before_end + const std::function& insert_gcode ) { std::string gcode; @@ -3427,7 +3425,7 @@ std::string GCodeGenerator::generate_travel_gcode( const Vec3d gcode_point{this->point_to_gcode(point)}; if (travel.size() - i <= 2 && !already_inserted) { - gcode += insert_before_end; + gcode += insert_gcode(); already_inserted = true; } @@ -3530,7 +3528,7 @@ std::string GCodeGenerator::travel_to( const Point &end_point, ExtrusionRole role, const std::string &comment, - const std::string &gcode_to_insert + const std::function& insert_gcode ) { // check whether a straight travel move would need retraction @@ -3588,7 +3586,7 @@ std::string GCodeGenerator::travel_to( ) ); - return wipe_retract_gcode + generate_travel_gcode(travel, comment, gcode_to_insert); + return wipe_retract_gcode + generate_travel_gcode(travel, comment, insert_gcode); } std::string GCodeGenerator::retract_and_wipe(bool toolchange) diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 9a96bc72c0..165b974873 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -320,7 +320,7 @@ private: std::string generate_travel_gcode( const Points3& travel, const std::string& comment, - const std::string& insert_before_end + const std::function& insert_gcode ); Polyline generate_travel_xy_path( const Point& start, @@ -333,10 +333,10 @@ private: const Point &end_point, ExtrusionRole role, const std::string &comment, - const std::string &gcode_to_insert + const std::function& insert_gcode ); - std::string travel_to_first_position(const Vec3crd& point, const double from_z, const std::string& gcode_to_insert); + std::string travel_to_first_position(const Vec3crd& point, const double from_z, const std::function& insert_gcode); bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None); diff --git a/src/libslic3r/GCode/WipeTowerIntegration.cpp b/src/libslic3r/GCode/WipeTowerIntegration.cpp index 4172547712..a7d6d0ec70 100644 --- a/src/libslic3r/GCode/WipeTowerIntegration.cpp +++ b/src/libslic3r/GCode/WipeTowerIntegration.cpp @@ -65,7 +65,7 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip 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); @@ -73,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, [](){return "";}); } gcode += gcodegen.unretract(); } else { @@ -269,7 +269,7 @@ std::string WipeTowerIntegration::finalize(GCodeGenerator &gcodegen) if (std::abs(gcodegen.writer().get_position().z() - m_final_purge.print_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", "" + "move to safe place for purging", [](){return "";} ); gcode += append_tcr(gcodegen, m_final_purge, -1); return gcode; From d556969b2b82a3e52dd0fa401624a8a841da2a4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Wed, 6 Mar 2024 18:10:42 +0100 Subject: [PATCH 10/11] Add G92 at the start of every object --- src/libslic3r/GCode.cpp | 12 ++++++------ src/libslic3r/GCode/LabelObjects.cpp | 12 ++++++++---- src/libslic3r/GCode/LabelObjects.hpp | 6 +++--- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index df690c0410..8e0d403742 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2806,7 +2806,7 @@ std::string GCodeGenerator::change_layer( gcode += m_writer.update_progress(++ m_layer_index, m_layer_count); if (m_writer.multiple_extruders) { - gcode += m_label_objects.maybe_change_instance(); + gcode += m_label_objects.maybe_change_instance(m_writer); } if (!EXTRUDER_CONFIG(travel_ramping_lift) && EXTRUDER_CONFIG(retract_layer_change)) { @@ -3149,13 +3149,13 @@ std::string GCodeGenerator::_extrude( const bool has_active_instance{m_label_objects.has_active_instance()}; if (m_writer.multiple_extruders && has_active_instance) { - gcode += m_label_objects.maybe_change_instance(); + gcode += m_label_objects.maybe_change_instance(m_writer); } if (!m_current_layer_first_position) { const Vec3crd point = to_3d(path.front().point, scaled(this->m_last_layer_z)); gcode += this->travel_to_first_position(point, unscaled(point.z()), [&](){ - return m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(); + return m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(m_writer); }); } else { // go to first point of extrusion path @@ -3163,7 +3163,7 @@ std::string GCodeGenerator::_extrude( const double z = this->m_last_layer_z; const std::string comment{"move to print after unknown position"}; gcode += this->retract_and_wipe(); - gcode += m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(); + gcode += m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(m_writer); gcode += this->m_writer.travel_to_xy(this->point_to_gcode(path.front().point), comment); gcode += this->m_writer.get_travel_to_z_gcode(z, comment); } else if ( this->last_position != path.front().point) { @@ -3172,7 +3172,7 @@ std::string GCodeGenerator::_extrude( comment += description_bridge; comment += " point"; const std::string travel_gcode{this->travel_to(*this->last_position, path.front().point, path_attr.role, comment, [&](){ - return m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(); + return m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(m_writer); })}; gcode += travel_gcode; } @@ -3187,7 +3187,7 @@ std::string GCodeGenerator::_extrude( } if (m_writer.multiple_extruders && !has_active_instance) { - gcode += m_label_objects.maybe_change_instance(); + gcode += m_label_objects.maybe_change_instance(m_writer); } if (!m_pending_pre_extrusion_gcode.empty()) { diff --git a/src/libslic3r/GCode/LabelObjects.cpp b/src/libslic3r/GCode/LabelObjects.cpp index 92e3b1e29c..8e34a7e17c 100644 --- a/src/libslic3r/GCode/LabelObjects.cpp +++ b/src/libslic3r/GCode/LabelObjects.cpp @@ -1,6 +1,7 @@ #include "LabelObjects.hpp" #include "ClipperUtils.hpp" +#include "GCode/GCodeWriter.hpp" #include "Model.hpp" #include "Print.hpp" #include "TriangleMeshSlicer.hpp" @@ -119,10 +120,13 @@ bool LabelObjects::update(const PrintInstance *instance) { return true; } -std::string LabelObjects::maybe_start_instance() { +std::string LabelObjects::maybe_start_instance(GCodeWriter& writer) { if (current_instance == nullptr && last_operation_instance != nullptr) { current_instance = last_operation_instance; - return this->start_object(*current_instance, LabelObjects::IncludeName::No); + + std::string result{this->start_object(*current_instance, LabelObjects::IncludeName::No)}; + result += writer.reset_e(true); + return result; } return ""; } @@ -136,12 +140,12 @@ std::string LabelObjects::maybe_stop_instance() { return ""; } -std::string LabelObjects::maybe_change_instance() { +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(); + return stop_instance_gcode + this->maybe_start_instance(writer); } return ""; } diff --git a/src/libslic3r/GCode/LabelObjects.hpp b/src/libslic3r/GCode/LabelObjects.hpp index e8eb2f21a4..ed51a65777 100644 --- a/src/libslic3r/GCode/LabelObjects.hpp +++ b/src/libslic3r/GCode/LabelObjects.hpp @@ -12,7 +12,7 @@ enum GCodeFlavor : unsigned char; enum class LabelObjectsStyle; struct PrintInstance; class Print; - +class GCodeWriter; namespace GCode { @@ -25,11 +25,11 @@ public: bool update(const PrintInstance *instance); - std::string maybe_start_instance(); + std::string maybe_start_instance(GCodeWriter& writer); std::string maybe_stop_instance(); - std::string maybe_change_instance(); + std::string maybe_change_instance(GCodeWriter& writer); bool has_active_instance(); From 1c2e3aed746c7279633e44ee4e1264ebf9ba0e7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Wed, 6 Mar 2024 15:42:31 +0100 Subject: [PATCH 11/11] Fix reset on layer change by keeping it all the time --- src/libslic3r/GCode.cpp | 10 +++++++--- src/libslic3r/GCode.hpp | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 8e0d403742..bbf2870968 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2816,8 +2816,9 @@ std::string GCodeGenerator::change_layer( std::optional{to_3d(this->point_to_gcode(*this->last_position), previous_layer_z)} : std::nullopt; gcode += GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Retraction_Start); - gcode += this->retract_and_wipe(); + gcode += this->retract_and_wipe(false, false); gcode += GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Retraction_End); + gcode += m_writer.reset_e(); } Vec3d new_position = this->writer().get_position(); @@ -3589,7 +3590,7 @@ std::string GCodeGenerator::travel_to( return wipe_retract_gcode + generate_travel_gcode(travel, comment, insert_gcode); } -std::string GCodeGenerator::retract_and_wipe(bool toolchange) +std::string GCodeGenerator::retract_and_wipe(bool toolchange, bool reset_e) { std::string gcode; @@ -3607,7 +3608,10 @@ std::string GCodeGenerator::retract_and_wipe(bool toolchange) methods even if we performed wipe, since this will ensure the entire retraction length is honored in case wipe path was too short. */ gcode += toolchange ? m_writer.retract_for_toolchange() : m_writer.retract(); - gcode += m_writer.reset_e(); + + if (reset_e) { + gcode += m_writer.reset_e(); + } return gcode; } diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 165b974873..5e6df97590 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -340,7 +340,7 @@ private: bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None); - std::string retract_and_wipe(bool toolchange = false); + std::string retract_and_wipe(bool toolchange = false, bool reset_e = true); std::string unretract() { return m_writer.unretract(); } std::string set_extruder(unsigned int extruder_id, double print_z); bool line_distancer_is_required(const std::vector& extruder_ids);