mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 12:41:20 -06:00 
			
		
		
		
	3D Honeycomb - switch direction at smallest bridge point, rather than every layer (#4425)
Co-authored-by: SoftFever <softfeverever@gmail.com>
This commit is contained in:
		
							parent
							
								
									350a2e4c4a
								
							
						
					
					
						commit
						587fab285c
					
				
					 2 changed files with 197 additions and 98 deletions
				
			
		|  | @ -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,50 +21,100 @@ 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 - offset2); |   points.push_back(baseLocation); | ||||||
|     for (size_t i = 0; i < gridLength; ++i) { |   for (coordf_t cLoc = baseLocation; cLoc < gridLength; cLoc+= (gridSize*2)) { | ||||||
|         points.push_back(baseLocation + i + offset2); |     for(size_t pi = 0; pi < critPoints.size(); pi++){ | ||||||
|         points.push_back(baseLocation + i + 1 - offset2); |       points.push_back(baseLocation + cLoc + critPoints[pi]); | ||||||
|     } |     } | ||||||
|     points.push_back(baseLocation + gridLength + offset2); |   } | ||||||
|  |   points.push_back(gridLength); | ||||||
|   return points; |   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.); |  | ||||||
|     coord_t  side    = 2 * (baseLocation & 1) - 1; |  | ||||||
|   std::vector<coordf_t> points; |   std::vector<coordf_t> points; | ||||||
|     points.push_back(baseLocation - offset2 * side); |   points.push_back(offsetBase); | ||||||
|     for (size_t i = 0; i < gridLength; ++i) { |   for (coordf_t cLoc = baseLocation; cLoc < gridLength; cLoc+= gridSize*2) { | ||||||
|         side = 2*((i+baseLocation) & 1) - 1; |     for(size_t pi = 0; pi < critPoints.size(); pi++){ | ||||||
|         points.push_back(baseLocation + offset2 * side); |       coordf_t offset = troctWave(critPoints[pi], gridSize, Zpos); | ||||||
|         points.push_back(baseLocation + offset2 * side); |       points.push_back(offsetBase + (offset * perpDir)); | ||||||
|     } |     } | ||||||
|     points.push_back(baseLocation - offset2 * side); |   } | ||||||
|  |   points.push_back(offsetBase); | ||||||
|   return points; |   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); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 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) | ||||||
| { | { | ||||||
|     assert(x.size() == y.size()); |     assert(x.size() == y.size()); | ||||||
|  | @ -71,43 +126,33 @@ 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
 |  | ||||||
|     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; |   std::vector<Pointfs> points; | ||||||
|     if ((curveType & 1) != 0) { |   std::vector<coordf_t> critPoints = getCriticalPoints(Zpos, gridSize); | ||||||
|         for (size_t x = 0; x <= gridWidth; ++x) { |   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()); |       points.push_back(Pointfs()); | ||||||
|       Pointfs &newPoints = points.back(); |       Pointfs &newPoints = points.back(); | ||||||
|       newPoints = zip( |       newPoints = zip( | ||||||
|                 perpendPoints(offset, x, gridHeight),  | 		      perpendPoints(Zpos, gridSize, critPoints, 0, boundsY, x, perpDir), | ||||||
|                 colinearPoints(offset, 0, gridHeight)); | 		      colinearPoints(Zpos, gridSize, critPoints, 0, boundsY)); | ||||||
|             // trim points to grid edges
 |       if (perpDir == 1) | ||||||
|             trim(newPoints, coordf_t(0.), coordf_t(0.), coordf_t(gridWidth), coordf_t(gridHeight)); |  | ||||||
|             if (x & 1) |  | ||||||
| 	std::reverse(newPoints.begin(), newPoints.end()); | 	std::reverse(newPoints.begin(), newPoints.end()); | ||||||
|     } |     } | ||||||
|     } |   } else { | ||||||
|     if ((curveType & 2) != 0) { |     int perpDir = 1; | ||||||
|         for (size_t y = 0; y <= gridHeight; ++y) { |     for (coordf_t y = gridSize; y <= (boundsY); y+= gridSize, perpDir *= -1) { | ||||||
|       points.push_back(Pointfs()); |       points.push_back(Pointfs()); | ||||||
|       Pointfs &newPoints = points.back(); |       Pointfs &newPoints = points.back(); | ||||||
|       newPoints = zip( |       newPoints = zip( | ||||||
|                 colinearPoints(offset, 0, gridWidth), | 		      colinearPoints(Zpos, gridSize, critPoints, 0, boundsX), | ||||||
|                 perpendPoints(offset, y, gridWidth)); | 		      perpendPoints(Zpos, gridSize, critPoints, 0, boundsX, y, perpDir)); | ||||||
|             // trim points to grid edges
 |       if (perpDir == -1) | ||||||
|             trim(newPoints, coordf_t(0.), coordf_t(0.), coordf_t(gridWidth), coordf_t(gridHeight)); |  | ||||||
|             if (y & 1) |  | ||||||
| 	std::reverse(newPoints.begin(), newPoints.end()); | 	std::reverse(newPoints.begin(), newPoints.end()); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | @ -117,22 +162,31 @@ static std::vector<Pointfs> makeNormalisedGrid(coordf_t z, size_t gridWidth, siz | ||||||
| // 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); |  | ||||||
|     std::vector<Pointfs> polylines = makeNormalisedGrid(normalisedZ, gridWidth, gridHeight, curveType); |  | ||||||
|   Polylines result; |   Polylines result; | ||||||
|   result.reserve(polylines.size()); |   result.reserve(polylines.size()); | ||||||
|     for (std::vector<Pointfs>::const_iterator it_polylines = polylines.begin(); 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) * scaleFactor), coord_t((*it)(1) * scaleFactor))); |       polyline.points.push_back(Point(coord_t((*it)(0)), coord_t((*it)(1)))); | ||||||
|   } |   } | ||||||
|   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                ¶ms,  |     const FillParams                ¶ms,  | ||||||
|     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) | ||||||
|  |  | ||||||
|  | @ -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                ¶ms,  | 	    const FillParams                ¶ms,  | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 David Eccles (gringer)
						David Eccles (gringer)