diff --git a/.github/workflows/conan-package.yml b/.github/workflows/conan-package.yml index 64ab6141b3..eb5829a6a7 100644 --- a/.github/workflows/conan-package.yml +++ b/.github/workflows/conan-package.yml @@ -54,7 +54,7 @@ jobs: conan-package-create-macos: needs: [ conan-recipe-version, conan-package-export ] - if: ${{ ((github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'master' || needs.conan-recipe-version.outputs.is_release_branch == 'true')) || github.event_name == 'pull_request') }} + if: ${{ ((github.event_name == 'push' && (github.ref_name == 'main' || needs.conan-recipe-version.outputs.is_release_branch == 'true')) || github.event_name == 'pull_request') }} uses: ultimaker/cura-workflows/.github/workflows/conan-package-create-macos.yml@main with: recipe_id_full: ${{ needs.conan-recipe-version.outputs.recipe_id_full }} @@ -62,7 +62,7 @@ jobs: conan-package-create-windows: needs: [ conan-recipe-version, conan-package-export ] - if: ${{ ((github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'master' || needs.conan-recipe-version.outputs.is_release_branch == 'true')) || github.event_name == 'pull_request') }} + if: ${{ ((github.event_name == 'push' && (github.ref_name == 'main' || needs.conan-recipe-version.outputs.is_release_branch == 'true')) || github.event_name == 'pull_request') }} uses: ultimaker/cura-workflows/.github/workflows/conan-package-create-windows.yml@main with: recipe_id_full: ${{ needs.conan-recipe-version.outputs.recipe_id_full }} @@ -70,8 +70,16 @@ jobs: conan-package-create-linux: needs: [ conan-recipe-version, conan-package-export ] - if: ${{ ((github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'master' || needs.conan-recipe-version.outputs.is_release_branch == 'true')) || github.event_name == 'pull_request') }} + if: ${{ ((github.event_name == 'push' && (github.ref_name == 'main' || needs.conan-recipe-version.outputs.is_release_branch == 'true')) || github.event_name == 'pull_request') }} uses: ultimaker/cura-workflows/.github/workflows/conan-package-create-linux.yml@main with: recipe_id_full: ${{ needs.conan-recipe-version.outputs.recipe_id_full }} secrets: inherit + + conan-package-create-wasm: + needs: [ conan-recipe-version, conan-package-export ] + if: ${{ (github.event_name == 'push' && (github.ref_name == 'main' || needs.conan-recipe-version.outputs.is_release_branch == 'true')) }} + uses: ultimaker/cura-workflows/.github/workflows/conan-package-create-wasm.yml@main + with: + recipe_id_full: ${{ needs.conan-recipe-version.outputs.recipe_id_full }} + secrets: inherit diff --git a/conanfile.py b/conanfile.py index 127a23576f..8e325411cd 100644 --- a/conanfile.py +++ b/conanfile.py @@ -217,6 +217,9 @@ def build(self): self.run(f"sentry-cli --auth-token {os.environ['SENTRY_TOKEN']} releases set-commits -o {sentry_org} -p {sentry_project} --commit \"Ultimaker/CuraEngine@{self.conan_data['commit']}\" {self.version}") self.run(f"sentry-cli --auth-token {os.environ['SENTRY_TOKEN']} releases finalize -o {sentry_org} -p {sentry_project} {self.version}") + def deploy(self): + copy(self, "CuraEngine*", src=os.path.join(self.package_folder, "bin"), dst=self.install_folder) + def package(self): match self.settings.os: case "Windows": diff --git a/include/BeadingStrategy/LimitedBeadingStrategy.h b/include/BeadingStrategy/LimitedBeadingStrategy.h index 42d34f86a4..3e15eefec2 100644 --- a/include/BeadingStrategy/LimitedBeadingStrategy.h +++ b/include/BeadingStrategy/LimitedBeadingStrategy.h @@ -35,7 +35,7 @@ class LimitedBeadingStrategy : public BeadingStrategy coord_t getOptimalThickness(coord_t bead_count) const override; coord_t getTransitionThickness(coord_t lower_bead_count) const override; coord_t getOptimalBeadCount(coord_t thickness) const override; - virtual std::string toString() const override; + std::string toString() const override; coord_t getTransitioningLength(coord_t lower_bead_count) const override; diff --git a/include/BeadingStrategy/OuterWallInsetBeadingStrategy.h b/include/BeadingStrategy/OuterWallInsetBeadingStrategy.h index c8c6eae31d..840c04f888 100644 --- a/include/BeadingStrategy/OuterWallInsetBeadingStrategy.h +++ b/include/BeadingStrategy/OuterWallInsetBeadingStrategy.h @@ -25,7 +25,7 @@ class OuterWallInsetBeadingStrategy : public BeadingStrategy coord_t getOptimalBeadCount(coord_t thickness) const override; coord_t getTransitioningLength(coord_t lower_bead_count) const override; - virtual std::string toString() const; + std::string toString() const override; private: BeadingStrategyPtr parent_; diff --git a/include/BeadingStrategy/RedistributeBeadingStrategy.h b/include/BeadingStrategy/RedistributeBeadingStrategy.h index 2f8304bf39..52d41e1a93 100644 --- a/include/BeadingStrategy/RedistributeBeadingStrategy.h +++ b/include/BeadingStrategy/RedistributeBeadingStrategy.h @@ -45,7 +45,7 @@ class RedistributeBeadingStrategy : public BeadingStrategy coord_t getTransitioningLength(coord_t lower_bead_count) const override; double getTransitionAnchorPos(coord_t lower_bead_count) const override; - virtual std::string toString() const; + std::string toString() const override; protected: /*! diff --git a/include/BeadingStrategy/WideningBeadingStrategy.h b/include/BeadingStrategy/WideningBeadingStrategy.h index bda69f345d..cd0f00c406 100644 --- a/include/BeadingStrategy/WideningBeadingStrategy.h +++ b/include/BeadingStrategy/WideningBeadingStrategy.h @@ -27,14 +27,14 @@ class WideningBeadingStrategy : public BeadingStrategy virtual ~WideningBeadingStrategy() override = default; - virtual Beading compute(coord_t thickness, coord_t bead_count) const override; - virtual coord_t getOptimalThickness(coord_t bead_count) const override; - virtual coord_t getTransitionThickness(coord_t lower_bead_count) const override; - virtual coord_t getOptimalBeadCount(coord_t thickness) const override; - virtual coord_t getTransitioningLength(coord_t lower_bead_count) const override; - virtual double getTransitionAnchorPos(coord_t lower_bead_count) const override; - virtual std::vector getNonlinearThicknesses(coord_t lower_bead_count) const override; - virtual std::string toString() const override; + Beading compute(coord_t thickness, coord_t bead_count) const override; + coord_t getOptimalThickness(coord_t bead_count) const override; + coord_t getTransitionThickness(coord_t lower_bead_count) const override; + coord_t getOptimalBeadCount(coord_t thickness) const override; + coord_t getTransitioningLength(coord_t lower_bead_count) const override; + double getTransitionAnchorPos(coord_t lower_bead_count) const override; + std::vector getNonlinearThicknesses(coord_t lower_bead_count) const override; + std::string toString() const override; protected: BeadingStrategyPtr parent_; diff --git a/include/ExtruderPlan.h b/include/ExtruderPlan.h index 7d8d1e3de6..338dfba115 100644 --- a/include/ExtruderPlan.h +++ b/include/ExtruderPlan.h @@ -180,7 +180,7 @@ class ExtruderPlan * \param maximum_cool_min_layer_time Maximum minimum layer time for all extruders in this layer * \param time_other_extr_plans Time spend on other extruders in this layer */ - void forceMinimalLayerTime(double maximum_cool_min_layer_time, double time_other_extr_plans); + bool forceMinimalLayerTime(double maximum_cool_min_layer_time, double time_other_extr_plans); /*! * @return The time needed for (un)retract the path diff --git a/include/FffGcodeWriter.h b/include/FffGcodeWriter.h index 69f808c85a..f2337823f0 100644 --- a/include/FffGcodeWriter.h +++ b/include/FffGcodeWriter.h @@ -204,7 +204,7 @@ class FffGcodeWriter : public NoCopy void startRaftLayer(const SliceDataStorage& storage, LayerPlan& gcode_layer, const LayerIndex layer_nr, size_t layer_extruder, size_t& current_extruder); - void endRaftLayer(const SliceDataStorage& storage, LayerPlan& gcode_layer, const LayerIndex layer_nr, size_t& current_extruder); + void endRaftLayer(const SliceDataStorage& storage, LayerPlan& gcode_layer, const LayerIndex layer_nr, size_t& current_extruder, const bool append_to_prime_tower = true); /*! * Convert the polygon data of a layer into a layer plan on the FffGcodeWriter::layer_plan_buffer @@ -660,8 +660,11 @@ class FffGcodeWriter : public NoCopy * \param[in] storage where the slice data is stored. * \param gcode_layer The initial planning of the gcode of the layer. * \param extruder_nr The extruder to switch to. + * \param append_to_prime_tower Indicates whether we should actually prime the extruder on the prime tower (normal + * case before actually using the extruder) or just do the basic priming (i.e. on first + * layer before starting the print */ - void setExtruder_addPrime(const SliceDataStorage& storage, LayerPlan& gcode_layer, const size_t extruder_nr) const; + void setExtruder_addPrime(const SliceDataStorage& storage, LayerPlan& gcode_layer, const size_t extruder_nr, const bool append_to_prime_tower = true) const; /*! * Add the prime tower gcode for the current layer. diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 5fab3d86ee..484e5b97ec 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -38,7 +38,6 @@ class Comb; class SliceDataStorage; class LayerPlanBuffer; - /*! * The LayerPlan class stores multiple moves that are planned. * @@ -100,6 +99,8 @@ class LayerPlan : public NoCopy Shape overhang_mask_; //!< The regions of a layer part where the walls overhang Shape roofing_mask_; //!< The regions of a layer part where the walls are exposed to the air + bool min_layer_time_used = false; //!< Wether or not the minimum layer time (cool_min_layer_time) was actually used in this layerplan. + const std::vector fan_speed_layer_time_settings_per_extruder_; enum CombBoundary diff --git a/include/PrimeTower.h b/include/PrimeTower.h new file mode 100644 index 0000000000..4de22dfa57 --- /dev/null +++ b/include/PrimeTower.h @@ -0,0 +1,243 @@ +// Copyright (c) 2022 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef PRIME_TOWER_H +#define PRIME_TOWER_H + +#include +#include + +#include "ExtruderUse.h" +#include "geometry/Polygon.h" +#include "settings/EnumSettings.h" +#include "settings/types/LayerIndex.h" +#include "utils/polygonUtils.h" + +namespace cura +{ + +class SliceDataStorage; +class LayerPlan; + +/*! + * Class for everything to do with the prime tower: + * - Generating the areas. + * - Checking up untill which height the prime tower has to be printed. + * - Generating the paths and adding them to the layer plan. + */ +class PrimeTower +{ +private: + using MovesByExtruder = std::map; + using MovesByLayer = std::map>; + + size_t extruder_count_; //!< Number of extruders + + bool wipe_from_middle_; //!< Whether to wipe on the inside of the hollow prime tower + Point2LL middle_; //!< The middle of the prime tower + + Point2LL post_wipe_point_; //!< Location to post-wipe the unused nozzle off on + + std::vector prime_tower_start_locations_; //!< The differernt locations where to pre-wipe the active nozzle + const unsigned int number_of_prime_tower_start_locations_ = 21; //!< The required size of \ref PrimeTower::wipe_locations + + MovesByExtruder prime_moves_; //!< For each extruder, the moves to be processed for actual priming. + + /* + * The first index is a bitmask representing an extruder combination, e.g. 0x05 for extruders 1+3. + * The second index is the used extruder index, e.g. 1 + * The polygons represent the sparse pattern to be printed when all the given extruders are unused for this layer + * and the given extruder is currently in use + */ + std::map> sparse_pattern_per_extruders_; + + MovesByLayer base_extra_moves_; //!< For each layer and each extruder, the extra moves to be processed for better adhesion/strength + MovesByExtruder inset_extra_moves_; //!< For each extruder, the extra inset moves to be processed for better adhesion on initial layer + + Shape outer_poly_; //!< The outline of the outermost prime tower. + std::vector outer_poly_base_; //!< The outline of the layers having extra width for the base + +public: + bool enabled_; //!< Whether the prime tower is enabled. + + /* + * In which order, from outside to inside, will we be printing the prime + * towers for maximum strength? + * + * This is the spatial order from outside to inside. This is NOT the actual + * order in time in which they are printed. + */ + std::vector extruder_order_; + + /*! + * \brief Creates a prime tower instance that will determine where and how + * the prime tower gets printed. + * + * \param storage A storage where it retrieves the prime tower settings. + */ + PrimeTower(); + + void initializeExtruders(const std::vector& used_extruders); + + /*! + * Check whether we actually use the prime tower. + */ + void checkUsed(); + + /*! + * Generate the prime tower area to be used on each layer + * + * Fills \ref PrimeTower::inner_poly and sets \ref PrimeTower::middle + */ + void generateGroundpoly(); + + /*! + * Generate the area where the prime tower should be. + */ + void generatePaths(const SliceDataStorage& storage); + + /*! + * Add path plans for the prime tower to the \p gcode_layer + * + * \param storage where to get settings from; where to get the maximum height of the prime tower from + * \param[in,out] gcode_layer Where to get the current extruder from; where to store the generated layer paths + * \param required_extruder_prime the extruders which actually required to be primed at this layer + * \param prev_extruder_nr The previous extruder with which paths were planned; from which extruder a switch was made + * \param new_extruder_nr The switched to extruder with which the prime tower paths should be generated. + */ + void addToGcode( + const SliceDataStorage& storage, + LayerPlan& gcode_layer, + const std::vector& required_extruder_prime, + const size_t prev_extruder_nr, + const size_t new_extruder_nr) const; + + /*! + * \brief Subtract the prime tower from the support areas in storage. + * + * \param storage The storage where to find the support from which to + * subtract a prime tower. + */ + void subtractFromSupport(SliceDataStorage& storage); + + /*! + * Get the outer polygon for the given layer, which may be the priming polygon only, or a larger polygon for layers with a base + * + * \param[in] layer_nr The index of the layer + * \return The outer polygon for the prime tower at the given layer + */ + const Shape& getOuterPoly(const LayerIndex& layer_nr) const; + + /*! + * Get the outer polygon for the very first layer, which may be the priming polygon only, or a larger polygon if there is a base + */ + const Shape& getGroundPoly() const; + +private: + /*! + * \see PrimeTower::generatePaths + * + * Generate the extrude paths for each extruder on even and odd layers + * Fill the ground poly with dense infill. + * \param cumulative_insets [in, out] The insets added to each extruder to compute the radius of its ring + */ + void generatePaths_denseInfill(std::vector& cumulative_insets); + + /*! + * \see WipeTower::generatePaths + * + * \brief Generate the sparse extrude paths for each extruders combination + * \param cumulative_insets The insets added to each extruder to compute the radius of its ring + */ + void generatePaths_sparseInfill(const std::vector& cumulative_insets); + + /*! + * \brief Generate the sparse extrude paths for an extruders combination + * + * \param first_extruder_nr The index of the first extruder to be pseudo-primed + * \param last_extruder_nr The index of the last extruder to be pseudo-primed + * \param rings_radii The external radii of each extruder ring, plus the internal radius of the internal ring + * \param line_width The actual line width of the extruder + * \param actual_extruder_nr The number of the actual extruder to be used + */ + Shape generatePath_sparseInfill( + const size_t first_extruder_idx, + const size_t last_extruder_idx, + const std::vector& rings_radii, + const coord_t line_width, + const size_t actual_extruder_nr); + + /*! + * Generate start locations on the prime tower. The locations are evenly spread around the prime tower's perimeter. + * The number of starting points is defined by "number_of_prime_tower_start_locations". The generated points will + * be stored in "prime_tower_start_locations". + */ + void generateStartLocations(); + + /*! + * \see PrimeTower::addToGcode + * + * Add path plans for the prime tower to the \p gcode_layer + * + * \param[in,out] gcode_layer Where to get the current extruder from. Where + * to store the generated layer paths. + * \param extruder The extruder we just switched to, with which the prime + * tower paths should be drawn. + */ + void addToGcode_denseInfill(LayerPlan& gcode_layer, const size_t extruder) const; + + /*! + * \brief Add path plans for the prime tower extra outer rings to make the stronger base + * \param gcode_layer The gcode export to add the paths plans to + * \param extruder_nr The current extruder number + * \return True if something has actually been added, according to the extruder number + * and current layer. + */ + bool addToGcode_base(LayerPlan& gcode_layer, const size_t extruder_nr) const; + + /*! + * \brief Add path plans for the prime tower extra inner rings to increase bed adhesion + * \param gcode_layer The gcode export to add the paths plans to + * \param extruder_nr The current extruder number + * \return True if something has actually been added, according to the extruder number + * and current layer. + */ + bool addToGcode_inset(LayerPlan& gcode_layer, const size_t extruder_nr) const; + + /*! + * \brief Add path plans in the case an extruder is not to be actually primed, but we still + * want to print something to make the prime tower consistent. + * \param gcode_layer The gcode export to add the paths plans to + * \param extruders_to_prime_idx The indexes of the extra extruders which also don't require being primed on this layer + * \param current_extruder_nr The extruder currently being used + */ + void addToGcode_sparseInfill(LayerPlan& gcode_layer, const std::vector& extruders_to_prime_idx, const size_t current_extruder_nr) const; + + /*! + * \brief Find the list of extruders that don't actually need to be primed during this layer, and for which + * we want to print only the sparse infill to keep the prime tower consistent. + * \param gcode_layer The current gcode export + * \param required_extruder_prime The pre-computed list of extruders uses during this layer + * \param method The current prime tower strategy + * \param initial_list_idx A list potentially containing extruders that we already know can be used for + * sparse infill + * \return The indexes of extruders to be used for sparse infill + */ + std::vector findExtrudersSparseInfill( + LayerPlan& gcode_layer, + const std::vector& required_extruder_prime, + cura::PrimeTowerMethod method, + const std::vector& initial_list_idx = {}) const; + + /*! + * For an extruder switch that happens not on the first layer, the extruder needs to be primed on the prime tower. + * This function picks a start location for this extruder on the prime tower's perimeter and travels there to avoid + * starting at the location everytime which can result in z-seam blobs. + */ + void gotoStartLocation(LayerPlan& gcode_layer, const int extruder) const; +}; + + +} // namespace cura + +#endif // PRIME_TOWER_H diff --git a/include/gcodeExport.h b/include/gcodeExport.h index 9d91325d97..efcbebfac7 100644 --- a/include/gcodeExport.h +++ b/include/gcodeExport.h @@ -541,7 +541,14 @@ class GCodeExport : public NoCopy void writeFanCommand(double speed); - void writeTemperatureCommand(const size_t extruder, const Temperature& temperature, const bool wait = false); + /*! + * \brief Write a GCode temperature command + * \param extruder The extruder number + * \param temperature The temperature to bo set + * \param wait Indicates whether we should just set the temperature and keep going, or wait for the temperature to be reach before going further + * \param force_write_on_equal When true, we should write the temperature command even if the actual set temperature is the same + */ + void writeTemperatureCommand(const size_t extruder, const Temperature& temperature, const bool wait = false, const bool force_write_on_equal = false); void writeBedTemperatureCommand(const Temperature& temperature, const bool wait = false); void writeBuildVolumeTemperatureCommand(const Temperature& temperature, const bool wait = false); diff --git a/include/raft.h b/include/raft.h index 67eb7145c5..a4493f0622 100644 --- a/include/raft.h +++ b/include/raft.h @@ -56,10 +56,10 @@ class Raft static size_t getTotalExtraLayers(); /*! - * \brief Get the amount of bottom for the raft interface. - * \note This is currently hard-coded to 1 because we have yet no setting for the bottom + * \brief Get the amount of layers for the raft base. + * \note This is currently hard-coded to 1 because we have yet no setting for the base */ - static size_t getBottomLayers(); + static size_t getBaseLayers(); /*! \brief Get the amount of layers for the raft interface. */ static size_t getInterfaceLayers(); @@ -84,7 +84,13 @@ class Raft static LayerType getLayerType(LayerIndex layer_index); private: - static size_t getLayersAmount(const std::string& extruder_nr, const std::string& layers_nr); + /*! + * \brief Get the amount of layers to be printed for the given raft section + * \param extruder_nr_setting_name The name of the setting to be fetched to get the proper extruder number + * \param target_raft_section The name of the setting to be fetched to get the number of layers + * \return The number of layers for the given raft section, or 0 if raft is disabled + */ + static size_t getLayersAmount(const std::string& extruder_nr_setting_name, const std::string& target_raft_section); }; } // namespace cura diff --git a/src/BeadingStrategy/BeadingStrategyFactory.cpp b/src/BeadingStrategy/BeadingStrategyFactory.cpp index c929bd5d9f..8b2d289449 100644 --- a/src/BeadingStrategy/BeadingStrategyFactory.cpp +++ b/src/BeadingStrategy/BeadingStrategyFactory.cpp @@ -41,22 +41,22 @@ BeadingStrategyPtr BeadingStrategyFactory::makeStrategy( wall_add_middle_threshold, inward_distributed_center_wall_count); spdlog::debug("Applying the Redistribute meta-strategy with outer-wall width = {}, inner-wall width = {}", preferred_bead_width_outer, preferred_bead_width_inner); - ret = make_unique(preferred_bead_width_outer, minimum_variable_line_ratio, move(ret)); + ret = make_unique(preferred_bead_width_outer, minimum_variable_line_ratio, std::move(ret)); if (print_thin_walls) { spdlog::debug("Applying the Widening Beading meta-strategy with minimum input width {} and minimum output width {}.", min_feature_size, min_bead_width); - ret = make_unique(move(ret), min_feature_size, min_bead_width); + ret = make_unique(std::move(ret), min_feature_size, min_bead_width); } if (outer_wall_offset > 0) { spdlog::debug("Applying the OuterWallOffset meta-strategy with offset = {}", outer_wall_offset); - ret = make_unique(outer_wall_offset, move(ret)); + ret = make_unique(outer_wall_offset, std::move(ret)); } // Apply the LimitedBeadingStrategy last, since that adds a 0-width marker wall which other beading strategies shouldn't touch. spdlog::debug("Applying the Limited Beading meta-strategy with maximum bead count = {}", max_bead_count); - ret = make_unique(max_bead_count, move(ret)); + ret = make_unique(max_bead_count, std::move(ret)); return ret; } } // namespace cura diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 976b1d9c24..1e422699f2 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -745,6 +745,8 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) last_planned_position = gcode_layer.getLastPlannedPositionOrStartingPosition(); } + endRaftLayer(storage, gcode_layer, layer_nr, current_extruder_nr, false); + layer_plan_buffer.handle(gcode_layer, gcode); } @@ -1092,17 +1094,17 @@ void FffGcodeWriter::startRaftLayer(const SliceDataStorage& storage, LayerPlan& } } -void FffGcodeWriter::endRaftLayer(const SliceDataStorage& storage, LayerPlan& gcode_layer, const LayerIndex layer_nr, size_t& current_extruder) +void FffGcodeWriter::endRaftLayer(const SliceDataStorage& storage, LayerPlan& gcode_layer, const LayerIndex layer_nr, size_t& current_extruder, const bool append_to_prime_tower) { // If required, fill prime tower with current extruder - setExtruder_addPrime(storage, gcode_layer, current_extruder); + setExtruder_addPrime(storage, gcode_layer, current_extruder, append_to_prime_tower); // If required, fill prime tower for other extruders - for (const ExtruderUse& extruder_use : extruder_order_per_layer.get(layer_nr)) + for (const ExtruderUse& extruder_use : getExtruderUse(layer_nr)) { - if (! gcode_layer.getPrimeTowerIsPlanned(extruder_use.extruder_nr) && extruder_use.prime != ExtruderPrime::None) + if (! append_to_prime_tower || (! gcode_layer.getPrimeTowerIsPlanned(extruder_use.extruder_nr) && extruder_use.prime != ExtruderPrime::None)) { - setExtruder_addPrime(storage, gcode_layer, extruder_use.extruder_nr); + setExtruder_addPrime(storage, gcode_layer, extruder_use.extruder_nr, append_to_prime_tower); current_extruder = extruder_use.extruder_nr; } } @@ -1223,7 +1225,7 @@ FffGcodeWriter::ProcessLayerResult FffGcodeWriter::processLayer(const SliceDataS for (const ExtruderUse& extruder_use : extruder_order) { - const size_t& extruder_nr = extruder_use.extruder_nr; + const size_t extruder_nr = extruder_use.extruder_nr; // Set extruder (if needed) and prime (if needed) setExtruder_addPrime(storage, gcode_layer, extruder_nr); @@ -1541,7 +1543,17 @@ void FffGcodeWriter::calculateExtruderOrderPerLayer(const SliceDataStorage& stor void FffGcodeWriter::calculatePrimeLayerPerExtruder(const SliceDataStorage& storage) { - for (LayerIndex layer_nr = -Raft::getTotalExtraLayers(); layer_nr < static_cast(storage.print_layer_count); ++layer_nr) + LayerIndex first_print_layer = -Raft::getTotalExtraLayers(); + for (size_t extruder_nr = 0; extruder_nr < MAX_EXTRUDERS; ++extruder_nr) + { + if (getExtruderNeedPrimeBlobDuringFirstLayer(storage, extruder_nr)) + { + // Extruders requiring a prime blob have to be primed at first layer + extruder_prime_layer_nr[extruder_nr] = std::min(extruder_prime_layer_nr[extruder_nr], first_print_layer); + } + } + + for (LayerIndex layer_nr = first_print_layer; layer_nr < static_cast(storage.print_layer_count); ++layer_nr) { const std::vector used_extruders = storage.getExtrudersUsed(layer_nr); for (size_t extruder_nr = 0; extruder_nr < used_extruders.size(); ++extruder_nr) @@ -1566,8 +1578,9 @@ std::vector FffGcodeWriter::getUsedExtrudersOnLayer( std::vector ret; std::vector extruder_is_used_on_this_layer = storage.getExtrudersUsed(layer_nr); const LayerIndex raft_base_layer_nr = -Raft::getTotalExtraLayers(); + Raft::LayerType layer_type = Raft::getLayerType(layer_nr); - if (layer_nr < 0 && layer_nr < raft_base_layer_nr + Raft::getBottomLayers()) + if (layer_type == Raft::RaftBase) { // Raft base layers area treated apart because they don't have a proper prime tower const size_t raft_base_extruder_nr = mesh_group_settings.get("raft_base_extruder_nr").extruder_nr_; @@ -3872,7 +3885,7 @@ bool FffGcodeWriter::addSupportBottomsToGCode(const SliceDataStorage& storage, L return true; } -void FffGcodeWriter::setExtruder_addPrime(const SliceDataStorage& storage, LayerPlan& gcode_layer, const size_t extruder_nr) const +void FffGcodeWriter::setExtruder_addPrime(const SliceDataStorage& storage, LayerPlan& gcode_layer, const size_t extruder_nr, const bool append_to_prime_tower) const { const size_t previous_extruder = gcode_layer.getExtruder(); const bool extruder_changed = gcode_layer.setExtruder(extruder_nr); @@ -3905,7 +3918,10 @@ void FffGcodeWriter::setExtruder_addPrime(const SliceDataStorage& storage, Layer } } - addPrimeTower(storage, gcode_layer, previous_extruder); + if (append_to_prime_tower) + { + addPrimeTower(storage, gcode_layer, previous_extruder); + } } void FffGcodeWriter::addPrimeTower(const SliceDataStorage& storage, LayerPlan& gcode_layer, const size_t prev_extruder) const diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 05d336da9c..bbaa54dd23 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1709,7 +1709,7 @@ void LayerPlan::spiralizeWallSlice( } } -void ExtruderPlan::forceMinimalLayerTime(double minTime, double time_other_extr_plans) +bool ExtruderPlan::forceMinimalLayerTime(double minTime, double time_other_extr_plans) { const double minimalSpeed = fan_speed_layer_time_settings_.cool_min_speed; const double travelTime = estimates_.getTravelTime(); @@ -1786,7 +1786,10 @@ void ExtruderPlan::forceMinimalLayerTime(double minTime, double time_other_extr_ path.speed_factor *= slow_down_factor; path.estimates.extrude_time /= slow_down_factor; } + + return true; } + return false; } double ExtruderPlan::getRetractTime(const GCodePath& path) @@ -1985,7 +1988,7 @@ void LayerPlan::processFanSpeedAndMinimalLayerTime(Point2LL starting_position) // apply minimum layer time behaviour ExtruderPlan& last_extruder_plan = extruder_plans_[last_extruder_idx]; - last_extruder_plan.forceMinimalLayerTime(maximum_cool_min_layer_time, other_extr_plan_time); + min_layer_time_used |= last_extruder_plan.forceMinimalLayerTime(maximum_cool_min_layer_time, other_extr_plan_time); last_extruder_plan.processFanSpeedForMinimalLayerTime(maximum_cool_min_layer_time, other_extr_plan_time); } @@ -1998,6 +2001,10 @@ void LayerPlan::writeGCode(GCodeExport& gcode) gcode.setLayerNr(layer_nr_); gcode.writeLayerComment(layer_nr_); + if (min_layer_time_used) + { + gcode.writeComment("note -- min layer time used"); + } // flow-rate compensation const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings; diff --git a/src/PrimeTower.cpp b/src/PrimeTower.cpp new file mode 100644 index 0000000000..bc4e2a07a7 --- /dev/null +++ b/src/PrimeTower.cpp @@ -0,0 +1,629 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "PrimeTower.h" + +#include +#include +#include + +#include + +#include "Application.h" //To get settings. +#include "ExtruderTrain.h" +#include "LayerPlan.h" +#include "Scene.h" +#include "Slice.h" +#include "gcodeExport.h" +#include "infill.h" +#include "raft.h" +#include "sliceDataStorage.h" + +#define CIRCLE_RESOLUTION 32 // The number of vertices in each circle. +#define ARC_RESOLUTION 4 // The number of segments in each arc of a wheel + + +namespace cura +{ + +PrimeTower::PrimeTower() + : wipe_from_middle_(false) +{ + const Scene& scene = Application::getInstance().current_slice_->scene; + + enabled_ = scene.current_mesh_group->settings.get("prime_tower_enable") && scene.current_mesh_group->settings.get("prime_tower_min_volume") > 10 + && scene.current_mesh_group->settings.get("prime_tower_size") > 10; +} + +void PrimeTower::initializeExtruders(const std::vector& used_extruders) +{ + // Add used extruders in default order, then sort. + for (unsigned int extruder_nr = 0; extruder_nr < used_extruders.size(); extruder_nr++) + { + if (used_extruders[extruder_nr]) + { + extruder_order_.push_back(extruder_nr); + } + } + + extruder_count_ = extruder_order_.size(); + + // Sort from high adhesion to low adhesion. + const Scene& scene = Application::getInstance().current_slice_->scene; + std::stable_sort( + extruder_order_.begin(), + extruder_order_.end(), + [&scene](const unsigned int& extruder_nr_a, const unsigned int& extruder_nr_b) -> bool + { + const Ratio adhesion_a = scene.extruders[extruder_nr_a].settings_.get("material_adhesion_tendency"); + const Ratio adhesion_b = scene.extruders[extruder_nr_b].settings_.get("material_adhesion_tendency"); + return adhesion_a < adhesion_b; + }); +} + +void PrimeTower::checkUsed() +{ + if (extruder_count_ <= 1) + { + enabled_ = false; + } +} + +void PrimeTower::generateGroundpoly() +{ + if (! enabled_) + { + return; + } + + const Scene& scene = Application::getInstance().current_slice_->scene; + const Settings& mesh_group_settings = scene.current_mesh_group->settings; + const coord_t tower_size = mesh_group_settings.get("prime_tower_size"); + + const coord_t x = mesh_group_settings.get("prime_tower_position_x"); + const coord_t y = mesh_group_settings.get("prime_tower_position_y"); + const coord_t tower_radius = tower_size / 2; + outer_poly_.push_back(PolygonUtils::makeCircle(Point2LL(x - tower_radius, y + tower_radius), tower_radius, TAU / CIRCLE_RESOLUTION)); + middle_ = Point2LL(x - tower_size / 2, y + tower_size / 2); + + post_wipe_point_ = Point2LL(x - tower_size / 2, y + tower_size / 2); +} + +void PrimeTower::generatePaths(const SliceDataStorage& storage) +{ + checkUsed(); + + // Maybe it turns out that we don't need a prime tower after all because there are no layer switches. + const int raft_total_extra_layers = Raft::getTotalExtraLayers(); + enabled_ &= storage.max_print_height_second_to_last_extruder >= -raft_total_extra_layers; + + if (enabled_) + { + generateGroundpoly(); + + std::vector cumulative_insets; + generatePaths_denseInfill(cumulative_insets); + + generateStartLocations(); + + generatePaths_sparseInfill(cumulative_insets); + } +} + +void PrimeTower::generatePaths_denseInfill(std::vector& cumulative_insets) +{ + const Scene& scene = Application::getInstance().current_slice_->scene; + const Settings& mesh_group_settings = scene.current_mesh_group->settings; + const coord_t layer_height = mesh_group_settings.get("layer_height"); + const PrimeTowerMethod method = mesh_group_settings.get("prime_tower_mode"); + const bool base_enabled = mesh_group_settings.get("prime_tower_brim_enable"); + const coord_t base_extra_radius = scene.settings.get("prime_tower_base_size"); + const bool has_raft = mesh_group_settings.get("adhesion_type") == EPlatformAdhesion::RAFT; + const coord_t base_height = std::max(scene.settings.get("prime_tower_base_height"), has_raft ? layer_height : 0); + const double base_curve_magnitude = mesh_group_settings.get("prime_tower_base_curve_magnitude"); + + for (size_t extruder_nr : extruder_order_) + { + // By default, add empty moves for every extruder + prime_moves_[extruder_nr]; + base_extra_moves_[extruder_nr]; + inset_extra_moves_[extruder_nr]; + } + + coord_t cumulative_inset = 0; // Each tower shape is going to be printed inside the other. This is the inset we're doing for each extruder. + for (size_t extruder_nr : extruder_order_) + { + const coord_t line_width = scene.extruders[extruder_nr].settings_.get("prime_tower_line_width"); + const coord_t required_volume = MM3_2INT(scene.extruders[extruder_nr].settings_.get("prime_tower_min_volume")); + const Ratio flow = scene.extruders[extruder_nr].settings_.get("prime_tower_flow"); + coord_t current_volume = 0; + Shape& prime_moves = prime_moves_[extruder_nr]; + + // Create the walls of the prime tower. + unsigned int wall_nr = 0; + for (; current_volume < required_volume; wall_nr++) + { + // Create a new polygon with an offset from the outer polygon. + Shape polygons = outer_poly_.offset(-cumulative_inset - wall_nr * line_width - line_width / 2); + prime_moves.push_back(polygons); + current_volume += polygons.length() * line_width * layer_height * flow; + if (polygons.empty()) // Don't continue. We won't ever reach the required volume because it doesn't fit. + { + break; + } + } + + // Generate the base outside extra rings + if ((method == PrimeTowerMethod::INTERLEAVED || (extruder_nr == extruder_order_.front() && method == PrimeTowerMethod::NORMAL)) && (base_enabled || has_raft) + && base_extra_radius > 0 && base_height > 0) + { + for (coord_t z = 0; z < base_height; z += layer_height) + { + double brim_radius_factor = std::pow((1.0 - static_cast(z) / base_height), base_curve_magnitude); + coord_t extra_radius = base_extra_radius * brim_radius_factor; + size_t extra_rings = extra_radius / line_width; + if (extra_rings == 0) + { + break; + } + extra_radius = line_width * extra_rings; + outer_poly_base_.push_back(outer_poly_.offset(extra_radius)); + base_extra_moves_[extruder_nr].push_back(PolygonUtils::generateOutset(outer_poly_, extra_rings, line_width)); + } + } + + cumulative_inset += wall_nr * line_width; + cumulative_insets.push_back(cumulative_inset); + } + + // Now we have the total cumulative inset, generate the base inside extra rings + for (size_t extruder_nr : extruder_order_) + { + if (extruder_nr == extruder_order_.back() || method == PrimeTowerMethod::INTERLEAVED) + { + const coord_t line_width = scene.extruders[extruder_nr].settings_.get("prime_tower_line_width"); + Shape pattern = PolygonUtils::generateInset(outer_poly_, line_width, cumulative_inset); + if (! pattern.empty()) + { + inset_extra_moves_[extruder_nr].push_back(pattern); + } + } + } +} + +void PrimeTower::generatePaths_sparseInfill(const std::vector& cumulative_insets) +{ + const Scene& scene = Application::getInstance().current_slice_->scene; + const Settings& mesh_group_settings = scene.current_mesh_group->settings; + const PrimeTowerMethod method = mesh_group_settings.get("prime_tower_mode"); + + struct ActualExtruder + { + size_t number; + coord_t line_width; + }; + + std::vector actual_extruders; + actual_extruders.reserve(extruder_order_.size()); + for (size_t extruder_nr : extruder_order_) + { + const coord_t line_width = scene.extruders[extruder_nr].settings_.get("prime_tower_line_width"); + actual_extruders.push_back({ extruder_nr, line_width }); + } + + if (method == PrimeTowerMethod::INTERLEAVED || method == PrimeTowerMethod::NORMAL) + { + // Pre-compute radiuses of each extruder ring + std::vector rings_radii; + const coord_t tower_size = mesh_group_settings.get("prime_tower_size"); + const coord_t tower_radius = tower_size / 2; + + rings_radii.push_back(tower_radius); + for (const coord_t& cumulative_inset : cumulative_insets) + { + rings_radii.push_back(tower_radius - cumulative_inset); + } + + // Generate all possible extruders combinations, e.g. if there are 4 extruders, we have combinations + // 0 / 0-1 / 0-1-2 / 0-1-2-3 / 1 / 1-2 / 1-2-3 / 2 / 2-3 / 3 + // A combination is represented by a bitmask + for (size_t first_extruder_idx = 0; first_extruder_idx < extruder_count_; ++first_extruder_idx) + { + size_t nb_extruders_sparse = method == PrimeTowerMethod::NORMAL ? first_extruder_idx + 1 : extruder_count_; + + for (size_t last_extruder_idx = first_extruder_idx; last_extruder_idx < nb_extruders_sparse; ++last_extruder_idx) + { + size_t extruders_combination = 0; + for (size_t extruder_idx = first_extruder_idx; extruder_idx <= last_extruder_idx; ++extruder_idx) + { + size_t extruder_nr = extruder_order_.at(extruder_idx); + extruders_combination |= (1 << extruder_nr); + } + + std::map infills_for_combination; + for (const ActualExtruder& actual_extruder : actual_extruders) + { + if (method == PrimeTowerMethod::INTERLEAVED || actual_extruder.number == extruder_order_.at(first_extruder_idx)) + { + Shape infill = generatePath_sparseInfill(first_extruder_idx, last_extruder_idx, rings_radii, actual_extruder.line_width, actual_extruder.number); + infills_for_combination[actual_extruder.number] = infill; + } + } + + sparse_pattern_per_extruders_[extruders_combination] = infills_for_combination; + } + } + } +} + +Shape PrimeTower::generatePath_sparseInfill( + const size_t first_extruder_idx, + const size_t last_extruder_idx, + const std::vector& rings_radii, + const coord_t line_width, + const size_t actual_extruder_nr) +{ + const Scene& scene = Application::getInstance().current_slice_->scene; + const coord_t max_bridging_distance = scene.extruders[actual_extruder_nr].settings_.get("prime_tower_max_bridging_distance"); + const coord_t outer_radius = rings_radii[first_extruder_idx]; + const coord_t inner_radius = rings_radii[last_extruder_idx + 1]; + const coord_t radius_delta = outer_radius - inner_radius; + const coord_t semi_line_width = line_width / 2; + + Shape pattern; + + // Split ring according to max bridging distance + const size_t nb_rings = std::ceil(static_cast(radius_delta) / max_bridging_distance); + if (nb_rings) + { + const coord_t actual_radius_step = radius_delta / nb_rings; + + for (size_t i = 0; i < nb_rings; ++i) + { + const coord_t ring_inner_radius = (inner_radius + i * actual_radius_step) + semi_line_width; + const coord_t ring_outer_radius = (inner_radius + (i + 1) * actual_radius_step) - semi_line_width; + + const size_t semi_nb_spokes = std::ceil((std::numbers::pi * ring_outer_radius) / max_bridging_distance); + + pattern.push_back(PolygonUtils::makeWheel(middle_, ring_inner_radius, ring_outer_radius, semi_nb_spokes, ARC_RESOLUTION)); + } + } + + return pattern; +} + +void PrimeTower::generateStartLocations() +{ + // Evenly spread out a number of dots along the prime tower's outline. This is done for the complete outline, + // so use the same start and end segments for this. + PolygonsPointIndex segment_start = PolygonsPointIndex(&outer_poly_, 0, 0); + PolygonsPointIndex segment_end = segment_start; + + PolygonUtils::spreadDots(segment_start, segment_end, number_of_prime_tower_start_locations_, prime_tower_start_locations_); +} + +void PrimeTower::addToGcode( + const SliceDataStorage& storage, + LayerPlan& gcode_layer, + const std::vector& required_extruder_prime, + const size_t prev_extruder_nr, + const size_t new_extruder_nr) const +{ + if (! enabled_) + { + return; + } + + if (gcode_layer.getPrimeTowerIsPlanned(new_extruder_nr)) + { // don't print the prime tower if it has been printed already with this extruder. + return; + } + + const LayerIndex layer_nr = gcode_layer.getLayerNr(); + if (layer_nr > storage.max_print_height_second_to_last_extruder + 1) + { + return; + } + + bool post_wipe = Application::getInstance().current_slice_->scene.extruders[prev_extruder_nr].settings_.get("prime_tower_wipe_enabled"); + + // Do not wipe on the first layer, we will generate non-hollow prime tower there for better bed adhesion. + if (prev_extruder_nr == new_extruder_nr || layer_nr == 0) + { + post_wipe = false; + } + + auto extruder_iterator = std::find_if( + required_extruder_prime.begin(), + required_extruder_prime.end(), + [new_extruder_nr](const ExtruderUse& extruder_use) + { + return extruder_use.extruder_nr == new_extruder_nr; + }); + + if (extruder_iterator == required_extruder_prime.end()) + { + // Extruder is not used on this layer + return; + } + + size_t new_extruder_idx; + auto iterator = std::find(extruder_order_.begin(), extruder_order_.end(), new_extruder_nr); + if (iterator != extruder_order_.end()) + { + new_extruder_idx = iterator - extruder_order_.begin(); + } + else + { + // Given extruder nr is not registered ?! + return; + } + + PrimeTowerMethod method = Application::getInstance().current_slice_->scene.current_mesh_group->settings.get("prime_tower_mode"); + std::vector extra_primed_extruders_idx; + + switch (extruder_iterator->prime) + { + case ExtruderPrime::None: + if (method != PrimeTowerMethod::INTERLEAVED) + { + gcode_layer.setPrimeTowerIsPlanned(new_extruder_nr); + } + break; + + case ExtruderPrime::Sparse: + gotoStartLocation(gcode_layer, new_extruder_nr); + extra_primed_extruders_idx = findExtrudersSparseInfill(gcode_layer, required_extruder_prime, method, { new_extruder_idx }); + addToGcode_sparseInfill(gcode_layer, extra_primed_extruders_idx, new_extruder_nr); + break; + + case ExtruderPrime::Prime: + gotoStartLocation(gcode_layer, new_extruder_nr); + + addToGcode_denseInfill(gcode_layer, new_extruder_nr); + gcode_layer.setPrimeTowerIsPlanned(new_extruder_nr); + + if (method == PrimeTowerMethod::INTERLEAVED && gcode_layer.getLayerNr() <= storage.max_print_height_second_to_last_extruder) + { + // Whatever happens before and after, use the current extruder to prime all the non-required extruders now + extra_primed_extruders_idx = findExtrudersSparseInfill(gcode_layer, required_extruder_prime, method); + addToGcode_sparseInfill(gcode_layer, extra_primed_extruders_idx, new_extruder_nr); + } + break; + } + + if (! gcode_layer.getPrimeTowerBaseIsPlanned() && addToGcode_base(gcode_layer, new_extruder_nr)) + { + gcode_layer.setPrimeTowerBaseIsPlanned(); + } + + if (! gcode_layer.getPrimeTowerInsetIsPlanned() && addToGcode_inset(gcode_layer, new_extruder_nr)) + { + gcode_layer.setPrimeTowerInsetIsPlanned(); + } + + for (const size_t& primed_extruder_idx : extra_primed_extruders_idx) + { + gcode_layer.setPrimeTowerIsPlanned(extruder_order_.at(primed_extruder_idx)); + } + + // post-wipe: + if (post_wipe) + { + // Make sure we wipe the old extruder on the prime tower. + const Settings& previous_settings = Application::getInstance().current_slice_->scene.extruders[prev_extruder_nr].settings_; + const Point2LL previous_nozzle_offset = Point2LL(previous_settings.get("machine_nozzle_offset_x"), previous_settings.get("machine_nozzle_offset_y")); + const Settings& new_settings = Application::getInstance().current_slice_->scene.extruders[new_extruder_nr].settings_; + const Point2LL new_nozzle_offset = Point2LL(new_settings.get("machine_nozzle_offset_x"), new_settings.get("machine_nozzle_offset_y")); + gcode_layer.addTravel(post_wipe_point_ - previous_nozzle_offset + new_nozzle_offset); + } +} + +void PrimeTower::addToGcode_denseInfill(LayerPlan& gcode_layer, const size_t extruder_nr) const +{ + const size_t raft_total_extra_layers = Raft::getTotalExtraLayers(); + const bool adhesion_raft = raft_total_extra_layers > 0; + LayerIndex absolute_layer_number = gcode_layer.getLayerNr() + raft_total_extra_layers; + + if (! adhesion_raft || absolute_layer_number > 0) + { + // Actual prime pattern + const GCodePathConfig& config = gcode_layer.configs_storage_.prime_tower_config_per_extruder[extruder_nr]; + const Shape& pattern = prime_moves_.at(extruder_nr); + gcode_layer.addPolygonsByOptimizer(pattern, config); + } +} + +bool PrimeTower::addToGcode_base(LayerPlan& gcode_layer, const size_t extruder_nr) const +{ + const size_t raft_total_extra_layers = Raft::getTotalExtraLayers(); + LayerIndex absolute_layer_number = gcode_layer.getLayerNr() + raft_total_extra_layers; + + const auto& pattern_extra_brim = base_extra_moves_.at(extruder_nr); + if (absolute_layer_number < pattern_extra_brim.size()) + { + // Extra rings for stronger base + const auto& pattern = pattern_extra_brim[absolute_layer_number]; + if (! pattern.empty()) + { + const GCodePathConfig& config = gcode_layer.configs_storage_.prime_tower_config_per_extruder[extruder_nr]; + gcode_layer.addPolygonsByOptimizer(pattern, config); + return true; + } + } + + return false; +} + +bool PrimeTower::addToGcode_inset(LayerPlan& gcode_layer, const size_t extruder_nr) const +{ + const size_t raft_total_extra_layers = Raft::getTotalExtraLayers(); + LayerIndex absolute_layer_number = gcode_layer.getLayerNr() + raft_total_extra_layers; + + if (absolute_layer_number == 0) // Extra-adhesion on very first layer only + { + const Shape& pattern_extra_inset = inset_extra_moves_.at(extruder_nr); + if (! pattern_extra_inset.empty()) + { + const GCodePathConfig& config = gcode_layer.configs_storage_.prime_tower_config_per_extruder[extruder_nr]; + gcode_layer.addPolygonsByOptimizer(pattern_extra_inset, config); + return true; + } + } + + return false; +} + +void PrimeTower::addToGcode_sparseInfill(LayerPlan& gcode_layer, const std::vector& extruders_to_prime_idx, const size_t current_extruder_nr) const +{ + std::vector> extruders_to_prime_idx_grouped; + + // Group extruders which are besides each other + for (size_t extruder_to_prime_idx : extruders_to_prime_idx) + { + if (extruders_to_prime_idx_grouped.empty()) + { + // First extruder : create new group + extruders_to_prime_idx_grouped.push_back({ extruder_to_prime_idx }); + } + else + { + std::vector& last_group = extruders_to_prime_idx_grouped.back(); + if (last_group.back() == extruder_to_prime_idx - 1) + { + // New extruders which belongs to same group + last_group.push_back(extruder_to_prime_idx); + } + else + { + // New extruders which belongs to new group + extruders_to_prime_idx_grouped.push_back({ extruder_to_prime_idx }); + } + } + } + + // And finally, append patterns for each group + const GCodePathConfig& config = gcode_layer.configs_storage_.prime_tower_config_per_extruder[current_extruder_nr]; + + for (const std::vector& group_idx : extruders_to_prime_idx_grouped) + { + size_t mask = 0; + for (const size_t& extruder_idx : group_idx) + { + mask |= (1 << extruder_order_.at(extruder_idx)); + } + + auto iterator_combination = sparse_pattern_per_extruders_.find(mask); + if (iterator_combination != sparse_pattern_per_extruders_.end()) + { + const std::map& infill_for_combination = iterator_combination->second; + + auto iterator_extruder_nr = infill_for_combination.find(current_extruder_nr); + if (iterator_extruder_nr != infill_for_combination.end()) + { + gcode_layer.addPolygonsByOptimizer(iterator_extruder_nr->second, config); + } + else + { + spdlog::warn("Sparse pattern not found for extruder {}, skipping\n", current_extruder_nr); + } + } + else + { + spdlog::warn("Sparse pattern not found for group {}, skipping\n", mask); + } + } +} + +std::vector PrimeTower::findExtrudersSparseInfill( + LayerPlan& gcode_layer, + const std::vector& required_extruder_prime, + PrimeTowerMethod method, + const std::vector& initial_list_idx) const +{ + std::vector extruders_to_prime_idx; + + for (size_t extruder_idx = 0; extruder_idx < extruder_order_.size(); extruder_idx++) + { + auto iterator_initial_list = std::find(initial_list_idx.begin(), initial_list_idx.end(), extruder_idx); + bool is_in_initial_list = iterator_initial_list != initial_list_idx.end(); + + if (is_in_initial_list) + { + extruders_to_prime_idx.push_back(extruder_idx); + } + else + { + size_t extruder_nr = extruder_order_.at(extruder_idx); + if (method == PrimeTowerMethod::INTERLEAVED && ! gcode_layer.getPrimeTowerIsPlanned(extruder_nr)) + { + auto iterator_required_list = std::find_if( + required_extruder_prime.begin(), + required_extruder_prime.end(), + [extruder_nr](const ExtruderUse& extruder_use) + { + return extruder_use.extruder_nr == extruder_nr && extruder_use.prime == ExtruderPrime::Prime; + }); + bool is_in_required_list = iterator_required_list != required_extruder_prime.end(); + + if (! is_in_required_list) + { + extruders_to_prime_idx.push_back(extruder_idx); + } + } + } + } + + return extruders_to_prime_idx; +} + +void PrimeTower::subtractFromSupport(SliceDataStorage& storage) +{ + for (size_t layer = 0; layer <= (size_t)storage.max_print_height_second_to_last_extruder + 1 && layer < storage.support.supportLayers.size(); layer++) + { + const Shape outside_polygon = getOuterPoly(layer).getOutsidePolygons(); + AABB outside_polygon_boundary_box(outside_polygon); + SupportLayer& support_layer = storage.support.supportLayers[layer]; + // take the differences of the support infill parts and the prime tower area + support_layer.excludeAreasFromSupportInfillAreas(outside_polygon, outside_polygon_boundary_box); + } +} + +const Shape& PrimeTower::getOuterPoly(const LayerIndex& layer_nr) const +{ + const LayerIndex absolute_layer_nr = layer_nr + Raft::getTotalExtraLayers(); + if (absolute_layer_nr < outer_poly_base_.size()) + { + return outer_poly_base_[absolute_layer_nr]; + } + else + { + return outer_poly_; + } +} + +const Shape& PrimeTower::getGroundPoly() const +{ + return getOuterPoly(-Raft::getTotalExtraLayers()); +} + +void PrimeTower::gotoStartLocation(LayerPlan& gcode_layer, const int extruder_nr) const +{ + if (gcode_layer.getLayerNr() != 0) + { + int current_start_location_idx = ((((extruder_nr + 1) * gcode_layer.getLayerNr()) % number_of_prime_tower_start_locations_) + number_of_prime_tower_start_locations_) + % number_of_prime_tower_start_locations_; + + const ClosestPointPolygon wipe_location = prime_tower_start_locations_[current_start_location_idx]; + const ExtruderTrain& train = Application::getInstance().current_slice_->scene.extruders[extruder_nr]; + const coord_t inward_dist = train.settings_.get("machine_nozzle_size") * 3 / 2; + const coord_t start_dist = train.settings_.get("machine_nozzle_size") * 2; + const Point2LL prime_end = PolygonUtils::moveInsideDiagonally(wipe_location, inward_dist); + const Point2LL outward_dir = wipe_location.location_ - prime_end; + const Point2LL prime_start = wipe_location.location_ + normal(outward_dir, start_dist); + + gcode_layer.addTravel(prime_start); + } +} + +} // namespace cura diff --git a/src/gcodeExport.cpp b/src/gcodeExport.cpp index 98d95d3f07..f071582d77 100644 --- a/src/gcodeExport.cpp +++ b/src/gcodeExport.cpp @@ -767,69 +767,73 @@ void GCodeExport::processInitialLayerTemperature(const SliceDataStorage& storage { Scene& scene = Application::getInstance().current_slice_->scene; const size_t num_extruders = scene.extruders.size(); + const bool material_print_temp_prepend = scene.current_mesh_group->settings.get("material_print_temp_prepend"); + const bool material_print_temp_wait = scene.current_mesh_group->settings.get("material_print_temp_wait"); + bool wait_start_extruder = false; - if (getFlavor() == EGCodeFlavor::GRIFFIN) - { - processInitialLayerBedTemperature(); - - ExtruderTrain& train = scene.extruders[start_extruder_nr]; - constexpr bool wait = true; - const Temperature print_temp_0 = train.settings_.get("material_print_temperature_layer_0"); - const Temperature print_temp_here = (print_temp_0 != 0) ? print_temp_0 : train.settings_.get("material_print_temperature"); - writeTemperatureCommand(start_extruder_nr, print_temp_here, wait); - } - else if (getFlavor() != EGCodeFlavor::ULTIGCODE) + switch (getFlavor()) { + case EGCodeFlavor::ULTIGCODE: + return; + case EGCodeFlavor::GRIFFIN: + wait_start_extruder = true; + break; + default: if (num_extruders > 1 || getFlavor() == EGCodeFlavor::REPRAP) { std::ostringstream tmp; tmp << "T" << start_extruder_nr; writeLine(tmp.str().c_str()); } + break; + } + + processInitialLayerBedTemperature(); - processInitialLayerBedTemperature(); + struct ExtruderInitialize + { + size_t nr; + Temperature temperature; + }; - if (scene.current_mesh_group->settings.get("material_print_temp_prepend") || (scene.current_mesh_group != scene.mesh_groups.begin())) + std::vector all_extruders; + std::vector extruders_used = storage.getExtrudersUsed(); + for (size_t extruder_nr = 0; extruder_nr < extruders_used.size(); ++extruder_nr) + { + if (extruders_used[extruder_nr]) { - for (unsigned extruder_nr = 0; extruder_nr < num_extruders; extruder_nr++) + const ExtruderTrain& train = scene.extruders[extruder_nr]; + Temperature extruder_temp; + if (extruder_nr == start_extruder_nr) { - if (storage.getExtrudersUsed()[extruder_nr]) - { - const ExtruderTrain& train = scene.extruders[extruder_nr]; - Temperature extruder_temp; - if (extruder_nr == start_extruder_nr) - { - const Temperature print_temp_0 = train.settings_.get("material_print_temperature_layer_0"); - extruder_temp = (print_temp_0 != 0) ? print_temp_0 : train.settings_.get("material_print_temperature"); - } - else - { - extruder_temp = train.settings_.get("material_standby_temperature"); - } - writeTemperatureCommand(extruder_nr, extruder_temp); - } + const Temperature print_temp_0 = train.settings_.get("material_print_temperature_layer_0"); + extruder_temp = (print_temp_0 != 0) ? print_temp_0 : train.settings_.get("material_print_temperature"); } - if (scene.current_mesh_group->settings.get("material_print_temp_wait")) + else { - for (unsigned extruder_nr = 0; extruder_nr < num_extruders; extruder_nr++) - { - if (storage.getExtrudersUsed()[extruder_nr]) - { - const ExtruderTrain& train = scene.extruders[extruder_nr]; - Temperature extruder_temp; - if (extruder_nr == start_extruder_nr) - { - const Temperature print_temp_0 = train.settings_.get("material_print_temperature_layer_0"); - extruder_temp = (print_temp_0 != 0) ? print_temp_0 : train.settings_.get("material_print_temperature"); - } - else - { - extruder_temp = train.settings_.get("material_standby_temperature"); - } - writeTemperatureCommand(extruder_nr, extruder_temp, true); - } - } + extruder_temp = train.settings_.get("material_standby_temperature"); } + + all_extruders.push_back({ extruder_nr, extruder_temp }); + } + } + + // First set all the required temperatures at once, but without waiting so that all heaters start heating right now + const bool prepend_all_temperatures = material_print_temp_prepend || (scene.current_mesh_group != scene.mesh_groups.begin()); + for (ExtruderInitialize& extruder : all_extruders) + { + if (extruder.nr == start_extruder_nr || prepend_all_temperatures) + { + writeTemperatureCommand(extruder.nr, extruder.temperature, false, true); + } + } + + // Now wait for all the required temperatures one after the other + for (ExtruderInitialize& extruder : all_extruders) + { + if (material_print_temp_wait || (extruder.nr == start_extruder_nr && wait_start_extruder)) + { + writeTemperatureCommand(extruder.nr, extruder.temperature, true, true); } } } @@ -1476,7 +1480,7 @@ void GCodeExport::writeFanCommand(double speed) current_fan_speed_ = speed; } -void GCodeExport::writeTemperatureCommand(const size_t extruder, const Temperature& temperature, const bool wait) +void GCodeExport::writeTemperatureCommand(const size_t extruder, const Temperature& temperature, const bool wait, const bool force_write_on_equal) { const ExtruderTrain& extruder_train = Application::getInstance().current_slice_->scene.extruders[extruder]; @@ -1512,7 +1516,7 @@ void GCodeExport::writeTemperatureCommand(const size_t extruder, const Temperatu } } - if ((! wait || extruder_attr_[extruder].waited_for_temperature_) && extruder_attr_[extruder].current_temperature_ == temperature) + if ((! wait || extruder_attr_[extruder].waited_for_temperature_) && ! force_write_on_equal && extruder_attr_[extruder].current_temperature_ == temperature) { return; } diff --git a/src/raft.cpp b/src/raft.cpp index c37955c7dd..ef08c19d6d 100644 --- a/src/raft.cpp +++ b/src/raft.cpp @@ -170,10 +170,10 @@ coord_t Raft::getFillerLayerHeight() size_t Raft::getTotalExtraLayers() { - return getBottomLayers() + getInterfaceLayers() + getSurfaceLayers() + getFillerLayerCount(); + return getBaseLayers() + getInterfaceLayers() + getSurfaceLayers() + getFillerLayerCount(); } -size_t Raft::getBottomLayers() +size_t Raft::getBaseLayers() { const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings; if (mesh_group_settings.get("adhesion_type") != EPlatformAdhesion::RAFT) @@ -195,23 +195,19 @@ size_t Raft::getSurfaceLayers() Raft::LayerType Raft::getLayerType(LayerIndex layer_index) { - const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings; - const ExtruderTrain& base_train = mesh_group_settings.get("raft_base_extruder_nr"); - const ExtruderTrain& interface_train = mesh_group_settings.get("raft_interface_extruder_nr"); - const ExtruderTrain& surface_train = mesh_group_settings.get("raft_surface_extruder_nr"); const auto airgap = Raft::getFillerLayerCount(); - const auto interface_layers = interface_train.settings_.get("raft_interface_layers"); - const auto surface_layers = surface_train.settings_.get("raft_surface_layers"); + const auto interface_layers = Raft::getInterfaceLayers(); + const auto surface_layers = Raft::getSurfaceLayers(); if (layer_index < -airgap - surface_layers - interface_layers) { return LayerType::RaftBase; } - if (layer_index < -airgap - surface_layers) + else if (layer_index < -airgap - surface_layers) { return LayerType::RaftInterface; } - if (layer_index < -airgap) + else if (layer_index < -airgap) { return LayerType::RaftSurface; } @@ -225,7 +221,7 @@ Raft::LayerType Raft::getLayerType(LayerIndex layer_index) } } -size_t Raft::getLayersAmount(const std::string& extruder_nr, const std::string& layers_nr) +size_t Raft::getLayersAmount(const std::string& extruder_nr_setting_name, const std::string& target_raft_section) { const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings; if (mesh_group_settings.get("adhesion_type") != EPlatformAdhesion::RAFT) @@ -233,8 +229,8 @@ size_t Raft::getLayersAmount(const std::string& extruder_nr, const std::string& return 0; } - const ExtruderTrain& train = mesh_group_settings.get(extruder_nr); - return train.settings_.get(layers_nr); + const ExtruderTrain& train = mesh_group_settings.get(extruder_nr_setting_name); + return train.settings_.get(target_raft_section); } diff --git a/src/sliceDataStorage.cpp b/src/sliceDataStorage.cpp index c030fc403d..9e479c6c2b 100644 --- a/src/sliceDataStorage.cpp +++ b/src/sliceDataStorage.cpp @@ -491,8 +491,8 @@ std::vector SliceDataStorage::getExtrudersUsed(const LayerIndex layer_nr) } if (adhesion_type == EPlatformAdhesion::RAFT) { - const LayerIndex raft_base_layer_index = -Raft::getTotalExtraLayers(); - if (layer_nr < raft_base_layer_index + Raft::getBottomLayers()) // Base layer. + const Raft::LayerType layer_type = Raft::getLayerType(layer_nr); + if (layer_type == Raft::RaftBase) { ret[mesh_group_settings.get("raft_base_extruder_nr").extruder_nr_] = true; // When using a raft, all prime blobs need to be on the lowest layer (the build plate). @@ -504,11 +504,11 @@ std::vector SliceDataStorage::getExtrudersUsed(const LayerIndex layer_nr) } } } - else if (layer_nr < raft_base_layer_index + Raft::getBottomLayers() + Raft::getInterfaceLayers()) // Interface layer. + else if (layer_type == Raft::RaftInterface) { ret[mesh_group_settings.get("raft_interface_extruder_nr").extruder_nr_] = true; } - else if (layer_nr < -static_cast(Raft::getFillerLayerCount())) // Any of the surface layers. + else if (layer_type == Raft::RaftSurface) { ret[mesh_group_settings.get("raft_surface_extruder_nr").extruder_nr_] = true; } diff --git a/src/slicer.cpp b/src/slicer.cpp index fa8fded180..f66e2d40ec 100644 --- a/src/slicer.cpp +++ b/src/slicer.cpp @@ -13,6 +13,7 @@ #include "Application.h" #include "Slice.h" #include "geometry/OpenPolyline.h" +#include "geometry/SingleShape.h" // Needed in order to call splitIntoParts() #include "plugins/slots.h" #include "raft.h" #include "settings/AdaptiveLayerHeights.h"