mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 04:31:15 -06:00 
			
		
		
		
	New functions for variable offsets of polygons / expolygons.
Test cases for the above. Improvements of older test cases.
This commit is contained in:
		
							parent
							
								
									18bbefcd61
								
							
						
					
					
						commit
						5e8572a196
					
				
					 8 changed files with 565 additions and 27 deletions
				
			
		|  | @ -107,8 +107,7 @@ void AddOuterPolyNodeToExPolygons(ClipperLib::PolyNode& polynode, ExPolygons* ex | |||
|   } | ||||
| } | ||||
|   | ||||
| ExPolygons | ||||
| PolyTreeToExPolygons(ClipperLib::PolyTree& polytree) | ||||
| ExPolygons PolyTreeToExPolygons(ClipperLib::PolyTree& polytree) | ||||
| { | ||||
|     ExPolygons retval; | ||||
|     for (int i = 0; i < polytree.ChildCount(); ++i) | ||||
|  | @ -151,8 +150,7 @@ Slic3r::Polylines ClipperPaths_to_Slic3rPolylines(const ClipperLib::Paths &input | |||
|     return retval; | ||||
| } | ||||
| 
 | ||||
| ExPolygons | ||||
| ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input) | ||||
| ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input) | ||||
| { | ||||
|     // init Clipper
 | ||||
|     ClipperLib::Clipper clipper; | ||||
|  | @ -167,8 +165,7 @@ ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input) | |||
|     return PolyTreeToExPolygons(polytree); | ||||
| } | ||||
| 
 | ||||
| ClipperLib::Path | ||||
| Slic3rMultiPoint_to_ClipperPath(const MultiPoint &input) | ||||
| ClipperLib::Path Slic3rMultiPoint_to_ClipperPath(const MultiPoint &input) | ||||
| { | ||||
|     ClipperLib::Path retval; | ||||
|     for (Points::const_iterator pit = input.points.begin(); pit != input.points.end(); ++pit) | ||||
|  | @ -176,8 +173,7 @@ Slic3rMultiPoint_to_ClipperPath(const MultiPoint &input) | |||
|     return retval; | ||||
| } | ||||
| 
 | ||||
