diff options
author | Remco Burema <41987080+rburema@users.noreply.github.com> | 2022-10-30 19:46:02 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-30 19:46:02 +0300 |
commit | c7adeae0d19a5511143ab382eb537c34dd0386ac (patch) | |
tree | da57a1a25c07a532314b75710a1e435cbef9c36c | |
parent | 4fa24ba16df055dc1ad840c0b3df554c6e2dc29b (diff) | |
parent | 4f053c7872e901d8944aa2ea55dae7c3852bb882 (diff) |
Merge pull request #1613 from Ultimaker/brim_per_material_optimized_order
Brim overhaul: New features and Bug fixes
-rw-r--r-- | include/LayerPlan.h | 4 | ||||
-rw-r--r-- | include/PathOrderOptimizer.h | 2 | ||||
-rw-r--r-- | include/PrimeTower.h | 6 | ||||
-rw-r--r-- | include/SkirtBrim.h | 166 | ||||
-rw-r--r-- | include/TreeModelVolumes.h | 4 | ||||
-rw-r--r-- | include/settings/PathConfigStorage.h | 1 | ||||
-rw-r--r-- | include/sliceDataStorage.h | 26 | ||||
-rw-r--r-- | include/utils/polygon.h | 22 | ||||
-rw-r--r-- | src/FffGcodeWriter.cpp | 162 | ||||
-rw-r--r-- | src/FffPolygonGenerator.cpp | 89 | ||||
-rw-r--r-- | src/LayerPlan.cpp | 5 | ||||
-rw-r--r-- | src/PrimeTower.cpp | 30 | ||||
-rw-r--r-- | src/SkirtBrim.cpp | 593 | ||||
-rw-r--r-- | src/TreeModelVolumes.cpp | 10 | ||||
-rw-r--r-- | src/Wireframe2gcode.cpp | 4 | ||||
-rw-r--r-- | src/raft.cpp | 3 | ||||
-rw-r--r-- | src/settings/PathConfigStorage.cpp | 1 | ||||
-rw-r--r-- | src/settings/Settings.cpp | 80 | ||||
-rw-r--r-- | src/sliceDataStorage.cpp | 146 | ||||
-rw-r--r-- | src/support.cpp | 28 | ||||
-rw-r--r-- | src/utils/polygon.cpp | 35 |
21 files changed, 1074 insertions, 343 deletions
diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 24c678063..5c1290bd6 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -618,8 +618,10 @@ public: * \param flow_ratio The ratio with which to multiply the extrusion amount * \param near_start_location Optional: Location near where to add the first line. If not provided the last position is used. * \param fan_speed optional fan speed override for this path + * \param reverse_print_direction Whether to reverse the optimized order and their printing direction. + * \param order_requirements Pairs where first needs to be printed before second. Pointers are pointing to elements of \p polygons */ - void addLinesByOptimizer(const Polygons& polygons, const GCodePathConfig& config, const SpaceFillType space_fill_type, const bool enable_travel_optimization = false, const coord_t wipe_dist = 0, const Ratio flow_ratio = 1.0, const std::optional<Point> near_start_location = std::optional<Point>(), const double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT, const bool reverse_print_direction = false); + void addLinesByOptimizer(const Polygons& polygons, const GCodePathConfig& config, const SpaceFillType space_fill_type, const bool enable_travel_optimization = false, const coord_t wipe_dist = 0, const Ratio flow_ratio = 1.0, const std::optional<Point> near_start_location = std::optional<Point>(), const double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT, const bool reverse_print_direction = false, const std::unordered_set<std::pair<ConstPolygonPointer, ConstPolygonPointer>>& order_requirements = PathOrderOptimizer<ConstPolygonPointer>::no_order_requirements); /*! * Add polygons to the g-code with monotonic order. diff --git a/include/PathOrderOptimizer.h b/include/PathOrderOptimizer.h index 2ceec9437..68dba02b6 100644 --- a/include/PathOrderOptimizer.h +++ b/include/PathOrderOptimizer.h @@ -365,8 +365,10 @@ protected: */ bool reverse_direction; +public: static const std::unordered_set<std::pair<PathType, PathType>> no_order_requirements; +protected: /*! * Order requirements on the paths. * For each pair the first needs to be printe before the second. diff --git a/include/PrimeTower.h b/include/PrimeTower.h index a2f6510b5..c0e50685b 100644 --- a/include/PrimeTower.h +++ b/include/PrimeTower.h @@ -47,7 +47,6 @@ public: bool would_have_actual_tower; //!< Whether there is an actual tower. bool multiple_extruders_on_first_layer; //!< Whether multiple extruders are allowed on the first layer of the prime tower (e.g. when a raft is there) Polygons outer_poly; //!< The outline of the outermost prime tower. - Polygons outer_poly_first_layer; //!< The outermost outline, plus optional brim on 'brim for prime tower' is enabled. /* * In which order, from outside to inside, will we be printing the prime @@ -67,6 +66,11 @@ public: PrimeTower(); /*! + * Check whether we actually use the prime tower. + */ + void checkUsed(const SliceDataStorage& storage); + + /*! * Generate the prime tower area to be used on each layer * * Fills \ref PrimeTower::inner_poly and sets \ref PrimeTower::middle diff --git a/include/SkirtBrim.h b/include/SkirtBrim.h index dfc3e2e2a..b79715180 100644 --- a/include/SkirtBrim.h +++ b/include/SkirtBrim.h @@ -5,6 +5,11 @@ #define SKIRT_BRIM_H #include "utils/Coord_t.h" +#include "ExtruderTrain.h" +#include "settings/EnumSettings.h" +#include "sliceDataStorage.h" + +#include <variant> namespace cura { @@ -12,10 +17,80 @@ namespace cura class Polygons; class SliceDataStorage; +constexpr coord_t min_brim_line_length = 3000u; //!< open polyline brim lines smaller than this will be removed + class SkirtBrim { +private: + /*! + * A helper class to store an offset yet to be performed on either an outline polygon, or based on an earlier generated brim line. + */ + struct Offset + { + Offset + ( + const std::variant<Polygons*, int>& reference_outline_or_index, + const bool external_only, + const coord_t offset_value, + const coord_t total_offset, + const size_t inset_idx, + const int extruder_nr, + const bool is_last + ) : + reference_outline_or_index(reference_outline_or_index), + external_only(external_only), + offset_value(offset_value), + total_offset(total_offset), + inset_idx(inset_idx), + extruder_nr(extruder_nr), + is_last(is_last) + {} + + std::variant<Polygons*, int> reference_outline_or_index; + bool external_only; //!< Wether to only offset outward from the reference polygons + coord_t offset_value; //!< Distance by which to offset from the reference + coord_t total_offset; //!< Total distance from the model + int inset_idx; //!< The outset index of this brimline + int extruder_nr; //!< The extruder by which to print this brim line + bool is_last; //!< Whether this is the last planned offset for this extruder. + }; + + /*! + * Defines an order on offsets (potentially from different extruders) based on how far the offset is from the original outline. + */ + static inline const auto OffsetSorter + { + [](const Offset& a, const Offset& b) + { + // Use extruder_nr in case both extruders have the same offset settings. + return a.total_offset != b.total_offset ? a.total_offset < b.total_offset : a.extruder_nr < b.extruder_nr; + } + }; + + SliceDataStorage& storage; //!< Where to retrieve settings and store brim lines. + const EPlatformAdhesion adhesion_type; //!< Whether we are generating brim, skirt, or raft + const bool has_ooze_shield; //!< Whether the meshgroup has an ooze shield + const bool has_draft_shield; //!< Whether the meshgroup has a draft shield + const std::vector<ExtruderTrain>& extruders; //!< The extruders of the current slice + const int extruder_count; //!< The total number of extruders + const std::vector<bool> extruder_is_used; //!< For each extruder whether it is actually used in this print + int first_used_extruder_nr; //!< The first extruder which is used + int skirt_brim_extruder_nr; //!< The extruder with which the skirt/brim is printed or -1 if printed with both + std::vector<bool> external_polys_only; //!< For each extruder whether to only generate brim on the outside + std::vector<coord_t> line_widths; //!< For each extruder the skirt/brim line width + std::vector<coord_t> skirt_brim_minimal_length; //!< For each extruder the minimal brim length + std::vector<int> line_count; //!< For each extruder the (minimal) number of brim lines to generate + std::vector<coord_t> gap; //!< For each extruder the gap between the part and the first brim/skirt line + public: /*! + * Precomputes some values used in several functions when calling \ref generate + * + * \param storage Storage containing the parts at the first layer. + */ + SkirtBrim(SliceDataStorage& storage); + + /*! * Generate skirt or brim (depending on parameters). * * When \p distance > 0 and \p count == 1 a skirt is generated, which has @@ -28,7 +103,41 @@ public: * \param primary_line_count Number of offsets / brim lines of the primary extruder. * \param set to false to force not doing brim generation for helper-structures (support and ooze/draft shields) */ - static void generate(SliceDataStorage& storage, Polygons first_layer_outline, const coord_t distance, size_t primary_line_count, const bool allow_helpers = true); + void generate(); + +private: + /*! + * Plan the offsets which we will be going to perform and put them in the right order. + * + * In order for brims of different materials to grow toward the middle, + * we need to perform the offsets alternatingly. + * We therefore first create all planned Offset objects, + * and then order them according to distance from the boundary. + * \param[out] starting_outlines The first layer outlines from which to compute the offsets. Returned as output parameter because pointers need to stay valid. + * \return An ordered list of offsets to perform in the order in which they are to be performed. + */ + std::vector<Offset> generateBrimOffsetPlan(std::vector<Polygons>& starting_outlines); + + /*! + * Generate the primary skirt/brim of the one skirt_brim_extruder or of all extruders simultaneously. + * + * \param[in,out] all_brim_offsets The offsets to perform. Adjusted when the minimal length constraint isn't met yet. + * \param[in,out] covered_area The area of the first layer covered by model or generated brim lines. + * \param[in,out] allowed_areas_per_extruder The difference between the machine bed area (offsetted by the nozzle offset) and the covered_area. + * \return The total length of the brim lines added by this method per extruder. + */ + std::vector<coord_t> generatePrimaryBrim(std::vector<Offset>& all_brim_offsets, Polygons& covered_area, std::vector<Polygons>& allowed_areas_per_extruder); + + /*! + * Generate the brim inside the ooze shield and draft shield + * + * \warning Adjusts brim_covered_area + * + * \param storage Storage containing the parts at the first layer. + * \param[in,out] brim_covered_area The area that was covered with brim before (in) and after (out) adding the shield brims + * \param[in,out] allowed_areas_per_extruder The difference between the machine areas and the \p covered_area + */ + void generateShieldBrim(Polygons& brim_covered_area, std::vector<Polygons>& allowed_areas_per_extruder); /*! * \brief Get the reference outline of the first layer around which to @@ -38,18 +147,57 @@ public: * in order to meet criteria for putting brim around the model as well as * around the support. * - * \param storage Storage containing the parts at the first layer. - * \param primary_line_count Number of offsets / brim lines of the primary - * extruder. - * \param is_skirt Whether a skirt is being generated vs a brim - * \param[out] first_layer_outline The resulting reference polygons + * \param extruder_nr The extruder for which to get the outlines. -1 to include outliens for all extruders + * \return The resulting reference polygons */ - static void getFirstLayerOutline(SliceDataStorage& storage, const size_t primary_line_count, const bool is_skirt, Polygons& first_layer_outline); + Polygons getFirstLayerOutline(const int extruder_nr = -1); -private: - static void generateSupportBrim(SliceDataStorage& storage, const bool merge_with_model_skirtbrim); + /*! + * The disallowed area around the internal holes of parts with other parts inside which would get an external brim. + * + * In order to prevent the external_only brim of a part inside another part to overlap with the internal holes of the outer part, + * we generate a disallowed area around those internal hole polygons. + * + * \param outline The full layer outlines + * \param extruder_nr The extruder for which to compute disallowed areas + * \return The disallowed areas + */ + Polygons getInternalHoleExclusionArea(const Polygons& outline, const int extruder_nr); /*! + * Generate a brim line with offset parameters given by \p offset from the \p starting_outlines and store it in the \ref storage. + * + * \warning Has side effects on \p covered_area, \p allowed_areas_per_extruder and \p total_length + * + * \param offset The parameters with which to perform the offset + * \param[in,out] covered_area The total area covered by the brims (and models) on the first layer. + * \param[in,out] allowed_areas_per_extruder The difference between the machine areas and the \p covered_area + * \param[out] result Where to store the resulting brim line + * \return The length of the added lines + */ + coord_t generateOffset(const Offset& offset, Polygons& covered_area, std::vector<Polygons>& allowed_areas_per_extruder, SkirtBrimLine& result); + + /*! + * Generate a skirt of extruders which don't yet comply with the minimum length requirement. + * + * This skirt goes directly adjacent to all primary brims. + * + * The skirt is stored in storage.skirt_brim. + * + * \param[in,out] covered_area The total area covered by the brims (and models) on the first layer. + * \param[in,out] allowed_areas_per_extruder The difference between the machine areas and the \p covered_area + * \param[in,out] total_length The total length of the brim lines for each extruder. + */ + void generateSecondarySkirtBrim(Polygons& covered_area, std::vector<Polygons>& allowed_areas_per_extruder, std::vector<coord_t>& total_length); + +public: + /*! + * Generate the brim which is printed from the outlines of the support inward. + */ + void generateSupportBrim(); + +private: + /*! * \brief Generate the skirt/brim lines around the model. * * \param start_distance The distance of the first outset from the parts at diff --git a/include/TreeModelVolumes.h b/include/TreeModelVolumes.h index 74f2b4cc3..98ff05734 100644 --- a/include/TreeModelVolumes.h +++ b/include/TreeModelVolumes.h @@ -129,7 +129,7 @@ private: * * \param a Polygons object representing the non-printable areas on and around the build platform */ - static Polygons calculateMachineBorderCollision(Polygon machine_border); + static Polygons calculateMachineBorderCollision(const Polygons&& machine_border); /*! * \brief Polygons representing the limits of the printable area of the @@ -199,4 +199,4 @@ private: } -#endif //TREEMODELVOLUMES_H
\ No newline at end of file +#endif //TREEMODELVOLUMES_H diff --git a/include/settings/PathConfigStorage.h b/include/settings/PathConfigStorage.h index 2bec85411..2480e345e 100644 --- a/include/settings/PathConfigStorage.h +++ b/include/settings/PathConfigStorage.h @@ -26,7 +26,6 @@ private: const size_t support_infill_extruder_nr; const size_t support_roof_extruder_nr; const size_t support_bottom_extruder_nr; - ExtruderTrain& skirt_brim_train; ExtruderTrain& raft_base_train; ExtruderTrain& raft_interface_train; ExtruderTrain& raft_surface_train; diff --git a/include/sliceDataStorage.h b/include/sliceDataStorage.h index d0cd4848a..fd71e91ee 100644 --- a/include/sliceDataStorage.h +++ b/include/sliceDataStorage.h @@ -299,6 +299,15 @@ public: Point getZSeamHint() const; }; +/*! + * Class to store all open polylines or closed polygons related to one outset index of brim/skirt. + */ +struct SkirtBrimLine +{ + Polygons open_polylines; + Polygons closed_polygons; +}; + class SliceDataStorage : public NoCopy { public: @@ -314,9 +323,9 @@ public: std::vector<RetractionConfig> extruder_switch_retraction_config_per_extruder; //!< Retraction config per extruder for when performing an extruder switch SupportStorage support; - - Polygons skirt_brim[MAX_EXTRUDERS]; //!< Skirt and brim polygons per extruder, ordered from inner to outer polygons. - size_t skirt_brim_max_locked_part_order[MAX_EXTRUDERS]; //!< Some parts (like skirt) always need to be printed before parts like support-brim, so lock 0..n for each extruder, where n is the value saved in this array. + + std::vector<SkirtBrimLine> skirt_brim[MAX_EXTRUDERS]; //!< Skirt/brim polygons per extruder, ordered from inner to outer polygons. + Polygons support_brim; //!< brim lines for support, going from the edge of the support inward. \note Not ordered by inset. Polygons raftOutline; //Storage for the outline of the raft. Will be filled with lines when the GCode is generated. Polygons primeRaftOutline; // ... the raft underneath the prime-tower will have to be printed first, if there is one. (When the raft has top layers with a different extruder for example.) @@ -351,9 +360,9 @@ public: * \param include_prime_tower Whether to include the prime tower in the * outline. * \param external_polys_only Whether to disregard all hole polygons. - * \param for_brim Whether the outline is to be used to construct the brim. + * \param extruder_nr (optional) only give back outlines for this extruder (where the walls are printed with this extruder) */ - Polygons getLayerOutlines(const LayerIndex layer_nr, const bool include_support, const bool include_prime_tower, const bool external_polys_only = false, const bool for_brim = false) const; + Polygons getLayerOutlines(const LayerIndex layer_nr, const bool include_support, const bool include_prime_tower, const bool external_polys_only = false, const int extruder_nr = -1) const; /*! * Get the extruders used. @@ -382,11 +391,10 @@ public: /*! * Gets the border of the usable print area for this machine. * - * \param adhesion_offset whether to offset the border by the adhesion width to account for brims, skirts and - * rafts, if present. - * \return a Polygon representing the usable area of the print bed. + * \param extruder_nr The extruder for which to return the allowed areas. -1 if the areas allowed for all extruders should be returned. + * \return the Polygons representing the usable area of the print bed. */ - Polygon getMachineBorder(bool adhesion_offset = false) const; + Polygons getMachineBorder(int extruder_nr = -1) const; private: /*! diff --git a/include/utils/polygon.h b/include/utils/polygon.h index d27f4c19e..4f612eeb1 100644 --- a/include/utils/polygon.h +++ b/include/utils/polygon.h @@ -419,6 +419,10 @@ public: { } + /*! + * Reserve a number of polygons to prevent reallocation and breakage of pointers. + * \param min_size The minimum size the new underlying array should have. + */ void reserve(size_t min_size) { path->reserve(min_size); @@ -754,6 +758,11 @@ public: return paths.size(); } + void reserve(size_t new_cap) + { + paths.reserve(new_cap); + } + /*! * Convenience function to check if the polygon has no points. * @@ -971,6 +980,11 @@ public: Polygons intersectionPolyLines(const Polygons& polylines, bool restitch = true, const coord_t max_stitch_distance = 10_mu) const; /*! + * Add the front to each polygon so that the polygon is represented as a polyline + */ + void toPolylines(); + + /*! * Split this poly line object into several line segment objects * and store them in the \p result */ @@ -1180,6 +1194,13 @@ public: std::vector<PolygonsPart> splitIntoParts(bool unionAll = false) const; /*! + * Sort the polygons into bins where each bin has polygons which are contained within one of the polygons in the previous bin. + * + * \warning When polygons are crossing each other the result is undefined. + */ + std::vector<Polygons> sortByNesting() const; + + /*! * Utility method for creating the tube (or 'donut') of a shape. * \param inner_offset Offset relative to the original shape-outline towards the inside of the shape. Sort-of like a negative normal offset, except it's the offset part that's kept, not the shape. * \param outer_offset Offset relative to the original shape-outline towards the outside of the shape. Comparable to normal offset. @@ -1196,6 +1217,7 @@ private: */ void removeEmptyHoles_processPolyTreeNode(const ClipperLib::PolyNode& node, const bool remove_holes, Polygons& ret) const; void splitIntoParts_processPolyTreeNode(ClipperLib::PolyNode* node, std::vector<PolygonsPart>& ret) const; + void sortByNesting_processPolyTreeNode(ClipperLib::PolyNode* node, const size_t nesting_idx, std::vector<Polygons>& ret) const; public: /*! diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index eda248645..1d430cf61 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -5,6 +5,7 @@ #include <limits> // numeric_limits #include <list> #include <optional> +#include <unordered_set> #include <boost/uuid/random_generator.hpp> //For generating a UUID. #include <boost/uuid/uuid_io.hpp> //For generating a UUID. @@ -360,19 +361,25 @@ size_t FffGcodeWriter::getStartExtruder(const SliceDataStorage& storage) { const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; const EPlatformAdhesion adhesion_type = mesh_group_settings.get<EPlatformAdhesion>("adhesion_type"); - const ExtruderTrain& skirt_brim_extruder = mesh_group_settings.get<ExtruderTrain&>("skirt_brim_extruder_nr"); + const int skirt_brim_extruder_nr = mesh_group_settings.get<int>("skirt_brim_extruder_nr"); + const ExtruderTrain* skirt_brim_extruder = (skirt_brim_extruder_nr < 0)? nullptr : &mesh_group_settings.get<ExtruderTrain&>("skirt_brim_extruder_nr"); size_t start_extruder_nr; - if (adhesion_type == EPlatformAdhesion::SKIRT && (skirt_brim_extruder.settings.get<int>("skirt_line_count") > 0 || skirt_brim_extruder.settings.get<coord_t>("skirt_brim_minimal_length") > 0)) + if (adhesion_type == EPlatformAdhesion::SKIRT + && skirt_brim_extruder + && (skirt_brim_extruder->settings.get<int>("skirt_line_count") > 0 || skirt_brim_extruder->settings.get<coord_t>("skirt_brim_minimal_length") > 0)) { - start_extruder_nr = skirt_brim_extruder.extruder_nr; + start_extruder_nr = skirt_brim_extruder->extruder_nr; } else if ((adhesion_type == EPlatformAdhesion::BRIM || mesh_group_settings.get<bool>("prime_tower_brim_enable")) - && (skirt_brim_extruder.settings.get<int>("brim_line_count") > 0 || skirt_brim_extruder.settings.get<coord_t>("skirt_brim_minimal_length") > 0)) + && skirt_brim_extruder + && (skirt_brim_extruder->settings.get<int>("brim_line_count") > 0 || skirt_brim_extruder->settings.get<coord_t>("skirt_brim_minimal_length") > 0)) { - start_extruder_nr = skirt_brim_extruder.extruder_nr; + start_extruder_nr = skirt_brim_extruder->extruder_nr; } - else if (adhesion_type == EPlatformAdhesion::RAFT) + else if (adhesion_type == EPlatformAdhesion::RAFT + && skirt_brim_extruder + ) { start_extruder_nr = mesh_group_settings.get<ExtruderTrain&>("raft_base_extruder_nr").extruder_nr; } @@ -1159,12 +1166,14 @@ void FffGcodeWriter::processSkirtBrim(const SliceDataStorage& storage, LayerPlan { return; } - const Polygons& original_skirt_brim = storage.skirt_brim[extruder_nr]; gcode_layer.setSkirtBrimIsPlanned(extruder_nr); + + const std::vector<SkirtBrimLine>& original_skirt_brim = storage.skirt_brim[extruder_nr]; if (original_skirt_brim.size() == 0) { return; } + // Start brim close to the prime location const ExtruderTrain& train = Application::getInstance().current_slice->scene.extruders[extruder_nr]; Point start_close_to; @@ -1179,72 +1188,113 @@ void FffGcodeWriter::processSkirtBrim(const SliceDataStorage& storage, LayerPlan start_close_to = gcode_layer.getLastPlannedPositionOrStartingPosition(); } - Polygons first_skirt_brim; - Polygons skirt_brim; - // Plan parts that need to be printed first: for example, skirt needs to be printed before support-brim. - for (size_t i_part = 0; i_part < original_skirt_brim.size(); ++i_part) + // figure out order requirements + struct BrimLineReference { - if (i_part < storage.skirt_brim_max_locked_part_order[extruder_nr]) - { - first_skirt_brim.add(original_skirt_brim[i_part]); - } - else - { - skirt_brim.add(original_skirt_brim[i_part]); - } - } - - const auto brim_zseam_config = ZSeamConfig(EZSeamType::SKIRT_BRIM); - - if (! first_skirt_brim.empty()) + const size_t inset_idx; + ConstPolygonPointer poly; + }; + + size_t total_line_count = 0; + for (const SkirtBrimLine& line : storage.skirt_brim[extruder_nr]) { - gcode_layer.addTravel(first_skirt_brim.back().closestPointTo(start_close_to)); - gcode_layer.addPolygonsByOptimizer(first_skirt_brim, gcode_layer.configs_storage.skirt_brim_config_per_extruder[extruder_nr], brim_zseam_config); + total_line_count += line.closed_polygons.size(); + total_line_count += line.open_polylines.size(); } - - if (skirt_brim.empty()) + Polygons all_brim_lines; + + // Add the support brim before the below algorithm which takes order requirements into account + // For support brim we don't care about the order, because support doesn't need to be accurate. + const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; + if (extruder_nr == mesh_group_settings.get<ExtruderTrain&>("support_extruder_nr_layer_0").extruder_nr) { - return; + total_line_count += storage.support_brim.size(); + Polygons support_brim_lines = storage.support_brim; + support_brim_lines.toPolylines(); + all_brim_lines = support_brim_lines; } + + all_brim_lines.reserve(total_line_count); - if (train.settings.get<bool>("brim_outside_only")) + const coord_t line_w = train.settings.get<coord_t>("skirt_brim_line_width") * train.settings.get<Ratio>("initial_layer_line_width_factor"); + const coord_t searching_radius = line_w * 2; + using GridT = SparsePointGridInclusive<BrimLineReference>; + GridT grid(searching_radius); + + for (size_t inset_idx = 0; inset_idx < storage.skirt_brim[extruder_nr].size(); inset_idx++) { - gcode_layer.addTravel(skirt_brim.back().closestPointTo(start_close_to)); - gcode_layer.addPolygonsByOptimizer(skirt_brim, gcode_layer.configs_storage.skirt_brim_config_per_extruder[extruder_nr], brim_zseam_config); + const SkirtBrimLine& offset = storage.skirt_brim[extruder_nr][inset_idx]; + for (bool closed : { false, true }) + { + for (ConstPolygonRef line : closed? offset.closed_polygons : offset.open_polylines) + { + if (line.size() <= 1) + { + continue; + } + all_brim_lines.emplace_back(line); + if (closed) + { // add closing segment + all_brim_lines.back().add(line.front()); + } + ConstPolygonPointer pp(all_brim_lines.back()); + for (Point p : line) + { + grid.insert(p, BrimLineReference{inset_idx, pp}); + } + } + } } - else + + const Settings& global_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; + bool inner_to_outer = global_settings.get<EPlatformAdhesion>("adhesion_type") == EPlatformAdhesion::BRIM && // for skirt outer to inner is faster + train.settings.get<coord_t>("brim_gap") < line_w; // for a large brim gap it's not so bad for the overextrudate to propagate inward. + std::unordered_set<std::pair<ConstPolygonPointer, ConstPolygonPointer>> order_requirements; + for (const std::pair<SquareGrid::GridPoint, SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem<BrimLineReference>>& p : grid) { - Polygons outer_brim, inner_brim; - for (unsigned int index = 0; index < skirt_brim.size(); index++) + const BrimLineReference& here = p.second.val; + Point loc_here = p.second.point; + std::vector<BrimLineReference> nearby_verts = grid.getNearbyVals(loc_here, searching_radius); + for (const BrimLineReference& nearby : nearby_verts) { - ConstPolygonRef polygon = skirt_brim[index]; - if (polygon.area() > 0) + if (nearby.poly == here.poly || nearby.inset_idx == here.inset_idx) + { + continue; + } + if ((nearby.inset_idx > here.inset_idx + 1) || (here.inset_idx > nearby.inset_idx + 1)) + { + continue; // not directly adjacent + } + if ((nearby.inset_idx < here.inset_idx) == inner_to_outer) { - outer_brim.add(polygon); + order_requirements.emplace(std::make_pair(nearby.poly, here.poly)); } else { - inner_brim.add(polygon); + order_requirements.emplace(std::make_pair(here.poly, nearby.poly)); } } - - if (! outer_brim.empty()) - { - gcode_layer.addTravel(outer_brim.back().closestPointTo(start_close_to)); - gcode_layer.addPolygonsByOptimizer(outer_brim, gcode_layer.configs_storage.skirt_brim_config_per_extruder[extruder_nr], brim_zseam_config); - } - - if (! inner_brim.empty()) - { - // Add polygon in reverse order - const coord_t wall_0_wipe_dist = 0; - const bool spiralize = false; - const float flow_ratio = 1.0; - const bool always_retract = false; - const bool reverse_order = true; - gcode_layer.addPolygonsByOptimizer(inner_brim, gcode_layer.configs_storage.skirt_brim_config_per_extruder[extruder_nr], brim_zseam_config, wall_0_wipe_dist, spiralize, flow_ratio, always_retract, reverse_order); - } } + assert(all_brim_lines.size() == total_line_count); // Otherwise pointers would have gotten invalidated + + const bool enable_travel_optimization = true; // Use the combing outline while deciding in which order to print the lines. Can't hurt for only one layer. + const coord_t wipe_dist = 0u; + const Ratio flow_ratio = 1.0; + const double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT; + const bool reverse_print_direction = false; + gcode_layer.addLinesByOptimizer + ( + all_brim_lines, + gcode_layer.configs_storage.skirt_brim_config_per_extruder[extruder_nr], + SpaceFillType::PolyLines, + enable_travel_optimization, + wipe_dist, + flow_ratio, + start_close_to, + fan_speed, + reverse_print_direction, + order_requirements + ); } void FffGcodeWriter::processOozeShield(const SliceDataStorage& storage, LayerPlan& gcode_layer) const diff --git a/src/FffPolygonGenerator.cpp b/src/FffPolygonGenerator.cpp index 56addd9e6..8bf5ebca8 100644 --- a/src/FffPolygonGenerator.cpp +++ b/src/FffPolygonGenerator.cpp @@ -407,6 +407,7 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper& computePrintHeightStatistics(storage); // handle helpers + storage.primeTower.checkUsed(storage); storage.primeTower.generateGroundpoly(); storage.primeTower.generatePaths(storage); storage.primeTower.subtractFromSupport(storage); @@ -860,8 +861,12 @@ void FffPolygonGenerator::computePrintHeightStatistics(SliceDataStorage& storage case EPlatformAdhesion::SKIRT: case EPlatformAdhesion::BRIM: { - const size_t skirt_brim_extruder_nr = mesh_group_settings.get<ExtruderTrain&>("skirt_brim_extruder_nr").extruder_nr; - max_print_height_per_extruder[skirt_brim_extruder_nr] = std::max(0, max_print_height_per_extruder[skirt_brim_extruder_nr]); // Includes layer 0. + const std::vector<ExtruderTrain*> skirt_brim_extruder_trains = mesh_group_settings.get<std::vector<ExtruderTrain*>>("skirt_brim_extruder_nr"); + for (ExtruderTrain* train : skirt_brim_extruder_trains) + { + const size_t skirt_brim_extruder_nr = train->extruder_nr; + max_print_height_per_extruder[skirt_brim_extruder_nr] = std::max(0, max_print_height_per_extruder[skirt_brim_extruder_nr]); // Includes layer 0. + } break; } case EPlatformAdhesion::RAFT: @@ -928,6 +933,23 @@ void FffPolygonGenerator::processOozeShield(SliceDataStorage& storage) { storage.oozeShield[layer_nr].removeSmallAreas(largest_printed_area); } + if (mesh_group_settings.get<bool>("prime_tower_enable")) + { + coord_t max_line_width = 0; + { // compute max_line_width + const std::vector<bool> extruder_is_used = storage.getExtrudersUsed(); + const auto& extruders = Application::getInstance().current_slice->scene.extruders; + for (int extruder_nr = 0; extruder_nr < int(extruders.size()); extruder_nr++) + { + if ( ! extruder_is_used[extruder_nr]) continue; + max_line_width = std::max(max_line_width, extruders[extruder_nr].settings.get<coord_t>("skirt_brim_line_width")); + } + } + for (LayerIndex layer_nr = 0; layer_nr <= storage.max_print_height_second_to_last_extruder; layer_nr++) + { + storage.oozeShield[layer_nr] = storage.oozeShield[layer_nr].difference(storage.primeTower.outer_poly.offset(max_line_width / 2)); + } + } } void FffPolygonGenerator::processDraftShield(SliceDataStorage& storage) @@ -962,58 +984,49 @@ void FffPolygonGenerator::processDraftShield(SliceDataStorage& storage) maximum_deviation = std::min(maximum_deviation, extruder.settings.get<coord_t>("meshfix_maximum_deviation")); } storage.draft_protection_shield = Simplify(maximum_resolution, maximum_deviation, 0).polygon(storage.draft_protection_shield); + if (mesh_group_settings.get<bool>("prime_tower_enable")) + { + coord_t max_line_width = 0; + { // compute max_line_width + const std::vector<bool> extruder_is_used = storage.getExtrudersUsed(); + const auto& extruders = Application::getInstance().current_slice->scene.extruders; + for (int extruder_nr = 0; extruder_nr < int(extruders.size()); extruder_nr++) + { + if ( ! extruder_is_used[extruder_nr]) continue; + max_line_width = std::max(max_line_width, extruders[extruder_nr].settings.get<coord_t>("skirt_brim_line_width")); + } + } + storage.draft_protection_shield = storage.draft_protection_shield.difference(storage.primeTower.outer_poly.offset(max_line_width / 2)); + } } void FffPolygonGenerator::processPlatformAdhesion(SliceDataStorage& storage) { const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; - ExtruderTrain& train = mesh_group_settings.get<ExtruderTrain&>("skirt_brim_extruder_nr"); - - Polygons first_layer_outline; - coord_t primary_line_count; - EPlatformAdhesion adhesion_type = mesh_group_settings.get<EPlatformAdhesion>("adhesion_type"); - if (adhesion_type == EPlatformAdhesion::SKIRT) + if (adhesion_type == EPlatformAdhesion::RAFT) { - primary_line_count = train.settings.get<size_t>("skirt_line_count"); - SkirtBrim::getFirstLayerOutline(storage, primary_line_count, true, first_layer_outline); - SkirtBrim::generate(storage, first_layer_outline, train.settings.get<coord_t>("skirt_gap"), primary_line_count); + Raft::generate(storage); + return; } - // Generate any brim for the prime tower, should happen _after_ any skirt, but _before_ any other brim (since FffGCodeWriter assumes that the outermost contour is last). - if (adhesion_type != EPlatformAdhesion::RAFT && storage.primeTower.enabled && mesh_group_settings.get<bool>("prime_tower_brim_enable")) + SkirtBrim skirt_brim(storage); + skirt_brim.generate(); + + if (mesh_group_settings.get<bool>("support_brim_enable")) { - constexpr bool dont_allow_helpers = false; - SkirtBrim::generate(storage, storage.primeTower.outer_poly, 0, train.settings.get<size_t>("brim_line_count"), dont_allow_helpers); + skirt_brim.generateSupportBrim(); } - switch (adhesion_type) + for (const auto& extruder : Application::getInstance().current_slice->scene.extruders) { - case EPlatformAdhesion::SKIRT: - // Already done, because of prime-tower-brim & ordering, see above. - break; - case EPlatformAdhesion::BRIM: - primary_line_count = train.settings.get<size_t>("brim_line_count"); - SkirtBrim::getFirstLayerOutline(storage, primary_line_count, false, first_layer_outline); - SkirtBrim::generate(storage, first_layer_outline, 0, primary_line_count); - break; - case EPlatformAdhesion::RAFT: - Raft::generate(storage); - break; - case EPlatformAdhesion::NONE: - if (mesh_group_settings.get<bool>("support_brim_enable")) + Simplify simplifier(extruder.settings); + for (auto skirt_brim_line : storage.skirt_brim[extruder.extruder_nr]) { - SkirtBrim::generate(storage, Polygons(), 0, 0); + skirt_brim_line.closed_polygons = simplifier.polygon(skirt_brim_line.closed_polygons); + skirt_brim_line.open_polylines = simplifier.polyline(skirt_brim_line.open_polylines); } - break; - } - - // Also apply maximum_[deviation|resolution] to skirt/brim. - Simplify simplifier(train.settings); - for (Polygons& polygons : storage.skirt_brim) - { - polygons = simplifier.polygon(polygons); } } diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 6a5a5124e..ecc67cdb4 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1132,7 +1132,8 @@ void LayerPlan::addLinesByOptimizer(const Polygons& polygons, const Ratio flow_ratio, const std::optional<Point> near_start_location, const double fan_speed, - const bool reverse_print_direction) + const bool reverse_print_direction, + const std::unordered_set<std::pair<ConstPolygonPointer, ConstPolygonPointer>>& order_requirements) { Polygons boundary; if (enable_travel_optimization && ! comb_boundary_minimum.empty()) @@ -1157,7 +1158,7 @@ void LayerPlan::addLinesByOptimizer(const Polygons& polygons, boundary = Simplify(MM2INT(0.1), MM2INT(0.1), 0).polygon(boundary); } constexpr bool detect_loops = true; - PathOrderOptimizer<ConstPolygonPointer> order_optimizer(near_start_location.value_or(getLastPlannedPositionOrStartingPosition()), ZSeamConfig(), detect_loops, &boundary, reverse_print_direction); + PathOrderOptimizer<ConstPolygonPointer> order_optimizer(near_start_location.value_or(getLastPlannedPositionOrStartingPosition()), ZSeamConfig(), detect_loops, &boundary, reverse_print_direction, order_requirements); for (size_t line_idx = 0; line_idx < polygons.size(); line_idx++) { order_optimizer.addPolyline(polygons[line_idx]); diff --git a/src/PrimeTower.cpp b/src/PrimeTower.cpp index 3bebe9758..582fb95bc 100644 --- a/src/PrimeTower.cpp +++ b/src/PrimeTower.cpp @@ -62,6 +62,20 @@ PrimeTower::PrimeTower() }); } +void PrimeTower::checkUsed(const SliceDataStorage& storage) +{ + std::vector<bool> extruder_is_used = storage.getExtrudersUsed(); + size_t used_extruder_count = 0; + for (bool is_used : extruder_is_used) + { + used_extruder_count += is_used; + } + if (used_extruder_count <= 1) + { + enabled = false; + } +} + void PrimeTower::generateGroundpoly() { if (!enabled) @@ -71,24 +85,14 @@ void PrimeTower::generateGroundpoly() const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; const coord_t tower_size = mesh_group_settings.get<coord_t>("prime_tower_size"); - - const Settings& brim_extruder_settings = mesh_group_settings.get<ExtruderTrain&>("skirt_brim_extruder_nr").settings; - const bool has_raft = (mesh_group_settings.get<EPlatformAdhesion>("adhesion_type") == EPlatformAdhesion::RAFT); - const bool has_prime_brim = mesh_group_settings.get<bool>("prime_tower_brim_enable"); - const coord_t offset = (has_raft || ! has_prime_brim) ? 0 : - brim_extruder_settings.get<size_t>("brim_line_count") * - brim_extruder_settings.get<coord_t>("skirt_brim_line_width") * - brim_extruder_settings.get<Ratio>("initial_layer_line_width_factor"); - - const coord_t x = mesh_group_settings.get<coord_t>("prime_tower_position_x") - offset; - const coord_t y = mesh_group_settings.get<coord_t>("prime_tower_position_y") - offset; + + const coord_t x = mesh_group_settings.get<coord_t>("prime_tower_position_x"); + const coord_t y = mesh_group_settings.get<coord_t>("prime_tower_position_y"); const coord_t tower_radius = tower_size / 2; outer_poly.add(PolygonUtils::makeCircle(Point(x - tower_radius, y + tower_radius), tower_radius, TAU / CIRCLE_RESOLUTION)); middle = Point(x - tower_size / 2, y + tower_size / 2); post_wipe_point = Point(x - tower_size / 2, y + tower_size / 2); - - outer_poly_first_layer = outer_poly.offset(offset); } void PrimeTower::generatePaths(const SliceDataStorage& storage) diff --git a/src/SkirtBrim.cpp b/src/SkirtBrim.cpp index 9313f98e9..f1cebf85d 100644 --- a/src/SkirtBrim.cpp +++ b/src/SkirtBrim.cpp @@ -3,29 +3,389 @@ #include <spdlog/spdlog.h> +#include "SkirtBrim.h" + #include "Application.h" #include "ExtruderTrain.h" -#include "SkirtBrim.h" #include "Slice.h" #include "settings/types/Ratio.h" #include "sliceDataStorage.h" #include "support.h" #include "utils/Simplify.h" //Simplifying the brim/skirt at every inset. +#include "settings/EnumSettings.h" +#include "utils/PolylineStitcher.h" namespace cura { -void SkirtBrim::getFirstLayerOutline(SliceDataStorage& storage, const size_t primary_line_count, const bool is_skirt, Polygons& first_layer_outline) +SkirtBrim::SkirtBrim(SliceDataStorage& storage) : + storage(storage), + adhesion_type(Application::getInstance().current_slice->scene.current_mesh_group->settings.get<EPlatformAdhesion>("adhesion_type")), + has_ooze_shield(storage.oozeShield.size() > 0 && storage.oozeShield[0].size() > 0), + has_draft_shield(storage.draft_protection_shield.size() > 0), + extruders(Application::getInstance().current_slice->scene.extruders), + extruder_count(extruders.size()), + extruder_is_used(storage.getExtrudersUsed()) +{ + first_used_extruder_nr = 0; + for (int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++) + { + if (extruder_is_used[extruder_nr]) + { + first_used_extruder_nr = extruder_nr; + break; + } + } + skirt_brim_extruder_nr = Application::getInstance().current_slice->scene.current_mesh_group->settings.get<int>("skirt_brim_extruder_nr"); + if (skirt_brim_extruder_nr == -1 && adhesion_type == EPlatformAdhesion::SKIRT) + { // Skirt is always printed with all extruders in order to satisfy minimum legnth constraint + // NOTE: the line count will only be satisfied for the first extruder used. + skirt_brim_extruder_nr = first_used_extruder_nr; + } + + line_widths.resize(extruder_count); + skirt_brim_minimal_length.resize(extruder_count); + external_polys_only.resize(extruder_count); + line_count.resize(extruder_count); + gap.resize(extruder_count); + for (int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++) + { + if (!extruder_is_used[extruder_nr]) + { + continue; + } + const ExtruderTrain& extruder = extruders[extruder_nr]; + + line_widths[extruder_nr] = extruder.settings.get<coord_t>("skirt_brim_line_width") * extruder.settings.get<Ratio>("initial_layer_line_width_factor"); + skirt_brim_minimal_length[extruder_nr] = extruder.settings.get<coord_t>("skirt_brim_minimal_length"); + external_polys_only[extruder_nr] = adhesion_type == EPlatformAdhesion::SKIRT || extruder.settings.get<bool>("brim_outside_only"); + line_count[extruder_nr] = extruder.settings.get<int>(adhesion_type == EPlatformAdhesion::BRIM ? "brim_line_count" : "skirt_line_count"); + gap[extruder_nr] = extruder.settings.get<coord_t>(adhesion_type == EPlatformAdhesion::BRIM ? "brim_gap" : "skirt_gap"); + } +} + + +std::vector<SkirtBrim::Offset> SkirtBrim::generateBrimOffsetPlan(std::vector<Polygons>& starting_outlines) +{ + std::vector<Offset> all_brim_offsets; + + if (skirt_brim_extruder_nr >= 0) + { + starting_outlines[skirt_brim_extruder_nr] = getFirstLayerOutline(); + } + else + { + for (int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++) + { + if (! extruder_is_used[extruder_nr]) + { + continue; + } + starting_outlines[extruder_nr] = getFirstLayerOutline(extruder_nr); + } + } + + for (int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++) + { + if (!extruder_is_used[extruder_nr] || (skirt_brim_extruder_nr >= 0 && extruder_nr != skirt_brim_extruder_nr)) + { + continue; // only include offsets for brim extruder + } + + for (int line_idx = 0; line_idx < line_count[extruder_nr]; line_idx++) + { + const bool is_last = line_idx == line_count[extruder_nr] - 1; + coord_t offset = gap[extruder_nr] + line_widths[extruder_nr] / 2 + line_widths[extruder_nr] * line_idx; + if (line_idx == 0) + { + all_brim_offsets.emplace_back(&starting_outlines[extruder_nr], external_polys_only[extruder_nr], offset, offset, line_idx, extruder_nr, is_last); + } + else + { + all_brim_offsets.emplace_back(line_idx - 1, external_polys_only[extruder_nr], line_widths[extruder_nr], offset, line_idx, extruder_nr, is_last); + } + } + } + + const Settings& global_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; + const bool prime_tower_brim_enable = global_settings.get<bool>("prime_tower_brim_enable"); + if (adhesion_type == EPlatformAdhesion::SKIRT && prime_tower_brim_enable && storage.primeTower.enabled) + { + const int extruder_nr = storage.primeTower.extruder_order[0]; + const ExtruderTrain& extruder = extruders[extruder_nr]; + int line_count = extruder.settings.get<int>("brim_line_count"); + coord_t gap = extruder.settings.get<coord_t>("brim_gap"); + for (int line_idx = 0; line_idx < line_count; line_idx++) + { + const bool is_last = line_idx == line_count - 1; + coord_t offset = gap + line_widths[extruder_nr] / 2 + line_widths[extruder_nr] * line_idx; + all_brim_offsets.emplace_back(&storage.primeTower.outer_poly, external_polys_only[extruder_nr], offset, offset, line_idx, extruder_nr, is_last); + } + } + + std::sort(all_brim_offsets.begin(), all_brim_offsets.end(), OffsetSorter); + + return all_brim_offsets; +} + +void SkirtBrim::generate() +{ + std::vector<Polygons> starting_outlines(extruder_count); + std::vector<Offset> all_brim_offsets = generateBrimOffsetPlan(starting_outlines); + + coord_t max_offset = 0; + for (const Offset& offset : all_brim_offsets) + { + max_offset = std::max(max_offset, offset.offset_value); + } + + constexpr LayerIndex layer_nr = 0; + const bool include_support = true; + Polygons covered_area = storage.getLayerOutlines(layer_nr, include_support, /*include_prime_tower*/ true, /*external_polys_only*/ false); + + std::vector<Polygons> allowed_areas_per_extruder(extruder_count); + for (int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++) + { + if (! extruder_is_used[extruder_nr]) + { + continue; + } + Polygons machine_area = storage.getMachineBorder(extruder_nr); + allowed_areas_per_extruder[extruder_nr] = machine_area.difference(covered_area); + if (external_polys_only[extruder_nr]) + { + // Expand covered area on inside of holes when external_only is enabled for any extruder, + // so that the brim lines don't overlap with the holes by half the line width + allowed_areas_per_extruder[extruder_nr] = allowed_areas_per_extruder[extruder_nr].difference(getInternalHoleExclusionArea(covered_area, extruder_nr)); + } + } + + std::vector<coord_t> total_length = generatePrimaryBrim(all_brim_offsets, covered_area, allowed_areas_per_extruder); + + // ooze/draft shield brim + generateShieldBrim(covered_area, allowed_areas_per_extruder); + + { // only allow secondary skirt/brim to appear on the very outside + covered_area = covered_area.getOutsidePolygons(); + for (int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++) + { + allowed_areas_per_extruder[extruder_nr] = allowed_areas_per_extruder[extruder_nr].difference(covered_area); + } + } + + // Secondary brim of all other materials which don;t meet minimum length constriant yet + generateSecondarySkirtBrim(covered_area, allowed_areas_per_extruder, total_length); + + // simplify paths to prevent buffer unnerruns in firmware + const Settings& global_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; + const coord_t maximum_resolution = global_settings.get<coord_t>("meshfix_maximum_resolution"); + const coord_t maximum_deviation = global_settings.get<coord_t>("meshfix_maximum_deviation"); + for (int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++) + { + for (SkirtBrimLine& line : storage.skirt_brim[extruder_nr]) + { + constexpr coord_t max_area_dev = 0u; // No area deviation applied + line.open_polylines = Simplify(maximum_resolution, maximum_deviation, max_area_dev).polyline(line.open_polylines); + line.closed_polygons = Simplify(maximum_resolution, maximum_deviation, max_area_dev).polygon(line.closed_polygons); + } + } +} + +std::vector<coord_t> SkirtBrim::generatePrimaryBrim(std::vector<Offset>& all_brim_offsets, Polygons& covered_area, std::vector<Polygons>& allowed_areas_per_extruder) +{ + std::vector<coord_t> total_length(extruder_count, 0u); + + for (size_t offset_idx = 0; offset_idx < all_brim_offsets.size(); offset_idx++) + { + Offset& offset = all_brim_offsets[offset_idx]; + if (storage.skirt_brim[offset.extruder_nr].size() <= offset.inset_idx) + { + storage.skirt_brim[offset.extruder_nr].resize(offset.inset_idx + 1); + } + SkirtBrimLine& output_location = storage.skirt_brim[offset.extruder_nr][offset.inset_idx]; + coord_t added_length = generateOffset(offset, covered_area, allowed_areas_per_extruder, output_location); + if (! added_length) + { // no more place for more brim. Trying to satisfy minimum length constraint with generateSecondarySkirtBrim + break; + } + total_length[offset.extruder_nr] += added_length; + + if + ( + offset.is_last && + total_length[offset.extruder_nr] < skirt_brim_minimal_length[offset.extruder_nr] && // This was the last offset of this extruder, but the brim lines don't meet minimal length yet + total_length[offset.extruder_nr] > 0u // No lines got added; we have no extrusion lines to build on + ) + { + offset.is_last = false; + constexpr bool is_last = true; + all_brim_offsets.emplace_back + ( + offset.inset_idx, + external_polys_only[offset.extruder_nr], + line_widths[offset.extruder_nr], + offset.total_offset + line_widths[offset.extruder_nr], + offset.inset_idx + 1, + offset.extruder_nr, + is_last + ); + std::sort(all_brim_offsets.begin() + offset_idx + 1, all_brim_offsets.end(), OffsetSorter); // reorder remaining offsets + } + } + return total_length; +} + +Polygons SkirtBrim::getInternalHoleExclusionArea(const Polygons& outline, const int extruder_nr) +{ + assert(extruder_nr >= 0); + const Settings& settings = Application::getInstance().current_slice->scene.extruders[extruder_nr].settings; + // If brim is external_only, the distance between the external brim of a part inside a hole and the inside hole of the outer part. + const coord_t hole_brim_distance = settings.get<coord_t>("brim_inside_margin"); + + Polygons ret; + std::vector<PolygonsPart> parts = outline.splitIntoParts(); + for (const PolygonsPart& part : parts) + { + for (size_t hole_idx = 1; hole_idx < part.size(); hole_idx++) + { + Polygon hole_poly = part[hole_idx]; + hole_poly.reverse(); + Polygons disallowed_region = hole_poly.offset(10u).difference(hole_poly.offset( - line_widths[extruder_nr] / 2 - hole_brim_distance)); + ret = ret.unionPolygons(disallowed_region); + } + } + return ret; +} + +coord_t SkirtBrim::generateOffset(const Offset& offset, Polygons& covered_area, std::vector<Polygons>& allowed_areas_per_extruder, SkirtBrimLine& result) +{ + coord_t length_added; + Polygons brim; + Polygons newly_covered; + { + if (std::holds_alternative<Polygons*>(offset.reference_outline_or_index)) + { + Polygons* reference_outline = std::get<Polygons*>(offset.reference_outline_or_index); + if (offset.external_only) + { // prevent unioning of external polys enclosed by other parts, e.g. a small part inside a hollow cylinder. + for (Polygons& polys : reference_outline->sortByNesting()) + { // offset external polygons of islands contained within another part in each batch + for (PolygonRef poly : polys) + { + if (poly.area() < 0) + { + poly.reverse(); + } + } + brim.add(polys.offset(offset.offset_value, ClipperLib::jtRound)); + newly_covered.add(polys.offset(offset.offset_value + line_widths[offset.extruder_nr] / 2, ClipperLib::jtRound)); + for (PolygonRef poly : polys) + { + poly.reverse(); + } + newly_covered.add(polys); // don't remove area inside external polygon + } + } + else + { + brim = reference_outline->offset(offset.offset_value, ClipperLib::jtRound); + newly_covered = reference_outline->offset(offset.offset_value + line_widths[offset.extruder_nr] / 2, ClipperLib::jtRound); + } + } + else + { + const int reference_idx = std::get<int>(offset.reference_outline_or_index); + Polygons polylines = storage.skirt_brim[offset.extruder_nr][reference_idx].closed_polygons; + polylines.toPolylines(); + polylines.add(storage.skirt_brim[offset.extruder_nr][reference_idx].open_polylines); + brim.add(polylines.offsetPolyLine(line_widths[offset.extruder_nr], ClipperLib::jtRound)); + newly_covered.add(polylines.offsetPolyLine(line_widths[offset.extruder_nr] * 3 / 2, ClipperLib::jtRound)); + } + } + + { // limit brim lines to allowed areas, stitch them and store them in the result + brim = Simplify(Application::getInstance().current_slice->scene.extruders[offset.extruder_nr].settings).polygon(brim); + brim.toPolylines(); + Polygons brim_lines = allowed_areas_per_extruder[offset.extruder_nr].intersectionPolyLines(brim, false); + length_added = brim_lines.polyLineLength(); + + const coord_t max_stitch_distance = line_widths[offset.extruder_nr]; + PolylineStitcher<Polygons, Polygon, Point>::stitch(brim_lines, result.open_polylines, result.closed_polygons, max_stitch_distance); + + // clean up too small lines + for (size_t line_idx = 0; line_idx < result.open_polylines.size(); ) + { + PolygonRef line = result.open_polylines[line_idx]; + if (line.shorterThan(min_brim_line_length)) + { + result.open_polylines.remove(line_idx); + } + else + { + line_idx++; + } + } + } + + { // update allowed_areas_per_extruder + for (int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++) + { + if (! extruder_is_used[extruder_nr]) + { + continue; + } + covered_area = covered_area.unionPolygons(newly_covered.unionPolygons()); + allowed_areas_per_extruder[extruder_nr] = allowed_areas_per_extruder[extruder_nr].difference(covered_area); + } + } + return length_added; +} + +Polygons SkirtBrim::getFirstLayerOutline(const int extruder_nr /* = -1 */) { - const ExtruderTrain& train = Application::getInstance().current_slice->scene.current_mesh_group->settings.get<ExtruderTrain&>("skirt_brim_extruder_nr"); - const ExtruderTrain& support_infill_extruder = Application::getInstance().current_slice->scene.current_mesh_group->settings.get<ExtruderTrain&>("support_infill_extruder_nr"); - const bool external_only = is_skirt || train.settings.get<bool>("brim_outside_only"); // Whether to include holes or not. Skirt doesn't have any holes. + Polygons first_layer_outline; + Settings& global_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; + int reference_extruder_nr = skirt_brim_extruder_nr; + assert( ! (reference_extruder_nr == -1 && extruder_nr == -1) && "We should only request the outlines of all layers when the brim is being generated for only one material"); + if (reference_extruder_nr == -1) + { + reference_extruder_nr = extruder_nr; + } + const int primary_line_count = line_count[reference_extruder_nr]; + const bool external_only = adhesion_type == EPlatformAdhesion::SKIRT || external_polys_only[reference_extruder_nr]; // Whether to include holes or not. Skirt doesn't have any holes. const LayerIndex layer_nr = 0; - if (is_skirt) + if (adhesion_type == EPlatformAdhesion::SKIRT) { constexpr bool include_support = true; - constexpr bool include_prime_tower = true; - first_layer_outline = storage.getLayerOutlines(layer_nr, include_support, include_prime_tower, external_only); + const bool skirt_around_prime_tower_brim = storage.primeTower.enabled && global_settings.get<bool>("prime_tower_brim_enable"); + const bool include_prime_tower = ! skirt_around_prime_tower_brim; // include manually otherwise + first_layer_outline = storage.getLayerOutlines(layer_nr, include_support, include_prime_tower, external_only, extruder_nr); + if (skirt_around_prime_tower_brim) + { + const int prime_tower_brim_extruder_nr = storage.primeTower.extruder_order[0]; + const ExtruderTrain& prime_tower_brim_extruder = extruders[prime_tower_brim_extruder_nr]; + int line_count = prime_tower_brim_extruder.settings.get<int>("brim_line_count"); + coord_t tower_gap = prime_tower_brim_extruder.settings.get<coord_t>("brim_gap"); + coord_t brim_width = tower_gap + line_count * line_widths[prime_tower_brim_extruder_nr]; + first_layer_outline = first_layer_outline.unionPolygons(storage.primeTower.outer_poly.offset(brim_width)); + } + + Polygons shields; + if (has_ooze_shield) + { + shields = storage.oozeShield[0]; + } + if (has_draft_shield) + { + shields = shields.unionPolygons(storage.draft_protection_shield); + } + first_layer_outline = first_layer_outline.unionPolygons(shields.offset( + line_widths[reference_extruder_nr] / 2 // because the shield is printed *on* the stored polygons; not inside hteir area + - gap[reference_extruder_nr])); // so that when we apply the gap we will end up right next to the shield + // NOTE: offsetting by -gap here and by +gap in the main brim algorithm effectively performs a morphological close, + // so in some cases with a large skirt gap and small models and small shield distance + // the skirt lines can cross the shield lines. + // This shouldn't be a big problem, since the skirt lines are far away from the model. first_layer_outline = first_layer_outline.approxConvexHull(); } else @@ -33,8 +393,7 @@ void SkirtBrim::getFirstLayerOutline(SliceDataStorage& storage, const size_t pri constexpr bool include_support = false; // Include manually below. constexpr bool include_prime_tower = false; // Include manually below. constexpr bool external_outlines_only = false; // Remove manually below. - constexpr bool for_brim = true; - first_layer_outline = storage.getLayerOutlines(layer_nr, include_support, include_prime_tower, external_outlines_only, for_brim); + first_layer_outline = storage.getLayerOutlines(layer_nr, include_support, include_prime_tower, external_outlines_only, extruder_nr); first_layer_outline = first_layer_outline.unionPolygons(); // To guard against overlapping outlines, which would produce holes according to the even-odd rule. Polygons first_layer_empty_holes; if (external_only) @@ -42,9 +401,12 @@ void SkirtBrim::getFirstLayerOutline(SliceDataStorage& storage, const size_t pri first_layer_empty_holes = first_layer_outline.getEmptyHoles(); first_layer_outline = first_layer_outline.removeEmptyHoles(); } - if (storage.support.generated && primary_line_count > 0 && ! storage.support.supportLayers.empty()) + if (storage.support.generated && primary_line_count > 0 && ! storage.support.supportLayers.empty() + && (extruder_nr == -1 || extruder_nr == global_settings.get<int>("support_infill_extruder_nr")) + ) { // remove model-brim from support SupportLayer& support_layer = storage.support.supportLayers[0]; + const ExtruderTrain& support_infill_extruder = global_settings.get<ExtruderTrain&>("support_infill_extruder_nr"); if (support_infill_extruder.settings.get<bool>("brim_replaces_support")) { // avoid gap in the middle @@ -54,7 +416,7 @@ void SkirtBrim::getFirstLayerOutline(SliceDataStorage& storage, const size_t pri // || || ||[]|| > expand to fit an extra brim line // |+-+| |+--+| // +---+ +----+ - const coord_t primary_extruder_skirt_brim_line_width = train.settings.get<coord_t>("skirt_brim_line_width") * train.settings.get<Ratio>("initial_layer_line_width_factor"); + const coord_t primary_extruder_skirt_brim_line_width = line_widths[reference_extruder_nr]; Polygons model_brim_covered_area = first_layer_outline.offset(primary_extruder_skirt_brim_line_width * (primary_line_count + primary_line_count % 2), ClipperLib::jtRound); // always leave a gap of an even number of brim lines, so that it fits if it's generating brim from both sides if (external_only) @@ -74,9 +436,14 @@ void SkirtBrim::getFirstLayerOutline(SliceDataStorage& storage, const size_t pri first_layer_outline.add(support_layer.support_bottom); first_layer_outline.add(support_layer.support_roof); } - if (storage.primeTower.enabled && ! train.settings.get<bool>("prime_tower_brim_enable")) + if + ( + storage.primeTower.enabled && + global_settings.get<bool>("prime_tower_brim_enable") && + (extruder_nr == -1 || int(storage.primeTower.extruder_order[0]) == extruder_nr) + ) { - first_layer_outline.add(storage.primeTower.outer_poly_first_layer); // don't remove parts of the prime tower, but make a brim for it + first_layer_outline.add(storage.primeTower.outer_poly); // don't remove parts of the prime tower, but make a brim for it } } constexpr coord_t join_distance = 20; @@ -88,87 +455,23 @@ void SkirtBrim::getFirstLayerOutline(SliceDataStorage& storage, const size_t pri { spdlog::error("Couldn't generate skirt / brim! No polygons on first layer."); } + return first_layer_outline; } -coord_t SkirtBrim::generatePrimarySkirtBrimLines(const coord_t start_distance, size_t& primary_line_count, const coord_t primary_extruder_minimal_length, const Polygons& first_layer_outline, Polygons& skirt_brim_primary_extruder) +void SkirtBrim::generateShieldBrim(Polygons& brim_covered_area, std::vector<Polygons>& allowed_areas_per_extruder) { - const Settings& adhesion_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings.get<ExtruderTrain&>("skirt_brim_extruder_nr").settings; - const coord_t primary_extruder_skirt_brim_line_width = adhesion_settings.get<coord_t>("skirt_brim_line_width") * adhesion_settings.get<Ratio>("initial_layer_line_width_factor"); - coord_t offset_distance = start_distance - primary_extruder_skirt_brim_line_width / 2; - for (unsigned int skirt_brim_number = 0; skirt_brim_number < primary_line_count; skirt_brim_number++) - { - offset_distance += primary_extruder_skirt_brim_line_width; - - Polygons outer_skirt_brim_line = first_layer_outline.offset(offset_distance, ClipperLib::jtRound); - - // Remove small inner skirt and brim holes. Holes have a negative area, remove anything smaller then 100x extrusion "area" - for (unsigned int n = 0; n < outer_skirt_brim_line.size(); n++) - { - double area = outer_skirt_brim_line[n].area(); - if (area < 0 && area > -primary_extruder_skirt_brim_line_width * primary_extruder_skirt_brim_line_width * 100) - { - outer_skirt_brim_line.remove(n--); - } - } - - skirt_brim_primary_extruder.add(outer_skirt_brim_line); - - const coord_t length = skirt_brim_primary_extruder.polygonLength(); - if (skirt_brim_number + 1 >= primary_line_count && length > 0 && length < primary_extruder_minimal_length) // Make brim or skirt have more lines when total length is too small. - { - primary_line_count++; - } - } - return offset_distance; -} - -void SkirtBrim::generate(SliceDataStorage& storage, Polygons first_layer_outline, const coord_t start_distance, size_t primary_line_count, const bool allow_helpers /*= true*/) -{ - const bool is_skirt = start_distance > 0; - Scene& scene = Application::getInstance().current_slice->scene; - const size_t skirt_brim_extruder_nr = scene.current_mesh_group->settings.get<ExtruderTrain&>("skirt_brim_extruder_nr").extruder_nr; - const Settings& adhesion_settings = scene.extruders[skirt_brim_extruder_nr].settings; - const coord_t primary_extruder_skirt_brim_line_width = adhesion_settings.get<coord_t>("skirt_brim_line_width") * adhesion_settings.get<Ratio>("initial_layer_line_width_factor"); - const coord_t primary_extruder_minimal_length = adhesion_settings.get<coord_t>("skirt_brim_minimal_length"); - - Polygons& skirt_brim_primary_extruder = storage.skirt_brim[skirt_brim_extruder_nr]; - - const bool has_ooze_shield = allow_helpers && storage.oozeShield.size() > 0 && storage.oozeShield[0].size() > 0; - const bool has_draft_shield = allow_helpers && storage.draft_protection_shield.size() > 0; - - coord_t gap; - if (is_skirt && (has_ooze_shield || has_draft_shield)) - { // make sure we don't generate skirt through draft / ooze shield - first_layer_outline = first_layer_outline.offset(start_distance - primary_extruder_skirt_brim_line_width / 2, ClipperLib::jtRound).unionPolygons(storage.draft_protection_shield); - if (has_ooze_shield) - { - first_layer_outline = first_layer_outline.unionPolygons(storage.oozeShield[0]); - } - first_layer_outline = first_layer_outline.approxConvexHull(); - gap = primary_extruder_skirt_brim_line_width / 2; - } - else - { - gap = start_distance; - } - - coord_t offset_distance = generatePrimarySkirtBrimLines(gap, primary_line_count, primary_extruder_minimal_length, first_layer_outline, skirt_brim_primary_extruder); - - // Skirt needs to be 'locked' first, otherwise the optimizer can change to order, which can cause undesirable outcomes w.r.t combo w. support-brim or prime-tower brim. - // If this method is called multiple times, the max order shouldn't reset to 0, so the maximum is taken. - storage.skirt_brim_max_locked_part_order[skirt_brim_extruder_nr] = std::max(is_skirt ? primary_line_count : 0, storage.skirt_brim_max_locked_part_order[skirt_brim_extruder_nr]); - - // handle support-brim - const ExtruderTrain& support_infill_extruder = scene.current_mesh_group->settings.get<ExtruderTrain&>("support_infill_extruder_nr"); - if (allow_helpers && support_infill_extruder.settings.get<bool>("support_brim_enable")) - { - const bool merge_with_model_skirtbrim = ! is_skirt; - generateSupportBrim(storage, merge_with_model_skirtbrim); + int extruder_nr = skirt_brim_extruder_nr; + if (extruder_nr < 0) + { // the shields are always printed with all extruders, so it doesn't really matter with which extruder we print the brim on the first layer + extruder_nr = first_used_extruder_nr; } // generate brim for ooze shield and draft shield - if (! is_skirt && (has_ooze_shield || has_draft_shield)) + if (adhesion_type == EPlatformAdhesion::BRIM && (has_ooze_shield || has_draft_shield)) { + const coord_t primary_extruder_skirt_brim_line_width = line_widths[extruder_nr]; + int primary_line_count = line_count[extruder_nr]; + // generate areas where to make extra brim for the shields // avoid gap in the middle // V @@ -188,56 +491,84 @@ void SkirtBrim::generate(SliceDataStorage& storage, Polygons first_layer_outline { shield_brim = shield_brim.unionPolygons(storage.draft_protection_shield.difference(storage.draft_protection_shield.offset(-primary_skirt_brim_width - primary_extruder_skirt_brim_line_width))); } - const Polygons outer_primary_brim = first_layer_outline.offset(offset_distance, ClipperLib::jtRound); - shield_brim = shield_brim.difference(outer_primary_brim.offset(primary_extruder_skirt_brim_line_width)); + shield_brim = shield_brim.intersection(allowed_areas_per_extruder[extruder_nr].offset(primary_extruder_skirt_brim_line_width / 2)); + const Polygons layer_outlines = storage.getLayerOutlines(/*layer_nr*/ 0, /*include_support*/ false, /*include_prime_tower*/ true, /*external_polys_only*/ false); + shield_brim = shield_brim.difference(layer_outlines.getOutsidePolygons()); // don't generate any shield brim inside holes + + const Polygons covered_area = shield_brim.offset(primary_extruder_skirt_brim_line_width / 2); + brim_covered_area = brim_covered_area.unionPolygons(covered_area); + allowed_areas_per_extruder[extruder_nr] = allowed_areas_per_extruder[extruder_nr].difference(covered_area); // generate brim within shield_brim - skirt_brim_primary_extruder.add(shield_brim); + storage.skirt_brim[extruder_nr].emplace_back(); + storage.skirt_brim[extruder_nr].back().closed_polygons.add(shield_brim); while (shield_brim.size() > 0) { shield_brim = shield_brim.offset(-primary_extruder_skirt_brim_line_width); - skirt_brim_primary_extruder.add(shield_brim); + storage.skirt_brim[extruder_nr].back().closed_polygons.add(shield_brim); // throw all polygons for the shileds onto one heap; because the brim lines are generated from both sides the order will not be important } + } - // update parameters to generate secondary skirt around - first_layer_outline = outer_primary_brim; - if (has_draft_shield) + if (adhesion_type == EPlatformAdhesion::SKIRT) + { + if (has_ooze_shield) { - first_layer_outline = first_layer_outline.unionPolygons(storage.draft_protection_shield); + const Polygons covered_area = storage.oozeShield[0].offset(line_widths[extruder_nr] / 2); + brim_covered_area = brim_covered_area.unionPolygons(covered_area); + allowed_areas_per_extruder[extruder_nr] = allowed_areas_per_extruder[extruder_nr].difference(covered_area); } - if (has_ooze_shield) + if (has_draft_shield) { - first_layer_outline = first_layer_outline.unionPolygons(storage.oozeShield[0]); + const Polygons covered_area = storage.draft_protection_shield.offset(line_widths[extruder_nr] / 2); + brim_covered_area = brim_covered_area.unionPolygons(covered_area); + allowed_areas_per_extruder[extruder_nr] = allowed_areas_per_extruder[extruder_nr].difference(covered_area); } - - offset_distance = 0; } +} - if (first_layer_outline.polygonLength() > 0) - { // process other extruders' brim/skirt (as one brim line around the old brim) - int last_width = primary_extruder_skirt_brim_line_width; - std::vector<bool> extruder_is_used = storage.getExtrudersUsed(); - for (size_t extruder_nr = 0; extruder_nr < Application::getInstance().current_slice->scene.extruders.size(); extruder_nr++) +void SkirtBrim::generateSecondarySkirtBrim(Polygons& covered_area, std::vector<Polygons>& allowed_areas_per_extruder, std::vector<coord_t>& total_length) +{ + constexpr coord_t bogus_total_offset = 0u; // Doesn't matter. The offsets won't be sorted here. + constexpr bool is_last = false; // Doesn't matter. Isn't used in the algorithm below. + for (int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++) + { + bool first = true; + Polygons reference_outline = covered_area; + while (total_length[extruder_nr] < skirt_brim_minimal_length[extruder_nr]) { - if (extruder_nr == skirt_brim_extruder_nr || ! extruder_is_used[extruder_nr]) + decltype(Offset::reference_outline_or_index) ref_polys_or_idx = nullptr; + coord_t offset_from_reference; + if (first) { - continue; + ref_polys_or_idx = &reference_outline; + offset_from_reference = line_widths[extruder_nr] / 2; } - const ExtruderTrain& train = Application::getInstance().current_slice->scene.extruders[extruder_nr]; - const coord_t width = train.settings.get<coord_t>("skirt_brim_line_width") * train.settings.get<Ratio>("initial_layer_line_width_factor"); - const coord_t minimal_length = train.settings.get<coord_t>("skirt_brim_minimal_length"); - offset_distance += last_width / 2 + width / 2; - last_width = width; - while (storage.skirt_brim[extruder_nr].polygonLength() < minimal_length) + else { - storage.skirt_brim[extruder_nr].add(first_layer_outline.offset(offset_distance, ClipperLib::jtRound)); - offset_distance += width; + ref_polys_or_idx = static_cast<int>(storage.skirt_brim[extruder_nr].size() - 1); + offset_from_reference = line_widths[extruder_nr]; } + constexpr bool external_only = false; // The reference outline may contain both outlines and hole polygons. + Offset extra_offset(ref_polys_or_idx, external_only, offset_from_reference, bogus_total_offset, storage.skirt_brim[extruder_nr].size(), extruder_nr, is_last); + + storage.skirt_brim[extruder_nr].emplace_back(); + SkirtBrimLine& output_location = storage.skirt_brim[extruder_nr].back(); + coord_t added_length = generateOffset(extra_offset, covered_area, allowed_areas_per_extruder, output_location); + + if ( ! added_length) + { + spdlog::warn("Couldn't satisfy minimum length constraint of extruder {}!\n", extruder_nr); + break; + } + + total_length[extra_offset.extruder_nr] += added_length; + + first = false; } } } -void SkirtBrim::generateSupportBrim(SliceDataStorage& storage, const bool merge_with_model_skirtbrim) +void SkirtBrim::generateSupportBrim() { constexpr coord_t brim_area_minimum_hole_size_multiplier = 100; @@ -252,7 +583,12 @@ void SkirtBrim::generateSupportBrim(SliceDataStorage& storage, const bool merge_ } const coord_t brim_width = brim_line_width * line_count; - Polygons& skirt_brim = storage.skirt_brim[support_infill_extruder.extruder_nr]; + coord_t skirt_brim_length = 0; + for (const SkirtBrimLine& brim_line : storage.skirt_brim[support_infill_extruder.extruder_nr]) + { + skirt_brim_length += brim_line.closed_polygons.polygonLength(); + skirt_brim_length += brim_line.open_polylines.polyLineLength(); + } SupportLayer& support_layer = storage.support.supportLayers[0]; @@ -264,8 +600,6 @@ void SkirtBrim::generateSupportBrim(SliceDataStorage& storage, const bool merge_ const Polygons brim_area = support_outline.difference(support_outline.offset(-brim_width)); support_layer.excludeAreasFromSupportInfillAreas(brim_area, AABB(brim_area)); - Polygons support_brim; - coord_t offset_distance = brim_line_width / 2; for (size_t skirt_brim_number = 0; skirt_brim_number < line_count; skirt_brim_number++) { @@ -283,9 +617,9 @@ void SkirtBrim::generateSupportBrim(SliceDataStorage& storage, const bool merge_ } } - support_brim.add(brim_line); + storage.support_brim.add(brim_line); - const coord_t length = skirt_brim.polygonLength() + support_brim.polygonLength(); + const coord_t length = skirt_brim_length + storage.support_brim.polygonLength(); if (skirt_brim_number + 1 >= line_count && length > 0 && length < minimal_length) // Make brim or skirt have more lines when total length is too small. { line_count++; @@ -295,23 +629,6 @@ void SkirtBrim::generateSupportBrim(SliceDataStorage& storage, const bool merge_ break; } } - - if (support_brim.size()) - { - if (merge_with_model_skirtbrim) - { - // to ensure that the skirt brim is printed from outside to inside, the support brim lines must - // come before the skirt brim lines in the Polygon object so that the outermost skirt brim line - // is at the back of the list - support_brim.add(skirt_brim); - skirt_brim = support_brim; - } - else - { - // OTOH, if we use a skirt instead of a brim for the polygon, the skirt line(s) should _always_ come first. - skirt_brim.add(support_brim); - } - } } } // namespace cura diff --git a/src/TreeModelVolumes.cpp b/src/TreeModelVolumes.cpp index 7479d5a82..ba7db9929 100644 --- a/src/TreeModelVolumes.cpp +++ b/src/TreeModelVolumes.cpp @@ -151,13 +151,11 @@ const Polygons& TreeModelVolumes::calculateInternalModel(const RadiusLayerPair& return ret.first->second; } -Polygons TreeModelVolumes::calculateMachineBorderCollision(Polygon machine_border) +Polygons TreeModelVolumes::calculateMachineBorderCollision(const Polygons&& machine_border) { - Polygons machine_volume_border; - machine_volume_border.add(machine_border.offset(MM2INT(1000))); //Put a border of 1m around the print volume so that we don't collide. - machine_border.reverse(); //Makes the polygon negative so that we subtract the actual volume from the collision area. - machine_volume_border.add(machine_border); + Polygons machine_volume_border = machine_border.offset(MM2INT(1000.0)); // Put a border of 1 meter around the print volume so that we don't collide. + machine_volume_border = machine_volume_border.difference(machine_border); // Subtract the actual volume from the collision area. return machine_volume_border; } -}
\ No newline at end of file +} diff --git a/src/Wireframe2gcode.cpp b/src/Wireframe2gcode.cpp index 3c78d2ee2..e9dc2adcc 100644 --- a/src/Wireframe2gcode.cpp +++ b/src/Wireframe2gcode.cpp @@ -25,7 +25,7 @@ namespace cura void Wireframe2gcode::writeGCode() { Settings& scene_settings = Application::getInstance().current_slice->scene.settings; - const size_t start_extruder_nr = scene_settings.get<ExtruderTrain&>("skirt_brim_extruder_nr").extruder_nr; // TODO: figure out how Wireframe works with dual extrusion + const size_t start_extruder_nr = scene_settings.get<ExtruderTrain&>("skirt_brim_extruder_nr").extruder_nr; // TODO: figure out how Wireframe works with dual extrusion // TODO: what if the extruder is not overridden?! gcode.preSetup(start_extruder_nr); gcode.setInitialAndBuildVolumeTemps(start_extruder_nr); @@ -563,7 +563,7 @@ void Wireframe2gcode::processStartingCode() { const Settings& scene_settings = Application::getInstance().current_slice->scene.settings; const size_t extruder_count = Application::getInstance().current_slice->scene.extruders.size(); - size_t start_extruder_nr = scene_settings.get<ExtruderTrain&>("skirt_brim_extruder_nr").extruder_nr; + size_t start_extruder_nr = scene_settings.get<ExtruderTrain&>("skirt_brim_extruder_nr").extruder_nr; // TODO if (Application::getInstance().communication->isSequential()) { diff --git a/src/raft.cpp b/src/raft.cpp index a3820cd2a..2ac51e2da 100644 --- a/src/raft.cpp +++ b/src/raft.cpp @@ -62,7 +62,8 @@ void Raft::generate(SliceDataStorage& storage) } } - storage.primeRaftOutline = storage.primeTower.outer_poly_first_layer.offset(distance, ClipperLib::jtRound); + storage.primeRaftOutline = storage.primeTower.outer_poly.offset(distance, ClipperLib::jtRound); + // NOTE: the raft doesn't take the prime tower brim into account, because it's (currently) not being printed when printing a raft if (settings.get<bool>("raft_remove_inside_corners")) { storage.primeRaftOutline = storage.primeRaftOutline.unionPolygons(storage.raftOutline); diff --git a/src/settings/PathConfigStorage.cpp b/src/settings/PathConfigStorage.cpp index 8bd976b04..9ab4b7704 100644 --- a/src/settings/PathConfigStorage.cpp +++ b/src/settings/PathConfigStorage.cpp @@ -132,7 +132,6 @@ PathConfigStorage::PathConfigStorage(const SliceDataStorage& storage, const Laye : support_infill_extruder_nr(Application::getInstance().current_slice->scene.current_mesh_group->settings.get<ExtruderTrain&>("support_infill_extruder_nr").extruder_nr) , support_roof_extruder_nr(Application::getInstance().current_slice->scene.current_mesh_group->settings.get<ExtruderTrain&>("support_roof_extruder_nr").extruder_nr) , support_bottom_extruder_nr(Application::getInstance().current_slice->scene.current_mesh_group->settings.get<ExtruderTrain&>("support_bottom_extruder_nr").extruder_nr) -, skirt_brim_train(Application::getInstance().current_slice->scene.current_mesh_group->settings.get<ExtruderTrain&>("skirt_brim_extruder_nr")) , raft_base_train(Application::getInstance().current_slice->scene.current_mesh_group->settings.get<ExtruderTrain&>("raft_base_extruder_nr")) , raft_interface_train(Application::getInstance().current_slice->scene.current_mesh_group->settings.get<ExtruderTrain&>("raft_interface_extruder_nr")) , raft_surface_train(Application::getInstance().current_slice->scene.current_mesh_group->settings.get<ExtruderTrain&>("raft_surface_extruder_nr")) diff --git a/src/settings/Settings.cpp b/src/settings/Settings.cpp index add349eb0..56ddaa26e 100644 --- a/src/settings/Settings.cpp +++ b/src/settings/Settings.cpp @@ -25,6 +25,7 @@ #include "settings/types/Velocity.h" //For velocity settings. #include "utils/FMatrix4x3.h" #include "utils/string.h" //For Escaped. +#include "utils/polygon.h" namespace cura { @@ -111,6 +112,24 @@ ExtruderTrain& Settings::get<ExtruderTrain&>(const std::string& key) const return Application::getInstance().current_slice->scene.extruders[extruder_nr]; } +template<> std::vector<ExtruderTrain*> Settings::get<std::vector<ExtruderTrain*>>(const std::string& key) const +{ + int extruder_nr = std::atoi(get<std::string>(key).c_str()); + std::vector<ExtruderTrain*> ret; + if (extruder_nr < 0) + { + for (ExtruderTrain& train : Application::getInstance().current_slice->scene.extruders) + { + ret.emplace_back(&train); + } + } + else + { + ret.emplace_back(&Application::getInstance().current_slice->scene.extruders[extruder_nr]); + } + return ret; +} + template<> LayerIndex Settings::get<LayerIndex>(const std::string& key) const { @@ -221,6 +240,67 @@ FlowTempGraph Settings::get<FlowTempGraph>(const std::string& key) const return result; } +template<> Polygons Settings::get<Polygons>(const std::string& key) const +{ + std::string value_string = get<std::string>(key); + + Polygons result; + if (value_string.empty()) + { + return result; //Empty at this point. + } + /* We're looking to match one or more floating point values separated by + * commas and surrounded by square brackets. Note that because the QML + * RegExpValidator only stops unrecognised characters being input and + * doesn't actually barf if the trailing ']' is missing, we are lenient here + * and make that bracket optional. + */ + std::regex polygons_regex(R"(\[(.*)\]?)"); + std::smatch polygons_match; + if (std::regex_search(value_string, polygons_match, polygons_regex) && polygons_match.size() > 1) + { + std::string polygons_string = polygons_match.str(1); + + std::regex polygon_regex(R"(\[((\[[^\[\]]*\]\s*,?\s*)*)\]\s*,?)"); // matches with a list of lists (a list of 2D vertices) + std::smatch polygon_match; + + std::regex_token_iterator<std::string::iterator> rend; //Default constructor gets the end-of-sequence iterator. + std::regex_token_iterator<std::string::iterator> polygon_match_iter(polygons_string.begin(), polygons_string.end(), polygon_regex, 0); + while (polygon_match_iter != rend) + { + std::string polygon_str = *polygon_match_iter++; + + result.emplace_back(); + PolygonRef poly = result.back(); + + std::regex point2D_regex(R"(\[([^,\[]*),([^,\]]*)\])"); // matches to a list of exactly two things + + const int submatches[] = {1, 2}; // Match first number and second number of a pair. + std::regex_token_iterator<std::string::iterator> match_iter(polygon_str.begin(), polygon_str.end(), point2D_regex, submatches); + while (match_iter != rend) + { + std::string first_substring = *match_iter++; + std::string second_substring = *match_iter++; + try + { + double first = std::stod(first_substring); + double second = std::stod(second_substring); + poly.emplace_back(MM2INT(first), MM2INT(second)); + } + catch (const std::invalid_argument& e) + { + spdlog::error("Couldn't read 2D graph element [{},{}] in setting '{}'. Ignored.\n", first_substring.c_str(), second_substring.c_str(), key.c_str()); + } + if (match_iter == rend) + { + break; + } + } + } + } + return result; +} + template<> FMatrix4x3 Settings::get<FMatrix4x3>(const std::string& key) const { diff --git a/src/sliceDataStorage.cpp b/src/sliceDataStorage.cpp index 45537d60d..47b90eff5 100644 --- a/src/sliceDataStorage.cpp +++ b/src/sliceDataStorage.cpp @@ -284,15 +284,15 @@ SliceDataStorage::SliceDataStorage() } machine_size.include(machine_min); machine_size.include(machine_max); - - std::fill(skirt_brim_max_locked_part_order, skirt_brim_max_locked_part_order + MAX_EXTRUDERS, 0); } -Polygons SliceDataStorage::getLayerOutlines(const LayerIndex layer_nr, const bool include_support, const bool include_prime_tower, const bool external_polys_only, const bool for_brim) const +Polygons SliceDataStorage::getLayerOutlines(const LayerIndex layer_nr, const bool include_support, const bool include_prime_tower, const bool external_polys_only, const int extruder_nr) const { + const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; if (layer_nr < 0 && layer_nr < -static_cast<LayerIndex>(Raft::getFillerLayerCount())) { // when processing raft - if (include_support) + ExtruderTrain& train = mesh_group_settings.get<ExtruderTrain&>("adhesion_extruder_nr"); + if (include_support && (extruder_nr == -1 || extruder_nr == int(train.extruder_nr))) { if (external_polys_only) { @@ -321,26 +321,20 @@ Polygons SliceDataStorage::getLayerOutlines(const LayerIndex layer_nr, const boo { for (const SliceMeshStorage& mesh : meshes) { - if (mesh.settings.get<bool>("infill_mesh") || mesh.settings.get<bool>("anti_overhang_mesh")) + if (mesh.settings.get<bool>("infill_mesh") || mesh.settings.get<bool>("anti_overhang_mesh") + || (extruder_nr != -1 && extruder_nr != int(mesh.settings.get<ExtruderTrain&>("wall_0_extruder_nr").extruder_nr))) { continue; } const SliceLayer& layer = mesh.layers[layer_nr]; - if (for_brim) - { - total.add(layer.getOutlines(external_polys_only).offset(mesh.settings.get<coord_t>("brim_gap"))); - } - else - { - layer.getOutlines(total, external_polys_only); - } + layer.getOutlines(total, external_polys_only); if (mesh.settings.get<ESurfaceMode>("magic_mesh_surface_mode") != ESurfaceMode::NORMAL) { total = total.unionPolygons(layer.openPolyLines.offsetPolyLine(MM2INT(0.1))); } } } - if (include_support) + if (include_support && (extruder_nr == -1 || extruder_nr == int(mesh_group_settings.get<ExtruderTrain&>("support_infill_extruder_nr").extruder_nr))) { const SupportLayer& support_layer = support.supportLayers[std::max(LayerIndex(0), layer_nr)]; if (support.generated) @@ -353,11 +347,12 @@ Polygons SliceDataStorage::getLayerOutlines(const LayerIndex layer_nr, const boo total.add(support_layer.support_roof); } } - if (include_prime_tower) + int prime_tower_outer_extruder_nr = primeTower.extruder_order[0]; + if (include_prime_tower && (extruder_nr == -1 || extruder_nr == prime_tower_outer_extruder_nr)) { if (primeTower.enabled) { - total.add(layer_nr == 0 ? primeTower.outer_poly_first_layer : primeTower.outer_poly); + total.add(primeTower.outer_poly); } } return total; @@ -548,11 +543,13 @@ bool SliceDataStorage::getExtruderPrimeBlobEnabled(const size_t extruder_nr) con return train.settings.get<bool>("prime_blob_enable"); } -Polygon SliceDataStorage::getMachineBorder(bool adhesion_offset) const +Polygons SliceDataStorage::getMachineBorder(int checking_extruder_nr) const { const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; - Polygon border{}; + Polygons border; + border.emplace_back(); + PolygonRef outline = border.back(); switch (mesh_group_settings.get<BuildPlateShape>("machine_shape")) { case BuildPlateShape::ELLIPTIC: @@ -564,58 +561,101 @@ Polygon SliceDataStorage::getMachineBorder(bool adhesion_offset) const for (unsigned int i = 0; i < circle_resolution; i++) { const double angle = M_PI * 2 * i / circle_resolution; - border.emplace_back(machine_size.getMiddle().x + std::cos(angle) * width / 2, machine_size.getMiddle().y + std::sin(angle) * depth / 2); + outline.emplace_back(machine_size.getMiddle().x + std::cos(angle) * width / 2, machine_size.getMiddle().y + std::sin(angle) * depth / 2); } break; } case BuildPlateShape::RECTANGULAR: default: - border = machine_size.flatten().toPolygon(); + outline = machine_size.flatten().toPolygon(); break; } - if (! adhesion_offset) + + Polygons disallowed_areas = mesh_group_settings.get<Polygons>("machine_disallowed_areas"); + disallowed_areas = disallowed_areas.unionPolygons(); // union overlapping disallowed areas + for (PolygonRef poly : disallowed_areas) + for (Point& p : poly) + p = Point(machine_size.max.x / 2 + p.X, machine_size.max.y / 2 - p.Y); // apparently the frontend stores the disallowed areas in a different coordinate system + + std::vector<bool> extruder_is_used = getExtrudersUsed(); + + constexpr coord_t prime_clearance = MM2INT(6.5); + for (size_t extruder_nr = 0; extruder_nr < extruder_is_used.size(); extruder_nr++) { - return border; + if ((checking_extruder_nr != -1 && int(extruder_nr) != checking_extruder_nr) || ! extruder_is_used[extruder_nr]) + { + continue; + } + Settings& extruder_settings = Application::getInstance().current_slice->scene.extruders[extruder_nr].settings; + if (!(extruder_settings.get<bool>("prime_blob_enable") && mesh_group_settings.get<bool>("extruder_prime_pos_abs"))) + { + continue; + } + Point prime_pos(extruder_settings.get<coord_t>("extruder_prime_pos_x"), extruder_settings.get<coord_t>("extruder_prime_pos_y")); + if (prime_pos == Point(0, 0)) + { + continue; // Ignore extruder prime position if it is not set. + } + Point translation(extruder_settings.get<coord_t>("machine_nozzle_offset_x"), extruder_settings.get<coord_t>("machine_nozzle_offset_y")); + prime_pos -= translation; + Polygons prime_polygons; + prime_polygons.emplace_back(PolygonUtils::makeCircle(prime_pos, prime_clearance, M_PI / 32)); + disallowed_areas = disallowed_areas.unionPolygons(prime_polygons); } - coord_t adhesion_size = 0; // Make sure there is enough room for the platform adhesion around support. - const ExtruderTrain& base_train = mesh_group_settings.get<ExtruderTrain&>("raft_base_extruder_nr"); - const ExtruderTrain& interface_train = mesh_group_settings.get<ExtruderTrain&>("raft_interface_extruder_nr"); - const ExtruderTrain& surface_train = mesh_group_settings.get<ExtruderTrain&>("raft_surface_extruder_nr"); - const ExtruderTrain& skirt_brim_train = mesh_group_settings.get<ExtruderTrain&>("skirt_brim_extruder_nr"); - coord_t extra_skirt_line_width = 0; - const std::vector<bool> is_extruder_used = getExtrudersUsed(); - for (size_t extruder_nr = 0; extruder_nr < Application::getInstance().current_slice->scene.extruders.size(); extruder_nr++) + Polygons disallowed_all_extruders; + bool first = true; + for (size_t extruder_nr = 0; extruder_nr < extruder_is_used.size(); extruder_nr++) { - if (extruder_nr == skirt_brim_train.extruder_nr || ! is_extruder_used[extruder_nr]) // Unused extruders and the primary adhesion extruder don't generate an extra skirt line. + if ((checking_extruder_nr != -1 && int(extruder_nr) != checking_extruder_nr) || !extruder_is_used[extruder_nr]) { continue; } - const ExtruderTrain& other_extruder = Application::getInstance().current_slice->scene.extruders[extruder_nr]; - extra_skirt_line_width += other_extruder.settings.get<coord_t>("skirt_brim_line_width") * other_extruder.settings.get<Ratio>("initial_layer_line_width_factor"); + Settings& extruder_settings = Application::getInstance().current_slice->scene.extruders[extruder_nr].settings; + Point translation(extruder_settings.get<coord_t>("machine_nozzle_offset_x"), extruder_settings.get<coord_t>("machine_nozzle_offset_y")); + Polygons extruder_border = disallowed_areas; + extruder_border.translate(translation); + if (first) + { + disallowed_all_extruders = extruder_border; + first = false; + } + else + { + disallowed_all_extruders = disallowed_all_extruders.unionPolygons(extruder_border); + } } - switch (mesh_group_settings.get<EPlatformAdhesion>("adhesion_type")) + disallowed_all_extruders.processEvenOdd(ClipperLib::pftNonZero); // prevent overlapping disallowed areas from XORing + + Polygons border_all_extruders = border; // each extruders border areas must be limited to the global border, which is the union of all extruders borders + if (mesh_group_settings.has("nozzle_offsetting_for_disallowed_areas") && mesh_group_settings.get<bool>("nozzle_offsetting_for_disallowed_areas")) { - case EPlatformAdhesion::BRIM: - adhesion_size = - skirt_brim_train.settings.get<coord_t>("skirt_brim_line_width") * skirt_brim_train.settings.get<Ratio>("initial_layer_line_width_factor") * skirt_brim_train.settings.get<size_t>("brim_line_count") + extra_skirt_line_width; - break; - case EPlatformAdhesion::RAFT: - adhesion_size = std::max({ base_train.settings.get<coord_t>("raft_margin"), interface_train.settings.get<coord_t>("raft_margin"), surface_train.settings.get<coord_t>("raft_margin") }); - break; - case EPlatformAdhesion::SKIRT: - adhesion_size = skirt_brim_train.settings.get<coord_t>("skirt_gap") - + skirt_brim_train.settings.get<coord_t>("skirt_brim_line_width") * skirt_brim_train.settings.get<Ratio>("initial_layer_line_width_factor") * skirt_brim_train.settings.get<size_t>("skirt_line_count") - + extra_skirt_line_width; - break; - case EPlatformAdhesion::NONE: - adhesion_size = 0; - break; - default: // Also use 0. - spdlog::info("Unknown platform adhesion type! Please implement the width of the platform adhesion here."); - break; + for (size_t extruder_nr = 0; extruder_nr < extruder_is_used.size(); extruder_nr++) + { + if ((checking_extruder_nr != -1 && int(extruder_nr) != checking_extruder_nr) || ! extruder_is_used[extruder_nr]) + { + continue; + } + Settings& extruder_settings = Application::getInstance().current_slice->scene.extruders[extruder_nr].settings; + Point translation(extruder_settings.get<coord_t>("machine_nozzle_offset_x"), extruder_settings.get<coord_t>("machine_nozzle_offset_y")); + for (size_t other_extruder_nr = 0; other_extruder_nr < extruder_is_used.size(); other_extruder_nr++) + { + // NOTE: the other extruder doesn't have to be used. Since the global border is the union of all extruders borders also unused extruders must be taken into account. + if (other_extruder_nr == extruder_nr) + { + continue; + } + Settings& other_extruder_settings = Application::getInstance().current_slice->scene.extruders[other_extruder_nr].settings; + Point other_translation(other_extruder_settings.get<coord_t>("machine_nozzle_offset_x"), other_extruder_settings.get<coord_t>("machine_nozzle_offset_y")); + Polygons translated_border = border; + translated_border.translate(translation - other_translation); + border_all_extruders = border_all_extruders.intersection(translated_border); + } + } } - return border.offset(-adhesion_size)[0]; + + border = border_all_extruders.difference(disallowed_all_extruders); + return border; } diff --git a/src/support.cpp b/src/support.cpp index 9d5841cff..fc2728ee7 100644 --- a/src/support.cpp +++ b/src/support.cpp @@ -481,24 +481,37 @@ Polygons AreaSupport::join(const SliceDataStorage& storage, const Polygons& supp break; } coord_t adhesion_size = 0; // Make sure there is enough room for the platform adhesion around support. - const ExtruderTrain& skirt_brim_extruder = mesh_group_settings.get<ExtruderTrain&>("skirt_brim_extruder_nr"); coord_t extra_skirt_line_width = 0; const std::vector<bool> is_extruder_used = storage.getExtrudersUsed(); for (size_t extruder_nr = 0; extruder_nr < Application::getInstance().current_slice->scene.extruders.size(); extruder_nr++) { - if (extruder_nr == skirt_brim_extruder.extruder_nr || ! is_extruder_used[extruder_nr]) // Unused extruders and the primary adhesion extruder don't generate an extra skirt line. + if (! is_extruder_used[extruder_nr]) //Unused extruders and the primary adhesion extruder don't generate an extra skirt line. { continue; } const ExtruderTrain& other_extruder = Application::getInstance().current_slice->scene.extruders[extruder_nr]; extra_skirt_line_width += other_extruder.settings.get<coord_t>("skirt_brim_line_width") * other_extruder.settings.get<Ratio>("initial_layer_line_width_factor"); } + const std::vector<ExtruderTrain*> skirt_brim_extruders = mesh_group_settings.get<std::vector<ExtruderTrain*>>("skirt_brim_extruder_nr"); + auto adhesion_width_str{ "brim_width" }; + auto adhesion_line_count_str{ "brim_line_count" }; switch (mesh_group_settings.get<EPlatformAdhesion>("adhesion_type")) { + case EPlatformAdhesion::SKIRT: + adhesion_width_str = "skirt_gap"; + adhesion_line_count_str = "skirt_line_count"; + [[fallthrough]]; case EPlatformAdhesion::BRIM: - adhesion_size = skirt_brim_extruder.settings.get<coord_t>("brim_width") - + skirt_brim_extruder.settings.get<coord_t>("skirt_brim_line_width") * skirt_brim_extruder.settings.get<size_t>("brim_line_count") * skirt_brim_extruder.settings.get<Ratio>("initial_layer_line_width_factor") - + extra_skirt_line_width; + for (ExtruderTrain* skirt_brim_extruder_p : skirt_brim_extruders) + { + ExtruderTrain& skirt_brim_extruder = *skirt_brim_extruder_p; + adhesion_size = std::max(adhesion_size, coord_t( + skirt_brim_extruder.settings.get<coord_t>(adhesion_width_str) + + skirt_brim_extruder.settings.get<coord_t>("skirt_brim_line_width") + * (skirt_brim_extruder.settings.get<coord_t>(adhesion_line_count_str) - 1) // - 1 because the line is also included in extra_skirt_line_width + * skirt_brim_extruder.settings.get<Ratio>("initial_layer_line_width_factor") + + extra_skirt_line_width)); + } break; case EPlatformAdhesion::RAFT: { @@ -507,11 +520,6 @@ Polygons AreaSupport::join(const SliceDataStorage& storage, const Polygons& supp mesh_group_settings.get<ExtruderTrain&>("raft_surface_extruder_nr").settings.get<coord_t>("raft_margin") }); break; } - case EPlatformAdhesion::SKIRT: - adhesion_size = skirt_brim_extruder.settings.get<coord_t>("skirt_gap") - + skirt_brim_extruder.settings.get<coord_t>("skirt_brim_line_width") * skirt_brim_extruder.settings.get<Ratio>("initial_layer_line_width_factor") * skirt_brim_extruder.settings.get<size_t>("skirt_line_count") - + extra_skirt_line_width; - break; case EPlatformAdhesion::NONE: adhesion_size = 0; break; diff --git a/src/utils/polygon.cpp b/src/utils/polygon.cpp index 5875600de..42c4e69a2 100644 --- a/src/utils/polygon.cpp +++ b/src/utils/polygon.cpp @@ -299,6 +299,15 @@ Polygons Polygons::intersectionPolyLines(const Polygons& polylines, bool restitc return ret; } +void Polygons::toPolylines() +{ + for (PolygonRef poly : *this) + { + if (poly.empty()) continue; + poly.emplace_back(poly.front()); + } +} + void Polygons::splitPolylinesIntoSegments(Polygons& result) const { for (ConstPolygonRef poly : *this) @@ -1340,6 +1349,32 @@ void Polygons::splitIntoParts_processPolyTreeNode(ClipperLib::PolyNode* node, st } } +std::vector<Polygons> Polygons::sortByNesting() const +{ + std::vector<Polygons> ret; + ClipperLib::Clipper clipper(clipper_init); + ClipperLib::PolyTree resultPolyTree; + clipper.AddPaths(paths, ClipperLib::ptSubject, true); + clipper.Execute(ClipperLib::ctUnion, resultPolyTree); + + sortByNesting_processPolyTreeNode(&resultPolyTree, 0, ret); + return ret; +} + +void Polygons::sortByNesting_processPolyTreeNode(ClipperLib::PolyNode* node, const size_t nesting_idx, std::vector<Polygons>& ret) const +{ + for (int n = 0; n < node->ChildCount(); n++) + { + ClipperLib::PolyNode* child = node->Childs[n]; + if (nesting_idx >= ret.size()) + { + ret.resize(nesting_idx + 1); + } + ret[nesting_idx].add(child->Contour); + sortByNesting_processPolyTreeNode(child, nesting_idx + 1, ret); + } +} + Polygons Polygons::tubeShape(const coord_t inner_offset, const coord_t outer_offset) const { return this->offset(outer_offset).difference(this->offset(-inner_offset)); |