diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 6c3b2c9fd6..69edd964e2 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -1691,9 +1691,11 @@ void TriangleSelector::get_seed_fill_contour_recursive(const int facet_idx, cons TriangleSelector::TriangleSplittingData TriangleSelector::serialize() const { // Each original triangle of the mesh is assigned a number encoding its state - // or how it is split. Each triangle is encoded by 4 bits (xxyy) or 8 bits (zzzzxxyy): + // or how it is split. Each triangle is encoded by 4 bits (xxyy), 8 bits + // (zzzzxxyy), or 12 bits (wwwwzzzzxxyy): // leaf triangle: xx = EnforcerBlockerType (Only values 0, 1, and 2. Value 3 is used as an indicator for additional 4 bits.), yy = 0 - // leaf triangle: xx = 0b11, yy = 0b00, zzzz = EnforcerBlockerType (subtracted by 3) + // leaf triangle: xx = 0b11, yy = 0b00, zzzz = EnforcerBlockerType (subtracted by 3) for states 3..16 + // leaf triangle: xx = 0b11, yy = 0b00, zzzz = 0b1110, wwww = EnforcerBlockerType (subtracted by 17) for states 17..32 // non-leaf: xx = special side, yy = number of split sides // These are bitwise appended and formed into one 64-bit integer. @@ -1736,13 +1738,25 @@ TriangleSelector::TriangleSplittingData TriangleSelector::serialize() const { data.used_states[n] = true; if (n >= 3) { - assert(n <= 16); - if (n <= 16) { - // Store "11" plus 4 bits of (n-3). - data.bitstream.insert(data.bitstream.end(), { true, true }); - n -= 3; + assert(n <= int(EnforcerBlockerType::ExtruderMax)); + + auto push_nibble = [&data = this->data](int value) { for (size_t bit_idx = 0; bit_idx < 4; ++bit_idx) - data.bitstream.push_back(n & (uint64_t(0b0001) << bit_idx)); + data.bitstream.push_back((value & (1 << int(bit_idx))) != 0); + }; + + // Store "11" plus either one nibble (legacy 3..16) or + // an escaped second nibble for 17..32. + data.bitstream.insert(data.bitstream.end(), { true, true }); + if (n <= 16) { + push_nibble(n - 3); + } else { + // 0b1110 marks the extended range 17..32. + constexpr int extended_prefix = 0b1110; + const int encoded = n - 17; + assert(encoded >= 0 && encoded <= 15); + push_nibble(extended_prefix); + push_nibble(encoded); } } else { // Simple case, compatible with PrusaSlicer 2.3.1 and older for storing paint on supports and seams. @@ -1818,8 +1832,19 @@ void TriangleSelector::deserialize(const TriangleSplittingData &data, int num_of_split_sides = code & 0b11; int num_of_children = num_of_split_sides == 0 ? 0 : num_of_split_sides + 1; bool is_split = num_of_children != 0; - // Only valid if not is_split. Value of the second nibble was subtracted by 3, so it is added back. - auto state = is_split ? EnforcerBlockerType::NONE : EnforcerBlockerType((code & 0b1100) == 0b1100 ? next_nibble() + 3 : code >> 2); + // Only valid if not is_split. + auto decode_leaf_state = [&next_nibble](int leaf_code) -> EnforcerBlockerType { + if ((leaf_code & 0b1100) != 0b1100) + return EnforcerBlockerType(leaf_code >> 2); + + const int extended = next_nibble(); + if (extended == 0b1110) + return EnforcerBlockerType(next_nibble() + 17); + + // Legacy path (states 3..16, plus historical fallback for 18). + return EnforcerBlockerType(extended + 3); + }; + auto state = is_split ? EnforcerBlockerType::NONE : decode_leaf_state(code); // BBS if (state == to_delete_filament) @@ -1916,7 +1941,17 @@ void TriangleSelector::TriangleSplittingData::update_used_states(const size_t bi if (const bool is_split = (code & 0b11) != 0; is_split) continue; - const uint8_t facet_state = (code & 0b1100) == 0b1100 ? read_next_nibble() + 3 : code >> 2; + uint8_t facet_state = 0; + if ((code & 0b1100) != 0b1100) { + facet_state = code >> 2; + } else { + const uint8_t extended = read_next_nibble(); + if (extended == 0b1110) + facet_state = read_next_nibble() + 17; + else + facet_state = extended + 3; + } + assert(facet_state < this->used_states.size()); if (facet_state >= this->used_states.size()) continue; @@ -1946,9 +1981,17 @@ bool TriangleSelector::has_facets(const TriangleSplittingData &data, const Enfor auto num_children_or_state = [&next_nibble]() -> int { int code = next_nibble(); int num_of_split_sides = code & 0b11; - return num_of_split_sides == 0 ? - ((code & 0b1100) == 0b1100 ? next_nibble() + 3 : code >> 2) : - - num_of_split_sides - 1; + if (num_of_split_sides != 0) + return - num_of_split_sides - 1; + + if ((code & 0b1100) != 0b1100) + return code >> 2; + + const int extended = next_nibble(); + if (extended == 0b1110) + return next_nibble() + 17; + + return extended + 3; }; int state = num_children_or_state(); diff --git a/tests/libslic3r/test_mixed_filament.cpp b/tests/libslic3r/test_mixed_filament.cpp index 938fbf4040..1ec5afe8c6 100644 --- a/tests/libslic3r/test_mixed_filament.cpp +++ b/tests/libslic3r/test_mixed_filament.cpp @@ -5,6 +5,7 @@ #include "libslic3r/MixedFilament.hpp" #include "libslic3r/PresetBundle.hpp" #include "libslic3r/Print.hpp" +#include "libslic3r/TriangleSelector.hpp" #include "libslic3r/GCode/ToolOrdering.hpp" #include @@ -126,6 +127,21 @@ TEST_CASE("Mixed filament remap follows stable row ids when same-pair rows reord CHECK(remap[4] == new_second_custom_virtual_id); } +TEST_CASE("TriangleSelector round-trips paint states above 16", "[MixedFilament][TriangleSelector]") +{ + TriangleMesh mesh = make_cube(20., 20., 20.); + + TriangleSelector source(mesh); + source.set_facet(0, EnforcerBlockerType::Extruder32); + const TriangleSelector::TriangleSplittingData serialized = source.serialize(); + + REQUIRE(TriangleSelector::has_facets(serialized, EnforcerBlockerType::Extruder32)); + + TriangleSelector restored(mesh); + restored.deserialize(serialized, true, EnforcerBlockerType::ExtruderMax); + CHECK(restored.has_facets(EnforcerBlockerType::Extruder32)); +} + TEST_CASE("Mixed filament remap keeps later painted colors stable when an earlier mixed row is deleted", "[MixedFilament]") { PresetBundle bundle;