| ClipperLib::Path | ||||
| Slic3rMultiPoint_to_ClipperPath_reversed(const Slic3r::MultiPoint &input) | ||||
| ClipperLib::Path Slic3rMultiPoint_to_ClipperPath_reversed(const Slic3r::MultiPoint &input) | ||||
| { | ||||
|     ClipperLib::Path output; | ||||
|     output.reserve(input.points.size()); | ||||
|  | @ -521,7 +517,7 @@ T _clipper_do(const ClipperLib::ClipType     clipType, | |||
| 
 | ||||
| // Fix of #117: A large fractal pyramid takes ages to slice
 | ||||
| // The Clipper library has difficulties processing overlapping polygons.
 | ||||
| // Namely, the function Clipper::JoinCommonEdges() has potentially a terrible time complexity if the output
 | ||||
| // Namely, the function ClipperLib::JoinCommonEdges() has potentially a terrible time complexity if the output
 | ||||
| // of the operation is of the PolyTree type.
 | ||||
| // This function implmenets a following workaround:
 | ||||
| // 1) Peform the Clipper operation with the output to Paths. This method handles overlaps in a reasonable time.
 | ||||
|  | @ -918,4 +914,304 @@ Polygons top_level_islands(const Slic3r::Polygons &polygons) | |||
|     return out; | ||||
| } | ||||
| 
 | ||||
| // Outer offset shall not split the input contour into multiples. It is expected, that the solution will be non empty and it will contain just a single polygon.
 | ||||
| ClipperLib::Paths fix_after_outer_offset(const ClipperLib::Path &input, ClipperLib::PolyFillType filltype, bool reverse_result) | ||||
| { | ||||
|   	ClipperLib::Paths solution; | ||||
|   	if (! input.empty()) { | ||||
| 		ClipperLib::Clipper clipper; | ||||
| 	  	clipper.AddPath(input, ClipperLib::ptSubject, true); | ||||
| 		clipper.ReverseSolution(reverse_result); | ||||
| 		clipper.Execute(ClipperLib::ctUnion, solution, filltype, filltype); | ||||
| 	} | ||||
|     return solution; | ||||
| } | ||||
| 
 | ||||
| // Inner offset may split the source contour into multiple contours, but one shall not be inside the other.
 | ||||
| ClipperLib::Paths fix_after_inner_offset(const ClipperLib::Path &input, ClipperLib::PolyFillType filltype, bool reverse_result) | ||||
| { | ||||
|   	ClipperLib::Paths solution; | ||||
|   	if (! input.empty()) { | ||||
| 		ClipperLib::Clipper clipper; | ||||
| 		clipper.AddPath(input, ClipperLib::ptSubject, true); | ||||
| 		ClipperLib::IntRect r = clipper.GetBounds(); | ||||
| 		r.left -= 10; r.top -= 10; r.right += 10; r.bottom += 10; | ||||
| 		if (filltype == ClipperLib::pftPositive) | ||||
| 			clipper.AddPath({ ClipperLib::IntPoint(r.left, r.bottom), ClipperLib::IntPoint(r.left, r.top), ClipperLib::IntPoint(r.right, r.top), ClipperLib::IntPoint(r.right, r.bottom) }, ClipperLib::ptSubject, true); | ||||
| 		else | ||||
| 			clipper.AddPath({ ClipperLib::IntPoint(r.left, r.bottom), ClipperLib::IntPoint(r.right, r.bottom), ClipperLib::IntPoint(r.right, r.top), ClipperLib::IntPoint(r.left, r.top) }, ClipperLib::ptSubject, true); | ||||
| 		clipper.ReverseSolution(reverse_result); | ||||
| 		clipper.Execute(ClipperLib::ctUnion, solution, filltype, filltype); | ||||
| 		if (! solution.empty()) | ||||
| 			solution.erase(solution.begin()); | ||||
| 	} | ||||
| 	return solution; | ||||
| } | ||||
| 
 | ||||
| ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::vector<float> &deltas, double miter_limit) | ||||
| { | ||||
| 	assert(contour.size() == deltas.size()); | ||||
| #ifndef NDEBUG | ||||
| 	// Verify that the deltas are either all positive, or all negative.
 | ||||
| 	bool positive = false; | ||||
| 	bool negative = false; | ||||
| 	for (float delta : deltas) | ||||
| 		if (delta < 0.f) | ||||
| 			negative = true; | ||||
| 		else if (delta > 0.f) | ||||
| 			positive = true; | ||||
| 	assert(! (negative && positive)); | ||||
| #endif /* NDEBUG */ | ||||
| 
 | ||||
| 	ClipperLib::Path out; | ||||
| 
 | ||||
| 	if (deltas.size() > 2) | ||||
| 	{ | ||||
| 		out.reserve(contour.size() * 2); | ||||
| 
 | ||||
| 		// Clamp miter limit to 2.
 | ||||
| 		miter_limit = (miter_limit > 2.) ? 2. / (miter_limit * miter_limit) : 0.5; | ||||
| 		 | ||||
| 		// perpenduclar vector
 | ||||
| 		auto   perp = [](const Vec2d &v) -> Vec2d { return Vec2d(v.y(), - v.x()); }; | ||||
| 
 | ||||
| 		// Add a new point to the output, scale by CLIPPER_OFFSET_SCALE and round to ClipperLib::cInt.
 | ||||
| 		auto   add_offset_point = [&out](Vec2d pt) { | ||||
| 			pt *= double(CLIPPER_OFFSET_SCALE); | ||||
| 			pt += Vec2d(0.5 - (pt.x() < 0), 0.5 - (pt.y() < 0)); | ||||
| 			out.emplace_back(ClipperLib::cInt(pt.x()), ClipperLib::cInt(pt.y())); | ||||
| 		}; | ||||
| 
 | ||||
| 		// Minimum edge length, squared.
 | ||||
| 		double lmin  = *std::max_element(deltas.begin(), deltas.end()) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR; | ||||
| 		double l2min = lmin * lmin; | ||||
| 		// Minimum angle to consider two edges to be parallel.
 | ||||
| 		double sin_min_parallel = EPSILON + 1. / double(CLIPPER_OFFSET_SCALE); | ||||
| 
 | ||||
| 		// Find the last point further from pt by l2min.
 | ||||
| 		Vec2d  pt     = contour.front().cast<double>(); | ||||
| 		size_t iprev  = contour.size() - 1; | ||||
| 		Vec2d  ptprev; | ||||
| 		for (; iprev > 0; -- iprev) { | ||||
| 			ptprev = contour[iprev].cast<double>(); | ||||
| 			if ((ptprev - pt).squaredNorm() > l2min) | ||||
| 				break; | ||||
| 		} | ||||
| 
 | ||||
| 		if (iprev != 0) { | ||||
| 			size_t ilast = iprev; | ||||
| 			// Normal to the (pt - ptprev) segment.
 | ||||
| 			Vec2d nprev = perp(pt - ptprev).normalized(); | ||||
| 			for (size_t i = 0; ; ) { | ||||
| 				// Find the next point further from pt by l2min.
 | ||||
| 				size_t j = i + 1; | ||||
| 				Vec2d ptnext; | ||||
| 				for (; j <= ilast; ++ j) { | ||||
| 					ptnext = contour[j].cast<double>(); | ||||
| 					double l2 = (ptnext - pt).squaredNorm(); | ||||
| 					if (l2 > l2min) | ||||
| 						break; | ||||
| 				} | ||||
| 				if (j > ilast) | ||||
| 					ptnext = contour.front().cast<double>(); | ||||
| 
 | ||||
| 				// Normal to the (ptnext - pt) segment.
 | ||||
| 				Vec2d nnext  = perp(ptnext - pt).normalized(); | ||||
| 
 | ||||
| 				double delta  = deltas[i]; | ||||
| 				double sin_a  = clamp(-1., 1., cross2(nprev, nnext)); | ||||
| 				double convex = sin_a * delta; | ||||
| 				if (convex <= - sin_min_parallel) { | ||||
| 					// Concave corner.
 | ||||
| 					add_offset_point(pt + nprev * delta); | ||||
| 					add_offset_point(pt); | ||||
| 					add_offset_point(pt + nnext * delta); | ||||
| 				} else if (convex < sin_min_parallel) { | ||||
| 					// Nearly parallel.
 | ||||
| 					add_offset_point((nprev.dot(nnext) > 0.) ? (pt + nprev * delta) : pt); | ||||
| 				} else { | ||||
| 					// Convex corner
 | ||||
| 					double dot = nprev.dot(nnext); | ||||
| 					double r = 1. + dot; | ||||
| 				  	if (r >= miter_limit) | ||||
| 						add_offset_point(pt + (nprev + nnext) * (delta / r)); | ||||
| 				  	else { | ||||
| 						double dx = std::tan(std::atan2(sin_a, dot) / 4.); | ||||
| 						Vec2d  newpt1 = pt + (nprev - perp(nprev) * dx) * delta; | ||||
| 						Vec2d  newpt2 = pt + (nnext + perp(nnext) * dx) * delta; | ||||
| #ifndef NDEBUG | ||||
| 						Vec2d vedge = 0.5 * (newpt1 + newpt2) - pt; | ||||
| 						double dist_norm = vedge.norm(); | ||||
| 						assert(std::abs(dist_norm - delta) < EPSILON); | ||||
| #endif /* NDEBUG */ | ||||
| 						add_offset_point(newpt1); | ||||
| 						add_offset_point(newpt2); | ||||
| 				  	} | ||||
| 				} | ||||
| 
 | ||||
| 				if (i == ilast) | ||||
| 					break; | ||||
| 
 | ||||
| 				ptprev = pt; | ||||
| 				nprev  = nnext; | ||||
| 				pt     = ptnext; | ||||
| 				i = j; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return out; | ||||
| } | ||||
| 
 | ||||
| Polygons variable_offset_inner(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit) | ||||
| { | ||||
| #ifndef NDEBUG | ||||
| 	// Verify that the deltas are all non positive.
 | ||||
| 	for (const std::vector<float> &ds : deltas) | ||||
| 		for (float delta : ds) | ||||
| 			assert(delta <= 0.); | ||||
| 	assert(expoly.holes.size() + 1 == deltas.size()); | ||||
| #endif /* NDEBUG */ | ||||
| 
 | ||||
| 	// 1) Offset the outer contour.
 | ||||
| 	ClipperLib::Paths contours = fix_after_inner_offset(mittered_offset_path_scaled(expoly.contour.points, deltas.front(), miter_limit), ClipperLib::pftNegative, true); | ||||
| 
 | ||||
| 	// 2) Offset the holes one by one, collect the results.
 | ||||
| 	ClipperLib::Paths holes; | ||||
| 	holes.reserve(expoly.holes.size()); | ||||
| 	for (const Polygon& hole : expoly.holes) | ||||
| 		append(holes, fix_after_outer_offset(mittered_offset_path_scaled(hole, deltas[1 + &hole - expoly.holes.data()], miter_limit), ClipperLib::pftPositive, false)); | ||||
| 
 | ||||
| 	// 3) Subtract holes from the contours.
 | ||||
| 	ClipperLib::Paths output; | ||||
| 	if (holes.empty()) | ||||
| 		output = std::move(contours); | ||||
| 	else { | ||||
| 		ClipperLib::Clipper clipper; | ||||
| 		clipper.Clear(); | ||||
| 		clipper.AddPaths(contours, ClipperLib::ptSubject, true); | ||||
| 		clipper.AddPaths(holes, ClipperLib::ptClip, true); | ||||
| 		clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); | ||||
| 	} | ||||
| 
 | ||||
| 	// 4) Unscale the output.
 | ||||
| 	unscaleClipperPolygons(output); | ||||
| 	return ClipperPaths_to_Slic3rPolygons(output); | ||||
| } | ||||
| 
 | ||||
| Polygons variable_offset_outer(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit) | ||||
| { | ||||
| #ifndef NDEBUG | ||||
| 	// Verify that the deltas are all non positive.
 | ||||
| for (const std::vector<float>& ds : deltas) | ||||
| 		for (float delta : ds) | ||||
| 			assert(delta >= 0.); | ||||
| 	assert(expoly.holes.size() + 1 == deltas.size()); | ||||
| #endif /* NDEBUG */ | ||||
| 
 | ||||
| 	// 1) Offset the outer contour.
 | ||||
| 	ClipperLib::Paths contours = fix_after_outer_offset(mittered_offset_path_scaled(expoly.contour.points, deltas.front(), miter_limit), ClipperLib::pftPositive, false); | ||||
| 
 | ||||
| 	// 2) Offset the holes one by one, collect the results.
 | ||||
| 	ClipperLib::Paths holes; | ||||
| 	holes.reserve(expoly.holes.size()); | ||||
| 	for (const Polygon& hole : expoly.holes) | ||||
| 		append(holes, fix_after_inner_offset(mittered_offset_path_scaled(hole, deltas[1 + &hole - expoly.holes.data()], miter_limit), ClipperLib::pftPositive, true)); | ||||
| 
 | ||||
| 	// 3) Subtract holes from the contours.
 | ||||
| 	ClipperLib::Paths output; | ||||
| 	if (holes.empty()) | ||||
| 		output = std::move(contours); | ||||
| 	else { | ||||
| 		ClipperLib::Clipper clipper; | ||||
| 		clipper.Clear(); | ||||
| 		clipper.AddPaths(contours, ClipperLib::ptSubject, true); | ||||
| 		clipper.AddPaths(holes, ClipperLib::ptClip, true); | ||||
| 		clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); | ||||
| 	} | ||||
| 
 | ||||
| 	// 4) Unscale the output.
 | ||||
| 	unscaleClipperPolygons(output); | ||||
| 	return ClipperPaths_to_Slic3rPolygons(output); | ||||
| } | ||||
| 
 | ||||
| ExPolygons variable_offset_outer_ex(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit) | ||||
| { | ||||
| #ifndef NDEBUG | ||||
| 	// Verify that the deltas are all non positive.
 | ||||
| for (const std::vector<float>& ds : deltas) | ||||
| 		for (float delta : ds) | ||||
| 			assert(delta >= 0.); | ||||
| 	assert(expoly.holes.size() + 1 == deltas.size()); | ||||
| #endif /* NDEBUG */ | ||||
| 
 | ||||
| 	// 1) Offset the outer contour.
 | ||||
| 	ClipperLib::Paths contours = fix_after_outer_offset(mittered_offset_path_scaled(expoly.contour.points, deltas.front(), miter_limit), ClipperLib::pftPositive, false); | ||||
| 
 | ||||
| 	// 2) Offset the holes one by one, collect the results.
 | ||||
| 	ClipperLib::Paths holes; | ||||
| 	holes.reserve(expoly.holes.size()); | ||||
| 	for (const Polygon& hole : expoly.holes) | ||||
| 		append(holes, fix_after_inner_offset(mittered_offset_path_scaled(hole, deltas[1 + &hole - expoly.holes.data()], miter_limit), ClipperLib::pftPositive, true)); | ||||
| 
 | ||||
| 	// 3) Subtract holes from the contours.
 | ||||
| 	unscaleClipperPolygons(contours); | ||||
| 	ExPolygons output; | ||||
| 	if (holes.empty()) { | ||||
| 		output.reserve(contours.size()); | ||||
| 		for (ClipperLib::Path &path : contours)  | ||||
| 			output.emplace_back(ClipperPath_to_Slic3rPolygon(path)); | ||||
| 	} else { | ||||
| 		ClipperLib::Clipper clipper; | ||||
| 		unscaleClipperPolygons(holes); | ||||
| 		clipper.AddPaths(contours, ClipperLib::ptSubject, true); | ||||
| 		clipper.AddPaths(holes, ClipperLib::ptClip, true); | ||||
| 	    ClipperLib::PolyTree polytree; | ||||
| 		clipper.Execute(ClipperLib::ctDifference, polytree, ClipperLib::pftNonZero, ClipperLib::pftNonZero); | ||||
| 	    output = PolyTreeToExPolygons(polytree); | ||||
| 	} | ||||
| 
 | ||||
| 	return output; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| ExPolygons variable_offset_inner_ex(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit) | ||||
| { | ||||
| #ifndef NDEBUG | ||||
| 	// Verify that the deltas are all non positive.
 | ||||
| for (const std::vector<float>& ds : deltas) | ||||
| 		for (float delta : ds) | ||||
| 			assert(delta <= 0.); | ||||
| 	assert(expoly.holes.size() + 1 == deltas.size()); | ||||
| #endif /* NDEBUG */ | ||||
| 
 | ||||
| 	// 1) Offset the outer contour.
 | ||||
| 	ClipperLib::Paths contours = fix_after_inner_offset(mittered_offset_path_scaled(expoly.contour.points, deltas.front(), miter_limit), ClipperLib::pftNegative, false); | ||||
| 
 | ||||
| 	// 2) Offset the holes one by one, collect the results.
 | ||||
| 	ClipperLib::Paths holes; | ||||
| 	holes.reserve(expoly.holes.size()); | ||||
| 	for (const Polygon& hole : expoly.holes) | ||||
| 		append(holes, fix_after_outer_offset(mittered_offset_path_scaled(hole, deltas[1 + &hole - expoly.holes.data()], miter_limit), ClipperLib::pftNegative, true)); | ||||
| 
 | ||||
| 	// 3) Subtract holes from the contours.
 | ||||
| 	unscaleClipperPolygons(contours); | ||||
| 	ExPolygons output; | ||||
| 	if (holes.empty()) { | ||||
| 		output.reserve(contours.size()); | ||||
| 		for (ClipperLib::Path &path : contours)  | ||||
| 			output.emplace_back(ClipperPath_to_Slic3rPolygon(path)); | ||||
| 	} else { | ||||
| 		ClipperLib::Clipper clipper; | ||||
| 		unscaleClipperPolygons(holes); | ||||
| 		clipper.AddPaths(contours, ClipperLib::ptSubject, true); | ||||
| 		clipper.AddPaths(holes, ClipperLib::ptClip, true); | ||||
| 	    ClipperLib::PolyTree polytree; | ||||
| 		clipper.Execute(ClipperLib::ctDifference, polytree, ClipperLib::pftNonZero, ClipperLib::pftNonZero); | ||||
| 	    output = PolyTreeToExPolygons(polytree); | ||||
| 	} | ||||
| 
 | ||||
| 	return output; | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -238,6 +238,11 @@ void safety_offset(ClipperLib::Paths* paths); | |||
| 
 | ||||
| Polygons top_level_islands(const Slic3r::Polygons &polygons); | ||||
| 
 | ||||
| Polygons  variable_offset_inner(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit = 2.); | ||||
| Polygons  variable_offset_outer(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit = 2.); | ||||
| ExPolygons variable_offset_outer_ex(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit = 2.); | ||||
| ExPolygons variable_offset_inner_ex(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit = 2.); | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
|  |  | |||
|  | @ -301,6 +301,15 @@ inline bool expolygons_contain(ExPolygons &expolys, const Point &pt) | |||
|     return false; | ||||
| } | ||||
| 
 | ||||
| inline ExPolygons expolygons_simplify(const ExPolygons &expolys, double tolerance) | ||||
| { | ||||
| 	ExPolygons out; | ||||
| 	out.reserve(expolys.size()); | ||||
| 	for (const ExPolygon &exp : expolys) | ||||
| 		exp.simplify(tolerance, &out); | ||||
| 	return out; | ||||
| } | ||||
| 
 | ||||
| extern BoundingBox get_extents(const ExPolygon &expolygon); | ||||
| extern BoundingBox get_extents(const ExPolygons &expolygons); | ||||
| extern BoundingBox get_extents_rotated(const ExPolygon &poly, double angle); | ||||
|  |  | |||
|  | @ -102,6 +102,15 @@ inline void        polygons_append(Polygons &dst, Polygons &&src) | |||
|     } | ||||
| } | ||||
| 
 | ||||
