Fix scarf seams

This commit is contained in:
Aleksandr Dobkin
2026-03-13 01:56:51 -07:00
parent 59f6d75df7
commit 9fae402d62
14 changed files with 368 additions and 168 deletions

View File

@@ -85,7 +85,7 @@ static void do_arc_fitting_tmpl(const POINTS& points, std::vector<PathFittingDat
} else { } else {
//BBS: save the first segment as line move when 3 point-line can't be fit as arc move //BBS: save the first segment as line move when 3 point-line can't be fit as arc move
if (result.empty() || result.back().path_type != EMovePathType::Linear_move) if (result.empty() || result.back().path_type != EMovePathType::Linear_move)
result.emplace_back(std::move(PathFittingData{front_index, front_index + 1, EMovePathType::Linear_move, ArcSegment()})); result.emplace_back(PathFittingData{front_index, front_index + 1, EMovePathType::Linear_move, ArcSegment()});
else if(result.back().path_type == EMovePathType::Linear_move) else if(result.back().path_type == EMovePathType::Linear_move)
result.back().end_point_index = front_index + 1; result.back().end_point_index = front_index + 1;
} }
@@ -98,7 +98,7 @@ static void do_arc_fitting_tmpl(const POINTS& points, std::vector<PathFittingDat
//BBS: handle the remain data //BBS: handle the remain data
if (front_index != back_index) { if (front_index != back_index) {
if (result.empty() || result.back().path_type != EMovePathType::Linear_move) if (result.empty() || result.back().path_type != EMovePathType::Linear_move)
result.emplace_back(std::move(PathFittingData{front_index, back_index, EMovePathType::Linear_move, ArcSegment()})); result.emplace_back(PathFittingData{front_index, back_index, EMovePathType::Linear_move, ArcSegment()});
else if (result.back().path_type == EMovePathType::Linear_move) else if (result.back().path_type == EMovePathType::Linear_move)
result.back().end_point_index = back_index; result.back().end_point_index = back_index;
} }

View File

@@ -5541,7 +5541,11 @@ static std::unique_ptr<EdgeGrid::Grid> calculate_layer_edge_grid(const Layer& la
return out; return out;
} }
std::string GCode::extrude_loop(const ExtrusionLoop &loop_ref, std::string description, double speed, const ExtrusionEntitiesPtr& region_perimeters, const Point* start_point) std::string GCode::extrude_loop(const ExtrusionLoop& loop_ref,
const std::string& description,
double speed,
const ExtrusionEntitiesPtr& region_perimeters,
const Point* start_point)
{ {
// get a copy; don't modify the orientation of the original loop object otherwise // get a copy; don't modify the orientation of the original loop object otherwise
// next copies (if any) would not detect the correct orientation // next copies (if any) would not detect the correct orientation
@@ -5707,7 +5711,7 @@ std::string GCode::extrude_loop(const ExtrusionLoop &loop_ref, std::string descr
if (!enable_seam_slope) { if (!enable_seam_slope) {
for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) { for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) {
gcode += this->_extrude(*path, description, speed_for_path(*path)); gcode += this->_extrude(*path, description, speed_for_path(*path));
// Orca: Adaptive PA - dont adapt PA after the first pultipath extrusion is completed // Orca: Adaptive PA - dont adapt PA after the first multipath extrusion is completed
// as we have already set the PA value to the average flow over the totality of the path // as we have already set the PA value to the average flow over the totality of the path
// in the first extrude move // in the first extrude move
// TODO: testing is needed with slope seams and adaptive PA. // TODO: testing is needed with slope seams and adaptive PA.
@@ -5819,7 +5823,7 @@ std::string GCode::extrude_loop(const ExtrusionLoop &loop_ref, std::string descr
return gcode; return gcode;
} }
std::string GCode::extrude_multi_path(const ExtrusionMultiPath &multipath, std::string description, double speed) std::string GCode::extrude_multi_path(const ExtrusionMultiPath& multipath, const std::string& description, double speed)
{ {
// extrude along the path // extrude along the path
std::string gcode; std::string gcode;
@@ -5868,7 +5872,10 @@ std::string GCode::extrude_multi_path(const ExtrusionMultiPath &multipath, std::
return gcode; return gcode;
} }
std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string description, double speed, const ExtrusionEntitiesPtr& region_perimeters) std::string GCode::extrude_entity(const ExtrusionEntity& entity,
const std::string& description,
double speed,
const ExtrusionEntitiesPtr& region_perimeters)
{ {
if (const ExtrusionPath* path = dynamic_cast<const ExtrusionPath*>(&entity)) if (const ExtrusionPath* path = dynamic_cast<const ExtrusionPath*>(&entity))
return this->extrude_path(*path, description, speed); return this->extrude_path(*path, description, speed);
@@ -5881,7 +5888,7 @@ std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string des
return ""; return "";
} }
std::string GCode::extrude_path(const ExtrusionPath &path, std::string description, double speed) std::string GCode::extrude_path(const ExtrusionPath& path, const std::string& description, double speed)
{ {
// Orca: Reset average multipath flow as this is a single line, single extrude volumetric speed path // Orca: Reset average multipath flow as this is a single line, single extrude volumetric speed path
m_multi_flow_segment_path_pa_set = false; m_multi_flow_segment_path_pa_set = false;
@@ -6155,12 +6162,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
throw RuntimeError("GCode: very low z"); throw RuntimeError("GCode: very low z");
} }
} }
gcode += this->travel_to( gcode += this->travel_to(path.first_point(), path.role(), "move to first " + description + " point", z);
path.first_point(),
path.role(),
"move to first " + description + " point; size " + std::to_string(path.polyline.size()),
z
);
m_need_change_layer_lift_z = false; m_need_change_layer_lift_z = false;
// Orca: ensure Z matches planned layer height // Orca: ensure Z matches planned layer height
if (_last_pos_undefined && !slope_need_z_travel) { if (_last_pos_undefined && !slope_need_z_travel) {

View File

@@ -390,12 +390,19 @@ private:
std::string change_layer(coordf_t print_z); std::string change_layer(coordf_t print_z);
// Orca: pass the complete collection of region perimeters to the extrude loop to check whether the wipe before external loop // Orca: pass the complete collection of region perimeters to the extrude loop to check whether the wipe before external loop
// should be executed // should be executed
std::string extrude_entity(const ExtrusionEntity &entity, std::string description = "", double speed = -1., const ExtrusionEntitiesPtr& region_perimeters = ExtrusionEntitiesPtr()); std::string extrude_entity(const ExtrusionEntity& entity,
const std::string& description = "",
double speed = -1.,
const ExtrusionEntitiesPtr& region_perimeters = ExtrusionEntitiesPtr());
// Orca: pass the complete collection of region perimeters to the extrude loop to check whether the wipe before external loop // Orca: pass the complete collection of region perimeters to the extrude loop to check whether the wipe before external loop
// should be executed // should be executed
std::string extrude_loop(const ExtrusionLoop &loop, std::string description, double speed = -1., const ExtrusionEntitiesPtr& region_perimeters = ExtrusionEntitiesPtr(), const Point* start_point = nullptr); std::string extrude_loop(const ExtrusionLoop& loop,
std::string extrude_multi_path(const ExtrusionMultiPath &multipath, std::string description = "", double speed = -1.); const std::string& description,
std::string extrude_path(const ExtrusionPath &path, std::string description = "", double speed = -1.); double speed = -1.,
const ExtrusionEntitiesPtr& region_perimeters = ExtrusionEntitiesPtr(),
const Point* start_point = nullptr);
std::string extrude_multi_path(const ExtrusionMultiPath& multipath, const std::string& description = "", double speed = -1.);
std::string extrude_path(const ExtrusionPath& path, const std::string& description = "", double speed = -1.);
// Orca: Adaptive PA variables // Orca: Adaptive PA variables
// Used for adaptive PA when extruding paths with multiple, varying flow segments. // Used for adaptive PA when extruding paths with multiple, varying flow segments.

View File

@@ -1514,8 +1514,7 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop,
current.path_idx = next_idx_modulo(current.path_idx, loop.paths.size()); current.path_idx = next_idx_modulo(current.path_idx, loop.paths.size());
current.segment_idx = 0; current.segment_idx = 0;
} }
const Point3 &p3 = loop.paths[current.path_idx].polyline.points[current.segment_idx]; current.foot_pt = loop.paths[current.path_idx].polyline.points[current.segment_idx].to_point();
current.foot_pt = Point(p3.x(), p3.y());
return current; return current;
}; };
@@ -1528,8 +1527,7 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop,
size_t closest_perimeter_point_index = 0; size_t closest_perimeter_point_index = 0;
{ // local space for the closest_perimeter_point_index { // local space for the closest_perimeter_point_index
Perimeter *closest_perimeter = nullptr; Perimeter *closest_perimeter = nullptr;
const Point3 &init_p3 = loop.paths[0].polyline.points[0]; ExtrusionLoop::ClosestPathPoint closest_point{0, 0, loop.paths[0].polyline.points[0].to_point()};
ExtrusionLoop::ClosestPathPoint closest_point{0,0,Point(init_p3.x(), init_p3.y())};
size_t points_count = std::accumulate(loop.paths.begin(), loop.paths.end(), 0, [](size_t acc,const ExtrusionPath& p) { size_t points_count = std::accumulate(loop.paths.begin(), loop.paths.end(), 0, [](size_t acc,const ExtrusionPath& p) {
return acc + p.polyline.points.size(); return acc + p.polyline.points.size();
}); });

View File

@@ -3,6 +3,7 @@
#include "ClipperUtils.hpp" #include "ClipperUtils.hpp"
#include "Geometry.hpp" #include "Geometry.hpp"
#include "PerimeterGenerator.hpp" #include "PerimeterGenerator.hpp"
#include "Point.hpp"
#include "Print.hpp" #include "Print.hpp"
#include "Surface.hpp" #include "Surface.hpp"
#include "BoundingBox.hpp" #include "BoundingBox.hpp"

View File

@@ -236,6 +236,11 @@ public:
// Convert to 2D line by dropping Z coordinate // Convert to 2D line by dropping Z coordinate
Line to_line() const { return Line(this->a.to_point(), this->b.to_point()); } Line to_line() const { return Line(this->a.to_point(), this->b.to_point()); }
static inline double distance_to_squared(const Point3& point, const Point3& a, const Point3& b)
{
return line_alg::distance_to_squared(Line3{a, b}, Vec<3, coord_t>{point});
}
Point3 a; Point3 a;
Point3 b; Point3 b;

View File

@@ -422,32 +422,23 @@ Points MultiPoint::concave_hull_2d(const Points& pts, const double tolerence)
return min_distance; return min_distance;
} }
void MultiPoint3::translate(const Point3& v)
void MultiPoint3::translate(double x, double y)
{ {
for (Vec3crd &p : points) { for (Point3& pt : points)
p(0) += coord_t(x); pt += v;
p(1) += coord_t(y);
}
}
void MultiPoint3::translate(const Point& vector)
{
this->translate(vector(0), vector(1));
} }
double MultiPoint3::length() const double MultiPoint3::length() const
{ {
double len = 0.0; const Lines3& lines = this->lines();
for (const Line3& line : this->lines()) double len = 0;
len += line.length(); for (auto it = lines.cbegin(); it != lines.cend(); ++it) {
len += it->length();
}
return len; return len;
} }
BoundingBox3 MultiPoint3::bounding_box() const BoundingBox3 MultiPoint3::bounding_box() const { return BoundingBox3(this->points); }
{
return BoundingBox3(points);
}
bool MultiPoint3::remove_duplicate_points() bool MultiPoint3::remove_duplicate_points()
{ {
@@ -472,48 +463,72 @@ bool MultiPoint3::remove_duplicate_points()
} }
// Douglas-Peucker simplification for 3D points // Douglas-Peucker simplification for 3D points
Points3 MultiPoint3::_douglas_peucker(const Points3 &points, double tolerance) Points3 MultiPoint3::_douglas_peucker(const Points3& pts, double tolerance)
{ {
if (points.size() <= 2) return points; Points3 result_pts;
double tolerance_sq = tolerance * tolerance;
// Find the point with maximum distance from line segment if (!pts.empty()) {
double max_dist = 0; const Point3* anchor = &pts.front();
size_t max_idx = 0; size_t anchor_idx = 0;
const Point3 &first = points.front(); const Point3* floater = &pts.back();
const Point3 &last = points.back(); size_t floater_idx = pts.size() - 1;
Line3 line(first, last); result_pts.reserve(pts.size());
result_pts.emplace_back(*anchor);
for (size_t i = 1; i < points.size() - 1; ++i) { if (anchor_idx != floater_idx) {
// Calculate perpendicular distance to line segment assert(pts.size() > 1);
Point3 proj = points[i].projection_onto(line); std::vector<size_t> dpStack;
double dist = points[i].distance_to(proj); dpStack.reserve(pts.size());
if (dist > max_dist) { dpStack.emplace_back(floater_idx);
max_dist = dist; for (;;) {
max_idx = i; double max_dist_sq = 0.0;
size_t furthest_idx = anchor_idx;
// find point furthest from line seg created by (anchor, floater) and note it
for (size_t i = anchor_idx + 1; i < floater_idx; ++i) {
double dist_sq = Line3::distance_to_squared(pts[i], *anchor, *floater);
if (dist_sq > max_dist_sq) {
max_dist_sq = dist_sq;
furthest_idx = i;
} }
} }
// remove point if less than tolerance
// If max distance is greater than tolerance, recursively simplify if (max_dist_sq <= tolerance_sq) {
if (max_dist > tolerance) { result_pts.emplace_back(*floater);
// Recursive call for first part anchor_idx = floater_idx;
Points3 left_points(points.begin(), points.begin() + max_idx + 1); anchor = floater;
Points3 left_result = _douglas_peucker(left_points, tolerance); assert(dpStack.back() == floater_idx);
dpStack.pop_back();
// Recursive call for second part if (dpStack.empty())
Points3 right_points(points.begin() + max_idx, points.end()); break;
Points3 right_result = _douglas_peucker(right_points, tolerance); floater_idx = dpStack.back();
// Concatenate results (avoiding duplicate middle point)
Points3 result = left_result;
result.insert(result.end(), right_result.begin() + 1, right_result.end());
return result;
} else { } else {
// All points between first and last can be removed floater_idx = furthest_idx;
Points3 result; dpStack.emplace_back(floater_idx);
result.push_back(first);
result.push_back(last);
return result;
} }
floater = &pts[floater_idx];
}
}
assert(result_pts.front() == pts.front());
assert(result_pts.back() == pts.back());
#if 0
{
static int iRun = 0;
BoundingBox bbox(pts);
BoundingBox bbox2(result_pts);
bbox.merge(bbox2);
SVG svg(debug_out_path("douglas_peucker_%d.svg", iRun ++).c_str(), bbox);
if (pts.front() == pts.back())
svg.draw(Polygon(pts), "black");
else
svg.draw(Polyline(pts), "black");
if (result_pts.front() == result_pts.back())
svg.draw(Polygon(result_pts), "green", scale_(0.1));
else
svg.draw(Polyline(result_pts), "green", scale_(0.1));
}
#endif
}
return result_pts;
} }
BoundingBox get_extents(const MultiPoint &mp) BoundingBox get_extents(const MultiPoint &mp)

