#include #include "libslic3r/TriangleSelector.hpp" #include "libslic3r/TriangleMesh.hpp" using namespace Slic3r; // A sphere gives well over ExtruderMax original facets, so every extruder state can be assigned // to a facet of its own without any splitting getting in the way. static TriangleMesh test_mesh() { return make_sphere(5., 2 * PI / 24); } // Read the nibble_idx-th 4-bit group of a serialized bitstream, least significant bit first. static int nibble_at(const std::vector &bitstream, size_t nibble_idx) { int n = 0; for (size_t bit = 0; bit < 4; ++bit) n |= int(bitstream[nibble_idx * 4 + bit]) << bit; return n; } TEST_CASE("Every extruder state survives a serialize/deserialize round trip", "[TriangleSelector]") { const TriangleMesh mesh = test_mesh(); const int max_state = int(EnforcerBlockerType::ExtruderMax); REQUIRE(int(mesh.its.indices.size()) >= max_state); TriangleSelector selector(mesh); for (int state = 1; state <= max_state; ++state) selector.set_facet(state - 1, EnforcerBlockerType(state)); TriangleSelector restored(mesh); restored.deserialize(selector.serialize()); for (int state = 1; state <= max_state; ++state) { INFO("Extruder " << state); REQUIRE(restored.has_facets(EnforcerBlockerType(state))); REQUIRE(restored.num_facets(EnforcerBlockerType(state)) == 1); } } TEST_CASE("Serialized data reports the extruder states it uses", "[TriangleSelector]") { const TriangleMesh mesh = test_mesh(); TriangleSelector selector(mesh); selector.set_facet(0, EnforcerBlockerType::Extruder16); selector.set_facet(1, EnforcerBlockerType::Extruder32); const TriangleSelector::TriangleSplittingData data = selector.serialize(); REQUIRE(data.used_states.size() == size_t(EnforcerBlockerType::ExtruderMax) + 1); REQUIRE(data.used_states[size_t(EnforcerBlockerType::Extruder16)]); REQUIRE(data.used_states[size_t(EnforcerBlockerType::Extruder32)]); REQUIRE_FALSE(data.used_states[size_t(EnforcerBlockerType::Extruder17)]); SECTION("used_states recomputed from the bitstream agrees") { TriangleSelector::TriangleSplittingData recomputed = data; recomputed.reset_used_states(); recomputed.update_used_states(0); REQUIRE(recomputed.used_states == data.used_states); } SECTION("has_facets on the raw data agrees") { REQUIRE(TriangleSelector::has_facets(data, EnforcerBlockerType::Extruder32)); REQUIRE_FALSE(TriangleSelector::has_facets(data, EnforcerBlockerType::Extruder17)); } } // States 3..17 must keep the pre-existing encoding ("11" prefix plus one nibble of state-3) so // projects written by older builds stay readable and newly written ones stay readable by them. TEST_CASE("Extruder states up to 17 keep the single-nibble encoding", "[TriangleSelector]") { const int state = GENERATE(3, 8, 16, 17); TriangleSelector selector(test_mesh()); selector.set_facet(0, EnforcerBlockerType(state)); const std::vector bitstream = selector.serialize().bitstream; INFO("Extruder " << state); // Two nibbles: the "11"-prefixed leaf code, then the state itself. REQUIRE(bitstream.size() == 8); REQUIRE(nibble_at(bitstream, 0) == 0b1100); REQUIRE(nibble_at(bitstream, 1) == state - 3); } // States 18 and above set the state nibble to 0b1111 and carry (state-18) in one more nibble. TEST_CASE("Extruder states above 17 are encoded in a second nibble", "[TriangleSelector]") { const int state = GENERATE(18, 25, 32); TriangleSelector selector(test_mesh()); selector.set_facet(0, EnforcerBlockerType(state)); const std::vector bitstream = selector.serialize().bitstream; INFO("Extruder " << state); REQUIRE(bitstream.size() == 12); REQUIRE(nibble_at(bitstream, 0) == 0b1100); REQUIRE(nibble_at(bitstream, 1) == 0b1111); REQUIRE(nibble_at(bitstream, 2) == state - 18); } // Model.cpp writes these hex strings into the 3MF for colored mesh imports; the selector must // decode exactly the states CONST_FILAMENTS assigns to them. TEST_CASE("Extruder states match the CONST_FILAMENTS hex encoding", "[TriangleSelector]") { struct Case { const char *hex; int state; }; const auto c = GENERATE(values({ {"8", 2}, {"0C", 3}, {"DC", 16}, {"EC", 17}, {"0FC", 18}, {"EFC", 32}, })); // get_triangle_as_string emits the nibbles most significant first, so read the hex backwards. const std::string hex = c.hex; std::vector bitstream; for (auto it = hex.rbegin(); it != hex.rend(); ++it) { const int nibble = *it >= 'A' ? (*it - 'A' + 10) : (*it - '0'); for (int bit = 0; bit < 4; ++bit) bitstream.push_back((nibble >> bit) & 1); } TriangleSelector::TriangleSplittingData data; data.triangles_to_split.emplace_back(0, 0); data.bitstream = bitstream; INFO("Hex " << c.hex << " -> extruder " << c.state); REQUIRE(TriangleSelector::has_facets(data, EnforcerBlockerType(c.state))); }