diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d769e5313..2287e8f92d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,7 +64,9 @@ set(engine_SRCS # Except main.cpp. src/multiVolumes.cpp src/path_ordering.cpp src/Preheat.cpp - src/PrimeTower.cpp + src/PrimeTower/PrimeTower.cpp + src/PrimeTower/PrimeTowerNormal.cpp + src/PrimeTower/PrimeTowerInterleaved.cpp src/raft.cpp src/Scene.cpp src/SkeletalTrapezoidation.cpp diff --git a/include/ExtruderPrime.h b/include/ExtruderPrime.h index f3c8d3cc48..e6cb89b3c4 100644 --- a/include/ExtruderPrime.h +++ b/include/ExtruderPrime.h @@ -10,7 +10,7 @@ namespace cura enum class ExtruderPrime { None, // Do not prime at all for this extruder on this layer - Sparse, // Just extrude a sparse priming which purpose is to make the tower stronger + Support, // Just extrude a sparse pattern which purpose is to support the upper parts of the prime tower Prime, // Do an actual prime }; diff --git a/include/FffGcodeWriter.h b/include/FffGcodeWriter.h index 3767309b60..f2337823f0 100644 --- a/include/FffGcodeWriter.h +++ b/include/FffGcodeWriter.h @@ -12,6 +12,7 @@ #include "GCodePathConfig.h" #include "LayerPlanBuffer.h" #include "gcodeExport.h" +#include "utils/LayerVector.h" #include "utils/NoCopy.h" #include "utils/gettime.h" @@ -60,13 +61,8 @@ class FffGcodeWriter : public NoCopy */ std::ofstream output_file; - /*! - * For each raft/filler layer, the extruders to be used in that layer in the order in which they are going to be used. - * The first number is the first raft layer. Indexing is shifted compared to normal negative layer numbers for raft/filler layers. - */ - std::vector> extruder_order_per_layer_negative_layers; - - std::vector> extruder_order_per_layer; //!< For each layer, the extruders to be used in that layer in the order in which they are going to be used + //!< For each layer, the extruders to be used in that layer in the order in which they are going to be used + LayerVector> extruder_order_per_layer; std::vector> mesh_order_per_extruder; //!< For each extruder, the order of the meshes (first element is first mesh to be printed) @@ -735,14 +731,6 @@ class FffGcodeWriter : public NoCopy * \return The first or last exruder used at the given index */ size_t findUsedExtruderIndex(const SliceDataStorage& storage, const LayerIndex& layer_nr, bool last) const; - - /*! - * Get the extruders use at the given layer - * - * \param layer_nr The index of the layer at which we want the extruders uses - * \return The extruders use at the given layer, which may be empty in some cases - */ - std::vector getExtruderUse(const LayerIndex& layer_nr) const; }; } // namespace cura diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 6e786a28a6..d81bb9c437 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -70,8 +70,6 @@ class LayerPlan : public NoCopy std::vector layer_start_pos_per_extruder_; //!< The starting position of a layer for each extruder std::vector has_prime_tower_planned_per_extruder_; //!< For each extruder, whether the prime tower is planned yet or not. - bool has_prime_tower_base_planned_; //!< Whether the prime tower base is planned yet or not. - bool has_prime_tower_inset_planned_; //!< Whether the prime tower inset is planned yet or not. std::optional last_planned_position_; //!< The last planned XY position of the print head (if known) std::shared_ptr current_mesh_; //!< The mesh of the last planned move. @@ -202,14 +200,14 @@ class LayerPlan : public NoCopy * Whether the prime tower is already planned for the specified extruder. * \param extruder_nr The extruder to check. */ - bool getPrimeTowerIsPlanned(unsigned int extruder_nr) const; + bool getPrimeTowerIsPlanned(size_t extruder_nr) const; /*! * Mark the prime tower as planned for the specified extruder. * \param extruder_nr The extruder to mark as having its prime tower * planned. */ - void setPrimeTowerIsPlanned(unsigned int extruder_nr); + void setPrimeTowerIsPlanned(size_t extruder_nr); /*! * Whether the prime tower extra base is already planned. diff --git a/include/PrimeTower/PrimeTower.h b/include/PrimeTower/PrimeTower.h new file mode 100644 index 0000000000..e0bbf5d5a6 --- /dev/null +++ b/include/PrimeTower/PrimeTower.h @@ -0,0 +1,234 @@ +// 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/ClosedLinesSet.h" +#include "geometry/Polygon.h" +#include "settings/EnumSettings.h" +#include "settings/types/LayerIndex.h" +#include "utils/LayerVector.h" +#include "utils/polygonUtils.h" + +namespace cura +{ + +class SliceDataStorage; +class LayerPlan; + +/*! + * Abstract class for everything to do with the prime tower: + * - Generating the occupation areas. + * - Checking up untill which height the prime tower has to be printed. + * - Inserting priming commands in extruders uses + * - Generating priming paths and adding them to the layer plan. + * + * We may adopt different strategies to generate the prime tower, thus this class is abstract and different + * implementations may co-exist. The common behavior implemented in the main class is: + * - Generate occupation areas as a cylinder with a flared base + * - Generate the base extra extrusion discs around the base cylinder + * - Generate the first layer extra inset inside the base cylinder + * Then it is the job of the specific implementation to handle the generation of extrusion paths for the base cylinder + */ +class PrimeTower +{ +protected: + struct OccupiedOutline + { + Polygon outline; + coord_t outer_radius; + }; + + struct ExtruderToolPaths + { + size_t extruder_nr; + ClosedLinesSet toolpaths; + coord_t outer_radius; + coord_t inner_radius; + }; + +private: + 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 + + static constexpr size_t number_of_prime_tower_start_locations_ = 21; //!< The required size of \ref PrimeTower::wipe_locations + static constexpr AngleRadians start_locations_step_ = (std::numbers::pi * 2.0) / number_of_prime_tower_start_locations_; + + /* + * The map index is the layer number + * For each layer, the list contains the extruders moves to be processed. This list is sorted from outer annuli to inner + * annuli, which is not the printing chronological order, but the physical arrangement. + */ + std::map> toolpaths_; + + OccupiedOutline outer_poly_; //!< The outline of the prime tower, not including the base + + //!< This is the exact outline of the extrusions lines of each layer, for layers having extra width for the base + LayerVector base_extrusion_outline_; + //!< This is the approximate outline of the area filled at each layer, for layers having extra width for the base + LayerVector base_occupied_outline_; + + static constexpr size_t circle_definition_{ 32 }; // The number of vertices in each circle. + static constexpr size_t arc_definition_{ 4 }; // The number of segments in each arc of a wheel + +public: + /*! \brief Creates a prime tower instance that will determine where and how the prime tower gets printed. */ + PrimeTower(); + + virtual ~PrimeTower() = default; + + /*! + * 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; + + /*! + * Get the occupied outline of the prime tower at the given layer + * + * \param[in] layer_nr The index of the layer + * \return The outer polygon for the prime tower at the given layer + * \note The returned outline is a close approximation of the actual toolpaths. The actual extrusion area may be slightly smaller. + * Use this method only if you need to get the exclusion area of the prime tower. Otherwise use getExtrusionOutline(). + * This method exists because this approximate area can be calculated as soon as the prime tower is initialized. + */ + const Polygon& getOccupiedOutline(const LayerIndex& layer_nr) const; + + /*! + * Get the occupied outline of the prime tower at the first layer + * + * \note @sa getOccupiedOutline() + */ + const Polygon& getOccupiedGroundOutline() const; + + /*! + * Get the extrusion outline of the prime tower at the given layer + * + * \param[in] layer_nr The index of the layer + * \return The extrusion outline for the prime tower at the given layer + * \note The returned outline is the exact outline of the extrusion path, which is useful if you need to generate a toolpath + * touching the prime tower. Otherwise use getExtrusionOutline(). This method will return the valid result only after + * processExtrudersUse() has been called, which is "late" is the global slicing operation. + */ + const Polygon& getExtrusionOutline(const LayerIndex& layer_nr) const; + + /*! + * \brief Get the required priming for the given extruder at the given layer + * \param extruder_is_used_on_this_layer A list indicating which extruders are used at this layer + * \param extruder_nr The extruder for which we want the priming information + * \param last_extruder The extruder that was in use just before using the new one + * \param storage The storage containing all the slice data + * \param layer_nr The layer at which we want to use the extruder + * \return An enumeration indication how the extruder will be used by the prime tower at this layer + */ + virtual ExtruderPrime getExtruderPrime( + const std::vector& extruder_is_used_on_this_layer, + size_t extruder_nr, + size_t last_extruder, + const SliceDataStorage& storage, + const LayerIndex& layer_nr) const = 0; + + /*! + * \brief This method has to be called once the extruders use for each layer have been calculated. From this point, + * we can start generating the prime tower, and also polish the given extruders uses. + * \param extruders_use The calculated extruders uses at each layer. This may be slightly changed to make sure that + * the prime tower can be properly printed. + * \param start_extruder The very first used extruder + */ + void processExtrudersUse(LayerVector>& extruders_use, const size_t start_extruder); + + /*! + * \brief Create the proper prime tower object according to the current settings + * \param storage The storage containing all the slice data + * \return The proper prime tower object, which may be null if prime tower is actually disabled or not required + */ + static PrimeTower* createPrimeTower(SliceDataStorage& storage); + +protected: + /*! + * \brief Once all the extruders uses have been calculated for each layer, this method makes a global pass to make + * sure that the prime tower can be properly printed. This is required because we sometimes need to know what + * a layer above is made of to fix a layer below. + * \param extruders_use The calculated extruders uses at each layer + * \param start_extruder The very first used extruder + */ + virtual void polishExtrudersUses(LayerVector>& /*extruders_use*/, const size_t /*start_extruder*/) + { + // Default behavior is to keep the extruders uses as they were calculated + } + + /*! + * \brief Generated the extruders toolpaths for each layer of the prime tower + * \param extruders_use The calculated extruders uses at each layer + * \return A map of extruders toolpaths per layer. The inner list is sorted from outer annuli to inner + * annuli, which is not the printing chronological order, but the physical arrangement. @sa toolpaths_ + */ + virtual std::map> generateToolPaths(const LayerVector>& extruders_use) = 0; + + /*! + * \brief Generate the actual priming toolpaths for the given extruder, starting at the given outer circle radius + * \param extruder_nr The extruder for which we want the priming toolpath + * \param outer_radius The radius of the starting outer circle + * \return A tuple containing the newly generated toolpaths, and the inner radius of the newly generated annulus + */ + std::tuple generatePrimeToolpaths(const size_t extruder_nr, const coord_t outer_radius); + + /*! + * \brief Generate support toolpaths using the wheel pattern applied on an annulus + * \param extruder_nr The extruder for which we want the support toolpath + * \param outer_radius The annulus outer radius + * \param inner_radius The annulis inner radius + * \return + */ + ClosedLinesSet generateSupportToolpaths(const size_t extruder_nr, const coord_t outer_radius, const coord_t inner_radius); + + /*! + * \brief Calculates whether an extruder requires priming at a specific layer + * \param extruder_is_used_on_this_layer The list of used extruders at this layer + * \param extruder_nr The extruder we now want to use + * \param last_extruder The extruder that was in use before switching to the new one + * \return True if the extruder needs to be primed, false otherwise + */ + static bool extruderRequiresPrime(const std::vector& extruder_is_used_on_this_layer, size_t extruder_nr, size_t last_extruder); + +private: + /*! \brief Generates the extra inset used for better adhesion at the first layer */ + void generateFirtLayerInset(); + + /*! \brief Generates the extra annuli around the first layers of the prime tower which help make it stronger */ + void generateBase(); + + /*! + * 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 size_t extruder) 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); +}; + +} // namespace cura + +#endif // PRIME_TOWER_H diff --git a/include/PrimeTower/PrimeTowerInterleaved.h b/include/PrimeTower/PrimeTowerInterleaved.h new file mode 100644 index 0000000000..d9d743ca4c --- /dev/null +++ b/include/PrimeTower/PrimeTowerInterleaved.h @@ -0,0 +1,40 @@ +// Copyright (c) 2024 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef PRIME_TOWER_INTERLEAVED_H +#define PRIME_TOWER_INTERLEAVED_H + +#include "PrimeTower/PrimeTower.h" + +namespace cura +{ + +/*! + * Specific prime tower implementation that generates interleaved priming paths. It is optimized to waste as few + * filament as possible, while ensuring that the prime tower is still robust even if it gets very high. + * When there is no actual priming required for extruders, it will create a kind of circular zigzag pattern that acts as + * a sparse support. Otherwise it will create priming annuli, stacked on top of each other. + * This is very effective when doing multi-color printing, however it can be used only if all the filaments properly + * adhere to each other. Otherwise there is a high risk that the tower will collapse during the print. + */ +class PrimeTowerInterleaved : public PrimeTower +{ +public: + PrimeTowerInterleaved(); + + virtual ExtruderPrime getExtruderPrime( + const std::vector& extruder_is_used_on_this_layer, + size_t extruder_nr, + size_t last_extruder, + const SliceDataStorage& storage, + const LayerIndex& layer_nr) const override; + +protected: + virtual void polishExtrudersUses(LayerVector>& extruders_use, const size_t start_extruder) override; + + virtual std::map> generateToolPaths(const LayerVector>& extruders_use) override; +}; + +} // namespace cura + +#endif // PRIME_TOWER_INTERLEAVED_H diff --git a/include/PrimeTower/PrimeTowerNormal.h b/include/PrimeTower/PrimeTowerNormal.h new file mode 100644 index 0000000000..5235774252 --- /dev/null +++ b/include/PrimeTower/PrimeTowerNormal.h @@ -0,0 +1,37 @@ +// Copyright (c) 2024 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef PRIME_TOWER_NORMAL_H +#define PRIME_TOWER_NORMAL_H + +#include "PrimeTower/PrimeTower.h" + +namespace cura +{ + +/*! + * Specific prime tower implementation that generates nested cylinders. Each layer, all the extruders will be used to + * contribute to the prime tower, even if they don't actually need priming. In this case, a circular zigzag pattern will + * be used to act as support for upper priming extrusions. + * Although this method is not very efficient, it is required when using different materials that don't properly adhere + * to each other. By nesting the cylinders, you make sure that the tower remains consistent and strong along the print. + */ +class PrimeTowerNormal : public PrimeTower +{ +public: + PrimeTowerNormal(); + + virtual ExtruderPrime getExtruderPrime( + const std::vector& extruder_is_used_on_this_layer, + size_t extruder_nr, + size_t last_extruder, + const SliceDataStorage& storage, + const LayerIndex& layer_nr) const override; + +protected: + virtual std::map> generateToolPaths(const LayerVector>& extruders_use) override; +}; + +} // namespace cura + +#endif // PRIME_TOWER_NORMAL_H diff --git a/include/TreeModelVolumes.h b/include/TreeModelVolumes.h index 0dee5f62c1..1774b24fdf 100644 --- a/include/TreeModelVolumes.h +++ b/include/TreeModelVolumes.h @@ -6,14 +6,15 @@ #include #include +#include #include #include -#include "TreeSupportSettings.h" -#include "geometry/Polygon.h" //For polygon parameters. +#include "TreeSupportEnums.h" +#include "geometry/Shape.h" #include "settings/EnumSettings.h" //To store whether X/Y or Z distance gets priority. #include "settings/types/LayerIndex.h" //Part of the RadiusLayerPair. -#include "sliceDataStorage.h" +#include "utils/PairHash.h" #include "utils/Simplify.h" namespace cura @@ -22,6 +23,7 @@ constexpr coord_t EPSILON = 5; constexpr coord_t FUDGE_LENGTH = 50; class SliceDataStorage; +class SliceMeshStorage; class LayerIndex; class Settings; @@ -169,7 +171,6 @@ class TreeModelVolumes */ Shape extractOutlineFromMesh(const SliceMeshStorage& mesh, LayerIndex layer_idx) const; - /*! * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. * diff --git a/include/geometry/ClosedPolyline.h b/include/geometry/ClosedPolyline.h index 8f4bc3e753..979ed6f29c 100644 --- a/include/geometry/ClosedPolyline.h +++ b/include/geometry/ClosedPolyline.h @@ -32,14 +32,17 @@ class ClosedPolyline : public Polyline bool explicitely_closed_{ false }; public: + /*! + * \brief Builds an empty closed polyline + * \warning By default, the line is tagged as non explicitely closed. We need this default + * constructor in various places, but be careful that the interpretation of the points + * added later will depend on this. + */ ClosedPolyline() = default; /*! * \brief Builds an empty closed polyline * \param explicitely_closed Indicates whether the line will be explicitely closed - * \warning By default, the line is tagged as explicitely closed. We need this default - * constructor in various places, but be careful that the interpretation of the points - * added later will depend on this. */ explicit ClosedPolyline(const bool explicitely_closed) : explicitely_closed_{ explicitely_closed } diff --git a/include/geometry/LinesSet.h b/include/geometry/LinesSet.h index 1e885cb89c..7834b821eb 100644 --- a/include/geometry/LinesSet.h +++ b/include/geometry/LinesSet.h @@ -65,6 +65,12 @@ class LinesSet { } + /*! \brief Constructor with a single existing line */ + explicit LinesSet(const LineType& line) + : lines_({ line }) + { + } + /*! * \brief Constructor that takes ownership of the data from the given set of lines * \warning This constructor is actually only defined for a LinesSet containing OpenPolyline @@ -261,6 +267,13 @@ class LinesSet */ void addPaths(ClipperLib::Clipper& clipper, ClipperLib::PolyType poly_typ) const; + /*! + * \brief Utility method to add a line to a ClipperLib::Clipper object + * \note This method needs to be public but you shouldn't need to use it from outside + */ + template + void addPath(ClipperLib::Clipper& clipper, const OtherLineLine& line, ClipperLib::PolyType poly_typ) const; + /*! * \brief Utility method to add all the lines to a ClipperLib::ClipperOffset object * \note This method needs to be public but you shouldn't need to use it from outside diff --git a/include/geometry/Shape.h b/include/geometry/Shape.h index aaa373f4e1..552325676f 100644 --- a/include/geometry/Shape.h +++ b/include/geometry/Shape.h @@ -43,6 +43,9 @@ class Shape : public LinesSet /*! \brief Constructor with an existing set of polygons */ Shape(const std::vector& polygons); + /*! \brief Constructor with a single existing polygon */ + explicit Shape(const Polygon& polygon); + /*! * \brief Constructor that takes ownership of the given list of points * \param explicitely_closed Specify whether the given points form an explicitely closed line @@ -66,8 +69,12 @@ class Shape : public LinesSet [[nodiscard]] Shape difference(const Shape& other) const; + [[nodiscard]] Shape difference(const Polygon& polygon) const; + [[nodiscard]] Shape unionPolygons(const Shape& other, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero) const; + [[nodiscard]] Shape unionPolygons(const Polygon& polygon, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero) const; + /*! * Union all polygons with each other (When polygons.add(polygon) has been called for overlapping polygons) */ diff --git a/include/settings/EnumSettings.h b/include/settings/EnumSettings.h index bc6ea72cee..847ccb10b8 100644 --- a/include/settings/EnumSettings.h +++ b/include/settings/EnumSettings.h @@ -246,9 +246,9 @@ enum class InsetDirection }; /*! - * Method used for prime tower generation + * Prime tower generation mode */ -enum class PrimeTowerMethod +enum class PrimeTowerMode { /*! * Prime tower that minimizes time and used filament as much as possible. diff --git a/include/settings/types/Angle.h b/include/settings/types/Angle.h index f2814ee3c8..20b6cc2405 100644 --- a/include/settings/types/Angle.h +++ b/include/settings/types/Angle.h @@ -105,17 +105,17 @@ class AngleRadians /* * \brief Default constructor setting the angle to 0. */ - AngleRadians() noexcept = default; + constexpr AngleRadians() noexcept = default; /*! * \brief Converts an angle from degrees into radians. */ - AngleRadians(const AngleDegrees& value); + constexpr AngleRadians(const AngleDegrees& value); /* * \brief Translate the double value in degrees to an AngleRadians instance. */ - AngleRadians(double value) + constexpr AngleRadians(double value) : value_(std::fmod(std::fmod(value, TAU) + TAU, TAU)) { } @@ -166,7 +166,7 @@ inline AngleDegrees::AngleDegrees(const AngleRadians& value) { } -inline AngleRadians::AngleRadians(const AngleDegrees& value) +constexpr inline AngleRadians::AngleRadians(const AngleDegrees& value) : value_(static_cast(value) * TAU / 360.0) { } diff --git a/include/sliceDataStorage.h b/include/sliceDataStorage.h index be95224cbf..4b434aa5ae 100644 --- a/include/sliceDataStorage.h +++ b/include/sliceDataStorage.h @@ -8,8 +8,6 @@ #include #include -#include "PrimeTower.h" -#include "RetractionConfig.h" #include "SupportInfillPart.h" #include "TopSurface.h" #include "WipeScriptConfig.h" @@ -27,15 +25,13 @@ #include "utils/AABB3D.h" #include "utils/NoCopy.h" -// libArachne -#include "utils/ExtrusionLine.h" - namespace cura { class Mesh; class SierpinskiFillProvider; class LightningGenerator; +class PrimeTower; /*! * A SkinPart is a connected area designated as top and/or bottom skin. @@ -391,7 +387,8 @@ class SliceDataStorage : public NoCopy std::vector spiralize_seam_vertex_indices; //!< the index of the seam vertex for each layer std::vector spiralize_wall_outlines; //!< the wall outline polygons for each layer - PrimeTower primeTower; + //!< Pointer to primer tower handler object (a null pointer indicates that there is no prime tower) + PrimeTower* prime_tower_{ nullptr }; std::vector ooze_shield; // oozeShield per layer Shape draft_protection_shield; //!< The polygons for a heightened skirt which protects from warping by gusts of wind and acts as a heated chamber. @@ -402,9 +399,7 @@ class SliceDataStorage : public NoCopy */ SliceDataStorage(); - ~SliceDataStorage() - { - } + ~SliceDataStorage(); /*! * Get all outlines within a given layer. @@ -462,6 +457,8 @@ class SliceDataStorage : public NoCopy */ Shape getMachineBorder(int extruder_nr = -1) const; + void initializePrimeTower(); + private: /*! * Construct the retraction_wipe_config_per_extruder diff --git a/include/utils/LayerVector.h b/include/utils/LayerVector.h new file mode 100644 index 0000000000..929f916297 --- /dev/null +++ b/include/utils/LayerVector.h @@ -0,0 +1,234 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef UTILS_LAYER_VECTOR_H +#define UTILS_LAYER_VECTOR_H + +#include + +#include "raft.h" +#include "settings/types/LayerIndex.h" + +namespace cura +{ + +/*! + * \brief The LayerVector class mimics a std::vector but with the index being a LayerIndex, thus it can have negative + * values (for raft layers). It also ensures that the first element in the list is always on the very first layer. + * \note When calling the init() method, LayerVector will call Raft::getTotalExtraLayers() so it requires the settings + * to be setup. This is the reason why this is not done in the constructor, and has to be called manually. + * After that, it is assumed that this value will not change as long as the vector is used. + * \warning It is assumed that items are inserted in layer order, and without missing items, like a std::vector would do + */ +template +class LayerVector +{ + // Required for some std calls as a container + using value_type = T; + using iterator = typename std::vector::iterator; + using const_iterator = typename std::vector::const_iterator; + using reverse_iterator = typename std::vector::reverse_iterator; + using const_reverse_iterator = typename std::vector::const_reverse_iterator; + using reference = value_type&; + using const_reference = const value_type&; + using difference_type = typename std::vector::difference_type; + +public: + LayerVector() = default; + + /*! + * \brief Initializes the vector for use + * \param contains_raft_layers Indicates whether this vector will contain raft layers + * \param print_layers The number of print layers (not including raft layers). This is only for pre-reserving + * stored data and can be safely omitted. + * \note It is not mandatory to call this method if you do not intend to store raft layers, but still good practice + */ + void init(bool contains_raft_layers, size_t print_layers = 0) + { + delta_ = contains_raft_layers ? Raft::getTotalExtraLayers() : 0; + + vector_.clear(); + if (print_layers) + { + vector_.reserve(print_layers + delta_); + } + } + + const_iterator begin() const + { + return vector_.begin(); + } + + iterator begin() + { + return vector_.begin(); + } + + const_iterator end() const + { + return vector_.end(); + } + + iterator end() + { + return vector_.end(); + } + + const_reverse_iterator rbegin() const + { + return vector_.rbegin(); + } + + reverse_iterator rbegin() + { + return vector_.rbegin(); + } + + const_reverse_iterator rend() const + { + return vector_.rend(); + } + + reverse_iterator rend() + { + return vector_.rend(); + } + + const_reference operator[](const LayerIndex& pos) const + { + return vector_[static_cast(pos + delta_)]; + } + + reference operator[](const LayerIndex& pos) + { + return vector_[static_cast(pos + delta_)]; + } + + const_reference at(const LayerIndex& pos) const + { + return vector_.at(static_cast(pos + delta_)); + } + + reference at(const LayerIndex& pos) + { + return vector_.at(static_cast(pos + delta_)); + } + + const_reference front() const + { + return vector_.front(); + } + + reference front() + { + return vector_.front(); + } + + [[nodiscard]] const_iterator iterator_at(const LayerIndex& pos) const + { + LayerIndex::value_type index = pos + delta_; + if (index >= 0 && static_cast(index) < size()) + { + return std::next(begin(), static_cast(index)); + } + return end(); + } + + [[nodiscard]] iterator iterator_at(const LayerIndex& pos) + { + LayerIndex::value_type index = pos + delta_; + if (index >= 0 && static_cast(index) < size()) + { + return std::next(begin(), static_cast(index)); + } + return end(); + } + + /*! + * \brief Safe method to retrieve an element from the list + * \param pos The position of the element to be retrieved + * \return The element at the given position, or if there is none, a default-constructed value. + */ + value_type get(const LayerIndex& pos) const noexcept + { + LayerIndex::value_type index = pos + delta_; + if (index >= 0 && static_cast(index) < size()) + { + return vector_[static_cast(index)]; + } + return value_type{}; + } + + [[nodiscard]] LayerIndex getLayer(const const_iterator& it) const + { + return std::distance(begin(), it) - static_cast(delta_); + } + + [[nodiscard]] LayerIndex getLayer(const iterator& it) const + { + return std::distance(begin(), it) - static_cast(delta_); + } + + [[nodiscard]] LayerIndex getLayer(const const_reverse_iterator& it) const + { + return std::distance(it, rend()) - 1 - static_cast(delta_); + } + + [[nodiscard]] LayerIndex getLayer(const reverse_iterator& it) const + { + return std::distance(it, rend()) - 1 - static_cast(delta_); + } + + void pop_back() + { + vector_.pop_back(); + } + + void push_back(const_reference item) + { + vector_.push_back(item); + } + + void push_back(T&& item) + { + vector_.push_back(std::move(item)); + } + + [[nodiscard]] size_t size() const + { + return vector_.size(); + } + + [[nodiscard]] bool empty() const + { + return vector_.empty(); + } + + void reserve(size_t size) + { + vector_.reserve(size); + } + + void resize(size_t size) + { + vector_.resize(size); + } + + void clear() + { + vector_.clear(); + } + + void emplace_back(auto&&... args) + { + vector_.emplace_back(std::forward(args)...); + } + +private: + std::vector vector_; + size_t delta_{ 0 }; +}; + +} // namespace cura + +#endif // UTILS_LAYER_VECTOR_H diff --git a/include/utils/PairHash.h b/include/utils/PairHash.h new file mode 100644 index 0000000000..a94993ed9a --- /dev/null +++ b/include/utils/PairHash.h @@ -0,0 +1,24 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef UTILS_PAIRHASH_H +#define UTILS_PAIRHASH_H + +#include +#include + +namespace std +{ + +template +struct hash> +{ + size_t operator()(const std::pair& pair) const + { + return 31 * std::hash()(pair.first) + 59 * std::hash()(pair.second); + } +}; + +} // namespace std + +#endif // UTILS_PAIRHASH_H diff --git a/include/utils/SymmetricPair.h b/include/utils/SymmetricPair.h deleted file mode 100644 index b7e3e1dc4e..0000000000 --- a/include/utils/SymmetricPair.h +++ /dev/null @@ -1,89 +0,0 @@ -//Copyright (c) 2020 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. - -#ifndef UTILS_SYMMETRIC_PAIR -#define UTILS_SYMMETRIC_PAIR - -#include // pair - -namespace cura -{ - -/*! - * A utility class for a pair of which the order between the first and the second doesn't matter. - * - * \tparam A The type of both elements of the pair. - */ -template -class SymmetricPair : public std::pair -{ -public: - /*! - * Forwarding std::pair constructor - */ - template - SymmetricPair(const SymmetricPair& pr) - : std::pair(pr) - { - } - /*! - * Forwarding std::pair constructor - */ - template - SymmetricPair(SymmetricPair&& pr) - : std::pair(pr) - { - } - /*! - * Forwarding std::pair constructor - */ - SymmetricPair(const A& first, const A& second) - : std::pair(first, second) - { - } - /*! - * Forwarding std::pair constructor - */ - template - SymmetricPair(U&& first, U&& second) - : std::pair(first, second) - { - } - /*! - * Forwarding std::pair constructor - */ - template - SymmetricPair(std::piecewise_construct_t pwc, std::tuple first_args, std::tuple second_args) - : std::pair(pwc, first_args, second_args) - { - } - - /*! - * Equality operator which checks if two SymmetricPairs are equal regardless of the order between first and second - */ - bool operator==(const SymmetricPair& other) const - { - return (std::pair::first == other.first && std::pair::second == other.second) || (std::pair::first == other.second && std::pair::second == other.first); - } -}; - -}//namespace cura - -namespace std -{ - -/*! - * Hash operator which creates a hash regardless of the order between first and second - */ -template -struct hash> -{ - size_t operator()(const cura::SymmetricPair& pr) const - { // has to be symmetric wrt a and b! - return std::hash()(pr.first) + std::hash()(pr.second); - } -}; -}//namespace std - - -#endif // UTILS_SYMMETRIC_PAIR \ No newline at end of file diff --git a/include/utils/polygonUtils.h b/include/utils/polygonUtils.h index 7ed9d92787..3856b8e645 100644 --- a/include/utils/polygonUtils.h +++ b/include/utils/polygonUtils.h @@ -13,6 +13,7 @@ #include "PolygonsPointIndex.h" #include "SparseLineGrid.h" #include "SparsePointGridInclusive.h" +#include "geometry/ClosedLinesSet.h" #include "geometry/Polygon.h" namespace cura @@ -78,37 +79,6 @@ struct ClosestPoint using ClosestPointPolygon = ClosestPoint; -} // namespace cura - -namespace std -{ -template<> -struct hash -{ - size_t operator()(const cura::ClosestPointPolygon& cpp) const - { - return std::hash()(cpp.p()); - } -}; -} // namespace std - - -namespace std -{ -template -struct hash> -{ - size_t operator()(const std::pair& pair) const - { - return 31 * std::hash()(pair.first) + 59 * std::hash()(pair.second); - } -}; -} // namespace std - - -namespace cura -{ - /*! * A point within a polygon and the index of which segment in the polygon the point lies on. */ @@ -125,34 +95,6 @@ class PolygonUtils public: static const std::function no_penalty_function; //!< Function always returning zero - /*! - * compute the length of a segment of a polygon - * - * if \p end == \p start then the full polygon is taken - * - * \warning assumes that start and end lie on the same polygon! - * - * \param start The start vertex of the segment - * \param end the end vertex of the segment - * \return the total length of all the line segments in between the two vertices. - */ - static int64_t segmentLength(PolygonsPointIndex start, PolygonsPointIndex end); - - /*! - * Generate evenly spread out dots along a segment of a polygon - * - * Start at a distance from \p start and end at a distance from \p end, - * unless \p end == \p start; then that point is in the result - * - * \warning Assumes that start and end lie on the same polygon! - * - * \param start The start vertex of the segment - * \param end the end vertex of the segment - * \param n_dots number of dots to spread out - * \param result Where to store the generated points - */ - static void spreadDots(PolygonsPointIndex start, PolygonsPointIndex end, unsigned int n_dots, std::vector& result); - /*! * Generate a grid of dots inside of the area of the \p polygons. */ @@ -191,14 +133,6 @@ class PolygonUtils */ static Point2LL getBoundaryPointWithOffset(const Polyline& poly, unsigned int point_idx, int64_t offset); - /*! - * Move a point away from the boundary by looking at the boundary normal of the nearest vert. - * - * \param point_on_boundary The object holding the point on the boundary along with the information of which line segment the point is on. - * \param offset The distance the point has to be moved inward from the polygon. - */ - static Point2LL moveInsideDiagonally(ClosestPointPolygon point_on_boundary, int64_t inset); - /*! * Moves the point \p from onto the nearest polygon or leaves the point as-is, when the comb boundary is not within the root of \p max_dist2 distance. * Given a \p distance more than zero, the point will end up inside, and conversely outside. @@ -666,37 +600,64 @@ class PolygonUtils static double relativeHammingDistance(const Shape& poly_a, const Shape& poly_b); /*! - * Create an approximation of a circle. + * Creates a regular polygon that is supposed to approximate a disc. + * + * \param mid The center of the disc. + * \param radius The radius of the disc. + * \param steps The numbers of segments (definition) of the generated disc. + * \return A new Polygon containing the disc. + */ + static Polygon makeDisc(const Point2LL& mid, const coord_t radius, const size_t steps); + + /*! + * Creates a closed polyline that is supposed to approximate a circle. * - * This creates a regular polygon that is supposed to approximate a circle. * \param mid The center of the circle. * \param radius The radius of the circle. - * \param a_step The angle between segments of the circle. - * \return A new Polygon containing the circle. + * \param segments The numbers of segments (definition) of the generated circle. + * \tparam explicitly_closed Indicates whether the circle should be explicitely (or implicitely) closed + * \return A new object containing the circle points. */ - template - static T makeCircle(const Point2LL& mid, const coord_t radius, const AngleRadians a_step = std::numbers::pi / 8, VA... args) + template + static T makeCircle(const Point2LL& mid, const coord_t radius, const size_t segments, VA... args) { T circle; - for (double a = 0; a < 2 * std::numbers::pi; a += a_step) + const AngleRadians step_angle = (std::numbers::pi * 2) / static_cast(segments); + for (size_t step = 0; step < segments; ++step) { - circle.emplace_back(mid + Point2LL(radius * cos(a), radius * sin(a)), args...); + const AngleRadians angle = static_cast(step) * step_angle; + circle.emplace_back(makeCirclePoint(mid, radius, angle), args...); } + + if constexpr (explicitely_closed) + { + circle.push_back(circle.front()); + } + return circle; } /*! - * Create a "wheel" shape. + * Create a point of a circle. + * + * \param mid The center of the circle. + * \param radius The radius of the circle. + * \param angle The point angular position + * \return The coordinates of the point on the circle. + */ + static Point2LL makeCirclePoint(const Point2LL& mid, const coord_t radius, const AngleRadians& angle); + + /*! + * This creates a polyline which represents the shape of a wheel, which is kind of a "circular zigzag" pattern. * - * This creates a polygon which represents the shape of a wheel. * \param mid The center of the circle. * \param inner_radius The radius of the wheel inner circle. * \param outer_radius The radius of the wheel outer circle. * \param semi_nb_spokes The semi number of spokes in the wheel. There will actually be N*2 spokes. - * \param arc_angle_resolution The number of segment on each arc. - * \return A new Polygon containing the circle. + * \param arc_angle_resolution The number of segments on each arc. + * \return A new Polyline containing the circle. */ - static Polygon makeWheel(const Point2LL& mid, const coord_t inner_radius, const coord_t outer_radius, const size_t semi_nb_spokes, const size_t arc_angle_resolution); + static ClosedPolyline makeWheel(const Point2LL& mid, const coord_t inner_radius, const coord_t outer_radius, const size_t semi_nb_spokes, const size_t arc_angle_resolution); /*! * Connect all polygons to their holes using zero widths hole channels, so that the polygons and their outlines are connected together @@ -707,7 +668,6 @@ class PolygonUtils static Shape unionManySmall(const Shape& polygon); - /*! * Intersects a polygon with an AABB. * \param src The polygon that has to be intersected with an AABB @@ -717,24 +677,28 @@ class PolygonUtils static Shape clipPolygonWithAABB(const Shape& src, const AABB& aabb); /*! - * Generate a few outset polygons around the given base, according to the given line width + * Generate a few outset circles around a base, according to the given line width * - * \param inner_poly The inner polygon to start generating the outset from - * \param count The number of outer polygons to add + * \param center The center of the outset + * \param inner_radius The inner radius to start generating the outset from + * \param outer_radius The outer radius to fit the outset into * \param line_width The actual line width to distance the polygons from each other (and from the base) - * \return The generated outset polygons + * \param circle_definition The definition (number of segments) of the generated circles + * \return The generated outset circles, and the outer radius or the shape */ - static Shape generateOutset(const Shape& inner_poly, size_t count, coord_t line_width); + static std::tuple + generateCirculatOutset(const Point2LL& center, const coord_t inner_radius, const coord_t outer_radius, const coord_t line_width, const size_t circle_definition); /*! - * Generate inset polygons inside the given base, until there is no space left, according to the given line width + * Generate inset circles inside the given base, until there is no space left, according to the given line width * - * \param outer_poly The outer polygon to start generating the inset from + * \param center The center of the inset + * \param outer_radius The outer radius to start generating the inset from * \param line_width The actual line width to distance the polygons from each other (and from the base) - * \param initial_inset The inset distance to be added to the first generated polygon - * \return The generated inset polygons + * \param circle_definition The definition (number of segments) of the generated circles + * \return The generated inset circles */ - static Shape generateInset(const Shape& outer_poly, coord_t line_width, coord_t initial_inset = 0); + static ClosedLinesSet generateCircularInset(const Point2LL& center, const coord_t outer_radius, const coord_t line_width, const size_t circle_definition); private: /*! @@ -750,7 +714,20 @@ class PolygonUtils static ClosestPointPolygon _moveInside2(const ClosestPointPolygon& closest_polygon_point, const int distance, Point2LL& from, const int64_t max_dist2); }; - } // namespace cura -#endif // POLYGON_OPTIMIZER_H +namespace std +{ + +template<> +struct hash +{ + size_t operator()(const cura::ClosestPointPolygon& cpp) const + { + return std::hash()(cpp.p()); + } +}; + +} // namespace std + +#endif // UTILS_POLYGON_UTILS_H diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 684599df9a..64d52a93a4 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -21,6 +21,7 @@ #include "InsetOrderOptimizer.h" #include "LayerPlan.h" #include "PathOrderMonotonic.h" //Monotonic ordering of skin lines. +#include "PrimeTower/PrimeTower.h" #include "Slice.h" #include "WallToolPaths.h" #include "bridge.h" @@ -279,7 +280,7 @@ void FffGcodeWriter::findLayerSeamsForSpiralize(SliceDataStorage& storage, size_ bool done_this_layer = false; // iterate through extruders until we find a mesh that has a part with insets - const std::vector extruder_order = getExtruderUse(layer_nr); + const std::vector extruder_order = extruder_order_per_layer.get(layer_nr); for (unsigned int extruder_idx = 0; ! done_this_layer && extruder_idx < extruder_order.size(); ++extruder_idx) { const size_t extruder_nr = extruder_order[extruder_idx].extruder_nr; @@ -643,9 +644,9 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) std::vector raft_outline_paths; raft_outline_paths.emplace_back(ParameterizedRaftPath{ line_spacing, storage.raft_base_outline }); - if (storage.primeTower.enabled_) + if (storage.prime_tower_) { - const Shape& raft_outline_prime_tower = storage.primeTower.getOuterPoly(layer_nr); + const Shape raft_outline_prime_tower = Shape(storage.prime_tower_->getExtrusionOutline(layer_nr)); if (line_spacing_prime_tower == line_spacing) { // Base layer is shared with prime tower base @@ -815,10 +816,10 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) constexpr int zag_skip_count = 0; constexpr coord_t pocket_size = 0; - if (storage.primeTower.enabled_) + if (storage.prime_tower_) { // Interface layer excludes prime tower base - raft_outline_path = raft_outline_path.difference(storage.primeTower.getOuterPoly(layer_nr)); + raft_outline_path = raft_outline_path.difference(storage.prime_tower_->getExtrusionOutline(layer_nr)); } Infill infill_comp( @@ -980,10 +981,10 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) constexpr size_t zag_skip_count = 0; constexpr coord_t pocket_size = 0; - if (storage.primeTower.enabled_) + if (storage.prime_tower_) { // Surface layers exclude prime tower base - raft_outline_path = raft_outline_path.difference(storage.primeTower.getOuterPoly(layer_nr)); + raft_outline_path = raft_outline_path.difference(storage.prime_tower_->getExtrusionOutline(layer_nr)); } for (const Shape& raft_island : raft_outline_path.splitIntoParts()) @@ -1110,7 +1111,7 @@ void FffGcodeWriter::endRaftLayer(const SliceDataStorage& storage, LayerPlan& gc setExtruder_addPrime(storage, gcode_layer, current_extruder, append_to_prime_tower); // If required, fill prime tower for other extruders - for (const ExtruderUse& extruder_use : getExtruderUse(layer_nr)) + for (const ExtruderUse& extruder_use : extruder_order_per_layer.get(layer_nr)) { if (! append_to_prime_tower || (! gcode_layer.getPrimeTowerIsPlanned(extruder_use.extruder_nr) && extruder_use.prime != ExtruderPrime::None)) { @@ -1193,12 +1194,9 @@ FffGcodeWriter::ProcessLayerResult FffGcodeWriter::processLayer(const SliceDataS } const coord_t comb_offset_from_outlines = max_inner_wall_width * 2; - assert( - static_cast(extruder_order_per_layer_negative_layers.size()) + layer_nr >= 0 && "Layer numbers shouldn't get more negative than there are raft/filler layers"); - const size_t first_extruder = findUsedExtruderIndex(storage, layer_nr, false); - const std::vector extruder_order = getExtruderUse(layer_nr); + const std::vector extruder_order = extruder_order_per_layer.get(layer_nr); const coord_t first_outer_wall_line_width = scene.extruders[first_extruder].settings_.get("wall_line_width_0"); LayerPlan& gcode_layer = *new LayerPlan( @@ -1238,7 +1236,7 @@ FffGcodeWriter::ProcessLayerResult FffGcodeWriter::processLayer(const SliceDataS for (const ExtruderUse& extruder_use : extruder_order) { - 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); @@ -1523,30 +1521,35 @@ void FffGcodeWriter::calculateExtruderOrderPerLayer(const SliceDataStorage& stor size_t last_extruder; // set the initial extruder of this meshgroup Scene& scene = Application::getInstance().current_slice_->scene; + size_t start_extruder; if (scene.current_mesh_group == scene.mesh_groups.begin()) { // first meshgroup - last_extruder = getStartExtruder(storage); + start_extruder = getStartExtruder(storage); } else { - last_extruder = gcode.getExtruderNr(); + start_extruder = gcode.getExtruderNr(); } + last_extruder = start_extruder; + + extruder_order_per_layer.init(true, storage.print_layer_count); - size_t extruder_count = Application::getInstance().current_slice_->scene.extruders.size(); const std::vector extruders_used = storage.getExtrudersUsed(); - const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings; - PrimeTowerMethod prime_tower_mode = mesh_group_settings.get("prime_tower_mode"); for (LayerIndex layer_nr = -Raft::getTotalExtraLayers(); layer_nr < static_cast(storage.print_layer_count); layer_nr++) { - std::vector>& extruder_order_per_layer_here = (layer_nr < 0) ? extruder_order_per_layer_negative_layers : extruder_order_per_layer; std::vector extruder_order = getUsedExtrudersOnLayer(storage, last_extruder, layer_nr, extruders_used); - extruder_order_per_layer_here.push_back(extruder_order); + extruder_order_per_layer.push_back(extruder_order); if (! extruder_order.empty()) { last_extruder = extruder_order.back().extruder_nr; } } + + if (storage.prime_tower_) + { + storage.prime_tower_->processExtrudersUse(extruder_order_per_layer, start_extruder); + } } void FffGcodeWriter::calculatePrimeLayerPerExtruder(const SliceDataStorage& storage) @@ -1585,8 +1588,6 @@ std::vector FffGcodeWriter::getUsedExtrudersOnLayer( assert(static_cast(extruder_count) > 0); std::vector ret; std::vector extruder_is_used_on_this_layer = storage.getExtrudersUsed(layer_nr); - const auto method = mesh_group_settings.get("prime_tower_mode"); - const auto prime_tower_enable = mesh_group_settings.get("prime_tower_enable"); const LayerIndex raft_base_layer_nr = -Raft::getTotalExtraLayers(); Raft::LayerType layer_type = Raft::getLayerType(layer_nr); @@ -1641,28 +1642,9 @@ std::vector FffGcodeWriter::getUsedExtrudersOnLayer( { ExtruderPrime prime = ExtruderPrime::None; - if (prime_tower_enable) + if (storage.prime_tower_) { - switch (method) - { - case PrimeTowerMethod::NORMAL: - if (extruder_is_used_on_this_layer[extruder_nr] && extruder_nr != last_extruder) - { - prime = ExtruderPrime::Prime; - } - else if (layer_nr < storage.max_print_height_second_to_last_extruder) - { - prime = ExtruderPrime::Sparse; - } - break; - - case PrimeTowerMethod::INTERLEAVED: - if (extruder_is_used_on_this_layer[extruder_nr] && extruder_nr != last_extruder) - { - prime = ExtruderPrime::Prime; - } - break; - } + prime = storage.prime_tower_->getExtruderPrime(extruder_is_used_on_this_layer, extruder_nr, last_extruder, storage, layer_nr); } if (extruder_is_used_on_this_layer[extruder_nr] || prime != ExtruderPrime::None) @@ -1672,11 +1654,6 @@ std::vector FffGcodeWriter::getUsedExtrudersOnLayer( } } - if (method == PrimeTowerMethod::INTERLEAVED && ret.size() == 1 && ret.front().prime == ExtruderPrime::None && layer_nr <= storage.max_print_height_second_to_last_extruder) - { - ret.front().prime = ExtruderPrime::Sparse; - } - assert(ret.size() <= (size_t)extruder_count && "Not more extruders may be planned in a layer than there are extruders!"); return ret; } @@ -2485,51 +2462,24 @@ bool FffGcodeWriter::partitionInfillBySkinAbove( size_t FffGcodeWriter::findUsedExtruderIndex(const SliceDataStorage& storage, const LayerIndex& layer_nr, bool last) const { - const std::vector extruder_use = getExtruderUse(layer_nr); + const std::vector extruder_use = extruder_order_per_layer.get(layer_nr); if (! extruder_use.empty()) { return last ? extruder_use.back().extruder_nr : extruder_use.front().extruder_nr; } - else if (layer_nr <= -extruder_order_per_layer_negative_layers.size()) + else if (layer_nr <= -Raft::getTotalExtraLayers()) { // Asking for extruder use below first layer, give first extruder return getStartExtruder(storage); } else { - // Asking for extruder on an empty layer, get the one from layer below + // Asking for extruder on an empty layer, get the last one from layer below return findUsedExtruderIndex(storage, layer_nr - 1, true); } } -std::vector FffGcodeWriter::getExtruderUse(const LayerIndex& layer_nr) const -{ - int layer_index; - const std::vector>* extruder_order; - - if (layer_nr >= 0) - { - layer_index = layer_nr; - extruder_order = &extruder_order_per_layer; - } - else - { - layer_index = extruder_order_per_layer_negative_layers.size() + layer_nr; - extruder_order = &extruder_order_per_layer_negative_layers; - } - - if (layer_index >= 0 && layer_index < extruder_order->size()) - { - return (*extruder_order)[layer_index]; - } - else - { - // No extruder use registered for this layer, which may happen in some edge-cases - return {}; - } -} - void FffGcodeWriter::processSpiralizedWall( const SliceDataStorage& storage, LayerPlan& gcode_layer, @@ -3480,15 +3430,16 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer const GCodePathConfig& config = configs[0]; constexpr bool retract_before_outer_wall = false; constexpr coord_t wipe_dist = 0; + const LayerIndex layer_nr = gcode_layer.getLayerNr(); ZSeamConfig z_seam_config = ZSeamConfig(EZSeamType::SHORTEST, gcode_layer.getLastPlannedPositionOrStartingPosition(), EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE, false); Shape disallowed_area_for_seams{}; - if (infill_extruder.settings_.get("support_z_seam_away_from_model") && (gcode_layer.getLayerNr() >= 0)) + if (infill_extruder.settings_.get("support_z_seam_away_from_model") && (layer_nr >= 0)) { for (std::shared_ptr mesh_ptr : storage.meshes) { auto& mesh = *mesh_ptr; - for (auto& part : mesh.layers[gcode_layer.getLayerNr()].parts) + for (auto& part : mesh.layers[layer_nr].parts) { disallowed_area_for_seams.push_back(part.print_outline); } @@ -4005,14 +3956,14 @@ void FffGcodeWriter::setExtruder_addPrime(const SliceDataStorage& storage, Layer void FffGcodeWriter::addPrimeTower(const SliceDataStorage& storage, LayerPlan& gcode_layer, const size_t prev_extruder) const { - if (! storage.primeTower.enabled_) + if (! storage.prime_tower_) { return; } - LayerIndex layer_nr = gcode_layer.getLayerNr(); - const std::vector extruder_order = getExtruderUse(layer_nr); - storage.primeTower.addToGcode(storage, gcode_layer, extruder_order, prev_extruder, gcode_layer.getExtruder()); + const LayerIndex layer_nr = gcode_layer.getLayerNr(); + const std::vector extruder_order = extruder_order_per_layer.get(layer_nr); + storage.prime_tower_->addToGcode(storage, gcode_layer, extruder_order, prev_extruder, gcode_layer.getExtruder()); } void FffGcodeWriter::finalize() diff --git a/src/FffPolygonGenerator.cpp b/src/FffPolygonGenerator.cpp index e7baf8e0a2..10a1336995 100644 --- a/src/FffPolygonGenerator.cpp +++ b/src/FffPolygonGenerator.cpp @@ -49,6 +49,7 @@ #include "utils/ThreadPool.h" #include "utils/gettime.h" #include "utils/math.h" +#include "PrimeTower/PrimeTower.h" #include "geometry/OpenPolyline.h" #include "utils/Simplify.h" // clang-format on @@ -405,8 +406,6 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper& Progress::messageProgressStage(Progress::Stage::SUPPORT, &time_keeper); - storage.primeTower.initializeExtruders(storage.getExtrudersUsed()); - AreaSupport::generateOverhangAreas(storage); AreaSupport::generateSupportAreas(storage); TreeSupport tree_support_generator(storage); @@ -415,8 +414,7 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper& computePrintHeightStatistics(storage); // handle helpers - storage.primeTower.generatePaths(storage); - storage.primeTower.subtractFromSupport(storage); + storage.initializePrimeTower(); spdlog::debug("Processing ooze shield"); processOozeShield(storage); @@ -426,7 +424,7 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper& // This catches a special case in which the models are in the air, and then // the adhesion mustn't be calculated. - if (! isEmptyLayer(storage, 0) || storage.primeTower.enabled_) + if (! isEmptyLayer(storage, 0) || storage.prime_tower_) { spdlog::debug("Processing platform adhesion"); processPlatformAdhesion(storage); @@ -983,7 +981,7 @@ void FffPolygonGenerator::processOozeShield(SliceDataStorage& storage) { storage.ooze_shield[layer_nr].removeSmallAreas(largest_printed_area); } - if (mesh_group_settings.get("prime_tower_enable")) + if (storage.prime_tower_) { coord_t max_line_width = 0; { // compute max_line_width @@ -998,7 +996,7 @@ void FffPolygonGenerator::processOozeShield(SliceDataStorage& storage) } for (LayerIndex layer_nr = 0; layer_nr <= storage.max_print_height_second_to_last_extruder; layer_nr++) { - storage.ooze_shield[layer_nr] = storage.ooze_shield[layer_nr].difference(storage.primeTower.getOuterPoly(layer_nr).offset(max_line_width / 2)); + storage.ooze_shield[layer_nr] = storage.ooze_shield[layer_nr].difference(storage.prime_tower_->getOccupiedOutline(layer_nr).offset(max_line_width / 2)); } } } @@ -1035,7 +1033,7 @@ void FffPolygonGenerator::processDraftShield(SliceDataStorage& storage) maximum_deviation = std::min(maximum_deviation, extruder.settings_.get("meshfix_maximum_deviation")); } storage.draft_protection_shield = Simplify(maximum_resolution, maximum_deviation, 0).polygon(storage.draft_protection_shield); - if (mesh_group_settings.get("prime_tower_enable")) + if (storage.prime_tower_) { coord_t max_line_width = 0; { // compute max_line_width @@ -1048,7 +1046,7 @@ void FffPolygonGenerator::processDraftShield(SliceDataStorage& storage) max_line_width = std::max(max_line_width, extruders[extruder_nr].settings_.get("skirt_brim_line_width")); } } - storage.draft_protection_shield = storage.draft_protection_shield.difference(storage.primeTower.getGroundPoly().offset(max_line_width / 2)); + storage.draft_protection_shield = storage.draft_protection_shield.difference(storage.prime_tower_->getOccupiedGroundOutline().offset(max_line_width / 2)); } } diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 365d7c2ef1..1b1e0f6b1c 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -100,8 +100,6 @@ LayerPlan::LayerPlan( , layer_type_(Raft::getLayerType(layer_nr)) , layer_thickness_(layer_thickness) , has_prime_tower_planned_per_extruder_(Application::getInstance().current_slice_->scene.extruders.size(), false) - , has_prime_tower_base_planned_(false) - , has_prime_tower_inset_planned_(false) , current_mesh_(nullptr) , last_extruder_previous_layer_(start_extruder) , last_planned_extruder_(&Application::getInstance().current_slice_->scene.extruders[start_extruder]) @@ -331,36 +329,16 @@ void LayerPlan::moveInsideCombBoundary(const coord_t distance, const std::option } } -bool LayerPlan::getPrimeTowerIsPlanned(unsigned int extruder_nr) const +bool LayerPlan::getPrimeTowerIsPlanned(size_t extruder_nr) const { return has_prime_tower_planned_per_extruder_[extruder_nr]; } -void LayerPlan::setPrimeTowerIsPlanned(unsigned int extruder_nr) +void LayerPlan::setPrimeTowerIsPlanned(size_t extruder_nr) { has_prime_tower_planned_per_extruder_[extruder_nr] = true; } -bool LayerPlan::getPrimeTowerBaseIsPlanned() const -{ - return has_prime_tower_base_planned_; -} - -void LayerPlan::setPrimeTowerBaseIsPlanned() -{ - has_prime_tower_base_planned_ = true; -} - -bool LayerPlan::getPrimeTowerInsetIsPlanned() const -{ - return has_prime_tower_inset_planned_; -} - -void LayerPlan::setPrimeTowerInsetIsPlanned() -{ - has_prime_tower_inset_planned_ = true; -} - std::optional> LayerPlan::getFirstTravelDestinationState() const { std::optional> ret; diff --git a/src/PrimeTower/PrimeTower.cpp b/src/PrimeTower/PrimeTower.cpp new file mode 100644 index 0000000000..accfeaf374 --- /dev/null +++ b/src/PrimeTower/PrimeTower.cpp @@ -0,0 +1,376 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "PrimeTower/PrimeTower.h" + +#include +#include +#include + +#include + +#include "Application.h" //To get settings. +#include "ExtruderTrain.h" +#include "LayerPlan.h" +#include "PrimeTower/PrimeTowerInterleaved.h" +#include "PrimeTower/PrimeTowerNormal.h" +#include "Scene.h" +#include "Slice.h" +#include "gcodeExport.h" +#include "infill.h" +#include "raft.h" +#include "sliceDataStorage.h" + + +namespace cura +{ + +PrimeTower::PrimeTower() + : wipe_from_middle_(false) +{ + const Scene& scene = Application::getInstance().current_slice_->scene; + const Settings& mesh_group_settings = scene.current_mesh_group->settings; + const coord_t tower_radius = mesh_group_settings.get("prime_tower_size") / 2; + 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 layer_height = mesh_group_settings.get("layer_height"); + 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 coord_t base_height = scene.settings.get("prime_tower_base_height"); + const double base_curve_magnitude = mesh_group_settings.get("prime_tower_base_curve_magnitude"); + + middle_ = Point2LL(x - tower_radius, y + tower_radius); + outer_poly_ = { PolygonUtils::makeDisc(middle_, tower_radius, circle_definition_), tower_radius }; + post_wipe_point_ = Point2LL(x - tower_radius, y + tower_radius); + + // Generate the base outline + if (base_enabled && base_extra_radius > 0 && base_height > 0) + { + base_occupied_outline_.init(true); + + for (coord_t z = 0; z < base_height; z += layer_height) + { + const double brim_radius_factor = std::pow((1.0 - static_cast(z) / static_cast(base_height)), base_curve_magnitude); + const coord_t extra_radius = std::llrint(static_cast(base_extra_radius) * brim_radius_factor); + const coord_t total_radius = tower_radius + extra_radius; + base_occupied_outline_.emplace_back(PolygonUtils::makeDisc(middle_, total_radius, circle_definition_), total_radius); + } + } +} + +void PrimeTower::generateBase() +{ + const Scene& scene = Application::getInstance().current_slice_->scene; + const Settings& mesh_group_settings = scene.current_mesh_group->settings; + 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 coord_t base_height = scene.settings.get("prime_tower_base_height"); + + if (base_enabled && base_extra_radius > 0 && base_height > 0) + { + base_extrusion_outline_.init(true); + + // Generate the base outside extra annuli for the first extruder of each layer + auto iterator_extrusion_paths = toolpaths_.begin(); + auto iterator_base_outline = base_occupied_outline_.begin(); + for (; iterator_extrusion_paths != toolpaths_.end() && iterator_base_outline != base_occupied_outline_.end(); ++iterator_extrusion_paths, ++iterator_base_outline) + { + std::vector& toolpaths_at_this_layer = iterator_extrusion_paths->second; + if (! toolpaths_at_this_layer.empty()) + { + const OccupiedOutline& base_ouline_at_this_layer = *iterator_base_outline; + ExtruderToolPaths& first_extruder_toolpaths = toolpaths_at_this_layer.front(); + const size_t extruder_nr = first_extruder_toolpaths.extruder_nr; + const coord_t line_width = scene.extruders[extruder_nr].settings_.get("prime_tower_line_width"); + + std::tuple outset + = PolygonUtils::generateCirculatOutset(middle_, first_extruder_toolpaths.outer_radius, base_ouline_at_this_layer.outer_radius, line_width, circle_definition_); + first_extruder_toolpaths.toolpaths.push_back(std::get<0>(outset)); + + base_extrusion_outline_.push_back(PolygonUtils::makeDisc(middle_, std::get<1>(outset), circle_definition_)); + } + } + } +} + +void PrimeTower::generateFirtLayerInset() +{ + // Generate the base inside extra disc for the last extruder of the first layer + if (! toolpaths_.empty()) + { + std::vector& toolpaths_first_layer = toolpaths_.begin()->second; + if (! toolpaths_first_layer.empty()) + { + ExtruderToolPaths& last_extruder_toolpaths = toolpaths_first_layer.back(); + const Scene& scene = Application::getInstance().current_slice_->scene; + const size_t extruder_nr = last_extruder_toolpaths.extruder_nr; + const coord_t line_width = scene.extruders[extruder_nr].settings_.get("prime_tower_line_width"); + ClosedLinesSet pattern = PolygonUtils::generateCircularInset(middle_, last_extruder_toolpaths.inner_radius, line_width, circle_definition_); + last_extruder_toolpaths.toolpaths.push_back(pattern); + } + } +} + +std::tuple PrimeTower::generatePrimeToolpaths(const size_t extruder_nr, const coord_t outer_radius) +{ + 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 coord_t line_width = scene.extruders[extruder_nr].settings_.get("prime_tower_line_width"); + const double required_volume = scene.extruders[extruder_nr].settings_.get("prime_tower_min_volume") * 1000000000; + const Ratio flow = scene.extruders[extruder_nr].settings_.get("prime_tower_flow"); + const coord_t semi_line_width = line_width / 2; + + double current_volume = 0; + coord_t current_outer_radius = outer_radius - semi_line_width; + ClosedLinesSet toolpaths; + while (current_volume < required_volume && current_outer_radius >= semi_line_width) + { + ClosedPolyline circle = PolygonUtils::makeCircle(middle_, current_outer_radius, circle_definition_); + toolpaths.push_back(circle); + current_volume += static_cast(circle.length() * line_width * layer_height) * flow; + current_outer_radius -= line_width; + } + + return { toolpaths, current_outer_radius + semi_line_width }; +} + +ClosedLinesSet PrimeTower::generateSupportToolpaths(const size_t extruder_nr, const coord_t outer_radius, const coord_t inner_radius) +{ + const Scene& scene = Application::getInstance().current_slice_->scene; + const double max_bridging_distance = static_cast(scene.extruders[extruder_nr].settings_.get("prime_tower_max_bridging_distance")); + const coord_t line_width = scene.extruders[extruder_nr].settings_.get("prime_tower_line_width"); + const coord_t radius_delta = outer_radius - inner_radius; + const coord_t semi_line_width = line_width / 2; + + ClosedLinesSet toolpaths; + + // Split annuli according to max bridging distance + const coord_t nb_annuli = static_cast(std::ceil(static_cast(radius_delta) / max_bridging_distance)); + if (nb_annuli > 0) + { + const coord_t actual_radius_step = radius_delta / nb_annuli; + + for (coord_t i = 0; i < nb_annuli; ++i) + { + const coord_t annulus_inner_radius = (inner_radius + i * actual_radius_step) + semi_line_width; + const coord_t annulus_outer_radius = (inner_radius + (i + 1) * actual_radius_step) - semi_line_width; + + const size_t semi_nb_spokes = static_cast(std::ceil((std::numbers::pi * static_cast(annulus_outer_radius)) / max_bridging_distance)); + + toolpaths.push_back(PolygonUtils::makeWheel(middle_, annulus_inner_radius, annulus_outer_radius, semi_nb_spokes, arc_definition_)); + } + } + + return toolpaths; +} + +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 (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; + } + + const ClosedLinesSet* toolpaths = nullptr; + auto iterator_layer = toolpaths_.find(layer_nr); + if (iterator_layer != toolpaths_.end()) + { + const std::vector& toolpaths_at_this_layer = iterator_layer->second; + auto iterator_extruder = std::find_if( + toolpaths_at_this_layer.begin(), + toolpaths_at_this_layer.end(), + [new_extruder_nr](const ExtruderToolPaths& extruder_toolpaths) + { + return extruder_toolpaths.extruder_nr == new_extruder_nr; + }); + if (iterator_extruder != iterator_layer->second.end()) + { + toolpaths = &(iterator_extruder->toolpaths); + } + } + + if (toolpaths && ! toolpaths->empty()) + { + gotoStartLocation(gcode_layer, new_extruder_nr); + + const GCodePathConfig& config = gcode_layer.configs_storage_.prime_tower_config_per_extruder[new_extruder_nr]; + gcode_layer.addLinesByOptimizer(*toolpaths, config, SpaceFillType::PolyLines); + } + + gcode_layer.setPrimeTowerIsPlanned(new_extruder_nr); + + // 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); + } +} + +const Polygon& PrimeTower::getOccupiedOutline(const LayerIndex& layer_nr) const +{ + auto iterator = base_occupied_outline_.iterator_at(layer_nr); + if (iterator != base_occupied_outline_.end()) + { + return iterator->outline; + } + else + { + return outer_poly_.outline; + } +} + +const Polygon& PrimeTower::getOccupiedGroundOutline() const +{ + if (! base_extrusion_outline_.empty()) + { + return base_extrusion_outline_.front(); + } + else + { + return outer_poly_.outline; + } +} + +const Polygon& PrimeTower::getExtrusionOutline(const LayerIndex& layer_nr) const +{ + auto iterator = base_extrusion_outline_.iterator_at(layer_nr); + if (iterator != base_extrusion_outline_.end()) + { + return *iterator; + } + else + { + return outer_poly_.outline; + } +} + +void PrimeTower::subtractFromSupport(SliceDataStorage& storage) +{ + for (size_t layer = 0; static_cast(layer) <= storage.max_print_height_second_to_last_extruder + 1 && layer < storage.support.supportLayers.size(); layer++) + { + const Polygon& outside_polygon = getOccupiedOutline(layer); + 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(Shape(outside_polygon), outside_polygon_boundary_box); + } +} + +void PrimeTower::processExtrudersUse(LayerVector>& extruders_use, const size_t start_extruder) +{ + polishExtrudersUses(extruders_use, start_extruder); + toolpaths_ = generateToolPaths(extruders_use); + generateBase(); + generateFirtLayerInset(); +} + +PrimeTower* PrimeTower::createPrimeTower(SliceDataStorage& storage) +{ + PrimeTower* prime_tower = nullptr; + const Scene& scene = Application::getInstance().current_slice_->scene; + const size_t raft_total_extra_layers = Raft::getTotalExtraLayers(); + + if (scene.extruders.size() > 1 && 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 + && storage.max_print_height_second_to_last_extruder >= -static_cast(raft_total_extra_layers)) + { + const Settings& mesh_group_settings = scene.current_mesh_group->settings; + const PrimeTowerMode method = mesh_group_settings.get("prime_tower_mode"); + + switch (method) + { + case PrimeTowerMode::NORMAL: + prime_tower = new PrimeTowerNormal(); + break; + case PrimeTowerMode::INTERLEAVED: + prime_tower = new PrimeTowerInterleaved(); + break; + } + } + + if (prime_tower) + { + prime_tower->subtractFromSupport(storage); + } + + return prime_tower; +} + +bool PrimeTower::extruderRequiresPrime(const std::vector& extruder_is_used_on_this_layer, size_t extruder_nr, size_t last_extruder) +{ + return extruder_is_used_on_this_layer[extruder_nr] && extruder_nr != last_extruder; +} + +void PrimeTower::gotoStartLocation(LayerPlan& gcode_layer, const size_t extruder_nr) const +{ + LayerIndex layer_nr = gcode_layer.getLayerNr(); + if (layer_nr != -Raft::getTotalExtraLayers()) + { + coord_t wipe_radius; + auto iterator = base_occupied_outline_.iterator_at(gcode_layer.getLayerNr()); + if (iterator != base_occupied_outline_.end()) + { + wipe_radius = iterator->outer_radius; + } + else + { + wipe_radius = outer_poly_.outer_radius; + } + + const ExtruderTrain& train = Application::getInstance().current_slice_->scene.extruders[extruder_nr]; + wipe_radius += train.settings_.get("machine_nozzle_size") * 2; + + // Layer number may be negative, make it positive (or null) before using modulo operator + while (layer_nr < 0) + { + layer_nr += number_of_prime_tower_start_locations_; + } + + size_t current_start_location_idx = ((extruder_nr + 1) * static_cast(layer_nr)) % number_of_prime_tower_start_locations_; + const AngleRadians angle = start_locations_step_ * current_start_location_idx; + const Point2LL prime_start = PolygonUtils::makeCirclePoint(middle_, wipe_radius, angle); + + gcode_layer.addTravel(prime_start); + } +} + +} // namespace cura diff --git a/src/PrimeTower/PrimeTowerInterleaved.cpp b/src/PrimeTower/PrimeTowerInterleaved.cpp new file mode 100644 index 0000000000..fabe0226ce --- /dev/null +++ b/src/PrimeTower/PrimeTowerInterleaved.cpp @@ -0,0 +1,137 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "PrimeTower/PrimeTowerInterleaved.h" + +#include "Application.h" +#include "LayerPlan.h" +#include "Scene.h" +#include "Slice.h" +#include "sliceDataStorage.h" + +namespace cura +{ + +PrimeTowerInterleaved::PrimeTowerInterleaved() + : PrimeTower() +{ +} + +ExtruderPrime PrimeTowerInterleaved::getExtruderPrime( + const std::vector& extruder_is_used_on_this_layer, + size_t extruder_nr, + size_t last_extruder, + const SliceDataStorage& /*storage*/, + const LayerIndex& /*layer_nr*/) const +{ + // For now, just calculate prime or not. Support extrusion requires the whole extruders list to be calculted, and + // will be processed later. + if (extruderRequiresPrime(extruder_is_used_on_this_layer, extruder_nr, last_extruder)) + { + return ExtruderPrime::Prime; + } + else + { + return ExtruderPrime::None; + } +} + +std::map> PrimeTowerInterleaved::generateToolPaths(const LayerVector>& extruders_use) +{ + const Scene& scene = Application::getInstance().current_slice_->scene; + const Settings& mesh_group_settings = scene.current_mesh_group->settings; + const coord_t tower_radius = mesh_group_settings.get("prime_tower_size") / 2; + const coord_t min_shell_thickness = mesh_group_settings.get("prime_tower_min_shell_thickness"); + coord_t shell_thickness = 0; + std::map> toolpaths; + + // Loop from top bo bottom, so that the required support increases with what is actually required + for (auto iterator = extruders_use.rbegin(); iterator != extruders_use.rend(); ++iterator) + { + const LayerIndex layer_nr = extruders_use.getLayer(iterator); + const std::vector& extruders_use_at_layer = extruders_use[layer_nr]; + std::vector toolpaths_at_layer; + size_t last_extruder_support = 0; + + // Generate actual priming patterns + coord_t prime_next_outer_radius = tower_radius; + for (const ExtruderUse& extruder_use : extruders_use_at_layer) + { + if (extruder_use.prime == ExtruderPrime::Prime) + { + ExtruderToolPaths extruder_toolpaths; + extruder_toolpaths.outer_radius = prime_next_outer_radius; + extruder_toolpaths.extruder_nr = extruder_use.extruder_nr; + + std::tie(extruder_toolpaths.toolpaths, extruder_toolpaths.inner_radius) = generatePrimeToolpaths(extruder_use.extruder_nr, prime_next_outer_radius); + toolpaths_at_layer.push_back(extruder_toolpaths); + + prime_next_outer_radius = extruder_toolpaths.inner_radius; + } + else if (extruder_use.prime == ExtruderPrime::Support) + { + last_extruder_support = extruder_use.extruder_nr; + } + } + + // Increase shell thickness if required + const coord_t layer_prime_thickness = tower_radius - prime_next_outer_radius; + shell_thickness = std::max(shell_thickness, layer_prime_thickness); + + if (shell_thickness > 0) + { + shell_thickness = std::max(shell_thickness, min_shell_thickness); + + // Generate extra inner support if required + const coord_t inner_support_radius = tower_radius - shell_thickness; + if (inner_support_radius < prime_next_outer_radius) + { + if (toolpaths_at_layer.empty()) + { + toolpaths_at_layer.emplace_back(last_extruder_support, ClosedLinesSet(), prime_next_outer_radius, inner_support_radius); + } + + ExtruderToolPaths& last_extruder_toolpaths = toolpaths_at_layer.back(); + ClosedLinesSet support_toolpaths = generateSupportToolpaths(last_extruder_toolpaths.extruder_nr, prime_next_outer_radius, inner_support_radius); + last_extruder_toolpaths.toolpaths.push_back(support_toolpaths); + last_extruder_toolpaths.outer_radius = prime_next_outer_radius; + last_extruder_toolpaths.inner_radius = inner_support_radius; + } + + toolpaths[layer_nr] = toolpaths_at_layer; + } + } + + return toolpaths; +} + +void PrimeTowerInterleaved::polishExtrudersUses(LayerVector>& extruders_use, const size_t start_extruder) +{ + size_t last_used_extruder = start_extruder; + + // Loop through the extruders uses from bottom to top to find the last used extruder at each layer, and make sure we always have some support to print + for (auto iterator = extruders_use.begin(); iterator != extruders_use.end(); ++iterator) + { + std::vector& extruders_use_at_layer = *iterator; + + // Make sure we always have something to print + if (extruders_use_at_layer.empty()) + { + extruders_use_at_layer.emplace_back(last_used_extruder, ExtruderPrime::Support); + } + else if (std::all_of( + extruders_use_at_layer.begin(), + extruders_use_at_layer.end(), + [](const ExtruderUse& extruder_use) + { + return extruder_use.prime == ExtruderPrime::None; + })) + { + extruders_use_at_layer.back().prime = ExtruderPrime::Support; + } + + last_used_extruder = extruders_use_at_layer.back().extruder_nr; + } +} + +} // namespace cura diff --git a/src/PrimeTower/PrimeTowerNormal.cpp b/src/PrimeTower/PrimeTowerNormal.cpp new file mode 100644 index 0000000000..eb99669423 --- /dev/null +++ b/src/PrimeTower/PrimeTowerNormal.cpp @@ -0,0 +1,127 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "PrimeTower/PrimeTowerNormal.h" + +#include "Application.h" +#include "LayerPlan.h" +#include "Scene.h" +#include "Slice.h" +#include "sliceDataStorage.h" + +namespace cura +{ + +PrimeTowerNormal::PrimeTowerNormal() + : PrimeTower() +{ +} + +ExtruderPrime PrimeTowerNormal::getExtruderPrime( + const std::vector& extruder_is_used_on_this_layer, + size_t extruder_nr, + size_t last_extruder, + const SliceDataStorage& storage, + const LayerIndex& layer_nr) const +{ + if (extruderRequiresPrime(extruder_is_used_on_this_layer, extruder_nr, last_extruder)) + { + return ExtruderPrime::Prime; + } + else if (layer_nr <= storage.max_print_height_second_to_last_extruder) + { + return ExtruderPrime::Support; + } + else + { + return ExtruderPrime::None; + } +} + +std::map> PrimeTowerNormal::generateToolPaths(const LayerVector>& extruders_use) +{ + const Scene& scene = Application::getInstance().current_slice_->scene; + const Settings& mesh_group_settings = scene.current_mesh_group->settings; + const coord_t tower_radius = mesh_group_settings.get("prime_tower_size") / 2; + std::map> toolpaths; + + // First make a basic list of used extruders numbers + std::vector extruder_order; + extruder_order.reserve(scene.extruders.size()); + for (const ExtruderTrain& extruder : scene.extruders) + { + extruder_order.push_back(extruder.extruder_nr_); + } + + // Then sort from high adhesion to low adhesion. This will give us the outside to inside extruder processing order. + std::sort( + extruder_order.begin(), + extruder_order.end(), + [&scene](const size_t extruder_nr_a, const size_t extruder_nr_b) + { + 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; + }); + + // For each extruder, generate the prime and support patterns, which will always be the same across layers + coord_t current_radius = tower_radius; + std::map extruders_prime_toolpaths; + std::map extruders_support_toolpaths; + for (size_t extruder_nr : extruder_order) + { + ExtruderToolPaths extruder_prime_toolpaths; + extruder_prime_toolpaths.extruder_nr = extruder_nr; + extruder_prime_toolpaths.outer_radius = current_radius; + std::tie(extruder_prime_toolpaths.toolpaths, extruder_prime_toolpaths.inner_radius) = generatePrimeToolpaths(extruder_nr, current_radius); + extruders_prime_toolpaths[extruder_nr] = extruder_prime_toolpaths; + + ExtruderToolPaths extruder_support_toolpaths = extruder_prime_toolpaths; + extruder_support_toolpaths.toolpaths = generateSupportToolpaths(extruder_nr, current_radius, extruder_prime_toolpaths.inner_radius); + extruders_support_toolpaths[extruder_nr] = extruder_support_toolpaths; + + current_radius = extruder_prime_toolpaths.inner_radius; + } + + // Now fill the extruders toolpaths according to their use + for (auto iterator = extruders_use.begin(); iterator != extruders_use.end(); ++iterator) + { + const LayerIndex layer_nr = extruders_use.getLayer(iterator); + std::vector extruders_use_at_layer = *iterator; + + // Sort to fit the global order, in order to insert the toolpaths in outside to inside order + std::sort( + extruders_use_at_layer.begin(), + extruders_use_at_layer.end(), + [extruder_order](const ExtruderUse& extruder_use1, const ExtruderUse& extruder_use2) + { + return std::find(extruder_order.begin(), extruder_order.end(), extruder_use1.extruder_nr) + < std::find(extruder_order.begin(), extruder_order.end(), extruder_use2.extruder_nr); + }); + + // Now put the proper toolpaths for each extruder use + std::vector toolpaths_at_layer; + for (const ExtruderUse& extruder_use : extruders_use_at_layer) + { + switch (extruder_use.prime) + { + case ExtruderPrime::None: + break; + + case ExtruderPrime::Prime: + toolpaths_at_layer.push_back(extruders_prime_toolpaths[extruder_use.extruder_nr]); + break; + + case ExtruderPrime::Support: + toolpaths_at_layer.push_back(extruders_support_toolpaths[extruder_use.extruder_nr]); + break; + } + } + + toolpaths[layer_nr] = toolpaths_at_layer; + } + + return toolpaths; +} + +} // namespace cura diff --git a/src/SkeletalTrapezoidation.cpp b/src/SkeletalTrapezoidation.cpp index 2bb4f2ccca..3b248bd9ff 100644 --- a/src/SkeletalTrapezoidation.cpp +++ b/src/SkeletalTrapezoidation.cpp @@ -2200,7 +2200,7 @@ void SkeletalTrapezoidation::generateLocalMaximaSingleBeads() // So our circle needs to be such that r=w/8 const coord_t r = width / 8; constexpr coord_t n_segments = 6; - const auto circle = PolygonUtils::makeCircle>(center, r, 2 * std::numbers::pi / n_segments, width, inset_index); + const auto circle = PolygonUtils::makeCircle, true>(center, r, n_segments, width, inset_index); line.junctions_.insert(line.junctions_.end(), circle.begin(), circle.end()); }; diff --git a/src/TreeModelVolumes.cpp b/src/TreeModelVolumes.cpp index a6d12804a6..3dade07b66 100644 --- a/src/TreeModelVolumes.cpp +++ b/src/TreeModelVolumes.cpp @@ -8,6 +8,7 @@ #include #include +#include "PrimeTower/PrimeTower.h" #include "TreeSupport.h" #include "TreeSupportEnums.h" #include "progress/Progress.h" @@ -148,9 +149,9 @@ TreeModelVolumes::TreeModelVolumes( anti_overhang_[layer_idx].push_back(storage.support.supportLayers[layer_idx].anti_overhang); } - if (storage.primeTower.enabled_) + if (storage.prime_tower_) { - anti_overhang_[layer_idx].push_back(storage.primeTower.getGroundPoly()); + anti_overhang_[layer_idx].push_back(storage.prime_tower_->getOccupiedOutline(layer_idx)); } anti_overhang_[layer_idx] = anti_overhang_[layer_idx].unionPolygons(); }); diff --git a/src/geometry/LinesSet.cpp b/src/geometry/LinesSet.cpp index 039931812a..ccfd7259a6 100644 --- a/src/geometry/LinesSet.cpp +++ b/src/geometry/LinesSet.cpp @@ -276,21 +276,28 @@ void LinesSet::removeDegenerateVerts() } } +template +template +void LinesSet::addPath(ClipperLib::Clipper& clipper, const OtherLineLine& line, ClipperLib::PolyType poly_typ) const +{ + // In this context, the "Closed" argument means "Is a surface" so it should be only + // true for actual filled polygons. Closed polylines are to be treated as lines here. + if constexpr (std::is_same::value) + { + clipper.AddPath(line.getPoints(), poly_typ, true); + } + else + { + clipper.AddPath(line.getPoints(), poly_typ, false); + } +} + template void LinesSet::addPaths(ClipperLib::Clipper& clipper, ClipperLib::PolyType poly_typ) const { for (const LineType& line : getLines()) { - // In this context, the "Closed" argument means "Is a surface" so it should be only - // true for actual filled polygons. Closed polylines are to be treated as lines here. - if constexpr (std::is_same::value) - { - clipper.AddPath(line.getPoints(), poly_typ, true); - } - else - { - clipper.AddPath(line.getPoints(), poly_typ, false); - } + addPath(clipper, line, poly_typ); } } @@ -345,5 +352,6 @@ template void LinesSet::addPaths(ClipperLib::ClipperOffset& clipper, Cl template void LinesSet::push_back(const Polygon& line, CheckNonEmptyParam checkNonEmpty); template void LinesSet::push_back(Polygon&& line, CheckNonEmptyParam checkNonEmpty); template void LinesSet::push_back(LinesSet&& lines_set); +template void LinesSet::addPath(ClipperLib::Clipper& clipper, const Polygon& line, ClipperLib::PolyType poly_typ) const; } // namespace cura diff --git a/src/geometry/Shape.cpp b/src/geometry/Shape.cpp index 8156bb5fa2..ed5dffbbf0 100644 --- a/src/geometry/Shape.cpp +++ b/src/geometry/Shape.cpp @@ -47,6 +47,11 @@ Shape::Shape(const std::vector& polygons) { } +Shape::Shape(const Polygon& polygon) + : LinesSet(polygon) +{ +} + void Shape::emplace_back(ClipperLib::Paths&& paths, bool explicitely_closed) { reserve(size() + paths.size()); @@ -152,6 +157,24 @@ Shape Shape::difference(const Shape& other) const return Shape(std::move(ret)); } +Shape Shape::difference(const Polygon& other) const +{ + if (empty()) + { + return {}; + } + if (other.empty()) + { + return *this; + } + ClipperLib::Paths ret; + ClipperLib::Clipper clipper(clipper_init); + addPaths(clipper, ClipperLib::ptSubject); + addPath(clipper, other, ClipperLib::ptClip); + clipper.Execute(ClipperLib::ctDifference, ret); + return Shape(std::move(ret)); +} + Shape Shape::unionPolygons(const Shape& other, ClipperLib::PolyFillType fill_type) const { if (empty() && other.empty()) @@ -174,6 +197,28 @@ Shape Shape::unionPolygons(const Shape& other, ClipperLib::PolyFillType fill_typ return Shape{ std::move(ret) }; } +Shape Shape::unionPolygons(const Polygon& polygon, ClipperLib::PolyFillType fill_type) const +{ + if (empty() && polygon.empty()) + { + return {}; + } + if (empty()) + { + return Shape(polygon); + } + if (polygon.empty() && size() <= 1) + { + return *this; + } + ClipperLib::Paths ret; + ClipperLib::Clipper clipper(clipper_init); + addPaths(clipper, ClipperLib::ptSubject); + addPath(clipper, polygon, ClipperLib::ptSubject); + clipper.Execute(ClipperLib::ctUnion, ret, fill_type, fill_type); + return Shape{ std::move(ret) }; +} + Shape Shape::unionPolygons() const { return unionPolygons(Shape()); diff --git a/src/settings/Settings.cpp b/src/settings/Settings.cpp index 136606cde2..8742268c4f 100644 --- a/src/settings/Settings.cpp +++ b/src/settings/Settings.cpp @@ -680,15 +680,15 @@ InsetDirection Settings::get(const std::string& key) const } template<> -PrimeTowerMethod Settings::get(const std::string& key) const +PrimeTowerMode Settings::get(const std::string& key) const { const std::string& value = get(key); if (value == "interleaved") { - return PrimeTowerMethod::INTERLEAVED; + return PrimeTowerMode::INTERLEAVED; } - return PrimeTowerMethod::NORMAL; + return PrimeTowerMode::NORMAL; } template<> diff --git a/src/sliceDataStorage.cpp b/src/sliceDataStorage.cpp index d89edfbceb..697721ccd0 100644 --- a/src/sliceDataStorage.cpp +++ b/src/sliceDataStorage.cpp @@ -10,6 +10,7 @@ #include "Application.h" //To get settings. #include "ExtruderTrain.h" #include "FffProcessor.h" //To create a mesh group with if none is provided. +#include "PrimeTower/PrimeTower.h" #include "Slice.h" #include "geometry/OpenPolyline.h" #include "infill/DensityProvider.h" // for destructor @@ -17,6 +18,7 @@ #include "infill/SierpinskiFillProvider.h" #include "infill/SubDivCube.h" // For the destructor #include "raft.h" +#include "utils/ExtrusionLine.h" #include "utils/math.h" //For PI. namespace cura @@ -268,6 +270,11 @@ SliceDataStorage::SliceDataStorage() machine_size.include(machine_max); } +SliceDataStorage::~SliceDataStorage() +{ + delete prime_tower_; +} + Shape SliceDataStorage::getLayerOutlines( const LayerIndex layer_nr, const bool include_support, @@ -364,9 +371,9 @@ Shape SliceDataStorage::getLayerOutlines( total.push_back(support_layer.support_roof); } } - if (include_prime_tower && primeTower.enabled_ && (extruder_nr == -1 || (! primeTower.extruder_order_.empty() && extruder_nr == primeTower.extruder_order_[0]))) + if (include_prime_tower && prime_tower_) { - total.push_back(primeTower.getOuterPoly(layer_nr)); + total.push_back(prime_tower_->getOccupiedOutline(layer_nr)); } return total; } @@ -641,9 +648,8 @@ Shape SliceDataStorage::getMachineBorder(int checking_extruder_nr) const } Point2LL translation(extruder_settings.get("machine_nozzle_offset_x"), extruder_settings.get("machine_nozzle_offset_y")); prime_pos -= translation; - Shape prime_polygons; - prime_polygons.emplace_back(PolygonUtils::makeCircle(prime_pos, prime_clearance, std::numbers::pi / 32)); - disallowed_areas = disallowed_areas.unionPolygons(prime_polygons); + Polygon prime_polygon = PolygonUtils::makeDisc(prime_pos, prime_clearance, 64); + disallowed_areas = disallowed_areas.unionPolygons(prime_polygon); } Shape disallowed_all_extruders; @@ -701,6 +707,10 @@ Shape SliceDataStorage::getMachineBorder(int checking_extruder_nr) const return border; } +void SliceDataStorage::initializePrimeTower() +{ + prime_tower_ = PrimeTower::createPrimeTower(*this); +} void SupportLayer::excludeAreasFromSupportInfillAreas(const Shape& exclude_polygons, const AABB& exclude_polygons_boundary_box) { diff --git a/src/utils/polygonUtils.cpp b/src/utils/polygonUtils.cpp index 3890055558..4b961d2bec 100644 --- a/src/utils/polygonUtils.cpp +++ b/src/utils/polygonUtils.cpp @@ -32,69 +32,6 @@ const std::function PolygonUtils::no_penalty_function = [](Point2 return 0; }; -int64_t PolygonUtils::segmentLength(PolygonsPointIndex start, PolygonsPointIndex end) -{ - assert(start.poly_idx_ == end.poly_idx_); - int64_t segment_length = 0; - Point2LL prev_vert = start.p(); - const Polygon& poly = start.getPolygon(); - for (unsigned int point_idx = 1; point_idx <= poly.size(); point_idx++) - { - unsigned int vert_idx = (start.point_idx_ + point_idx) % poly.size(); - Point2LL vert = poly[vert_idx]; - segment_length += vSize(vert - prev_vert); - - if (vert_idx == end.point_idx_) - { // break at the end of the loop, so that [end] and [start] may be the same - return segment_length; - } - prev_vert = vert; - } - assert(false && "The segment end should have been encountered!"); - return segment_length; -} - -void PolygonUtils::spreadDots(PolygonsPointIndex start, PolygonsPointIndex end, unsigned int n_dots, std::vector& result) -{ - assert(start.poly_idx_ == end.poly_idx_); - int64_t segment_length = segmentLength(start, end); - - const Polygon& poly = start.getPolygon(); - unsigned int n_dots_in_between = n_dots; - if (start == end) - { - result.emplace_back(start.p(), start.point_idx_, &poly); - n_dots_in_between--; // generate one less below, because we already pushed a point to the result - } - - int64_t wipe_point_dist = segment_length / (n_dots_in_between + 1); // distance between two wipe points; keep a distance at both sides of the segment - - int64_t dist_past_vert_to_insert_point = wipe_point_dist; - unsigned int n_points_generated = 0; - PolygonsPointIndex vert = start; - while (true) - { - Point2LL p0 = vert.p(); - Point2LL p1 = vert.next().p(); - Point2LL p0p1 = p1 - p0; - int64_t p0p1_length = vSize(p0p1); - - for (; dist_past_vert_to_insert_point < p0p1_length && n_points_generated < n_dots_in_between; dist_past_vert_to_insert_point += wipe_point_dist) - { - result.emplace_back(p0 + normal(p0p1, dist_past_vert_to_insert_point), vert.point_idx_, &poly); - n_points_generated++; - } - dist_past_vert_to_insert_point -= p0p1_length; - - ++vert; - if (vert == end) - { // break at end of loop to allow for [start] and [end] being the same, meaning the full polygon - break; - } - } - assert(result.size() == n_dots && "we didn't generate as many wipe locations as we asked for."); -} - std::vector PolygonUtils::spreadDotsArea(const Shape& polygons, coord_t grid_size) { return spreadDotsArea(polygons, Point2LL(grid_size, grid_size)); @@ -225,26 +162,6 @@ Point2LL PolygonUtils::getBoundaryPointWithOffset(const Polyline& poly, unsigned return poly[point_idx] + normal(getVertexInwardNormal(poly, point_idx), -offset); } -Point2LL PolygonUtils::moveInsideDiagonally(ClosestPointPolygon point_on_boundary, int64_t inset) -{ - if (! point_on_boundary.isValid()) - { - return no_point; - } - - const Polygon& poly = *point_on_boundary.poly_; - Point2LL p0 = poly[point_on_boundary.point_idx_]; - Point2LL p1 = poly[(point_on_boundary.point_idx_ + 1) % poly.size()]; - if (vSize2(p0 - point_on_boundary.location_) < vSize2(p1 - point_on_boundary.location_)) - { - return point_on_boundary.location_ + normal(getVertexInwardNormal(poly, point_on_boundary.point_idx_), inset); - } - else - { - return point_on_boundary.location_ + normal(getVertexInwardNormal(poly, (point_on_boundary.point_idx_ + 1) % poly.size()), inset); - } -} - unsigned int PolygonUtils::moveOutside(const Shape& polygons, Point2LL& from, int distance, int64_t maxDist2) { return moveInside(polygons, from, -distance, maxDist2); @@ -1386,43 +1303,39 @@ double PolygonUtils::relativeHammingDistance(const Shape& poly_a, const Shape& p return hamming_distance / total_area; } -Polygon PolygonUtils::makeWheel(const Point2LL& mid, const coord_t inner_radius, const coord_t outer_radius, const size_t semi_nb_spokes, const size_t arc_angle_resolution) +Polygon PolygonUtils::makeDisc(const Point2LL& mid, const coord_t radius, const size_t steps) +{ + return makeCircle(mid, radius, steps); +} + +Point2LL PolygonUtils::makeCirclePoint(const Point2LL& mid, const coord_t radius, const AngleRadians& angle) { - Polygon wheel; + return mid + Point2LL(std::llrint(static_cast(radius) * cos(angle)), std::llrint(static_cast(radius) * sin(angle))); +} - std::vector> target_radii; - target_radii.push_back({ inner_radius, outer_radius }); - target_radii.push_back({ outer_radius, inner_radius }); +ClosedPolyline PolygonUtils::makeWheel(const Point2LL& mid, const coord_t inner_radius, const coord_t outer_radius, const size_t semi_nb_spokes, const size_t arc_angle_resolution) +{ + ClosedPolyline wheel; const size_t nb_spokes = semi_nb_spokes * 2; - const float angle_step = TAU / nb_spokes; - const float arc_step = angle_step / arc_angle_resolution; - float angle = 0.0; + const double spoke_angle_step = TAU / static_cast(nb_spokes); + const double arc_angle_step = spoke_angle_step / static_cast(arc_angle_resolution); + for (size_t spoke = 0; spoke < nb_spokes; ++spoke) { - const std::pair& radii = target_radii.at(spoke % 2); - - angle = spoke * angle_step; - float cos_angle = cos(angle); - float sin_angle = sin(angle); - wheel.emplace_back(mid + Point2LL(radii.first * cos_angle, radii.first * sin_angle)); + const double spoke_angle = static_cast(spoke) * spoke_angle_step; + const coord_t radius = spoke % 2 == 0 ? inner_radius : outer_radius; - for (size_t arc_part = 0; arc_part < arc_angle_resolution; ++arc_part) + for (size_t arc_part = 0; arc_part <= arc_angle_resolution; ++arc_part) { - wheel.emplace_back(mid + Point2LL(radii.second * cos_angle, radii.second * sin_angle)); - if (arc_part < arc_angle_resolution - 1) - { - angle += arc_step; - cos_angle = cos(angle); - sin_angle = sin(angle); - } + const double angle = spoke_angle + static_cast(arc_part) * arc_angle_step; + wheel.push_back(makeCirclePoint(mid, radius, angle)); } } return wheel; } - Shape PolygonUtils::connect(const Shape& input) { Shape ret; @@ -1605,29 +1518,32 @@ Shape PolygonUtils::clipPolygonWithAABB(const Shape& src, const AABB& aabb) return out; } -Shape PolygonUtils::generateOutset(const Shape& inner_poly, size_t count, coord_t line_width) +std::tuple + PolygonUtils::generateCirculatOutset(const Point2LL& center, const coord_t inner_radius, const coord_t outer_radius, coord_t line_width, const size_t circle_definition) { - Shape outset; + ClosedLinesSet outset; + const coord_t semi_line_width = line_width / 2; + coord_t radius = inner_radius + semi_line_width; - Shape current_outset; - for (size_t index = 0; index < count; ++index) + while (radius + semi_line_width <= outer_radius) { - current_outset = index == 0 ? inner_poly.offset(line_width / 2) : current_outset.offset(line_width); - outset.push_back(current_outset); + outset.push_back(makeCircle(center, radius, circle_definition)); + radius += line_width; } - return outset; + return { outset, radius - semi_line_width }; } -Shape PolygonUtils::generateInset(const Shape& outer_poly, coord_t line_width, coord_t initial_inset) +ClosedLinesSet PolygonUtils::generateCircularInset(const Point2LL& center, const coord_t outer_radius, const coord_t line_width, const size_t circle_definition) { - Shape inset; + ClosedLinesSet inset; + const coord_t semi_line_width = line_width / 2; + coord_t radius = outer_radius - semi_line_width; - Shape current_inset = outer_poly.offset(-(initial_inset + line_width / 2)); - while (! current_inset.empty()) + while (radius - semi_line_width >= line_width) { - inset.push_back(current_inset); - current_inset = current_inset.offset(-line_width); + inset.push_back(makeCircle(center, radius, circle_definition)); + radius -= line_width; } return inset; diff --git a/tests/utils/PolygonUtilsTest.cpp b/tests/utils/PolygonUtilsTest.cpp index cc1907390e..dd13f1e5ad 100644 --- a/tests/utils/PolygonUtilsTest.cpp +++ b/tests/utils/PolygonUtilsTest.cpp @@ -339,45 +339,6 @@ class PolygonUtilsTest : public testing::Test } }; -TEST_F(PolygonUtilsTest, spreadDotsSegment) -{ - std::vector supposed; - supposed.emplace_back(Point2LL(50, 0), 0, &test_squares[0], 0); - supposed.emplace_back(Point2LL(100, 0), 1, &test_squares[0], 0); - supposed.emplace_back(Point2LL(100, 50), 1, &test_squares[0], 0); - - std::vector result; - PolygonUtils::spreadDots(PolygonsPointIndex(&test_squares, 0, 0), PolygonsPointIndex(&test_squares, 0, 2), 3, result); - - ASSERT_EQ(result.size(), supposed.size()); - for (size_t point_idx = 0; point_idx < result.size(); point_idx++) - { - EXPECT_EQ(result[point_idx].p(), supposed[point_idx].p()); - } -} - -TEST_F(PolygonUtilsTest, spreadDotsFull) -{ - std::vector supposed; - supposed.emplace_back(Point2LL(0, 0), 0, &test_squares[0], 0); - supposed.emplace_back(Point2LL(50, 0), 0, &test_squares[0], 0); - supposed.emplace_back(Point2LL(100, 0), 1, &test_squares[0], 0); - supposed.emplace_back(Point2LL(100, 50), 1, &test_squares[0], 0); - supposed.emplace_back(Point2LL(100, 100), 2, &test_squares[0], 0); - supposed.emplace_back(Point2LL(50, 100), 2, &test_squares[0], 0); - supposed.emplace_back(Point2LL(0, 100), 3, &test_squares[0], 0); - supposed.emplace_back(Point2LL(0, 50), 3, &test_squares[0], 0); - - std::vector result; - PolygonUtils::spreadDots(PolygonsPointIndex(&test_squares, 0, 0), PolygonsPointIndex(&test_squares, 0, 0), 8, result); - - ASSERT_EQ(result.size(), supposed.size()); - for (size_t point_idx = 0; point_idx < result.size(); point_idx++) - { - EXPECT_EQ(result[point_idx].p(), supposed[point_idx].p()); - } -} - struct GetNextParallelIntersectionParameters { std::optional predicted;