Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve 3D Honeycomb density calculation, clean up code #5078

Open
wants to merge 1 commit into
base: merill-merge
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
287 changes: 189 additions & 98 deletions src/libslic3r/Fill/Fill3DHoneycomb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

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
Expand All @@ -17,48 +21,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<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
// 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<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;
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<coordf_t> 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<coordf_t> 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<coordf_t> 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<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)
{
for (Vec2d &pt : pts) {
pt(0) = clamp(minX, maxX, pt(0));
pt(1) = clamp(minY, maxY, pt(1));
std::vector<coordf_t> 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<coordf_t> &x, const std::vector<coordf_t> &y)
Expand All @@ -72,96 +126,133 @@ 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
// horizontal slice of a truncated regular octahedron with edge length 1.
// curveType specifies which lines to print, 1 for vertical lines
// (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)
// horizontal slice of a truncated regular octahedron.
static std::vector<Pointfs> 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<Pointfs> 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<Pointfs> points;
std::vector<coordf_t> 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<Pointfs> polylines = makeNormalisedGrid(normalisedZ, gridWidth, gridHeight, curveType);
Polylines result;
result.reserve(polylines.size());
for (std::vector<Pointfs>::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<Pointfs> polylines = makeActualGrid(z, gridSize, boundWidth, boundHeight);
Polylines result;
result.reserve(polylines.size());
for (std::vector<Pointfs>::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;
}

void Fill3DHoneycomb::_fill_surface_single(
const FillParams &params,
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
const std::pair<float, Point> &direction,
ExPolygon expolygon,
Polylines &polylines_out) const
{
// no rotation is supported for this infill pattern
BoundingBox bb = expolygon.contour.bounding_box();
coord_t distance = coord_t(scale_(this->get_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 4*$gridSize because gridSize describes a half-wave,
// and there's a plus/minus cycle)
bb.merge(align_to_grid(bb.min, Point(gridSize*4, gridSize*4)));

// generate pattern
Polylines polylines = makeGrid(
scale_(this->z),
distance,
(size_t)ceil(bb.size().x() / distance) + 1,
(size_t)ceil(bb.size().y() / distance) + 1,
size_t((this->layer_id / thickness_layers) % 2) + 1);
//makeGrid(coord_t z, coord_t gridSize, size_t gridWidth, size_t gridHeight, size_t curveType)
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, to_polygons(expolygon));
Expand Down