diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp index 2d2cf9896d..da5c7357c1 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp @@ -1660,7 +1660,7 @@ void SkeletalTrapezoidation::propagateBeadingsDownward(edge_t* edge_to_peak, ptr } -SkeletalTrapezoidation::Beading SkeletalTrapezoidation::interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right, coord_t switching_radius) const +SkeletalTrapezoidation::Beading SkeletalTrapezoidation::interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right, coord_t switching_radius) { assert(ratio_left_to_whole >= 0.0 && ratio_left_to_whole <= 1.0); Beading ret = interpolate(left, ratio_left_to_whole, right); @@ -1684,6 +1684,12 @@ SkeletalTrapezoidation::Beading SkeletalTrapezoidation::interpolate(const Beadin { // We cant adjust to fit the next edge because there is no previous one?! return ret; } + // ret follows the thicker of left/right, which can hold fewer insets than left when bead + // count and thickness disagree; skip the adjustment rather than index ret past its end. + if (next_inset_idx >= coord_t(ret.toolpath_locations.size())) + { + return ret; + } assert(next_inset_idx < coord_t(left.toolpath_locations.size())); assert(left.toolpath_locations[next_inset_idx] <= switching_radius); assert(left.toolpath_locations[next_inset_idx + 1] >= switching_radius); @@ -1703,7 +1709,7 @@ SkeletalTrapezoidation::Beading SkeletalTrapezoidation::interpolate(const Beadin } -SkeletalTrapezoidation::Beading SkeletalTrapezoidation::interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right) const +SkeletalTrapezoidation::Beading SkeletalTrapezoidation::interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right) { assert(ratio_left_to_whole >= 0.0 && ratio_left_to_whole <= 1.0); float ratio_right_to_whole = 1.0 - ratio_left_to_whole; diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp index 70c2a7d9a3..d87cd74728 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp @@ -488,7 +488,7 @@ protected: * beads. * \return The beading at the interpolated location. */ - Beading interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right, coord_t switching_radius) const; + static Beading interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right, coord_t switching_radius); /*! * Subroutine of \ref interpolate(const Beading&, Ratio, const Beading&, coord_t) @@ -501,7 +501,7 @@ protected: * \param right One of the beadings to interpolate between. * \return The beading at the interpolated location. */ - Beading interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right) const; + static Beading interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right); /*! * Get the beading at a certain node of the skeletal graph, or create one if diff --git a/tests/libslic3r/test_arachne_walls.cpp b/tests/libslic3r/test_arachne_walls.cpp index 2d8ee3bc03..4ca18400af 100644 --- a/tests/libslic3r/test_arachne_walls.cpp +++ b/tests/libslic3r/test_arachne_walls.cpp @@ -18,6 +18,7 @@ #include #include "libslic3r/Arachne/WallToolPaths.hpp" +#include "libslic3r/Arachne/SkeletalTrapezoidation.hpp" #include "libslic3r/Arachne/utils/ExtrusionLine.hpp" #include "libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.hpp" #include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp" @@ -265,3 +266,46 @@ TEST_CASE("Arachne widening keeps two beads in transition band (#14376)", "[Arac for (const coord_t w : beading.bead_widths) CHECK(w <= inner_width); } + +namespace { +// Exposes the protected static interpolate() for a focused unit test. +struct InterpolateProbe : SkeletalTrapezoidation { + using SkeletalTrapezoidation::interpolate; +}; +} // anonymous namespace + +// interpolate() indexes the merged beading with an index derived from `left`. The merged beading +// follows the thicker of left/right, so when the thicker side has fewer insets the index runs past +// its end. +TEST_CASE("Beading interpolation tolerates a thicker side with fewer insets", "[Arachne][Regression]") { + using Beading = BeadingStrategy::Beading; + + // Thicker side (right) has fewer insets, so the merged beading holds only 2 toolpath locations. + const coord_t w = scaled(0.42); + Beading left; + left.total_thickness = scaled(1.0); + left.bead_widths = { w, w, w, w }; + left.toolpath_locations = { scaled(0.1), scaled(0.3), scaled(0.5), scaled(0.7) }; + left.left_over = 0; + + Beading right; + right.total_thickness = scaled(2.0); + right.bead_widths = { w, w }; + right.toolpath_locations = { scaled(0.1), scaled(0.3) }; + right.left_over = 0; + + // Just past left's location [2] (0.5), so the derived index is 2, past the end of the 2-inset merged beading. + const coord_t switching_radius = scaled(0.6); + + Beading result; + REQUIRE_NOTHROW(result = InterpolateProbe::interpolate(left, 0.5, right, switching_radius)); + + // With the guard the adjustment is skipped, so the result is the plain interpolation. + const Beading expected = InterpolateProbe::interpolate(left, 0.5, right); + REQUIRE(result.toolpath_locations.size() == expected.toolpath_locations.size()); + REQUIRE(result.bead_widths.size() == expected.bead_widths.size()); + for (size_t i = 0; i < expected.toolpath_locations.size(); ++i) { + CHECK(result.toolpath_locations[i] == expected.toolpath_locations[i]); + CHECK(result.bead_widths[i] == expected.bead_widths[i]); + } +}