Belt printer: address the review on #14394

Code review items (raistlin7447):

1. PrintObject::slice() zeroes m_belt_min_z, m_belt_global_z_offset and
   m_belt_global_xy_correction before slicing. They were only written in belt
   mode, so a project switched to a normal printer, or whose tilt axis was set
   to None, kept the old offsets and shifted the adaptive infill octree and the
   organic support layers by them.
2. TreeModelVolumes shifts the support blockers into the raft-offset index
   space; a test now pins the index the blocker lands on.
3. The final-alignment clamp in libnest2d is opt-in (NfpPConfig::clamp_to_bin)
   and arrange sets it for belt printers only. Printers with an off-centre
   best_object_pos keep their alignment; a flat-bed test pins that.
4. The preview's belt view follows the loaded G-code, not the selected printer:
   GCodeProcessor carries the file's belt keys (and, for a belt file, its bed)
   into export_config_for_render(), and GCodeViewer enables the belt view from
   the header tilt.
5. belt_shift_layer_grid() also shifts the cached belt floor and the global Z
   offset, so a support-only or brim-only change after the purge-prism snap
   matches a fresh slice.
6. update_print_fff_config() resets raft_layers and draft_shield on a belt
   printer instead of only greying out the fields Print::validate() rejects.
7. GCodeWriter takes a first-layer point test instead of the FirstLayerPlane;
   GCode installs one that measures from the belt surface, like its
   extrusions, so the first-layer travel speed and the second-layer
   temperature change no longer depend on the gcode_remap_* convention.
8. belt_brim_clip_leading_edge() is exported and called by both the generator
   and the test.
9. Both phong.vs shaders use slope.up_direction for the overhang highlight.

The pre-slice and G-code axis remaps are gated on belt_printer through
BeltTransformPipeline::axis_remap_enabled(), so belt keys left in a profile
cannot change a non-belt print.

Tests requested in the review: belt-only keys at non-default values leave
non-belt G-code unchanged; switching a sliced project from belt to non-belt
(and tilt axis None) matches a fresh slice; a support-only change on a belt
purge print matches a fresh slice; non-belt start G-code moves keep the
first-layer Z in the processor; the belt brim's segment count catches a band
emitted twice.

The belt-to-non-belt test exposed an unrelated gap: invalidate_step(posSlice)
re-invalidated posSupportMaterial but not posSimplifySupportPath, so after
any re-slice the regenerated support paths were exported unsimplified.
posSimplifySupportPath is now in that list.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
harrierpigeon
2026-10-05 23:06:01 -05:00
co-authored by Claude Fable 5.1
parent a3dea04ade
commit 61a0db4a87
24 changed files with 680 additions and 95 deletions
+46
View File
@@ -6,6 +6,7 @@
#include "test_utils.hpp"
#include <cstddef>
#include <fstream>
#include <string>
#include <catch2/catch_test_macros.hpp>
@@ -47,3 +48,48 @@ TEST_CASE("The belt header's angle marks G-code as belt G-code", "[GCodeProcesso
"; CONFIG_BLOCK_START\n; belt_printer = 1\n; belt_slice_rotation_angle = -45\n; CONFIG_BLOCK_END\n";
CHECK_THAT(processed_belt_tilt(gcode), WithinAbs(45., 1e-6));
}
TEST_CASE("Non-belt start G-code moves keep the first-layer Z in the processor", "[GCodeProcessor]")
{
// The belt path (GCodeWriter tests: "start-gcode prepare-stage moves keep their real Z")
// stores the real Z of a move inside the start G-code. Every other printer must keep
// the historical behaviour: a prepare-stage move is pinned to the first-layer height
// so the preview does not draw the start sequence's travel. The gate is the belt
// header, so a file without one, whatever its config block says, takes this path.
struct BBLPrinterGuard {
bool prev = GCodeProcessor::s_IsBBLPrinter;
BBLPrinterGuard() { GCodeProcessor::s_IsBBLPrinter = false; }
~BBLPrinterGuard() { GCodeProcessor::s_IsBBLPrinter = prev; }
} bbl_guard;
const std::string gcode =
"G90\n"
"G21\n"
"M83\n"
";TYPE:Custom\n"
"G1 E-1.5 F2100\n"
"G1 X45 Y0.3 Z50 F12000\n" // prepare-stage travel to a high Z
"G1 E1.5 F1800\n"
";TYPE:Outer wall\n"
"G1 X46 Y0.3 Z50 E0.05\n"
"; CONFIG_BLOCK_START\n; belt_printer = 0\n; belt_slice_rotation_angle = 45\n; CONFIG_BLOCK_END\n";
GCodeProcessor processor;
FullPrintConfig config;
config.initial_layer_print_height.value = 0.3;
processor.apply_config(config);
processor.process_buffer(gcode);
const GCodeProcessorResult &result = processor.get_result();
REQUIRE_THAT(result.belt_tilt_angle, WithinAbs(0., 1e-6));
size_t first_extrude = result.moves.size();
for (size_t i = 0; i < result.moves.size(); ++i)
if (result.moves[i].type == EMoveType::Extrude) { first_extrude = i; break; }
REQUIRE(first_extrude < result.moves.size());
REQUIRE(first_extrude > 0);
// The extrusion keeps its real Z; the prepare-stage move before it is pinned to the
// first-layer height.
CHECK_THAT(result.moves[first_extrude].position.z(), WithinAbs(50., 1e-3));
CHECK_THAT(result.moves[first_extrude - 1].position.z(), WithinAbs(0.3, 1e-3));
}