Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/Ultimaker/CuraEngine.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRemco Burema <41987080+rburema@users.noreply.github.com>2022-10-30 19:46:02 +0300
committerGitHub <noreply@github.com>2022-10-30 19:46:02 +0300
commitc7adeae0d19a5511143ab382eb537c34dd0386ac (patch)
treeda57a1a25c07a532314b75710a1e435cbef9c36c
parent4fa24ba16df055dc1ad840c0b3df554c6e2dc29b (diff)
parent4f053c7872e901d8944aa2ea55dae7c3852bb882 (diff)
Merge pull request #1613 from Ultimaker/brim_per_material_optimized_order
Brim overhaul: New features and Bug fixes
-rw-r--r--include/LayerPlan.h4
-rw-r--r--include/PathOrderOptimizer.h2
-rw-r--r--include/PrimeTower.h6
-rw-r--r--include/SkirtBrim.h166
-rw-r--r--include/TreeModelVolumes.h4
-rw-r--r--include/settings/PathConfigStorage.h1
-rw-r--r--include/sliceDataStorage.h26
-rw-r--r--include/utils/polygon.h22
-rw-r--r--src/FffGcodeWriter.cpp162
-rw-r--r--src/FffPolygonGenerator.cpp89
-rw-r--r--src/LayerPlan.cpp5
-rw-r--r--src/PrimeTower.cpp30
-rw-r--r--src/SkirtBrim.cpp593
-rw-r--r--src/TreeModelVolumes.cpp10
-rw-r--r--src/Wireframe2gcode.cpp4
-rw-r--r--src/raft.cpp3
-rw-r--r--src/settings/PathConfigStorage.cpp1
-rw-r--r--src/settings/Settings.cpp80
-rw-r--r--src/sliceDataStorage.cpp146
-rw-r--r--src/support.cpp28
-rw-r--r--src/utils/polygon.cpp35
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));