ENH: 3D Honeycomb

Cherry-picks new 3D Honeycomb from Orca Slicer by David Eccles (gringer).
jira: 6697

Orignal commit message:
3D Honeycomb - switch direction at smallest bridge point, rather than every layer (#4425)

Co-authored-by: SoftFever <softfeverever@gmail.com>
Change-Id: Ida2e5b76a7b906be21045e053200519af1bd9489
(cherry picked from commit a9f521c37e04a0cf404184848aa738b8a6043f87)
This commit is contained in:
David Eccles (gringer) 2024-03-17 03:53:36 +13:00 committed by Lane.Wei
parent e2e82dd2ad
commit 0f986b4ffe
2 changed files with 197 additions and 98 deletions

View File

@ -6,6 +6,11 @@
namespace Slic3r { namespace Slic3r {
// sign function
template <typename T> int sgn(T val) {
return (T(0) < val) - (val < T(0));
}
/* /*
Creates a contiguous sequence of points at a specified height that make Creates a contiguous sequence of points at a specified height that make
up a horizontal slice of the edges of a space filling truncated up a horizontal slice of the edges of a space filling truncated
@ -16,48 +21,98 @@ and Y axes.
Credits: David Eccles (gringer). 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<coordf_t> getCriticalPoints(coordf_t Zpos, coordf_t gridSize)
{
std::vector<coordf_t> 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 // 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) // basic printing line (i.e. Y points for columns, X points for rows)
// Note: a negative offset only causes a change in the perpendicular // Note: a negative offset only causes a change in the perpendicular
// direction // direction
static std::vector<coordf_t> colinearPoints(const coordf_t offset, const size_t baseLocation, size_t gridLength) static std::vector<coordf_t> colinearPoints(const coordf_t Zpos, coordf_t gridSize, std::vector<coordf_t> critPoints,
const size_t baseLocation, size_t gridLength)
{ {
const coordf_t offset2 = std::abs(offset / coordf_t(2.)); std::vector<coordf_t> points;
std::vector<coordf_t> points; points.push_back(baseLocation);
points.push_back(baseLocation - offset2); for (coordf_t cLoc = baseLocation; cLoc < gridLength; cLoc+= (gridSize*2)) {
for (size_t i = 0; i < gridLength; ++i) { for(size_t pi = 0; pi < critPoints.size(); pi++){
points.push_back(baseLocation + i + offset2); points.push_back(baseLocation + cLoc + critPoints[pi]);
points.push_back(baseLocation + i + 1 - offset2);
} }
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 // 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) // the basic printing line (i.e. X points for columns, Y points for rows)
static std::vector<coordf_t> perpendPoints(const coordf_t offset, const size_t baseLocation, size_t gridLength) static std::vector<coordf_t> perpendPoints(const coordf_t Zpos, coordf_t gridSize, std::vector<coordf_t> critPoints,
size_t baseLocation, size_t gridLength,
size_t offsetBase, coordf_t perpDir)
{ {
coordf_t offset2 = offset / coordf_t(2.); std::vector<coordf_t> points;
coord_t side = 2 * (baseLocation & 1) - 1; points.push_back(offsetBase);
std::vector<coordf_t> points; for (coordf_t cLoc = baseLocation; cLoc < gridLength; cLoc+= gridSize*2) {
points.push_back(baseLocation - offset2 * side); for(size_t pi = 0; pi < critPoints.size(); pi++){
for (size_t i = 0; i < gridLength; ++i) { coordf_t offset = troctWave(critPoints[pi], gridSize, Zpos);
side = 2*((i+baseLocation) & 1) - 1; points.push_back(offsetBase + (offset * perpDir));
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)
{
for (Vec2d &pt : pts) {
pt.x() = std::clamp(pt.x(), minX, maxX);
pt.y() = std::clamp(pt.y(), minY, maxY);
} }
}
points.push_back(offsetBase);
return points;
} }
static inline Pointfs zip(const std::vector<coordf_t> &x, const std::vector<coordf_t> &y) static inline Pointfs zip(const std::vector<coordf_t> &x, const std::vector<coordf_t> &y)
@ -71,68 +126,67 @@ static inline Pointfs zip(const std::vector<coordf_t> &x, const std::vector<coor
} }
// Generate a set of curves (array of array of 2d points) that describe a // Generate a set of curves (array of array of 2d points) that describe a
// horizontal slice of a truncated regular octahedron with edge length 1. // horizontal slice of a truncated regular octahedron.
// curveType specifies which lines to print, 1 for vertical lines static std::vector<Pointfs> makeActualGrid(coordf_t Zpos, coordf_t gridSize, size_t boundsX, size_t boundsY)
// (columns), 2 for horizontal lines (rows), and 3 for both.
static std::vector<Pointfs> makeNormalisedGrid(coordf_t z, size_t gridWidth, size_t gridHeight, size_t curveType)
{ {
// offset required to create a regular octagram std::vector<Pointfs> points;
coordf_t octagramGap = coordf_t(0.5); std::vector<coordf_t> critPoints = getCriticalPoints(Zpos, gridSize);
coordf_t zCycle = fmod(Zpos + gridSize/2, gridSize * 2.) / (gridSize * 2.);
// sawtooth wave function for range f($z) = [-$octagramGap .. $octagramGap] bool printVert = zCycle < 0.5;
coordf_t a = std::sqrt(coordf_t(2.)); // period if (printVert) {
coordf_t wave = fabs(fmod(z, a) - a/2.)/a*4. - 1.; int perpDir = -1;
coordf_t offset = wave * octagramGap; for (coordf_t x = 0; x <= (boundsX); x+= gridSize, perpDir *= -1) {
points.push_back(Pointfs());
std::vector<Pointfs> points; Pointfs &newPoints = points.back();
if ((curveType & 1) != 0) { newPoints = zip(
for (size_t x = 0; x <= gridWidth; ++x) { perpendPoints(Zpos, gridSize, critPoints, 0, boundsY, x, perpDir),
points.push_back(Pointfs()); colinearPoints(Zpos, gridSize, critPoints, 0, boundsY));
Pointfs &newPoints = points.back(); if (perpDir == 1)
newPoints = zip( std::reverse(newPoints.begin(), newPoints.end());
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());
}
} }
if ((curveType & 2) != 0) { } else {
for (size_t y = 0; y <= gridHeight; ++y) { int perpDir = 1;
points.push_back(Pointfs()); for (coordf_t y = gridSize; y <= (boundsY); y+= gridSize, perpDir *= -1) {
Pointfs &newPoints = points.back(); points.push_back(Pointfs());
newPoints = zip( Pointfs &newPoints = points.back();
colinearPoints(offset, 0, gridWidth), newPoints = zip(
perpendPoints(offset, y, gridWidth)); colinearPoints(Zpos, gridSize, critPoints, 0, boundsX),
// trim points to grid edges perpendPoints(Zpos, gridSize, critPoints, 0, boundsX, y, perpDir));
trim(newPoints, coordf_t(0.), coordf_t(0.), coordf_t(gridWidth), coordf_t(gridHeight)); if (perpDir == -1)
if (y & 1) std::reverse(newPoints.begin(), newPoints.end());
std::reverse(newPoints.begin(), newPoints.end());
}
} }
return points; }
return points;
} }
// Generate a set of curves (array of array of 2d points) that describe a // Generate a set of curves (array of array of 2d points) that describe a
// horizontal slice of a truncated regular octahedron with a specified // horizontal slice of a truncated regular octahedron with a specified
// grid square size. // 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; std::vector<Pointfs> polylines = makeActualGrid(z, gridSize, boundWidth, boundHeight);
coordf_t normalisedZ = coordf_t(z) / coordf_t(scaleFactor); Polylines result;
std::vector<Pointfs> polylines = makeNormalisedGrid(normalisedZ, gridWidth, gridHeight, curveType); result.reserve(polylines.size());
Polylines result; for (std::vector<Pointfs>::const_iterator it_polylines = polylines.begin();
result.reserve(polylines.size()); it_polylines != polylines.end(); ++ it_polylines) {
for (std::vector<Pointfs>::const_iterator it_polylines = polylines.begin(); it_polylines != polylines.end(); ++ it_polylines) { result.push_back(Polyline());
result.push_back(Polyline()); Polyline &polyline = result.back();
Polyline &polyline = result.back(); for (Pointfs::const_iterator it = it_polylines->begin(); it != it_polylines->end(); ++ it)
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))));
polyline.points.push_back(Point(coord_t((*it)(0) * scaleFactor), coord_t((*it)(1) * scaleFactor))); }
} return result;
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( void Fill3DHoneycomb::_fill_surface_single(
const FillParams &params, const FillParams &params,
unsigned int thickness_layers, unsigned int thickness_layers,
@ -142,27 +196,75 @@ void Fill3DHoneycomb::_fill_surface_single(
{ {
// no rotation is supported for this infill pattern // no rotation is supported for this infill pattern
BoundingBox bb = expolygon.contour.bounding_box(); 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 // align bounding box to a multiple of our honeycomb grid module
// (a module is 2*$distance since one $distance half-module is // (a module is 2*$gridSize since one $gridSize half-module is
// growing while the other $distance half-module is shrinking) // growing while the other $gridSize half-module is shrinking)
bb.merge(align_to_grid(bb.min, Point(2*distance, 2*distance))); bb.merge(align_to_grid(bb.min, Point(gridSize*4, gridSize*4)));
// generate pattern // generate pattern
Polylines polylines = makeGrid( Polylines polylines =
scale_(this->z), makeGrid(
distance, scale_(this->z) * zScale,
ceil(bb.size()(0) / distance) + 1, gridSize,
ceil(bb.size()(1) / distance) + 1, bb.size()(0),
((this->layer_id/thickness_layers) % 2) + 1); bb.size()(1),
!params.dont_adjust);
// move pattern in place // move pattern in place
for (Polyline &pl : polylines) for (Polyline &pl : polylines){
pl.translate(bb.min); pl.translate(bb.min);
}
// clip pattern to boundaries, chain the clipped polylines // clip pattern to boundaries, chain the clipped polylines
polylines = intersection_pl(polylines, expolygon); polylines = intersection_pl(polylines, to_polygons(expolygon));
// connect lines if needed // connect lines if needed
if (params.dont_connect() || polylines.size() <= 1) if (params.dont_connect() || polylines.size() <= 1)
@ -171,4 +273,4 @@ void Fill3DHoneycomb::_fill_surface_single(
this->connect_infill(std::move(polylines), expolygon, polylines_out, this->spacing, params); this->connect_infill(std::move(polylines), expolygon, polylines_out, this->spacing, params);
} }
} // namespace Slic3r } // namespace Slic3r

View File

@ -15,9 +15,6 @@ public:
Fill* clone() const override { return new Fill3DHoneycomb(*this); }; Fill* clone() const override { return new Fill3DHoneycomb(*this); };
~Fill3DHoneycomb() override {} ~Fill3DHoneycomb() override {}
// require bridge flow since most of this pattern hangs in air
bool use_bridge_flow() const override { return true; }
protected: protected:
void _fill_surface_single( void _fill_surface_single(
const FillParams &params, const FillParams &params,