From d623ece5dada9566ef9d39cb61c9e0b4e59adaae Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 22 Sep 2023 13:22:05 +0200 Subject: [PATCH 01/18] Least squares circle fitting. --- src/libslic3r/Geometry/Circle.cpp | 79 +++++++++++++++++++++++++++++++ src/libslic3r/Geometry/Circle.hpp | 7 +++ 2 files changed, 86 insertions(+) diff --git a/src/libslic3r/Geometry/Circle.cpp b/src/libslic3r/Geometry/Circle.cpp index cdaf72d6a8..ff36b19584 100644 --- a/src/libslic3r/Geometry/Circle.cpp +++ b/src/libslic3r/Geometry/Circle.cpp @@ -146,4 +146,83 @@ Circled circle_ransac(const Vec2ds& input, size_t iterations, double* min_error) return circle_best; } +template +Circled circle_least_squares_by_solver(const Vec2ds &input, Solver solver) +{ + Circled out; + if (input.size() < 3) { + out = Circled::make_invalid(); + } else { + Eigen::Matrix A(input.size(), 3); + Eigen::VectorXd b(input.size()); + for (size_t r = 0; r < input.size(); ++ r) { + const Vec2d &p = input[r]; + A.row(r) = Vec3d(2. * p.x(), 2. * p.y(), - 1.); + b(r) = p.squaredNorm(); + } + auto result = solver(A, b); + out.center = result.head<2>(); + double r2 = out.center.squaredNorm() - result(2); + if (r2 <= EPSILON) + out.make_invalid(); + else + out.radius = sqrt(r2); + } + + return out; +} + +Circled circle_least_squares_svd(const Vec2ds &input) +{ + return circle_least_squares_by_solver(input, + [](const Eigen::Matrix &A, const Eigen::VectorXd &b) + { return A.bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(b).eval(); }); +} + +Circled circle_least_squares_qr(const Vec2ds &input) +{ + return circle_least_squares_by_solver(input, + [](const Eigen::Matrix &A, const Eigen::VectorXd &b) + { return A.colPivHouseholderQr().solve(b).eval(); }); +} + +Circled circle_least_squares_normal(const Vec2ds &input) +{ + Circled out; + if (input.size() < 3) { + out = Circled::make_invalid(); + } else { + Eigen::Matrix A = Eigen::Matrix::Zero(); + Eigen::Matrix b = Eigen::Matrix::Zero(); + for (size_t i = 0; i < input.size(); ++ i) { + const Vec2d &p = input[i]; + // Calculate right hand side of a normal equation. + b += p.squaredNorm() * Vec3d(2. * p.x(), 2. * p.y(), -1.); + // Calculate normal matrix (correlation matrix). + // Diagonal: + A(0, 0) += 4. * p.x() * p.x(); + A(1, 1) += 4. * p.y() * p.y(); + A(2, 2) += 1.; + // Off diagonal elements: + const double a = 4. * p.x() * p.y(); + A(0, 1) += a; + A(1, 0) += a; + const double b = -2. * p.x(); + A(0, 2) += b; + A(2, 0) += b; + const double c = -2. * p.y(); + A(1, 2) += c; + A(2, 1) += c; + } + auto result = A.ldlt().solve(b).eval(); + out.center = result.head<2>(); + double r2 = out.center.squaredNorm() - result(2); + if (r2 <= EPSILON) + out.make_invalid(); + else + out.radius = sqrt(r2); + } + return out; +} + } } // namespace Slic3r::Geometry diff --git a/src/libslic3r/Geometry/Circle.hpp b/src/libslic3r/Geometry/Circle.hpp index f4bca148d7..ca32d32b50 100644 --- a/src/libslic3r/Geometry/Circle.hpp +++ b/src/libslic3r/Geometry/Circle.hpp @@ -141,6 +141,13 @@ Circled circle_taubin_newton(const Vec2ds& input, size_t cycles = 20); // Find circle using RANSAC randomized algorithm. Circled circle_ransac(const Vec2ds& input, size_t iterations = 20, double* min_error = nullptr); +// Least squares fitting with SVD. Most accurate, but slowest. +Circled circle_least_squares_svd(const Vec2ds &input); +// Least squares fitting with QR decomposition. Medium accuracy, medium speed. +Circled circle_least_squares_qr(const Vec2ds &input); +// Least squares fitting solving normal equations. Low accuracy, high speed. +Circled circle_least_squares_normal(const Vec2ds &input); + // Randomized algorithm by Emo Welzl, working with squared radii for efficiency. The returned circle radius is inflated by epsilon. template CircleSq smallest_enclosing_circle2_welzl(const Points &points, const typename Vector::Scalar epsilon) From 6164051d6026308bbb798c56efd8887a46fe83cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Tue, 19 Sep 2023 14:55:38 +0200 Subject: [PATCH 02/18] Use proper formula for second moment of area. SupportSpotsGenerator originally used a heuristic formula. Current formula is properly derived using known properties of second moment of area. Several tests of this formula are added. --- src/libslic3r/SupportSpotsGenerator.cpp | 168 +++++++++++------- src/libslic3r/SupportSpotsGenerator.hpp | 27 +++ tests/libslic3r/CMakeLists.txt | 1 + .../test_support_spots_generator.cpp | 97 ++++++++++ 4 files changed, 229 insertions(+), 64 deletions(-) create mode 100644 tests/libslic3r/test_support_spots_generator.cpp diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index fd5bd7d5e8..eb3f1f07a1 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -166,7 +166,7 @@ struct SliceConnection this->second_moment_of_area_covariance_accumulator += other.second_moment_of_area_covariance_accumulator; } - void print_info(const std::string &tag) + void print_info(const std::string &tag) const { Vec3f centroid = centroid_accumulator / area; Vec2f variance = (second_moment_of_area_accumulator / area - centroid.head<2>().cwiseProduct(centroid.head<2>())); @@ -179,6 +179,27 @@ struct SliceConnection } }; +Integrals::Integrals (const Polygons& polygons) { + for (const Polygon &polygon : polygons) { + Vec2f p0 = unscaled(polygon.first_point()).cast(); + for (size_t i = 2; i < polygon.points.size(); i++) { + Vec2f p1 = unscaled(polygon.points[i - 1]).cast(); + Vec2f p2 = unscaled(polygon.points[i]).cast(); + + float sign = cross2(p1 - p0, p2 - p1) > 0 ? 1.0f : -1.0f; + + auto [area, first_moment_of_area, second_moment_area, + second_moment_of_area_covariance] = compute_moments_of_area_of_triangle(p0, p1, p2); + + this->area += sign * area; + this->x_i += sign * first_moment_of_area; + this->x_i_squared += sign * second_moment_area; + this->xy += sign * second_moment_of_area_covariance; + } + } +} + + SliceConnection estimate_slice_connection(size_t slice_idx, const Layer *layer) { SliceConnection connection; @@ -200,22 +221,11 @@ SliceConnection estimate_slice_connection(size_t slice_idx, const Layer *layer) Polygons overlap = intersection(ClipperUtils::clip_clipper_polygons_with_subject_bbox(slice_polys, below_bb), ClipperUtils::clip_clipper_polygons_with_subject_bbox(below_polys, slice_bb)); - for (const Polygon &poly : overlap) { - Vec2f p0 = unscaled(poly.first_point()).cast(); - for (size_t i = 2; i < poly.points.size(); i++) { - Vec2f p1 = unscaled(poly.points[i - 1]).cast(); - Vec2f p2 = unscaled(poly.points[i]).cast(); - - float sign = cross2(p1 - p0, p2 - p1) > 0 ? 1.0f : -1.0f; - - auto [area, first_moment_of_area, second_moment_area, - second_moment_of_area_covariance] = compute_moments_of_area_of_triangle(p0, p1, p2); - connection.area += sign * area; - connection.centroid_accumulator += sign * Vec3f(first_moment_of_area.x(), first_moment_of_area.y(), layer->print_z * area); - connection.second_moment_of_area_accumulator += sign * second_moment_area; - connection.second_moment_of_area_covariance_accumulator += sign * second_moment_of_area_covariance; - } - } + const Integrals integrals{overlap}; + connection.area += integrals.area; + connection.centroid_accumulator += Vec3f(integrals.x_i.x(), integrals.x_i.y(), layer->print_z * integrals.area); + connection.second_moment_of_area_accumulator += integrals.x_i_squared; + connection.second_moment_of_area_covariance_accumulator += integrals.xy; return connection; }; @@ -450,6 +460,48 @@ std::vector check_extrusion_entity_stability(const ExtrusionEntit } } +/** + * Calculates the second moment of area over an arbitrary polygon. + * + * Important note: The calculated moment is for an axis with origin at + * the polygon centroid! + * + * @param integrals Integrals over the polygon area. + * @param axis_direction Direction of the rotation axis going through centroid. + */ +float compute_second_moment( + const Integrals& integrals, + const Vec2f& axis_direction +) { + // Second moment of area for any axis intersecting coordinate system origin + // can be evaluated using the second moments of area calculated for the coordinate + // system axis and the moment product (int xy). + // The equation is derived appling known formulas for the moment of inertia + // to a plannar problem. One can reason about second moment + // of area by by setting density to 1 in the moment of inertia formulas. + const auto area = integrals.area; + const auto I_xx = integrals.x_i_squared.y(); + const auto I_yy = integrals.x_i_squared.x(); + const auto I_xy = -integrals.xy; + + const Vec2f centroid = integrals.x_i / area; + + Matrix2f moment_tensor{}; + moment_tensor << + I_xx, I_xy, + I_xy, I_yy; + + const float moment_at_0_0 = axis_direction.transpose() * moment_tensor * axis_direction; + + // Apply parallel axis theorem to move the moment to centroid + using line_alg::distance_to_infinite_squared; + + const Linef axis_at_0_0 = {{0, 0}, axis_direction.cast()}; + + const double distance = distance_to_infinite_squared(axis_at_0_0, centroid.cast()); + return moment_at_0_0 - area * distance; +} + class ObjectPart { public: @@ -482,43 +534,22 @@ public: this->sticking_second_moment_of_area_covariance_accumulator += sticking_area * position.x() * position.y(); } - float compute_directional_xy_variance(const Vec2f &line_dir, - const Vec3f ¢roid_accumulator, - const Vec2f &second_moment_of_area_accumulator, - const float &second_moment_of_area_covariance_accumulator, - const float &area) const - { - assert(area > 0); - Vec3f centroid = centroid_accumulator / area; - Vec2f variance = (second_moment_of_area_accumulator / area - centroid.head<2>().cwiseProduct(centroid.head<2>())); - float covariance = second_moment_of_area_covariance_accumulator / area - centroid.x() * centroid.y(); - // Var(aX+bY)=a^2*Var(X)+b^2*Var(Y)+2*a*b*Cov(X,Y) - float directional_xy_variance = line_dir.x() * line_dir.x() * variance.x() + line_dir.y() * line_dir.y() * variance.y() + - 2.0f * line_dir.x() * line_dir.y() * covariance; -#ifdef DETAILED_DEBUG_LOGS - BOOST_LOG_TRIVIAL(debug) << "centroid: " << centroid.x() << " " << centroid.y() << " " << centroid.z(); - BOOST_LOG_TRIVIAL(debug) << "variance: " << variance.x() << " " << variance.y(); - BOOST_LOG_TRIVIAL(debug) << "covariance: " << covariance; - BOOST_LOG_TRIVIAL(debug) << "directional_xy_variance: " << directional_xy_variance; -#endif - return directional_xy_variance; - } - float compute_elastic_section_modulus(const Vec2f &line_dir, - const Vec3f &extreme_point, - const Vec3f ¢roid_accumulator, - const Vec2f &second_moment_of_area_accumulator, - const float &second_moment_of_area_covariance_accumulator, - const float &area) const - { - float directional_xy_variance = compute_directional_xy_variance(line_dir, centroid_accumulator, second_moment_of_area_accumulator, - second_moment_of_area_covariance_accumulator, area); - if (directional_xy_variance < EPSILON) { return 0.0f; } - Vec3f centroid = centroid_accumulator / area; + float compute_elastic_section_modulus( + const Vec2f &line_dir, + const Vec3f &extreme_point, + const Integrals& integrals + ) const { + float second_moment_of_area = compute_second_moment(integrals, Vec2f{-line_dir.y(), line_dir.x()}); + + if (second_moment_of_area < EPSILON) { return 0.0f; } + + Vec2f centroid = integrals.x_i / integrals.area; float extreme_fiber_dist = line_alg::distance_to(Linef(centroid.head<2>().cast(), (centroid.head<2>() + Vec2f(line_dir.y(), -line_dir.x())).cast()), extreme_point.head<2>().cast()); - float elastic_section_modulus = area * directional_xy_variance / extreme_fiber_dist; + + float elastic_section_modulus = second_moment_of_area / extreme_fiber_dist; #ifdef DETAILED_DEBUG_LOGS BOOST_LOG_TRIVIAL(debug) << "extreme_fiber_dist: " << extreme_fiber_dist; @@ -534,6 +565,12 @@ public: float layer_z, const Params ¶ms) const { + // Note that exteme point is calculated for the current layer, while it should + // be computed for the first layer. The shape of the first layer however changes a lot, + // during support points additions (for organic supports it is not even clear how) + // and during merging. Using the current layer is heuristics and also small optimization, + // as the AABB tree for it is calculated anyways. This heuristic should usually be + // on the safe side. Vec2f line_dir = (extruded_line.b - extruded_line.a).normalized(); const Vec3f &mass_centroid = this->volume_centroid_accumulator / this->volume; float mass = this->volume * params.filament_density; @@ -548,19 +585,19 @@ public: { if (this->sticking_area < EPSILON) return {1.0f, SupportPointCause::UnstableFloatingPart}; + Integrals integrals; + integrals.area = this->sticking_area; + integrals.x_i = this->sticking_centroid_accumulator.head<2>(); + integrals.x_i_squared = this->sticking_second_moment_of_area_accumulator; + integrals.xy = this->sticking_second_moment_of_area_covariance_accumulator; + Vec3f bed_centroid = this->sticking_centroid_accumulator / this->sticking_area; - float bed_yield_torque = -compute_elastic_section_modulus(line_dir, extreme_point, this->sticking_centroid_accumulator, - this->sticking_second_moment_of_area_accumulator, - this->sticking_second_moment_of_area_covariance_accumulator, - this->sticking_area) * - params.get_bed_adhesion_yield_strength(); + float bed_yield_torque = -compute_elastic_section_modulus(line_dir, extreme_point, integrals) * params.get_bed_adhesion_yield_strength(); Vec2f bed_weight_arm = (mass_centroid.head<2>() - bed_centroid.head<2>()); float bed_weight_arm_len = bed_weight_arm.norm(); - float bed_weight_dir_xy_variance = compute_directional_xy_variance(bed_weight_arm, this->sticking_centroid_accumulator, - this->sticking_second_moment_of_area_accumulator, - this->sticking_second_moment_of_area_covariance_accumulator, - this->sticking_area); + + float bed_weight_dir_xy_variance = compute_second_moment(integrals, {-bed_weight_arm.y(), bed_weight_arm.x()}) / this->sticking_area; float bed_weight_sign = bed_weight_arm_len < 2.0f * sqrt(bed_weight_dir_xy_variance) ? -1.0f : 1.0f; float bed_weight_torque = bed_weight_sign * bed_weight_arm_len * weight; @@ -600,11 +637,14 @@ public: Vec3f conn_centroid = connection.centroid_accumulator / connection.area; if (layer_z - conn_centroid.z() < 3.0f) { return {-1.0f, SupportPointCause::WeakObjectPart}; } - float conn_yield_torque = compute_elastic_section_modulus(line_dir, extreme_point, connection.centroid_accumulator, - connection.second_moment_of_area_accumulator, - connection.second_moment_of_area_covariance_accumulator, - connection.area) * - params.material_yield_strength; + + Integrals integrals; + integrals.area = connection.area; + integrals.x_i = connection.centroid_accumulator.head<2>(); + integrals.x_i_squared = connection.second_moment_of_area_accumulator; + integrals.xy = connection.second_moment_of_area_covariance_accumulator; + + float conn_yield_torque = compute_elastic_section_modulus(line_dir, extreme_point, integrals) * params.material_yield_strength; float conn_weight_arm = (conn_centroid.head<2>() - mass_centroid.head<2>()).norm(); if (layer_z - conn_centroid.z() < 30.0) { diff --git a/src/libslic3r/SupportSpotsGenerator.hpp b/src/libslic3r/SupportSpotsGenerator.hpp index 2e07ef0290..ddb87f0f94 100644 --- a/src/libslic3r/SupportSpotsGenerator.hpp +++ b/src/libslic3r/SupportSpotsGenerator.hpp @@ -147,6 +147,33 @@ struct PartialObject bool connected_to_bed; }; + +/** + * Unsacled values of integrals over a polygonal domain. + */ +class Integrals{ + public: + /** + * Construct integral x_i int x_i^2 (i=1,2), xy and integral 1 (area). + * + * @param polygons List of polygons specifing the domain. + */ + explicit Integrals(const Polygons& polygons); + + // TODO refactor and delete the default constructor + Integrals() = default; + + float area{}; + Vec2f x_i{Vec2f::Zero()}; + Vec2f x_i_squared{Vec2f::Zero()}; + float xy{}; +}; + +float compute_second_moment( + const Integrals& integrals, + const Vec2f& axis_direction +); + using PartialObjects = std::vector; // Both support points and partial objects are sorted from the lowest z to the highest diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 90693c8a04..7b0d200c7d 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -39,6 +39,7 @@ add_executable(${_TEST_NAME}_tests test_astar.cpp test_anyptr.cpp test_jump_point_search.cpp + test_support_spots_generator.cpp ../data/prusaparts.cpp ../data/prusaparts.hpp ) diff --git a/tests/libslic3r/test_support_spots_generator.cpp b/tests/libslic3r/test_support_spots_generator.cpp new file mode 100644 index 0000000000..3ea8dcd070 --- /dev/null +++ b/tests/libslic3r/test_support_spots_generator.cpp @@ -0,0 +1,97 @@ +#include "libslic3r/Point.hpp" +#include +#include + +using namespace Slic3r; +using namespace SupportSpotsGenerator; + + +TEST_CASE("Numerical integral calculation compared with exact solution.", "[SupportSpotsGenerator]") { + const float width = 10; + const float height = 20; + const Polygon polygon = { + scaled(Vec2f{-width / 2, -height / 2}), + scaled(Vec2f{width / 2, -height / 2}), + scaled(Vec2f{width / 2, height / 2}), + scaled(Vec2f{-width / 2, height / 2}) + }; + + const Integrals integrals{{polygon}}; + CHECK(integrals.area == Approx(width * height)); + CHECK(integrals.x_i.x() == Approx(0)); + CHECK(integrals.x_i.y() == Approx(0)); + CHECK(integrals.x_i_squared.x() == Approx(std::pow(width, 3) * height / 12)); + CHECK(integrals.x_i_squared.y() == Approx(width * std::pow(height, 3) / 12)); +} + +TEST_CASE("Moment values and ratio check.", "[SupportSpotsGenerator]") { + const float width = 40; + const float height = 2; + + // Moments are calculated at centroid. + // Polygon centroid must not be (0, 0). + const Polygon polygon = { + scaled(Vec2f{0, 0}), + scaled(Vec2f{width, 0}), + scaled(Vec2f{width, height}), + scaled(Vec2f{0, height}) + }; + + const Integrals integrals{{polygon}}; + + const Vec2f x_axis{1, 0}; + const float x_axis_moment = compute_second_moment(integrals, x_axis); + + const Vec2f y_axis{0, 1}; + const float y_axis_moment = compute_second_moment(integrals, y_axis); + + const float moment_ratio = std::pow(width / height, 2); + + // Ensure the object transaltion has no effect. + CHECK(x_axis_moment == Approx(width * std::pow(height, 3) / 12)); + CHECK(y_axis_moment == Approx(std::pow(width, 3) * height / 12)); + // If the object is "wide" the y axis moments should be large compared to x axis moment. + CHECK(y_axis_moment / x_axis_moment == Approx(moment_ratio)); +} + +TEST_CASE("Moments calculation for rotated axis.", "[SupportSpotsGenerator]") { + + Polygon polygon = { + scaled(Vec2f{6.362284076172198, 138.9674202217155}), + scaled(Vec2f{97.48779843751677, 106.08136606617076}), + scaled(Vec2f{135.75221821532384, 66.84428834668765}), + scaled(Vec2f{191.5308049852741, 45.77905628725614}), + scaled(Vec2f{182.7525148049201, 74.01799041087513}), + scaled(Vec2f{296.83210979283473, 196.80022572637228}), + scaled(Vec2f{215.16434429179148, 187.45715418834143}), + scaled(Vec2f{64.64574271229334, 284.293883209721}), + scaled(Vec2f{110.76507036894843, 174.35633141113783}), + scaled(Vec2f{77.56229640885199, 189.33057746591336}) + }; + + Integrals integrals{{polygon}}; + + std::mt19937 generator{std::random_device{}()}; + std::uniform_real_distribution angle_distribution{0, 2*M_PI}; + + // Meassured counterclockwise from (1, 0) + const float angle = angle_distribution(generator); + Vec2f axis{std::cos(angle), std::sin(angle)}; + + float moment_calculated_then_rotated = compute_second_moment( + integrals, + axis + ); + + // We want to rotate the object clockwise by angle to align the axis with (1, 0) + // Method .rotate is counterclockwise for positive angle + polygon.rotate(-angle); + + Integrals integrals_rotated{{polygon}}; + float moment_rotated_polygon = compute_second_moment( + integrals_rotated, + Vec2f{1, 0} + ); + + CHECK(moment_calculated_then_rotated == Approx(moment_rotated_polygon)); +} From d9d0ed293eab43cb4c6c0aa751c5957c7a12039e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Thu, 21 Sep 2023 14:48:02 +0200 Subject: [PATCH 03/18] Improve curvature calculation. Fix authored by Pavel Mikus. --- src/libslic3r/GCode/ExtrusionProcessor.hpp | 128 ++++++++++++--------- 1 file changed, 76 insertions(+), 52 deletions(-) diff --git a/src/libslic3r/GCode/ExtrusionProcessor.hpp b/src/libslic3r/GCode/ExtrusionProcessor.hpp index 3e2bbf080d..774413bd53 100644 --- a/src/libslic3r/GCode/ExtrusionProcessor.hpp +++ b/src/libslic3r/GCode/ExtrusionProcessor.hpp @@ -49,6 +49,34 @@ std::vector estimate_points_properties(const POINTS float flow_width, float max_line_length = -1.0f) { + bool looped = input_points.front() == input_points.back(); + std::function get_prev_index = [](int idx, size_t count) { + if (idx > 0) { + return idx - 1; + } else + return idx; + }; + if (looped) { + get_prev_index = [](int idx, size_t count) { + if (idx == 0) + idx = count; + return --idx; + }; + }; + std::function get_next_index = [](int idx, size_t size) { + if (idx + 1 < size) { + return idx + 1; + } else + return idx; + }; + if (looped) { + get_next_index = [](int idx, size_t count) { + if (++idx == count) + idx = 0; + return idx; + }; + }; + using P = typename POINTS::value_type; using AABBScalar = typename AABBTreeLines::LinesDistancer::Scalar; @@ -156,69 +184,65 @@ std::vector estimate_points_properties(const POINTS points = std::move(new_points); } - std::vector angles_for_curvature(points.size()); + float accumulated_distance = 0; std::vector distances_for_curvature(points.size()); + for (int point_idx = 0; point_idx < int(points.size()); ++point_idx) { + const ExtendedPoint &a = points[point_idx]; + const ExtendedPoint &b = points[get_prev_index(point_idx, points.size())]; - for (size_t point_idx = 0; point_idx < points.size(); ++point_idx) { - ExtendedPoint &a = points[point_idx]; - size_t prev = prev_idx_modulo(point_idx, points.size()); - size_t next = next_idx_modulo(point_idx, points.size()); - - int iter_limit = points.size(); - while ((a.position - points[prev].position).squaredNorm() < 1 && iter_limit > 0) { - prev = prev_idx_modulo(prev, points.size()); - iter_limit--; - } - - while ((a.position - points[next].position).squaredNorm() < 1 && iter_limit > 0) { - next = next_idx_modulo(next, points.size()); - iter_limit--; - } - - distances_for_curvature[point_idx] = (points[prev].position - a.position).norm(); - float alfa = angle(a.position - points[prev].position, points[next].position - a.position); - angles_for_curvature[point_idx] = alfa; + distances_for_curvature[point_idx] = (b.position - a.position).norm(); + accumulated_distance += distances_for_curvature[point_idx]; } - if (std::accumulate(distances_for_curvature.begin(), distances_for_curvature.end(), 0) > EPSILON) + if (accumulated_distance > EPSILON) for (float window_size : {3.0f, 9.0f, 16.0f}) { - size_t tail_point = 0; - float tail_window_acc = 0; - float tail_angle_acc = 0; + for (int point_idx = 0; point_idx < int(points.size()); ++point_idx) { + ExtendedPoint ¤t = points[point_idx]; - size_t head_point = 0; - float head_window_acc = 0; - float head_angle_acc = 0; - - for (size_t point_idx = 0; point_idx < points.size(); ++point_idx) { - if (point_idx == 0) { - while (tail_window_acc < window_size * 0.5) { - tail_window_acc += distances_for_curvature[tail_point]; - tail_angle_acc += angles_for_curvature[tail_point]; - tail_point = prev_idx_modulo(tail_point, points.size()); + Vec2d back_position = current.position; + { + size_t back_point_index = point_idx; + float dist_backwards = 0; + while (dist_backwards < window_size * 0.5 && back_point_index != get_prev_index(back_point_index, points.size())) { + float line_dist = distances_for_curvature[get_prev_index(back_point_index, points.size())]; + if (dist_backwards + line_dist > window_size * 0.5) { + back_position = points[back_point_index].position + + (window_size * 0.5 - dist_backwards) * + (points[get_prev_index(back_point_index, points.size())].position - + points[back_point_index].position) + .normalized(); + dist_backwards += window_size * 0.5 - dist_backwards + EPSILON; + } else { + dist_backwards += line_dist; + back_point_index = get_prev_index(back_point_index, points.size()); + } } } - while (tail_window_acc - distances_for_curvature[next_idx_modulo(tail_point, points.size())] > window_size * 0.5) { - tail_point = next_idx_modulo(tail_point, points.size()); - tail_window_acc -= distances_for_curvature[tail_point]; - tail_angle_acc -= angles_for_curvature[tail_point]; + + Vec2d front_position = current.position; + { + size_t front_point_index = point_idx; + float dist_forwards = 0; + while (dist_forwards < window_size * 0.5 && front_point_index != get_next_index(front_point_index, points.size())) { + float line_dist = distances_for_curvature[front_point_index]; + if (dist_forwards + line_dist > window_size * 0.5) { + front_position = points[front_point_index].position + + (window_size * 0.5 - dist_forwards) * + (points[get_next_index(front_point_index, points.size())].position - + points[front_point_index].position) + .normalized(); + dist_forwards += window_size * 0.5 - dist_forwards + EPSILON; + } else { + dist_forwards += line_dist; + front_point_index = get_next_index(front_point_index, points.size()); + } + } } - while (head_window_acc < window_size * 0.5) { - head_point = next_idx_modulo(head_point, points.size()); - head_window_acc += distances_for_curvature[head_point]; - head_angle_acc += angles_for_curvature[head_point]; + float new_curvature = angle(current.position - back_position, front_position - current.position) / window_size; + if (abs(current.curvature) < abs(new_curvature)) { + current.curvature = new_curvature; } - - float curvature = (tail_angle_acc + head_angle_acc) / window_size; - if (std::abs(curvature) > std::abs(points[point_idx].curvature)) { - points[point_idx].curvature = curvature; - } - - tail_window_acc += distances_for_curvature[point_idx]; - tail_angle_acc += angles_for_curvature[point_idx]; - head_window_acc -= distances_for_curvature[point_idx]; - head_angle_acc -= angles_for_curvature[point_idx]; } } From 0a4766e8d329c259065805e6ae986c08fcef6df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Fri, 22 Sep 2023 14:41:04 +0200 Subject: [PATCH 04/18] Fix linux compiling issue. Just add .template to the right place. --- src/libslic3r/Geometry/Circle.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Geometry/Circle.cpp b/src/libslic3r/Geometry/Circle.cpp index ff36b19584..24d408c6a2 100644 --- a/src/libslic3r/Geometry/Circle.cpp +++ b/src/libslic3r/Geometry/Circle.cpp @@ -161,7 +161,7 @@ Circled circle_least_squares_by_solver(const Vec2ds &input, Solver solver) b(r) = p.squaredNorm(); } auto result = solver(A, b); - out.center = result.head<2>(); + out.center = result.template head<2>(); double r2 = out.center.squaredNorm() - result(2); if (r2 <= EPSILON) out.make_invalid(); From fc03b9f36cf05493c84eebf568ebd457041222a5 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Mon, 25 Sep 2023 11:02:34 +0200 Subject: [PATCH 05/18] Updated compatible printer condition for some filament profiles. --- resources/profiles/PrusaResearch.idx | 1 + resources/profiles/PrusaResearch.ini | 36 ++++++++++++++-------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index cf33c536b1..b68100ac8e 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,4 +1,5 @@ min_slic3r_version = 2.6.2-alpha0 +1.11.0-alpha4 Updated compatible printer conditions for specific filament profiles. 1.11.0-alpha3 Added new print profiles for Prusa MINI Input Shaper (Alpha). Updated MK4 IS profiles. 1.11.0-alpha2 Added MK3.9 and Prusa MINI Input Shaper (alpha). Enabled binary g-code, arc fitting and QOI/PNG for MINI and MINI IS. 1.11.0-alpha1 Updated ramming parameters. Updated start-gcode for XL Multi-Tool. Updated output filename format. diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 9c908fb88b..b208e92ea5 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,7 +5,7 @@ name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 1.11.0-alpha3 +config_version = 1.11.0-alpha4 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/ changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% @@ -6399,18 +6399,18 @@ compatible_printers_condition = nozzle_diameter[0]!=0.8 and printer_model!="MK2S [filament:Generic PETG @PG] inherits = Generic PETG; *PETPG* -compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 +compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 [filament:Generic PETG @PG 0.6] inherits = Generic PETG; *PET06PG* filament_max_volumetric_speed = 17 -compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.6 +compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]==0.6 [filament:Generic PETG @PG 0.8] inherits = Generic PETG; *PET08PG* first_layer_temperature = 240 temperature = 250 -compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.8 +compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]==0.8 [filament:Generic PETG @XL] inherits = Generic PETG @PG; *PETXL* @@ -7563,18 +7563,18 @@ compatible_printers_condition = nozzle_diameter[0]!=0.8 and printer_notes!~/.*PG [filament:Generic PLA @PG] inherits = Generic PLA; *PLAPG* -compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 +compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 [filament:Generic PLA @PG 0.6] inherits = Generic PLA; *PLA06PG* filament_max_volumetric_speed = 15 -compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.6 +compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]==0.6 [filament:Generic PLA @PG 0.8] inherits = Generic PLA; *PLA08PG* first_layer_temperature = 220 temperature = 220 -compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.8 +compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]==0.8 [filament:Generic PLA @XL] inherits = Generic PLA @PG; *PLAXL* @@ -10376,16 +10376,16 @@ compatible_printers_condition = nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!= [filament:Prusa PETG @PG] inherits = Prusa PETG; *PETPG* -compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 +compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 [filament:Prusa PETG @PG 0.6] inherits = Prusa PETG; *PET06PG* -compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.6 +compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]==0.6 [filament:Prusa PETG @PG 0.8] inherits = Prusa PETG; *PET08PG* temperature = 250 -compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.8 +compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]==0.8 [filament:Prusa PETG @XL] inherits = Prusa PETG @PG; *PETXL* @@ -10469,17 +10469,17 @@ compatible_printers_condition = nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!= [filament:Prusament PETG @PG] inherits = Prusament PETG; *PETPG* -compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 +compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 [filament:Prusament PETG @PG 0.6] inherits = Prusament PETG; *PET06PG* -compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.6 +compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]==0.6 [filament:Prusament PETG @PG 0.8] inherits = Prusament PETG; *PET08PG* first_layer_temperature = 250 temperature = 260 -compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.8 +compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]==0.8 [filament:Prusament PETG @XL] inherits = Prusament PETG @PG; *PETXL* @@ -10790,16 +10790,16 @@ compatible_printers_condition = nozzle_diameter[0]!=0.8 and printer_notes!~/.*PG [filament:Prusa PLA @PG] inherits = Prusa PLA; *PLAPG* -compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 +compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 [filament:Prusa PLA @PG 0.6] inherits = Prusa PLA; *PLA06PG* filament_max_volumetric_speed = 15.5 -compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.6 +compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]==0.6 [filament:Prusa PLA @PG 0.8] inherits = Prusa PLA; *PLA08PG* -compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.8 +compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]==0.8 [filament:Prusa PLA @XL] inherits = Prusa PLA @PG; *PLAXL* @@ -12247,12 +12247,12 @@ compatible_printers_condition = nozzle_diameter[0]!=0.8 and printer_notes!~/.*PG [filament:Prusament PLA @PG] inherits = Prusament PLA; *PLAPG* -compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 +compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 [filament:Prusament PLA @PG 0.6] inherits = Prusament PLA; *PLA06PG* filament_max_volumetric_speed = 16 -compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.6 +compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]==0.6 [filament:Prusament PLA @PG 0.8] inherits = Prusament PLA; *PLA08PG* From fd7d7e1ae59552c7e3b87fe33947d6b4f0739846 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 22 Sep 2023 15:42:30 +0200 Subject: [PATCH 06/18] SubstitutionManager: Follow-up 2b25f55f8 - Fixed a crash. (Added missed cleaning of the m_chb_match_single_lines on Delete All substitutions) + Added missed Layout on add substitution + EditGCodeDialog: Deleted unused code caused compilation warning. --- src/slic3r/GUI/EditGCodeDialog.cpp | 1 - src/slic3r/GUI/Tab.cpp | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/EditGCodeDialog.cpp b/src/slic3r/GUI/EditGCodeDialog.cpp index 834930e08f..93c8d59c26 100644 --- a/src/slic3r/GUI/EditGCodeDialog.cpp +++ b/src/slic3r/GUI/EditGCodeDialog.cpp @@ -277,7 +277,6 @@ void EditGCodeDialog::add_selected_value_to_gcode() if (val.IsEmpty()) return; - const long pos = m_gcode_editor->GetInsertionPoint(); m_gcode_editor->WriteText(m_gcode_editor->GetInsertionPoint() == m_gcode_editor->GetLastPosition() ? "\n" + val : val); if (val.Last() == ']') { diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index ee4ebdf1ba..92d7bbfc04 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -4735,6 +4735,7 @@ void SubstitutionManager::update_from_config() if (m_substitutions == subst && m_grid_sizer->IsShown(1)) { // just update visibility for chb_match_single_lines int subst_id = 0; + assert(m_chb_match_single_lines.size() == size_t(subst.size()/4)); for (size_t i = 0; i < subst.size(); i += 4) { const std::string& params = subst[i + 2]; const bool regexp = strchr(params.c_str(), 'r') != nullptr || strchr(params.c_str(), 'R') != nullptr; @@ -4772,8 +4773,10 @@ void SubstitutionManager::delete_all() m_config->option("gcode_substitutions")->values.clear(); call_ui_update(); - if (!m_grid_sizer->IsEmpty()) + if (!m_grid_sizer->IsEmpty()) { m_grid_sizer->Clear(true); + m_chb_match_single_lines.clear(); + } m_parent->GetParent()->Layout(); } @@ -4839,6 +4842,7 @@ wxSizer* TabPrint::create_substitutions_widget(wxWindow* parent) m_subst_manager.init(m_config, parent, grid_sizer); m_subst_manager.set_cb_edited_substitution([this]() { update_dirty(); + Layout(); wxGetApp().mainframe->on_config_changed(m_config); // invalidate print }); m_subst_manager.set_cb_hide_delete_all_btn([this]() { From c968b797ed1e30accbe22835b31b5ea809c98598 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 26 Sep 2023 11:20:35 +0200 Subject: [PATCH 07/18] Follow-up to c8263d1: do the same change for XL temp notifications --- src/libslic3r/GCode/GCodeProcessor.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 6e7e8b8944..9504860944 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -595,8 +595,12 @@ void GCodeProcessor::apply_config(const PrintConfig& config) for (size_t i = 0; i < extruders_count; ++ i) { m_extruder_offsets[i] = to_3d(config.extruder_offset.get_at(i).cast().eval(), 0.f); m_extruder_colors[i] = static_cast(i); - m_extruder_temps_config[i] = static_cast(config.temperature.get_at(i)); m_extruder_temps_first_layer_config[i] = static_cast(config.first_layer_temperature.get_at(i)); + m_extruder_temps_config[i] = static_cast(config.temperature.get_at(i)); + if (m_extruder_temps_config[i] == 0) { + // This means the value should be ignored and first layer temp should be used. + m_extruder_temps_config[i] = m_extruder_temps_first_layer_config[i]; + } m_result.filament_diameters[i] = static_cast(config.filament_diameter.get_at(i)); m_result.filament_densities[i] = static_cast(config.filament_density.get_at(i)); m_result.filament_cost[i] = static_cast(config.filament_cost.get_at(i)); From 6d41a76af77af2196eab759db594075706a667bd Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 28 Aug 2023 16:46:27 +0200 Subject: [PATCH 08/18] M486 cancel object implementation: - gcode_label_objects changed from coBool to coEnum - the start-end pairs are emitted at the start for all objects --- src/libslic3r/GCode.cpp | 61 +++++++++++++++++++++++++++++++---- src/libslic3r/PrintConfig.cpp | 20 ++++++++++-- src/libslic3r/PrintConfig.hpp | 7 +++- 3 files changed, 78 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 5afe6b34c4..1d72639f55 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -103,6 +103,41 @@ namespace Slic3r { } + // Accepts vector of PrintObjectPtrs and an object and instance ids. Returns starting tag for label object function. + static std::string label_object_start(LabelObjects label_object_style, const SpanOfConstPtrs& objects, int object_id, int instance_id) + { + int unique_id = 0; + for (size_t idx = 0; idx < size_t(object_id); ++idx) + unique_id += int(objects[idx]->model_object()->instances.size()); + unique_id += instance_id; + + std::string name = objects[object_id]->model_object()->name; + if (label_object_style == loMarlin && objects[object_id]->model_object()->instances.size() > 1u) + name += " (copy " + std::to_string(instance_id) + ")"; + + std::string out; + if (label_object_style == loOctoprint) + out += std::string("; printing object ") + name + " id:" + std::to_string(object_id) + " copy " + std::to_string(instance_id) + "\n"; + else if (label_object_style == loMarlin) { + out += std::string("M486 S") + std::to_string(unique_id) + "\n"; + out += std::string("M486 N") + name + "\n"; + } + return out; + } + + + static std::string label_object_stop(LabelObjects label_object_style, int object_id, int instance_id, const std::string& name) + { + std::string out; + if (label_object_style == loOctoprint) + out += std::string("; stop printing object ") + name + " id:" + std::to_string(object_id) + " copy " + std::to_string(instance_id) + "\n"; + else if (label_object_style == loMarlin) + out += std::string("M486 S-1\n"); + return out; + } + + + // Return true if tch_prefix is found in custom_gcode static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std::string& tch_prefix, unsigned next_extruder) { @@ -1215,6 +1250,18 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail // Set other general things. file.write(this->preamble()); + // Label all objects so printer knows about them since the start. + if (config().gcode_label_objects != loDisabled) { + file.write("\n"); + for (size_t object_idx = 0; object_idx < print.objects().size(); ++object_idx) { + for (size_t inst_idx = 0; inst_idx < print.objects()[object_idx]->model_object()->instances.size(); ++inst_idx) { + file.write(label_object_start(config().gcode_label_objects, print.objects(), object_idx, inst_idx)); + file.write(label_object_stop(config().gcode_label_objects, object_idx, inst_idx, print.objects()[object_idx]->model_object()->name)); + } + } + file.write("\n"); + } + print.throw_if_canceled(); // Collect custom seam data from all objects. @@ -2354,13 +2401,13 @@ void GCodeGenerator::process_layer_single_object( m_avoid_crossing_perimeters.use_external_mp_once(); m_last_obj_copy = this_object_copy; this->set_origin(unscale(offset)); - if (this->config().gcode_label_objects) { - for (const PrintObject *po : print_object.print()->objects()) + if (this->config().gcode_label_objects != loDisabled) { + for (const PrintObject* po : print_object.print()->objects()) { if (po == &print_object) break; - else - ++ object_id; - gcode += std::string("; printing object ") + print_object.model_object()->name + " id:" + std::to_string(object_id) + " copy " + std::to_string(print_instance.instance_id) + "\n"; + ++object_id; + } + gcode += label_object_start(config().gcode_label_objects, print_object.print()->objects(), object_id, print_instance.instance_id); } } }; @@ -2538,8 +2585,8 @@ void GCodeGenerator::process_layer_single_object( } } } - if (! first && this->config().gcode_label_objects) - gcode += std::string("; stop printing object ") + print_object.model_object()->name + " id:" + std::to_string(object_id) + " copy " + std::to_string(print_instance.instance_id) + "\n"; + if (! first && config().gcode_label_objects != loDisabled) + gcode += label_object_stop(config().gcode_label_objects, object_id, print_instance.instance_id, print_object.model_object()->name); } void GCodeGenerator::apply_print_config(const PrintConfig &print_config) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 6d52484e9c..2c033c5d9c 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -229,6 +229,13 @@ static const t_config_enum_values s_keys_map_DraftShield = { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(DraftShield) +static const t_config_enum_values s_keys_map_LabelObjects = { + { "disabled", loDisabled }, + { "octoprint", loOctoprint }, + { "marlin", loMarlin } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(LabelObjects) + static const t_config_enum_values s_keys_map_GCodeThumbnailsFormat = { { "PNG", int(GCodeThumbnailsFormat::PNG) }, { "JPG", int(GCodeThumbnailsFormat::JPG) }, @@ -1493,13 +1500,18 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert; def->set_default_value(new ConfigOptionEnum(gcfRepRapSprinter)); - def = this->add("gcode_label_objects", coBool); + def = this->add("gcode_label_objects", coEnum); def->label = L("Label objects"); def->tooltip = L("Enable this to add comments into the G-Code labeling print moves with what object they belong to," " which is useful for the Octoprint CancelObject plugin. This settings is NOT compatible with " "Single Extruder Multi Material setup and Wipe into Object / Wipe into Infill."); + def->set_enum({ + { "disabled", L("Disabled") }, + { "octoprint", L("OctoPrint comments") }, + { "marlin", L("Marlin (M486)") } + }); def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(0)); + def->set_default_value(new ConfigOptionEnum(loDisabled)); def = this->add("gcode_substitutions", coStrings); def->label = L("G-code substitutions"); @@ -4335,6 +4347,10 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va } else if (opt_key == "draft_shield" && (value == "1" || value == "0")) { // draft_shield used to be a bool, it was turned into an enum in PrusaSlicer 2.4.0. value = value == "1" ? "enabled" : "disabled"; + } else if (opt_key == "gcode_label_objects" && (value == "1" || value == "0")) { + // gcode_label_objects used to be a bool (the behavior was nothing or "octoprint"), it is + // and enum since PrusaSlicer 2.6.2. + value = value == "1" ? "octoprint" : "disabled"; } else if (opt_key == "octoprint_host") { opt_key = "print_host"; } else if (opt_key == "octoprint_cafile") { diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 390f91daf6..5466ad1fd3 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -147,6 +147,10 @@ enum DraftShield { dsDisabled, dsLimited, dsEnabled }; +enum LabelObjects { + loDisabled, loOctoprint, loMarlin +}; + enum class PerimeterGeneratorType { // Classic perimeter generator using Clipper offsets with constant extrusion width. @@ -183,6 +187,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLAPillarConnectionMode) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLASupportTreeType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BrimType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(DraftShield) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(LabelObjects) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeThumbnailsFormat) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType) @@ -729,7 +734,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloats, filament_multitool_ramming_flow)) ((ConfigOptionBool, gcode_comments)) ((ConfigOptionEnum, gcode_flavor)) - ((ConfigOptionBool, gcode_label_objects)) + ((ConfigOptionEnum, gcode_label_objects)) // Triples of strings: "search pattern", "replace with pattern", "attribs" // where "attribs" are one of: // r - regular expression From 5d50a91c30298ec767f5517d00dddf62db45c5d9 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 5 Sep 2023 15:43:13 +0200 Subject: [PATCH 09/18] Label objects refactoring (enum->enum class, initial header export separated in a dedicated function) --- src/libslic3r/GCode.cpp | 43 +++++++++++++++++++++-------------- src/libslic3r/PrintConfig.cpp | 8 +++---- src/libslic3r/PrintConfig.hpp | 4 ++-- 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 1d72639f55..6eca721c12 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -112,13 +112,13 @@ namespace Slic3r { unique_id += instance_id; std::string name = objects[object_id]->model_object()->name; - if (label_object_style == loMarlin && objects[object_id]->model_object()->instances.size() > 1u) + if (label_object_style == LabelObjects::Marlin && objects[object_id]->model_object()->instances.size() > 1u) name += " (copy " + std::to_string(instance_id) + ")"; std::string out; - if (label_object_style == loOctoprint) + if (label_object_style == LabelObjects::Octoprint) out += std::string("; printing object ") + name + " id:" + std::to_string(object_id) + " copy " + std::to_string(instance_id) + "\n"; - else if (label_object_style == loMarlin) { + else if (label_object_style == LabelObjects::Marlin) { out += std::string("M486 S") + std::to_string(unique_id) + "\n"; out += std::string("M486 N") + name + "\n"; } @@ -129,14 +129,32 @@ namespace Slic3r { static std::string label_object_stop(LabelObjects label_object_style, int object_id, int instance_id, const std::string& name) { std::string out; - if (label_object_style == loOctoprint) + if (label_object_style == LabelObjects::Octoprint) out += std::string("; stop printing object ") + name + " id:" + std::to_string(object_id) + " copy " + std::to_string(instance_id) + "\n"; - else if (label_object_style == loMarlin) + else if (label_object_style == LabelObjects::Marlin) out += std::string("M486 S-1\n"); return out; } + static std::string label_all_objects(LabelObjects label_objects_style, const Print& print) + { + std::string out; + + if (label_objects_style != LabelObjects::Disabled) { + out += "\n"; + for (size_t object_idx = 0; object_idx < print.objects().size(); ++object_idx) { + for (size_t inst_idx = 0; inst_idx < print.objects()[object_idx]->model_object()->instances.size(); ++inst_idx) { + out += label_object_start(label_objects_style, print.objects(), object_idx, inst_idx); + out += label_object_stop(label_objects_style, object_idx, inst_idx, print.objects()[object_idx]->model_object()->name); + } + } + out += "\n"; + } + return out; + } + + // Return true if tch_prefix is found in custom_gcode static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std::string& tch_prefix, unsigned next_extruder) @@ -1251,16 +1269,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail file.write(this->preamble()); // Label all objects so printer knows about them since the start. - if (config().gcode_label_objects != loDisabled) { - file.write("\n"); - for (size_t object_idx = 0; object_idx < print.objects().size(); ++object_idx) { - for (size_t inst_idx = 0; inst_idx < print.objects()[object_idx]->model_object()->instances.size(); ++inst_idx) { - file.write(label_object_start(config().gcode_label_objects, print.objects(), object_idx, inst_idx)); - file.write(label_object_stop(config().gcode_label_objects, object_idx, inst_idx, print.objects()[object_idx]->model_object()->name)); - } - } - file.write("\n"); - } + file.write(label_all_objects(config().gcode_label_objects, print)); print.throw_if_canceled(); @@ -2401,7 +2410,7 @@ void GCodeGenerator::process_layer_single_object( m_avoid_crossing_perimeters.use_external_mp_once(); m_last_obj_copy = this_object_copy; this->set_origin(unscale(offset)); - if (this->config().gcode_label_objects != loDisabled) { + if (this->config().gcode_label_objects != LabelObjects::Disabled) { for (const PrintObject* po : print_object.print()->objects()) { if (po == &print_object) break; @@ -2585,7 +2594,7 @@ void GCodeGenerator::process_layer_single_object( } } } - if (! first && config().gcode_label_objects != loDisabled) + if (! first && config().gcode_label_objects != LabelObjects::Disabled) gcode += label_object_stop(config().gcode_label_objects, object_id, print_instance.instance_id, print_object.model_object()->name); } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 2c033c5d9c..c80189ae3c 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -230,9 +230,9 @@ static const t_config_enum_values s_keys_map_DraftShield = { CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(DraftShield) static const t_config_enum_values s_keys_map_LabelObjects = { - { "disabled", loDisabled }, - { "octoprint", loOctoprint }, - { "marlin", loMarlin } + { "disabled", int(LabelObjects::Disabled) }, + { "octoprint", int(LabelObjects::Octoprint) }, + { "marlin", int(LabelObjects::Marlin) } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(LabelObjects) @@ -1511,7 +1511,7 @@ void PrintConfigDef::init_fff_params() { "marlin", L("Marlin (M486)") } }); def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum(loDisabled)); + def->set_default_value(new ConfigOptionEnum(LabelObjects::Disabled)); def = this->add("gcode_substitutions", coStrings); def->label = L("G-code substitutions"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 5466ad1fd3..c01884c765 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -147,8 +147,8 @@ enum DraftShield { dsDisabled, dsLimited, dsEnabled }; -enum LabelObjects { - loDisabled, loOctoprint, loMarlin +enum class LabelObjects { + Disabled, Octoprint, Marlin }; enum class PerimeterGeneratorType From 395a5639cced0b6b2c0a8bf18ba09668919174d9 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 5 Sep 2023 16:00:16 +0200 Subject: [PATCH 10/18] Label objects: rename Marlin -> Firmware-specific, updated tooltip --- src/libslic3r/GCode.cpp | 6 +++--- src/libslic3r/PrintConfig.cpp | 12 +++++++----- src/libslic3r/PrintConfig.hpp | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 6eca721c12..04b12ba719 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -112,13 +112,13 @@ namespace Slic3r { unique_id += instance_id; std::string name = objects[object_id]->model_object()->name; - if (label_object_style == LabelObjects::Marlin && objects[object_id]->model_object()->instances.size() > 1u) + if (label_object_style == LabelObjects::Firmware && objects[object_id]->model_object()->instances.size() > 1u) name += " (copy " + std::to_string(instance_id) + ")"; std::string out; if (label_object_style == LabelObjects::Octoprint) out += std::string("; printing object ") + name + " id:" + std::to_string(object_id) + " copy " + std::to_string(instance_id) + "\n"; - else if (label_object_style == LabelObjects::Marlin) { + else if (label_object_style == LabelObjects::Firmware) { out += std::string("M486 S") + std::to_string(unique_id) + "\n"; out += std::string("M486 N") + name + "\n"; } @@ -131,7 +131,7 @@ namespace Slic3r { std::string out; if (label_object_style == LabelObjects::Octoprint) out += std::string("; stop printing object ") + name + " id:" + std::to_string(object_id) + " copy " + std::to_string(instance_id) + "\n"; - else if (label_object_style == LabelObjects::Marlin) + else if (label_object_style == LabelObjects::Firmware) out += std::string("M486 S-1\n"); return out; } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index c80189ae3c..5e79c97d3e 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -232,7 +232,7 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(DraftShield) static const t_config_enum_values s_keys_map_LabelObjects = { { "disabled", int(LabelObjects::Disabled) }, { "octoprint", int(LabelObjects::Octoprint) }, - { "marlin", int(LabelObjects::Marlin) } + { "firmware", int(LabelObjects::Firmware) } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(LabelObjects) @@ -1502,13 +1502,15 @@ void PrintConfigDef::init_fff_params() def = this->add("gcode_label_objects", coEnum); def->label = L("Label objects"); - def->tooltip = L("Enable this to add comments into the G-Code labeling print moves with what object they belong to," - " which is useful for the Octoprint CancelObject plugin. This settings is NOT compatible with " - "Single Extruder Multi Material setup and Wipe into Object / Wipe into Infill."); + def->tooltip = L("Selects whether labels should be exported at object boundaries and in what format.\n" + " Octoprint = comments to be consumed by Octoprint CancelObject plugin.\n" + " Firmware = firmware specific G-code (it will be chosen based on firmware flavor and it can end up to be empty).\n\n" + "This settings is NOT compatible with Single Extruder Multi Material setup and Wipe into Object / Wipe into Infill."); + def->set_enum({ { "disabled", L("Disabled") }, { "octoprint", L("OctoPrint comments") }, - { "marlin", L("Marlin (M486)") } + { "firmware", L("Firmware-specific") } }); def->mode = comAdvanced; def->set_default_value(new ConfigOptionEnum(LabelObjects::Disabled)); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index c01884c765..d3fd0ff429 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -148,7 +148,7 @@ enum DraftShield { }; enum class LabelObjects { - Disabled, Octoprint, Marlin + Disabled, Octoprint, Firmware }; enum class PerimeterGeneratorType From ef31e4f4871d89c9849e9614b8fa3d4745a7a00d Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 5 Sep 2023 16:09:14 +0200 Subject: [PATCH 11/18] LabelObjects: differentiate based on firmware flavor, change M486 N to M486 A --- src/libslic3r/GCode.cpp | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 04b12ba719..da40218779 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -104,7 +104,7 @@ namespace Slic3r { // Accepts vector of PrintObjectPtrs and an object and instance ids. Returns starting tag for label object function. - static std::string label_object_start(LabelObjects label_object_style, const SpanOfConstPtrs& objects, int object_id, int instance_id) + static std::string label_object_start(LabelObjects label_object_style, GCodeFlavor flavor, const SpanOfConstPtrs& objects, int object_id, int instance_id) { int unique_id = 0; for (size_t idx = 0; idx < size_t(object_id); ++idx) @@ -119,25 +119,33 @@ namespace Slic3r { if (label_object_style == LabelObjects::Octoprint) out += std::string("; printing object ") + name + " id:" + std::to_string(object_id) + " copy " + std::to_string(instance_id) + "\n"; else if (label_object_style == LabelObjects::Firmware) { - out += std::string("M486 S") + std::to_string(unique_id) + "\n"; - out += std::string("M486 N") + name + "\n"; + if (flavor == GCodeFlavor::gcfMarlinFirmware || flavor == GCodeFlavor::gcfMarlinLegacy || flavor == GCodeFlavor::gcfRepRapFirmware) { + out += std::string("M486 S") + std::to_string(unique_id) + "\n"; + out += std::string("M486 A") + name + "\n"; + } else { + // Not supported by / implemented for the other firmware flavors. + } } return out; } - static std::string label_object_stop(LabelObjects label_object_style, int object_id, int instance_id, const std::string& name) + static std::string label_object_stop(LabelObjects label_object_style, GCodeFlavor flavor, int object_id, int instance_id, const std::string& name) { std::string out; if (label_object_style == LabelObjects::Octoprint) out += std::string("; stop printing object ") + name + " id:" + std::to_string(object_id) + " copy " + std::to_string(instance_id) + "\n"; else if (label_object_style == LabelObjects::Firmware) - out += std::string("M486 S-1\n"); + if (flavor == GCodeFlavor::gcfMarlinFirmware || flavor == GCodeFlavor::gcfMarlinLegacy || flavor == GCodeFlavor::gcfRepRapFirmware) + out += std::string("M486 S-1\n"); + else { + // Not supported by / implemented for the other firmware flavors. + } return out; } - static std::string label_all_objects(LabelObjects label_objects_style, const Print& print) + static std::string label_all_objects(LabelObjects label_objects_style, GCodeFlavor flavor, const Print& print) { std::string out; @@ -145,8 +153,8 @@ namespace Slic3r { out += "\n"; for (size_t object_idx = 0; object_idx < print.objects().size(); ++object_idx) { for (size_t inst_idx = 0; inst_idx < print.objects()[object_idx]->model_object()->instances.size(); ++inst_idx) { - out += label_object_start(label_objects_style, print.objects(), object_idx, inst_idx); - out += label_object_stop(label_objects_style, object_idx, inst_idx, print.objects()[object_idx]->model_object()->name); + out += label_object_start(label_objects_style, flavor, print.objects(), object_idx, inst_idx); + out += label_object_stop(label_objects_style, flavor, object_idx, inst_idx, print.objects()[object_idx]->model_object()->name); } } out += "\n"; @@ -1269,7 +1277,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail file.write(this->preamble()); // Label all objects so printer knows about them since the start. - file.write(label_all_objects(config().gcode_label_objects, print)); + file.write(label_all_objects(config().gcode_label_objects, config().gcode_flavor, print)); print.throw_if_canceled(); @@ -2416,7 +2424,7 @@ void GCodeGenerator::process_layer_single_object( break; ++object_id; } - gcode += label_object_start(config().gcode_label_objects, print_object.print()->objects(), object_id, print_instance.instance_id); + gcode += label_object_start(config().gcode_label_objects, config().gcode_flavor, print_object.print()->objects(), object_id, print_instance.instance_id); } } }; @@ -2595,7 +2603,7 @@ void GCodeGenerator::process_layer_single_object( } } if (! first && config().gcode_label_objects != LabelObjects::Disabled) - gcode += label_object_stop(config().gcode_label_objects, object_id, print_instance.instance_id, print_object.model_object()->name); + gcode += label_object_stop(config().gcode_label_objects, config().gcode_flavor, object_id, print_instance.instance_id, print_object.model_object()->name); } void GCodeGenerator::apply_print_config(const PrintConfig &print_config) From 37dc3378ec1f42db6cc71ae95f3f6e97ccb81ed6 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 6 Sep 2023 09:33:41 +0200 Subject: [PATCH 12/18] Label objects: List of objects moved before custom start gcode, quotes added for M486 A on RRF --- src/libslic3r/GCode.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index da40218779..6bd7a06cf8 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -121,7 +121,8 @@ namespace Slic3r { else if (label_object_style == LabelObjects::Firmware) { if (flavor == GCodeFlavor::gcfMarlinFirmware || flavor == GCodeFlavor::gcfMarlinLegacy || flavor == GCodeFlavor::gcfRepRapFirmware) { out += std::string("M486 S") + std::to_string(unique_id) + "\n"; - out += std::string("M486 A") + name + "\n"; + out += std::string("M486 A"); + out += (flavor == GCodeFlavor::gcfRepRapFirmware ? (std::string("\"") + name + "\"") : name) + "\n"; } else { // Not supported by / implemented for the other firmware flavors. } @@ -1205,6 +1206,9 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail // Emit machine envelope limits for the Marlin firmware. this->print_machine_envelope(file, print); + // Label all objects so printer knows about them since the start. + file.write(label_all_objects(config().gcode_label_objects, config().gcode_flavor, print)); + // Update output variables after the extruders were initialized. m_placeholder_parser_integration.init(m_writer); // Let the start-up script prime the 1st printing tool. @@ -1276,9 +1280,6 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail // Set other general things. file.write(this->preamble()); - // Label all objects so printer knows about them since the start. - file.write(label_all_objects(config().gcode_label_objects, config().gcode_flavor, print)); - print.throw_if_canceled(); // Collect custom seam data from all objects. From 387376fb254fce66ce978bc266be58869a391664 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 6 Sep 2023 16:54:18 +0200 Subject: [PATCH 13/18] LabelObjects: Fixed a typo --- src/libslic3r/PrintConfig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 5e79c97d3e..a53a2d7286 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1503,7 +1503,7 @@ void PrintConfigDef::init_fff_params() def = this->add("gcode_label_objects", coEnum); def->label = L("Label objects"); def->tooltip = L("Selects whether labels should be exported at object boundaries and in what format.\n" - " Octoprint = comments to be consumed by Octoprint CancelObject plugin.\n" + " OctoPrint = comments to be consumed by OctoPrint CancelObject plugin.\n" " Firmware = firmware specific G-code (it will be chosen based on firmware flavor and it can end up to be empty).\n\n" "This settings is NOT compatible with Single Extruder Multi Material setup and Wipe into Object / Wipe into Infill."); From fe3cf27394d3fd07557d2411502ea1b49e7adf1c Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 12 Sep 2023 17:23:27 +0200 Subject: [PATCH 14/18] Label objects: refactoring + fix of object/instance indexing when instances are rotated/out-of-bed --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/GCode.cpp | 75 ++-------------- src/libslic3r/GCode.hpp | 2 + src/libslic3r/GCode/LabelObjects.cpp | 122 +++++++++++++++++++++++++++ src/libslic3r/GCode/LabelObjects.hpp | 49 +++++++++++ src/libslic3r/PrintConfig.cpp | 14 +-- src/libslic3r/PrintConfig.hpp | 6 +- 7 files changed, 192 insertions(+), 78 deletions(-) create mode 100644 src/libslic3r/GCode/LabelObjects.cpp create mode 100644 src/libslic3r/GCode/LabelObjects.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 72f741ae4d..609c666505 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -158,6 +158,8 @@ set(SLIC3R_SOURCES GCode/ExtrusionProcessor.hpp GCode/FindReplace.cpp GCode/FindReplace.hpp + GCode/LabelObjects.cpp + GCode/LabelObjects.hpp GCode/GCodeWriter.cpp GCode/GCodeWriter.hpp GCode/PostProcessor.cpp diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 6bd7a06cf8..f2b4e8480b 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -28,6 +28,7 @@ #include "Exception.hpp" #include "ExtrusionEntity.hpp" #include "Geometry/ConvexHull.hpp" +#include "GCode/LabelObjects.hpp" #include "GCode/PrintExtents.hpp" #include "GCode/Thumbnails.hpp" #include "GCode/WipeTower.hpp" @@ -102,69 +103,6 @@ namespace Slic3r { gcode += '\n'; } - - // Accepts vector of PrintObjectPtrs and an object and instance ids. Returns starting tag for label object function. - static std::string label_object_start(LabelObjects label_object_style, GCodeFlavor flavor, const SpanOfConstPtrs& objects, int object_id, int instance_id) - { - int unique_id = 0; - for (size_t idx = 0; idx < size_t(object_id); ++idx) - unique_id += int(objects[idx]->model_object()->instances.size()); - unique_id += instance_id; - - std::string name = objects[object_id]->model_object()->name; - if (label_object_style == LabelObjects::Firmware && objects[object_id]->model_object()->instances.size() > 1u) - name += " (copy " + std::to_string(instance_id) + ")"; - - std::string out; - if (label_object_style == LabelObjects::Octoprint) - out += std::string("; printing object ") + name + " id:" + std::to_string(object_id) + " copy " + std::to_string(instance_id) + "\n"; - else if (label_object_style == LabelObjects::Firmware) { - if (flavor == GCodeFlavor::gcfMarlinFirmware || flavor == GCodeFlavor::gcfMarlinLegacy || flavor == GCodeFlavor::gcfRepRapFirmware) { - out += std::string("M486 S") + std::to_string(unique_id) + "\n"; - out += std::string("M486 A"); - out += (flavor == GCodeFlavor::gcfRepRapFirmware ? (std::string("\"") + name + "\"") : name) + "\n"; - } else { - // Not supported by / implemented for the other firmware flavors. - } - } - return out; - } - - - static std::string label_object_stop(LabelObjects label_object_style, GCodeFlavor flavor, int object_id, int instance_id, const std::string& name) - { - std::string out; - if (label_object_style == LabelObjects::Octoprint) - out += std::string("; stop printing object ") + name + " id:" + std::to_string(object_id) + " copy " + std::to_string(instance_id) + "\n"; - else if (label_object_style == LabelObjects::Firmware) - if (flavor == GCodeFlavor::gcfMarlinFirmware || flavor == GCodeFlavor::gcfMarlinLegacy || flavor == GCodeFlavor::gcfRepRapFirmware) - out += std::string("M486 S-1\n"); - else { - // Not supported by / implemented for the other firmware flavors. - } - return out; - } - - - static std::string label_all_objects(LabelObjects label_objects_style, GCodeFlavor flavor, const Print& print) - { - std::string out; - - if (label_objects_style != LabelObjects::Disabled) { - out += "\n"; - for (size_t object_idx = 0; object_idx < print.objects().size(); ++object_idx) { - for (size_t inst_idx = 0; inst_idx < print.objects()[object_idx]->model_object()->instances.size(); ++inst_idx) { - out += label_object_start(label_objects_style, flavor, print.objects(), object_idx, inst_idx); - out += label_object_stop(label_objects_style, flavor, object_idx, inst_idx, print.objects()[object_idx]->model_object()->name); - } - } - out += "\n"; - } - return out; - } - - - // Return true if tch_prefix is found in custom_gcode static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std::string& tch_prefix, unsigned next_extruder) { @@ -1207,7 +1145,8 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail this->print_machine_envelope(file, print); // Label all objects so printer knows about them since the start. - file.write(label_all_objects(config().gcode_label_objects, config().gcode_flavor, print)); + m_label_objects.init(print); + file.write(m_label_objects.all_objects_header()); // Update output variables after the extruders were initialized. m_placeholder_parser_integration.init(m_writer); @@ -2419,13 +2358,13 @@ void GCodeGenerator::process_layer_single_object( m_avoid_crossing_perimeters.use_external_mp_once(); m_last_obj_copy = this_object_copy; this->set_origin(unscale(offset)); - if (this->config().gcode_label_objects != LabelObjects::Disabled) { + if (this->config().gcode_label_objects != LabelObjectsStyle::Disabled) { for (const PrintObject* po : print_object.print()->objects()) { if (po == &print_object) break; ++object_id; } - gcode += label_object_start(config().gcode_label_objects, config().gcode_flavor, print_object.print()->objects(), object_id, print_instance.instance_id); + gcode += m_label_objects.start_object(print_instance.print_object.instances()[print_instance.instance_id], GCode::LabelObjects::IncludeName::No); } } }; @@ -2603,8 +2542,8 @@ void GCodeGenerator::process_layer_single_object( } } } - if (! first && config().gcode_label_objects != LabelObjects::Disabled) - gcode += label_object_stop(config().gcode_label_objects, config().gcode_flavor, object_id, print_instance.instance_id, print_object.model_object()->name); + if (! first) + gcode += m_label_objects.stop_object(print_instance.print_object.instances()[print_instance.instance_id]); } void GCodeGenerator::apply_print_config(const PrintConfig &print_config) diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index ae1cc333b1..d25113b0d2 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -28,6 +28,7 @@ #include "GCode/CoolingBuffer.hpp" #include "GCode/FindReplace.hpp" #include "GCode/GCodeWriter.hpp" +#include "GCode/LabelObjects.hpp" #include "GCode/PressureEqualizer.hpp" #include "GCode/RetractWhenCrossingPerimeters.hpp" #include "GCode/SmoothPath.hpp" @@ -353,6 +354,7 @@ private: OozePrevention m_ooze_prevention; GCode::Wipe m_wipe; + GCode::LabelObjects m_label_objects; AvoidCrossingPerimeters m_avoid_crossing_perimeters; JPSPathFinder m_avoid_crossing_curled_overhangs; RetractWhenCrossingPerimeters m_retract_when_crossing_perimeters; diff --git a/src/libslic3r/GCode/LabelObjects.cpp b/src/libslic3r/GCode/LabelObjects.cpp new file mode 100644 index 0000000000..dd4378d4fa --- /dev/null +++ b/src/libslic3r/GCode/LabelObjects.cpp @@ -0,0 +1,122 @@ +#include "LabelObjects.hpp" + + +namespace Slic3r::GCode { + + + +void LabelObjects::init(const Print& print) +{ + m_label_objects_style = print.config().gcode_label_objects; + m_flavor = print.config().gcode_flavor; + + if (m_label_objects_style == LabelObjectsStyle::Disabled) + return; + + std::map> model_object_to_print_instances; + + // Iterate over all PrintObjects and their PrintInstances, collect PrintInstances which + // belong to the same ModelObject. + for (const PrintObject* po : print.objects()) + for (const PrintInstance& pi : po->instances()) + model_object_to_print_instances[pi.model_instance->get_object()].emplace_back(&pi); + + // Now go through the map, assign a unique_id to each of the PrintInstances and get the indices of the + // respective ModelObject and ModelInstance so we can use them in the tags. This will maintain + // indices even in case that some instances are rotated (those end up in different PrintObjects) + // or when some are out of bed (these ModelInstances have no corresponding PrintInstances). + int unique_id = 0; + for (const auto& [model_object, print_instances] : model_object_to_print_instances) { + const ModelObjectPtrs& model_objects = model_object->get_model()->objects; + int object_id = int(std::find(model_objects.begin(), model_objects.end(), model_object) - model_objects.begin()); + for (const PrintInstance* const pi : print_instances) { + int instance_id = int(std::find(model_object->instances.begin(), model_object->instances.end(), pi->model_instance) - model_object->instances.begin()); + if (m_label_objects_style != LabelObjectsStyle::Octoprint) { + // OctoPrint comments have always indexed instances from 0, let's keep it that way. + // In the other cases, we will use one-based indexing so the indices match with the one in PrusaSlicer. + ++instance_id; + } + m_label_data.emplace(pi, LabelData{model_object->name, unique_id, object_id, instance_id, print_instances.size() > 1}); + ++unique_id; + } + } +} + + + +std::string LabelObjects::all_objects_header() const +{ + if (m_label_objects_style == LabelObjectsStyle::Disabled) + return std::string(); + + std::string out; + + // Let's sort the values according to unique_id so they are in the same order in which they were added. + std::vector> label_data_sorted; + for (const auto& pi_and_label : m_label_data) + label_data_sorted.emplace_back(pi_and_label); + std::sort(label_data_sorted.begin(), label_data_sorted.end(), [](const auto& ld1, const auto& ld2) { return ld1.second.unique_id < ld2.second.unique_id; }); + + out += "\n"; + for (const auto& [print_instance, label] : label_data_sorted) { + out += start_object(*print_instance, IncludeName::Yes); + out += stop_object(*print_instance); + } + out += "\n"; + return out; +} + + + +std::string LabelObjects::start_object(const PrintInstance& print_instance, IncludeName include_name) const +{ + if (m_label_objects_style == LabelObjectsStyle::Disabled) + return std::string(); + + const LabelData& label = m_label_data.at(&print_instance); + + std::string name = label.name; + if (m_label_objects_style == LabelObjectsStyle::Firmware && label.object_has_more_instances) + name += " (copy " + std::to_string(label.instance_id) + ")"; + + std::string out; + if (m_label_objects_style == LabelObjectsStyle::Octoprint) + out += std::string("; printing object ") + name + " id:" + std::to_string(label.object_id) + " copy " + std::to_string(label.instance_id) + "\n"; + else if (m_label_objects_style == LabelObjectsStyle::Firmware) { + if (m_flavor == GCodeFlavor::gcfMarlinFirmware || m_flavor == GCodeFlavor::gcfMarlinLegacy || m_flavor == GCodeFlavor::gcfRepRapFirmware) { + out += std::string("M486 S") + std::to_string(label.unique_id) + "\n"; + if (include_name == IncludeName::Yes) { + out += std::string("M486 A"); + out += (m_flavor == GCodeFlavor::gcfRepRapFirmware ? (std::string("\"") + name + "\"") : name) + "\n"; + } + } else { + // Not supported by / implemented for the other firmware flavors. + } + } + return out; +} + + + +std::string LabelObjects::stop_object(const PrintInstance& print_instance) const +{ + if (m_label_objects_style == LabelObjectsStyle::Disabled) + return std::string(); + + const LabelData& label = m_label_data.at(&print_instance); + + std::string out; + if (m_label_objects_style == LabelObjectsStyle::Octoprint) + out += std::string("; stop printing object ") + label.name + " id:" + std::to_string(label.object_id) + " copy " + std::to_string(label.instance_id) + "\n"; + else if (m_label_objects_style == LabelObjectsStyle::Firmware) + if (m_flavor == GCodeFlavor::gcfMarlinFirmware || m_flavor == GCodeFlavor::gcfMarlinLegacy || m_flavor == GCodeFlavor::gcfRepRapFirmware) + out += std::string("M486 S-1\n"); + else { + // Not supported by / implemented for the other firmware flavors. + } + return out; +} + + + +} // namespace Slic3r::GCode diff --git a/src/libslic3r/GCode/LabelObjects.hpp b/src/libslic3r/GCode/LabelObjects.hpp new file mode 100644 index 0000000000..b19b46de96 --- /dev/null +++ b/src/libslic3r/GCode/LabelObjects.hpp @@ -0,0 +1,49 @@ +#ifndef slic3r_GCode_LabelObjects_hpp_ +#define slic3r_GCode_LabelObjects_hpp_ + +#include "Print.hpp" + +namespace Slic3r { + +enum GCodeFlavor : unsigned char; +enum class LabelObjectsStyle; + + +namespace GCode { + + //std::string label_object_start(LabelObjectsStyle label_object_style, GCodeFlavor flavor, const SpanOfConstPtrs& objects, int object_id, int instance_id); + //std::string label_object_stop(LabelObjectsStyle label_object_style, GCodeFlavor flavor, int object_id, int instance_id, const std::string& name); + //std::string label_all_objects(LabelObjectsStyle label_objects_style, GCodeFlavor flavor, const Print& print); + + +class LabelObjects { +public: + enum class IncludeName { + No, + Yes + }; + void init(const Print& print); + std::string all_objects_header() const; + std::string start_object(const PrintInstance& print_instance, IncludeName include_name) const; + std::string stop_object(const PrintInstance& print_instance) const; + +private: + struct LabelData { + std::string name; + int unique_id; + int object_id; + int instance_id; + bool object_has_more_instances; + }; + + LabelObjectsStyle m_label_objects_style; + GCodeFlavor m_flavor; + std::unordered_map m_label_data; + +}; + + +} // namespace GCode +} // namespace Slic3r + +#endif // slic3r_GCode_LabelObjects_hpp_ diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index a53a2d7286..38767ea62e 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -229,12 +229,12 @@ static const t_config_enum_values s_keys_map_DraftShield = { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(DraftShield) -static const t_config_enum_values s_keys_map_LabelObjects = { - { "disabled", int(LabelObjects::Disabled) }, - { "octoprint", int(LabelObjects::Octoprint) }, - { "firmware", int(LabelObjects::Firmware) } +static const t_config_enum_values s_keys_map_LabelObjectsStyle = { + { "disabled", int(LabelObjectsStyle::Disabled) }, + { "octoprint", int(LabelObjectsStyle::Octoprint) }, + { "firmware", int(LabelObjectsStyle::Firmware) } }; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(LabelObjects) +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(LabelObjectsStyle) static const t_config_enum_values s_keys_map_GCodeThumbnailsFormat = { { "PNG", int(GCodeThumbnailsFormat::PNG) }, @@ -1507,13 +1507,13 @@ void PrintConfigDef::init_fff_params() " Firmware = firmware specific G-code (it will be chosen based on firmware flavor and it can end up to be empty).\n\n" "This settings is NOT compatible with Single Extruder Multi Material setup and Wipe into Object / Wipe into Infill."); - def->set_enum({ + def->set_enum({ { "disabled", L("Disabled") }, { "octoprint", L("OctoPrint comments") }, { "firmware", L("Firmware-specific") } }); def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum(LabelObjects::Disabled)); + def->set_default_value(new ConfigOptionEnum(LabelObjectsStyle::Disabled)); def = this->add("gcode_substitutions", coStrings); def->label = L("G-code substitutions"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index d3fd0ff429..63ffcde0e3 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -147,7 +147,7 @@ enum DraftShield { dsDisabled, dsLimited, dsEnabled }; -enum class LabelObjects { +enum class LabelObjectsStyle { Disabled, Octoprint, Firmware }; @@ -187,7 +187,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLAPillarConnectionMode) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLASupportTreeType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BrimType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(DraftShield) -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(LabelObjects) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(LabelObjectsStyle) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeThumbnailsFormat) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType) @@ -734,7 +734,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloats, filament_multitool_ramming_flow)) ((ConfigOptionBool, gcode_comments)) ((ConfigOptionEnum, gcode_flavor)) - ((ConfigOptionEnum, gcode_label_objects)) + ((ConfigOptionEnum, gcode_label_objects)) // Triples of strings: "search pattern", "replace with pattern", "attribs" // where "attribs" are one of: // r - regular expression From 6016c6b021af08c1901bd153128af90d0d86b280 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 13 Sep 2023 12:32:36 +0200 Subject: [PATCH 15/18] Label objects: more refactoring (name composed only once) --- src/libslic3r/GCode/LabelObjects.cpp | 36 +++++++++++++++++----------- src/libslic3r/GCode/LabelObjects.hpp | 7 ------ 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/libslic3r/GCode/LabelObjects.cpp b/src/libslic3r/GCode/LabelObjects.cpp index dd4378d4fa..ee57ce9a21 100644 --- a/src/libslic3r/GCode/LabelObjects.cpp +++ b/src/libslic3r/GCode/LabelObjects.cpp @@ -30,14 +30,26 @@ void LabelObjects::init(const Print& print) const ModelObjectPtrs& model_objects = model_object->get_model()->objects; int object_id = int(std::find(model_objects.begin(), model_objects.end(), model_object) - model_objects.begin()); for (const PrintInstance* const pi : print_instances) { + bool object_has_more_instances = print_instances.size() > 1u; int instance_id = int(std::find(model_object->instances.begin(), model_object->instances.end(), pi->model_instance) - model_object->instances.begin()); - if (m_label_objects_style != LabelObjectsStyle::Octoprint) { - // OctoPrint comments have always indexed instances from 0, let's keep it that way. - // In the other cases, we will use one-based indexing so the indices match with the one in PrusaSlicer. - ++instance_id; + + // Now compose the name of the object and define whether indexing is 0 or 1-based. + std::string name = model_object->name; + if (m_label_objects_style == LabelObjectsStyle::Octoprint) { + // use zero-based indexing for objects and instances, as we always have done + name += " id:" + std::to_string(object_id) + " copy " + std::to_string(instance_id); } - m_label_data.emplace(pi, LabelData{model_object->name, unique_id, object_id, instance_id, print_instances.size() > 1}); - ++unique_id; + else if (m_label_objects_style == LabelObjectsStyle::Firmware) { + // use one-based indexing for objects and instances so indices match what we see in PrusaSlicer. + ++object_id; + ++instance_id; + + if (object_has_more_instances) + name += " (Instance " + std::to_string(instance_id) + ")"; + } + + m_label_data.emplace(pi, LabelData{name, unique_id}); + ++unique_id; } } } @@ -63,7 +75,7 @@ std::string LabelObjects::all_objects_header() const out += stop_object(*print_instance); } out += "\n"; - return out; + return out; } @@ -75,19 +87,15 @@ std::string LabelObjects::start_object(const PrintInstance& print_instance, Incl const LabelData& label = m_label_data.at(&print_instance); - std::string name = label.name; - if (m_label_objects_style == LabelObjectsStyle::Firmware && label.object_has_more_instances) - name += " (copy " + std::to_string(label.instance_id) + ")"; - std::string out; if (m_label_objects_style == LabelObjectsStyle::Octoprint) - out += std::string("; printing object ") + name + " id:" + std::to_string(label.object_id) + " copy " + std::to_string(label.instance_id) + "\n"; + out += std::string("; printing object ") + label.name + "\n"; else if (m_label_objects_style == LabelObjectsStyle::Firmware) { if (m_flavor == GCodeFlavor::gcfMarlinFirmware || m_flavor == GCodeFlavor::gcfMarlinLegacy || m_flavor == GCodeFlavor::gcfRepRapFirmware) { out += std::string("M486 S") + std::to_string(label.unique_id) + "\n"; if (include_name == IncludeName::Yes) { out += std::string("M486 A"); - out += (m_flavor == GCodeFlavor::gcfRepRapFirmware ? (std::string("\"") + name + "\"") : name) + "\n"; + out += (m_flavor == GCodeFlavor::gcfRepRapFirmware ? (std::string("\"") + label.name + "\"") : label.name) + "\n"; } } else { // Not supported by / implemented for the other firmware flavors. @@ -107,7 +115,7 @@ std::string LabelObjects::stop_object(const PrintInstance& print_instance) const std::string out; if (m_label_objects_style == LabelObjectsStyle::Octoprint) - out += std::string("; stop printing object ") + label.name + " id:" + std::to_string(label.object_id) + " copy " + std::to_string(label.instance_id) + "\n"; + out += std::string("; stop printing object ") + label.name + "\n"; else if (m_label_objects_style == LabelObjectsStyle::Firmware) if (m_flavor == GCodeFlavor::gcfMarlinFirmware || m_flavor == GCodeFlavor::gcfMarlinLegacy || m_flavor == GCodeFlavor::gcfRepRapFirmware) out += std::string("M486 S-1\n"); diff --git a/src/libslic3r/GCode/LabelObjects.hpp b/src/libslic3r/GCode/LabelObjects.hpp index b19b46de96..a62090530d 100644 --- a/src/libslic3r/GCode/LabelObjects.hpp +++ b/src/libslic3r/GCode/LabelObjects.hpp @@ -11,10 +11,6 @@ enum class LabelObjectsStyle; namespace GCode { - //std::string label_object_start(LabelObjectsStyle label_object_style, GCodeFlavor flavor, const SpanOfConstPtrs& objects, int object_id, int instance_id); - //std::string label_object_stop(LabelObjectsStyle label_object_style, GCodeFlavor flavor, int object_id, int instance_id, const std::string& name); - //std::string label_all_objects(LabelObjectsStyle label_objects_style, GCodeFlavor flavor, const Print& print); - class LabelObjects { public: @@ -31,9 +27,6 @@ private: struct LabelData { std::string name; int unique_id; - int object_id; - int instance_id; - bool object_has_more_instances; }; LabelObjectsStyle m_label_objects_style; From e659e96b0447a07ce3f0c851ddff024cc5129585 Mon Sep 17 00:00:00 2001 From: Justin Schuh Date: Fri, 19 May 2023 08:56:48 -0700 Subject: [PATCH 16/18] Support Klipper exclude_objects feature (PR #10618): comment by @lukasmatena: The functions are not called anywhere because of how I resolved conflicts. I will integrate the functions into current PrusaSlicer in the next commit. --- src/libslic3r/GCode.cpp | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index f2b4e8480b..bea09c5833 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -130,6 +130,43 @@ namespace Slic3r { return ok; } + inline std::string make_klipper_exclude_object_name(const std::string &name, int object_id, int instance_id) + { + constexpr char banned_chars[] = "-. \r\n\v\t\f"; + std::string cleaned_name = name; + const char *next = cleaned_name.c_str(); + while (next = std::strpbrk(next, banned_chars)) + cleaned_name[next - cleaned_name.c_str()] = '_'; + return cleaned_name + "_id_" + std::to_string(object_id) + "_copy_" + std::to_string(instance_id); + } + + // Label excluded objects for Klipper + std::string make_klipper_exclude_object_header(const Print &print) { + std::string output; + int object_id = 0; + for (auto object : print.objects()) { + int instance_id = 0; + for (auto instance : object->instances()) { + char buffer[64]; + output += "EXCLUDE_OBJECT_DEFINE NAME="; + output += make_klipper_exclude_object_name(object->model_object()->name, object_id, instance_id++); + Polygon outline = object->model_object()->convex_hull_2d(instance.model_instance->get_matrix()); + outline.douglas_peucker(50000.f); + auto center = outline.centroid(); + std::snprintf(buffer, sizeof(buffer) - 1, " CENTER=%.3f,%.3f", unscale(center[0]), unscale(center[1])); + output += buffer + std::string(" POLYGON=["); + for (auto point : outline) { + std::snprintf(buffer, sizeof(buffer) - 1, "[%.3f,%.3f],", unscale(point[0]), unscale(point[1])); + output += buffer; + } + output.pop_back(); + output += "]\n"; + } + ++object_id; + } + return output; + } + std::string OozePrevention::pre_toolchange(GCodeGenerator &gcodegen) { std::string gcode; From 501cfb64f9319ce9a728a679c124f67422874b98 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 13 Sep 2023 13:43:46 +0200 Subject: [PATCH 17/18] Label objects: integrating the recently merged PR: - code from @jschuh's functions moved into respective functions in LabelObjects class - name no longer contains object_id, instance_id is newly 1-based - replacing banned characters using std::replace_if --- src/libslic3r/GCode.cpp | 37 ---------------------------- src/libslic3r/GCode/LabelObjects.cpp | 31 ++++++++++++++++++++--- 2 files changed, 28 insertions(+), 40 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index bea09c5833..f2b4e8480b 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -130,43 +130,6 @@ namespace Slic3r { return ok; } - inline std::string make_klipper_exclude_object_name(const std::string &name, int object_id, int instance_id) - { - constexpr char banned_chars[] = "-. \r\n\v\t\f"; - std::string cleaned_name = name; - const char *next = cleaned_name.c_str(); - while (next = std::strpbrk(next, banned_chars)) - cleaned_name[next - cleaned_name.c_str()] = '_'; - return cleaned_name + "_id_" + std::to_string(object_id) + "_copy_" + std::to_string(instance_id); - } - - // Label excluded objects for Klipper - std::string make_klipper_exclude_object_header(const Print &print) { - std::string output; - int object_id = 0; - for (auto object : print.objects()) { - int instance_id = 0; - for (auto instance : object->instances()) { - char buffer[64]; - output += "EXCLUDE_OBJECT_DEFINE NAME="; - output += make_klipper_exclude_object_name(object->model_object()->name, object_id, instance_id++); - Polygon outline = object->model_object()->convex_hull_2d(instance.model_instance->get_matrix()); - outline.douglas_peucker(50000.f); - auto center = outline.centroid(); - std::snprintf(buffer, sizeof(buffer) - 1, " CENTER=%.3f,%.3f", unscale(center[0]), unscale(center[1])); - output += buffer + std::string(" POLYGON=["); - for (auto point : outline) { - std::snprintf(buffer, sizeof(buffer) - 1, "[%.3f,%.3f],", unscale(point[0]), unscale(point[1])); - output += buffer; - } - output.pop_back(); - output += "]\n"; - } - ++object_id; - } - return output; - } - std::string OozePrevention::pre_toolchange(GCodeGenerator &gcodegen) { std::string gcode; diff --git a/src/libslic3r/GCode/LabelObjects.cpp b/src/libslic3r/GCode/LabelObjects.cpp index ee57ce9a21..2104f77e52 100644 --- a/src/libslic3r/GCode/LabelObjects.cpp +++ b/src/libslic3r/GCode/LabelObjects.cpp @@ -46,6 +46,10 @@ void LabelObjects::init(const Print& print) if (object_has_more_instances) name += " (Instance " + std::to_string(instance_id) + ")"; + if (m_flavor == gcfKlipper) { + const std::string banned = "-. \r\n\v\t\f"; + std::replace_if(name.begin(), name.end(), [&banned](char c) { return banned.find(c) != std::string::npos; }, '_'); + } } m_label_data.emplace(pi, LabelData{name, unique_id}); @@ -71,8 +75,25 @@ std::string LabelObjects::all_objects_header() const out += "\n"; for (const auto& [print_instance, label] : label_data_sorted) { - out += start_object(*print_instance, IncludeName::Yes); - out += stop_object(*print_instance); + if (m_flavor == gcfKlipper) { + char buffer[64]; + out += "EXCLUDE_OBJECT_DEFINE NAME=" + label.name; + Polygon outline = print_instance->model_instance->get_object()->convex_hull_2d(print_instance->model_instance->get_matrix()); + assert(! outline.empty()); + outline.douglas_peucker(50000.f); + Point center = outline.centroid(); + std::snprintf(buffer, sizeof(buffer) - 1, " CENTER=%.3f,%.3f", unscale(center[0]), unscale(center[1])); + out += buffer + std::string(" POLYGON=["); + for (const Point& point : outline) { + std::snprintf(buffer, sizeof(buffer) - 1, "[%.3f,%.3f],", unscale(point[0]), unscale(point[1])); + out += buffer; + } + out.pop_back(); + out += "]\n"; + } else { + out += start_object(*print_instance, IncludeName::Yes); + out += stop_object(*print_instance); + } } out += "\n"; return out; @@ -97,7 +118,9 @@ std::string LabelObjects::start_object(const PrintInstance& print_instance, Incl out += std::string("M486 A"); out += (m_flavor == GCodeFlavor::gcfRepRapFirmware ? (std::string("\"") + label.name + "\"") : label.name) + "\n"; } - } else { + } else if (m_flavor == gcfKlipper) + out += "EXCLUDE_OBJECT_START NAME=" + label.name + "\n"; + else { // Not supported by / implemented for the other firmware flavors. } } @@ -119,6 +142,8 @@ std::string LabelObjects::stop_object(const PrintInstance& print_instance) const else if (m_label_objects_style == LabelObjectsStyle::Firmware) if (m_flavor == GCodeFlavor::gcfMarlinFirmware || m_flavor == GCodeFlavor::gcfMarlinLegacy || m_flavor == GCodeFlavor::gcfRepRapFirmware) out += std::string("M486 S-1\n"); + else if (m_flavor ==gcfKlipper) + out += "EXCLUDE_OBJECT_END NAME=" + label.name + "\n"; else { // Not supported by / implemented for the other firmware flavors. } From f599ddca8f3407460b2c2b6ccf1030c2a2ba9e22 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 13 Sep 2023 16:11:13 +0200 Subject: [PATCH 18/18] Label objects: use non-convex projection if it only contains one (Ex)Polygon, remove unused code, tidy includes --- src/libslic3r/GCode.cpp | 12 ++------- src/libslic3r/GCode/LabelObjects.cpp | 39 ++++++++++++++++++++++++++-- src/libslic3r/GCode/LabelObjects.hpp | 4 +-- 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index f2b4e8480b..dd62029769 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2340,9 +2340,8 @@ void GCodeGenerator::process_layer_single_object( const bool print_wipe_extrusions) { bool first = true; - int object_id = 0; // Delay layer initialization as many layers may not print with all extruders. - auto init_layer_delayed = [this, &print_instance, &layer_to_print, &first, &object_id, &gcode]() { + auto init_layer_delayed = [this, &print_instance, &layer_to_print, &first, &gcode]() { if (first) { first = false; const PrintObject &print_object = print_instance.print_object; @@ -2358,14 +2357,7 @@ void GCodeGenerator::process_layer_single_object( m_avoid_crossing_perimeters.use_external_mp_once(); m_last_obj_copy = this_object_copy; this->set_origin(unscale(offset)); - if (this->config().gcode_label_objects != LabelObjectsStyle::Disabled) { - for (const PrintObject* po : print_object.print()->objects()) { - if (po == &print_object) - break; - ++object_id; - } - gcode += m_label_objects.start_object(print_instance.print_object.instances()[print_instance.instance_id], GCode::LabelObjects::IncludeName::No); - } + gcode += m_label_objects.start_object(print_instance.print_object.instances()[print_instance.instance_id], GCode::LabelObjects::IncludeName::No); } }; diff --git a/src/libslic3r/GCode/LabelObjects.cpp b/src/libslic3r/GCode/LabelObjects.cpp index 2104f77e52..4c32122ad2 100644 --- a/src/libslic3r/GCode/LabelObjects.cpp +++ b/src/libslic3r/GCode/LabelObjects.cpp @@ -1,9 +1,43 @@ #include "LabelObjects.hpp" +#include "ClipperUtils.hpp" +#include "Model.hpp" +#include "Print.hpp" +#include "TriangleMeshSlicer.hpp" + namespace Slic3r::GCode { +namespace { + +Polygon instance_outline(const PrintInstance* pi) +{ + ExPolygons outline; + const ModelObject* mo = pi->model_instance->get_object(); + const ModelInstance* mi = pi->model_instance; + for (const ModelVolume *v : mo->volumes) { + Polygons vol_outline; + vol_outline = project_mesh(v->mesh().its, + mi->get_matrix() * v->get_matrix(), + [] {}); + switch (v->type()) { + case ModelVolumeType::MODEL_PART: outline = union_ex(outline, vol_outline); break; + case ModelVolumeType::NEGATIVE_VOLUME: outline = diff_ex(outline, vol_outline); break; + default:; + } + } + + // The projection may contain multiple polygons, which is not supported by Klipper. + // When that happens, calculate and use a 2d convex hull instead. + if (outline.size() == 1u) + return outline.front().contour; + else + return pi->model_instance->get_object()->convex_hull_2d(pi->model_instance->get_matrix()); +} + +}; // anonymous namespace + void LabelObjects::init(const Print& print) { @@ -78,7 +112,7 @@ std::string LabelObjects::all_objects_header() const if (m_flavor == gcfKlipper) { char buffer[64]; out += "EXCLUDE_OBJECT_DEFINE NAME=" + label.name; - Polygon outline = print_instance->model_instance->get_object()->convex_hull_2d(print_instance->model_instance->get_matrix()); + Polygon outline = instance_outline(print_instance); assert(! outline.empty()); outline.douglas_peucker(50000.f); Point center = outline.centroid(); @@ -139,7 +173,7 @@ std::string LabelObjects::stop_object(const PrintInstance& print_instance) const std::string out; if (m_label_objects_style == LabelObjectsStyle::Octoprint) out += std::string("; stop printing object ") + label.name + "\n"; - else if (m_label_objects_style == LabelObjectsStyle::Firmware) + else if (m_label_objects_style == LabelObjectsStyle::Firmware) { if (m_flavor == GCodeFlavor::gcfMarlinFirmware || m_flavor == GCodeFlavor::gcfMarlinLegacy || m_flavor == GCodeFlavor::gcfRepRapFirmware) out += std::string("M486 S-1\n"); else if (m_flavor ==gcfKlipper) @@ -147,6 +181,7 @@ std::string LabelObjects::stop_object(const PrintInstance& print_instance) const else { // Not supported by / implemented for the other firmware flavors. } + } return out; } diff --git a/src/libslic3r/GCode/LabelObjects.hpp b/src/libslic3r/GCode/LabelObjects.hpp index a62090530d..78add73009 100644 --- a/src/libslic3r/GCode/LabelObjects.hpp +++ b/src/libslic3r/GCode/LabelObjects.hpp @@ -1,12 +1,12 @@ #ifndef slic3r_GCode_LabelObjects_hpp_ #define slic3r_GCode_LabelObjects_hpp_ -#include "Print.hpp" - namespace Slic3r { enum GCodeFlavor : unsigned char; enum class LabelObjectsStyle; +struct PrintInstance; +class Print; namespace GCode {