View File

@@ -121,9 +121,13 @@ public:
void append(const Point3& point) { this->points.push_back(point); } void append(const Point3& point) { this->points.push_back(point); }
void append(const Vec3crd& point) { this->points.push_back(Point3(point)); } void append(const Vec3crd& point) { this->points.push_back(Point3(point)); }
void append(const Points3::const_iterator& begin, const Points3::const_iterator& end)
{
this->points.insert(this->points.end(), begin, end);
}
void translate(double x, double y); void translate(double x, double y, double z = 0) { this->translate(Point3(coord_t(x), coord_t(y), coord_t(z))); }
void translate(const Point& vector); void translate(const Point3& vector);
void reverse() { std::reverse(this->points.begin(), this->points.end()); } void reverse() { std::reverse(this->points.begin(), this->points.end()); }
void rotate(double angle) { this->rotate(cos(angle), sin(angle)); } void rotate(double angle) { this->rotate(cos(angle), sin(angle)); }
void rotate(double cos_angle, double sin_angle); void rotate(double cos_angle, double sin_angle);

View File

@@ -464,10 +464,8 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
}; };
std::unordered_map<Point, PointInfo, PointHash> point_occurrence; std::unordered_map<Point, PointInfo, PointHash> point_occurrence;
for (const ExtrusionPath& path : paths) { for (const ExtrusionPath& path : paths) {
const Point3 &first_p3 = path.polyline.first_point(); Point first_p = path.polyline.first_point().to_point();
const Point3 &last_p3 = path.polyline.last_point(); Point last_p = path.polyline.last_point().to_point();
Point first_p = Point(first_p3.x(), first_p3.y());
Point last_p = Point(last_p3.x(), last_p3.y());
++point_occurrence[first_p].occurrence; ++point_occurrence[first_p].occurrence;
++point_occurrence[last_p].occurrence; ++point_occurrence[last_p].occurrence;
if (path.role() == erOverhangPerimeter) { if (path.role() == erOverhangPerimeter) {
@@ -660,13 +658,17 @@ bool paths_touch(const ExtrusionPath &path_one, const ExtrusionPath &path_two, d
{ {
AABBTreeLines::LinesDistancer<Line> lines_two{path_two.as_polyline().lines()}; AABBTreeLines::LinesDistancer<Line> lines_two{path_two.as_polyline().lines()};
for (size_t pt_idx = 0; pt_idx < path_one.polyline.size(); pt_idx++) { for (size_t pt_idx = 0; pt_idx < path_one.polyline.size(); pt_idx++) {
const Point3 &p3 = path_one.polyline.points[pt_idx]; Point pt = path_one.polyline.points[pt_idx].to_point();
if (lines_two.distance_from_lines<false>(Point(p3.x(), p3.y())) < limit_distance) { return true; } if (lines_two.distance_from_lines<false>(pt) < limit_distance) {
return true;
}
} }
AABBTreeLines::LinesDistancer<Line> lines_one{path_one.as_polyline().lines()}; AABBTreeLines::LinesDistancer<Line> lines_one{path_one.as_polyline().lines()};
for (size_t pt_idx = 0; pt_idx < path_two.polyline.size(); pt_idx++) { for (size_t pt_idx = 0; pt_idx < path_two.polyline.size(); pt_idx++) {
const Point3 &p3 = path_two.polyline.points[pt_idx]; Point pt = path_two.polyline.points[pt_idx].to_point();
if (lines_one.distance_from_lines<false>(Point(p3.x(), p3.y())) < limit_distance) { return true; } if (lines_one.distance_from_lines<false>(pt) < limit_distance) {
return true;
}
} }
return false; return false;
} }
@@ -1032,8 +1034,7 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
size_t min_dist_idx = 0; size_t min_dist_idx = 0;
double min_dist = std::numeric_limits<double>::max(); double min_dist = std::numeric_limits<double>::max();
for (size_t i = 0; i < overhang_region.front().polyline.size(); i++) { for (size_t i = 0; i < overhang_region.front().polyline.size(); i++) {
const Point3 &p3 = overhang_region.front().polyline.points[i]; Point p = overhang_region.front().polyline.points[i].to_point();
Point p = Point(p3.x(), p3.y());
if (double d = lower_layer_aabb_tree.distance_from_lines<true>(p) < min_dist) { if (double d = lower_layer_aabb_tree.distance_from_lines<true>(p) < min_dist) {
min_dist = d; min_dist = d;
min_dist_idx = i; min_dist_idx = i;

View File

@@ -1,4 +1,5 @@
#include "Point.hpp" #include "Point.hpp"
#include "Exception.hpp"
#include "Line.hpp" #include "Line.hpp"
#include "MultiPoint.hpp" #include "MultiPoint.hpp"
#include "Polyline.hpp" #include "Polyline.hpp"
@@ -266,11 +267,21 @@ Points to_points(const Points3 &points3) {
Points points2; Points points2;
points2.reserve(points3.size()); points2.reserve(points3.size());
for (const Point3 &pt : points3) { for (const Point3 &pt : points3) {
points2.push_back(pt.to_point()); points2.emplace_back(pt.to_point());
} }
return points2; return points2;
} }
Points3 to_points3(const Points& points)
{
Points3 points3;
points3.reserve(points.size());
for (const Point& pt : points) {
points3.emplace_back(pt);
}
return points3;
}
// Point3 method implementations // Point3 method implementations
void Point3::rotate(double angle, const Point3 &center) { void Point3::rotate(double angle, const Point3 &center) {
Vec3crd diff = *this - center; Vec3crd diff = *this - center;
@@ -311,6 +322,7 @@ double Point3::ccw_angle(const Point3 &p1, const Point3 &p2) const {
Point3 Point3::projection_onto(const MultiPoint3 &poly) const { Point3 Point3::projection_onto(const MultiPoint3 &poly) const {
// TODO: Implement proper 3D projection when MultiPoint3 conversion methods are ready // TODO: Implement proper 3D projection when MultiPoint3 conversion methods are ready
// For now, stub implementation // For now, stub implementation
throw RuntimeError("Point3::projection_onto(MultiPoint3) not implemented yet");
return *this; return *this;
} }

View File

@@ -62,7 +62,7 @@ using PointsAllocator = tbb::scalable_allocator<BaseType>;
using Points = std::vector<Point, PointsAllocator<Point>>; using Points = std::vector<Point, PointsAllocator<Point>>;
using PointPtrs = std::vector<Point*>; using PointPtrs = std::vector<Point*>;
using PointConstPtrs = std::vector<const Point*>; using PointConstPtrs = std::vector<const Point*>;
using Points3 = std::vector<Point3>; using Points3 = std::vector<Point3, PointsAllocator<Point3>>;
using Pointfs = std::vector<Vec2d>; using Pointfs = std::vector<Vec2d>;
using Vec2ds = std::vector<Vec2d>; using Vec2ds = std::vector<Vec2d>;
using Pointf3s = std::vector<Vec3d>; using Pointf3s = std::vector<Vec3d>;
@@ -88,6 +88,7 @@ using Transform3d = Eigen::Transform<double, 3, Eigen::Affine, Eigen::DontAli
Polyline to_polyline(const Points &points); Polyline to_polyline(const Points &points);
Polyline3 to_polyline(const Points3 &points); Polyline3 to_polyline(const Points3 &points);
Points to_points(const Points3 &points); Points to_points(const Points3 &points);
Points3 to_points3(const Points& points);
// using ColorRGBA = std::array<float, 4>; // using ColorRGBA = std::array<float, 4>;
// I don't know why Eigen::Transform::Identity() return a const object... // I don't know why Eigen::Transform::Identity() return a const object...
@@ -422,6 +423,12 @@ inline Point lerp(const Point &a, const Point &b, double t)
return ((1. - t) * a.cast<double>() + t * b.cast<double>()).cast<coord_t>(); return ((1. - t) * a.cast<double>() + t * b.cast<double>()).cast<coord_t>();
} }
inline Point3 lerp(const Point3& a, const Point3& b, double t)
{
assert((t >= -EPSILON) && (t <= 1. + EPSILON));
return Point3(((1. - t) * a.cast<double>() + t * b.cast<double>()).cast<coord_t>());
}
// if IncludeBoundary, then a bounding box is defined even for a single point. // if IncludeBoundary, then a bounding box is defined even for a single point.
// otherwise a bounding box is only defined if it has a positive area. // otherwise a bounding box is only defined if it has a positive area.
template<bool IncludeBoundary = false> template<bool IncludeBoundary = false>

View File

@@ -661,19 +661,17 @@ void ThickPolyline::start_at_index(int index)
Lines3 Polyline3::lines() const Lines3 Polyline3::lines() const
{ {
Lines3 lines; Lines3 lines;
if (points.size() >= 2) if (this->points.size() >= 2) {
{ lines.reserve(this->points.size() - 1);
lines.reserve(points.size() - 1); for (Points3::const_iterator it = this->points.begin(); it != this->points.end() - 1; ++it) {
for (Points3::const_iterator it = points.begin(); it != points.end() - 1; ++it)
{
lines.emplace_back(*it, *(it + 1)); lines.emplace_back(*it, *(it + 1));
} }
} }
return lines; return lines;
} }
// Polyline3 ZAA methods implementation Polyline Polyline3::to_polyline() const
Polyline Polyline3::to_polyline() const { {
Polyline out; Polyline out;
out.points.reserve(this->points.size()); out.points.reserve(this->points.size());
for (const Point3 &point : this->points) { for (const Point3 &point : this->points) {
@@ -682,8 +680,10 @@ Polyline Polyline3::to_polyline() const {
return out; return out;
} }
void Polyline3::clip_end(double distance) { void Polyline3::clip_end(double distance)
size_t remove_after_index = this->size(); {
bool last_point_inserted = false;
size_t remove_after_index = MultiPoint3::size();
while (distance > 0) { while (distance > 0) {
Vec3d last_point = this->last_point().cast<double>(); Vec3d last_point = this->last_point().cast<double>();
this->points.pop_back(); this->points.pop_back();
@@ -695,32 +695,63 @@ void Polyline3::clip_end(double distance) {
Vec3d v = this->last_point().cast<double>() - last_point; Vec3d v = this->last_point().cast<double>() - last_point;
double lsqr = v.squaredNorm(); double lsqr = v.squaredNorm();
if (lsqr > distance * distance) { if (lsqr > distance * distance) {
Vec3d result = last_point + v * (distance / sqrt(lsqr)); this->points.emplace_back((last_point + v * (distance / sqrt(lsqr))).cast<coord_t>());
this->points.emplace_back(Point3(coord_t(result.x()), coord_t(result.y()), coord_t(result.z()))); last_point_inserted = true;
break; break;
} }
distance -= sqrt(lsqr); distance -= sqrt(lsqr);
} }
// Clear fitting result if it's affected // BBS: don't need to clip fitting result if it's empty
if (!fitting_result.empty()) { if (fitting_result.empty())
return;
while (!fitting_result.empty() && fitting_result.back().start_point_index >= remove_after_index) while (!fitting_result.empty() && fitting_result.back().start_point_index >= remove_after_index)
fitting_result.pop_back(); fitting_result.pop_back();
if (!fitting_result.empty()) { if (!fitting_result.empty()) {
// BBS: last remaining segment is arc move, then clip the arc at last point
if (fitting_result.back().path_type == EMovePathType::Arc_move_ccw ||
fitting_result.back().path_type == EMovePathType::Arc_move_cw) {
if (fitting_result.back().arc_data.clip_end(this->last_point().to_point()))
// BBS: succeed to clip arc, then update the last point
// TODO: fix z parameter
this->points.back() = Point3(fitting_result.back().arc_data.end_point, this->points.back().z());
else
// BBS: Failed to clip arc, then back to linear move
fitting_result.back().path_type = EMovePathType::Linear_move;
}
fitting_result.back().end_point_index = this->points.size() - 1; fitting_result.back().end_point_index = this->points.size() - 1;
} }
} }
}
void Polyline3::simplify(double tolerance) { void Polyline3::simplify(double tolerance)
{
this->points = MultiPoint3::_douglas_peucker(this->points, tolerance); this->points = MultiPoint3::_douglas_peucker(this->points, tolerance);
this->fitting_result.clear(); this->fitting_result.clear();
} }
void Polyline3::simplify_by_fitting_arc(double tolerance) { void Polyline3::simplify_by_fitting_arc(double tolerance)
// For now, just use regular simplify {
// Full ZAA implementation would use ArcFitter::do_arc_fitting_and_simplify // BBS: do arc fit first, then use DP simplify to handle the straight part to reduce point.
this->simplify(tolerance); Points points_2d = to_points(this->points);
ArcFitter::do_arc_fitting_and_simplify(points_2d, this->fitting_result, tolerance);
this->points = to_points3(points_2d);
}
void Polyline3::reverse()
{
// BBS: reverse points
MultiPoint3::reverse();
// BBS: reverse the fitting_result
if (!this->fitting_result.empty()) {
for (size_t i = 0; i < this->fitting_result.size(); i++) {
std::swap(fitting_result[i].start_point_index, fitting_result[i].end_point_index);
fitting_result[i].start_point_index = MultiPoint3::size() - 1 - fitting_result[i].start_point_index;
fitting_result[i].end_point_index = MultiPoint3::size() - 1 - fitting_result[i].end_point_index;
if (fitting_result[i].is_arc_move())
fitting_result[i].reverse_arc_path();
}
std::reverse(this->fitting_result.begin(), this->fitting_result.end());
}
} }
bool Polyline3::split_at_index(const size_t index, Polyline3 *p1, Polyline3 *p2) const bool Polyline3::split_at_index(const size_t index, Polyline3 *p1, Polyline3 *p2) const
@@ -737,7 +768,7 @@ bool Polyline3::split_at_index(const size_t index, Polyline3 *p1, Polyline3 *p2)
p2->append(this->last_point()); p2->append(this->last_point());
*p1 = *this; *p1 = *this;
} else { } else {
// Split first part // BBS: spilit first part
p1->clear(); p1->clear();
p1->points.reserve(index + 1); p1->points.reserve(index + 1);
p1->points.insert(p1->begin(), this->begin(), this->begin() + index + 1); p1->points.insert(p1->begin(), this->begin(), this->begin() + index + 1);
@@ -745,7 +776,6 @@ bool Polyline3::split_at_index(const size_t index, Polyline3 *p1, Polyline3 *p2)
if (this->split_fitting_result_before_index(index, new_endpoint, p1->fitting_result)) if (this->split_fitting_result_before_index(index, new_endpoint, p1->fitting_result))
p1->points.back() = new_endpoint; p1->points.back() = new_endpoint;
// Split second part
p2->clear(); p2->clear();
p2->points.reserve(this->size() - index); p2->points.reserve(this->size() - index);
p2->points.insert(p2->begin(), this->begin() + index, this->end()); p2->points.insert(p2->begin(), this->begin() + index, this->end());
@@ -756,58 +786,74 @@ bool Polyline3::split_at_index(const size_t index, Polyline3 *p1, Polyline3 *p2)
return true; return true;
} }
void Polyline3::append(const Point3& point) { void Polyline3::append(const Point3& point)
// Don't append if same as last point {
// // Don't append if same as last point
// if (!this->empty() && this->last_point() == point)
// return;
// this->points.push_back(point);
// append_fitting_result_after_append_points();
// BBS: don't need to append same point
if (!this->empty() && this->last_point() == point) if (!this->empty() && this->last_point() == point)
return; return;
MultiPoint3::append(point);
this->points.push_back(point); append_fitting_result_after_append_points();
// Clear fitting result as structure changed
this->fitting_result.clear();
} }
void Polyline3::append(const Polyline3 &src) { void Polyline3::append(const Polyline3& src)
{
if (!src.is_valid()) return; if (!src.is_valid()) return;
if (this->points.empty()) { if (this->points.empty()) {
this->points = src.points; this->points = src.points;
this->fitting_result = src.fitting_result; this->fitting_result = src.fitting_result;
} else { } else {
// Append points // BBS: append the first point to create connection first, update the fitting date as well
if (!src.points.empty() && !this->points.empty() && this->last_point() == src.points.front()) { this->append(src.points[0]);
// Skip first point if it's the same as our last point // BBS: append a polyline which has fitting data to a polyline without fitting data.
this->points.insert(this->points.end(), src.points.begin() + 1, src.points.end()); // Then create a fake fitting data first, so that we can keep the fitting data in last polyline
} else { if (this->fitting_result.empty() && !src.fitting_result.empty()) {
this->points.insert(this->points.end(), src.points.begin(), src.points.end()); this->fitting_result.emplace_back(PathFittingData{0, this->points.size() - 1, EMovePathType::Linear_move, ArcSegment()});
} }
// Note: Full arc fitting integration would merge fitting_result here // BBS: then append the remain points
this->fitting_result.clear(); MultiPoint3::append(src.points.begin() + 1, src.points.end());
// BBS: finally append the fitting data
append_fitting_result_after_append_polyline(src);
} }
} }
void Polyline3::append_before(const Point3& point) { void Polyline3::append_before(const Point3& point)
// Don't append if same as first point {
// BBS: don't need to append same point
if (!this->empty() && this->first_point() == point) if (!this->empty() && this->first_point() == point)
return; return;
if (this->size() == 1) {
this->points.insert(this->points.begin(), point);
// Clear fitting result as structure changed
this->fitting_result.clear(); this->fitting_result.clear();
MultiPoint3::append(point);
MultiPoint3::reverse();
} else {
this->reverse();
this->append(point);
this->reverse();
}
} }
void Polyline3::split_at(Point &point, Polyline3* p1, Polyline3* p2) const { void Polyline3::split_at(Point& point, Polyline3* p1, Polyline3* p2) const
{
if (this->points.empty()) return; if (this->points.empty()) return;
// Check if the point is on the polyline // 0 judge whether the point is on the polyline
int index = this->find_point(point); int index = this->find_point(point);
if (index != -1) { if (index != -1) {
// The split point is on the polyline // BBS: the spilit point is on the polyline, then easy
split_at_index(index, p1, p2); split_at_index(index, p1, p2);
point = p1->is_valid() ? p1->last_point().to_point() : p2->first_point().to_point(); point = p1->is_valid() ? p1->last_point().to_point() : p2->first_point().to_point();
return; return;
} }
// Find the line to split at // 1 find the line to split at
size_t line_idx = 0; size_t line_idx = 0;
Point p = this->first_point().to_point(); Point p = this->first_point().to_point();
double min = (p - point).cast<double>().norm(); double min = (p - point).cast<double>().norm();
@@ -821,7 +867,8 @@ void Polyline3::split_at(Point &point, Polyline3* p1, Polyline3* p2) const {
} }
} }
// Judge whether the closest point is one vertex of polyline // 2 judge whether the cloest point is one vertex of polyline.
// and spilit the polyline at different index
index = this->find_point(p); index = this->find_point(p);
if (index != -1) { if (index != -1) {
this->split_at_index(index, p1, p2); this->split_at_index(index, p1, p2);
@@ -834,7 +881,6 @@ void Polyline3::split_at(Point &point, Polyline3* p1, Polyline3* p2) const {
this->split_at_index(line_idx + 1, &temp, p2); this->split_at_index(line_idx + 1, &temp, p2);
p2->append_before(Point3(point, p2->first_point().z())); p2->append_before(Point3(point, p2->first_point().z()));
} }
point = p;
} }
void Polyline3::split_at(Point3 &point, Polyline3* p1, Polyline3* p2) const { void Polyline3::split_at(Point3 &point, Polyline3* p1, Polyline3* p2) const {
@@ -845,46 +891,153 @@ void Polyline3::split_at(Point3 &point, Polyline3* p1, Polyline3* p2) const {
bool Polyline3::split_at_length(const double length, Polyline3 *p1, Polyline3 *p2) const { bool Polyline3::split_at_length(const double length, Polyline3 *p1, Polyline3 *p2) const {
if (this->points.empty()) return false; if (this->points.empty()) return false;
if (length < 0 || length > this->length()) { return false; } if (length < 0 || length > this->length()) {
return false;
}
if (length < SCALED_EPSILON) { if (length < SCALED_EPSILON) {
p1->clear(); p1->clear();
p1->append_before(this->first_point()); p1->append(this->first_point());
*p2 = *this; *p2 = *this;
} else if (is_approx(length, this->length(), SCALED_EPSILON)) { } else if (is_approx(length, this->length(), SCALED_EPSILON)) {
p2->clear(); p2->clear();
p2->append_before(this->last_point()); p2->append(this->last_point());
*p1 = *this; *p1 = *this;
} else { } else {
// Find the line to split at // 1 find the line to split at
size_t line_idx = 0; size_t line_idx = 0;
double acc_length = 0; double acc_length = 0;
Point p = this->first_point().to_point(); Point3 p = this->first_point();
for (const auto& l : this->lines()) { for (const auto& l : this->lines()) {
p = l.b.to_point(); p = l.b;
const double current_length = l.length(); const double current_length = l.length();
if (acc_length + current_length >= length) { if (acc_length + current_length >= length) {
p = lerp(l.a.to_point(), l.b.to_point(), (length - acc_length) / current_length); p = lerp(l.a, l.b, (length - acc_length) / current_length);
break; break;
} }
acc_length += current_length; acc_length += current_length;
line_idx++; line_idx++;
} }
// Judge whether the closest point is one vertex of polyline // 2 judge whether the cloest point is one vertex of polyline.
// and spilit the polyline at different index
int index = this->find_point(p); int index = this->find_point(p);
if (index != -1) { if (index != -1) {
this->split_at_index(index, p1, p2); this->split_at_index(index, p1, p2);
} else { } else {
Polyline3 temp; Polyline3 temp;
this->split_at_index(line_idx, p1, &temp); this->split_at_index(line_idx, p1, &temp);
p1->append_before(Point3(p, p1->last_point().z())); p1->append(p);
this->split_at_index(line_idx + 1, &temp, p2); this->split_at_index(line_idx + 1, &temp, p2);
p2->append_before(Point3(p, p2->first_point().z())); p2->append_before(p);
} }
} }
return true; return true;
} }
bool Polyline3::split_fitting_result_before_index(size_t index, Point3& new_endpoint, std::vector<PathFittingData>& data) const
{
data.clear();
new_endpoint = this->points[index];
if (!this->fitting_result.empty()) {
// BBS: max size
data.reserve(this->fitting_result.size());
// BBS: save fitting result before index
for (size_t i = 0; i < this->fitting_result.size(); i++) {
if (this->fitting_result[i].start_point_index < index)
data.push_back(this->fitting_result[i]);
else
break;
}
if (!data.empty()) {
// BBS: need to clip the arc and generate new end point
if (data.back().is_arc_move() && data.back().end_point_index > index) {
if (!data.back().arc_data.clip_end(this->points[index].to_point()))
// BBS: failed to clip arc, then return to be linear move
data.back().path_type = EMovePathType::Linear_move;
else
// BBS: succeed to clip arc, then update and return the new end point
new_endpoint = Point3(data.back().arc_data.end_point, 0);
}
data.back().end_point_index = index;
}
data.shrink_to_fit();
return true;
}
return false;
}
bool Polyline3::split_fitting_result_after_index(size_t index, Point3& new_startpoint, std::vector<PathFittingData>& data) const
{
data.clear();
new_startpoint = this->points[index];
if (!this->fitting_result.empty()) {
data.reserve(this->fitting_result.size());
for (size_t i = 0; i < this->fitting_result.size(); i++) {
if (this->fitting_result[i].end_point_index > index)
data.push_back(this->fitting_result[i]);
}
if (!data.empty()) {
for (size_t i = 0; i < data.size(); i++) {
if (i != 0) {
data[i].start_point_index -= index;
data[i].end_point_index -= index;
} else {
data[i].end_point_index -= index;
// BBS: need to clip the arc and generate new start point
if (data.front().is_arc_move() && data.front().start_point_index < index) {
if (!data.front().arc_data.clip_start(this->points[index].to_point()))
// BBS: failed to clip arc, then return to be linear move
data.front().path_type = EMovePathType::Linear_move;
else
// BBS: succeed to clip arc, then update and return the new start point
new_startpoint = Point3(data.front().arc_data.start_point, 0);
}
data[i].start_point_index = 0;
}
}
}
data.shrink_to_fit();
return true;
}
return false;
}
void Polyline3::append_fitting_result_after_append_points()
{
if (!fitting_result.empty()) {
if (fitting_result.back().is_linear_move()) {
fitting_result.back().end_point_index = this->points.size() - 1;
} else {
size_t new_start = fitting_result.back().end_point_index;
size_t new_end = this->points.size() - 1;
if (new_start != new_end)
fitting_result.emplace_back(PathFittingData{new_start, new_end, EMovePathType::Linear_move, ArcSegment()});
}
}
}
void Polyline3::append_fitting_result_after_append_polyline(const Polyline3& src)
{
if (!this->fitting_result.empty()) {
// BBS: offset and save the fitting_result from src polyline
if (!src.fitting_result.empty()) {
size_t old_size = this->fitting_result.size();
size_t index_offset = this->fitting_result.back().end_point_index;
this->fitting_result.insert(this->fitting_result.end(), src.fitting_result.begin(), src.fitting_result.end());
for (size_t i = old_size; i < this->fitting_result.size(); i++) {
this->fitting_result[i].start_point_index += index_offset;
this->fitting_result[i].end_point_index += index_offset;
}
} else {
// BBS: the append polyline has no fitting data, then append as linear move directly
size_t new_start = this->fitting_result.back().end_point_index;
size_t new_end = this->size() - 1;
if (new_start != new_end)
this->fitting_result.emplace_back(PathFittingData{new_start, new_end, EMovePathType::Linear_move, ArcSegment()});
}
}
}
} }

View File

@@ -315,7 +315,7 @@ public:
void simplify_by_fitting_arc(double tolerance); void simplify_by_fitting_arc(double tolerance);
// Reverse the polyline // Reverse the polyline
using MultiPoint3::reverse; void reverse();
// Split polyline at given index // Split polyline at given index
bool split_at_index(const size_t index, Polyline3 *p1, Polyline3 *p2) const; bool split_at_index(const size_t index, Polyline3 *p1, Polyline3 *p2) const;
@@ -344,15 +344,10 @@ public:
std::vector<PathFittingData> fitting_result; std::vector<PathFittingData> fitting_result;
private: private:
// Helper methods for split_at_index void append_fitting_result_after_append_points();
bool split_fitting_result_before_index(size_t index, Point3 &new_endpoint, std::vector<PathFittingData> &result) const { void append_fitting_result_after_append_polyline(const Polyline3& src);
// Simplified stub - full implementation would handle arc fitting data bool split_fitting_result_before_index(size_t index, Point3& new_endpoint, std::vector<PathFittingData>& result) const;
return false; bool split_fitting_result_after_index(size_t index, Point3& new_startpoint, std::vector<PathFittingData>& result) const;
}
bool split_fitting_result_after_index(size_t index, Point3 &new_startpoint, std::vector<PathFittingData> &result) const {
// Simplified stub - full implementation would handle arc fitting data
return false;
}
}; };
typedef std::vector<Polyline3> Polylines3; typedef std::vector<Polyline3> Polylines3;