diff --git a/CMakeLists.txt b/CMakeLists.txt index d99b58a178..daf6022fbb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -189,6 +189,7 @@ target_compile_definitions(_CuraEngine $<$,$>:ENABLE_REMOTE_PLUGINS> $<$:OLDER_APPLE_CLANG> CURA_ENGINE_VERSION=\"${CURA_ENGINE_VERSION}\" + CURA_ENGINE_HASH=\"${CURA_ENGINE_HASH}\" $<$:BUILD_TESTS> PRIVATE $<$:NOMINMAX> diff --git a/conandata.yml b/conandata.yml index 909c921748..a44c2c758b 100644 --- a/conandata.yml +++ b/conandata.yml @@ -1,4 +1,5 @@ -version: "5.9.0-alpha.0" +version: "5.9.0-beta.1" +commit: "unknown" requirements: - "scripta/0.1.0@ultimaker/testing" requirements_arcus: @@ -6,4 +7,4 @@ requirements_arcus: requirements_plugins: - "curaengine_grpc_definitions/0.2.1" requirements_cura_resources: - - "cura_resources/(latest)@ultimaker/testing" + - "cura_resources/5.9.0-beta.1" diff --git a/conanfile.py b/conanfile.py index d40e9c70c4..9f2b96c9c7 100644 --- a/conanfile.py +++ b/conanfile.py @@ -140,6 +140,7 @@ def generate(self): tc = CMakeToolchain(self) tc.variables["CURA_ENGINE_VERSION"] = self.version + tc.variables["CURA_ENGINE_HASH"] = self.conan_data["commit"] tc.variables["ENABLE_ARCUS"] = self.options.enable_arcus tc.variables["ENABLE_TESTING"] = not self.conf.get("tools.build:skip_test", False, check_type=bool) tc.variables["ENABLE_BENCHMARKS"] = self.options.enable_benchmarks diff --git a/include/InsetOrderOptimizer.h b/include/InsetOrderOptimizer.h index afba5dc452..80c0856f3d 100644 --- a/include/InsetOrderOptimizer.h +++ b/include/InsetOrderOptimizer.h @@ -59,7 +59,8 @@ class InsetOrderOptimizer const Point2LL& model_center_point, const Shape& disallowed_areas_for_seams = {}, const bool scarf_seam = false, - const bool smooth_speed = false); + const bool smooth_speed = false, + const Shape& overhang_areas = Shape()); /*! * Adds the insets to the given layer plan. @@ -114,6 +115,7 @@ class InsetOrderOptimizer Shape disallowed_areas_for_seams_; const bool scarf_seam_; const bool smooth_speed_; + Shape overhang_areas_; std::vector> inset_polys_; // vector of vectors holding the inset polygons Shape retraction_region_; // After printing an outer wall, move into this region so that retractions do not leave visible blobs. Calculated lazily if needed (see @@ -128,7 +130,7 @@ class InsetOrderOptimizer * * \param closed_line The polygon to insert the seam point in. (It's assumed to be closed at least.) * - * \return The index of the inserted seam point, or std::nullopt if no seam point was inserted. + * \return The index of the inserted seam point, or the index of the closest point if an existing one can be used. */ std::optional insertSeamPoint(ExtrusionLine& closed_line); diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 0b5f632489..0be204faf2 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -298,6 +298,11 @@ class LayerPlan : public NoCopy */ void setSeamOverhangMask(const Shape& polys); + /*! + * Get the seam overhang mask, which contains the areas where we don't want to place the seam because they are overhanding + */ + const Shape& getSeamOverhangMask() const; + /*! * Set roofing_mask. * @@ -677,50 +682,6 @@ class LayerPlan : public NoCopy const bool is_top_layer, const bool is_bottom_layer); - - /*! - * Given a wall polygon and a start vertex index, return the index of the first vertex that is supported (is not above air) - * - * Uses bridge_wall_mask and overhang_mask to determine where there is air below - * - * \param wall The wall polygon - * \param start_idx The index of the starting vertex of \p wall - * \return The index of the first supported vertex - if no vertices are supported, start_idx is returned - */ - template - size_t locateFirstSupportedVertex(const T& wall, const size_t start_idx) const - { - if (bridge_wall_mask_.empty() && seam_overhang_mask_.empty()) - { - return start_idx; - } - - const auto air_below = bridge_wall_mask_.unionPolygons(seam_overhang_mask_); - - size_t curr_idx = start_idx; - - while (true) - { - const Point2LL& vertex = cura::make_point(wall[curr_idx]); - if (! air_below.inside(vertex, true)) - { - // vertex isn't above air so it's OK to use - return curr_idx; - } - - if (++curr_idx >= wall.size()) - { - curr_idx = 0; - } - - if (curr_idx == start_idx) - { - // no vertices are supported so just return the original index - return start_idx; - } - } - } - /*! * Write the planned paths to gcode * @@ -942,6 +903,13 @@ class LayerPlan : public NoCopy * \return The distance from the start of the current wall line to the first bridge segment */ coord_t computeDistanceToBridgeStart(const ExtrusionLine& wall, const size_t current_index, const coord_t min_bridge_line_len) const; + + /*! + * \brief Calculates whether the given segment is to be treated as overhanging + * \param p0 The start point of the segment + * \param p1 The end point of the segment + */ + bool segmentIsOnOverhang(const Point3LL& p0, const Point3LL& p1) const; }; } // namespace cura diff --git a/include/PathOrderOptimizer.h b/include/PathOrderOptimizer.h index be8e61873b..d7ad78eea8 100644 --- a/include/PathOrderOptimizer.h +++ b/include/PathOrderOptimizer.h @@ -21,7 +21,9 @@ #include "path_ordering.h" #include "settings/EnumSettings.h" //To get the seam settings. #include "settings/ZSeamConfig.h" //To read the seam configuration. +#include "utils/Score.h" #include "utils/linearAlg2D.h" //To find the angle of corners to hide seams. +#include "utils/math.h" #include "utils/polygonUtils.h" #include "utils/views/dfs.h" @@ -113,7 +115,9 @@ class PathOrderOptimizer const bool reverse_direction = false, const std::unordered_multimap& order_requirements = no_order_requirements_, const bool group_outer_walls = false, - const Shape& disallowed_areas_for_seams = {}) + const Shape& disallowed_areas_for_seams = {}, + const bool use_shortest_for_inner_walls = false, + const Shape& overhang_areas = Shape()) : start_point_(start_point) , seam_config_(seam_config) , combing_boundary_((combing_boundary != nullptr && ! combing_boundary->empty()) ? combing_boundary : nullptr) @@ -122,7 +126,8 @@ class PathOrderOptimizer , _group_outer_walls(group_outer_walls) , order_requirements_(&order_requirements) , disallowed_area_for_seams{ disallowed_areas_for_seams } - + , use_shortest_for_inner_walls_(use_shortest_for_inner_walls) + , overhang_areas_(overhang_areas) { } @@ -130,11 +135,12 @@ class PathOrderOptimizer * Add a new polygon to be optimized. * \param polygon The polygon to optimize. */ - void addPolygon(const Path& polygon, std::optional force_start_index = std::nullopt) + void addPolygon(const Path& polygon, std::optional force_start_index = std::nullopt, const bool is_outer_wall = false) { constexpr bool is_closed = true; paths_.emplace_back(polygon, is_closed); paths_.back().force_start_index_ = force_start_index; + paths_.back().is_outer_wall = is_outer_wall; } /*! @@ -180,6 +186,20 @@ class PathOrderOptimizer } } + // Set actual used start point calculation strategy for each path + for (auto& path : paths_) + { + if (use_shortest_for_inner_walls_ && ! path.is_outer_wall) + { + path.seam_config_ = ZSeamConfig(EZSeamType::SHORTEST); + path.force_start_index_ = std::nullopt; + } + else + { + path.seam_config_ = seam_config_; + } + } + // Add all vertices to a bucket grid so that we can find nearby endpoints quickly. const coord_t snap_radius = 10_mu; // 0.01mm grid cells. Chaining only needs to consider polylines which are next to each other. SparsePointGridInclusive line_bucket_grid(snap_radius); @@ -205,16 +225,19 @@ class PathOrderOptimizer // For some Z seam types the start position can be pre-computed. // This is faster since we don't need to re-compute the start position at each step then. - precompute_start &= seam_config_.type_ == EZSeamType::RANDOM || seam_config_.type_ == EZSeamType::USER_SPECIFIED || seam_config_.type_ == EZSeamType::SHARPEST_CORNER; if (precompute_start) { for (auto& path : paths_) { - if (! path.is_closed_ || path.converted_->empty()) + if (path.seam_config_.type_ == EZSeamType::RANDOM || path.seam_config_.type_ == EZSeamType::USER_SPECIFIED + || path.seam_config_.type_ == EZSeamType::SHARPEST_CORNER) { - continue; // Can't pre-compute the seam for open polylines since they're at the endpoint nearest to the current position. + if (! path.is_closed_ || path.converted_->empty()) + { + continue; // Can't pre-compute the seam for open polylines since they're at the endpoint nearest to the current position. + } + path.start_vertex_ = findStartLocation(path, path.seam_config_.pos_); } - path.start_vertex_ = findStartLocation(path, seam_config_.pos_); } } @@ -298,6 +321,17 @@ class PathOrderOptimizer */ const std::unordered_multimap* order_requirements_; + /*! + * If true, we will compute the seam position of inner walls using a "shortest" seam configs, for inner walls that + * are directly following an outer wall. + */ + const bool use_shortest_for_inner_walls_; + + /*! + * Contains the overhang areas, where we would prefer not to place the start locations of walls + */ + const Shape overhang_areas_; + std::vector getOptimizedOrder(SparsePointGridInclusive line_bucket_grid, size_t snap_radius) { std::vector optimized_order; // To store our result in. @@ -583,8 +617,8 @@ class PathOrderOptimizer continue; } - const bool precompute_start - = seam_config_.type_ == EZSeamType::RANDOM || seam_config_.type_ == EZSeamType::USER_SPECIFIED || seam_config_.type_ == EZSeamType::SHARPEST_CORNER; + const bool precompute_start = path->seam_config_.type_ == EZSeamType::RANDOM || path->seam_config_.type_ == EZSeamType::USER_SPECIFIED + || path->seam_config_.type_ == EZSeamType::SHARPEST_CORNER; if (! path->is_closed_ || ! precompute_start) // Find the start location unless we've already precomputed it. { path->start_vertex_ = findStartLocation(*path, start_position); @@ -690,117 +724,171 @@ class PathOrderOptimizer // Rest of the function only deals with (closed) polygons. We need to be able to find the seam location of those polygons. - if (seam_config_.type_ == EZSeamType::RANDOM) - { - size_t vert = getRandomPointInPolygon(*path.converted_); - return vert; - } + // ########## Step 1: define the weighs of each criterion + // Standard weight for the "main" selection criterion, depending on the selected strategy. There should be + // exactly one calculation using this criterion. + constexpr double weight_main_criterion = 1.0; + + // If seam is not "shortest", we still compute the shortest distance score but with a very low weight + const double weight_distance = path.seam_config_.type_ == EZSeamType::SHORTEST ? weight_main_criterion : 0.02; + + // Avoiding overhangs is more important than the rest + constexpr double weight_exclude_overhang = 2.0; + + // In order to avoid jumping seams, we give a small score to the vertex X and Y position, so that if we have + // e.g. multiple corners with the same angle, we will always choose the ones at the top-right + constexpr double weight_consistency_x = 0.05; + constexpr double weight_consistency_y = weight_consistency_x / 2.0; // Less weight on Y to avoid symmetry effects - if (path.force_start_index_.has_value()) + // ########## Step 2: define which criteria should be taken into account in the total score + const bool calculate_forced_pos_score = path.force_start_index_.has_value(); + + bool calculate_distance_score = false; + bool calculate_corner_score = false; + bool calculate_random_score = false; + if (! calculate_forced_pos_score) { - // Start index already known, since we forced it, return. - return path.force_start_index_.value(); + // For most seam types, the shortest distance matters. Not for SHARPEST_CORNER though. + calculate_distance_score = path.seam_config_.type_ != EZSeamType::SHARPEST_CORNER || path.seam_config_.corner_pref_ == EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE; + + calculate_corner_score + = path.seam_config_.type_ == EZSeamType::SHARPEST_CORNER + && (path.seam_config_.corner_pref_ != EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE && path.seam_config_.corner_pref_ != EZSeamCornerPrefType::PLUGIN); + + calculate_random_score = path.seam_config_.type_ == EZSeamType::RANDOM; } - // Precompute segments lengths because we are going to need them multiple times - std::vector segments_sizes(path.converted_->size()); + const bool calculate_consistency_score = calculate_distance_score || calculate_corner_score; + + // Whatever the strategy, always avoid overhang + const bool calculate_overhang_score = ! overhang_areas_.empty(); + + // ########## Step 3: calculate some values that we are going to need multiple times, depending on which scoring is active + const AABB path_bounding_box = calculate_consistency_score ? AABB(*path.converted_) : AABB(); + + const Point2LL forced_start_pos = path.force_start_index_.has_value() ? path.converted_->at(path.force_start_index_.value()) : Point2LL(); + + std::vector segments_sizes; coord_t total_length = 0; - for (size_t i = 0; i < path.converted_->size(); ++i) + if (calculate_corner_score) { - const Point2LL& here = path.converted_->at(i); - const Point2LL& next = path.converted_->at((i + 1) % path.converted_->size()); - const coord_t segment_size = vSize(next - here); - segments_sizes[i] = segment_size; - total_length += segment_size; + segments_sizes.resize(path.converted_->size()); + for (size_t i = 0; i < path.converted_->size(); ++i) + { + const Point2LL& here = path.converted_->at(i); + const Point2LL& next = path.converted_->at((i + 1) % path.converted_->size()); + const coord_t segment_size = vSize(next - here); + segments_sizes[i] = segment_size; + total_length += segment_size; + } } - size_t best_i; - double best_score = std::numeric_limits::infinity(); - for (const auto& [i, here] : *path.converted_ | ranges::views::drop_last(1) | ranges::views::enumerate) + auto scoreFromDistance = [](const Point2LL& here, const Point2LL& remote_pos) -> double { - // For most seam types, the shortest distance matters. Not for SHARPEST_CORNER though. - // For SHARPEST_CORNER, use a fixed starting score of 0. - const double score_distance = (seam_config_.type_ == EZSeamType::SHARPEST_CORNER && seam_config_.corner_pref_ != EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE) - ? MM2INT(10) - : vSize2(here - target_pos); + // Fixed divider for shortest distances computation. The divider should be set so that the minimum encountered + // distance gives a score very close to 1.0, and a medium-far distance gives a score close to 0.5 + constexpr double distance_divider = 20.0; - double corner_angle = cornerAngle(path, i, segments_sizes, total_length); - // angles < 0 are concave (left turning) - // angles > 0 are convex (right turning) + // Use actual (non-squared) distance to ensure a proper scoring distribution + const double distance = vSizeMM(here - remote_pos); - double corner_shift; + // Use reciprocal function to normalize distance score decreasingly + return 1.0 / (1.0 + (distance / distance_divider)); + }; - if (seam_config_.type_ == EZSeamType::SHORTEST) + // ########## Step 4: now calculate the total score of each vertex and select the best one + std::optional best_i; + Score best_score; + for (const auto& [i, here] : *path.converted_ | ranges::views::drop_last(1) | ranges::views::enumerate) + { + Score vertex_score; + + if (calculate_forced_pos_score) { - // the more a corner satisfies our criteria, the closer it appears to be - // shift 10mm for a very acute corner - corner_shift = MM2INT(10) * MM2INT(10); + vertex_score += CriterionScore{ .score = scoreFromDistance(here, forced_start_pos), .weight = weight_main_criterion }; } - else + + if (calculate_distance_score) { - // the larger the distance from prev_point to p1, the more a corner will "attract" the seam - // so the user has some control over where the seam will lie. - - // the divisor here may need adjusting to obtain the best results (TBD) - corner_shift = score_distance / 50; - } - - double score = score_distance; - switch (seam_config_.corner_pref_) - { - default: - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_INNER: - // Give advantage to concave corners. More advantage for sharper corners. - score += corner_angle * corner_shift; - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_OUTER: - // Give advantage to convex corners. More advantage for sharper corners. - score -= corner_angle * corner_shift; - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_ANY: - score -= std::abs(corner_angle) * corner_shift; // Still give sharper corners more advantage. - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE: - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_WEIGHTED: // Give sharper corners some advantage, but sharper concave corners even more. - { - double score_corner = std::abs(corner_angle) * corner_shift; - if (corner_angle < 0) // Concave corner. + vertex_score += CriterionScore{ .score = scoreFromDistance(here, target_pos), .weight = weight_distance }; + } + + if (calculate_corner_score) + { + double corner_angle = cornerAngle(path, i, segments_sizes, total_length); + // angles < 0 are concave (left turning) + // angles > 0 are convex (right turning) + + CriterionScore score_corner{ .weight = weight_main_criterion }; + + switch (path.seam_config_.corner_pref_) { - score_corner *= 2; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_INNER: + // Give advantage to concave corners. More advantage for sharper corners. + score_corner.score = cura::inverse_lerp(1.0, -1.0, corner_angle); + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_OUTER: + // Give advantage to convex corners. More advantage for sharper corners. + score_corner.score = cura::inverse_lerp(-1.0, 1.0, corner_angle); + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_ANY: + // Still give sharper corners more advantage. + score_corner.score = std::abs(corner_angle); + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_WEIGHTED: + // Give sharper corners some advantage, but sharper concave corners even more. + if (corner_angle < 0) + { + score_corner.score = -corner_angle; + } + else + { + score_corner.score = corner_angle / 2.0; + } + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE: + case EZSeamCornerPrefType::PLUGIN: + break; } - score -= score_corner; - break; + + vertex_score += score_corner; } + + if (calculate_random_score) + { + vertex_score += CriterionScore{ .score = cura::randf(), .weight = weight_main_criterion }; } - constexpr double EPSILON = 5.0; - if (std::abs(best_score - score) <= EPSILON) + if (calculate_consistency_score) { - // add breaker for two candidate starting location with similar score - // if we don't do this then we (can) get an un-even seam - // ties are broken by favouring points with lower x-coord - // if x-coord for both points are equal then break ties by - // favouring points with lower y-coord - const Point2LL& best_point = path.converted_->at(best_i); - if (std::abs(here.Y - best_point.Y) <= EPSILON ? best_point.X < here.X : best_point.Y < here.Y) - { - best_score = std::min(best_score, score); - best_i = i; - } + CriterionScore score_consistency_x{ .weight = weight_consistency_x }; + score_consistency_x.score = cura::inverse_lerp(path_bounding_box.min_.X, path_bounding_box.max_.X, here.X); + vertex_score += score_consistency_x; + + CriterionScore score_consistency_y{ .weight = weight_consistency_y }; + score_consistency_y.score = cura::inverse_lerp(path_bounding_box.min_.Y, path_bounding_box.max_.Y, here.Y); + vertex_score += score_consistency_y; } - else if (score < best_score) + + if (calculate_overhang_score) + { + CriterionScore score_exclude_overhang{ .weight = weight_exclude_overhang }; + score_exclude_overhang.score = overhang_areas_.inside(here, true) ? 0.0 : 1.0; + vertex_score += score_exclude_overhang; + } + + if (! best_i.has_value() || vertex_score > best_score) { best_i = i; - best_score = score; + best_score = vertex_score; } } if (! disallowed_area_for_seams.empty()) { - best_i = pathIfZseamIsInDisallowedArea(best_i, path, 0); + best_i = pathIfZseamIsInDisallowedArea(best_i.value_or(0), path, 0); } - return best_i; + return best_i.value_or(0); } /*! @@ -939,16 +1027,6 @@ class PathOrderOptimizer return sum * sum; // Squared distance, for fair comparison with direct distance. } - /*! - * Get a random vertex of a polygon. - * \param polygon A polygon to get a random vertex of. - * \return A random index in that polygon. - */ - size_t getRandomPointInPolygon(const PointsSet& polygon) const - { - return rand() % polygon.size(); - } - bool isLoopingPolyline(const OrderablePath& path) { if (path.converted_->empty()) diff --git a/include/communication/EmscriptenCommunication.h b/include/communication/EmscriptenCommunication.h index 5144b96dc6..b1c7e7c903 100644 --- a/include/communication/EmscriptenCommunication.h +++ b/include/communication/EmscriptenCommunication.h @@ -23,12 +23,14 @@ class EmscriptenCommunication : public CommandLine std::string progress_handler_; ///< Handler for progress messages. std::string gcode_header_handler_; ///< Handler for getting the GCode handler. std::string slice_info_handler_; ///< Handler for slice information messages. - + std::string engine_info_handler_; ///< Handler for curaengine info : version and hash. /** * \brief Creates a message containing slice information. * \return A string containing the slice information message. */ [[nodiscard]] static std::string createSliceInfoMessage(); + [[nodiscard]] static std::string createEngineInfoMessage(); + public: /** @@ -48,6 +50,11 @@ class EmscriptenCommunication : public CommandLine */ void sendGCodePrefix(const std::string& prefix) const override; + /** + * \brief Indicate that we're beginning to send g-code. + */ + void beginGCode() override; + /** * \brief Initiates the slicing of the next item. */ diff --git a/include/path_ordering.h b/include/path_ordering.h index 0a7fb1af13..9366ba44c8 100644 --- a/include/path_ordering.h +++ b/include/path_ordering.h @@ -82,6 +82,16 @@ struct PathOrdering */ std::optional force_start_index_; + /*! + * The start point calculation strategy to be used for this path + */ + ZSeamConfig seam_config_; + + /*! + * Indicates whether this path is an outer (or inner) wall + */ + bool is_outer_wall{ false }; + /*! * Get vertex data from the custom path type. * diff --git a/include/utils/AABB.h b/include/utils/AABB.h index e3a93c678b..6be6ba9b2c 100644 --- a/include/utils/AABB.h +++ b/include/utils/AABB.h @@ -9,6 +9,7 @@ namespace cura { +class PointsSet; class Polygon; class Shape; @@ -21,10 +22,10 @@ class AABB AABB(); //!< initializes with invalid min and max AABB(const Point2LL& min, const Point2LL& max); //!< initializes with given min and max AABB(const Shape& shape); //!< Computes the boundary box for the given shape - AABB(const Polygon& poly); //!< Computes the boundary box for the given polygons + AABB(const PointsSet& poly); //!< Computes the boundary box for the given polygons void calculate(const Shape& shape); //!< Calculates the aabb for the given shape (throws away old min and max data of this aabb) - void calculate(const Polygon& poly); //!< Calculates the aabb for the given polygon (throws away old min and max data of this aabb) + void calculate(const PointsSet& poly); //!< Calculates the aabb for the given polygon (throws away old min and max data of this aabb) /*! * Whether the bounding box contains the specified point. @@ -80,7 +81,7 @@ class AABB */ void include(const Point2LL& point); - void include(const Polygon& polygon); + void include(const PointsSet& polygon); /*! * \brief Includes the specified bounding box in the bounding box. diff --git a/include/utils/CriterionScore.h b/include/utils/CriterionScore.h new file mode 100644 index 0000000000..e4313d988e --- /dev/null +++ b/include/utils/CriterionScore.h @@ -0,0 +1,31 @@ +// Copyright (c) 2024 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef UTILS_CRITERION_SCORE_H +#define UTILS_CRITERION_SCORE_H + +namespace cura +{ + +/*! + * This structure represents a score given by a single crtierion when calculating a global score to select a best + * candidate among a list with multiple criteria. + */ +struct CriterionScore +{ + /*! + * The score given by the criterion. To ensure a proper selection, this value must be contained in [0.0, 1.0] and + * the different given scores must be evenly distributed in this range. + */ + double score{ 0.0 }; + + /*! + * The weight to be given when taking this score into the global score. A score that contributes "normally" to the + * global score should have a weight of 1.0, and others should be adjusted around this value, to give them more or + * less influence. + */ + double weight{ 0.0 }; +}; + +} // namespace cura +#endif // UTILS_CRITERION_SCORE_H diff --git a/include/utils/SVG.h b/include/utils/SVG.h index 96bcfa0a45..7105468f10 100644 --- a/include/utils/SVG.h +++ b/include/utils/SVG.h @@ -62,6 +62,7 @@ class SVG : NoCopy private: std::string toString(const Color color) const; std::string toString(const ColorObject& color) const; + void handleFlush(const bool flush) const; FILE* out_; // the output file const AABB aabb_; // the boundary box to display @@ -122,7 +123,7 @@ class SVG : NoCopy */ void writeLines(const std::vector& polyline, const ColorObject color = Color::BLACK) const; - void writeLine(const Point2LL& a, const Point2LL& b, const ColorObject color = Color::BLACK, const double stroke_width = 1.0) const; + void writeLine(const Point2LL& a, const Point2LL& b, const ColorObject color = Color::BLACK, const double stroke_width = 1.0, const bool flush = true) const; void writeArrow(const Point2LL& a, const Point2LL& b, const ColorObject color = Color::BLACK, const double stroke_width = 1.0, const double head_size = 5.0) const; @@ -148,10 +149,12 @@ class SVG : NoCopy void writePolygon(Polygon poly, const ColorObject color = Color::BLACK, const double stroke_width = 1.0, const bool flush = true) const; - void writePolylines(const Shape& polys, const ColorObject color = Color::BLACK, const double stroke_width = 1.0) const; + void writePolylines(const Shape& polys, const ColorObject color = Color::BLACK, const double stroke_width = 1.0, const bool flush = true) const; void writePolyline(const Polygon& poly, const ColorObject color = Color::BLACK, const double stroke_width = 1.0) const; + void writePolyline(const Polyline& poly, const ColorObject color = Color::BLACK, const double stroke_width = 1.0, const bool flush = true) const; + /*! * Draw variable-width paths into the image. * @@ -183,7 +186,7 @@ class SVG : NoCopy * \param color The color to draw the line with. * \param width_factor A multiplicative factor on the line width. */ - void writeLine(const ExtrusionLine& line, const ColorObject color = Color::BLACK, const double width_factor = 1.0) const; + void writeLine(const ExtrusionLine& line, const ColorObject color = Color::BLACK, const double width_factor = 1.0, const bool flush = true) const; /*! * Draws a grid across the image and writes down coordinates. diff --git a/include/utils/Score.h b/include/utils/Score.h new file mode 100644 index 0000000000..f8108498ee --- /dev/null +++ b/include/utils/Score.h @@ -0,0 +1,61 @@ +// Copyright (c) 2024 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef UTILS_SCORE_H +#define UTILS_SCORE_H + +#include + +#include "CriterionScore.h" + +namespace cura +{ + +/*! + * This class represents a score to be calculated over different criteria, to select the best candidate among a list. + */ +class Score +{ +private: + double value_{ 0.0 }; + +public: + /*! + * Get the actual score value, should be used for debug purposes only + */ + double getValue() const + { + return value_; + } + + /*! + * Add the calculated score of an inidividual criterion to the global score, taking care of its weight + */ + void operator+=(const CriterionScore& criterion_score) + { + value_ += criterion_score.score * criterion_score.weight; + } + + /*! + * Comparison operators to allow selecting the best global score + */ + auto operator<=>(const Score&) const = default; +}; + +} // namespace cura + +namespace fmt +{ + +template<> +struct formatter : formatter +{ + auto format(const cura::Score& score, format_context& ctx) + { + return fmt::format_to(ctx.out(), "Score{{{}}}", score.getValue()); + } +}; + +} // namespace fmt + +#endif // UTILS_SCORE_H diff --git a/include/utils/math.h b/include/utils/math.h index f1ad772c72..a0650f0e8f 100644 --- a/include/utils/math.h +++ b/include/utils/math.h @@ -108,5 +108,33 @@ template return (dividend + divisor - 1) / divisor; } +/*! + * \brief Calculates the "inverse linear interpolation" of a value over a range, i.e. given a range [min, max] the + * value "min" would give a result of 0.0 and the value "max" would give a result of 1.0, values in between will + * be interpolated linearly. + * \note The returned value may be out of the [0.0, 1.0] range if the given value is outside the [min, max] range, it is + * up to the caller to clamp the result if required + * \note The range_min value may be greater than the range_max, inverting the interpolation logic + */ +template +[[nodiscard]] inline double inverse_lerp(T range_min, T range_max, T value) +{ + if (range_min == range_max) + { + return 0.0; + } + + return static_cast(value - range_min) / (range_max - range_min); +} + +/*! + * \brief Get a random floating point number in the range [0.0, 1.0] + */ +template +[[nodiscard]] inline T randf() +{ + return static_cast(std::rand()) / static_cast(RAND_MAX); +} + } // namespace cura #endif // UTILS_MATH_H diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 9a7f26a410..fcba3b052b 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -173,7 +173,7 @@ void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keep { if (extruder_is_used_in_filler_layers) { - process_layer_starting_layer_nr = -Raft::getFillerLayerCount(); + process_layer_starting_layer_nr = -static_cast(Raft::getFillerLayerCount()); break; } } @@ -2064,14 +2064,10 @@ void getBestAngledLinesToSupportPoints(OpenLinesSet& result_lines, const Shape& // We do this because supporting lines are hanging over air, // and therefore print best as part of a continuous print move, // rather than having a travel move before and after them. -// -// We also double-insert most lines, since that allows the -// elimination of all travel moves, and overlap isn't an issue -// because the lines are hanging. void integrateSupportingLine(OpenLinesSet& infill_lines, const OpenPolyline& line_to_add) { // Returns the line index and the index of the point within an infill_line, null for no match found. - const auto findMatchingSegment = [&](Point2LL p) -> std::optional> + const auto findMatchingSegment = [&](Point2LL p) -> std::optional> { for (size_t i = 0; i < infill_lines.size(); ++i) { @@ -2101,59 +2097,76 @@ void integrateSupportingLine(OpenLinesSet& infill_lines, const OpenPolyline& lin { /* both ends intersect with the same line. * If the inserted line has ends x, y - * and the original line was A--(x)--B---C--(y)--D - * Then the new line will be A--x--y--C---B--x--y--D - * Note that the middle part of the line is reversed. + * and the original line was ...--A--(x)--B--...--C--(y)--D--... + * Then the new lines will be ...--A--x--y--C--...--B--x + * And line y--D--... + * Note that some parts of the line are reversed, + * and the last one is completly split apart. */ OpenPolyline& old_line = infill_lines[front_line_index]; - OpenPolyline new_line; + OpenPolyline new_line_start; + OpenPolyline new_line_end; Point2LL x, y; - size_t x_index, y_index; + std::ptrdiff_t x_index, y_index; if (front_point_index < back_point_index) { x = line_to_add.front(); y = line_to_add.back(); - x_index = front_point_index; - y_index = back_point_index; + x_index = static_cast(front_point_index); + y_index = static_cast(back_point_index); } else { y = line_to_add.front(); x = line_to_add.back(); - y_index = front_point_index; - x_index = back_point_index; + y_index = static_cast(front_point_index); + x_index = static_cast(back_point_index); } - new_line.insert(new_line.end(), old_line.begin(), old_line.begin() + x_index); - new_line.push_back(x); - new_line.push_back(y); - new_line.insert(new_line.end(), old_line.rend() - y_index, old_line.rend() - x_index); - new_line.push_back(x); - new_line.push_back(y); - new_line.insert(new_line.end(), old_line.begin() + y_index, old_line.end()); - old_line.setPoints(std::move(new_line.getPoints())); + + new_line_start.insert(new_line_start.end(), old_line.begin(), old_line.begin() + x_index); + new_line_start.push_back(x); + new_line_start.push_back(y); + new_line_start.insert(new_line_start.end(), old_line.rend() - y_index, old_line.rend() - x_index); + new_line_start.push_back(x); + + new_line_end.push_back(y); + new_line_end.insert(new_line_end.end(), old_line.begin() + y_index, old_line.end()); + + old_line.setPoints(std::move(new_line_start.getPoints())); + infill_lines.push_back(new_line_end); } else { /* Different lines * If the line_to_add has ends [front, back] - * Existing line (intersects front): A B front C D E - * Other existing line (intersects back): M N back O P Q - * Result is Line: A B front back O P Q - * And line: M N back front C D E + * Existing line (intersects front): ...--A--(x)--B--... + * Other existing line (intersects back): ...--C--(y)--D--... + * Result is Line: ...--A--x--y--D--... + * And line: x--B--... + * And line: ...--C--y */ OpenPolyline& old_front = infill_lines[front_line_index]; OpenPolyline& old_back = infill_lines[back_line_index]; - OpenPolyline new_front, new_back; - new_front.insert(new_front.end(), old_front.begin(), old_front.begin() + front_point_index); - new_front.push_back(line_to_add.front()); - new_front.push_back(line_to_add.back()); - new_front.insert(new_front.end(), old_back.begin() + back_point_index, old_back.end()); - new_back.insert(new_back.end(), old_back.begin(), old_back.begin() + back_point_index); - new_back.push_back(line_to_add.back()); - new_back.push_back(line_to_add.front()); - new_back.insert(new_back.end(), old_front.begin() + front_point_index, old_front.end()); + OpenPolyline full_line, new_front, new_back; + const Point2LL x = line_to_add.front(); + const Point2LL y = line_to_add.back(); + const auto x_index = static_cast(front_point_index); + const auto y_index = static_cast(back_point_index); + + new_front.push_back(x); + new_front.insert(new_front.end(), old_front.begin() + x_index, old_front.end()); + + new_back.insert(new_back.end(), old_back.begin(), old_back.begin() + y_index); + new_back.push_back(y); + + full_line.insert(full_line.end(), old_front.begin(), old_front.begin() + x_index); + full_line.push_back(x); + full_line.push_back(y); + full_line.insert(full_line.end(), old_back.begin() + y_index, old_back.end()); + old_front.setPoints(std::move(new_front.getPoints())); old_back.setPoints(std::move(new_back.getPoints())); + infill_lines.push_back(full_line); } } else @@ -2860,7 +2873,7 @@ size_t FffGcodeWriter::findUsedExtruderIndex(const SliceDataStorage& storage, co { return last ? extruder_use.back().extruder_nr : extruder_use.front().extruder_nr; } - else if (layer_nr <= -Raft::getTotalExtraLayers()) + else if (layer_nr <= -LayerIndex(Raft::getTotalExtraLayers())) { // Asking for extruder use below first layer, give first extruder return getStartExtruder(storage); @@ -3148,7 +3161,8 @@ bool FffGcodeWriter::processInsets( mesh.bounding_box.flatten().getMiddle(), disallowed_areas_for_seams, scarf_seam, - smooth_speed); + smooth_speed, + gcode_layer.getSeamOverhangMask()); added_something |= wall_orderer.addToLayer(); } return added_something; diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index 56961e2bde..7c8723bb95 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -54,7 +54,8 @@ InsetOrderOptimizer::InsetOrderOptimizer( const Point2LL& model_center_point, const Shape& disallowed_areas_for_seams, const bool scarf_seam, - const bool smooth_speed) + const bool smooth_speed, + const Shape& overhang_areas) : gcode_writer_(gcode_writer) , storage_(storage) , gcode_layer_(gcode_layer) @@ -78,6 +79,7 @@ InsetOrderOptimizer::InsetOrderOptimizer( , disallowed_areas_for_seams_{ disallowed_areas_for_seams } , scarf_seam_(scarf_seam) , smooth_speed_(smooth_speed) + , overhang_areas_(overhang_areas) { } @@ -93,6 +95,7 @@ bool InsetOrderOptimizer::addToLayer() const bool current_extruder_is_wall_x = wall_x_extruder_nr_ == extruder_nr_; const bool reverse = shouldReversePath(use_one_extruder, current_extruder_is_wall_x, outer_to_inner); + const bool use_shortest_for_inner_walls = outer_to_inner; auto walls_to_be_added = getWallsToBeAdded(reverse, use_one_extruder); const auto order = pack_by_inset ? getInsetOrder(walls_to_be_added, outer_to_inner) : getRegionOrder(walls_to_be_added, outer_to_inner); @@ -114,7 +117,9 @@ bool InsetOrderOptimizer::addToLayer() reverse, order, group_outer_walls, - disallowed_areas_for_seams_); + disallowed_areas_for_seams_, + use_shortest_for_inner_walls, + overhang_areas_); for (auto& line : walls_to_be_added) { @@ -126,7 +131,7 @@ bool InsetOrderOptimizer::addToLayer() // If the user indicated that we may deviate from the vertices for the seam, we can insert a seam point, if needed. force_start = insertSeamPoint(line); } - order_optimizer.addPolygon(&line, force_start); + order_optimizer.addPolygon(&line, force_start, line.is_outer_wall()); } else { @@ -202,7 +207,7 @@ std::optional InsetOrderOptimizer::insertSeamPoint(ExtrusionLine& closed Point2LL closest_point; size_t closest_junction_idx = 0; coord_t closest_distance_sqd = std::numeric_limits::max(); - bool should_reclaculate_closest = false; + bool should_recalculate_closest = false; if (z_seam_config_.type_ == EZSeamType::USER_SPECIFIED) { // For user-defined seams you usually don't _actually_ want the _closest_ point, per-se, @@ -248,24 +253,27 @@ std::optional InsetOrderOptimizer::insertSeamPoint(ExtrusionLine& closed closest_junction_idx = i; } } - should_reclaculate_closest = true; + should_recalculate_closest = true; } const auto& start_pt = closed_line.junctions_[closest_junction_idx]; const auto& end_pt = closed_line.junctions_[(closest_junction_idx + 1) % closed_line.junctions_.size()]; - if (should_reclaculate_closest) + if (should_recalculate_closest) { // In the second case (see above) the closest point hasn't actually been calculated yet, // since in that case we'de need the start and end points. So do that here. closest_point = LinearAlg2D::getClosestOnLineSegment(request_point, start_pt.p_, end_pt.p_); } constexpr coord_t smallest_dist_sqd = 25; - if (vSize2(closest_point - start_pt.p_) <= smallest_dist_sqd || vSize2(closest_point - end_pt.p_) <= smallest_dist_sqd) + if (vSize2(closest_point - start_pt.p_) <= smallest_dist_sqd) { - // Early out if the closest point is too close to the start or end point. - // NOTE: Maybe return the index here anyway, since this is the point the current caller would want to force the seam to. - // However, then the index returned would have a caveat that it _can_ point to an already exisiting point then. - return std::nullopt; + // If the closest point is very close to the start point, just use it instead. + return closest_junction_idx; + } + if (vSize2(closest_point - end_pt.p_) <= smallest_dist_sqd) + { + // If the closest point is very close to the end point, just use it instead. + return (closest_junction_idx + 1) % closed_line.junctions_.size(); } // NOTE: This could also be done on a single axis (skipping the implied sqrt), but figuring out which one and then using the right values became a bit messy/verbose. diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 8f6c12317c..2b17229c36 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -743,8 +743,7 @@ void LayerPlan::addWallLine( segment_flow, width_factor, spiralize, - (overhang_mask_.empty() || (! overhang_mask_.inside(p0.toPoint2LL(), true) && ! overhang_mask_.inside(p1.toPoint2LL(), true))) ? speed_factor - : overhang_speed_factor, + segmentIsOnOverhang(p0, p1) ? overhang_speed_factor : speed_factor, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); } @@ -761,8 +760,7 @@ void LayerPlan::addWallLine( segment_flow, width_factor, spiralize, - (overhang_mask_.empty() || (! overhang_mask_.inside(p0.toPoint2LL(), true) && ! overhang_mask_.inside(p1.toPoint2LL(), true))) ? speed_factor - : overhang_speed_factor, + segmentIsOnOverhang(p0, p1) ? overhang_speed_factor : speed_factor, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); } @@ -867,7 +865,7 @@ void LayerPlan::addWallLine( flow, width_factor, spiralize, - (overhang_mask_.empty() || (! overhang_mask_.inside(p0.toPoint2LL(), true) && ! overhang_mask_.inside(p1.toPoint2LL(), true))) ? speed_factor : overhang_speed_factor, + segmentIsOnOverhang(p0, p1) ? overhang_speed_factor : 1.0_r, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); } @@ -1330,11 +1328,6 @@ void LayerPlan::addWall( { return; } - if (is_closed) - { - // make sure wall start point is not above air! - start_idx = locateFirstSupportedVertex(wall, start_idx); - } const bool actual_scarf_seam = scarf_seam && is_closed; double non_bridge_line_volume = max_non_bridge_line_volume; // assume extruder is fully pressurised before first non-bridge line is output @@ -1725,6 +1718,13 @@ void LayerPlan::addLinesInGivenOrder( } } +bool LayerPlan::segmentIsOnOverhang(const Point3LL& p0, const Point3LL& p1) const +{ + const OpenPolyline segment{ p0.toPoint2LL(), p1.toPoint2LL() }; + const OpenLinesSet intersected_lines = overhang_mask_.intersection(OpenLinesSet{ segment }); + return ! intersected_lines.empty() && (static_cast(intersected_lines.length()) / segment.length()) > 0.5; +} + void LayerPlan::sendLineTo(const GCodePath& path, const Point3LL& position, const double extrude_speed) { Application::getInstance().communication_->sendLineTo( @@ -2294,6 +2294,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) extruder_nr = extruder_plan.extruder_nr_; gcode.ResetLastEValueAfterWipe(prev_extruder); + gcode.writePrepareFansForNozzleSwitch(); const RetractionAndWipeConfig& prev_retraction_config = storage_.retraction_wipe_config_per_extruder[prev_extruder]; if (prev_retraction_config.retraction_hop_after_extruder_switch) @@ -2306,8 +2307,6 @@ void LayerPlan::writeGCode(GCodeExport& gcode) gcode.switchExtruder(extruder_nr, prev_retraction_config.extruder_switch_retraction_config); } - gcode.writePrepareFansForNozzleSwitch(); - { // require printing temperature to be met constexpr bool wait = true; gcode.writeTemperatureCommand(extruder_nr, extruder_plan.required_start_temperature_, wait); @@ -3045,6 +3044,11 @@ void LayerPlan::setSeamOverhangMask(const Shape& polys) seam_overhang_mask_ = polys; } +const Shape& LayerPlan::getSeamOverhangMask() const +{ + return seam_overhang_mask_; +} + void LayerPlan::setRoofingMask(const Shape& polys) { roofing_mask_ = polys; diff --git a/src/PrimeTower.cpp b/src/PrimeTower.cpp index bc4e2a07a7..3b7933235f 100644 --- a/src/PrimeTower.cpp +++ b/src/PrimeTower.cpp @@ -604,7 +604,7 @@ const Shape& PrimeTower::getOuterPoly(const LayerIndex& layer_nr) const const Shape& PrimeTower::getGroundPoly() const { - return getOuterPoly(-Raft::getTotalExtraLayers()); + return getOuterPoly(-LayerIndex(Raft::getTotalExtraLayers())); } void PrimeTower::gotoStartLocation(LayerPlan& gcode_layer, const int extruder_nr) const diff --git a/src/PrimeTower/PrimeTower.cpp b/src/PrimeTower/PrimeTower.cpp index 074d1f501a..30b8ab7eb7 100644 --- a/src/PrimeTower/PrimeTower.cpp +++ b/src/PrimeTower/PrimeTower.cpp @@ -351,7 +351,7 @@ bool PrimeTower::extruderRequiresPrime(const std::vector& extruder_is_used void PrimeTower::gotoStartLocation(LayerPlan& gcode_layer, const size_t extruder_nr) const { LayerIndex layer_nr = gcode_layer.getLayerNr(); - if (layer_nr != -Raft::getTotalExtraLayers()) + if (layer_nr != -LayerIndex(Raft::getTotalExtraLayers())) { coord_t wipe_radius; auto iterator = base_occupied_outline_.iterator_at(gcode_layer.getLayerNr()); diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index 67a68e1cf6..783807e002 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -2093,6 +2093,17 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) if (! found) { next_removed_holes_by_idx.emplace(idx); + + // Individual pieces of the hole could still be valid (if the 'hole' is made by branches surrounding others' for instance). + for (const auto& poly : hole) + { + if (poly.area() < 0) + { + auto poly_copy = poly; + poly_copy.reverse(); + valid_holes[layer_idx].push_back(poly_copy); + } + } } else { diff --git a/src/communication/CommandLine.cpp b/src/communication/CommandLine.cpp index e5f8b4340a..da4bef97e5 100644 --- a/src/communication/CommandLine.cpp +++ b/src/communication/CommandLine.cpp @@ -186,7 +186,9 @@ void CommandLine::sliceNext() force_read_parent = false; force_read_nondefault = false; } - else if (argument.starts_with("--progress_cb") || argument.starts_with("--slice_info_cb") || argument.starts_with("--gcode_header_cb")) + else if ( + argument.starts_with("--progress_cb") || argument.starts_with("--slice_info_cb") || argument.starts_with("--gcode_header_cb") + || argument.starts_with("--engine_info_cb")) { // Unused in command line slicing, but used in EmscriptenCommunication. argument_index++; diff --git a/src/communication/EmscriptenCommunication.cpp b/src/communication/EmscriptenCommunication.cpp index 8155290ee9..e62e68cf32 100644 --- a/src/communication/EmscriptenCommunication.cpp +++ b/src/communication/EmscriptenCommunication.cpp @@ -38,6 +38,10 @@ EmscriptenCommunication::EmscriptenCommunication(const std::vector& { gcode_header_handler_ = *ranges::next(gcode_header_flag); } + if (auto engine_info_flag = ranges::find(arguments_, "--engine_info_cb"); engine_info_flag != arguments_.end()) + { + engine_info_handler_ = *ranges::next(engine_info_flag); + } } void EmscriptenCommunication::sendGCodePrefix(const std::string& prefix) const @@ -109,6 +113,35 @@ std::string EmscriptenCommunication::createSliceInfoMessage() return buffer.GetString(); } +std::string EmscriptenCommunication::createEngineInfoMessage() +{ + // Construct a string with rapidjson containing the engine information + rapidjson::Document doc; + auto& allocator = doc.GetAllocator(); + doc.SetObject(); + + // Set the slicer version + rapidjson::Value version("version", allocator); + rapidjson::Value version_value(CURA_ENGINE_VERSION, allocator); + doc.AddMember(version, version_value, allocator); + + // Set the hash + rapidjson::Value hash("hash", allocator); + rapidjson::Value hash_value(CURA_ENGINE_HASH, allocator); + doc.AddMember(hash, hash_value, allocator); + + // Serialize the JSON document to a string + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + doc.Accept(writer); + return buffer.GetString(); +} + +void EmscriptenCommunication::beginGCode() +{ + auto engine_info = createEngineInfoMessage(); + emscripten_run_script(fmt::format("globalThis[\"{}\"]({})", engine_info_handler_, engine_info).c_str()); +} void EmscriptenCommunication::sliceNext() { CommandLine::sliceNext(); diff --git a/src/raft.cpp b/src/raft.cpp index edaff83509..f4d40eb8f0 100644 --- a/src/raft.cpp +++ b/src/raft.cpp @@ -199,26 +199,23 @@ Raft::LayerType Raft::getLayerType(LayerIndex layer_index) const auto interface_layers = Raft::getInterfaceLayers(); const auto surface_layers = Raft::getSurfaceLayers(); - if (layer_index < -airgap - surface_layers - interface_layers) + if (layer_index < -LayerIndex(airgap + surface_layers + interface_layers)) { return LayerType::RaftBase; } - else if (layer_index < -airgap - surface_layers) + if (layer_index < -LayerIndex(airgap + surface_layers)) { return LayerType::RaftInterface; } - else if (layer_index < -airgap) + if (layer_index < -LayerIndex(airgap)) { return LayerType::RaftSurface; } - else if (layer_index < 0) + if (layer_index < LayerIndex(0)) { return LayerType::Airgap; } - else - { - return LayerType::Model; - } + return LayerType::Model; } size_t Raft::getLayersAmount(const std::string& extruder_nr_setting_name, const std::string& target_raft_section) diff --git a/src/utils/AABB.cpp b/src/utils/AABB.cpp index 163c6a5f26..36cfa32e98 100644 --- a/src/utils/AABB.cpp +++ b/src/utils/AABB.cpp @@ -33,7 +33,7 @@ AABB::AABB(const Shape& shape) calculate(shape); } -AABB::AABB(const Polygon& poly) +AABB::AABB(const PointsSet& poly) : min_(POINT_MAX, POINT_MAX) , max_(POINT_MIN, POINT_MIN) { @@ -83,7 +83,7 @@ void AABB::calculate(const Shape& shape) } } -void AABB::calculate(const Polygon& poly) +void AABB::calculate(const PointsSet& poly) { min_ = Point2LL(POINT_MAX, POINT_MAX); max_ = Point2LL(POINT_MIN, POINT_MIN); @@ -141,7 +141,7 @@ void AABB::include(const Point2LL& point) max_.Y = std::max(max_.Y, point.Y); } -void AABB::include(const Polygon& polygon) +void AABB::include(const PointsSet& polygon) { for (const Point2LL& point : polygon) { diff --git a/src/utils/SVG.cpp b/src/utils/SVG.cpp index 69a115d9d4..83f223e9b1 100644 --- a/src/utils/SVG.cpp +++ b/src/utils/SVG.cpp @@ -59,6 +59,14 @@ std::string SVG::toString(const ColorObject& color) const } } +void SVG::handleFlush(const bool flush) const +{ + if (flush) + { + fflush(out_); + } +} + SVG::SVG(std::string filename, AABB aabb, Point2LL canvas_size, ColorObject background) : SVG( @@ -249,7 +257,7 @@ void SVG::writeLines(const std::vector& polyline, const ColorObject co fprintf(out_, "\" />\n"); // Write the end of the tag. } -void SVG::writeLine(const Point2LL& a, const Point2LL& b, const ColorObject color, const double stroke_width) const +void SVG::writeLine(const Point2LL& a, const Point2LL& b, const ColorObject color, const double stroke_width, const bool flush) const { Point3D fa = transformF(a); Point3D fb = transformF(b); @@ -262,6 +270,8 @@ void SVG::writeLine(const Point2LL& a, const Point2LL& b, const ColorObject colo static_cast(fb.y_), toString(color).c_str(), static_cast(stroke_width)); + + handleFlush(flush); } void SVG::writeArrow(const Point2LL& a, const Point2LL& b, const ColorObject color, const double stroke_width, const double head_size) const @@ -342,10 +352,7 @@ void SVG::writePolygons(const Shape& polys, const ColorObject color, const doubl writePolygon(poly, color, stroke_width, false); } - if (flush) - { - fflush(out_); - } + handleFlush(flush); } void SVG::writePolygon(const Polygon poly, const ColorObject color, const double stroke_width, const bool flush) const @@ -381,19 +388,18 @@ void SVG::writePolygon(const Polygon poly, const ColorObject color, const double i++; } - if (flush) - { - fflush(out_); - } + handleFlush(flush); } -void SVG::writePolylines(const Shape& polys, const ColorObject color, const double stroke_width) const +void SVG::writePolylines(const Shape& polys, const ColorObject color, const double stroke_width, const bool flush) const { for (const Polygon& poly : polys) { - writePolyline(poly, color, stroke_width); + writePolyline(poly, color, stroke_width, false); } + + handleFlush(flush); } void SVG::writePolyline(const Polygon& poly, const ColorObject color, const double stroke_width) const @@ -427,6 +433,16 @@ void SVG::writePolyline(const Polygon& poly, const ColorObject color, const doub } } +void SVG::writePolyline(const Polyline& poly, const ColorObject color, const double stroke_width, const bool flush) const +{ + for (auto iterator = poly.beginSegments(); iterator != poly.endSegments(); ++iterator) + { + writeLine((*iterator).start, (*iterator).end, color, stroke_width, false); + } + + handleFlush(flush); +} + void SVG::writePaths(const std::vector& paths, const ColorObject color, const double width_factor) const { for (const VariableWidthLines& lines : paths) @@ -443,7 +459,7 @@ void SVG::writeLines(const VariableWidthLines& lines, const ColorObject color, c } } -void SVG::writeLine(const ExtrusionLine& line, const ColorObject color, const double width_factor) const +void SVG::writeLine(const ExtrusionLine& line, const ColorObject color, const double width_factor, const bool flush) const { constexpr double minimum_line_width = 10; // Always have some width, otherwise some lines become completely invisible. if (line.junctions_.empty()) // Only draw lines that have at least 2 junctions, otherwise they are degenerate. @@ -481,6 +497,11 @@ void SVG::writeLine(const ExtrusionLine& line, const ColorObject color, const do start_vertex = end_vertex; // For the next line segment. } + + if (flush) + { + fflush(out_); + } } void SVG::writeCoordinateGrid(const coord_t grid_size, const Color color, const double stroke_width, const double font_size) const diff --git a/tests/PathOrderOptimizerTest.cpp b/tests/PathOrderOptimizerTest.cpp index c8621ba3d8..97cc1617c5 100644 --- a/tests/PathOrderOptimizerTest.cpp +++ b/tests/PathOrderOptimizerTest.cpp @@ -12,25 +12,17 @@ namespace cura class PathOrderOptimizerTest : public testing::Test { public: - /*! - * A blank optimizer with no polygons added yet. Fresh and virgin. - */ - PathOrderOptimizer optimizer; - /*! * A simple isosceles triangle. Base length and height 50. */ Polygon triangle; PathOrderOptimizerTest() - : optimizer(Point2LL(0, 0)) { } void SetUp() override { - optimizer = PathOrderOptimizer(Point2LL(0, 0)); - triangle.clear(); triangle.push_back(Point2LL(0, 0)); triangle.push_back(Point2LL(50, 0)); @@ -44,6 +36,7 @@ class PathOrderOptimizerTest : public testing::Test */ TEST_F(PathOrderOptimizerTest, OptimizeWhileEmpty) { + PathOrderOptimizer optimizer(Point2LL(0, 0)); optimizer.optimize(); // Don't crash. EXPECT_EQ(optimizer.paths_.size(), 0) << "Still empty!"; } @@ -54,6 +47,8 @@ TEST_F(PathOrderOptimizerTest, OptimizeWhileEmpty) */ TEST_F(PathOrderOptimizerTest, ThreeTrianglesShortestOrder) { + PathOrderOptimizer optimizer(Point2LL(0, 0)); + Polygon near = triangle; // Copy, then translate. near.translate(Point2LL(100, 100)); Polygon middle = triangle;