diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 6a0ed586ca..bbf2870968 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,10 @@ 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(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", [](){return "";})); m_enable_cooling_markers = true; // Disable motion planner when traveling to first object point. m_avoid_crossing_perimeters.disable_once(); @@ -1277,6 +1279,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. @@ -1333,6 +1337,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)); @@ -2206,6 +2211,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); @@ -2352,6 +2362,11 @@ LayerResult GCodeGenerator::process_layer( } if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) { + 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; this->set_origin(0., 0.); m_avoid_crossing_perimeters.use_external_mp(); @@ -2373,6 +2388,12 @@ LayerResult GCodeGenerator::process_layer( // Extrude brim with the extruder of the 1st region. if (! m_brim_done) { + + 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.); m_avoid_crossing_perimeters.use_external_mp(); for (const ExtrusionEntity *ee : print.brim().entities) @@ -2382,8 +2403,7 @@ LayerResult GCodeGenerator::process_layer( // Allow a straight travel move to the first object point. m_avoid_crossing_perimeters.disable_once(); } - - std::vector instances_to_print = sort_print_object_instances(layers, ordering, single_object_instance_idx); + 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(); @@ -2507,7 +2527,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; @@ -2518,12 +2538,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 +2722,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) @@ -2788,6 +2805,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(m_writer); + } + 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){ @@ -2795,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(); @@ -3069,7 +3091,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::function& insert_gcode) { std::string gcode; const Vec3d gcode_point = to_3d(this->point_to_gcode(point.head<2>()), unscaled(point.z())); @@ -3093,6 +3115,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 += insert_gcode(); + 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); @@ -3124,15 +3148,23 @@ std::string GCodeGenerator::_extrude( std::string gcode; const std::string_view description_bridge = path_attr.role.is_bridge() ? " (bridge)"sv : ""sv; + 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(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())); + gcode += this->travel_to_first_position(point, unscaled(point.z()), [&](){ + return m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(m_writer); + }); } 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_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) { @@ -3140,7 +3172,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)}; + 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(m_writer); + })}; gcode += travel_gcode; } } @@ -3153,6 +3187,10 @@ std::string GCodeGenerator::_extrude( gcode += "FIRST_UNRETRACT" + this->unretract(); } + if (m_writer.multiple_extruders && !has_active_instance) { + gcode += m_label_objects.maybe_change_instance(m_writer); + } + 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; @@ -3366,7 +3404,8 @@ std::string GCodeGenerator::_extrude( std::string GCodeGenerator::generate_travel_gcode( const Points3& travel, - const std::string& comment + const std::string& comment, + const std::function& insert_gcode ) { std::string gcode; @@ -3381,9 +3420,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_gcode(); + 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 +3525,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::function& insert_gcode ) { // check whether a straight travel move would need retraction @@ -3537,10 +3587,10 @@ std::string GCodeGenerator::travel_to( ) ); - return wipe_retract_gcode + generate_travel_gcode(travel, comment); + 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; @@ -3558,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; } @@ -3589,8 +3642,13 @@ std::string GCodeGenerator::set_extruder(unsigned int extruder_id, double print_ return gcode; } + std::string gcode{}; + if (!this->m_config.complete_objects.value) { + gcode += this->m_label_objects.maybe_stop_instance(); + } + // prepend retraction on the current extruder - std::string gcode = this->retract_and_wipe(true); + 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.hpp b/src/libslic3r/GCode.hpp index 7800103b9b..5e6df97590 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::function& insert_gcode ); Polyline generate_travel_xy_path( const Point& start, @@ -331,14 +332,15 @@ private: const Point &start_point, const Point &end_point, ExtrusionRole role, - const std::string &comment + const std::string &comment, + const std::function& insert_gcode ); - 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::function& insert_gcode); 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); @@ -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..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" @@ -56,7 +57,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) @@ -111,7 +112,47 @@ 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(GCodeWriter& writer) { + if (current_instance == nullptr && last_operation_instance != nullptr) { + current_instance = last_operation_instance; + + std::string result{this->start_object(*current_instance, LabelObjects::IncludeName::No)}; + result += writer.reset_e(true); + return result; + } + return ""; +} + +std::string LabelObjects::maybe_stop_instance() { + if (current_instance != nullptr) { + const std::string result{this->stop_object(*current_instance)}; + current_instance = nullptr; + return result; + } + return ""; +} + +std::string LabelObjects::maybe_change_instance(GCodeWriter& writer) { + if (last_operation_instance != current_instance) { + const std::string stop_instance_gcode{this->maybe_stop_instance()}; + // Be carefull with refactoring: this->maybe_stop_instance() + this->maybe_start_instance() + // may not be evaluated in order. The order is indeed undefined! + return stop_instance_gcode + this->maybe_start_instance(writer); + } + return ""; +} + +bool LabelObjects::has_active_instance() { + return this->current_instance != nullptr; +} std::string LabelObjects::all_objects_header() const { diff --git a/src/libslic3r/GCode/LabelObjects.hpp b/src/libslic3r/GCode/LabelObjects.hpp index 9002655afd..ed51a65777 100644 --- a/src/libslic3r/GCode/LabelObjects.hpp +++ b/src/libslic3r/GCode/LabelObjects.hpp @@ -12,39 +12,52 @@ enum GCodeFlavor : unsigned char; enum class LabelObjectsStyle; struct PrintInstance; class Print; - +class GCodeWriter; 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(GCodeWriter& writer); + + std::string maybe_stop_instance(); + + std::string maybe_change_instance(GCodeWriter& writer); + + bool has_active_instance(); + +private: + struct LabelData + { + const PrintInstance* pi; + std::string name; + std::string center; + std::string polygon; + int unique_id; + }; + enum class IncludeName { No, Yes }; - void init(const 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..a7d6d0ec70 100644 --- a/src/libslic3r/GCode/WipeTowerIntegration.cpp +++ b/src/libslic3r/GCode/WipeTowerIntegration.cpp @@ -58,13 +58,14 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip || will_go_down); // don't dig into the print if (should_travel_to_tower) { const Point xy_point = wipe_tower_point_to_object_point(gcodegen, start_pos); + gcode += gcodegen.m_label_objects.maybe_stop_instance(); gcode += gcodegen.retract_and_wipe(); gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); const std::string comment{"Travel to a Wipe Tower"}; if (gcodegen.m_current_layer_first_position) { if (gcodegen.last_position) { gcode += gcodegen.travel_to( - *gcodegen.last_position, xy_point, ExtrusionRole::Mixed, comment + *gcodegen.last_position, xy_point, ExtrusionRole::Mixed, comment, [](){return "";} ); } else { gcode += gcodegen.writer().travel_to_xy(gcodegen.point_to_gcode(xy_point), comment); @@ -72,7 +73,7 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip } } else { const Vec3crd point = to_3d(xy_point, scaled(z)); - gcode += gcodegen.travel_to_first_position(point, current_z); + gcode += gcodegen.travel_to_first_position(point, current_z, [](){return "";}); } gcode += gcodegen.unretract(); } else { @@ -268,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; 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..3e0b0e03ac --- /dev/null +++ b/tests/fff_print/test_cancel_object.cpp @@ -0,0 +1,200 @@ +#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, "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); + } +} 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()) {