Merge upstream/main into belt-printer

Brings belt-printer up to main 4b4a261787. Resolutions:

- G-code header (#15897, #15915): main moved the header, config and
  thumbnail block later in _do_export; write_belt_header() moves with it,
  still after the thumbnails and outside the BTT_TFT gate.
- _extrude: first-layer acceleration keeps the per-path first-layer plane
  test with main's cached nozzle index (#16028); main's set_speed out-param
  form (#16108) everywhere else.
- GCodeWriter (#16108): the arc-to-polyline fallback for machine mappings
  that cannot express G2/G3 now runs in the out-param extrude_arc_to_xy,
  which is the overload GCode calls, and appends to the caller's string.
- GCodeProcessorResult: the belt fields join main's forwarding assign.
- Clipper2 (#15969): belt arrange helpers take Slic3r::Point; the tree
  support join types lose their ClipperLib qualifier.
- CLI arrange (#15837): belt printers still reserve no wipe tower.
- Wipe tower options (#15841): the two new sparse-layer toggles are hidden
  for belt printers like the rest of the tower options.
- Keyboard shortcuts (#15706): main's registry replaces the old key switch;
  the belt view toggle is re-registered in the next commit.
- Print::process: the belt purge-plan undo runs before main's SliceStarted
  event.
- scripts/filament_id_snapshot.json: deleted on main (a77209af8f).
- Includes and appended tests: union of both sides.
This commit is contained in:
harrierpigeon
2026-10-04 19:20:33 -05:00
11471 changed files with 2992690 additions and 2720441 deletions
+57 -69
View File
@@ -1,5 +1,5 @@
#include "../ClipperUtils.hpp"
// #include "../ClipperZUtils.hpp"
#include "../ClipperZUtils.hpp"
#include "../ExtrusionEntityCollection.hpp"
#include "../Layer.hpp"
#include "../Print.hpp"
@@ -7,18 +7,33 @@
#include "../MutablePolygon.hpp"
#include "../Geometry.hpp"
#include "../Point.hpp"
#include "clipper/clipper_z.hpp"
#include <cmath>
#include <boost/container/static_vector.hpp>
#include <boost/log/trivial.hpp>
#include <algorithm>
#include <cstddef>
#include <limits>
#include <memory>
#include <math.h>
#include <initializer_list>
#include <tbb/parallel_for.h>
#include <utility>
#include <vector>
#include <unordered_map>
#include "SupportCommon.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/Slicing.hpp"
#include "libslic3r/ExtrusionEntity.hpp"
#include "libslic3r/Polyline.hpp"
#include "SupportLayer.hpp"
#include "SupportParameters.hpp"
#include "libslic3r/libslic3r.h"
#include "libslic3r/Surface.hpp"
#include "libslic3r/Utils.hpp"
// #define SLIC3R_DEBUG
@@ -39,9 +54,9 @@ namespace Slic3r {
//FIXME this should be dependent on the nozzle diameter!
#define SUPPORT_MATERIAL_MARGIN 1.5
//#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3.
//#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5
#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0.
//#define SUPPORT_SURFACES_OFFSET_PARAMETERS jtMiter, 3.
//#define SUPPORT_SURFACES_OFFSET_PARAMETERS jtMiter, 1.5
#define SUPPORT_SURFACES_OFFSET_PARAMETERS jtSquare, 0.
// Convert some of the intermediate layers into top/bottom interface layers as well as base interface layers.
std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interface_layers(
@@ -282,13 +297,13 @@ SupportGeneratorLayersPtr generate_raft_base(
polygons_append(brim, offset(ex, brim_object_gap));
else {
if (brim_outer)
polygons_append(brim, offset(ex.contour, brim_object_gap, ClipperLib::jtRound, float(scale_(0.1))));
polygons_append(brim, offset(ex.contour, brim_object_gap, jtRound, float(scale_(0.1))));
else
brim.emplace_back(ex.contour);
if (brim_inner) {
Polygons holes = ex.holes;
polygons_reverse(holes);
holes = shrink(holes, brim_object_gap, ClipperLib::jtRound, float(scale_(0.1)));
holes = shrink(holes, brim_object_gap, jtRound, float(scale_(0.1)));
polygons_reverse(holes);
polygons_append(brim, std::move(holes));
} else
@@ -495,32 +510,25 @@ void tree_supports_generate_paths(
// Offset expolygon inside, returns number of expolygons collected (0 or 1).
// Vertices of output paths are marked with Z = source contour index of the expoly.
// Vertices at the intersection of source contours are marked with Z = -1.
auto shrink_expolygon_with_contour_idx = [](const Slic3r::ExPolygon &expoly, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib_Z::Paths &out) -> int
auto shrink_expolygon_with_contour_idx = [](const Slic3r::ExPolygon &expoly, const float delta, JoinType joinType, double miterLimit, ClipperZUtils::ZPaths &out) -> int
{
assert(delta > 0);
auto append_paths_with_z = [](ClipperLib::Paths &src, coord_t contour_idx, ClipperLib_Z::Paths &dst) {
auto append_paths_with_z = [](const Polygons &src, coord_t contour_idx, ClipperZUtils::ZPaths &dst) {
dst.reserve(next_highest_power_of_2(dst.size() + src.size()));
for (const ClipperLib::Path &contour : src) {
ClipperLib_Z::Path tmp;
tmp.reserve(contour.size());
for (const Point &p : contour)
tmp.emplace_back(p.x(), p.y(), contour_idx);
dst.emplace_back(std::move(tmp));
}
for (const Polygon &contour : src)
dst.emplace_back(ClipperZUtils::to_zpath(contour.points, contour_idx));
};
// Oriented CCW, the sign of d alone decides between growing and shrinking.
auto offset_ccw = [joinType, miterLimit](Polygon polygon, float d) {
if (! polygon.is_counter_clockwise())
polygon.reverse();
return offset(polygon, d, joinType, miterLimit);
};
// 1) Offset the outer contour.
ClipperLib_Z::Paths contours;
ClipperZUtils::ZPaths contours;
{
ClipperLib::ClipperOffset co;
if (joinType == jtRound)
co.ArcTolerance = miterLimit;
else
co.MiterLimit = miterLimit;
co.ShortestEdgeLength = double(delta * 0.005);
co.AddPath(expoly.contour.points, joinType, ClipperLib::etClosedPolygon);
ClipperLib::Paths contours_raw;
co.Execute(contours_raw, - delta);
Polygons contours_raw = offset_ccw(expoly.contour, - delta);
if (contours_raw.empty())
// No need to try to offset the holes.
return 0;
@@ -532,24 +540,9 @@ void tree_supports_generate_paths(
append(out, std::move(contours));
} else {
// 2) Offset the holes one by one, collect the offsetted holes.
ClipperLib_Z::Paths holes;
{
for (const Polygon &hole : expoly.holes) {
ClipperLib::ClipperOffset co;
if (joinType == jtRound)
co.ArcTolerance = miterLimit;
else
co.MiterLimit = miterLimit;
co.ShortestEdgeLength = double(delta * 0.005);
co.AddPath(hole.points, joinType, ClipperLib::etClosedPolygon);
ClipperLib::Paths out2;
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
// contours will be CCW oriented even though the input paths are CW oriented.
// Offset is applied after contour reorientation, thus the signum of the offset value is reversed.
co.Execute(out2, delta);
append_paths_with_z(out2, 1 + (&hole - expoly.holes.data()), holes);
}
}
ClipperZUtils::ZPaths holes;
for (const Polygon &hole : expoly.holes)
append_paths_with_z(offset_ccw(hole, delta), 1 + (&hole - expoly.holes.data()), holes);
// 3) Subtract holes from the contours.
if (holes.empty()) {
@@ -558,16 +551,11 @@ void tree_supports_generate_paths(
} else {
// Negative offset. There is a chance, that the offsetted hole intersects the outer contour.
// Subtract the offsetted holes from the offsetted contours.
ClipperLib_Z::Clipper clipper;
clipper.ZFillFunction([](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, ClipperLib_Z::IntPoint &pt) {
//pt.z() = std::max(std::max(e1bot.z(), e1top.z()), std::max(e2bot.z(), e2top.z()));
ClipperZUtils::ZPaths output = ClipperZUtils::clip_zpaths(ctDifference, contours, false, holes,
[](const ClipperZUtils::ZPoint &, const ClipperZUtils::ZPoint &, const ClipperZUtils::ZPoint &, const ClipperZUtils::ZPoint &, ClipperZUtils::ZPoint &pt) {
// Just mark the intersection.
pt.z() = -1;
});
clipper.AddPaths(contours, ClipperLib_Z::ptSubject, true);
clipper.AddPaths(holes, ClipperLib_Z::ptClip, true);
ClipperLib_Z::Paths output;
clipper.Execute(ClipperLib_Z::ctDifference, output, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
if (! output.empty()) {
append(out, std::move(output));
} else {
@@ -584,7 +572,7 @@ void tree_supports_generate_paths(
// Clip the sheath path to avoid the extruder to get exactly on the first point of the loop.
const double clip_length = spacing * 0.15;
const double anchor_length = spacing * 6.;
ClipperLib_Z::Paths anchor_candidates;
ClipperZUtils::ZPaths anchor_candidates;
for (ExPolygon& expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5 * flow.scaled_width()))) {
std::unique_ptr<ExtrusionEntityCollection> eec;
ExPolygons regions_to_draw_inner_wall{expoly};
@@ -610,10 +598,10 @@ void tree_supports_generate_paths(
// First genrate a 2nd perimeter loop as a source for anchor candidates.
// The anchor candidate points are annotated with an index of the source contour or with -1 if on intersection.
anchor_candidates.clear();
shrink_expolygon_with_contour_idx(expoly, flow.scaled_width(), DefaultJoinType, 1.2, anchor_candidates);
shrink_expolygon_with_contour_idx(expoly, flow.scaled_width(), jtMiter, 1.2, anchor_candidates);
// Orient all contours CW.
for (auto &path : anchor_candidates)
if (ClipperLib_Z::Area(path) > 0) std::reverse(path.begin(), path.end());
if (ClipperZUtils::area(path) > 0) std::reverse(path.begin(), path.end());
// Draw the perimeters.
Polylines polylines;
@@ -630,13 +618,13 @@ void tree_supports_generate_paths(
pl.clip_end(clip_length);
if (pl.size() < 2) continue;
// Find the foot of the seam point on anchor_candidates. Only pick an anchor point that was created by offsetting the source contour.
ClipperLib_Z::Path *closest_contour = nullptr;
Vec2d closest_point;
int closest_point_idx = -1;
double closest_point_t = 0.;
double d2min = std::numeric_limits<double>::max();
Vec2d seam_pt = pl.back().cast<double>();
for (ClipperLib_Z::Path &path : anchor_candidates)
ClipperZUtils::ZPath *closest_contour = nullptr;
Vec2d closest_point;
int closest_point_idx = -1;
double closest_point_t = 0.;
double d2min = std::numeric_limits<double>::max();
Vec2d seam_pt = pl.back().cast<double>();
for (ClipperZUtils::ZPath &path : anchor_candidates)
for (int i = 0; i < int(path.size()); ++i) {
int j = next_idx_modulo(i, path);
if (path[i].z() == idx_loop || path[j].z() == idx_loop) {
@@ -664,14 +652,14 @@ void tree_supports_generate_paths(
// Try to cut an anchor from the closest_contour.
// Both closest_contour and pl are CW oriented.
pl.points.emplace_back(closest_point.cast<coord_t>());
const ClipperLib_Z::Path &path = *closest_contour;
double remaining_length = anchor_length - (seam_pt - closest_point).norm();
int i = closest_point_idx;
int j = next_idx_modulo(i, *closest_contour);
Vec2d pi(path[i].x(), path[i].y());
Vec2d pj(path[j].x(), path[j].y());
Vec2d v = pj - pi;
double l = v.norm();
const ClipperZUtils::ZPath &path = *closest_contour;
double remaining_length = anchor_length - (seam_pt - closest_point).norm();
int i = closest_point_idx;
int j = next_idx_modulo(i, *closest_contour);
Vec2d pi(path[i].x(), path[i].y());
Vec2d pj(path[j].x(), path[j].y());
Vec2d v = pj - pi;
double l = v.norm();
if (remaining_length < (1. - closest_point_t) * l) {
// Just trim the current line.
pl.points.emplace_back((closest_point + v * (remaining_length / l)).cast<coord_t>());