#include "../ClipperUtils.hpp" #include "../ShortestPath.hpp" #include "../Surface.hpp" #include "Fill3DHoneycomb.hpp" 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 octahedron tesselation. The octahedrons are oriented so that the square faces are in the horizontal plane with edges parallel to the X 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 Zpos, coordf_t gridSize, std::vector critPoints, const size_t baseLocation, size_t gridLength) { 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(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 Zpos, coordf_t gridSize, std::vector critPoints, size_t baseLocation, size_t gridLength, size_t offsetBase, coordf_t perpDir) { 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) { assert(x.size() == y.size()); Pointfs out; out.reserve(x.size()); for (size_t i = 0; i < x.size(); ++ i) out.push_back(Vec2d(x[i], y[i])); return out; } // Generate a set of curves (array of array of 2d points) that describe a // horizontal slice of a truncated regular octahedron. static std::vector makeActualGrid(coordf_t Zpos, coordf_t gridSize, size_t boundsX, size_t boundsY) { 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()); } } 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; } // 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. // 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) { 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, const std::pair &direction, ExPolygon expolygon, Polylines &polylines_out) { // no rotation is supported for this infill pattern // BBL: add support for rotation auto infill_angle = float(this->angle); if (std::abs(infill_angle) >= EPSILON) expolygon.rotate(-infill_angle); BoundingBox bb = expolygon.contour.bounding_box(); // 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*$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) * zScale, gridSize, bb.size()(0), bb.size()(1), !params.dont_adjust); // move pattern in place for (Polyline &pl : polylines){ pl.translate(bb.min); } // clip pattern to boundaries, chain the clipped polylines polylines = intersection_pl(polylines, to_polygons(expolygon)); // copy from fliplines if (!polylines.empty()) { int infill_start_idx = polylines_out.size(); // only rotate what belongs to us. // connect lines if (params.dont_connect() || polylines.size() <= 1) append(polylines_out, chain_polylines(std::move(polylines))); else this->connect_infill(std::move(polylines), expolygon, polylines_out, this->spacing, params); // rotate back if (std::abs(infill_angle) >= EPSILON) { for (auto it = polylines_out.begin() + infill_start_idx; it != polylines_out.end(); ++it) it->rotate(infill_angle); } } } } // namespace Slic3r