diff --git a/src/libslic3r/ArcFitter.cpp b/src/libslic3r/ArcFitter.cpp index b15548ee84..1d48861e0c 100644 --- a/src/libslic3r/ArcFitter.cpp +++ b/src/libslic3r/ArcFitter.cpp @@ -85,7 +85,7 @@ static void do_arc_fitting_tmpl(const POINTS& points, std::vectorpaths.size() == 1) { if (!p1.polyline.is_valid()) { std::swap(this->paths.front().polyline.points, p2.polyline.points); diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 6a64a2ed2d..4c08a2e7a4 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -5541,7 +5541,11 @@ static std::unique_ptr calculate_layer_edge_grid(const Layer& la 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 // 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) { for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++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 // in the first extrude move // 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; } -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 std::string gcode; @@ -5868,7 +5872,10 @@ std::string GCode::extrude_multi_path(const ExtrusionMultiPath &multipath, std:: 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(&entity)) return this->extrude_path(*path, description, speed); @@ -5881,7 +5888,7 @@ std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string des 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 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"); } } - gcode += this->travel_to( - path.first_point(), - path.role(), - "move to first " + description + " point; size " + std::to_string(path.polyline.size()), - z - ); + gcode += this->travel_to(path.first_point(), path.role(), "move to first " + description + " point", z); m_need_change_layer_lift_z = false; // Orca: ensure Z matches planned layer height if (_last_pos_undefined && !slope_need_z_travel) { diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 8d877264f6..29ef4a0dc8 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -390,13 +390,20 @@ private: 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 // 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 // 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_multi_path(const ExtrusionMultiPath &multipath, std::string description = "", double speed = -1.); - std::string extrude_path(const ExtrusionPath &path, std::string description = "", double speed = -1.); - + std::string extrude_loop(const ExtrusionLoop& loop, + const std::string& description, + 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 // Used for adaptive PA when extruding paths with multiple, varying flow segments. // This contains the sum of the mm3_per_mm values weighted by the length of each path segment. diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 12f19f6fa8..5e39ee9f46 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -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.segment_idx = 0; } - const Point3 &p3 = loop.paths[current.path_idx].polyline.points[current.segment_idx]; - current.foot_pt = Point(p3.x(), p3.y()); + current.foot_pt = loop.paths[current.path_idx].polyline.points[current.segment_idx].to_point(); return current; }; @@ -1528,8 +1527,7 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, size_t closest_perimeter_point_index = 0; { // local space for the closest_perimeter_point_index Perimeter *closest_perimeter = nullptr; - const Point3 &init_p3 = loop.paths[0].polyline.points[0]; - ExtrusionLoop::ClosestPathPoint closest_point{0,0,Point(init_p3.x(), init_p3.y())}; + ExtrusionLoop::ClosestPathPoint closest_point{0, 0, loop.paths[0].polyline.points[0].to_point()}; 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(); }); diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index ec88905b96..fdf03570a8 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -3,6 +3,7 @@ #include "ClipperUtils.hpp" #include "Geometry.hpp" #include "PerimeterGenerator.hpp" +#include "Point.hpp" #include "Print.hpp" #include "Surface.hpp" #include "BoundingBox.hpp" diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index 28423b0dfc..99935b1fc4 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -236,6 +236,11 @@ public: // Convert to 2D line by dropping Z coordinate 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 b; diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index cb9c67f0f9..694d3ea9d0 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -422,32 +422,23 @@ Points MultiPoint::concave_hull_2d(const Points& pts, const double tolerence) return min_distance; } - -void MultiPoint3::translate(double x, double y) +void MultiPoint3::translate(const Point3& v) { - for (Vec3crd &p : points) { - p(0) += coord_t(x); - p(1) += coord_t(y); - } -} - -void MultiPoint3::translate(const Point& vector) -{ - this->translate(vector(0), vector(1)); + for (Point3& pt : points) + pt += v; } double MultiPoint3::length() const { - double len = 0.0; - for (const Line3& line : this->lines()) - len += line.length(); + const Lines3& lines = this->lines(); + double len = 0; + for (auto it = lines.cbegin(); it != lines.cend(); ++it) { + len += it->length(); + } return len; } -BoundingBox3 MultiPoint3::bounding_box() const -{ - return BoundingBox3(points); -} +BoundingBox3 MultiPoint3::bounding_box() const { return BoundingBox3(this->points); } bool MultiPoint3::remove_duplicate_points() { @@ -472,48 +463,72 @@ bool MultiPoint3::remove_duplicate_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; - - // Find the point with maximum distance from line segment - double max_dist = 0; - size_t max_idx = 0; - const Point3 &first = points.front(); - const Point3 &last = points.back(); - Line3 line(first, last); - - for (size_t i = 1; i < points.size() - 1; ++i) { - // Calculate perpendicular distance to line segment - Point3 proj = points[i].projection_onto(line); - double dist = points[i].distance_to(proj); - if (dist > max_dist) { - max_dist = dist; - max_idx = i; + Points3 result_pts; + double tolerance_sq = tolerance * tolerance; + if (!pts.empty()) { + const Point3* anchor = &pts.front(); + size_t anchor_idx = 0; + const Point3* floater = &pts.back(); + size_t floater_idx = pts.size() - 1; + result_pts.reserve(pts.size()); + result_pts.emplace_back(*anchor); + if (anchor_idx != floater_idx) { + assert(pts.size() > 1); + std::vector dpStack; + dpStack.reserve(pts.size()); + dpStack.emplace_back(floater_idx); + for (;;) { + 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_dist_sq <= tolerance_sq) { + result_pts.emplace_back(*floater); + anchor_idx = floater_idx; + anchor = floater; + assert(dpStack.back() == floater_idx); + dpStack.pop_back(); + if (dpStack.empty()) + break; + floater_idx = dpStack.back(); + } else { + floater_idx = furthest_idx; + dpStack.emplace_back(floater_idx); + } + 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 } - - // If max distance is greater than tolerance, recursively simplify - if (max_dist > tolerance) { - // Recursive call for first part - Points3 left_points(points.begin(), points.begin() + max_idx + 1); - Points3 left_result = _douglas_peucker(left_points, tolerance); - - // Recursive call for second part - Points3 right_points(points.begin() + max_idx, points.end()); - Points3 right_result = _douglas_peucker(right_points, tolerance); - - // Concatenate results (avoiding duplicate middle point) - Points3 result = left_result; - result.insert(result.end(), right_result.begin() + 1, right_result.end()); - return result; - } else { - // All points between first and last can be removed - Points3 result; - result.push_back(first); - result.push_back(last); - return result; - } + return result_pts; } BoundingBox get_extents(const MultiPoint &mp) diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 44d1e86cf1..de386b501c 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -121,9 +121,13 @@ public: void append(const Point3& point) { this->points.push_back(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(const Point& vector); + void translate(double x, double y, double z = 0) { this->translate(Point3(coord_t(x), coord_t(y), coord_t(z))); } + void translate(const Point3& vector); void reverse() { std::reverse(this->points.begin(), this->points.end()); } void rotate(double angle) { this->rotate(cos(angle), sin(angle)); } void rotate(double cos_angle, double sin_angle); diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index b7cb3e34ef..e16a0b022a 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -464,10 +464,8 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p }; std::unordered_map point_occurrence; for (const ExtrusionPath& path : paths) { - const Point3 &first_p3 = path.polyline.first_point(); - const Point3 &last_p3 = path.polyline.last_point(); - Point first_p = Point(first_p3.x(), first_p3.y()); - Point last_p = Point(last_p3.x(), last_p3.y()); + Point first_p = path.polyline.first_point().to_point(); + Point last_p = path.polyline.last_point().to_point(); ++point_occurrence[first_p].occurrence; ++point_occurrence[last_p].occurrence; if (path.role() == erOverhangPerimeter) { @@ -660,13 +658,17 @@ bool paths_touch(const ExtrusionPath &path_one, const ExtrusionPath &path_two, d { AABBTreeLines::LinesDistancer lines_two{path_two.as_polyline().lines()}; for (size_t pt_idx = 0; pt_idx < path_one.polyline.size(); pt_idx++) { - const Point3 &p3 = path_one.polyline.points[pt_idx]; - if (lines_two.distance_from_lines(Point(p3.x(), p3.y())) < limit_distance) { return true; } + Point pt = path_one.polyline.points[pt_idx].to_point(); + if (lines_two.distance_from_lines(pt) < limit_distance) { + return true; + } } AABBTreeLines::LinesDistancer lines_one{path_one.as_polyline().lines()}; for (size_t pt_idx = 0; pt_idx < path_two.polyline.size(); pt_idx++) { - const Point3 &p3 = path_two.polyline.points[pt_idx]; - if (lines_one.distance_from_lines(Point(p3.x(), p3.y())) < limit_distance) { return true; } + Point pt = path_two.polyline.points[pt_idx].to_point(); + if (lines_one.distance_from_lines(pt) < limit_distance) { + return true; + } } return false; } @@ -1032,8 +1034,7 @@ std::tuple, Polygons> generate_extra_perimeters_over size_t min_dist_idx = 0; double min_dist = std::numeric_limits::max(); for (size_t i = 0; i < overhang_region.front().polyline.size(); i++) { - const Point3 &p3 = overhang_region.front().polyline.points[i]; - Point p = Point(p3.x(), p3.y()); + Point p = overhang_region.front().polyline.points[i].to_point(); if (double d = lower_layer_aabb_tree.distance_from_lines(p) < min_dist) { min_dist = d; min_dist_idx = i; diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index eea69b1195..9a2c2b14cd 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -1,4 +1,5 @@ #include "Point.hpp" +#include "Exception.hpp" #include "Line.hpp" #include "MultiPoint.hpp" #include "Polyline.hpp" @@ -266,11 +267,21 @@ Points to_points(const Points3 &points3) { Points points2; points2.reserve(points3.size()); for (const Point3 &pt : points3) { - points2.push_back(pt.to_point()); + points2.emplace_back(pt.to_point()); } 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 void Point3::rotate(double angle, const Point3 ¢er) { 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 { // TODO: Implement proper 3D projection when MultiPoint3 conversion methods are ready // For now, stub implementation + throw RuntimeError("Point3::projection_onto(MultiPoint3) not implemented yet"); return *this; } diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index bde79076ef..83945d73ce 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -62,7 +62,7 @@ using PointsAllocator = tbb::scalable_allocator; using Points = std::vector>; using PointPtrs = std::vector; using PointConstPtrs = std::vector; -using Points3 = std::vector; +using Points3 = std::vector>; using Pointfs = std::vector; using Vec2ds = std::vector; using Pointf3s = std::vector; @@ -88,6 +88,7 @@ using Transform3d = Eigen::Transform; // 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() + t * b.cast()).cast(); } +inline Point3 lerp(const Point3& a, const Point3& b, double t) +{ + assert((t >= -EPSILON) && (t <= 1. + EPSILON)); + return Point3(((1. - t) * a.cast() + t * b.cast()).cast()); +} + // 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. template diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index efbc9dfb7b..98a5366ecf 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -661,19 +661,17 @@ void ThickPolyline::start_at_index(int index) Lines3 Polyline3::lines() const { Lines3 lines; - if (points.size() >= 2) - { - lines.reserve(points.size() - 1); - for (Points3::const_iterator it = points.begin(); it != points.end() - 1; ++it) - { + if (this->points.size() >= 2) { + lines.reserve(this->points.size() - 1); + for (Points3::const_iterator it = this->points.begin(); it != this->points.end() - 1; ++it) { lines.emplace_back(*it, *(it + 1)); } } return lines; } -// Polyline3 ZAA methods implementation -Polyline Polyline3::to_polyline() const { +Polyline Polyline3::to_polyline() const +{ Polyline out; out.points.reserve(this->points.size()); for (const Point3 &point : this->points) { @@ -682,8 +680,10 @@ Polyline Polyline3::to_polyline() const { return out; } -void Polyline3::clip_end(double distance) { - size_t remove_after_index = this->size(); +void Polyline3::clip_end(double distance) +{ + bool last_point_inserted = false; + size_t remove_after_index = MultiPoint3::size(); while (distance > 0) { Vec3d last_point = this->last_point().cast(); this->points.pop_back(); @@ -692,35 +692,66 @@ void Polyline3::clip_end(double distance) { this->fitting_result.clear(); return; } - Vec3d v = this->last_point().cast() - last_point; + Vec3d v = this->last_point().cast() - last_point; double lsqr = v.squaredNorm(); if (lsqr > distance * distance) { - Vec3d result = last_point + v * (distance / sqrt(lsqr)); - this->points.emplace_back(Point3(coord_t(result.x()), coord_t(result.y()), coord_t(result.z()))); + this->points.emplace_back((last_point + v * (distance / sqrt(lsqr))).cast()); + last_point_inserted = true; break; } 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()) + return; + while (!fitting_result.empty() && fitting_result.back().start_point_index >= remove_after_index) + fitting_result.pop_back(); if (!fitting_result.empty()) { - while (!fitting_result.empty() && fitting_result.back().start_point_index >= remove_after_index) - fitting_result.pop_back(); - if (!fitting_result.empty()) { - fitting_result.back().end_point_index = this->points.size() - 1; + // 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; } } -void Polyline3::simplify(double tolerance) { +void Polyline3::simplify(double tolerance) +{ this->points = MultiPoint3::_douglas_peucker(this->points, tolerance); this->fitting_result.clear(); } -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 - this->simplify(tolerance); +void Polyline3::simplify_by_fitting_arc(double tolerance) +{ + // BBS: do arc fit first, then use DP simplify to handle the straight part to reduce point. + 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 @@ -737,7 +768,7 @@ bool Polyline3::split_at_index(const size_t index, Polyline3 *p1, Polyline3 *p2) p2->append(this->last_point()); *p1 = *this; } else { - // Split first part + // BBS: spilit first part p1->clear(); p1->points.reserve(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)) p1->points.back() = new_endpoint; - // Split second part p2->clear(); p2->points.reserve(this->size() - index); 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; } -void Polyline3::append(const Point3& point) { - // Don't append if same as last point +void Polyline3::append(const Point3& 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) return; - - this->points.push_back(point); - // Clear fitting result as structure changed - this->fitting_result.clear(); + MultiPoint3::append(point); + append_fitting_result_after_append_points(); } -void Polyline3::append(const Polyline3 &src) { +void Polyline3::append(const Polyline3& src) +{ if (!src.is_valid()) return; if (this->points.empty()) { this->points = src.points; this->fitting_result = src.fitting_result; } else { - // Append points - if (!src.points.empty() && !this->points.empty() && this->last_point() == src.points.front()) { - // Skip first point if it's the same as our last point - this->points.insert(this->points.end(), src.points.begin() + 1, src.points.end()); - } else { - this->points.insert(this->points.end(), src.points.begin(), src.points.end()); + // BBS: append the first point to create connection first, update the fitting date as well + this->append(src.points[0]); + // BBS: append a polyline which has fitting data to a polyline without fitting data. + // Then create a fake fitting data first, so that we can keep the fitting data in last polyline + if (this->fitting_result.empty() && !src.fitting_result.empty()) { + 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 - this->fitting_result.clear(); + // BBS: then append the remain points + 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) { - // Don't append if same as first point +void Polyline3::append_before(const Point3& point) +{ + // BBS: don't need to append same point if (!this->empty() && this->first_point() == point) return; - - this->points.insert(this->points.begin(), point); - // Clear fitting result as structure changed - this->fitting_result.clear(); + if (this->size() == 1) { + 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; - // Check if the point is on the polyline + // 0 judge whether the point is on the polyline int index = this->find_point(point); 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); point = p1->is_valid() ? p1->last_point().to_point() : p2->first_point().to_point(); return; } - // Find the line to split at + // 1 find the line to split at size_t line_idx = 0; Point p = this->first_point().to_point(); double min = (p - point).cast().norm(); @@ -815,13 +861,14 @@ void Polyline3::split_at(Point &point, Polyline3* p1, Polyline3* p2) const { for (Lines3::const_iterator line = lines.begin(); line != lines.end(); ++line) { Point p_tmp = point.projection_onto(line->to_line()); if ((p_tmp - point).cast().norm() < min) { - p = p_tmp; - min = (p - point).cast().norm(); + p = p_tmp; + min = (p - point).cast().norm(); line_idx = line - lines.begin(); } } - // 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); if (index != -1) { 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); p2->append_before(Point3(point, p2->first_point().z())); } - point = p; } 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 { 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) { p1->clear(); - p1->append_before(this->first_point()); + p1->append(this->first_point()); *p2 = *this; } else if (is_approx(length, this->length(), SCALED_EPSILON)) { p2->clear(); - p2->append_before(this->last_point()); + p2->append(this->last_point()); *p1 = *this; } else { - // Find the line to split at + // 1 find the line to split at size_t line_idx = 0; double acc_length = 0; - Point p = this->first_point().to_point(); - for (const auto &l : this->lines()) { - p = l.b.to_point(); + Point3 p = this->first_point(); + for (const auto& l : this->lines()) { + p = l.b; const double current_length = l.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; } acc_length += current_length; 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); if (index != -1) { this->split_at_index(index, p1, p2); } else { Polyline3 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); - p2->append_before(Point3(p, p2->first_point().z())); + p2->append_before(p); } } return true; } +bool Polyline3::split_fitting_result_before_index(size_t index, Point3& new_endpoint, std::vector& 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& 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()}); + } + } +} } diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 6d409d3983..88671391db 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -315,7 +315,7 @@ public: void simplify_by_fitting_arc(double tolerance); // Reverse the polyline - using MultiPoint3::reverse; + void reverse(); // Split polyline at given index bool split_at_index(const size_t index, Polyline3 *p1, Polyline3 *p2) const; @@ -344,15 +344,10 @@ public: std::vector fitting_result; private: - // Helper methods for split_at_index - bool split_fitting_result_before_index(size_t index, Point3 &new_endpoint, std::vector &result) const { - // Simplified stub - full implementation would handle arc fitting data - return false; - } - bool split_fitting_result_after_index(size_t index, Point3 &new_startpoint, std::vector &result) const { - // Simplified stub - full implementation would handle arc fitting data - return false; - } + void append_fitting_result_after_append_points(); + void append_fitting_result_after_append_polyline(const Polyline3& src); + bool split_fitting_result_before_index(size_t index, Point3& new_endpoint, std::vector& result) const; + bool split_fitting_result_after_index(size_t index, Point3& new_startpoint, std::vector& result) const; }; typedef std::vector Polylines3;