mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-30 20:21:12 -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 { | ||||
| 
 | ||||
| // 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 | ||||
| up a horizontal slice of the edges of a space filling truncated | ||||
|  | @ -16,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) | ||||
|   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; | ||||
|     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) | ||||
| { | ||||
|     for (Vec2d &pt : pts) { | ||||
|         pt.x() = std::clamp(pt.x(), minX, maxX); | ||||
|         pt.y() = std::clamp(pt.y(), minY, maxY); | ||||
|   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) | ||||
|  | @ -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
 | ||||
| // 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; | ||||
| } | ||||
| 
 | ||||
| // 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, | ||||
|  | @ -142,27 +196,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) | ||||
|  |  | |||
|  | @ -15,9 +15,6 @@ public: | |||
|     Fill* clone() const override { return new Fill3DHoneycomb(*this); }; | ||||
|     ~Fill3DHoneycomb() override {} | ||||
| 
 | ||||
| 	// require bridge flow since most of this pattern hangs in air
 | ||||
|     bool use_bridge_flow() const override { return true; } | ||||
| 
 | ||||
| protected: | ||||
| 	void _fill_surface_single( | ||||
| 	    const FillParams                ¶ms,  | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 David Eccles (gringer)
						David Eccles (gringer)