| inline Polygons polygons_simplify(const Polygons &polys, double tolerance) | ||||
| { | ||||
| 	Polygons out; | ||||
| 	out.reserve(polys.size()); | ||||
| 	for (const Polygon &p : polys) | ||||
| 		polygons_append(out, p.simplify(tolerance)); | ||||
| 	return out; | ||||
| } | ||||
| 
 | ||||
| inline void polygons_rotate(Polygons &polys, double angle) | ||||
| { | ||||
|     const double cos_angle = cos(angle); | ||||
|  |  | |||
|  | @ -95,7 +95,6 @@ SCENARIO(" Bridge flow specifics.", "[Flow]") { | |||
| SCENARIO("Flow: Flow math for non-bridges", "[Flow]") { | ||||
|     GIVEN("Nozzle Diameter of 0.4, a desired width of 1mm and layer height of 0.5") { | ||||
|         ConfigOptionFloatOrPercent	width(1.0, false); | ||||
|         float spacing			= 0.4f; | ||||
|         float nozzle_diameter	= 0.4f; | ||||
|         float bridge_flow		= 0.f; | ||||
|         float layer_height		= 0.5f; | ||||
|  | @ -119,7 +118,6 @@ SCENARIO("Flow: Flow math for non-bridges", "[Flow]") { | |||
|     } | ||||
|     /// Check the min/max
 | ||||
|     GIVEN("Nozzle Diameter of 0.25") { | ||||
|         float spacing			= 0.4f; | ||||
|         float nozzle_diameter	= 0.25f; | ||||
|         float bridge_flow		= 0.f; | ||||
|         float layer_height		= 0.5f; | ||||
|  | @ -161,7 +159,6 @@ SCENARIO("Flow: Flow math for non-bridges", "[Flow]") { | |||
| SCENARIO("Flow: Flow math for bridges", "[Flow]") { | ||||
|     GIVEN("Nozzle Diameter of 0.4, a desired width of 1mm and layer height of 0.5") { | ||||
|         auto width = ConfigOptionFloatOrPercent(1.0, false); | ||||
|         float spacing			= 0.4f; | ||||
| 		float nozzle_diameter	= 0.4f; | ||||
| 		float bridge_flow		= 1.0f; | ||||
| 		float layer_height		= 0.5f; | ||||
|  |  | |||
|  | @ -14,8 +14,8 @@ using namespace Slic3r; | |||
| /// Helper method to find the tool used for the brim (always the first extrusion)
 | ||||
| static int get_brim_tool(const std::string &gcode) | ||||
| { | ||||
|     int brim_tool = -1; | ||||
|     int tool = -1; | ||||
|     int brim_tool	= -1; | ||||
|     int tool		= -1; | ||||
| 	GCodeReader parser; | ||||
|     parser.parse_buffer(gcode, [&tool, &brim_tool] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) | ||||
|     { | ||||
|  | @ -29,7 +29,7 @@ static int get_brim_tool(const std::string &gcode) | |||
|     return brim_tool; | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("Skirt height is honored") { | ||||
| TEST_CASE("Skirt height is honored", "[Skirt]") { | ||||
|     DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); | ||||
|     config.set_deserialize({ | ||||
|     	{ "skirts",					1 }, | ||||
|  | @ -60,7 +60,7 @@ TEST_CASE("Skirt height is honored") { | |||
|     REQUIRE(layers_with_skirt.size() == (size_t)config.opt_int("skirt_height")); | ||||
| } | ||||
| 
 | ||||
| SCENARIO("Original Slic3r Skirt/Brim tests", "[!mayfail]") { | ||||
| SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") { | ||||
|     GIVEN("A default configuration") { | ||||
| 	    DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); | ||||
| 		config.set_num_extruders(4); | ||||
|  | @ -73,7 +73,8 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[!mayfail]") { | |||
|         	{ "first_layer_speed", 				"100%" }, | ||||
|         	// remove noise from top/solid layers
 | ||||
|         	{ "top_solid_layers", 				0 }, | ||||
|         	{ "bottom_solid_layers", 			1 } | ||||
|         	{ "bottom_solid_layers", 			1 }, | ||||
| 			{ "start_gcode",					"T[initial_tool]\n" } | ||||
|         }); | ||||
| 
 | ||||
|         WHEN("Brim width is set to 5") { | ||||
|  | @ -120,25 +121,29 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[!mayfail]") { | |||
| 
 | ||||
|         WHEN("Perimeter extruder = 2 and support extruders = 3") { | ||||
|             THEN("Brim is printed with the extruder used for the perimeters of first object") { | ||||
| 		        std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, { | ||||
| 				config.set_deserialize({ | ||||
| 					{ "skirts", 					0 }, | ||||
| 					{ "brim_width", 				5 }, | ||||
| 					{ "perimeter_extruder", 		2 }, | ||||
| 					{ "support_material_extruder", 	3 } | ||||
| 		        }); | ||||
| 					{ "support_material_extruder", 	3 }, | ||||
| 					{ "infill_extruder", 			4 } | ||||
| 				}); | ||||
| 		        std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config); | ||||
|                 int tool = get_brim_tool(gcode); | ||||
|                 REQUIRE(tool == config.opt_int("perimeter_extruder") - 1); | ||||
|             } | ||||
|         } | ||||
|         WHEN("Perimeter extruder = 2, support extruders = 3, raft is enabled") { | ||||
|             THEN("brim is printed with same extruder as skirt") { | ||||
| 		        std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, { | ||||
| 		            { "skirts", 					0 }, | ||||
| 		            { "brim_width", 				5 }, | ||||
| 		            { "perimeter_extruder", 		2 }, | ||||
| 		            { "support_material_extruder", 	3 }, | ||||
| 		            { "raft_layers", 				1 } | ||||
| 		        }); | ||||
| 				config.set_deserialize({ | ||||
| 					{ "skirts",						0 }, | ||||
| 					{ "brim_width", 				5 }, | ||||
| 					{ "perimeter_extruder", 		2 }, | ||||
| 					{ "support_material_extruder", 	3 }, | ||||
| 					{ "infill_extruder", 			4 }, | ||||
| 					{ "raft_layers", 				1 } | ||||
| 				}); | ||||
| 		        std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config); | ||||
|                 int tool = get_brim_tool(gcode); | ||||
|                 REQUIRE(tool == config.opt_int("support_material_extruder") - 1); | ||||
|             } | ||||
|  | @ -200,6 +205,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[!mayfail]") { | |||
| 	            { "infill_extruder", 			3 },			// ensure that a tool command gets emitted.
 | ||||
| 	            { "cooling", 					false },		// to prevent speeds to be altered
 | ||||
| 	            { "first_layer_speed", 			"100%" },		// to prevent speeds to be altered
 | ||||
| 				{ "start_gcode",				"T[initial_tool]\n" } | ||||
|         	}); | ||||
| 
 | ||||
|             THEN("overhang generates?") { | ||||
|  |  | |||
|  | @ -2,7 +2,9 @@ get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) | |||
| add_executable(${_TEST_NAME}_tests  | ||||
| 	${_TEST_NAME}_tests.cpp | ||||
| 	test_3mf.cpp | ||||
| 	test_clipper_offset.cpp | ||||
| 	test_config.cpp | ||||
| #	test_elephant_foot_compensation.cpp | ||||
| 	test_geometry.cpp | ||||
| 	test_polygon.cpp | ||||
| 	test_stl.cpp | ||||
|  |  | |||
							
								
								
									
										214
									
								
								tests/libslic3r/test_clipper_offset.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								tests/libslic3r/test_clipper_offset.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,214 @@ | |||
| #include <catch2/catch.hpp> | ||||
| 
 | ||||
| #include <iostream> | ||||
| #include <boost/filesystem.hpp> | ||||
| 
 | ||||
| #include "libslic3r/ClipperUtils.hpp" | ||||
| #include "libslic3r/ExPolygon.hpp" | ||||
| #include "libslic3r/SVG.hpp" | ||||
| 
 | ||||
| using namespace Slic3r; | ||||
| 
 | ||||
| #define TESTS_EXPORT_SVGS | ||||
| 
 | ||||
| SCENARIO("Constant offset", "[ClipperUtils]") { | ||||
| 	coord_t s = 1000000; | ||||
| 	GIVEN("20mm box") { | ||||
| 		ExPolygon box20mm; | ||||
| 		box20mm.contour.points = { { 0, 0 }, { 20 * s, 0 }, { 20 * s, 20 * s}, { 0, 20 * s} }; | ||||
| 		std::vector<float> deltas_plus(box20mm.contour.points.size(), 1. * s); | ||||
| 		std::vector<float> deltas_minus(box20mm.contour.points.size(), - 1. * s); | ||||
| 		Polygons output; | ||||
| 		WHEN("Slic3r::offset()") { | ||||
| 			for (double miter : { 2.0, 1.5, 1.2 }) { | ||||
| 				DYNAMIC_SECTION("plus 1mm, miter " << miter << "x") { | ||||
| 					output = Slic3r::offset(box20mm, 1. * s, ClipperLib::jtMiter, miter); | ||||
| #ifdef TESTS_EXPORT_SVGS | ||||
| 					{ | ||||
| 						SVG svg(debug_out_path("constant_offset_box20mm_plus1mm_miter%lf.svg", miter).c_str(), get_extents(output)); | ||||
| 						svg.draw(box20mm, "blue"); | ||||
| 						svg.draw_outline(output, "black", coord_t(scale_(0.01))); | ||||
| 					} | ||||
| #endif | ||||
| 					THEN("Area is 22^2mm2") { | ||||
| 						REQUIRE(output.size() == 1); | ||||
| 						REQUIRE(output.front().area() == Approx(22. * 22. * s * s)); | ||||
| 					} | ||||
| 				} | ||||
| 				DYNAMIC_SECTION("minus 1mm, miter " << miter << "x") { | ||||
| 					output = Slic3r::offset(box20mm, - 1. * s, ClipperLib::jtMiter, miter); | ||||
| #ifdef TESTS_EXPORT_SVGS | ||||
| 					{ | ||||
| 						SVG svg(debug_out_path("constant_offset_box20mm_minus1mm_miter%lf.svg", miter).c_str(), get_extents(output)); | ||||
| 						svg.draw(box20mm, "blue"); | ||||
| 						svg.draw_outline(output, "black", coord_t(scale_(0.01))); | ||||
| 					} | ||||
| #endif | ||||
| 					THEN("Area is 18^2mm2") { | ||||
| 						REQUIRE(output.size() == 1); | ||||
| 						REQUIRE(output.front().area() == Approx(18. * 18. * s * s)); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		WHEN("Slic3r::variable_offset_outer/inner") { | ||||
| 			for (double miter : { 2.0, 1.5, 1.2 }) { | ||||
| 				DYNAMIC_SECTION("plus 1mm, miter " << miter << "x") { | ||||
| 					output = Slic3r::variable_offset_outer(box20mm, { deltas_plus }, miter); | ||||
| #ifdef TESTS_EXPORT_SVGS | ||||
| 					{ | ||||
| 						SVG svg(debug_out_path("variable_offset_box20mm_plus1mm_miter%lf.svg", miter).c_str(), get_extents(output)); | ||||
| 						svg.draw(box20mm, "blue"); | ||||
| 						svg.draw_outline(output, "black", coord_t(scale_(0.01))); | ||||
| 					} | ||||
| #endif | ||||
| 					THEN("Area is 22^2mm2") { | ||||
| 						REQUIRE(output.size() == 1); | ||||
| 						REQUIRE(output.front().area() == Approx(22. * 22. * s * s)); | ||||
| 					} | ||||
| 				} | ||||
| 				DYNAMIC_SECTION("minus 1mm, miter " << miter << "x") { | ||||
| 					output = Slic3r::variable_offset_inner(box20mm, { deltas_minus }, miter); | ||||
| #ifdef TESTS_EXPORT_SVGS | ||||
| 					{ | ||||
| 						SVG svg(debug_out_path("variable_offset_box20mm_minus1mm_miter%lf.svg", miter).c_str(), get_extents(output)); | ||||
| 						svg.draw(box20mm, "blue"); | ||||
| 						svg.draw_outline(output, "black", coord_t(scale_(0.01))); | ||||
| 					} | ||||
| #endif | ||||
| 					THEN("Area is 18^2mm2") { | ||||
| 						REQUIRE(output.size() == 1); | ||||
| 						REQUIRE(output.front().area() == Approx(18. * 18. * s * s)); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	GIVEN("20mm box with 10mm hole") { | ||||
| 		ExPolygon box20mm; | ||||
| 		box20mm.contour.points = { { 0, 0 }, { 20 * s, 0 }, { 20 * s, 20 * s}, { 0, 20 * s} }; | ||||
| 		box20mm.holes.emplace_back(Slic3r::Polygon({ { 5 * s, 5 * s }, { 5 * s, 15 * s}, { 15 * s, 15 * s}, { 15 * s, 5 * s }  })); | ||||
| 		std::vector<float> deltas_plus(box20mm.contour.points.size(), 1. * s); | ||||
| 		std::vector<float> deltas_minus(box20mm.contour.points.size(), -1. * s); | ||||
| 		ExPolygons output; | ||||
| 		SECTION("Slic3r::offset()") { | ||||
| 			for (double miter : { 2.0, 1.5, 1.2 }) { | ||||
| 				DYNAMIC_SECTION("miter " << miter << "x") { | ||||
| 					WHEN("plus 1mm") { | ||||
| 						output = Slic3r::offset_ex(box20mm, 1. * s, ClipperLib::jtMiter, miter); | ||||
| #ifdef TESTS_EXPORT_SVGS | ||||
| 						{ | ||||
| 							SVG svg(debug_out_path("constant_offset_box20mm_10mm_hole_plus1mm_miter%lf.svg", miter).c_str(), get_extents(output)); | ||||
| 							svg.draw(box20mm, "blue"); | ||||
| 							svg.draw_outline(to_polygons(output), "black", coord_t(scale_(0.01))); | ||||
| 						} | ||||
| #endif | ||||
| 						THEN("Area is 22^2-8^2 mm2") { | ||||
| 							REQUIRE(output.size() == 1); | ||||
| 							REQUIRE(output.front().area() == Approx((22. * 22. - 8. * 8.) * s * s)); | ||||
| 						} | ||||
| 					} | ||||
| 					WHEN("minus 1mm") { | ||||
| 						output = Slic3r::offset_ex(box20mm, - 1. * s, ClipperLib::jtMiter, miter); | ||||
| #ifdef TESTS_EXPORT_SVGS | ||||
| 						{ | ||||
| 							SVG svg(debug_out_path("constant_offset_box20mm_10mm_hole_minus1mm_miter%lf.svg", miter).c_str(), get_extents(output)); | ||||
| 							svg.draw(box20mm, "blue"); | ||||
| 							svg.draw_outline(to_polygons(output), "black", coord_t(scale_(0.01))); | ||||
| 						} | ||||
| #endif | ||||
| 						THEN("Area is 18^2-12^2 mm2") { | ||||
| 							REQUIRE(output.size() == 1); | ||||
| 							REQUIRE(output.front().area() == Approx((18. * 18. - 12. * 12.) * s * s)); | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		SECTION("Slic3r::variable_offset_outer()") { | ||||
| 			for (double miter : { 2.0, 1.5, 1.2 }) { | ||||
| 				DYNAMIC_SECTION("miter " << miter << "x") { | ||||
| 					WHEN("plus 1mm") { | ||||
| 						output = Slic3r::variable_offset_outer_ex(box20mm, { deltas_plus, deltas_plus }, miter); | ||||
| #ifdef TESTS_EXPORT_SVGS | ||||
| 						{ | ||||
| 							SVG svg(debug_out_path("variable_offset_box20mm_10mm_hole_plus1mm_miter%lf.svg", miter).c_str(), get_extents(output)); | ||||
| 							svg.draw(box20mm, "blue"); | ||||
| 							svg.draw_outline(to_polygons(output), "black", coord_t(scale_(0.01))); | ||||
| 						} | ||||
| #endif | ||||
| 						THEN("Area is 22^2-8^2 mm2") { | ||||
| 							REQUIRE(output.size() == 1); | ||||
| 							REQUIRE(output.front().area() == Approx((22. * 22. - 8. * 8.) * s * s)); | ||||
| 						} | ||||
| 					} | ||||
| 					WHEN("minus 1mm") { | ||||
| 						output = Slic3r::variable_offset_inner_ex(box20mm, { deltas_minus, deltas_minus }, miter); | ||||
| #ifdef TESTS_EXPORT_SVGS | ||||
| 						{ | ||||
| 							SVG svg(debug_out_path("variable_offset_box20mm_10mm_hole_minus1mm_miter%lf.svg", miter).c_str(), get_extents(output)); | ||||
| 							svg.draw(box20mm, "blue"); | ||||
| 							svg.draw_outline(to_polygons(output), "black", coord_t(scale_(0.01))); | ||||
| 						} | ||||
| #endif | ||||
| 						THEN("Area is 18^2-12^2 mm2") { | ||||
| 							REQUIRE(output.size() == 1); | ||||
| 							REQUIRE(output.front().area() == Approx((18. * 18. - 12. * 12.) * s * s)); | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	GIVEN("20mm right angle triangle") { | ||||
| 		ExPolygon triangle20mm; | ||||
| 		triangle20mm.contour.points = { { 0, 0 }, { 20 * s, 0 }, { 0, 20 * s} }; | ||||
| 		Polygons output; | ||||
| 		double offset = 1.; | ||||
| 		// Angle of the sharp corner bisector.
 | ||||
| 		double angle_bisector = M_PI / 8.; | ||||
| 		// Area tapered by mitering one sharp corner.
 | ||||
| 		double area_tapered = pow(offset * (1. / sin(angle_bisector) - 1.), 2.) * tan(angle_bisector); | ||||
| 		double l_triangle_side_offsetted = 20. + offset * (1. + 1. / tan(angle_bisector)); | ||||
| 		double area_offsetted = (0.5 * l_triangle_side_offsetted * l_triangle_side_offsetted - 2. * area_tapered) * s * s; | ||||
| 		SECTION("Slic3r::offset()") { | ||||
| 			for (double miter : { 2.0, 1.5, 1.2 }) { | ||||
| 				DYNAMIC_SECTION("Outer offset 1mm, miter " << miter << "x") { | ||||
| 					output = Slic3r::offset(triangle20mm, offset * s, ClipperLib::jtMiter, 2.0); | ||||
| #ifdef TESTS_EXPORT_SVGS | ||||
| 					{ | ||||
| 						SVG svg(debug_out_path("constant_offset_triangle20mm_plus1mm_miter%lf.svg", miter).c_str(), get_extents(output)); | ||||
| 						svg.draw(triangle20mm, "blue"); | ||||
| 						svg.draw_outline(output, "black", coord_t(scale_(0.01))); | ||||
| 					} | ||||
| #endif | ||||
| 					THEN("Area matches") { | ||||
| 						REQUIRE(output.size() == 1); | ||||
| 						REQUIRE(output.front().area() == Approx(area_offsetted)); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		SECTION("Slic3r::variable_offset_outer()") { | ||||
| 			std::vector<float> deltas(triangle20mm.contour.points.size(), 1. * s); | ||||
| 			for (double miter : { 2.0, 1.5, 1.2 }) { | ||||
| 				DYNAMIC_SECTION("Outer offset 1mm, miter " << miter << "x") { | ||||
| 					output = Slic3r::variable_offset_outer(triangle20mm, { deltas }, 2.0); | ||||
| #ifdef TESTS_EXPORT_SVGS | ||||
| 					{ | ||||
| 						SVG svg(debug_out_path("variable_offset_triangle20mm_plus1mm_miter%lf.svg", miter).c_str(), get_extents(output)); | ||||
| 						svg.draw(triangle20mm, "blue"); | ||||
| 						svg.draw_outline(output, "black", coord_t(scale_(0.01))); | ||||
| 					} | ||||
| #endif | ||||
| 					THEN("Area matches") { | ||||
| 						REQUIRE(output.size() == 1); | ||||
| 						REQUIRE(output.front().area() == Approx(area_offsetted)); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 bubnikv
						bubnikv