mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-08 00:01:09 +00:00
Belt printer: no layer changes that print nothing, and a preview that survives them (#16245)
Fixes the preview layer bar on belt prints with several parts along the belt (reported with a cube on filament 1 and a 3DBenchy on filament 2, no purge tower): the top slider layer held nearly the whole print, the slider jumped every other layer through the single-colour stretch before the second part, and with the belt purge tower the whole print greyed out while dragging. ## Cause Since #16236 the slicing frame of a belt object starts at the belt below its leading end, so its first layers are empty. On a single part they carry the brim bands. With several parts along the belt the later parts' empty layers fall between the earlier parts' printing layers and were written to the G-code as layer changes with no moves at all. The preview numbers its layers (`libvgcode::Layers`) from the vertices it is given and expects consecutive ids, so at the first such gap it stopped creating layers and folded everything after it into the last one. ## Fix - `GCode::collect_layers_to_print` drops the belt layers that print nothing (no object, support or brim content): no layer change without moves in the file. - `libvgcode::convert` renumbers the layers consecutively over the moves that exist, so a file with empty layers from any source still previews correctly. - The layer slider labels each belt layer with its print Z (the slicer's layer Z, which increases along the belt) instead of libvgcode's toolpath height, which on a tilted layer is wherever its last extrusion happened to end; the slider assumes the list increases, so the labels showed "0 / max" on alternate layers. The processor reads that print Z from the `;Z:` tag non-BBL printers write (it only knew `; Z_HEIGHT:`), on belt printers only, so nothing changes for other printers. ## Verification - New regression test *Belt G-code has no layer that prints nothing* (two cubes 60 mm apart along the belt): fails on the previous code with one empty layer, passes now. - `fff_print_tests` 356 passed, `libslic3r_tests` 1116 passed; `scripts/clang_tidy_diff.py --base upstream/belt-printer`: no findings. - The reported project sliced through the CLI: 595 layers, none without an extrusion, Z strictly increasing. - Scripted GUI pass on the reported project with and without the purge tower: the slider has one entry per G-code layer, each step shows a thin tilted strip advancing along the belt, the top layer alone is a thin strip, nothing greys out while dragging, the slider opens at the top after slicing, and every label reads the layer number and the print Z matching the G-code's `;Z:` (checked at the top, mid-print and through the two-part stretch); raw-view toggle and slider retention unchanged. Left as is: the lower handle at the bottom still reads `1 / 0.00` rather than the first layer's Z (index correct); pre-existing.
This commit is contained in:
@@ -2440,6 +2440,36 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> 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<coordf_t, std::vector<LayerToPrint>> &group) {
|
||||
return std::none_of(group.second.begin(), group.second.end(), prints_something);
|
||||
}),
|
||||
layers_to_print.end());
|
||||
}
|
||||
|
||||
return layers_to_print;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <cmath>
|
||||
#include <chrono>
|
||||
#include <Eigen/Geometry>
|
||||
@@ -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<unsigned int>::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;
|
||||
|
||||
|
||||
@@ -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<double> m_belt_layer_zs;
|
||||
//BBS: save m_gcode_result as well
|
||||
const GCodeProcessorResult* m_gcode_result;
|
||||
std::array<unsigned int, static_cast<size_t>(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<double> 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<float> zs = m_viewer.get_layers_zs();
|
||||
std::vector<double> ret;
|
||||
std::transform(zs.begin(), zs.end(), std::back_inserter(ret), [](float z) { return static_cast<double>(z); });
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "LibVGCodeWrapper.hpp"
|
||||
|
||||
#include <limits>
|
||||
#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<Slic3r::GCodeProcessorResult::MoveVertex>& 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<uint32_t>::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<uint32_t>::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<uint32_t>(curr.gcode_id), static_cast<uint32_t>(curr.layer_id),
|
||||
static_cast<uint32_t>(curr.gcode_id), layer_id,
|
||||
static_cast<uint8_t>(curr.extruder_id), static_cast<uint8_t>(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<uint32_t>(curr.gcode_id), static_cast<uint32_t>(curr.layer_id),
|
||||
static_cast<uint32_t>(curr.gcode_id), layer_id,
|
||||
static_cast<uint8_t>(curr.extruder_id), static_cast<uint8_t>(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<uint32_t>(curr.gcode_id), static_cast<uint32_t>(curr.layer_id),
|
||||
convert(curr.extrusion_role), curr_type, static_cast<uint32_t>(curr.gcode_id), layer_id,
|
||||
static_cast<uint8_t>(curr.extruder_id), static_cast<uint8_t>(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<uint32_t>(curr.gcode_id), static_cast<uint32_t>(curr.layer_id),
|
||||
static_cast<uint32_t>(curr.gcode_id), layer_id,
|
||||
static_cast<uint8_t>(curr.extruder_id), static_cast<uint8_t>(curr.cp_color_id), curr.time,
|
||||
/* ORCA: Add Pressure Advance visualization support */ 0.0f, curr.pressure_advance,
|
||||
/* ORCA: Add Acceleration visualization support */ curr.acceleration,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user