diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 8ad30a611a..c96d299211 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2440,6 +2440,36 @@ std::vector>> GCode::collec layers_to_print.emplace_back(std::move(merged)); } + // Belt printers: drop the layers that print nothing at all. An object's + // slicing frame starts at the belt below its leading end, so its first layers + // are empty, and with several objects along the belt those empty layers fall + // between other objects' printing layers. A layer change with no moves is + // noise in the file, and the preview (libvgcode) numbers its layers from the + // moves it sees, so a gap folds every later layer into the one before it. + if (print.config().belt_printer.value) { + auto prints_something = [](const LayerToPrint <p) { + if (ltp.object_layer != nullptr && ltp.object_layer->has_extrusions()) + return true; + if (ltp.support_layer != nullptr && ltp.support_layer->has_extrusions()) + return true; + if (ltp.belt_brim_band != nullptr && ! ltp.belt_brim_band->fills.empty()) + return true; + if (ltp.object_layer != nullptr && ltp.original_object != nullptr && ltp.original_object->has_belt_brim()) { + const auto &by_layer = ltp.original_object->belt_brim_by_layer(); + const size_t id = ltp.object_layer->id(); + if (id < by_layer.size() && ! by_layer[id].empty()) + return true; + } + return false; + }; + layers_to_print.erase( + std::remove_if(layers_to_print.begin(), layers_to_print.end(), + [&prints_something](const std::pair> &group) { + return std::none_of(group.second.begin(), group.second.end(), prints_something); + }), + layers_to_print.end()); + } + return layers_to_print; } diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index deb776bf32..ac5a435bcd 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -4305,6 +4305,15 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers return; } + // ;Z: -- the layer Z tag non-BBL printers write. Only read on a belt printer, + // where the preview labels its layers with it (GCodeViewer::load_as_gcode); + // elsewhere print_z stays unset, as it always was, so nothing downstream of + // it changes for other printers. + if (m_belt_printer && boost::starts_with(comment, "Z:")) { + m_print_z = get_z_height(comment); + return; + } + if (boost::starts_with(comment, " CONFIG_BLOCK_START")) { m_in_config_block = true; return; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 949177d4ec..dba2546f0c 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -87,6 +87,7 @@ #include #include +#include #include #include #include @@ -1635,6 +1636,26 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const m_viewer.reset_default_extrusion_roles_colors(); m_viewer.load(std::move(data)); + // Belt printers: libvgcode labels a layer with the height of its toolpaths, which + // on a tilted layer is wherever its last extrusion happened to end, and the layer + // slider looks its labels and the colour-change ticks up in that list assuming it + // increases. Give it the layers' print Z instead (the slicer's layer Z, which + // increases along the belt), numbered the way libvgcode::convert() numbers the + // layers: consecutively over the moves that exist. + m_belt_layer_zs.clear(); + if (is_belt) { + unsigned int src_layer_id = std::numeric_limits::max(); + for (size_t i = 1; i < gcode_result.moves.size(); ++ i) { + const GCodeProcessorResult::MoveVertex &mv = gcode_result.moves[i]; + if (mv.layer_id != src_layer_id) { + src_layer_id = mv.layer_id; + m_belt_layer_zs.emplace_back(double(mv.print_z)); + } + } + if (m_belt_layer_zs.size() != m_viewer.get_layers_count()) + m_belt_layer_zs.clear(); + } + // #if !VGCODE_ENABLE_COG_AND_TOOL_MARKERS // const size_t vertices_count = m_viewer.get_vertices_count(); // m_cog.reset(); @@ -1905,6 +1926,7 @@ void GCodeViewer::reset() //BBS: should also reset the result id BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": current result id %1% ")%m_last_result_id; m_last_result_id = -1; + m_belt_layer_zs.clear(); //BBS: add only gcode mode m_only_gcode_in_preview = false; diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index e5703939b2..c038aec671 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -201,6 +201,8 @@ private: unsigned int m_last_result_id{ 0 }; // Belt printers: the view the loaded result was converted for (see load_as_gcode). bool m_last_belt_show_designed{ true }; + // Belt printers: the print Z of each viewer layer, in the viewer's layer numbering. + std::vector m_belt_layer_zs; //BBS: save m_gcode_result as well const GCodeProcessorResult* m_gcode_result; std::array(EMoveType::Count)> m_move_type_counts{}; @@ -329,6 +331,10 @@ public: const BoundingBoxf3& get_max_bounding_box() const { return m_max_bounding_box; } const BoundingBoxf3& get_shell_bounding_box() const { return m_shell_bounding_box; } std::vector get_layers_zs() const { + // Belt printers: the layer Z the slider labels and the colour-change ticks + // use is the layer's print Z (see load_as_gcode), not a toolpath height. + if (! m_belt_layer_zs.empty()) + return m_belt_layer_zs; const std::vector zs = m_viewer.get_layers_zs(); std::vector ret; std::transform(zs.begin(), zs.end(), std::back_inserter(ret), [](float z) { return static_cast(z); }); diff --git a/src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.cpp b/src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.cpp index d6f9650972..ea010a41b5 100644 --- a/src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.cpp +++ b/src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.cpp @@ -17,6 +17,8 @@ #include "libslic3r/libslic3r.h" #include "LibVGCodeWrapper.hpp" + +#include #include "libslic3r/Print.hpp" #include "libslic3r/Color.hpp" #include "libslic3r/CustomGCode.hpp" @@ -230,10 +232,22 @@ GCodeInputData convert(const Slic3r::GCodeProcessorResult& result, const std::ve const std::vector& moves = result.moves; ret.vertices.reserve(2 * moves.size()); + // libvgcode numbers its layers from the vertices it is given and expects them to + // arrive one layer after the other with consecutive ids: a layer without any move + // (a belt file can carry layer changes that print nothing) would leave a gap, and + // every later vertex would then be folded into the last layer created. Renumber + // the ids consecutively over the moves that exist. + uint32_t src_layer_id = std::numeric_limits::max(); + uint32_t layer_id = 0; for (size_t i = 1; i < moves.size(); ++i) { const Slic3r::GCodeProcessorResult::MoveVertex& curr = moves[i]; const Slic3r::GCodeProcessorResult::MoveVertex& prev = moves[i - 1]; const EMoveType curr_type = convert(curr.type); + if (curr.layer_id != src_layer_id) { + if (src_layer_id != std::numeric_limits::max()) + ++ layer_id; + src_layer_id = curr.layer_id; + } const EOptionType option_type = move_type_to_option(curr_type); if (option_type == EOptionType::COUNT || option_type == EOptionType::Travels || option_type == EOptionType::Wipes) { if (ret.vertices.empty() || prev.type != curr.type || prev.extrusion_role != curr.extrusion_role @@ -245,7 +259,7 @@ GCodeInputData convert(const Slic3r::GCodeProcessorResult& result, const std::ve #if VGCODE_ENABLE_COG_AND_TOOL_MARKERS const libvgcode::PathVertex vertex = { xform_pos(prev.position), curr.height, curr.width, curr.feedrate, prev.actual_feedrate, curr.mm3_per_mm, curr.fan_speed, curr.temperature, 0.0f, convert(curr.extrusion_role), curr_type, - static_cast(curr.gcode_id), static_cast(curr.layer_id), + static_cast(curr.gcode_id), layer_id, static_cast(curr.extruder_id), static_cast(curr.cp_color_id), { 0.0f, 0.0f }, /* ORCA: Add Pressure Advance visualization support */ 0.0f, curr.pressure_advance, /* ORCA: Add Acceleration visualization support */ curr.acceleration, @@ -253,7 +267,7 @@ GCodeInputData convert(const Slic3r::GCodeProcessorResult& result, const std::ve #else const libvgcode::PathVertex vertex = { xform_pos(prev.position), curr.height, curr.width, curr.feedrate, prev.actual_feedrate, curr.mm3_per_mm, curr.fan_speed, curr.temperature, convert(curr.extrusion_role), curr_type, - static_cast(curr.gcode_id), static_cast(curr.layer_id), + static_cast(curr.gcode_id), layer_id, static_cast(curr.extruder_id), static_cast(curr.cp_color_id), { 0.0f, 0.0f }, /* ORCA: Add Pressure Advance visualization support */ 0.0f, curr.pressure_advance, /* ORCA: Add Acceleration visualization support */ curr.acceleration, @@ -267,7 +281,7 @@ GCodeInputData convert(const Slic3r::GCodeProcessorResult& result, const std::ve const libvgcode::PathVertex vertex = { xform_pos(curr.position), curr.height, curr.width, curr.feedrate, curr.actual_feedrate, curr.mm3_per_mm, curr.fan_speed, curr.temperature, result.filament_densities[curr.extruder_id] * curr.mm3_per_mm * (curr.position - prev.position).norm(), - convert(curr.extrusion_role), curr_type, static_cast(curr.gcode_id), static_cast(curr.layer_id), + convert(curr.extrusion_role), curr_type, static_cast(curr.gcode_id), layer_id, static_cast(curr.extruder_id), static_cast(curr.cp_color_id), curr.time, /* ORCA: Add Pressure Advance visualization support */ 0.0f, curr.pressure_advance, /* ORCA: Add Acceleration visualization support */ curr.acceleration, @@ -275,7 +289,7 @@ GCodeInputData convert(const Slic3r::GCodeProcessorResult& result, const std::ve #else const libvgcode::PathVertex vertex = { xform_pos(curr.position), curr.height, curr.width, curr.feedrate, curr.actual_feedrate, curr.mm3_per_mm, curr.fan_speed, curr.temperature, convert(curr.extrusion_role), curr_type, - static_cast(curr.gcode_id), static_cast(curr.layer_id), + static_cast(curr.gcode_id), layer_id, static_cast(curr.extruder_id), static_cast(curr.cp_color_id), curr.time, /* ORCA: Add Pressure Advance visualization support */ 0.0f, curr.pressure_advance, /* ORCA: Add Acceleration visualization support */ curr.acceleration, diff --git a/tests/fff_print/test_print.cpp b/tests/fff_print/test_print.cpp index 7185b1eae5..0f1d33c67f 100644 --- a/tests/fff_print/test_print.cpp +++ b/tests/fff_print/test_print.cpp @@ -1244,6 +1244,68 @@ TEST_CASE("Organic tree supports place a support blocker at its own height above CHECK(collides(last + num_raft)); } +// Two parts along the belt: the second part's slicing frame starts at the belt +// below its leading end, so its first layers are empty and interleave with the +// first part's printing layers. Those must not reach the G-code as layer changes +// that print nothing: the preview numbers its layers from the moves it sees, and +// a gap folded every later layer into the one before it. +TEST_CASE("Belt G-code has no layer that prints nothing", "[Print][belt][GCode][Regression]") +{ + DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "belt_printer", 1 }, + { "belt_slice_rotation", "x" }, + { "belt_slice_rotation_angle", 45 }, + { "gcode_remap_x", "rev_x" }, + { "gcode_remap_y", "pos_z" }, + { "gcode_remap_z", "pos_y" }, + { "layer_height", 0.2 }, + { "initial_layer_print_height", 0.2 }, + { "skirt_loops", 0 }, + { "z_hop", 0 }, + { "brim_type", "outer_only" }, + { "brim_width", 4 }, + { "machine_start_gcode", "T[initial_tool]\n" }, + { "layer_change_gcode", "G92 E0\n" }, + }); + Print print; + Model model; + TriangleMesh cube_a(its_make_cube(20., 20., 20.)); + TriangleMesh cube_b(its_make_cube(20., 20., 20.)); + init_print({ cube_a, cube_b }, print, model, config); + // 60 mm apart along the belt: the second cube's lead-in layers fall among the + // first cube's layers. + model.objects[0]->instances.front()->set_offset(Vec3d(50., 40., 0.)); + model.objects[1]->instances.front()->set_offset(Vec3d(50., 100., 0.)); + print.apply(model, config); + print.set_status_silent(); + const std::string gc = gcode(print); + REQUIRE(! gc.empty()); + + size_t layers = 0, empty = 0, total_header = 0; + bool extruded = true; // before the first layer change + std::istringstream in(gc); + std::string line; + auto close_layer = [&]() { if (! extruded) ++ empty; }; + while (std::getline(in, line)) { + if (line.rfind(";LAYER_CHANGE", 0) == 0) { + close_layer(); + ++ layers; + extruded = false; + } else if (line.rfind("; total layer number: ", 0) == 0) { + total_header = size_t(std::atoi(line.c_str() + 22)); + } else if (! extruded && line.rfind("G1 ", 0) == 0 && line.find('E') != std::string::npos + && (line.find('X') != std::string::npos || line.find('Y') != std::string::npos)) { + extruded = true; + } + } + close_layer(); + INFO("layers " << layers << ", header " << total_header << ", layers without extrusion " << empty); + CHECK(layers > 150); // both cubes, 141 layers each, overlapping along the belt + CHECK(empty == 0); + CHECK(total_header == layers); +} + // A part with an overhang on its LEADING side (the end that prints first) needs // supports below the object's own lowest slicing layer: the belt under that overhang // is reached before the object's first contact with it, so the support layers sit at