| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #include "Adaptive.hpp" |
| | #include <iostream> |
| | #include <cmath> |
| | #include <cstring> |
| | #include <ctime> |
| | #include <algorithm> |
| | #include <numbers> |
| | #include <optional> |
| |
|
| | namespace ClipperLib |
| | { |
| | void TranslatePath(const Path& input, Path& output, IntPoint delta); |
| | } |
| |
|
| | namespace AdaptivePath |
| | { |
| | using namespace ClipperLib; |
| | using namespace std; |
| | #define SAME_POINT_TOL_SQRD_SCALED 4.0 |
| | #define UNUSED(expr) (void)(expr) |
| |
|
| | |
| | |
| | |
| |
|
| | inline double DistanceSqrd(const IntPoint& pt1, const IntPoint& pt2) |
| | { |
| | double Dx = double(pt1.X - pt2.X); |
| | double dy = double(pt1.Y - pt2.Y); |
| | return (Dx * Dx + dy * dy); |
| | } |
| |
|
| | inline bool SetSegmentLength(const IntPoint& pt1, IntPoint& pt2, double new_length) |
| | { |
| | double Dx = double(pt2.X - pt1.X); |
| | double dy = double(pt2.Y - pt1.Y); |
| | double l = sqrt(Dx * Dx + dy * dy); |
| | if (l > 0.0) { |
| | pt2.X = long(pt1.X + new_length * Dx / l); |
| | pt2.Y = long(pt1.Y + new_length * dy / l); |
| | return true; |
| | } |
| | return false; |
| | } |
| |
|
| | inline bool HasAnyPath(const Paths& paths) |
| | { |
| | for (Paths::size_type i = 0; i < paths.size(); i++) { |
| | if (!paths[i].empty()) { |
| | return true; |
| | } |
| | } |
| | return false; |
| | } |
| |
|
| | inline double averageDV(const vector<double>& vec) |
| | { |
| | double s = 0; |
| | std::size_t size = vec.size(); |
| | if (size == 0) { |
| | return 0; |
| | } |
| | for (std::size_t i = 0; i < size; i++) { |
| | s += vec[i]; |
| | } |
| | return s / double(size); |
| | } |
| |
|
| | inline DoublePoint rotate(const DoublePoint& in, double rad) |
| | { |
| | double c = cos(rad); |
| | double s = sin(rad); |
| | return DoublePoint(c * in.X - s * in.Y, s * in.X + c * in.Y); |
| | } |
| |
|
| | |
| | inline double PathLength(const Path& path) |
| | { |
| | double len = 0; |
| | if (path.size() < 2) { |
| | return len; |
| | } |
| | for (size_t i = 1; i < path.size(); i++) { |
| | len += sqrt(DistanceSqrd(path[i - 1], path[i])); |
| | } |
| | return len; |
| | } |
| |
|
| | inline double PointSideOfLine(const IntPoint& p1, const IntPoint& p2, const IntPoint& pt) |
| | { |
| | return double((pt.X - p1.X) * (p2.Y - p1.Y) - (pt.Y - p2.Y) * (p2.X - p1.X)); |
| | } |
| |
|
| | inline double Angle3Points(const DoublePoint& p1, const DoublePoint& p2, const DoublePoint& p3) |
| | { |
| | double t1 = atan2(p2.Y - p1.Y, p2.X - p1.X); |
| | double t2 = atan2(p3.Y - p2.Y, p3.X - p2.X); |
| | double a = fabs(t2 - t1); |
| | return min(a, 2 * std::numbers::pi - a); |
| | } |
| |
|
| | inline DoublePoint DirectionV(const IntPoint& pt1, const IntPoint& pt2) |
| | { |
| | double DX = double(pt2.X - pt1.X); |
| | double DY = double(pt2.Y - pt1.Y); |
| | double l = sqrt(DX * DX + DY * DY); |
| | if (l < NTOL) { |
| | return DoublePoint(0, 0); |
| | } |
| | return DoublePoint(DX / l, DY / l); |
| | } |
| |
|
| | inline void NormalizeV(DoublePoint& pt) |
| | { |
| | double len = sqrt(pt.X * pt.X + pt.Y * pt.Y); |
| | if (len > NTOL) { |
| | pt.X /= len; |
| | pt.Y /= len; |
| | } |
| | } |
| |
|
| | inline DoublePoint GetPathDirectionV(const Path& pth, size_t pointIndex) |
| | { |
| | if (pth.size() < 2) { |
| | return DoublePoint(0, 0); |
| | } |
| | const IntPoint& p1 = pth.at(pointIndex > 0 ? pointIndex - 1 : pth.size() - 1); |
| | const IntPoint& p2 = pth.at(pointIndex); |
| | return DirectionV(p1, p2); |
| | } |
| |
|
| | |
| | bool isClose(const IntPoint& a, const IntPoint& b) |
| | { |
| | return abs(a.X - b.X) <= 1 && abs(a.Y - b.Y) <= 1; |
| | } |
| |
|
| | |
| | void filterCloseValues(Paths& ppg) |
| | { |
| | for (auto& pth : ppg) { |
| | while (true) { |
| | auto i = std::adjacent_find(pth.begin(), pth.end(), isClose); |
| | if (i == pth.end()) { |
| | break; |
| | } |
| | pth.erase(i); |
| | } |
| | |
| | |
| | while (pth.size() > 1 && isClose(pth.front(), pth.back())) { |
| | pth.pop_back(); |
| | } |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| |
|
| | class BoundBox |
| | { |
| | public: |
| | BoundBox() |
| | { |
| | minX = 0; |
| | maxX = 0; |
| | minY = 0; |
| | maxY = 0; |
| | } |
| |
|
| | |
| | BoundBox(const IntPoint& p1) |
| | { |
| | minX = p1.X; |
| | maxX = p1.X; |
| | minY = p1.Y; |
| | maxY = p1.Y; |
| | } |
| |
|
| | void SetFirstPoint(const IntPoint& p1) |
| | { |
| | minX = p1.X; |
| | maxX = p1.X; |
| | minY = p1.Y; |
| | maxY = p1.Y; |
| | } |
| |
|
| | |
| | void AddPoint(const IntPoint& pt) |
| | { |
| | minX = min(pt.X, minX); |
| | maxX = max(pt.X, maxX); |
| | minY = min(pt.Y, minY); |
| | maxY = max(pt.Y, maxY); |
| | } |
| |
|
| | |
| | BoundBox(const IntPoint& p1, const IntPoint& p2) |
| | { |
| | if (p1.X < p2.X) { |
| | minX = p1.X; |
| | maxX = p2.X; |
| | } |
| | else { |
| | minX = p2.X; |
| | maxX = p1.X; |
| | } |
| | if (p1.Y < p2.Y) { |
| | minY = p1.Y; |
| | maxY = p2.Y; |
| | } |
| | else { |
| | minY = p2.Y; |
| | maxY = p1.Y; |
| | } |
| | } |
| |
|
| | |
| | BoundBox(const IntPoint& center, long radius) |
| | { |
| | minX = center.X - radius; |
| | maxX = center.X + radius; |
| | minY = center.Y - radius; |
| | maxY = center.Y + radius; |
| | } |
| |
|
| | |
| | inline bool CollidesWith(const BoundBox& bb2) |
| | { |
| | return minX <= bb2.maxX && maxX >= bb2.minX && minY <= bb2.maxY && maxY >= bb2.minY; |
| | } |
| |
|
| | |
| | inline bool Contains(const BoundBox& bb2) |
| | { |
| | return minX <= bb2.minX && maxX >= bb2.maxX && minY <= bb2.minY && maxY >= bb2.maxY; |
| | } |
| |
|
| | ClipperLib::cInt minX; |
| | ClipperLib::cInt maxX; |
| | ClipperLib::cInt minY; |
| | ClipperLib::cInt maxY; |
| | }; |
| |
|
| | std::ostream& operator<<(std::ostream& s, const BoundBox& p) |
| | { |
| | s << "(" << p.minX << "," << p.minY << ") - (" << p.maxX << "," << p.maxY << ")"; |
| | return s; |
| | } |
| |
|
| | int getPathNestingLevel(const Path& path, const Paths& paths) |
| | { |
| | int nesting = 0; |
| | for (const auto& other : paths) { |
| | if (!path.empty() && PointInPolygon(path.front(), other) != 0) { |
| | nesting++; |
| | } |
| | } |
| | return nesting; |
| | } |
| |
|
| | void appendDirectChildPaths(Paths& outPaths, const Path& path, const Paths& paths) |
| | { |
| | int nesting = getPathNestingLevel(path, paths); |
| | for (const auto& other : paths) { |
| | if (!path.empty() && !other.empty() && PointInPolygon(other.front(), path) != 0) { |
| | if (getPathNestingLevel(other, paths) == nesting + 1) { |
| | outPaths.push_back(other); |
| | } |
| | } |
| | } |
| | } |
| |
|
| | void AverageDirection(const vector<DoublePoint>& unityVectors, DoublePoint& output) |
| | { |
| | std::size_t size = unityVectors.size(); |
| | output.X = 0; |
| | output.Y = 0; |
| | |
| | for (std::size_t i = 0; i < size; i++) { |
| | DoublePoint v = unityVectors[i]; |
| | output.X += v.X; |
| | output.Y += v.Y; |
| | } |
| | |
| | double magnitude = sqrt(output.X * output.X + output.Y * output.Y); |
| | output.X /= magnitude; |
| | output.Y /= magnitude; |
| | } |
| |
|
| | double DistancePointToLineSegSquared( |
| | const IntPoint& p1, |
| | const IntPoint& p2, |
| | const IntPoint& pt, |
| | IntPoint& closestPoint, |
| | double& ptParameter, |
| | bool clamp = true |
| | ) |
| | { |
| | double D21X = double(p2.X - p1.X); |
| | double D21Y = double(p2.Y - p1.Y); |
| | double DP1X = double(pt.X - p1.X); |
| | double DP1Y = double(pt.Y - p1.Y); |
| | double lsegLenSqr = D21X * D21X + D21Y * D21Y; |
| | if (lsegLenSqr == 0) { |
| | closestPoint = p1; |
| | ptParameter = 0; |
| | return DP1X * DP1X + DP1Y * DP1Y; |
| | } |
| | double parameter = DP1X * D21X + DP1Y * D21Y; |
| | if (clamp) { |
| | |
| | if (parameter < 0) { |
| | parameter = 0; |
| | } |
| | else if (parameter > lsegLenSqr) { |
| | parameter = lsegLenSqr; |
| | } |
| | } |
| | |
| | ptParameter = parameter / lsegLenSqr; |
| | closestPoint.X = long(p1.X + ptParameter * D21X); |
| | closestPoint.Y = long(p1.Y + ptParameter * D21Y); |
| | |
| | double DX = double(pt.X - closestPoint.X); |
| | double DY = double(pt.Y - closestPoint.Y); |
| | return DX * DX + DY * DY; |
| | } |
| |
|
| | void ScaleUpPaths(Paths& paths, long scaleFactor) |
| | { |
| | for (auto& pth : paths) { |
| | for (auto& pt : pth) { |
| | pt.X *= scaleFactor; |
| | pt.Y *= scaleFactor; |
| | } |
| | } |
| | } |
| |
|
| | void ScaleDownPaths(Paths& paths, long scaleFactor) |
| | { |
| | for (auto& pth : paths) { |
| | for (auto& pt : pth) { |
| | pt.X /= scaleFactor; |
| | pt.Y /= scaleFactor; |
| | } |
| | } |
| | } |
| |
|
| |
|
| | double DistancePointToPathsSqrd( |
| | const Paths& paths, |
| | const IntPoint& pt, |
| | IntPoint& closestPointOnPath, |
| | size_t& clpPathIndex, |
| | size_t& clpSegmentIndex, |
| | double& clpParameter |
| | ) |
| | { |
| | double minDistSq = __DBL_MAX__; |
| | IntPoint clp; |
| | |
| | for (Path::size_type i = 0; i < paths.size(); i++) { |
| | const Path* path = &paths[i]; |
| | Path::size_type size = path->size(); |
| | |
| | for (Path::size_type j = 0; j < size; j++) { |
| | double ptPar; |
| | double distSq = DistancePointToLineSegSquared( |
| | path->at(j > 0 ? j - 1 : size - 1), |
| | path->at(j), |
| | pt, |
| | clp, |
| | ptPar |
| | ); |
| | if (distSq < minDistSq) { |
| | clpPathIndex = i; |
| | clpSegmentIndex = j; |
| | clpParameter = ptPar; |
| | closestPointOnPath = clp; |
| | minDistSq = distSq; |
| | } |
| | } |
| | } |
| | return minDistSq; |
| | } |
| |
|
| | |
| | void CleanPath(const Path& inp, Path& outpt, double tolerance) |
| | { |
| | if (inp.size() < 3) { |
| | outpt = inp; |
| | return; |
| | } |
| | outpt.clear(); |
| | Path tmp; |
| | CleanPolygon(inp, tmp, tolerance); |
| | long size = long(tmp.size()); |
| |
|
| | |
| | |
| | if (size <= 2) { |
| | outpt.push_back(inp.front()); |
| | outpt.push_back(inp.back()); |
| | return; |
| | } |
| |
|
| | |
| | double clpPar = 0; |
| | size_t clpSegmentIndex = 0; |
| | size_t clpPathIndex = 0; |
| | Paths tmpPaths; |
| | tmpPaths.push_back(tmp); |
| | IntPoint clp; |
| | |
| | DistancePointToPathsSqrd(tmpPaths, inp.front(), clp, clpPathIndex, clpSegmentIndex, clpPar); |
| |
|
| |
|
| | |
| | if (DistanceSqrd(clp, tmp.at(clpSegmentIndex)) > 0 |
| | && DistanceSqrd(clp, tmp.at(clpSegmentIndex > 0 ? clpSegmentIndex - 1 : size - 1)) > 0) { |
| | outpt.push_back(clp); |
| | } |
| |
|
| | |
| | long index; |
| | for (long i = 0; i < size; i++) { |
| | index = static_cast<long>(clpSegmentIndex + i); |
| | if (index >= size) { |
| | index -= size; |
| | } |
| | outpt.push_back(tmp.at(index)); |
| | } |
| |
|
| |
|
| | if (DistanceSqrd(outpt.front(), inp.front()) > SAME_POINT_TOL_SQRD_SCALED) { |
| | outpt.insert(outpt.begin(), inp.front()); |
| | } |
| |
|
| | if (DistanceSqrd(outpt.back(), inp.back()) > SAME_POINT_TOL_SQRD_SCALED) { |
| | outpt.push_back(inp.back()); |
| | } |
| | } |
| |
|
| | bool Circle2CircleIntersect( |
| | const IntPoint& c1, |
| | const IntPoint& c2, |
| | double radius, |
| | pair<DoublePoint, DoublePoint>& intersections |
| | ) |
| | { |
| | double DX = double(c2.X - c1.X); |
| | double DY = double(c2.Y - c1.Y); |
| | double d = sqrt(DX * DX + DY * DY); |
| | if (d < NTOL) { |
| | return false; |
| | } |
| | if (d >= radius) { |
| | return false; |
| | } |
| | double a_2 = sqrt(4 * radius * radius - d * d) / 2.0; |
| | intersections.first |
| | = DoublePoint(0.5 * (c1.X + c2.X) - DY * a_2 / d, 0.5 * (c1.Y + c2.Y) + DX * a_2 / d); |
| | intersections.second |
| | = DoublePoint(0.5 * (c1.X + c2.X) + DY * a_2 / d, 0.5 * (c1.Y + c2.Y) - DX * a_2 / d); |
| | return true; |
| | } |
| |
|
| | bool Line2CircleIntersect( |
| | const IntPoint& c, |
| | double radius, |
| | const IntPoint& p1, |
| | const IntPoint& p2, |
| | vector<DoublePoint>& result, |
| | bool clamp = true |
| | ) |
| | { |
| | |
| |
|
| | |
| | if (clamp) { |
| | BoundBox cBB(c, (ClipperLib::cInt)radius + 1); |
| | BoundBox sBB(p1, p2); |
| | if (!sBB.CollidesWith(cBB)) { |
| | return false; |
| | } |
| | } |
| |
|
| | double dx = double(p2.X - p1.X); |
| | double dy = double(p2.Y - p1.Y); |
| | double lcx = double(p1.X - c.X); |
| | double lcy = double(p1.Y - c.Y); |
| | double a = dx * dx + dy * dy; |
| | double b = 2 * dx * lcx + 2 * dy * lcy; |
| | double C = lcx * lcx + lcy * lcy - radius * radius; |
| | double sq = b * b - 4 * a * C; |
| | if (sq < 0) { |
| | return false; |
| | } |
| | sq = sqrt(sq); |
| | double t1 = (-b - sq) / (2 * a); |
| | double t2 = (-b + sq) / (2 * a); |
| | result.clear(); |
| | if (clamp) { |
| | if (t1 >= 0.0 && t1 <= 1.0) { |
| | result.emplace_back(p1.X + t1 * dx, p1.Y + t1 * dy); |
| | } |
| | if (t2 >= 0.0 && t2 <= 1.0) { |
| | result.emplace_back(p1.X + t2 * dx, p1.Y + t2 * dy); |
| | } |
| | } |
| | else { |
| | result.emplace_back(p1.X + t2 * dx, p1.Y + t2 * dy); |
| | result.emplace_back(p1.X + t2 * dx, p1.Y + t2 * dy); |
| | } |
| | return !result.empty(); |
| | } |
| |
|
| | |
| | IntPoint Compute2DPolygonCentroid(const Path& vertices) |
| | { |
| | DoublePoint centroid(0, 0); |
| | double signedArea = 0.0; |
| | double x0 = 0.0; |
| | double y0 = 0.0; |
| | double x1 = 0.0; |
| | double y1 = 0.0; |
| | double a = 0.0; |
| |
|
| | |
| | size_t i = 0; |
| | Path::size_type size = vertices.size(); |
| | for (i = 0; i < size; ++i) { |
| | x0 = double(vertices[i].X); |
| | y0 = double(vertices[i].Y); |
| | x1 = double(vertices[(i + 1) % size].X); |
| | y1 = double(vertices[(i + 1) % size].Y); |
| | a = x0 * y1 - x1 * y0; |
| | signedArea += a; |
| | centroid.X += (x0 + x1) * a; |
| | centroid.Y += (y0 + y1) * a; |
| | } |
| |
|
| | signedArea *= 0.5; |
| | centroid.X /= (6.0 * signedArea); |
| | centroid.Y /= (6.0 * signedArea); |
| | return IntPoint(long(centroid.X), long(centroid.Y)); |
| | } |
| |
|
| | |
| | bool IsPointWithinCutRegion(const Paths& toolBoundPaths, const IntPoint& point) |
| | { |
| | for (size_t i = 0; i < toolBoundPaths.size(); i++) { |
| | int pip = PointInPolygon(point, toolBoundPaths[i]); |
| | if (i == 0 && pip == 0) { |
| | return false; |
| | } |
| | if (i > 0 && pip != 0) { |
| | return false; |
| | } |
| | } |
| | return true; |
| | } |
| |
|
| | |
| | bool IntersectionPoint( |
| | const IntPoint& s1p1, |
| | const IntPoint& s1p2, |
| | const IntPoint& s2p1, |
| | const IntPoint& s2p2, |
| | IntPoint& intersection |
| | ) |
| | { |
| | double S1DX = double(s1p2.X - s1p1.X); |
| | double S1DY = double(s1p2.Y - s1p1.Y); |
| | double S2DX = double(s2p2.X - s2p1.X); |
| | double S2DY = double(s2p2.Y - s2p1.Y); |
| | double d = S1DY * S2DX - S2DY * S1DX; |
| | if (fabs(d) < NTOL) { |
| | return false; |
| | } |
| |
|
| | double LPDX = double(s1p1.X - s2p1.X); |
| | double LPDY = double(s1p1.Y - s2p1.Y); |
| | double p1d = S2DY * LPDX - S2DX * LPDY; |
| | double p2d = S1DY * LPDX - S1DX * LPDY; |
| | if ((d < 0) && (p1d < d || p1d > 0 || p2d < d || p2d > 0)) { |
| | return false; |
| | } |
| | if ((d > 0) && (p1d < 0 || p1d > d || p2d < 0 || p2d > d)) { |
| | return false; |
| | } |
| | double t = p1d / d; |
| | intersection = IntPoint(long(s1p1.X + S1DX * t), long(s1p1.Y + S1DY * t)); |
| | return true; |
| | } |
| |
|
| | |
| | bool IntersectionPoint(const Paths& paths, const IntPoint& p1, const IntPoint& p2, IntPoint& intersection) |
| | { |
| | BoundBox segBB(p1, p2); |
| | for (size_t i = 0; i < paths.size(); i++) { |
| | const Path* path = &paths[i]; |
| | size_t size = path->size(); |
| | if (size < 2) { |
| | continue; |
| | } |
| | BoundBox pathBB(path->front()); |
| | for (size_t j = 0; j < size; j++) { |
| |
|
| | const IntPoint* pp2 = &path->at(j); |
| |
|
| | |
| | pathBB.AddPoint(*pp2); |
| | if (!pathBB.CollidesWith(segBB)) { |
| | continue; |
| | } |
| |
|
| | const IntPoint* pp1 = &path->at(j > 0 ? j - 1 : size - 1); |
| | double LDY = double(p2.Y - p1.Y); |
| | double LDX = double(p2.X - p1.X); |
| | double PDX = double(pp2->X - pp1->X); |
| | double PDY = double(pp2->Y - pp1->Y); |
| | double d = LDY * PDX - PDY * LDX; |
| | if (fabs(d) < NTOL) { |
| | continue; |
| | } |
| |
|
| | double LPDX = double(p1.X - pp1->X); |
| | double LPDY = double(p1.Y - pp1->Y); |
| | double p1d = PDY * LPDX - PDX * LPDY; |
| | double p2d = LDY * LPDX - LDX * LPDY; |
| | if ((d < 0) && (p1d < d || p1d > 0 || p2d < d || p2d > 0)) { |
| | continue; |
| | } |
| | if ((d > 0) && (p1d < 0 || p1d > d || p2d < 0 || p2d > d)) { |
| | continue; |
| | } |
| | double t = p1d / d; |
| | intersection = IntPoint(long(p1.X + LDX * t), long(p1.Y + LDY * t)); |
| | return true; |
| | } |
| | } |
| | return false; |
| | } |
| |
|
| | void SmoothPaths(Paths& paths, double stepSize, long pointCount, long iterations) |
| | { |
| | Paths output; |
| | output.resize(paths.size()); |
| | const long scale = 1000; |
| | const double stepScaled = stepSize * scale; |
| |
|
| | ScaleUpPaths(paths, scale); |
| | vector<pair<size_t /*path index*/, IntPoint>> points; |
| | for (size_t i = 0; i < paths.size(); i++) { |
| | for (const auto& pt : paths[i]) { |
| | if (points.empty()) { |
| | points.emplace_back(i, pt); |
| | continue; |
| | } |
| | const auto back = points.back(); |
| | const IntPoint& lastPt = back.second; |
| |
|
| |
|
| | const double l = sqrt(DistanceSqrd(lastPt, pt)); |
| |
|
| | if (l < 0.5 * stepScaled) { |
| | if (points.size() > 1) { |
| | points.pop_back(); |
| | } |
| | points.emplace_back(i, pt); |
| | continue; |
| | } |
| | size_t lastPathIndex = back.first; |
| | const long steps = max(long(l / stepScaled), 1L); |
| | const long left = pointCount * iterations * 2; |
| | const long right = steps - pointCount * iterations * 2; |
| | for (long idx = 0; idx <= steps; idx++) { |
| | if (idx > left && idx < right) { |
| | idx = right; |
| | continue; |
| | } |
| | const double p = double(idx) / steps; |
| | const IntPoint ptx( |
| | long(lastPt.X + double(pt.X - lastPt.X) * p), |
| | long(lastPt.Y + double(pt.Y - lastPt.Y) * p) |
| | ); |
| |
|
| | if (idx == 0 && DistanceSqrd(back.second, ptx) < scale && points.size() > 1) { |
| | points.pop_back(); |
| | } |
| |
|
| | if (p < 0.5) { |
| | points.emplace_back(lastPathIndex, ptx); |
| | } |
| | else { |
| | points.emplace_back(i, ptx); |
| | } |
| | } |
| | } |
| | } |
| | if (points.empty()) { |
| | return; |
| | } |
| | const long size = long(points.size()); |
| | for (long iter = 0; iter < iterations; iter++) { |
| | for (long i = 1; i < size - 1; i++) { |
| | IntPoint& cp = points[i].second; |
| | IntPoint avgPoint(cp); |
| | long cnt = 1; |
| |
|
| | long ptsToAverage = pointCount; |
| | if (i <= ptsToAverage) { |
| | ptsToAverage = max(i - 1, 0L); |
| | } |
| | else if (i + ptsToAverage >= size - 1) { |
| | ptsToAverage = size - 1 - i; |
| | } |
| | for (long j = i - ptsToAverage; j <= i + ptsToAverage; j++) { |
| | if (j == i) { |
| | continue; |
| | } |
| | long index = j; |
| | if (index < 0) { |
| | index = 0; |
| | } |
| | if (index >= size) { |
| | index = size - 1; |
| | } |
| | IntPoint& p = points[index].second; |
| | avgPoint.X += p.X; |
| | avgPoint.Y += p.Y; |
| | cnt++; |
| | } |
| | cp.X = avgPoint.X / cnt; |
| | cp.Y = avgPoint.Y / cnt; |
| | } |
| | } |
| |
|
| | for (const auto& pr : points) { |
| | output[pr.first].push_back(pr.second); |
| | } |
| | for (size_t i = 0; i < paths.size(); i++) { |
| | CleanPath(output[i], paths[i], 1.4 * scale); |
| | } |
| | ScaleDownPaths(paths, scale); |
| | } |
| |
|
| | bool PopPathWithClosestPoint( |
| | Paths& paths |
| | |
| | , |
| | IntPoint p1, |
| | Path& result |
| | ) |
| | { |
| |
|
| | if (paths.empty()) { |
| | return false; |
| | } |
| |
|
| | double minDistSqrd = __DBL_MAX__; |
| | size_t closestPathIndex = 0; |
| | long closestPointIndex = 0; |
| | for (size_t pathIndex = 0; pathIndex < paths.size(); pathIndex++) { |
| | Path& path = paths.at(pathIndex); |
| | for (size_t i = 0; i < path.size(); i++) { |
| | double dist = DistanceSqrd(p1, path.at(i)); |
| | if (dist < minDistSqrd) { |
| | minDistSqrd = dist; |
| | closestPathIndex = pathIndex; |
| | closestPointIndex = long(i); |
| | } |
| | } |
| | } |
| |
|
| | result.clear(); |
| | |
| | Path& closestPath = paths.at(closestPathIndex); |
| | for (size_t i = 0; i < closestPath.size(); i++) { |
| | long index = closestPointIndex + long(i); |
| | if (index >= long(closestPath.size())) { |
| | index -= long(closestPath.size()); |
| | } |
| | result.push_back(closestPath.at(index)); |
| | } |
| | |
| | paths.erase(paths.begin() + closestPathIndex); |
| | return true; |
| | } |
| |
|
| | void DeduplicatePaths(const Paths& inputs, Paths& outputs) |
| | { |
| | outputs.clear(); |
| | for (const auto& new_pth : inputs) { |
| | bool duplicate = false; |
| | |
| | for (const auto& old_pth : outputs) { |
| | bool all_points_exists = true; |
| | for (const auto pt1 : new_pth) { |
| | bool pointExists = false; |
| | for (const auto pt2 : old_pth) { |
| | if (DistanceSqrd(pt1, pt2) < SAME_POINT_TOL_SQRD_SCALED) { |
| | pointExists = true; |
| | break; |
| | } |
| | } |
| | if (!pointExists) { |
| | all_points_exists = false; |
| | break; |
| | } |
| | } |
| | if (all_points_exists) { |
| | duplicate = true; |
| | break; |
| | } |
| | } |
| |
|
| | if (!duplicate && !new_pth.empty()) { |
| | outputs.push_back(new_pth); |
| | } |
| | } |
| | } |
| |
|
| | void ConnectPaths(Paths input, Paths& output) |
| | { |
| | output.clear(); |
| | bool newPath = true; |
| | Path joined; |
| | while (!input.empty()) { |
| | if (newPath) { |
| | if (!joined.empty()) { |
| | output.push_back(joined); |
| | } |
| | joined.clear(); |
| | for (auto pt : input.front()) { |
| | joined.push_back(pt); |
| | } |
| | input.erase(input.begin()); |
| | newPath = false; |
| | } |
| | bool anyMatch = false; |
| | for (size_t i = 0; i < input.size(); i++) { |
| | Path& n = input.at(i); |
| | if (DistanceSqrd(n.front(), joined.back()) < SAME_POINT_TOL_SQRD_SCALED) { |
| | for (auto pt : n) { |
| | joined.push_back(pt); |
| | } |
| | input.erase(input.begin() + i); |
| | anyMatch = true; |
| | break; |
| | } |
| | else if (DistanceSqrd(n.back(), joined.back()) < SAME_POINT_TOL_SQRD_SCALED) { |
| | ReversePath(n); |
| | for (auto pt : n) { |
| | joined.push_back(pt); |
| | } |
| | input.erase(input.begin() + i); |
| | anyMatch = true; |
| | break; |
| | } |
| | else if (DistanceSqrd(n.front(), joined.front()) < SAME_POINT_TOL_SQRD_SCALED) { |
| | for (auto pt : n) { |
| | joined.insert(joined.begin(), pt); |
| | } |
| | input.erase(input.begin() + i); |
| | anyMatch = true; |
| | break; |
| | } |
| | else if (DistanceSqrd(n.back(), joined.front()) < SAME_POINT_TOL_SQRD_SCALED) { |
| | ReversePath(n); |
| | for (auto pt : n) { |
| | joined.insert(joined.begin(), pt); |
| | } |
| | input.erase(input.begin() + i); |
| | anyMatch = true; |
| | break; |
| | } |
| | } |
| | if (!anyMatch) { |
| | newPath = true; |
| | } |
| | } |
| | if (!joined.empty()) { |
| | output.push_back(joined); |
| | } |
| | } |
| |
|
| | |
| | class PerfCounter |
| | { |
| | public: |
| | PerfCounter(string p_name) |
| | { |
| | name = p_name; |
| | count = 0; |
| | running = false; |
| | start_ticks = 0; |
| | total_ticks = 0; |
| | } |
| | inline void Start() |
| | { |
| | #ifdef DEV_MODE |
| | start_ticks = clock(); |
| | if (running) { |
| | cerr << "PerfCounter already running:" << name << endl; |
| | } |
| | running = true; |
| | #endif |
| | } |
| | inline void Stop() |
| | { |
| | #ifdef DEV_MODE |
| | if (!running) { |
| | cerr << "PerfCounter not running:" << name << endl; |
| | } |
| | total_ticks += clock() - start_ticks; |
| | start_ticks = clock(); |
| | count++; |
| | running = false; |
| | #endif |
| | } |
| | void DumpResults() |
| | { |
| | double total_time = double(total_ticks) / CLOCKS_PER_SEC; |
| | cout << "Perf: " << name.c_str() << " total_time: " << total_time |
| | << " sec, call_count:" << count << " per_call:" << double(total_time / count) << endl; |
| | start_ticks = clock(); |
| | total_ticks = 0; |
| | count = 0; |
| | } |
| |
|
| | private: |
| | string name; |
| | clock_t start_ticks; |
| | clock_t total_ticks; |
| | size_t count; |
| | bool running = false; |
| | }; |
| |
|
| | PerfCounter Perf_ProcessPolyNode("ProcessPolyNode"); |
| | PerfCounter Perf_CalcCutAreaCirc("CalcCutArea"); |
| | PerfCounter Perf_CalcCutAreaClip("CalcCutAreaClip"); |
| | PerfCounter Perf_NextEngagePoint("NextEngagePoint"); |
| | PerfCounter Perf_PointIterations("PointIterations"); |
| | PerfCounter Perf_ExpandCleared("ExpandCleared"); |
| | PerfCounter Perf_DistanceToBoundary("DistanceToBoundary"); |
| | PerfCounter Perf_AppendToolPath("AppendToolPath"); |
| | PerfCounter Perf_IsAllowedToCutTrough("IsAllowedToCutTrough"); |
| | PerfCounter Perf_IsClearPath("IsClearPath"); |
| |
|
| | |
| | |
| | |
| | class ClearedArea |
| | { |
| | public: |
| | ClearedArea(ClipperLib::cInt p_toolRadiusScaled) |
| | { |
| | toolRadiusScaled = p_toolRadiusScaled; |
| | }; |
| |
|
| | void SetClearedPaths(const Paths& paths) |
| | { |
| | clearedPaths = paths; |
| | bboxPathsInvalid = true; |
| | bboxClippedInvalid = true; |
| | } |
| | void ExpandCleared(const Path toClearToolPath) |
| | { |
| | if (toClearToolPath.empty()) { |
| | return; |
| | } |
| | Perf_ExpandCleared.Start(); |
| | clipof.Clear(); |
| | clipof.AddPath(toClearToolPath, JoinType::jtRound, EndType::etOpenRound); |
| | Paths toolCoverPoly; |
| | clipof.Execute(toolCoverPoly, toolRadiusScaled + 1); |
| | clip.Clear(); |
| | clip.AddPaths(clearedPaths, PolyType::ptSubject, true); |
| | clip.AddPaths(toolCoverPoly, PolyType::ptClip, true); |
| | clip.Execute(ClipType::ctUnion, clearedPaths); |
| | CleanPolygons(clearedPaths); |
| | bboxPathsInvalid = true; |
| | bboxClippedInvalid = true; |
| | Perf_ExpandCleared.Stop(); |
| | } |
| |
|
| | |
| | Paths& GetBoundedClearedPaths(const IntPoint& toolPos) |
| | { |
| | BoundBox toolBB(toolPos, toolRadiusScaled); |
| | if (!bboxPathsInvalid && clearedBBPathsInFocus.Contains(toolBB)) { |
| | return clearedBoundedPaths; |
| | } |
| | ClipperLib::cInt delta = focusBBFactor1 * toolRadiusScaled; |
| | clearedBBPathsInFocus.SetFirstPoint(IntPoint(toolPos.X - delta, toolPos.Y - delta)); |
| | clearedBBPathsInFocus.AddPoint(IntPoint(toolPos.X + delta, toolPos.Y + delta)); |
| |
|
| | BoundBox bb(toolPos, focusBBFactor2 * toolRadiusScaled); |
| | clearedBoundedPaths.clear(); |
| | for (const auto& pth : clearedPaths) { |
| | if (pth.size() < 2) { |
| | continue; |
| | } |
| | Path bPath; |
| | size_t size = pth.size(); |
| | for (size_t i = 0; i < size + 1; i++) { |
| | IntPoint last = (i > 0 ? pth[i - 1] : pth.back()); |
| | IntPoint next = i < size ? pth[i] : pth.front(); |
| | BoundBox ptbox(last, next); |
| | if (ptbox.CollidesWith(bb)) { |
| | if (bPath.empty() || bPath.back() != last) { |
| | bPath.push_back(last); |
| | } |
| | bPath.push_back(next); |
| | } |
| | else { |
| | if (!bPath.empty()) { |
| | clearedBoundedPaths.push_back(bPath); |
| | bPath.clear(); |
| | } |
| | } |
| | } |
| | if (!bPath.empty()) { |
| | clearedBoundedPaths.push_back(bPath); |
| | bPath.clear(); |
| | } |
| | } |
| | bboxPathsInvalid = false; |
| | return clearedBoundedPaths; |
| | } |
| |
|
| | |
| | Paths& GetBoundedClearedAreaClipped(const IntPoint& toolPos) |
| | { |
| | BoundBox toolBB(toolPos, toolRadiusScaled); |
| | if (!bboxClippedInvalid && clearedBBClippedInFocus.Contains(toolBB)) { |
| | return clearedBoundedClipped; |
| | } |
| | ClipperLib::cInt delta = focusBBFactor1 * toolRadiusScaled; |
| | clearedBBClippedInFocus.SetFirstPoint(IntPoint(toolPos.X - delta, toolPos.Y - delta)); |
| | clearedBBClippedInFocus.AddPoint(IntPoint(toolPos.X + delta, toolPos.Y + delta)); |
| |
|
| | |
| | ClipperLib::cInt delta2 = focusBBFactor2 * toolRadiusScaled; |
| | Path bbPath; |
| | bbPath.push_back(IntPoint(toolPos.X - delta2, toolPos.Y - delta2)); |
| | bbPath.push_back(IntPoint(toolPos.X + delta2, toolPos.Y - delta2)); |
| | bbPath.push_back(IntPoint(toolPos.X + delta2, toolPos.Y + delta2)); |
| | bbPath.push_back(IntPoint(toolPos.X - delta2, toolPos.Y + delta2)); |
| | clip.Clear(); |
| | clip.AddPath(bbPath, PolyType::ptSubject, true); |
| | clip.AddPaths(clearedPaths, PolyType::ptClip, true); |
| | clip.Execute(ClipType::ctIntersection, clearedBoundedClipped); |
| | bboxClippedInvalid = false; |
| | return clearedBoundedClipped; |
| | } |
| |
|
| | |
| | Paths& GetCleared() |
| | { |
| | return clearedPaths; |
| | } |
| |
|
| | private: |
| | Clipper clip; |
| | ClipperOffset clipof; |
| | Paths clearedPaths; |
| | Paths clearedBoundedClipped; |
| | Paths clearedBoundedPaths; |
| |
|
| | ClipperLib::cInt toolRadiusScaled; |
| | BoundBox clearedBBClippedInFocus; |
| | BoundBox clearedBBPathsInFocus; |
| |
|
| | bool bboxClippedInvalid = false; |
| | bool bboxPathsInvalid = false; |
| | |
| | const ClipperLib::cInt focusBBFactor1 = 8; |
| | const ClipperLib::cInt focusBBFactor2 = 9; |
| | }; |
| |
|
| | |
| | |
| | |
| | class Interpolation |
| | { |
| | public: |
| | const double MIN_ANGLE = -std::numbers::pi / 4; |
| | const double MAX_ANGLE = std::numbers::pi / 4; |
| |
|
| | void clear() |
| | { |
| | m_min.reset(); |
| | m_max.reset(); |
| | } |
| | bool bothSides() |
| | { |
| | return m_min && m_max && m_min->second < 0 && m_max->second >= 0; |
| | } |
| | |
| | void addPoint(double error, std::pair<double, IntPoint> angle, bool allowSkip = false) |
| | { |
| | if (!m_min) { |
| | m_min = {angle, error}; |
| | } |
| | else if (!m_max) { |
| | m_max = {angle, error}; |
| | if (m_min->second > m_max->second) { |
| | auto tmp = m_min; |
| | m_min = m_max; |
| | m_max = tmp; |
| | } |
| | } |
| | else if (bothSides()) { |
| | if (error < 0) { |
| | m_min = {angle, error}; |
| | } |
| | else { |
| | m_max = {angle, error}; |
| | } |
| | } |
| | else { |
| | if (allowSkip && abs(error) > abs(m_min->second) && abs(error) > abs(m_max->second)) { |
| | return; |
| | } |
| | if (abs(m_min->second) > abs(m_max->second)) { |
| | m_min.reset(); |
| | } |
| | else { |
| | m_max.reset(); |
| | } |
| | addPoint(error, angle); |
| | } |
| | } |
| |
|
| | double interpolateAngle() |
| | { |
| | if (!m_min) { |
| | return MIN_ANGLE; |
| | } |
| |
|
| | if (!m_max) { |
| | return MAX_ANGLE; |
| | } |
| | double p = (0 - m_min->second) / (m_max->second - m_min->second); |
| |
|
| | |
| | |
| | |
| | |
| | const double minInterp = .2; |
| | p = max(min(p, 1 - minInterp), minInterp); |
| |
|
| | return m_min->first.first * (1 - p) + m_max->first.first * p; |
| | } |
| |
|
| | double clampAngle(double angle) |
| | { |
| | return max(min(angle, MAX_ANGLE), MIN_ANGLE); |
| | } |
| |
|
| | size_t getPointCount() |
| | { |
| | return (m_min ? 1 : 0) + (m_max ? 1 : 0); |
| | } |
| |
|
| | public: |
| | |
| | std::optional<std::pair<std::pair<double, IntPoint>, double>> m_min; |
| | std::optional<std::pair<std::pair<double, IntPoint>, double>> m_max; |
| | }; |
| |
|
| | |
| | |
| | |
| |
|
| | class EngagePoint |
| | { |
| | public: |
| | struct EngageState |
| | { |
| | size_t currentPathIndex = 0; |
| | size_t currentSegmentIndex = 0; |
| | double segmentPos = 0; |
| | double totalDistance = 0; |
| | double currentPathLength = 0; |
| | int passes = 0; |
| |
|
| | double metric = 0; |
| |
|
| | bool operator<(const EngageState& other) const |
| | { |
| | return (metric < other.metric); |
| | } |
| | }; |
| | EngagePoint(const Paths& p_toolBoundPaths) |
| | { |
| | SetPaths(p_toolBoundPaths); |
| |
|
| | state.currentPathIndex = 0; |
| | state.currentSegmentIndex = 0; |
| | state.segmentPos = 0; |
| | state.totalDistance = 0; |
| | calculateCurrentPathLength(); |
| | } |
| |
|
| | void SetPaths(const Paths& paths) |
| | { |
| | toolBoundPaths = paths; |
| | state.currentPathIndex = 0; |
| | state.currentSegmentIndex = 0; |
| | state.segmentPos = 0; |
| | state.totalDistance = 0; |
| | state.passes = 0; |
| | calculateCurrentPathLength(); |
| | } |
| |
|
| | EngageState GetState() |
| | { |
| | return state; |
| | } |
| |
|
| | void SetState(const EngageState& new_state) |
| | { |
| | state = new_state; |
| | } |
| |
|
| | void ResetPasses() |
| | { |
| | state.passes = 0; |
| | } |
| | void moveToClosestPoint(const IntPoint& pt, double step) |
| | { |
| |
|
| | Path result; |
| | IntPoint current = pt; |
| | |
| | Paths toChain = toolBoundPaths; |
| | toolBoundPaths.clear(); |
| | |
| | |
| | |
| | |
| | while (PopPathWithClosestPoint(toChain, current, result)) { |
| | toolBoundPaths.push_back(result); |
| | if (!result.empty()) { |
| | current = result.back(); |
| | } |
| | } |
| |
|
| | double minDistSq = __DBL_MAX__; |
| | size_t minPathIndex = state.currentPathIndex; |
| | size_t minSegmentIndex = state.currentSegmentIndex; |
| | double minSegmentPos = state.segmentPos; |
| | state.totalDistance = 0; |
| | for (;;) { |
| | while (moveForward(step)) { |
| | double distSqrd = DistanceSqrd(pt, getCurrentPoint()); |
| | if (distSqrd < minDistSq) { |
| | minDistSq = distSqrd; |
| | minPathIndex = state.currentPathIndex; |
| | minSegmentIndex = state.currentSegmentIndex; |
| | minSegmentPos = state.segmentPos; |
| | } |
| | } |
| | if (!nextPath()) { |
| | break; |
| | } |
| | } |
| | state.currentPathIndex = minPathIndex; |
| | state.currentSegmentIndex = minSegmentIndex; |
| | state.segmentPos = minSegmentPos; |
| | calculateCurrentPathLength(); |
| | ResetPasses(); |
| | } |
| | bool nextEngagePoint( |
| | Adaptive2d* parent, |
| | ClearedArea& clearedArea, |
| | double step, |
| | double minCutArea, |
| | double maxCutArea, |
| | int maxPases = 2 |
| | ) |
| | { |
| | Perf_NextEngagePoint.Start(); |
| | double prevArea = 0; |
| | |
| | IntPoint initialPoint(-1000000000, -1000000000); |
| | for (;;) { |
| | if (!moveForward(step)) { |
| | if (!nextPath()) { |
| | state.passes++; |
| | if (state.passes >= maxPases) { |
| | Perf_NextEngagePoint.Stop(); |
| | return false; |
| | } |
| | prevArea = 0; |
| | } |
| | } |
| | IntPoint cpt = getCurrentPoint(); |
| | double area = parent->CalcCutArea(clip, initialPoint, cpt, clearedArea); |
| | if (area > minCutArea && area < maxCutArea && area > prevArea) { |
| | Perf_NextEngagePoint.Stop(); |
| | return true; |
| | } |
| | prevArea = area; |
| | } |
| | } |
| | IntPoint getCurrentPoint() |
| | { |
| | const Path* pth = &toolBoundPaths.at(state.currentPathIndex); |
| | const IntPoint* p1 = &pth->at( |
| | state.currentSegmentIndex > 0 ? state.currentSegmentIndex - 1 : pth->size() - 1 |
| | ); |
| | const IntPoint* p2 = &pth->at(state.currentSegmentIndex); |
| | double segLength = sqrt(DistanceSqrd(*p1, *p2)); |
| | return IntPoint( |
| | long(p1->X + state.segmentPos * double(p2->X - p1->X) / segLength), |
| | long(p1->Y + state.segmentPos * double(p2->Y - p1->Y) / segLength) |
| | ); |
| | } |
| |
|
| | DoublePoint getCurrentDir() |
| | { |
| | const Path* pth = &toolBoundPaths.at(state.currentPathIndex); |
| | const IntPoint* p1 = &pth->at( |
| | state.currentSegmentIndex > 0 ? state.currentSegmentIndex - 1 : pth->size() - 1 |
| | ); |
| | const IntPoint* p2 = &pth->at(state.currentSegmentIndex); |
| | double segLength = sqrt(DistanceSqrd(*p1, *p2)); |
| | return DoublePoint(double(p2->X - p1->X) / segLength, double(p2->Y - p1->Y) / segLength); |
| | } |
| |
|
| | bool moveForward(double distance) |
| | { |
| | const Path* pth = &toolBoundPaths.at(state.currentPathIndex); |
| | if (distance < NTOL) { |
| | throw std::invalid_argument("distance must be positive"); |
| | } |
| | state.totalDistance += distance; |
| | double segmentLength = currentSegmentLength(); |
| | while (state.segmentPos + distance > segmentLength) { |
| | state.currentSegmentIndex++; |
| | if (state.currentSegmentIndex >= pth->size()) { |
| | state.currentSegmentIndex = 0; |
| | } |
| | distance = distance - (segmentLength - state.segmentPos); |
| | state.segmentPos = 0; |
| | segmentLength = currentSegmentLength(); |
| | } |
| | state.segmentPos += distance; |
| | return state.totalDistance <= 1.2 * state.currentPathLength; |
| | } |
| |
|
| | bool nextPath() |
| | { |
| | state.currentPathIndex++; |
| | state.currentSegmentIndex = 0; |
| | state.segmentPos = 0; |
| | state.totalDistance = 0; |
| | if (state.currentPathIndex >= toolBoundPaths.size()) { |
| | state.currentPathIndex = 0; |
| | calculateCurrentPathLength(); |
| | return false; |
| | } |
| | calculateCurrentPathLength(); |
| | return true; |
| | } |
| |
|
| | private: |
| | Paths toolBoundPaths; |
| | EngageState state; |
| | Clipper clip; |
| | void calculateCurrentPathLength() |
| | { |
| | const Path* pth = &toolBoundPaths.at(state.currentPathIndex); |
| | size_t size = pth->size(); |
| | state.currentPathLength = 0; |
| | for (size_t i = 0; i < size; i++) { |
| | const IntPoint* p1 = &pth->at(i > 0 ? i - 1 : size - 1); |
| | const IntPoint* p2 = &pth->at(i); |
| | state.currentPathLength += sqrt(DistanceSqrd(*p1, *p2)); |
| | } |
| | } |
| |
|
| | double currentSegmentLength() |
| | { |
| | const Path* pth = &toolBoundPaths.at(state.currentPathIndex); |
| | const IntPoint* p1 = &pth->at( |
| | state.currentSegmentIndex > 0 ? state.currentSegmentIndex - 1 : pth->size() - 1 |
| | ); |
| | const IntPoint* p2 = &pth->at(state.currentSegmentIndex); |
| | return sqrt(DistanceSqrd(*p1, *p2)); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| |
|
| | Adaptive2d::Adaptive2d() |
| | {} |
| |
|
| | double Adaptive2d::CalcCutArea( |
| | Clipper& clip, |
| | const IntPoint& c1, |
| | const IntPoint& c2, |
| | ClearedArea& clearedArea, |
| | bool preventConventional |
| | ) |
| | { |
| |
|
| | double dist = DistanceSqrd(c1, c2); |
| | if (dist < NTOL) { |
| | return 0; |
| | } |
| |
|
| | Perf_CalcCutAreaCirc.Start(); |
| |
|
| | |
| | double rsqrd = toolRadiusScaled * toolRadiusScaled; |
| | double area = 0; |
| | Paths interPaths; |
| | IntPoint clp; |
| | vector<DoublePoint> inters; |
| | BoundBox c2BB(c2, toolRadiusScaled); |
| | BoundBox c1BB(c1, toolRadiusScaled); |
| | Paths& clearedBounded = clearedArea.GetBoundedClearedAreaClipped(c2); |
| | for (const Path& path : clearedBounded) { |
| | size_t size = path.size(); |
| | if (size == 0) { |
| | continue; |
| | } |
| |
|
| | |
| | |
| | BoundBox pathBB(path.front()); |
| | for (const auto& pt : path) { |
| | pathBB.AddPoint(pt); |
| | } |
| | if (!c2BB.CollidesWith(c2)) { |
| | continue; |
| | } |
| | |
| |
|
| | size_t curPtIndex = 0; |
| | bool found = false; |
| | |
| | |
| | for (size_t i = 0; i < size; i++) { |
| | if (DistanceSqrd(path[curPtIndex], c2) > rsqrd) { |
| | found = true; |
| | break; |
| | } |
| | curPtIndex++; |
| | if (curPtIndex >= size) { |
| | curPtIndex = 0; |
| | } |
| | } |
| | if (!found) { |
| | continue; |
| | } |
| |
|
| | |
| | |
| | size_t prevPtIndex = curPtIndex; |
| | Path* interPath = NULL; |
| | bool prev_inside = false; |
| | const IntPoint* p1 = &path[prevPtIndex]; |
| | double par; |
| | for (size_t i = 0; i < size; i++) { |
| | curPtIndex++; |
| | if (curPtIndex >= size) { |
| | curPtIndex = 0; |
| | } |
| | const IntPoint* p2 = &path[curPtIndex]; |
| | BoundBox segBB(*p1, *p2); |
| | if (!prev_inside) { |
| | if (segBB.CollidesWith(c2BB) |
| | && DistancePointToLineSegSquared(*p1, *p2, c2, clp, par) |
| | <= rsqrd) { |
| | prev_inside = true; |
| | interPaths.push_back(Path()); |
| | if (interPaths.size() > 1) { |
| | break; |
| | |
| | } |
| | interPath = &interPaths.back(); |
| | |
| | if (Line2CircleIntersect(c2, toolRadiusScaled, *p1, *p2, inters)) { |
| | interPath->push_back(IntPoint(long(inters[0].X), long(inters[0].Y))); |
| | if (inters.size() > 1) { |
| | interPath->push_back(IntPoint(long(inters[1].X), long(inters[1].Y))); |
| | prev_inside = false; |
| | } |
| | else { |
| | interPath->push_back(IntPoint(*p2)); |
| | } |
| | } |
| | else { |
| | interPath->push_back(IntPoint(*p2)); |
| | } |
| | } |
| | } |
| | else if (interPath != NULL) { |
| | if ((DistanceSqrd(c2, *p2) <= rsqrd)) { |
| | |
| | interPath->push_back(IntPoint(*p2)); |
| | } |
| | else { |
| | if (Line2CircleIntersect(c2, toolRadiusScaled, *p1, *p2, inters)) { |
| | if (inters.size() > 1) { |
| | interPath->push_back(IntPoint(long(inters[1].X), long(inters[1].Y))); |
| | } |
| | else { |
| | interPath->push_back(IntPoint(long(inters[0].X), long(inters[0].Y))); |
| | } |
| | } |
| | prev_inside = false; |
| | } |
| | } |
| | prevPtIndex = curPtIndex; |
| | p1 = p2; |
| | } |
| | if (interPaths.size() > 1) { |
| | break; |
| | |
| | } |
| | } |
| | Perf_CalcCutAreaCirc.Stop(); |
| | if (interPaths.size() == 1 && interPaths.front().size() > 1) { |
| | Perf_CalcCutAreaCirc.Start(); |
| | Path* interPath = &interPaths.front(); |
| | |
| | size_t ipc2_size = interPath->size(); |
| | const IntPoint& fpc2 = interPath->front(); |
| | const IntPoint& lpc2 = interPath->back(); |
| | |
| | double interPathLen = 0; |
| | for (size_t j = 1; j < ipc2_size; j++) { |
| | interPathLen += sqrt(DistanceSqrd(interPath->at(j - 1), interPath->at(j))); |
| | } |
| |
|
| | Paths inPaths; |
| | inPaths.reserve(200); |
| | inPaths.push_back(*interPath); |
| | Path pthToSubtract; |
| | pthToSubtract.push_back(fpc2); |
| |
|
| | double fi1 = atan2(fpc2.Y - c2.Y, fpc2.X - c2.X); |
| | double fi2 = atan2(lpc2.Y - c2.Y, lpc2.X - c2.X); |
| | double minFi = fi1; |
| | double maxFi = fi2; |
| | if (maxFi < minFi) { |
| | maxFi += 2 * std::numbers::pi; |
| | } |
| |
|
| | if (preventConventional && interPathLen >= MIN_STEP_CLIPPER) { |
| | |
| | IntPoint midPoint( |
| | long(c2.X + toolRadiusScaled * cos(0.5 * (maxFi + minFi))), |
| | long(c2.Y + toolRadiusScaled * sin(0.5 * (maxFi + minFi))) |
| | ); |
| | if (PointSideOfLine(c1, c2, midPoint) < 0) { |
| | area = __DBL_MAX__; |
| | Perf_CalcCutAreaCirc.Stop(); |
| | |
| | |
| | |
| | return area; |
| | } |
| | } |
| |
|
| | double scanDistance = 2.5 * toolRadiusScaled; |
| | |
| | double stepDistance = min(double(MIN_STEP_CLIPPER), interPathLen / 24) + 1; |
| | const IntPoint* prevPt = &interPath->front(); |
| | double distance = 0; |
| | for (size_t j = 1; j < ipc2_size; j++) { |
| | const IntPoint* cpt = &interPath->at(j); |
| | double segLen = sqrt(DistanceSqrd(*cpt, *prevPt)); |
| | if (segLen < NTOL) { |
| | continue; |
| | } |
| | for (double pos_unclamped = 0.0; pos_unclamped < segLen + stepDistance; |
| | pos_unclamped += stepDistance) { |
| | double pos = pos_unclamped; |
| | if (pos > segLen) { |
| | distance += stepDistance - (pos - segLen); |
| | pos = segLen; |
| | } |
| | else { |
| | distance += stepDistance; |
| | } |
| | double dx = double(cpt->X - prevPt->X); |
| | double dy = double(cpt->Y - prevPt->Y); |
| | IntPoint segPoint( |
| | long(prevPt->X + dx * pos / segLen), |
| | long(prevPt->Y + dy * pos / segLen) |
| | ); |
| | IntPoint scanPoint( |
| | long(c2.X + scanDistance * cos(minFi + distance * (maxFi - minFi) / interPathLen)), |
| | long(c2.Y + scanDistance * sin(minFi + distance * (maxFi - minFi) / interPathLen)) |
| | ); |
| |
|
| | IntPoint intersC2(segPoint.X, segPoint.Y); |
| | IntPoint intersC1(segPoint.X, segPoint.Y); |
| |
|
| | |
| | if (Line2CircleIntersect(c2, toolRadiusScaled, segPoint, scanPoint, inters)) { |
| | if (inters.size() > 1) { |
| | intersC2.X = long(inters[1].X); |
| | intersC2.Y = long(inters[1].Y); |
| | } |
| | else { |
| | intersC2.X = long(inters[0].X); |
| | intersC2.Y = long(inters[0].Y); |
| | } |
| | } |
| | else { |
| | pthToSubtract.push_back(segPoint); |
| | } |
| |
|
| | if (Line2CircleIntersect(c1, toolRadiusScaled, segPoint, scanPoint, inters)) { |
| | if (inters.size() > 1) { |
| | intersC1.X = long(inters[1].X); |
| | intersC1.Y = long(inters[1].Y); |
| | } |
| | else { |
| | intersC1.X = long(inters[0].X); |
| | intersC1.Y = long(inters[0].Y); |
| | } |
| | if (DistanceSqrd(segPoint, intersC2) < DistanceSqrd(segPoint, intersC1)) { |
| | pthToSubtract.push_back(intersC2); |
| | } |
| | else { |
| | pthToSubtract.push_back(intersC1); |
| | } |
| | } |
| | else { |
| | pthToSubtract.push_back(segPoint); |
| | } |
| | } |
| | prevPt = cpt; |
| | } |
| |
|
| | pthToSubtract.push_back(lpc2); |
| | pthToSubtract.push_back(c2); |
| |
|
| | double segArea = Area(pthToSubtract); |
| | double A = (maxFi - minFi) * rsqrd / 2; |
| | area += A - fabs(segArea); |
| | Perf_CalcCutAreaCirc.Stop(); |
| | } |
| | else if (interPaths.size() > 1) { |
| | Perf_CalcCutAreaClip.Start(); |
| | |
| | |
| | |
| | |
| | Path oldTool; |
| | Path newTool; |
| | TranslatePath(toolGeometry, oldTool, c1); |
| | TranslatePath(toolGeometry, newTool, c2); |
| | clip.Clear(); |
| | clip.AddPath(newTool, PolyType::ptSubject, true); |
| | clip.AddPath(oldTool, PolyType::ptClip, true); |
| | Paths toolDiff; |
| | clip.Execute(ClipType::ctDifference, toolDiff); |
| |
|
| | |
| | clip.Clear(); |
| | clip.AddPaths(toolDiff, PolyType::ptSubject, true); |
| | clip.AddPaths(clearedBounded, PolyType::ptClip, true); |
| | Paths cutAreaPoly; |
| | clip.Execute(ClipType::ctDifference, cutAreaPoly); |
| |
|
| | |
| | area = 0; |
| | for (Path& path : cutAreaPoly) { |
| | area += fabs(Area(path)); |
| | } |
| | Perf_CalcCutAreaClip.Stop(); |
| | } |
| | return area; |
| | } |
| |
|
| | void Adaptive2d::ApplyStockToLeave(Paths& inputPaths) |
| | { |
| | ClipperOffset clipof; |
| | if (stockToLeave > NTOL) { |
| | clipof.Clear(); |
| | clipof.AddPaths(inputPaths, JoinType::jtRound, EndType::etClosedPolygon); |
| | if (opType == OperationType::otClearingOutside |
| | || opType == OperationType::otProfilingOutside) { |
| | clipof.Execute(inputPaths, stockToLeave * scaleFactor); |
| | } |
| | else { |
| | clipof.Execute(inputPaths, -stockToLeave * scaleFactor); |
| | } |
| | } |
| | else { |
| | |
| | clipof.Clear(); |
| | clipof.AddPaths(inputPaths, JoinType::jtRound, EndType::etClosedPolygon); |
| | clipof.Execute(inputPaths, -1); |
| | filterCloseValues(inputPaths); |
| | clipof.Clear(); |
| | clipof.AddPaths(inputPaths, JoinType::jtRound, EndType::etClosedPolygon); |
| | clipof.Execute(inputPaths, 1); |
| | filterCloseValues(inputPaths); |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| |
|
| | std::list<AdaptiveOutput> Adaptive2d::Execute( |
| | const DPaths& stockPaths, |
| | const DPaths& paths, |
| | std::function<bool(TPaths)> progressCallbackFn |
| | ) |
| | { |
| | |
| | |
| | |
| |
|
| | |
| | tolerance = max(tolerance, 0.01); |
| | tolerance = min(tolerance, 1.0); |
| |
|
| | |
| | scaleFactor = MIN_STEP_CLIPPER / tolerance / min(1.0, stepOverFactor * toolDiameter); |
| |
|
| | current_region = 0; |
| | cout << "Tool Diameter: " << toolDiameter << endl; |
| | cout << "Min step size: " << round(MIN_STEP_CLIPPER / scaleFactor * 1000 * 10) / 10 << " um" |
| | << endl; |
| | cout << flush; |
| |
|
| | toolRadiusScaled = long(toolDiameter * scaleFactor / 2); |
| | stepOverScaled = toolRadiusScaled * stepOverFactor; |
| | progressCallback = &progressCallbackFn; |
| | lastProgressTime = clock(); |
| | stopProcessing = false; |
| |
|
| | if (helixRampTargetDiameter < NTOL) { |
| | helixRampTargetDiameter = toolDiameter; |
| | } |
| | helixRampTargetDiameter = min(helixRampTargetDiameter, toolDiameter); |
| | helixRampMinDiameter = max(helixRampMinDiameter, toolDiameter / 8); |
| | helixRampTargetDiameter = max(helixRampTargetDiameter, helixRampMinDiameter); |
| |
|
| | helixRampMaxRadiusScaled = long(helixRampTargetDiameter * scaleFactor / 2); |
| | helixRampMinRadiusScaled = long(helixRampMinDiameter * scaleFactor / 2); |
| | if (finishingProfile) { |
| | finishPassOffsetScaled = long(stepOverScaled / 10); |
| | } |
| |
|
| | ClipperOffset clipof; |
| | Clipper clip; |
| |
|
| | |
| | clipof.Clear(); |
| | Path p; |
| | p << IntPoint(0, 0); |
| | clipof.AddPath(p, JoinType::jtRound, EndType::etOpenRound); |
| | Paths toolGeometryPaths; |
| | clipof.Execute(toolGeometryPaths, toolRadiusScaled); |
| | toolGeometry = toolGeometryPaths[0]; |
| | |
| | Path slotCut; |
| | TranslatePath(toolGeometryPaths[0], slotCut, IntPoint(toolRadiusScaled / 2, 0)); |
| | clip.Clear(); |
| | clip.AddPath(toolGeometryPaths[0], PolyType::ptSubject, true); |
| | clip.AddPath(slotCut, PolyType::ptClip, true); |
| | Paths crossing; |
| | clip.Execute(ClipType::ctDifference, crossing); |
| | referenceCutArea = fabs(Area(crossing[0])); |
| | optimalCutAreaPD = 2 * stepOverFactor * referenceCutArea / toolRadiusScaled; |
| | #ifdef DEV_MODE |
| | cout << "optimalCutAreaPD:" << optimalCutAreaPD << " scaleFactor:" << scaleFactor |
| | << " toolRadiusScaled:" << toolRadiusScaled |
| | << " helixRampMaxRadiusScaled:" << helixRampMaxRadiusScaled << endl; |
| | #endif |
| | |
| | |
| | |
| | Paths converted; |
| | for (size_t i = 0; i < paths.size(); i++) { |
| | Path cpth; |
| | for (size_t j = 0; j < paths[i].size(); j++) { |
| | std::pair<double, double> pt = paths[i][j]; |
| | cpth.push_back(IntPoint(long(pt.first * scaleFactor), long(pt.second * scaleFactor))); |
| | } |
| | Path cpth2; |
| | CleanPath(cpth, cpth2, FINISHING_CLEAN_PATH_TOLERANCE); |
| | converted.push_back(cpth2); |
| | } |
| |
|
| | DeduplicatePaths(converted, inputPaths); |
| | ConnectPaths(inputPaths, inputPaths); |
| | SimplifyPolygons(inputPaths); |
| | ApplyStockToLeave(inputPaths); |
| |
|
| | |
| | |
| | |
| | stockInputPaths.clear(); |
| | for (size_t i = 0; i < stockPaths.size(); i++) { |
| | Path cpth; |
| | for (size_t j = 0; j < stockPaths[i].size(); j++) { |
| | std::pair<double, double> pt = stockPaths[i][j]; |
| | cpth.push_back(IntPoint(long(pt.first * scaleFactor), long(pt.second * scaleFactor))); |
| | } |
| |
|
| | stockInputPaths.push_back(cpth); |
| | } |
| |
|
| | SimplifyPolygons(stockInputPaths); |
| | |
| |
|
| | |
| | |
| | |
| | double cornerRoundingOffset = 0.15 * toolRadiusScaled / 2; |
| | if (opType == OperationType::otClearingInside || opType == OperationType::otClearingOutside) { |
| |
|
| | |
| | clipof.Clear(); |
| | clipof.AddPaths(stockInputPaths, JoinType::jtSquare, EndType::etClosedPolygon); |
| | double overshootDistance = 4 * toolRadiusScaled + stockToLeave * scaleFactor; |
| | if (forceInsideOut) { |
| | overshootDistance = 0; |
| | } |
| | Paths stockOvershoot; |
| | clipof.Execute(stockOvershoot, overshootDistance); |
| | ReversePaths(stockOvershoot); |
| |
|
| | if (opType == OperationType::otClearingOutside) { |
| | |
| | for (const auto& p : stockOvershoot) { |
| | inputPaths.push_back(p); |
| | } |
| | } |
| | else if (opType == OperationType::otClearingInside) { |
| | |
| | |
| | } |
| |
|
| | clipof.Clear(); |
| | clipof.AddPaths(inputPaths, JoinType::jtRound, EndType::etClosedPolygon); |
| | Paths paths; |
| | clipof.Execute(paths, -toolRadiusScaled - finishPassOffsetScaled - cornerRoundingOffset); |
| | for (const auto& current : paths) { |
| | int nesting = getPathNestingLevel(current, paths); |
| | if (nesting % 2 != 0 && (polyTreeNestingLimit == 0 || nesting <= polyTreeNestingLimit)) { |
| | Paths toolBoundPaths; |
| | toolBoundPaths.push_back(current); |
| | if (polyTreeNestingLimit != nesting) { |
| | appendDirectChildPaths(toolBoundPaths, current, paths); |
| | } |
| |
|
| | |
| | clipof.Clear(); |
| | clipof.AddPaths(toolBoundPaths, JoinType::jtRound, EndType::etClosedPolygon); |
| | clipof.Execute(toolBoundPaths, cornerRoundingOffset); |
| |
|
| | |
| | |
| | |
| | |
| | Paths boundPaths; |
| | clipof.Clear(); |
| | clipof.AddPaths(toolBoundPaths, JoinType::jtRound, EndType::etClosedPolygon); |
| | clipof.Execute(boundPaths, toolRadiusScaled + finishPassOffsetScaled); |
| | ProcessPolyNode(boundPaths, toolBoundPaths); |
| | } |
| | } |
| | } |
| |
|
| | if (opType == OperationType::otProfilingInside || opType == OperationType::otProfilingOutside) { |
| | double offset = opType == OperationType::otProfilingInside |
| | ? -2 * (helixRampMaxRadiusScaled + toolRadiusScaled) - MIN_STEP_CLIPPER |
| | : 2 * (helixRampMaxRadiusScaled + toolRadiusScaled) + MIN_STEP_CLIPPER; |
| | for (const auto& current : inputPaths) { |
| | int nesting = getPathNestingLevel(current, inputPaths); |
| | if (nesting % 2 != 0 && (polyTreeNestingLimit == 0 || nesting <= polyTreeNestingLimit)) { |
| | Paths profilePaths; |
| | profilePaths.push_back(current); |
| | if (polyTreeNestingLimit != nesting) { |
| | appendDirectChildPaths(profilePaths, current, inputPaths); |
| | } |
| | for (size_t i = 0; i < profilePaths.size(); i++) { |
| | double efOffset = i == 0 ? offset : -offset; |
| | clipof.Clear(); |
| | clipof.AddPath(profilePaths[i], JoinType::jtSquare, EndType::etClosedPolygon); |
| | Paths off1; |
| | clipof.Execute(off1, efOffset); |
| | |
| | Paths boundPaths; |
| | clip.Clear(); |
| | if (efOffset < 0) { |
| | clip.AddPath(profilePaths[i], PolyType::ptSubject, true); |
| | clip.AddPaths(off1, PolyType::ptClip, true); |
| | } |
| | else { |
| | clip.AddPaths(off1, PolyType::ptSubject, true); |
| | clip.AddPath(profilePaths[i], PolyType::ptClip, true); |
| | } |
| | clip.Execute(ClipType::ctDifference, boundPaths, PolyFillType::pftEvenOdd); |
| |
|
| | |
| | Paths toolBoundPaths; |
| | clipof.Clear(); |
| | clipof.AddPaths(boundPaths, JoinType::jtRound, EndType::etClosedPolygon); |
| | clipof.Execute( |
| | toolBoundPaths, |
| | -toolRadiusScaled - finishPassOffsetScaled - cornerRoundingOffset |
| | ); |
| |
|
| | |
| | clipof.Clear(); |
| | clipof.AddPaths(toolBoundPaths, JoinType::jtRound, EndType::etClosedPolygon); |
| | clipof.Execute(toolBoundPaths, cornerRoundingOffset); |
| |
|
| | |
| | |
| | |
| | clipof.Clear(); |
| | clipof.AddPaths(toolBoundPaths, JoinType::jtRound, EndType::etClosedPolygon); |
| | clipof.Execute(boundPaths, toolRadiusScaled + finishPassOffsetScaled); |
| |
|
| | ProcessPolyNode(boundPaths, toolBoundPaths); |
| | } |
| | } |
| | } |
| | } |
| | return results; |
| | } |
| |
|
| | bool Adaptive2d::FindEntryPoint( |
| | TPaths& progressPaths, |
| | const Paths& toolBoundPaths, |
| | const Paths& boundPaths, |
| | ClearedArea& clearedArea , |
| | IntPoint& entryPoint , |
| | IntPoint& toolPos, |
| | DoublePoint& toolDir, |
| | long& helixRadiusScaled |
| | ) |
| | { |
| | Paths incOffset; |
| | Paths lastValidOffset; |
| | Clipper clip; |
| | ClipperOffset clipof; |
| | bool found = false; |
| | Paths clearedPaths; |
| | Paths checkPaths = toolBoundPaths; |
| | for (int iter = 0; iter < 10; iter++) { |
| | clipof.Clear(); |
| | clipof.AddPaths(checkPaths, JoinType::jtSquare, EndType::etClosedPolygon); |
| | double step = MIN_STEP_CLIPPER; |
| | double currentDelta = -1; |
| | clipof.Execute(incOffset, currentDelta); |
| | while (!incOffset.empty()) { |
| | clipof.Execute(incOffset, currentDelta); |
| | if (!incOffset.empty()) { |
| | lastValidOffset = incOffset; |
| | } |
| | currentDelta -= step; |
| | } |
| | for (size_t i = 0; i < lastValidOffset.size(); i++) { |
| | if (!lastValidOffset[i].empty()) { |
| | entryPoint = Compute2DPolygonCentroid(lastValidOffset[i]); |
| | found = true; |
| | break; |
| | } |
| | } |
| | |
| | |
| | |
| | for (size_t j = 0; j < checkPaths.size(); j++) { |
| | int pip = PointInPolygon(entryPoint, checkPaths[j]); |
| | if ((j == 0 && pip == 0) || (j > 0 && pip != 0)) { |
| | found = false; |
| | break; |
| | } |
| | } |
| | |
| | const auto checkHelixFit = [&](long testHelixRadiusScaled) { |
| | clipof.Clear(); |
| | Path p1; |
| | p1.push_back(entryPoint); |
| | clipof.AddPath(p1, JoinType::jtRound, EndType::etOpenRound); |
| | clipof.Execute(clearedPaths, (double)(testHelixRadiusScaled + toolRadiusScaled)); |
| | CleanPolygons(clearedPaths); |
| | |
| | clip.Clear(); |
| | clip.AddPaths(clearedPaths, PolyType::ptSubject, true); |
| | clip.AddPaths(boundPaths, PolyType::ptClip, true); |
| | Paths crossing; |
| | clip.Execute(ClipType::ctDifference, crossing); |
| |
|
| | return crossing.empty(); |
| | }; |
| |
|
| | if (found) { |
| | |
| | if (!checkHelixFit(helixRampMinRadiusScaled)) { |
| | |
| | found = false; |
| | } |
| | else { |
| | |
| | |
| | long minSize = helixRampMinRadiusScaled; |
| | long maxSize = helixRampMaxRadiusScaled; |
| | while (minSize < maxSize) { |
| | long testSize = (minSize + maxSize + 1) / 2; |
| | if (checkHelixFit(testSize)) { |
| | minSize = testSize; |
| | } |
| | else { |
| | maxSize = testSize - 1; |
| | } |
| | } |
| | helixRadiusScaled = minSize; |
| | checkHelixFit(helixRadiusScaled); |
| | clearedArea.SetClearedPaths(clearedPaths); |
| | } |
| | } |
| |
|
| | if (!found) { |
| | clip.Clear(); |
| | clip.AddPaths(checkPaths, PolyType::ptSubject, true); |
| | auto bounds = clip.GetBounds(); |
| | clip.Clear(); |
| | Path rect; |
| | rect << IntPoint(bounds.left, bounds.bottom); |
| | rect << IntPoint(bounds.left, (bounds.top + bounds.bottom) / 2); |
| | rect << IntPoint((bounds.left + bounds.right) / 2, (bounds.top + bounds.bottom) / 2); |
| | rect << IntPoint((bounds.left + bounds.right) / 2, bounds.bottom); |
| | clip.AddPath(rect, PolyType::ptSubject, true); |
| | clip.AddPaths(checkPaths, PolyType::ptClip, true); |
| | clip.Execute(ClipType::ctIntersection, checkPaths); |
| | } |
| | if (found) { |
| | break; |
| | } |
| | } |
| |
|
| | if (!found) { |
| | cerr << "Start point not found!" << endl; |
| | } |
| | if (found) { |
| | |
| | clipof.Clear(); |
| | Path hp; |
| | hp << entryPoint; |
| | clipof.AddPath(hp, JoinType::jtRound, EndType::etOpenRound); |
| | Paths hps; |
| | clipof.Execute(hps, helixRadiusScaled); |
| | AddPathsToProgress(progressPaths, hps); |
| |
|
| | toolPos = IntPoint(entryPoint.X, entryPoint.Y - helixRadiusScaled); |
| | toolDir = DoublePoint(1.0, 0.0); |
| | } |
| | return found; |
| | } |
| |
|
| | bool Adaptive2d::FindEntryPointOutside( |
| | TPaths& progressPaths, |
| | const Paths& toolBoundPaths, |
| | const Paths& boundPaths, |
| | ClearedArea& clearedArea , |
| | IntPoint& entryPoint , |
| | IntPoint& toolPos, |
| | DoublePoint& toolDir |
| | ) |
| | { |
| |
|
| | UNUSED(progressPaths); |
| | UNUSED(boundPaths); |
| |
|
| | Clipper clip; |
| | ClipperOffset clipof; |
| | Paths clearedPaths; |
| | |
| | for (const auto& pth : toolBoundPaths) { |
| | for (size_t i = 0; i < pth.size(); i++) { |
| | IntPoint checkPoint = pth[i]; |
| | IntPoint lastPoint = i > 0 ? pth[i - 1] : pth.back(); |
| | |
| | if (PointInPolygon(checkPoint, stockInputPaths.front()) == 0) { |
| |
|
| | clipof.Clear(); |
| | clipof.AddPaths(stockInputPaths, JoinType::jtSquare, EndType::etClosedPolygon); |
| | clipof.Execute(clearedPaths, 1000 * toolRadiusScaled); |
| |
|
| | clip.Clear(); |
| | clip.AddPaths(clearedPaths, PolyType::ptSubject, true); |
| | clip.AddPaths(stockInputPaths, PolyType::ptClip, true); |
| | clip.Execute(ClipType::ctDifference, clearedPaths); |
| | CleanPolygons(clearedPaths); |
| | SimplifyPolygons(clearedPaths); |
| | clearedArea.SetClearedPaths(clearedPaths); |
| | entryPoint = checkPoint; |
| | toolPos = entryPoint; |
| | |
| | double len = sqrt(DistanceSqrd(lastPoint, checkPoint)); |
| | toolDir = DoublePoint( |
| | (checkPoint.X - lastPoint.X) / len, |
| | (checkPoint.Y - lastPoint.Y) / len |
| | ); |
| | return true; |
| | } |
| | } |
| | } |
| | return false; |
| | } |
| |
|
| | |
| | |
| | |
| | bool Adaptive2d::IsClearPath(const Path& tp, ClearedArea& cleared, double safetyClearance) |
| | { |
| | Perf_IsClearPath.Start(); |
| | Clipper clip; |
| | ClipperOffset clipof; |
| | clipof.AddPath(tp, JoinType::jtRound, EndType::etOpenRound); |
| | Paths toolShape; |
| | clipof.Execute(toolShape, toolRadiusScaled + safetyClearance); |
| | clip.AddPaths(toolShape, PolyType::ptSubject, true); |
| | clip.AddPaths(cleared.GetCleared(), PolyType::ptClip, true); |
| | Paths crossing; |
| | clip.Execute(ClipType::ctDifference, crossing); |
| | double collisionArea = 0; |
| | for (auto& p : crossing) { |
| | collisionArea += fabs(Area(p)); |
| | } |
| | Perf_IsClearPath.Stop(); |
| | return collisionArea < 1.0; |
| | } |
| |
|
| | bool Adaptive2d::IsAllowedToCutTrough( |
| | const IntPoint& p1, |
| | const IntPoint& p2, |
| | ClearedArea& cleared, |
| | const Paths& toolBoundPaths, |
| | double areaFactor, |
| | bool skipBoundsCheck |
| | ) |
| | { |
| | Perf_IsAllowedToCutTrough.Start(); |
| |
|
| | if (!skipBoundsCheck && !IsPointWithinCutRegion(toolBoundPaths, p2)) { |
| | |
| | Perf_IsAllowedToCutTrough.Stop(); |
| | return false; |
| | } |
| | else if (!skipBoundsCheck && !IsPointWithinCutRegion(toolBoundPaths, p1)) { |
| | |
| | Perf_IsAllowedToCutTrough.Stop(); |
| | return false; |
| | } |
| | else { |
| | Clipper clip; |
| | double distance = sqrt(DistanceSqrd(p1, p2)); |
| | double stepSize = min(0.5 * stepOverScaled, 8 * MIN_STEP_CLIPPER); |
| | if (distance < stepSize / 2) { |
| | Perf_IsAllowedToCutTrough.Stop(); |
| | return true; |
| | } |
| | if (distance < stepSize) { |
| | areaFactor *= 2; |
| | } |
| |
|
| | IntPoint toolPos1 = p1; |
| | long steps = long(distance / stepSize) + 1; |
| | stepSize = distance / steps; |
| | for (long i = 1; i <= steps; i++) { |
| | double p = double(i) / steps; |
| | IntPoint toolPos2( |
| | long(p1.X + double(p2.X - p1.X) * p), |
| | long(p1.Y + double(p2.Y - p1.Y) * p) |
| | ); |
| | double area = CalcCutArea(clip, toolPos1, toolPos2, cleared, false); |
| | |
| | if (area > areaFactor * stepSize * optimalCutAreaPD) { |
| | Perf_IsAllowedToCutTrough.Stop(); |
| | return false; |
| | } |
| | |
| | if (!skipBoundsCheck && !IsPointWithinCutRegion(toolBoundPaths, toolPos2)) { |
| | Perf_IsAllowedToCutTrough.Stop(); |
| | return false; |
| | } |
| | toolPos1 = toolPos2; |
| | } |
| | } |
| | Perf_IsAllowedToCutTrough.Stop(); |
| | return true; |
| | } |
| |
|
| | bool Adaptive2d::ResolveLinkPath( |
| | const IntPoint& startPoint, |
| | const IntPoint& endPoint, |
| | ClearedArea& clearedArea, |
| | Path& output |
| | ) |
| | { |
| | vector<pair<IntPoint, IntPoint>> queue; |
| | queue.emplace_back(startPoint, endPoint); |
| | Path checkPath; |
| | double totalLength = 0; |
| | double directDistance = sqrt(DistanceSqrd(startPoint, endPoint)); |
| | Paths linkPaths; |
| |
|
| | double scanStep = 2 * MIN_STEP_CLIPPER; |
| | if (scanStep > scaleFactor * 0.1) { |
| | scanStep = scaleFactor * 0.1; |
| | } |
| | if (scanStep < scaleFactor * 0.01) { |
| | scanStep = scaleFactor * 0.01; |
| | } |
| | long limit = 10000; |
| |
|
| | double clearance = stepOverScaled; |
| | double offClearance = 2 * stepOverScaled; |
| | if (offClearance > directDistance / 2) { |
| | offClearance = directDistance / 2; |
| | clearance = 0; |
| | } |
| |
|
| | long cnt = 0; |
| |
|
| | |
| | IntPoint clp; |
| | size_t pindex; |
| | size_t sindex; |
| | double par; |
| |
|
| | |
| | clock_t time_limit = (clock_t)(max(keepToolDownDistRatio, 3.0) * CLOCKS_PER_SEC / 6); |
| |
|
| | clock_t time_out = clock() + time_limit; |
| |
|
| | while (!queue.empty()) { |
| | if (stopProcessing) { |
| | return false; |
| | } |
| | if (clock() > time_out) { |
| | cout << "Unable to resolve tool down linking path (limit reached)." << endl; |
| | return false; |
| | } |
| |
|
| | cnt++; |
| | if (cnt > limit) { |
| | cout << "Unable to resolve tool down linking path @(" << endPoint.X / scaleFactor << "," |
| | << endPoint.Y / scaleFactor << ") (" << limit << " points limit reached)." << endl; |
| | return false; |
| | } |
| | pair<IntPoint, IntPoint> pointPair = queue.back(); |
| | queue.pop_back(); |
| |
|
| | |
| | for (size_t i = 0; i < linkPaths.size(); i++) { |
| | if (linkPaths[i].front() != pointPair.first && linkPaths[i].back() != pointPair.first |
| | && linkPaths[i].front() != pointPair.second && linkPaths[i].back() != pointPair.second |
| | && IntersectionPoint( |
| | linkPaths[i].front(), |
| | linkPaths[i].back(), |
| | pointPair.first, |
| | pointPair.second, |
| | clp |
| | )) { |
| | cout << "Unable to resolve tool down linking path (self-intersects)." << endl; |
| | return false; |
| | } |
| | } |
| |
|
| | DoublePoint direction = DirectionV(pointPair.first, pointPair.second); |
| | checkPath.clear(); |
| | if (pointPair.first == startPoint) { |
| | checkPath.push_back(IntPoint( |
| | pointPair.first.X + offClearance * direction.X, |
| | pointPair.first.Y + offClearance * direction.Y |
| | )); |
| | } |
| | else { |
| | checkPath.push_back(pointPair.first); |
| | } |
| | if (pointPair.second == endPoint) { |
| | checkPath.push_back(IntPoint( |
| | pointPair.second.X - offClearance * direction.X, |
| | pointPair.second.Y - offClearance * direction.Y |
| | )); |
| | } |
| | else { |
| | checkPath.push_back(pointPair.second); |
| | } |
| |
|
| | if (IsClearPath(checkPath, clearedArea, clearance)) { |
| | totalLength += sqrt(DistanceSqrd(pointPair.first, pointPair.second)); |
| | if (totalLength > keepToolDownDistRatio * directDistance) { |
| | return false; |
| | } |
| | Path link; |
| | link.push_back(pointPair.first); |
| | link.push_back(pointPair.second); |
| | linkPaths.push_back(link); |
| | } |
| | else { |
| | if (sqrt(DistanceSqrd(pointPair.first, pointPair.second)) < 4) { |
| | |
| | return false; |
| | } |
| | DoublePoint pDir(-direction.Y, direction.X); |
| | |
| | IntPoint midPoint( |
| | 0.5 * double(pointPair.first.X + pointPair.second.X), |
| | 0.5 * double(pointPair.first.Y + pointPair.second.Y) |
| | ); |
| | for (long i = 1;; i++) { |
| | if (stopProcessing) { |
| | return false; |
| | } |
| | double offset = i * scanStep; |
| | IntPoint checkPoint1(midPoint.X + offset * pDir.X, midPoint.Y + offset * pDir.Y); |
| | IntPoint checkPoint2(midPoint.X - offset * pDir.X, midPoint.Y - offset * pDir.Y); |
| |
|
| | if (DistancePointToPathsSqrd(clearedArea.GetCleared(), checkPoint1, clp, pindex, sindex, par) |
| | < DistancePointToPathsSqrd( |
| | clearedArea.GetCleared(), |
| | checkPoint2, |
| | clp, |
| | pindex, |
| | sindex, |
| | par |
| | )) { |
| | |
| | IntPoint tmp = checkPoint2; |
| | checkPoint2 = checkPoint1; |
| | checkPoint1 = tmp; |
| | } |
| |
|
| | checkPath.clear(); |
| | checkPath.push_back(checkPoint1); |
| | if (IsClearPath(checkPath, clearedArea, clearance + 1)) { |
| | queue.emplace_back(pointPair.first, checkPoint1); |
| | queue.emplace_back(checkPoint1, pointPair.second); |
| | break; |
| | } |
| | else { |
| |
|
| | checkPath.clear(); |
| | checkPath.push_back(checkPoint2); |
| | if (IsClearPath(checkPath, clearedArea, clearance + 1)) { |
| | queue.emplace_back(pointPair.first, checkPoint2); |
| | queue.emplace_back(checkPoint2, pointPair.second); |
| | break; |
| | } |
| | } |
| | if (offset > keepToolDownDistRatio * directDistance) { |
| | return false; |
| | } |
| | } |
| | } |
| | } |
| | if (linkPaths.empty()) { |
| | return false; |
| | } |
| | ConnectPaths(linkPaths, linkPaths); |
| | output = linkPaths[0]; |
| | return true; |
| | } |
| |
|
| | bool Adaptive2d::MakeLeadPath( |
| | bool leadIn, |
| | const IntPoint& startPoint, |
| | const DoublePoint& startDir, |
| | const IntPoint& beaconPoint, |
| | ClearedArea& clearedArea, |
| | const Paths& toolBoundPaths, |
| | Path& output |
| | ) |
| | { |
| | IntPoint currentPoint = startPoint; |
| | DoublePoint targetDir = DirectionV(currentPoint, beaconPoint); |
| | double distanceToBeacon = sqrt(DistanceSqrd(startPoint, beaconPoint)); |
| | double stepSize = 0.2 * stepOverScaled + 1; |
| | double maxPathLen = stepOverScaled; |
| | DoublePoint nextDir = startDir; |
| | IntPoint nextPoint |
| | = IntPoint(currentPoint.X + nextDir.X * stepSize, currentPoint.Y + nextDir.Y * stepSize); |
| | Path checkPath; |
| | double adaptFactor = 0.4; |
| | double alfa = std::numbers::pi / 64; |
| | double pathLen = 0; |
| | checkPath.push_back(nextPoint); |
| | for (int i = 0; i < 10000; i++) { |
| | if (IsAllowedToCutTrough( |
| | IntPoint( |
| | currentPoint.X + MIN_STEP_CLIPPER * nextDir.X, |
| | currentPoint.Y + MIN_STEP_CLIPPER * nextDir.Y |
| | ), |
| | nextPoint, |
| | clearedArea, |
| | toolBoundPaths |
| | )) { |
| | if (output.empty()) { |
| | output.push_back(currentPoint); |
| | } |
| | output.push_back(nextPoint); |
| | currentPoint = nextPoint; |
| | pathLen += stepSize; |
| | targetDir = DirectionV(currentPoint, beaconPoint); |
| | nextDir = DoublePoint( |
| | nextDir.X + adaptFactor * targetDir.X, |
| | nextDir.Y + adaptFactor * targetDir.Y |
| | ); |
| | NormalizeV(nextDir); |
| | if (pathLen > maxPathLen) { |
| | break; |
| | } |
| | if (pathLen > distanceToBeacon / 2) { |
| | break; |
| | } |
| | } |
| | else { |
| | nextDir = rotate(nextDir, leadIn ? -alfa : alfa); |
| | } |
| | nextPoint |
| | = IntPoint(currentPoint.X + nextDir.X * stepSize, currentPoint.Y + nextDir.Y * stepSize); |
| | } |
| | if (output.empty()) { |
| | output.push_back(startPoint); |
| | } |
| | return true; |
| | } |
| | void Adaptive2d::AppendToolPath( |
| | TPaths& progressPaths, |
| | AdaptiveOutput& output, |
| | const Path& passToolPath, |
| | ClearedArea& clearedBefore, |
| | ClearedArea& clearedAfter, |
| | const Paths& toolBoundPaths |
| | ) |
| | { |
| | if (passToolPath.size() < 2) { |
| | return; |
| | } |
| | Perf_AppendToolPath.Start(); |
| | UNUSED(progressPaths); |
| | |
| |
|
| | IntPoint endPoint(passToolPath[0]); |
| | |
| | if (!output.AdaptivePaths.empty() && output.AdaptivePaths.back().second.size() > 1) { |
| | auto& lastTPath = output.AdaptivePaths.back(); |
| |
|
| | auto& lastPrevTPoint = lastTPath.second.at(lastTPath.second.size() - 2); |
| | auto& lastTPoint = lastTPath.second.back(); |
| |
|
| | IntPoint startPrevPoint( |
| | long(lastPrevTPoint.first * scaleFactor), |
| | long(lastPrevTPoint.second * scaleFactor) |
| | ); |
| | IntPoint startPoint(long(lastTPoint.first * scaleFactor), long(lastTPoint.second * scaleFactor)); |
| |
|
| | |
| | bool linkFound = false; |
| | double linkDistance = sqrt(DistanceSqrd(startPoint, endPoint)); |
| | if (linkDistance < NTOL) { |
| | linkFound = true; |
| | } |
| |
|
| | if (!linkFound) { |
| | size_t clpPathIndex; |
| | size_t clpSegmentIndex; |
| | double clpParameter; |
| | IntPoint clp; |
| |
|
| | double beaconOffset = stepOverScaled; |
| | if (beaconOffset > linkDistance) { |
| | beaconOffset = linkDistance; |
| | } |
| |
|
| | double pathLen = PathLength(passToolPath); |
| | if (beaconOffset > pathLen / 2) { |
| | beaconOffset = pathLen / 2; |
| | } |
| | if (beaconOffset > linkDistance / 2) { |
| | beaconOffset = linkDistance / 2; |
| | } |
| |
|
| | DistancePointToPathsSqrd( |
| | toolBoundPaths, |
| | startPoint, |
| | clp, |
| | clpPathIndex, |
| | clpSegmentIndex, |
| | clpParameter |
| | ); |
| | DoublePoint startDir = GetPathDirectionV(toolBoundPaths[clpPathIndex], clpSegmentIndex); |
| |
|
| | DistancePointToPathsSqrd( |
| | toolBoundPaths, |
| | endPoint, |
| | clp, |
| | clpPathIndex, |
| | clpSegmentIndex, |
| | clpParameter |
| | ); |
| | DoublePoint endDir = GetPathDirectionV(toolBoundPaths[clpPathIndex], clpSegmentIndex); |
| |
|
| | IntPoint startBeacon( |
| | startPoint.X - beaconOffset * (startDir.Y - startDir.X), |
| | startPoint.Y + beaconOffset * (startDir.X + startDir.Y) |
| | ); |
| | IntPoint endBeacon( |
| | endPoint.X - beaconOffset * (endDir.X + endDir.Y), |
| | endPoint.Y + beaconOffset * (endDir.X - endDir.Y) |
| | ); |
| | Path leadOutPath; |
| | MakeLeadPath(false, startPoint, startDir, startBeacon, clearedBefore, toolBoundPaths, leadOutPath); |
| |
|
| | Path leadInPath; |
| | MakeLeadPath( |
| | true, |
| | endPoint, |
| | DoublePoint(-endDir.X, -endDir.Y), |
| | endBeacon, |
| | clearedBefore, |
| | toolBoundPaths, |
| | leadInPath |
| | ); |
| | ReversePath(leadInPath); |
| |
|
| | Path linkPath; |
| | MotionType linkType = MotionType::mtCutting; |
| |
|
| | |
| | |
| | |
| |
|
| | if (ResolveLinkPath(leadOutPath.back(), leadInPath.front(), clearedBefore, linkPath)) { |
| | linkType = MotionType::mtLinkClear; |
| | double remainingLeadInExtension = stepOverScaled / 2; |
| | while (linkPath.size() >= 2 && remainingLeadInExtension > NTOL) { |
| | IntPoint p1 = linkPath.at(linkPath.size() - 2); |
| | IntPoint p2 = linkPath.at(linkPath.size() - 1); |
| | double l = sqrt(DistanceSqrd(p1, p2)); |
| | if (l >= remainingLeadInExtension) { |
| | IntPoint splitPoint( |
| | p1.X + (p2.X - p1.X) * (l - remainingLeadInExtension) / l, |
| | p1.Y + (p2.Y - p1.Y) * (l - remainingLeadInExtension) / l |
| | ); |
| | linkPath.pop_back(); |
| | linkPath.push_back(splitPoint); |
| | leadInPath.insert(leadInPath.begin(), splitPoint); |
| | remainingLeadInExtension = 0; |
| | Path checkPath; |
| | checkPath.push_back(p2); |
| | checkPath.push_back(splitPoint); |
| | if (!IsClearPath(checkPath, clearedBefore, 0)) { |
| | remainingLeadInExtension = stepOverScaled / 2; |
| | } |
| | } |
| | else { |
| | linkPath.pop_back(); |
| | leadInPath.insert(leadInPath.begin(), p1); |
| | remainingLeadInExtension -= l; |
| | if (remainingLeadInExtension < NTOL) { |
| | Path checkPath; |
| | checkPath.push_back(p2); |
| | checkPath.push_back(p1); |
| | if (!IsClearPath(checkPath, clearedBefore, 0)) { |
| | remainingLeadInExtension = stepOverScaled / 2; |
| | } |
| | } |
| | } |
| | } |
| | } |
| | else { |
| | linkType = MotionType::mtLinkNotClear; |
| | double dist = sqrt(DistanceSqrd(leadOutPath.back(), leadInPath.front())); |
| | if (dist < 2 * stepOverScaled |
| | && IsAllowedToCutTrough( |
| | IntPoint( |
| | leadOutPath.back().X + (leadInPath.front().X - leadOutPath.back().X) / dist, |
| | leadOutPath.back().Y + (leadInPath.front().Y - leadOutPath.back().Y) / dist |
| | ), |
| | IntPoint( |
| | leadInPath.front().X - (leadInPath.front().X - leadOutPath.back().X) / dist, |
| | leadInPath.front().Y - (leadInPath.front().Y - leadOutPath.back().Y) / dist |
| | ), |
| | clearedBefore, |
| | toolBoundPaths |
| | )) { |
| | linkType = MotionType::mtCutting; |
| | } |
| | |
| |
|
| | linkPath.clear(); |
| | linkPath.push_back(leadOutPath.back()); |
| | linkPath.push_back(leadInPath.front()); |
| | } |
| |
|
| | |
| | Paths linkPaths; |
| | linkPaths.push_back(leadOutPath); |
| | linkPaths.push_back(linkPath); |
| | linkPaths.push_back(leadInPath); |
| |
|
| | if (linkType == MotionType::mtLinkClear) { |
| | SmoothPaths(linkPaths, 0.1 * stepOverScaled, 1, 4); |
| | } |
| |
|
| | leadOutPath = linkPaths[0]; |
| | linkPath = linkPaths[1]; |
| | leadInPath = linkPaths[2]; |
| |
|
| | |
| | TPath linkPath1; |
| | linkPath1.first = MotionType::mtCutting; |
| | for (const auto& pt : leadOutPath) { |
| | linkPath1.second.emplace_back(double(pt.X) / scaleFactor, double(pt.Y) / scaleFactor); |
| | } |
| | output.AdaptivePaths.push_back(linkPath1); |
| |
|
| | |
| | TPath linkPath2; |
| | linkPath2.first = linkType; |
| | for (const auto& pt : linkPath) { |
| | linkPath2.second.emplace_back(double(pt.X) / scaleFactor, double(pt.Y) / scaleFactor); |
| | } |
| | output.AdaptivePaths.push_back(linkPath2); |
| |
|
| | |
| | TPath linkPath3; |
| | linkPath3.first = MotionType::mtCutting; |
| | for (const auto& pt : leadInPath) { |
| | linkPath3.second.emplace_back(double(pt.X) / scaleFactor, double(pt.Y) / scaleFactor); |
| | } |
| |
|
| | output.AdaptivePaths.push_back(linkPath3); |
| |
|
| | clearedAfter.ExpandCleared(leadInPath); |
| | clearedAfter.ExpandCleared(leadOutPath); |
| |
|
| | linkFound = true; |
| | } |
| | if (!linkFound) { |
| | |
| | Path tp; |
| | tp << startPoint; |
| | tp << endPoint; |
| | MotionType mt = IsClearPath(tp, clearedBefore) ? MotionType::mtLinkClear |
| | : MotionType::mtLinkNotClear; |
| |
|
| | |
| | if (mt == MotionType::mtLinkClear && linkDistance < toolRadiusScaled) { |
| | mt = MotionType::mtCutting; |
| | clearedAfter.ExpandCleared(tp); |
| | } |
| |
|
| | TPath linkPath; |
| | linkPath.first = mt; |
| | linkPath.second.emplace_back( |
| | double(startPoint.X) / scaleFactor, |
| | double(startPoint.Y) / scaleFactor |
| | ); |
| | linkPath.second.emplace_back( |
| | double(endPoint.X) / scaleFactor, |
| | double(endPoint.Y) / scaleFactor |
| | ); |
| | output.AdaptivePaths.push_back(linkPath); |
| | } |
| | } |
| | TPath cutPath; |
| | cutPath.first = MotionType::mtCutting; |
| | for (const auto& p : passToolPath) { |
| | DPoint nextT; |
| | nextT.first = double(p.X) / scaleFactor; |
| | nextT.second = double(p.Y) / scaleFactor; |
| | cutPath.second.push_back(nextT); |
| | } |
| |
|
| | if (!cutPath.second.empty()) { |
| | output.AdaptivePaths.push_back(cutPath); |
| | } |
| | Perf_AppendToolPath.Stop(); |
| | } |
| |
|
| | void Adaptive2d::CheckReportProgress(TPaths& progressPaths, bool force) |
| | { |
| | if (!force && (clock() - lastProgressTime < PROGRESS_TICKS)) { |
| | return; |
| | } |
| | lastProgressTime = clock(); |
| | if (progressPaths.empty()) { |
| | return; |
| | } |
| | if (progressCallback) { |
| | if ((*progressCallback)(progressPaths)) { |
| | stopProcessing = true; |
| | } |
| | } |
| | |
| | if (progressPaths.back().second.empty()) { |
| | return; |
| | } |
| | TPath* lastPath = &progressPaths.back(); |
| | DPoint* lastPoint = &lastPath->second.back(); |
| | DPoint next(lastPoint->first, lastPoint->second); |
| | while (progressPaths.size() > 1) { |
| | progressPaths.pop_back(); |
| | } |
| | while (!progressPaths.front().second.empty()) { |
| | progressPaths.front().second.pop_back(); |
| | } |
| | progressPaths.front().first = MotionType::mtCutting; |
| | progressPaths.front().second.push_back(next); |
| | } |
| |
|
| | void Adaptive2d::AddPathsToProgress(TPaths& progressPaths, Paths paths, MotionType mt) |
| | { |
| | for (const auto& pth : paths) { |
| | if (!pth.empty()) { |
| | progressPaths.push_back(TPath()); |
| | progressPaths.back().first = mt; |
| | for (const auto pt : pth) { |
| | progressPaths.back().second.emplace_back( |
| | double(pt.X) / scaleFactor, |
| | double(pt.Y) / scaleFactor |
| | ); |
| | } |
| | progressPaths.back().second.emplace_back( |
| | double(pth.front().X) / scaleFactor, |
| | double(pth.front().Y) / scaleFactor |
| | ); |
| | } |
| | } |
| | } |
| |
|
| | void Adaptive2d::AddPathToProgress(TPaths& progressPaths, const Path pth, MotionType mt) |
| | { |
| | if (!pth.empty()) { |
| | progressPaths.push_back(TPath()); |
| | progressPaths.back().first = mt; |
| | for (const auto pt : pth) { |
| | progressPaths.back().second.emplace_back( |
| | double(pt.X) / scaleFactor, |
| | double(pt.Y) / scaleFactor |
| | ); |
| | } |
| | } |
| | } |
| |
|
| | void Adaptive2d::ProcessPolyNode(Paths boundPaths, Paths toolBoundPaths) |
| | { |
| | Perf_ProcessPolyNode.Start(); |
| | current_region++; |
| | cout << "** Processing region: " << current_region << endl; |
| |
|
| | |
| | |
| | Clipper clip; |
| | ClipperOffset clipof; |
| |
|
| | IntPoint entryPoint; |
| | long helixRadiusScaled; |
| | TPaths progressPaths; |
| | progressPaths.reserve(10000); |
| |
|
| | CleanPolygons(toolBoundPaths); |
| | SimplifyPolygons(toolBoundPaths); |
| |
|
| | CleanPolygons(boundPaths); |
| | SimplifyPolygons(boundPaths); |
| |
|
| | AddPathsToProgress(progressPaths, toolBoundPaths, MotionType::mtLinkClear); |
| |
|
| | IntPoint toolPos; |
| | DoublePoint toolDir; |
| | ClearedArea cleared(toolRadiusScaled); |
| | bool outsideEntry = false; |
| | bool firstEngagePoint = true; |
| | Paths engageBounds = toolBoundPaths; |
| |
|
| | if (!forceInsideOut |
| | && FindEntryPointOutside( |
| | progressPaths, |
| | toolBoundPaths, |
| | boundPaths, |
| | cleared, |
| | entryPoint, |
| | toolPos, |
| | toolDir |
| | )) { |
| | if (!Orientation(engageBounds[0])) { |
| | ReversePath(engageBounds[0]); |
| | } |
| | |
| | Paths outsideEngage; |
| | clipof.Clear(); |
| | clipof.AddPaths(stockInputPaths, JoinType::jtRound, EndType::etClosedPolygon); |
| | clipof.Execute(outsideEngage, toolRadiusScaled - stepOverFactor * toolRadiusScaled); |
| | CleanPolygons(outsideEngage); |
| | ReversePaths(outsideEngage); |
| | for (const auto& p : outsideEngage) { |
| | engageBounds.push_back(p); |
| | } |
| | outsideEntry = true; |
| | } |
| | else { |
| | if (!FindEntryPoint( |
| | progressPaths, |
| | toolBoundPaths, |
| | boundPaths, |
| | cleared, |
| | entryPoint, |
| | toolPos, |
| | toolDir, |
| | helixRadiusScaled |
| | )) { |
| | Perf_ProcessPolyNode.Stop(); |
| | return; |
| | } |
| | } |
| |
|
| | EngagePoint engage(engageBounds); |
| |
|
| | if (outsideEntry) { |
| | engage.moveToClosestPoint(toolPos, 2 * MIN_STEP_CLIPPER); |
| | engage.moveForward(MIN_STEP_CLIPPER); |
| | toolPos = engage.getCurrentPoint(); |
| | toolDir = engage.getCurrentDir(); |
| | entryPoint = toolPos; |
| | } |
| |
|
| | |
| | |
| |
|
| | AdaptiveOutput output; |
| | output.ReturnMotionType = 0; |
| | output.HelixCenterPoint.first = double(entryPoint.X) / scaleFactor; |
| | output.HelixCenterPoint.second = double(entryPoint.Y) / scaleFactor; |
| |
|
| | long stepScaled = long(MIN_STEP_CLIPPER); |
| | IntPoint engagePoint; |
| |
|
| | IntPoint newToolPos; |
| | DoublePoint newToolDir; |
| |
|
| | CheckReportProgress(progressPaths, true); |
| |
|
| | IntPoint startPoint = toolPos; |
| | output.StartPoint = DPoint(double(startPoint.X) / scaleFactor, double(startPoint.Y) / scaleFactor); |
| |
|
| | Path passToolPath; |
| | Path toClearPath; |
| | IntPoint clp; |
| | vector<DoublePoint> gyro; |
| | vector<double> angleHistory; |
| | double angle = std::numbers::pi; |
| | engagePoint = toolPos; |
| | Interpolation interp; |
| |
|
| | long total_iterations = 0; |
| | long total_points = 0; |
| | long total_exceeded = 0; |
| | long total_output_points = 0; |
| | long over_cut_count = 0; |
| | long bad_engage_count = 0; |
| | double prevDistFromStart = 0; |
| | double refinement_factor = 1; |
| | bool prevDistTrend = false; |
| |
|
| | double perf_total_len = 0; |
| | #ifdef DEV_MODE |
| | clock_t start_clock = clock(); |
| | #endif |
| | ClearedArea clearedBeforePass(toolRadiusScaled); |
| | clearedBeforePass.SetClearedPaths(cleared.GetCleared()); |
| |
|
| | |
| | |
| | |
| | for (long pass = 0; pass < PASSES_LIMIT; pass++) { |
| | if (stopProcessing) { |
| | break; |
| | } |
| |
|
| | passToolPath.clear(); |
| | toClearPath.clear(); |
| | angleHistory.clear(); |
| |
|
| | |
| | if (progressPaths.empty()) { |
| | progressPaths.push_back(TPath()); |
| | } |
| | else { |
| | |
| | if (!progressPaths.back().second.empty()) { |
| | progressPaths.push_back(TPath()); |
| | } |
| | } |
| |
|
| | angle = std::numbers::pi / 4; |
| | bool recalcArea = false; |
| | double cumulativeCutArea = 0; |
| | |
| | gyro.clear(); |
| | for (int i = 0; i < DIRECTION_SMOOTHING_BUFLEN; i++) { |
| | gyro.push_back(toolDir); |
| | } |
| |
|
| | size_t clpPathIndex; |
| | size_t clpSegmentIndex; |
| | double clpParameter; |
| | double passLength = 0; |
| | double noCutDistance = 0; |
| | clearedBeforePass.SetClearedPaths(cleared.GetCleared()); |
| | |
| | |
| | |
| | for (long point_index = 0; point_index < POINTS_PER_PASS_LIMIT; point_index++) { |
| | if (stopProcessing) { |
| | break; |
| | } |
| |
|
| | total_points++; |
| | AverageDirection(gyro, toolDir); |
| | Perf_DistanceToBoundary.Start(); |
| |
|
| | double distanceToBoundary = sqrt( |
| | DistancePointToPathsSqrd(toolBoundPaths, toolPos, clp, clpPathIndex, clpSegmentIndex, clpParameter) |
| | ); |
| | DoublePoint boundaryDir = GetPathDirectionV(toolBoundPaths[clpPathIndex], clpSegmentIndex); |
| | double distBoundaryPointToEngage = sqrt(DistanceSqrd(clp, engagePoint)); |
| |
|
| | Perf_DistanceToBoundary.Stop(); |
| | double distanceToEngage = sqrt(DistanceSqrd(toolPos, engagePoint)); |
| |
|
| | double targetAreaPD = optimalCutAreaPD; |
| |
|
| | |
| | double slowDownDistance = max(double(toolRadiusScaled) / 4, MIN_STEP_CLIPPER * 8); |
| | if (distanceToBoundary < slowDownDistance || distanceToEngage < slowDownDistance) { |
| | stepScaled = long(MIN_STEP_CLIPPER); |
| | } |
| | else if (fabs(angle) > NTOL) { |
| | stepScaled = long(MIN_STEP_CLIPPER / fabs(angle)); |
| | } |
| | else { |
| | stepScaled = long(MIN_STEP_CLIPPER * 8); |
| | } |
| |
|
| | |
| | if (stepScaled > min(long(toolRadiusScaled / 4), long(MIN_STEP_CLIPPER * 8))) { |
| | stepScaled = min(long(toolRadiusScaled / 4), long(MIN_STEP_CLIPPER * 8)); |
| | } |
| | if (stepScaled < MIN_STEP_CLIPPER) { |
| | stepScaled = long(MIN_STEP_CLIPPER); |
| | } |
| |
|
| | |
| | |
| | |
| | double predictedAngle = averageDV(angleHistory); |
| | double maxError = AREA_ERROR_FACTOR * optimalCutAreaPD; |
| | double area = 0; |
| | double areaPD = 0; |
| | interp.clear(); |
| | |
| | Perf_PointIterations.Start(); |
| | int iteration; |
| | double prev_error = __DBL_MAX__; |
| | bool pointNotInterp; |
| | for (iteration = 0; iteration < MAX_ITERATIONS; iteration++) { |
| | total_iterations++; |
| | if (iteration == 0) { |
| | angle = predictedAngle; |
| | pointNotInterp = true; |
| | } |
| | else if (iteration == 1) { |
| | angle = interp.MIN_ANGLE; |
| | pointNotInterp = true; |
| | } |
| | else if (iteration == 2) { |
| | if (interp.bothSides()) { |
| | angle = interp.interpolateAngle(); |
| | pointNotInterp = false; |
| | } |
| | else { |
| | angle = interp.MAX_ANGLE; |
| | pointNotInterp = true; |
| | } |
| | } |
| | else { |
| | angle = interp.interpolateAngle(); |
| | pointNotInterp = false; |
| | } |
| | angle = interp.clampAngle(angle); |
| |
|
| | newToolDir = rotate(toolDir, angle); |
| | newToolPos = IntPoint( |
| | long(toolPos.X + newToolDir.X * stepScaled), |
| | long(toolPos.Y + newToolDir.Y * stepScaled) |
| | ); |
| |
|
| | |
| | bool intRepeat = false; |
| | if (interp.m_min && newToolPos == interp.m_min->first.second) { |
| | interp.m_min = {{angle, newToolPos}, interp.m_min->second}; |
| | intRepeat = true; |
| | } |
| | if (interp.m_max && newToolPos == interp.m_max->first.second) { |
| | interp.m_max = {{angle, newToolPos}, interp.m_max->second}; |
| | intRepeat = true; |
| | } |
| |
|
| | if (intRepeat) { |
| | if (interp.m_min && interp.m_max |
| | && abs(interp.m_min->first.second.X - interp.m_max->first.second.X) <= 1 |
| | && abs(interp.m_min->first.second.Y - interp.m_max->first.second.Y) <= 1) { |
| | if (pointNotInterp) { |
| | |
| | |
| | |
| | continue; |
| | } |
| | |
| | double error; |
| | if (abs(interp.m_min->second) < abs(interp.m_max->second)) { |
| | newToolDir = rotate(toolDir, interp.m_min->first.first); |
| | newToolPos = interp.m_min->first.second; |
| | error = interp.m_min->second; |
| | } |
| | else { |
| | newToolDir = rotate(toolDir, interp.m_max->first.first); |
| | newToolPos = interp.m_max->first.second; |
| | error = interp.m_max->second; |
| | } |
| | areaPD = error + targetAreaPD; |
| | area = areaPD * double(stepScaled); |
| | break; |
| | } |
| | continue; |
| | } |
| |
|
| | area = CalcCutArea(clip, toolPos, newToolPos, cleared); |
| |
|
| | areaPD = area / double(stepScaled); |
| | double error = areaPD - targetAreaPD; |
| | interp.addPoint(error, {angle, newToolPos}, pointNotInterp); |
| | |
| | |
| | |
| | if (fabs(error) < maxError) { |
| | angleHistory.push_back(angle); |
| | if (angleHistory.size() > ANGLE_HISTORY_POINTS) { |
| | angleHistory.erase(angleHistory.begin()); |
| | } |
| | break; |
| | } |
| | if (iteration == MAX_ITERATIONS - 1) { |
| | total_exceeded++; |
| | } |
| | prev_error = error; |
| | } |
| | Perf_PointIterations.Stop(); |
| |
|
| | recalcArea = false; |
| | |
| | double relDistToBoundary = 4 * distanceToBoundary / stepOverScaled; |
| | if (relDistToBoundary <= 1.0 && passLength > 2 * stepOverFactor |
| | && distanceToEngage > 2 * stepOverScaled |
| | && distBoundaryPointToEngage > 2 * stepOverScaled) { |
| | double wb = 1 - relDistToBoundary; |
| | newToolDir = DoublePoint( |
| | newToolDir.X + wb * boundaryDir.X, |
| | newToolDir.Y + wb * boundaryDir.Y |
| | ); |
| | NormalizeV(newToolDir); |
| | newToolPos = IntPoint( |
| | long(toolPos.X + newToolDir.X * stepScaled), |
| | long(toolPos.Y + newToolDir.Y * stepScaled) |
| | ); |
| | recalcArea = true; |
| | } |
| |
|
| | |
| | |
| | |
| | long rotateStep = 0; |
| | while (!IsPointWithinCutRegion(toolBoundPaths, newToolPos) && rotateStep < 180) { |
| | rotateStep++; |
| | |
| | recalcArea = true; |
| | newToolDir = rotate(newToolDir, std::numbers::pi / 90); |
| | newToolPos = IntPoint( |
| | long(toolPos.X + newToolDir.X * stepScaled), |
| | long(toolPos.Y + newToolDir.Y * stepScaled) |
| | ); |
| | } |
| | if (rotateStep >= 180) { |
| | #ifdef DEV_MODE |
| | cerr << "Warning: unexpected number of rotate iterations." << endl; |
| | #endif |
| | break; |
| | } |
| |
|
| | if (recalcArea) { |
| | area = CalcCutArea(clip, toolPos, newToolPos, cleared); |
| | } |
| |
|
| | |
| | if (area > stepScaled * optimalCutAreaPD && areaPD > 2 * optimalCutAreaPD) { |
| | over_cut_count++; |
| | break; |
| | } |
| |
|
| | |
| | |
| | double distFromStart = sqrt(DistanceSqrd(toolPos, startPoint)); |
| | bool distanceTrend = distFromStart > prevDistFromStart ? true : false; |
| |
|
| | if (distanceTrend != prevDistTrend) { |
| | cleared.ExpandCleared(toClearPath); |
| | toClearPath.clear(); |
| | } |
| | prevDistTrend = distanceTrend; |
| | prevDistFromStart = distFromStart; |
| |
|
| | if (area > 0.5 * MIN_CUT_AREA_FACTOR * optimalCutAreaPD) { |
| | noCutDistance = 0; |
| | if (toClearPath.empty()) { |
| | toClearPath.push_back(toolPos); |
| | } |
| | toClearPath.push_back(newToolPos); |
| |
|
| | cumulativeCutArea += area; |
| |
|
| | |
| | if (passToolPath.empty()) { |
| | |
| | |
| | if (output.AdaptivePaths.empty() && outsideEntry) { |
| | entryPoint = toolPos; |
| | output.HelixCenterPoint.first = double(entryPoint.X) / scaleFactor; |
| | output.HelixCenterPoint.second = double(entryPoint.Y) / scaleFactor; |
| | output.StartPoint = DPoint( |
| | double(entryPoint.X) / scaleFactor, |
| | double(entryPoint.Y) / scaleFactor |
| | ); |
| | } |
| | passToolPath.push_back(toolPos); |
| | } |
| | passToolPath.push_back(newToolPos); |
| | perf_total_len += stepScaled; |
| | passLength += stepScaled; |
| | toolPos = newToolPos; |
| |
|
| | |
| | if (progressPaths.empty()) { |
| | progressPaths.push_back(TPath()); |
| | } |
| | progressPaths.back().second.emplace_back( |
| | double(newToolPos.X) / scaleFactor, |
| | double(newToolPos.Y) / scaleFactor |
| | ); |
| |
|
| | |
| | gyro.push_back(newToolDir); |
| | gyro.erase(gyro.begin()); |
| | CheckReportProgress(progressPaths); |
| | } |
| | else { |
| | #ifdef DEV_MODE |
| | |
| | |
| | |
| | |
| | |
| | #endif |
| | |
| | if (noCutDistance > stepOverScaled) { |
| | break; |
| | } |
| | noCutDistance += stepScaled; |
| | } |
| | } |
| |
|
| | if (!toClearPath.empty()) { |
| | cleared.ExpandCleared(toClearPath); |
| | toClearPath.clear(); |
| | } |
| | if (cumulativeCutArea > MIN_CUT_AREA_FACTOR * optimalCutAreaPD) { |
| | Path cleaned; |
| | CleanPath(passToolPath, cleaned, CLEAN_PATH_TOLERANCE); |
| | total_output_points += long(cleaned.size()); |
| | AppendToolPath(progressPaths, output, cleaned, clearedBeforePass, cleared, toolBoundPaths); |
| | CheckReportProgress(progressPaths); |
| | bad_engage_count = 0; |
| | engage.ResetPasses(); |
| | } |
| | else { |
| | bad_engage_count++; |
| | } |
| |
|
| | if (bad_engage_count > 10000) { |
| | cerr << "Break (next valid engage point not found)." << endl; |
| | break; |
| | } |
| |
|
| | |
| | if (firstEngagePoint) { |
| | engage.moveToClosestPoint(newToolPos, stepScaled + 1); |
| | firstEngagePoint = false; |
| | } |
| | else { |
| | double moveDistance = ENGAGE_SCAN_DISTANCE_FACTOR * stepOverScaled * refinement_factor; |
| |
|
| | if (!engage.nextEngagePoint( |
| | this, |
| | cleared, |
| | moveDistance, |
| | ENGAGE_AREA_THR_FACTOR * optimalCutAreaPD, |
| | 4 * referenceCutArea * stepOverFactor |
| | )) { |
| | |
| | Paths remaining; |
| | for (const auto& p : cleared.GetCleared()) { |
| | if (!p.empty() && IsPointWithinCutRegion(toolBoundPaths, p.front()) |
| | && DistancePointToPathsSqrd( |
| | boundPaths, |
| | p.front(), |
| | clp, |
| | clpPathIndex, |
| | clpSegmentIndex, |
| | clpParameter |
| | ) > 4 * toolRadiusScaled * toolRadiusScaled) { |
| | remaining.push_back(p); |
| | } |
| | }; |
| | if (remaining.empty()) { |
| | cout << "All cleared." << endl; |
| | break; |
| | } |
| | else { |
| | cout << "Clearing " << remaining.size() << " remaining internal path(s)." << endl; |
| | } |
| |
|
| | |
| | clipof.Clear(); |
| | clipof.AddPaths(remaining, JoinType::jtRound, EndType::etClosedPolygon); |
| | clipof.Execute(remaining, toolRadiusScaled - 0.5 * stepOverScaled); |
| |
|
| | ReversePaths(remaining); |
| | engage.SetPaths(remaining); |
| | engage.moveToClosestPoint(newToolPos, stepScaled + 1); |
| | if (!engage.nextEngagePoint( |
| | this, |
| | cleared, |
| | moveDistance, |
| | ENGAGE_AREA_THR_FACTOR * optimalCutAreaPD, |
| | 4 * referenceCutArea * stepOverFactor |
| | )) { |
| | break; |
| | } |
| | } |
| | } |
| | toolPos = engage.getCurrentPoint(); |
| | toolDir = engage.getCurrentDir(); |
| | } |
| |
|
| | |
| | |
| | |
| | if (finishingProfile) { |
| | Paths finishingPaths; |
| | clipof.Clear(); |
| | clipof.AddPaths(boundPaths, JoinType::jtRound, EndType::etClosedPolygon); |
| | clipof.Execute(finishingPaths, -toolRadiusScaled); |
| |
|
| | clipof.Clear(); |
| | clipof.AddPaths(finishingPaths, JoinType::jtRound, EndType::etClosedPolygon); |
| | clipof.Execute(toolBoundPaths, -1); |
| |
|
| | IntPoint lastPoint = toolPos; |
| | Path finShiftedPath; |
| |
|
| | bool allCutsAllowed = true; |
| | while (!stopProcessing && PopPathWithClosestPoint(finishingPaths, lastPoint, finShiftedPath)) { |
| | if (finShiftedPath.empty()) { |
| | continue; |
| | } |
| | |
| | |
| | bool allPointsOutside = true; |
| | IntPoint p1 = finShiftedPath.front(); |
| | for (const auto& pt : finShiftedPath) { |
| |
|
| | |
| | if (IsPointWithinCutRegion( |
| | stockInputPaths, |
| | IntPoint((p1.X + pt.X) / 2, (p1.Y + pt.Y) / 2) |
| | )) { |
| | allPointsOutside = false; |
| | break; |
| | } |
| | |
| | if (IsPointWithinCutRegion(stockInputPaths, pt)) { |
| | allPointsOutside = false; |
| | break; |
| | } |
| |
|
| | p1 = pt; |
| | } |
| | if (allPointsOutside) { |
| | continue; |
| | } |
| |
|
| | progressPaths.push_back(TPath()); |
| | |
| | for (auto& pt : finShiftedPath) { |
| | progressPaths.back().second.emplace_back( |
| | double(pt.X) / scaleFactor, |
| | double(pt.Y) / scaleFactor |
| | ); |
| | } |
| |
|
| | if (!finShiftedPath.empty()) { |
| | finShiftedPath << finShiftedPath.front(); |
| | } |
| |
|
| | Path finCleaned; |
| | CleanPath(finShiftedPath, finCleaned, FINISHING_CLEAN_PATH_TOLERANCE); |
| |
|
| | |
| | for (size_t i = 1; i < finCleaned.size(); i++) { |
| | if (!IsAllowedToCutTrough( |
| | finCleaned.at(i - 1), |
| | finCleaned.at(i), |
| | cleared, |
| | toolBoundPaths, |
| | 2.0, |
| | true |
| | )) { |
| | allCutsAllowed = false; |
| | } |
| | } |
| |
|
| | |
| | finCleaned.push_back(finCleaned.front()); |
| | AppendToolPath(progressPaths, output, finCleaned, cleared, cleared, toolBoundPaths); |
| |
|
| | cleared.ExpandCleared(finCleaned); |
| |
|
| | if (!finCleaned.empty()) { |
| | lastPoint.X = finCleaned.back().X; |
| | lastPoint.Y = finCleaned.back().Y; |
| | } |
| | } |
| |
|
| | Path returnPath; |
| | returnPath << lastPoint; |
| | returnPath << entryPoint; |
| | output.ReturnMotionType = IsClearPath(returnPath, cleared) ? MotionType::mtLinkClear |
| | : MotionType::mtLinkNotClear; |
| |
|
| | |
| | #ifdef DEV_MODE |
| | Perf_ProcessPolyNode.Stop(); |
| | Perf_ProcessPolyNode.DumpResults(); |
| | Perf_PointIterations.DumpResults(); |
| | Perf_CalcCutAreaCirc.DumpResults(); |
| | Perf_CalcCutAreaClip.DumpResults(); |
| | Perf_NextEngagePoint.DumpResults(); |
| | Perf_ExpandCleared.DumpResults(); |
| | Perf_DistanceToBoundary.DumpResults(); |
| | Perf_AppendToolPath.DumpResults(); |
| | Perf_IsAllowedToCutTrough.DumpResults(); |
| | Perf_IsClearPath.DumpResults(); |
| | #endif |
| | CheckReportProgress(progressPaths, true); |
| | #ifdef DEV_MODE |
| | double duration = ((double)(clock() - start_clock)) / CLOCKS_PER_SEC; |
| | cout << "PolyNode perf:" << perf_total_len / double(scaleFactor) / duration << " mm/sec" |
| | << " processed_points:" << total_points << " output_points:" << total_output_points |
| | << " total_iterations:" << total_iterations |
| | << " iter_per_point:" << (double(total_iterations) / ((double(total_points) + 0.001))) |
| | << " total_exceeded:" << total_exceeded << " (" |
| | << 100 * double(total_exceeded) / double(total_points) << "%)" << endl; |
| | #else |
| | (void)total_output_points; |
| | (void)over_cut_count; |
| | (void)total_exceeded; |
| | (void)total_points; |
| | (void)total_iterations; |
| | (void)perf_total_len; |
| | #endif |
| |
|
| | |
| | if (!allCutsAllowed) { |
| | cerr << "Warning: some cuts may be above optimal step-over. Please double check the " |
| | "results." |
| | << endl |
| | << "Hint: try to modify accuracy and/or step-over." << endl; |
| | } |
| | } |
| | results.push_back(output); |
| | } |
| |
|
| | } |
| |
|