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
+27 -12
View File
@@ -7,6 +7,8 @@
// CuraEngine is released under the terms of the AGPLv3 or higher.
#include "TreeModelVolumes.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/ExPolygon.hpp"
#include "TreeSupportCommon.hpp"
#include "BeltFloorContext.hpp"
@@ -19,13 +21,26 @@
#include "../PrintConfig.hpp"
#include "../Utils.hpp"
#include "../format.hpp"
#include "libslic3r/libslic3r.h"
#include <cstddef>
#include <algorithm>
#include <cassert>
#include <limits>
#include <functional>
#include <chrono>
#include <optional>
#include <numeric>
#include <cmath>
#include <string_view>
#include <boost/log/trivial.hpp>
#include <tbb/parallel_for.h>
#include <tbb/task_group.h>
#include <vector>
#include <unordered_map>
#include <utility>
namespace Slic3r::TreeSupport3D
{
@@ -166,7 +181,7 @@ TreeModelVolumes::TreeModelVolumes(
tbb::parallel_for(tbb::blocked_range<size_t>(num_raft_layers, num_layers, std::min<size_t>(1, std::max<size_t>(16, num_layers / (8 * tbb::this_task_arena::max_concurrency())))),
[&](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx)
outlines[layer_idx] = polygons_simplify(to_polygons(print_object.get_layer(layer_idx - num_raft_layers)->lslices), mesh_settings.resolution, polygons_strictly_simple);
outlines[layer_idx] = polygons_simplify(to_polygons(print_object.get_layer(layer_idx - num_raft_layers)->lslices), mesh_settings.resolution);
});
// Belt floor: pre-compute belt surface polygon per-layer for clipping.
@@ -543,7 +558,7 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
// if a key does not exist when it is accessed it is added!
collision_areas_offsetted[layer_idx] = offset_value == 0 ?
union_(collision_areas) :
offset(union_ex(collision_areas), offset_value, ClipperLib::jtMiter, 1.2);
offset(union_ex(collision_areas), offset_value, jtMiter, 1.2);
if(throw_on_cancel)
throw_on_cancel();
}
@@ -596,11 +611,11 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
// the conditional -0.5 ensures that plastic can never touch on the diagonal
// downward when the z_distance_top_layers = 1. It is assumed to be better to
// not support an overhang<90 degree than to risk fusing to it.
append(collisions, offset(union_ex(collision_areas_original), radius + required_range_x, ClipperLib::jtMiter, 1.2));
append(collisions, offset(union_ex(collision_areas_original), radius + required_range_x, jtMiter, 1.2));
}
if (processing_last_mesh) {
if (layer_idx < int(anti_overhang.size()))
append(collisions, offset(union_ex(anti_overhang[layer_idx]), radius, ClipperLib::jtMiter, 1.2));
append(collisions, offset(union_ex(anti_overhang[layer_idx]), radius, jtMiter, 1.2));
// NOTE: m_belt_floor is NOT added to collision here — branches
// should grow toward the belt and terminate at it, not avoid it.
// Belt floor clipping is done post-generation in organic_draw_branches().
@@ -610,7 +625,7 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
if (processing_last_mesh) {
if (! dst.empty())
collisions = union_(collisions, dst);
dst = polygons_simplify(collisions, min_resolution, polygons_strictly_simple);
dst = polygons_simplify(collisions, min_resolution);
} else
append(dst, std::move(collisions));
if (throw_on_cancel)
@@ -638,7 +653,7 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
if (processing_last_mesh) {
if (! dst.empty())
placable = union_(placable, dst);
dst = polygons_simplify(placable, min_resolution, polygons_strictly_simple);
dst = polygons_simplify(placable, min_resolution);
} else
append(dst, placable);
if (throw_on_cancel)
@@ -687,8 +702,8 @@ void TreeModelVolumes::calculateCollisionHolefree(const std::vector<RadiusLayerP
// this union is important as otherwise holes(in form of lines that will increase to holes in a later step) can get unioned onto the area.
data.emplace_back(RadiusLayerPair(radius, layer_idx), polygons_simplify(
offset(union_ex(this->getCollision(m_increase_until_radius, layer_idx, false)),
5 - increase_radius_ceil, ClipperLib::jtRound, m_min_resolution),
m_min_resolution, polygons_strictly_simple));
5 - increase_radius_ceil, jtRound, m_min_resolution),
m_min_resolution));
if (throw_on_cancel)
throw_on_cancel();
}
@@ -775,10 +790,10 @@ void TreeModelVolumes::calculateAvoidance(const std::vector<RadiusLayerPair> &ke
latest_avoidance = union_(current_layer_collisions,
offset(latest_avoidance,
istep + 1 == move_steps ? - last_move_step : - move_step,
ClipperLib::jtRound, m_min_resolution));
jtRound, m_min_resolution));
if (task.to_model)
latest_avoidance = diff(latest_avoidance, getPlaceableAreas(task.radius, layer_idx, throw_on_cancel));
latest_avoidance = polygons_simplify(latest_avoidance, m_min_resolution, polygons_strictly_simple);
latest_avoidance = polygons_simplify(latest_avoidance, m_min_resolution);
data.emplace_back(RadiusLayerPair{task.radius, layer_idx}, latest_avoidance);
if (throw_on_cancel)
throw_on_cancel();
@@ -901,12 +916,12 @@ void TreeModelVolumes::calculateWallRestrictions(const std::vector<RadiusLayerPa
data[layer_idx - min_layer_bottom] = polygons_simplify(
// radius contains m_current_min_xy_dist_delta already if required
intersection(getCollision(0, layer_idx, false), getCollision(radius, layer_idx - 1, true)),
m_min_resolution, polygons_strictly_simple);
m_min_resolution);
if (! data_min.empty())
data_min[layer_idx - min_layer_bottom] =
polygons_simplify(
intersection(getCollision(0, layer_idx, true), getCollision(radius, layer_idx - 1, true)),
m_min_resolution, polygons_strictly_simple);
m_min_resolution);
if (throw_on_cancel)
throw_on_cancel();
}