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
+96 -45
View File
@@ -1,12 +1,31 @@
#include "TriangleSelector.hpp"
#include "Geometry.hpp"
#include "Model.hpp"
#include "AABBTreeIndirect.hpp"
#include "Point.hpp"
#include "libslic3r.h"
#include "Utils.hpp"
#include "TriangleMesh.hpp"
#include <algorithm>
#include <array>
#include <Eigen/Geometry>
#include <Eigen/Core>
#include <boost/container/small_vector.hpp>
#include <boost/log/trivial.hpp>
#include <cmath>
#include <cassert>
#include <cstddef>
#include <cstring>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <memory>
#include <optional>
#include <tbb/parallel_for.h>
#include <utility>
#include <vector>
#include <queue>
#ifndef NDEBUG
// #define EXPENSIVE_DEBUG_CHECKS
@@ -1519,9 +1538,11 @@ void TriangleSelector::get_facets(std::vector<indexed_triangle_set>& facets_per_
}
}
indexed_triangle_set TriangleSelector::get_facets_strict(EnforcerBlockerType state) const
indexed_triangle_set TriangleSelector::get_facets_strict(EnforcerBlockerType state, std::vector<int> *out_source) const
{
indexed_triangle_set out;
if (out_source)
out_source->clear();
size_t num_vertices = 0;
for (const Vertex &v : m_vertices)
@@ -1535,8 +1556,13 @@ indexed_triangle_set TriangleSelector::get_facets_strict(EnforcerBlockerType sta
out.vertices.emplace_back(v.v);
}
for (int itriangle = 0; itriangle < m_orig_size_indices; ++ itriangle)
for (int itriangle = 0; itriangle < m_orig_size_indices; ++ itriangle) {
this->get_facets_strict_recursive(m_triangles[itriangle], m_neighbors[itriangle], state, out.indices);
// Everything the recursion just appended came from this original triangle, whatever depth it
// was split to. Recording it here keeps the recursive helpers untouched.
if (out_source)
out_source->resize(out.indices.size(), itriangle);
}
for (auto &triangle : out.indices)
for (int i = 0; i < 3; ++ i)
@@ -1778,6 +1804,13 @@ TriangleSelector::TriangleSplittingData TriangleSelector::serialize() const {
return out.data;
}
// A split code keeps the split side (one split) or the kept side (two splits) in its upper two
// bits, where 3 is not a side. The value is ignored for a three-side split.
static bool split_code_valid(int code)
{
return (code & 0b11) == 3 || (code >> 2) != 3;
}
void TriangleSelector::deserialize(const TriangleSplittingData &data,
bool needs_reset,
EnforcerBlockerType max_ebt,
@@ -1812,11 +1845,12 @@ void TriangleSelector::deserialize(const TriangleSplittingData &data,
for (auto [triangle_id, ibit] : data.triangles_to_split) {
assert(triangle_id < int(m_triangles.size()));
assert(ibit < int(data.bitstream.size()));
auto next_nibble = [&data, &ibit = ibit]() {
// Set when the bitstream runs out or holds an impossible split before this triangle's tree is complete.
bool corrupt = false;
auto next_nibble = [&data, &ibit = ibit, &corrupt]() {
int n = 0;
for (int i = 0; i < 4; ++ i)
n |= data.bitstream[ibit ++] << i;
if (! data.read_nibble(ibit, n))
corrupt = true;
return n;
};
// Decode a leaf state stored behind the "11" prefix: one nibble of (state-3) for states
@@ -1835,6 +1869,10 @@ void TriangleSelector::deserialize(const TriangleSplittingData &data,
bool is_split = num_of_children != 0;
// Only valid if not is_split.
auto state = is_split ? EnforcerBlockerType::NONE : ((code & 0b1100) == 0b1100 ? decode_leaf_state() : EnforcerBlockerType(code >> 2));
if (is_split && ! split_code_valid(code))
corrupt = true;
if (corrupt)
break;
// BBS
if (state == to_delete_filament)
@@ -1849,7 +1887,7 @@ void TriangleSelector::deserialize(const TriangleSplittingData &data,
}
// Only valid if is_split.
int special_side = code >> 2;
int special_side = num_of_split_sides == 3 ? 0 : code >> 2;
// Take care of the first iteration separately, so handling of the others is simpler.
if (parents.empty()) {
@@ -1904,47 +1942,55 @@ void TriangleSelector::deserialize(const TriangleSplittingData &data,
if (parents.empty())
break;
}
if (corrupt) {
// Every split above allocated all of its children, so the partial tree unwinds cleanly.
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": malformed paint data, dropping paint of triangle " << triangle_id;
undivide_triangle(triangle_id);
m_triangles[triangle_id].set_state(EnforcerBlockerType::NONE);
}
}
}
void TriangleSelector::TriangleSplittingData::update_used_states(const size_t bitstream_start_idx) {
assert(bitstream_start_idx < this->bitstream.size());
assert(!this->bitstream.empty() && this->bitstream.size() != bitstream_start_idx);
assert((this->bitstream.size() - bitstream_start_idx) % 4 == 0);
bool TriangleSelector::TriangleSplittingData::update_used_states(const size_t bitstream_start_idx) {
int ibit = static_cast<int>(bitstream_start_idx);
uint64_t states = 0;
do {
// Walk one triangle's tree depth-first, counting the nodes still to be read; a split node adds its children.
for (int pending_nodes = 1; pending_nodes > 0; --pending_nodes) {
int code;
if (!this->read_nibble(ibit, code))
return false;
if (this->bitstream.empty() || this->bitstream.size() == bitstream_start_idx)
return;
if (const int num_of_split_sides = code & 0b11; num_of_split_sides != 0) {
if (!split_code_valid(code))
return false;
pending_nodes += num_of_split_sides + 1;
continue;
}
size_t nibble_idx = bitstream_start_idx;
auto read_next_nibble = [&data_bitstream = std::as_const(this->bitstream), &nibble_idx]() -> uint8_t {
assert(nibble_idx + 3 < data_bitstream.size());
uint8_t code = 0;
for (size_t bit_idx = 0; bit_idx < 4; ++bit_idx)
code |= data_bitstream[nibble_idx++] << bit_idx;
return code;
};
while (nibble_idx < this->bitstream.size()) {
const uint8_t code = read_next_nibble();
if (const bool is_split = (code & 0b11) != 0; is_split)
continue;
uint8_t facet_state;
if ((code & 0b1100) == 0b1100) {
// Leaf behind the "11" prefix: one nibble of (state-3), or 0b1111 + (state-18).
const uint8_t nibble = read_next_nibble();
facet_state = nibble == 0b1111 ? uint8_t(read_next_nibble() + 18) : uint8_t(nibble + 3);
} else {
facet_state = code >> 2;
int facet_state = code >> 2;
if (facet_state == 0b11) {
// Leaf behind the "11" prefix: one nibble of (state-3), or 0b1111 + (state-18).
int nibble;
if (!this->read_nibble(ibit, nibble))
return false;
facet_state = nibble + 3;
if (nibble == 0b1111) {
if (!this->read_nibble(ibit, nibble))
return false;
facet_state = nibble + 18;
}
}
states |= uint64_t(1) << facet_state;
}
assert(facet_state < this->used_states.size());
if (facet_state >= this->used_states.size())
continue;
} while (static_cast<size_t>(ibit) < this->bitstream.size());
this->used_states[facet_state] = true;
}
// The leaf encoding tops out at state 33, so every state fits the 64-bit mask.
for (size_t state_idx = 0; state_idx < std::min<size_t>(this->used_states.size(), 64); ++state_idx)
if (states & (uint64_t(1) << state_idx))
this->used_states[state_idx] = true;
return true;
}
// Lightweight variant of deserialization, which only tests whether a face of test_state exists.
@@ -1956,11 +2002,12 @@ bool TriangleSelector::has_facets(const TriangleSplittingData &data, const Enfor
for (const TriangleBitStreamMapping &triangle_id_and_ibit : data.triangles_to_split) {
int ibit = triangle_id_and_ibit.bitstream_start_idx;
assert(ibit < int(data.bitstream.size()));
auto next_nibble = [&data, &ibit = ibit]() {
// Stop reading a triangle whose stream is truncated.
bool truncated = false;
auto next_nibble = [&data, &ibit = ibit, &truncated]() {
int n = 0;
for (int i = 0; i < 4; ++ i)
n |= data.bitstream[ibit ++] << i;
if (! data.read_nibble(ibit, n))
truncated = true;
return n;
};
// < 0 -> negative of a number of children
@@ -1978,6 +2025,8 @@ bool TriangleSelector::has_facets(const TriangleSplittingData &data, const Enfor
};
int state = num_children_or_state();
if (truncated)
continue;
if (state < 0) {
// Root is split.
parents_children.clear();
@@ -1985,6 +2034,8 @@ bool TriangleSelector::has_facets(const TriangleSplittingData &data, const Enfor
do {
if (-- parents_children.back() >= 0) {
int state = num_children_or_state();
if (truncated)
break;
if (state < 0)
// Child is split.
parents_children.emplace_back(- state);