From 3e1fb510b09d3ce3758b940b1452a1692a4e528d Mon Sep 17 00:00:00 2001 From: "David Eccles (gringer)" Date: Tue, 12 Mar 2024 10:59:57 +1300 Subject: [PATCH] [FEATURE] 3D Honeycomb - switch direction at smallest bridge point, rather than every layer Code was taken without any changes from SoftFever/OrcaSlicer/pull/4425 Original PrusaSlicer PR: prusa3d/PrusaSlicer/pull/6434 --- src/libslic3r/Fill/Fill3DHoneycomb.cpp | 292 +++++++++++++++++-------- 1 file changed, 197 insertions(+), 95 deletions(-) diff --git a/src/libslic3r/Fill/Fill3DHoneycomb.cpp b/src/libslic3r/Fill/Fill3DHoneycomb.cpp index 2f40be1460..b555dbcd56 100644 --- a/src/libslic3r/Fill/Fill3DHoneycomb.cpp +++ b/src/libslic3r/Fill/Fill3DHoneycomb.cpp @@ -20,6 +20,11 @@ namespace Slic3r { +// sign function +template int sgn(T val) { + return (T(0) < val) - (val < T(0)); +} + /* Creates a contiguous sequence of points at a specified height that make up a horizontal slice of the edges of a space filling truncated @@ -30,48 +35,98 @@ and Y axes. Credits: David Eccles (gringer). */ +// triangular wave function +// this has period (gridSize * 2), and amplitude (gridSize / 2), +// with triWave(pos = 0) = 0 +static coordf_t triWave(coordf_t pos, coordf_t gridSize) +{ + float t = (pos / (gridSize * 2.)) + 0.25; // convert relative to grid size + t = t - (int)t; // extract fractional part + return((1. - abs(t * 8. - 4.)) * (gridSize / 4.) + (gridSize / 4.)); +} + +// truncated octagonal waveform, with period and offset +// as per the triangular wave function. The Z position adjusts +// the maximum offset [between -(gridSize / 4) and (gridSize / 4)], with a +// period of (gridSize * 2) and troctWave(Zpos = 0) = 0 +static coordf_t troctWave(coordf_t pos, coordf_t gridSize, coordf_t Zpos) +{ + coordf_t Zcycle = triWave(Zpos, gridSize); + coordf_t perpOffset = Zcycle / 2; + coordf_t y = triWave(pos, gridSize); + return((abs(y) > abs(perpOffset)) ? + (sgn(y) * perpOffset) : + (y * sgn(perpOffset))); +} + +// Identify the important points of curve change within a truncated +// octahedron wave (as waveform fraction t): +// 1. Start of wave (always 0.0) +// 2. Transition to upper "horizontal" part +// 3. Transition from upper "horizontal" part +// 4. Transition to lower "horizontal" part +// 5. Transition from lower "horizontal" part +/* o---o + * / \ + * o/ \ + * \ / + * \ / + * o---o + */ +static std::vector getCriticalPoints(coordf_t Zpos, coordf_t gridSize) +{ + std::vector res = {0.}; + coordf_t perpOffset = abs(triWave(Zpos, gridSize) / 2.); + + coordf_t normalisedOffset = perpOffset / gridSize; + // // for debugging: just generate evenly-distributed points + // for(coordf_t i = 0; i < 2; i += 0.05){ + // res.push_back(gridSize * i); + // } + // note: 0 == straight line + if(normalisedOffset > 0){ + res.push_back(gridSize * (0. + normalisedOffset)); + res.push_back(gridSize * (1. - normalisedOffset)); + res.push_back(gridSize * (1. + normalisedOffset)); + res.push_back(gridSize * (2. - normalisedOffset)); + } + return(res); +} + // Generate an array of points that are in the same direction as the // basic printing line (i.e. Y points for columns, X points for rows) // Note: a negative offset only causes a change in the perpendicular // direction -static std::vector colinearPoints(const coordf_t offset, const size_t baseLocation, size_t gridLength) +static std::vector colinearPoints(const coordf_t Zpos, coordf_t gridSize, std::vector critPoints, + const size_t baseLocation, size_t gridLength) { - const coordf_t offset2 = std::abs(offset / coordf_t(2.)); - std::vector points; - points.push_back(baseLocation - offset2); - for (size_t i = 0; i < gridLength; ++i) { - points.push_back(baseLocation + i + offset2); - points.push_back(baseLocation + i + 1 - offset2); + std::vector points; + points.push_back(baseLocation); + for (coordf_t cLoc = baseLocation; cLoc < gridLength; cLoc+= (gridSize*2)) { + for(size_t pi = 0; pi < critPoints.size(); pi++){ + points.push_back(baseLocation + cLoc + critPoints[pi]); } - points.push_back(baseLocation + gridLength + offset2); - return points; + } + points.push_back(gridLength); + return points; } // Generate an array of points for the dimension that is perpendicular to // the basic printing line (i.e. X points for columns, Y points for rows) -static std::vector perpendPoints(const coordf_t offset, const size_t baseLocation, size_t gridLength) -{ - coordf_t offset2 = offset / coordf_t(2.); - coord_t side = 2 * (baseLocation & 1) - 1; - std::vector points; - points.push_back(baseLocation - offset2 * side); - for (size_t i = 0; i < gridLength; ++i) { - side = 2*((i+baseLocation) & 1) - 1; - points.push_back(baseLocation + offset2 * side); - points.push_back(baseLocation + offset2 * side); - } - points.push_back(baseLocation - offset2 * side); - return points; -} - -// Trims an array of points to specified rectangular limits. Point -// components that are outside these limits are set to the limits. -static inline void trim(Pointfs &pts, coordf_t minX, coordf_t minY, coordf_t maxX, coordf_t maxY) + static std::vector perpendPoints(const coordf_t Zpos, coordf_t gridSize, std::vector critPoints, + size_t baseLocation, size_t gridLength, + size_t offsetBase, coordf_t perpDir) { - for (Vec2d &pt : pts) { - pt.x() = std::clamp(pt.x(), minX, maxX); - pt.y() = std::clamp(pt.y(), minY, maxY); + std::vector points; + points.push_back(offsetBase); + for (coordf_t cLoc = baseLocation; cLoc < gridLength; cLoc+= gridSize*2) { + for(size_t pi = 0; pi < critPoints.size(); pi++){ + coordf_t offset = troctWave(critPoints[pi], gridSize, Zpos); + points.push_back(offsetBase + (offset * perpDir)); } + } + points.push_back(offsetBase); + return points; } static inline Pointfs zip(const std::vector &x, const std::vector &y) @@ -85,68 +140,67 @@ static inline Pointfs zip(const std::vector &x, const std::vector makeNormalisedGrid(coordf_t z, size_t gridWidth, size_t gridHeight, size_t curveType) +// horizontal slice of a truncated regular octahedron. +static std::vector makeActualGrid(coordf_t Zpos, coordf_t gridSize, size_t boundsX, size_t boundsY) { - // offset required to create a regular octagram - coordf_t octagramGap = coordf_t(0.5); - - // sawtooth wave function for range f($z) = [-$octagramGap .. $octagramGap] - coordf_t a = std::sqrt(coordf_t(2.)); // period - coordf_t wave = fabs(fmod(z, a) - a/2.)/a*4. - 1.; - coordf_t offset = wave * octagramGap; - - std::vector points; - if ((curveType & 1) != 0) { - for (size_t x = 0; x <= gridWidth; ++x) { - points.push_back(Pointfs()); - Pointfs &newPoints = points.back(); - newPoints = zip( - perpendPoints(offset, x, gridHeight), - colinearPoints(offset, 0, gridHeight)); - // trim points to grid edges - trim(newPoints, coordf_t(0.), coordf_t(0.), coordf_t(gridWidth), coordf_t(gridHeight)); - if (x & 1) - std::reverse(newPoints.begin(), newPoints.end()); - } + std::vector points; + std::vector critPoints = getCriticalPoints(Zpos, gridSize); + coordf_t zCycle = fmod(Zpos + gridSize/2, gridSize * 2.) / (gridSize * 2.); + bool printVert = zCycle < 0.5; + if (printVert) { + int perpDir = -1; + for (coordf_t x = 0; x <= (boundsX); x+= gridSize, perpDir *= -1) { + points.push_back(Pointfs()); + Pointfs &newPoints = points.back(); + newPoints = zip( + perpendPoints(Zpos, gridSize, critPoints, 0, boundsY, x, perpDir), + colinearPoints(Zpos, gridSize, critPoints, 0, boundsY)); + if (perpDir == 1) + std::reverse(newPoints.begin(), newPoints.end()); } - if ((curveType & 2) != 0) { - for (size_t y = 0; y <= gridHeight; ++y) { - points.push_back(Pointfs()); - Pointfs &newPoints = points.back(); - newPoints = zip( - colinearPoints(offset, 0, gridWidth), - perpendPoints(offset, y, gridWidth)); - // trim points to grid edges - trim(newPoints, coordf_t(0.), coordf_t(0.), coordf_t(gridWidth), coordf_t(gridHeight)); - if (y & 1) - std::reverse(newPoints.begin(), newPoints.end()); - } + } else { + int perpDir = 1; + for (coordf_t y = gridSize; y <= (boundsY); y+= gridSize, perpDir *= -1) { + points.push_back(Pointfs()); + Pointfs &newPoints = points.back(); + newPoints = zip( + colinearPoints(Zpos, gridSize, critPoints, 0, boundsX), + perpendPoints(Zpos, gridSize, critPoints, 0, boundsX, y, perpDir)); + if (perpDir == -1) + std::reverse(newPoints.begin(), newPoints.end()); } - return points; + } + return points; } // Generate a set of curves (array of array of 2d points) that describe a // horizontal slice of a truncated regular octahedron with a specified // grid square size. -static Polylines makeGrid(coord_t z, coord_t gridSize, size_t gridWidth, size_t gridHeight, size_t curveType) +// gridWidth and gridHeight define the width and height of the bounding box respectively +static Polylines makeGrid(coordf_t z, coordf_t gridSize, coordf_t boundWidth, coordf_t boundHeight, bool fillEvenly) { - coord_t scaleFactor = gridSize; - coordf_t normalisedZ = coordf_t(z) / coordf_t(scaleFactor); - std::vector polylines = makeNormalisedGrid(normalisedZ, gridWidth, gridHeight, curveType); - Polylines result; - result.reserve(polylines.size()); - for (std::vector::const_iterator it_polylines = polylines.begin(); it_polylines != polylines.end(); ++ it_polylines) { - result.push_back(Polyline()); - Polyline &polyline = result.back(); - for (Pointfs::const_iterator it = it_polylines->begin(); it != it_polylines->end(); ++ it) - polyline.points.push_back(Point(coord_t((*it)(0) * scaleFactor), coord_t((*it)(1) * scaleFactor))); - } - return result; + std::vector polylines = makeActualGrid(z, gridSize, boundWidth, boundHeight); + Polylines result; + result.reserve(polylines.size()); + for (std::vector::const_iterator it_polylines = polylines.begin(); + it_polylines != polylines.end(); ++ it_polylines) { + result.push_back(Polyline()); + Polyline &polyline = result.back(); + for (Pointfs::const_iterator it = it_polylines->begin(); it != it_polylines->end(); ++ it) + polyline.points.push_back(Point(coord_t((*it)(0)), coord_t((*it)(1)))); + } + return result; } +// FillParams has the following useful information: +// density <0 .. 1> [proportion of space to fill] +// anchor_length [???] +// anchor_length_max [???] +// dont_connect() [avoid connect lines] +// dont_adjust [avoid filling space evenly] +// monotonic [fill strictly left to right] +// complete [complete each loop] + void Fill3DHoneycomb::_fill_surface_single( const FillParams ¶ms, unsigned int thickness_layers, @@ -156,27 +210,75 @@ void Fill3DHoneycomb::_fill_surface_single( { // no rotation is supported for this infill pattern BoundingBox bb = expolygon.contour.bounding_box(); - coord_t distance = coord_t(scale_(this->spacing) / params.density); + + // Note: with equally-scaled X/Y/Z, the pattern will create a vertically-stretched + // truncated octahedron; so Z is pre-adjusted first by scaling by sqrt(2) + coordf_t zScale = sqrt(2); + + // adjustment to account for the additional distance of octagram curves + // note: this only strictly applies for a rectangular area where the total + // Z travel distance is a multiple of the spacing... but it should + // be at least better than the prevous estimate which assumed straight + // lines + // = 4 * integrate(func=4*x(sqrt(2) - 1) + 1, from=0, to=0.25) + // = (sqrt(2) + 1) / 2 [... I think] + // make a first guess at the preferred grid Size + coordf_t gridSize = (scale_(this->spacing) * ((zScale + 1.) / 2.) / params.density); + + // This density calculation is incorrect for many values > 25%, possibly + // due to quantisation error, so this value is used as a first guess, then the + // Z scale is adjusted to make the layer patterns consistent / symmetric + // This means that the resultant infill won't be an ideal truncated octahedron, + // but it should look better than the equivalent quantised version + + coordf_t layerHeight = scale_(thickness_layers); + // ceiling to an integer value of layers per Z + // (with a little nudge in case it's close to perfect) + coordf_t layersPerModule = floor((gridSize * 2) / (zScale * layerHeight) + 0.05); + if(params.density > 0.42){ // exact layer pattern for >42% density + layersPerModule = 2; + // re-adjust the grid size for a partial octahedral path + // (scale of 1.1 guessed based on modeling) + gridSize = (scale_(this->spacing) * 1.1 / params.density); + // re-adjust zScale to make layering consistent + zScale = (gridSize * 2) / (layersPerModule * layerHeight); + } else { + if(layersPerModule < 2){ + layersPerModule = 2; + } + // re-adjust zScale to make layering consistent + zScale = (gridSize * 2) / (layersPerModule * layerHeight); + // re-adjust the grid size to account for the new zScale + gridSize = (scale_(this->spacing) * ((zScale + 1.) / 2.) / params.density); + // re-calculate layersPerModule and zScale + layersPerModule = floor((gridSize * 2) / (zScale * layerHeight) + 0.05); + if(layersPerModule < 2){ + layersPerModule = 2; + } + zScale = (gridSize * 2) / (layersPerModule * layerHeight); + } // align bounding box to a multiple of our honeycomb grid module - // (a module is 2*$distance since one $distance half-module is - // growing while the other $distance half-module is shrinking) - bb.merge(align_to_grid(bb.min, Point(2*distance, 2*distance))); + // (a module is 2*$gridSize since one $gridSize half-module is + // growing while the other $gridSize half-module is shrinking) + bb.merge(align_to_grid(bb.min, Point(gridSize*4, gridSize*4))); // generate pattern - Polylines polylines = makeGrid( - scale_(this->z), - distance, - ceil(bb.size()(0) / distance) + 1, - ceil(bb.size()(1) / distance) + 1, - ((this->layer_id/thickness_layers) % 2) + 1); + Polylines polylines = + makeGrid( + scale_(this->z) * zScale, + gridSize, + bb.size()(0), + bb.size()(1), + !params.dont_adjust); // move pattern in place - for (Polyline &pl : polylines) - pl.translate(bb.min); + for (Polyline &pl : polylines){ + pl.translate(bb.min); + } // clip pattern to boundaries, chain the clipped polylines - polylines = intersection_pl(polylines, expolygon); + polylines = intersection_pl(polylines, to_polygons(expolygon)); // connect lines if needed if (params.dont_connect() || polylines.size() <= 1) @@ -185,4 +287,4 @@ void Fill3DHoneycomb::_fill_surface_single( this->connect_infill(std::move(polylines), expolygon, polylines_out, this->spacing, params); } -} // namespace Slic3r +} // namespace Slic3r \ No newline at end of file