diff --git a/CMakeLists.txt b/CMakeLists.txt index 2287e8f92d..1d3a6350ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -165,6 +165,7 @@ set(engine_SRCS # Except main.cpp. src/geometry/Polyline.cpp src/geometry/ClosedPolyline.cpp src/geometry/MixedLinesSet.cpp + src/TreeSupportCradle.cpp ) add_library(_CuraEngine STATIC ${engine_SRCS} ${engine_PB_SRCS}) diff --git a/include/FffGcodeWriter.h b/include/FffGcodeWriter.h index f2337823f0..45be16e44b 100644 --- a/include/FffGcodeWriter.h +++ b/include/FffGcodeWriter.h @@ -11,6 +11,7 @@ #include "FanSpeedLayerTime.h" #include "GCodePathConfig.h" #include "LayerPlanBuffer.h" +#include "SupportInfillPart.h" #include "gcodeExport.h" #include "utils/LayerVector.h" #include "utils/NoCopy.h" @@ -635,12 +636,11 @@ class FffGcodeWriter : public NoCopy * layer. * * \param[in] storage Where the slice data is stored. - * \param[in] support_roof_outlines which polygons to generate roofs for. - * \param[in] current_roof_config config to be used. + * \param[in] support_roof_outlines Collection of polygons to generate roofs for. * \param gcodeLayer The initial planning of the g-code of the layer. * \return Whether any support skin was added to the layer plan. */ - bool addSupportRoofsToGCode(const SliceDataStorage& storage, const Shape& support_roof_outlines, const GCodePathConfig& current_roof_config, LayerPlan& gcode_layer) const; + bool addSupportRoofsToGCode(const SliceDataStorage& storage, const std::vector& support_roof_outlines, LayerPlan& gcode_layer) const; /*! * Add the support bottoms to the layer plan \p gcodeLayer of the current diff --git a/include/PathOrderOptimizer.h b/include/PathOrderOptimizer.h index be8e61873b..7e3cfc8701 100644 --- a/include/PathOrderOptimizer.h +++ b/include/PathOrderOptimizer.h @@ -205,7 +205,7 @@ class PathOrderOptimizer // For some Z seam types the start position can be pre-computed. // This is faster since we don't need to re-compute the start position at each step then. - precompute_start &= seam_config_.type_ == EZSeamType::RANDOM || seam_config_.type_ == EZSeamType::USER_SPECIFIED || seam_config_.type_ == EZSeamType::SHARPEST_CORNER; + precompute_start &= seam_config_.type_ == EZSeamType::RANDOM || seam_config_.type_ == EZSeamType::USER_SPECIFIED || seam_config_.type_ == EZSeamType::INTERNAL_SPECIFIED || seam_config_.type_ == EZSeamType::SHARPEST_CORNER; if (precompute_start) { for (auto& path : paths_) @@ -584,7 +584,7 @@ class PathOrderOptimizer } const bool precompute_start - = seam_config_.type_ == EZSeamType::RANDOM || seam_config_.type_ == EZSeamType::USER_SPECIFIED || seam_config_.type_ == EZSeamType::SHARPEST_CORNER; + = seam_config_.type_ == EZSeamType::RANDOM || seam_config_.type_ == EZSeamType::USER_SPECIFIED || seam_config_.type_ == EZSeamType::INTERNAL_SPECIFIED ||seam_config_.type_ == EZSeamType::SHARPEST_CORNER; if (! path->is_closed_ || ! precompute_start) // Find the start location unless we've already precomputed it. { path->start_vertex_ = findStartLocation(*path, start_position); @@ -676,6 +676,14 @@ class PathOrderOptimizer { if (! path.is_closed_) { + if (seam_config_.type_ == EZSeamType::INTERNAL_SPECIFIED) + { + if(getDirectDistance(path.converted_->back(), seam_config_.pos_) < getDirectDistance(path.converted_->front(), seam_config_.pos_)) + { + return path.converted_->size() - 1; // Back end is closer. + } + return 0; + } // For polylines, the seam settings are not applicable. Simply choose the position closest to target_pos then. const coord_t back_distance = (combing_boundary_ == nullptr) ? getDirectDistance(path.converted_->back(), target_pos) : getCombingDistance(path.converted_->back(), target_pos); diff --git a/include/SupportInfillPart.h b/include/SupportInfillPart.h index c864a67be6..753ae648af 100644 --- a/include/SupportInfillPart.h +++ b/include/SupportInfillPart.h @@ -4,10 +4,12 @@ #ifndef SUPPORT_INFILL_PART_H #define SUPPORT_INFILL_PART_H +#include #include #include "geometry/Polygon.h" #include "geometry/SingleShape.h" +#include "settings/EnumSettings.h" #include "utils/AABB.h" #include "utils/ExtrusionLine.h" @@ -36,8 +38,17 @@ class SupportInfillPart coord_t custom_line_distance_; //!< The distance between support infill lines. 0 means use the default line distance instead. bool use_fractional_config_; //!< Request to use the configuration used to fill a partial layer height here, instead of the normal full layer height configuration. + EFillMethod custom_line_pattern_; + std::optional start_near_location; - SupportInfillPart(const SingleShape& outline, coord_t support_line_width, bool use_fractional_config, int inset_count_to_generate = 0, coord_t custom_line_distance = 0); + SupportInfillPart(const SingleShape& outline, + coord_t support_line_width, + bool use_fractional_config, + int inset_count_to_generate = 0, + coord_t custom_line_distance = 0, + EFillMethod custom_line_pattern = EFillMethod::NONE, + std::optional start_near_location = std::optional() + ); const Shape& getInfillArea() const; }; diff --git a/include/TreeModelVolumes.h b/include/TreeModelVolumes.h index 1774b24fdf..aefe8dd202 100644 --- a/include/TreeModelVolumes.h +++ b/include/TreeModelVolumes.h @@ -10,6 +10,7 @@ #include #include +#include "TreeSupportEnums.h" #include "TreeSupportEnums.h" #include "geometry/Shape.h" #include "settings/EnumSettings.h" //To store whether X/Y or Z distance gets priority. @@ -40,6 +41,7 @@ class TreeModelVolumes coord_t max_move, coord_t max_move_slow, coord_t min_offset_per_step, + const coord_t min_radius, size_t current_mesh_idx, double progress_multiplier, double progress_offset, @@ -50,6 +52,7 @@ class TreeModelVolumes TreeModelVolumes(const TreeModelVolumes&) = delete; TreeModelVolumes& operator=(const TreeModelVolumes&) = delete; + /*! * \brief Precalculate avoidances and collisions up to this layer. * @@ -57,7 +60,7 @@ class TreeModelVolumes * Not calling this will cause the class to lazily calculate avoidances and collisions as needed, which will be a lot slower on systems with more then one or two cores! * */ - void precalculate(coord_t max_layer); + void precalculate(LayerIndex max_layer); /*! * \brief Provides the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. @@ -129,6 +132,7 @@ class TreeModelVolumes */ const Shape& getWallRestriction(coord_t radius, LayerIndex layer_idx, bool min_xy_dist); + /*! * \brief Round \p radius upwards to either a multiple of radius_sample_resolution_ or a exponentially increasing value * @@ -143,12 +147,46 @@ class TreeModelVolumes /*! * \brief Round \p radius upwards to the maximum that would still round up to the same value as the provided one. * - * \param radius The radius of the node of interest + * \param radius The radius of the element of interest * \param min_xy_dist is the minimum xy distance used. * \return The maximum radius, resulting in the same rounding. */ coord_t getRadiusNextCeil(coord_t radius, bool min_xy_dist) const; + LayerIndex getFirstAntiPreferredLayerIdx(); + + /*! + * \brief Provide hints which areas should be avoided in the future. + * \param area The area that should be avoided in the future. + * \param layer_idx The layer said area is on. + */ + void addAreaToAntiPreferred(const Shape area, LayerIndex layer_idx); + + void precalculateAntiPreferred(); + + /*! + * \brief Get areas that were additionally set to be avoided + * \param layer_idx The layer said area is on. + * \param radius The radius of the node of interest. + * \returns The area that should be avoided + */ + const Shape& getAntiPreferredAreas(LayerIndex layer_idx, coord_t radius); + + /*! + * \brief Get avoidance areas for areas that were additionally set to be avoided + * \param layer_idx The layer said area is on. + * \param radius The radius of the node of interest. + * \returns The area that should be avoided + */ + const Shape& getAntiPreferredAvoidance(coord_t radius, LayerIndex layer_idx, AvoidanceType type, bool to_model, bool min_xy_dist); + + /*! + * \brief Get areas that were are classified as support blocker + * \param layer_idx The layer said area is on. + * \returns The area should not contain support + */ + const Shape& getSupportBlocker(LayerIndex layer_idx); + private: /*! @@ -309,6 +347,17 @@ class TreeModelVolumes calculateAvoidanceToModel(std::deque{ RadiusLayerPair(key) }); } + /*! + * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model with radius 0 from the smallest actual avoidance. + * This is done as a real 0 radius avoidance would not be able to be printed. These 0 radius avoidances are used for calculating roof and cradle. + * + * The result is a 2D area that would cause nodes of radius 0 to + * collide with the model in a not wanted way. Result is saved in the cache. + * \param max_layer The result will be calculated up to the this layer. + */ + void calculateFake0Avoidances(const LayerIndex max_layer); + + /*! * \brief Creates the areas that can not be passed when expanding an area downwards. As such these areas are an somewhat abstract representation of a wall (as in a printed * object). @@ -463,6 +512,18 @@ class TreeModelVolumes */ RestPreference support_rest_preference_; + /*! + * \brief Largest DTT a cradle supporting tip may have. + */ + size_t max_cradle_dtt = 0; + + LayerIndex first_anti_preferred_layer_idx_ = 0; + + /*! + * \brief radii for which avoidance was already precalculated. Used to calculate anti preferred avoidance. + */ + std::deque precalculated_avoidance_radii; + /*! * \brief Caches for the collision, avoidance and areas on the model where support can be placed safely * at given radius and layer indices. @@ -520,9 +581,45 @@ class TreeModelVolumes mutable std::unordered_map wall_restrictions_cache_min_; std::unique_ptr critical_wall_restrictions_cache_min_ = std::make_unique(); + mutable std::unordered_map anti_preferred_; + std::unique_ptr critical_anti_preferred_ = std::make_unique(); + + mutable std::unordered_map anti_preferred_cache_; + mutable std::unordered_map anti_preferred_cache_to_model_; + mutable std::unordered_map anti_preferred_cache_collision; + std::unique_ptr critical_anti_preferred_caches = std::make_unique(); + + enum class CalculationType + { + AVOIDANCE, + AVOIDANCE_0, + AVOIDANCE_TO_MODEL, + AVOIDANCE_COLLISION, + COLLISION, + COLLISION_HOLEFREE, + PLACEABLE, + PLACEABLE_ACCUMULATED, + WALL_RESTRICTION + }; + std::unique_ptr critical_calculation_request_ = std::make_unique(); + std::unordered_map, std::shared_ptr> critical_calculation_map_; + + std::shared_ptr getLockForCalculation(coord_t radius, CalculationType type) + { + std::lock_guard critical_section(*critical_calculation_request_); + std::pair key(radius, type); + if (! critical_calculation_map_.contains(key)) + { + critical_calculation_map_[key] = std::make_shared(); + } + return critical_calculation_map_[key]; + } + std::unique_ptr critical_progress_ = std::make_unique(); Simplify simplifier_ = Simplify(0, 0, 0); // a simplifier to simplify polygons. Will be properly initialised in the constructor. + + Shape empty_polygon = Shape(); }; } // namespace cura diff --git a/include/TreeSupport.h b/include/TreeSupport.h index a4f1be756a..bd10156a7d 100644 --- a/include/TreeSupport.h +++ b/include/TreeSupport.h @@ -6,6 +6,7 @@ #include "TreeModelVolumes.h" #include "TreeSupportBaseCircle.h" +#include "TreeSupportCradle.h" #include "TreeSupportElement.h" #include "TreeSupportEnums.h" #include "TreeSupportSettings.h" @@ -36,7 +37,6 @@ constexpr auto SUPPORT_TREE_MINIMUM_FAKE_ROOF_AREA = 100.0; constexpr auto SUPPORT_TREE_MINIMUM_FAKE_ROOF_LAYERS = 1; constexpr auto SUPPORT_TREE_MINIMUM_ROOF_AREA_HARD_LIMIT = false; constexpr auto SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL = false; -constexpr auto SUPPORT_TREE_AVOID_SUPPORT_BLOCKER = true; constexpr coord_t SUPPORT_TREE_EXPONENTIAL_THRESHOLD = 1000; constexpr auto SUPPORT_TREE_EXPONENTIAL_FACTOR = 1.5; constexpr size_t SUPPORT_TREE_PRE_EXPONENTIAL_STEPS = 1; @@ -99,9 +99,10 @@ class TreeSupport * * \param storage[in] Background storage to access meshes. * \param currently_processing_meshes[in] Indexes of all meshes that are processed in this iteration + * \param top_most_cradle_layer_idx[in] Layer_idx of the top-most cradle. * \return Uppermost layer precalculated. -1 if no layer were precalculated as no overhang is present. */ - LayerIndex precalculate(const SliceDataStorage& storage, std::vector currently_processing_meshes); + LayerIndex precalculate(const SliceDataStorage& storage, std::vector currently_processing_meshes, LayerIndex top_most_cradle_layer_idx); /*! @@ -112,8 +113,14 @@ class TreeSupport * \param mesh[in] The mesh that is currently processed. * \param move_bounds[out] Storage for the influence areas. * \param storage[in] Background storage, required for adding roofs. + * \param cradle_data_model[out] All generated cradles, with its corresponding cradle lines. + */ - void generateInitialAreas(const SliceMeshStorage& mesh, std::vector>& move_bounds, SliceDataStorage& storage); + void generateInitialAreas( + const SliceMeshStorage& mesh, + std::vector>& move_bounds, + SliceDataStorage& storage, + std::vector>& cradle_data_model); /*! @@ -153,9 +160,10 @@ class TreeSupport * \param to_bp_areas[in] The Elements of the current Layer that will reach the buildplate. * Value is the influence area where the center of a circle of support may be placed. * \param to_model_areas[in] The Elements of the current Layer that do not have to reach the buildplate. Also contains main as every element that can reach the buildplate is - * not forced to. Value is the influence area where the center of a circle of support may be placed. \param influence_areas[in] The Elements of the current Layer without - * avoidances removed. This is the largest possible influence area for this layer. Value is the influence area where the center of a circle of support may be placed. \param - * layer_idx[in] The current layer. + * not forced to. Value is the influence area where the center of a circle of support may be placed. + * \param influence_areas[in] The Elements of the current Layer without + * avoidances removed. This is the largest possible influence area for this layer. Value is the influence area where the center of a circle of support may be placed. + * \param layer_idx[in] The current layer. */ void mergeInfluenceAreas(PropertyAreasUnordered& to_bp_areas, PropertyAreas& to_model_areas, PropertyAreas& influence_areas, LayerIndex layer_idx); @@ -205,27 +213,54 @@ class TreeSupport * * \param to_bp_areas[out] Influence areas that can reach the buildplate * \param to_model_areas[out] Influence areas that do not have to reach the buildplate. This has overlap with new_layer_data, as areas that can reach the buildplate are also - * considered valid areas to the model. This redundancy is required if a to_buildplate influence area is allowed to merge with a to model influence area. \param - * influence_areas[out] Area than can reach all further up support points. No assurance is made that the buildplate or the model can be reached in accordance to the - * user-supplied settings. \param bypass_merge_areas[out] Influence areas ready to be added to the layer below that do not need merging. \param last_layer[in] Influence areas - * of the current layer. \param layer_idx[in] Number of the current layer. \param mergelayer[in] Will the merge method be called on this layer. This information is required as - * some calculation can be avoided if they are not required for merging. + * considered valid areas to the model. This redundancy is required if a to_buildplate influence area is allowed to merge with a to model influence area. + * \param influence_areas[out] Area than can reach all further up support points. No assurance is made that the buildplate or the model can be reached in accordance to the + * user-supplied settings. + * \param bypass_merge_areas[out] Influence areas ready to be added to the layer below that do not need merging. + * \param last_layer[in] Influence areas of the current layer. + * \param layer_idx[in] Number of the current layer. + * \param mergelayer[in] Will the merge method be called on this layer. This information is required as some calculation can be avoided if they are not required for merging. */ void increaseAreas( PropertyAreasUnordered& to_bp_areas, PropertyAreas& to_model_areas, PropertyAreas& influence_areas, - std::vector& bypass_merge_areas, + PropertyAreas& bypass_merge_areas, const std::vector& last_layer, const LayerIndex layer_idx, const bool mergelayer); + /*! + * \brief Evaluate which cradle lines will have to be removed, remove them and remove tips that support said lines + * + + * \param to_bp_areas[in,out] The Elements of the current Layer that will reach the buildplate. + * Value is the influence area where the center of a circle of support may be placed. + * \param to_model_areas[in,out] The Elements of the current Layer that do not have to reach the buildplate. Also contains main as every element that can reach the buildplate + is + * not forced to. Value is the influence area where the center of a circle of support may be placed. + * \param influence_areas[in,out] The Elements of the current Layer without + * avoidances removed. This is the largest possible influence area for this layer. Value is the influence area where the center of a circle of support may be placed. + * \param layer_idx[in] The current layer. + * \param move_bounds[in,out] All currently existing influence areas + * \param cradle_data[in] Information about all cradle lines on this layer. + */ + void handleCradleLineValidity( + PropertyAreasUnordered& to_bp_areas, + PropertyAreas& to_model_areas, + PropertyAreas& influence_areas, + PropertyAreas& bypass_merge_areas, + LayerIndex layer_idx, + std::vector>& move_bounds, + std::vector>& cradle_data); + /*! * \brief Propagates influence downwards, and merges overlapping ones. * * \param move_bounds[in,out] All currently existing influence areas + * \param cradle_data[in,out] All currently existing cradles, with its corresponding cradle lines. */ - void createLayerPathing(std::vector>& move_bounds); + void createLayerPathing(std::vector>& move_bounds, std::vector>& cradle_data); /*! @@ -287,18 +322,101 @@ class TreeSupport const std::map& inverse_tree_order); - void filterFloatingLines(std::vector& support_layer_storage); + /*! + * \brief Accumulate areas needed later, union all import and add all roof to storage. + * \param support_layer_storage[in,out] Areas where support should be generated. + * \param support_layer_storage_fractional[in,out] Areas where support has to be, projected up for fractional height. + * \param support_roof_storage[in] Areas where support was replaced with roof. + * \param support_roof_extra_wall_storage[in] Areas where support was replaced with roof, but roofs need to have a wall to print correctly. + * \param support_roof_storage_fractional[in] Areas of roof that were projected one layer up. + * \param support_roof_extra_wall_storage_fractional[in] Areas of roof that were projected one layer up, but roofs need to have a wall to print correctly. + * \param fake_roof_areas_combined[out] All areas that contain the fake roofs. + * \param cradle_base_areas[out] Copy of all cradle base areas. Already added to correct storage. + * \param cradle_support_line_areas[out] All cradle lines consisting of regular support. Already added as support. + * \param storage[in,out] The storage where the support should be stored. + * \param cradle_data[in] All currently existing cradles, with its corresponding cradle lines. + */ + void prepareSupportAreas( + std::vector& support_layer_storage, + std::vector& support_layer_storage_fractional, + std::vector& support_roof_storage, + std::vector& support_roof_extra_wall_storage, + std::vector& support_roof_storage_fractional, + std::vector& support_roof_extra_wall_storage_fractional, + std::vector& fake_roof_areas_combined, + std::vector& cradle_base_areas, + std::vector& cradle_support_line_areas, + SliceDataStorage& storage, + std::vector>& cradle_data); + + /*! + * \brief Calculates which holes are valid (rest on walls) and which holes rest on which other holes + * \param support_layer_storage[in] Areas where support should be generated. + * \param hole_parts[out] Parts of holes, ordered by layer. + * \param valid_holes[out] Indices of holes that rest on outer wall, by layer. + * \param non_removable_holes[out] Indices of holes that can not be removed, by layer. + * \param hole_rest_map[out] Ordered by layer, information on which hole index on the layer below a given hole rests on + */ + void calculateSupportHoles(std::vector& support_layer_storage, + std::vector>& hole_parts, + std::vector>& valid_holes, + std::vector>& non_removable_holes, + std::vector>>& hole_rest_map); + + /*! + * \brief Generates support areas with high density infill to support interface above. Has to be called even if no support skin will generate, + * as cradle lines are added to the support_layer_storage here. + + * \param support_layer_storage[in,out] Areas where support should be generated. + * \param support_skin_storage[out] Areas where high density support should be generated. + * \param fake_roof_areas_combined[in] All areas that contain the fake roofs. + * \param cradle_base_areas[in] Copy of all cradle base areas. Already added to correct storage. + * \param cradle_support_line_areas[in] All cradle lines consisting of regular support. Already added as support. + * \param hole_parts[in] Parts of holes, ordered by layer. + * \param valid_holes[in] Indices of holes that rest on outer wall, by layer. + * \param non_removable_holes[in] Indices of holes that can not be removed, by layer. + * \param hole_rest_map[in] Ordered by layer, information on which hole index on the layer below a given hole rests on + * \param storage[in] The storage where the support should be stored. + * \param layer_tree_polygons[in] Resulting branch areas with the layerindex they appear on. + */ + void generateSupportSkin( + std::vector& support_layer_storage, + std::vector& support_skin_storage, + std::vector& fake_roof_areas_combined, + std::vector& cradle_base_areas, + std::vector& cradle_support_line_areas, + std::vector>& hole_parts, + std::vector>& valid_holes, + std::vector>& non_removable_holes, + std::vector>>& hole_rest_map, + SliceDataStorage& storage, + std::vector>& layer_tree_polygons); + + /*! + * \brief Filters out holes that would cause support to be printed mid-air. + * \param support_layer_storage[in,out] Areas where support should be generated. + * \param hole_parts[in] Parts of holes, ordered by layer. + * \param valid_holes[in] Indices of holes that rest on outer wall, by layer. + * \param non_removable_holes[in] Indices of holes that can not be removed, by layer. + * \param hole_rest_map[in] Ordered by layer, information on which hole index on the layer below a given hole rests on + */ + void removeFloatingHoles(std::vector& support_layer_storage, + std::vector>& hole_parts, + std::vector>& valid_holes, + std::vector>& non_removable_holes, + std::vector>>& hole_rest_map); /*! * \brief Generates Support Floor, ensures Support Roof can not cut of branches, and saves the branches as support to storage * * \param support_layer_storage[in] Areas where support should be generated. - * \param support_roof_storage[in] Areas where support was replaced with roof. + * \param support_skin_storage[in] Areas where high density support should be generated. + * \param support_layer_storage_fractional[out] Areas where support has to be, projected up for fractional height. * \param storage[in,out] The storage where the support should be stored. */ void finalizeInterfaceAndSupportAreas( std::vector& support_layer_storage, - std::vector& support_roof_storage, + std::vector& support_skin_storage, std::vector& support_layer_storage_fractional, SliceDataStorage& storage); @@ -307,8 +425,9 @@ class TreeSupport * * \param move_bounds[in] All currently existing influence areas * \param storage[in,out] The storage where the support should be stored. + * \param cradle_data[in] All currently existing cradles, with its corresponding cradle lines. */ - void drawAreas(std::vector>& move_bounds, SliceDataStorage& storage); + void drawAreas(std::vector>& move_bounds, SliceDataStorage& storage, std::vector>& cradle_data); /*! * \brief Settings with the indexes of meshes that use these settings. @@ -316,18 +435,17 @@ class TreeSupport std::vector>> grouped_meshes; /*! - * \brief Areas that should have been support roof, but where the roof settings would not allow any lines to be generated. + * \brief Areas that use a higher density pattern of regular support to support the model (fake_roof). */ - std::vector additional_required_support_area; + std::vector> fake_roof_areas; /*! - * \brief Areas that use a higher density pattern of regular support to support the model (fake_roof). + * \brief Areas where no support may be. Areas will be subtracted from support areas. */ - std::vector> fake_roof_areas; + std::vector support_free_areas; /*! * \brief Generator for model collision, avoidance and internal guide volumes. - * */ TreeModelVolumes volumes_; diff --git a/include/TreeSupportCradle.h b/include/TreeSupportCradle.h new file mode 100644 index 0000000000..e1ae8106e6 --- /dev/null +++ b/include/TreeSupportCradle.h @@ -0,0 +1,526 @@ +#ifndef CURAENGINE_TREESUPPORTCRADLE_H +#define CURAENGINE_TREESUPPORTCRADLE_H +#include + +#include "TreeSupportSettings.h" +#include "polyclipping/clipper.hpp" +#include "settings/types/LayerIndex.h" +#include "sliceDataStorage.h" +#include "utils/Coord_t.h" +#include "TreeModelVolumes.h" +#include "TreeSupportEnums.h" + +namespace cura +{ +// todo rename file as now general TreeSupportTipDataStructures +struct TreeSupportCradle; + +struct OverhangInformation +{ + OverhangInformation(Shape overhang, bool roof) + : overhang_(overhang) + , is_roof_(roof) + , is_cradle_(false) + , cradle_layer_idx_(-1) + , cradle_line_idx_(-1) + , cradle_(nullptr) + { + } + + OverhangInformation(Shape overhang, bool roof, TreeSupportCradle* cradle, int32_t cradle_layer_idx = -1, int32_t cradle_line_idx = -1) + : overhang_(overhang) + , is_roof_(roof) + , is_cradle_(true) + , cradle_layer_idx_(cradle_layer_idx) + , cradle_line_idx_(cradle_line_idx) + , cradle_(cradle) + { + } + + Shape overhang_; + bool is_roof_; + bool is_cradle_; + int32_t cradle_layer_idx_; + int32_t cradle_line_idx_; + TreeSupportCradle* cradle_; + + bool isCradleLine() + { + return is_cradle_ && cradle_line_idx_ >= 0 && cradle_layer_idx_ >= 0; + } +}; +struct TreeSupportCradleLine +{ + // required to shrink a vector using resize + TreeSupportCradleLine() + { + spdlog::error("Dummy TreeSupportCradleLine constructor called"); + } + + TreeSupportCradleLine(OpenPolyline line, LayerIndex layer_idx, bool is_roof) + : line_(line) + , layer_idx_(layer_idx) + , is_roof_(is_roof) + { + } + Shape area_; + OpenPolyline line_; + OpenPolyline removed_line_; + LayerIndex layer_idx_; + bool is_base_ = false; + bool is_roof_; + + void addLineToRemoved(OpenPolyline& line_to_add) + { + if (removed_line_.empty()) + { + removed_line_.push_back(line_to_add.front()); + removed_line_.push_back(line_to_add.back()); + } + else + { + if (vSize2(removed_line_.front() - removed_line_.back()) < vSize2(line_to_add.front() - removed_line_.back())) + { + removed_line_.front() = line_to_add.front(); + } + + if (vSize2(removed_line_.front() - removed_line_.back()) < vSize2(removed_line_.front() - line_to_add.back())) + { + removed_line_.back() = line_to_add.back(); + } + } + } +}; + +struct CradleConfig +{ + CradleConfig(const SliceMeshStorage& mesh, bool roof): + cradle_layers_(retrieveSetting(mesh.settings, "support_tree_cradle_height") / mesh.settings.get("layer_height")) + , cradle_layers_min_(retrieveSetting(mesh.settings, "support_tree_cradle_min_height") / mesh.settings.get("layer_height")) + , cradle_line_count_(retrieveSetting(mesh.settings, "support_tree_cradle_line_count")) + , cradle_length_(retrieveSetting(mesh.settings, "support_tree_cradle_length")) + , cradle_length_min_(retrieveSetting(mesh.settings, "support_tree_min_cradle_length")) + , cradle_line_width_(retrieveSetting(mesh.settings, "support_tree_cradle_line_width")) + , cradle_lines_roof_(roof && retrieveSetting(mesh.settings, "support_tree_roof_cradle") != "none") + , cradle_base_roof_(roof && ( + retrieveSetting(mesh.settings, "support_tree_roof_cradle") == "cradle_and_base" || + retrieveSetting(mesh.settings, "support_tree_roof_cradle") == "large_cradle_and_base" + )) + , large_cradle_base_(roof && retrieveSetting(mesh.settings, "support_tree_roof_cradle") == "large_cradle_and_base") + , large_cradle_line_tips_(retrieveSetting(mesh.settings, "support_tree_large_cradle_line_tips")) + , cradle_z_distance_layers_(round_divide(retrieveSetting(mesh.settings, "support_tree_cradle_z_distance"), mesh.settings.get("layer_height"))) + + { + TreeSupportSettings config(mesh.settings); //todo replace with gathering settings manually + if (cradle_layers_) + { + cradle_layers_ += cradle_z_distance_layers_; + } + if (cradle_length_ - cradle_line_width_ > 0) + { + cradle_length_ -= cradle_line_width_; + cradle_length_min_ -= cradle_line_width_; + } + + cradle_z_distance_layers_ = round_divide(retrieveSetting(mesh.settings, "support_tree_cradle_z_distance"), config.layer_height); + cradle_support_base_area_radius_ = retrieveSetting(mesh.settings, "support_tree_cradle_base_diameter") / 2; + + for (size_t cradle_z_dist_ctr = 0; cradle_z_dist_ctr < cradle_z_distance_layers_ + 1; cradle_z_dist_ctr++) + { + cradle_xy_distance_.emplace_back(config.xy_min_distance); + } + + for (int i = 0; i < 9; i++) + { + std::stringstream setting_name_dist; + setting_name_dist << "support_tree_cradle_xy_dist_"; + setting_name_dist << i; + coord_t next_cradle_xy_dist = retrieveSetting(mesh.settings, setting_name_dist.str()); + std::stringstream setting_name_height; + setting_name_height << "support_tree_cradle_xy_height_"; + setting_name_height << i; + coord_t next_cradle_xy_dist_height = retrieveSetting(mesh.settings, setting_name_height.str()); + if (next_cradle_xy_dist_height == 0) + { + break; + } + + for (int layer_delta = 0; layer_delta < round_up_divide(next_cradle_xy_dist_height, config.layer_height); layer_delta++) + { + cradle_xy_distance_.emplace_back(next_cradle_xy_dist); + } + } + + for (int cradle_xy_dist_fill = cradle_xy_distance_.size(); cradle_xy_dist_fill <= cradle_layers_ + 1; cradle_xy_dist_fill++) + { + cradle_xy_distance_.emplace_back(config.xy_min_distance); + } + + min_distance_between_lines_areas_ = (config.fill_outline_gaps ? config.min_feature_size / 2 - 5 : config.min_wall_line_width / 2 - 5); + cradle_line_distance_ = min_distance_between_lines_areas_ + config.xy_distance; + } + + /*! + * \brief Amount of layers of the cradle to support pointy overhangs. + */ + size_t cradle_layers_; + + /*! + * \brief Minimum amount of layers of the cradle to support pointy overhangs. + */ + size_t cradle_layers_min_; + + /*! + * \brief Amount of lines used for the cradle. + */ + size_t cradle_line_count_; + + /*! + * \brief Length of lines used for the cradle. + */ + coord_t cradle_length_; + + /*! + * \brief Minimum length of lines used for the cradle. TODO Width is effectively added to length ... fix or document? + */ + coord_t cradle_length_min_; + + /*! + * \brief Width of lines used for the cradle. + */ + coord_t cradle_line_width_; + + /*! + * \brief If cradle lines should be drawn as roof. + */ + bool cradle_lines_roof_; + + /*! + * \brief If the cradle base should be drawn as roof. + */ + bool cradle_base_roof_; + + /*! + * \brief Distances the cradle lines should be from the model. First value corresponds to cradle line on the same layer as the first model line. + */ + std::vector cradle_xy_distance_; + + /*! + * \brief If the (roof) cradle base should contain all cradle lines at cradle_tip_dtt size. + */ + bool large_cradle_base_; + + /*! + * \brief If the cradle lines should also be supported by larger tips. + */ + bool large_cradle_line_tips_; + + /*! + * \brief Distances in lines between the cradle and the support they are supported by. + */ + size_t cradle_z_distance_layers_; + + /*! + * \brief Distance to top of tips that support either the pointy overhang or the cradle lines at the bottom-most layer. + */ + coord_t cradle_support_base_area_radius_; + + /*! + * \brief Distance between line areas to prevent them from fusing + */ + coord_t min_distance_between_lines_areas_; + + /*! + * \brief Targeted distance between line areas + */ + coord_t cradle_line_distance_; + +}; + +struct TreeSupportCradle +{ + std::vector> lines_; + bool is_roof_; + LayerIndex layer_idx_; + std::vector base_below_; + std::vector centers_; + std::vector shadow_; + std::unordered_map> overhang_; + CradlePlacementMethod cradle_placement_method_; + const std::shared_ptr config_; + size_t mesh_idx_; + + + TreeSupportCradle(LayerIndex layer_idx, Point2LL center, bool roof, CradlePlacementMethod cradle_placement_method, std::shared_ptr config, size_t mesh_idx) + : layer_idx_(layer_idx) + , centers_({ center }) + , is_roof_(roof) + , cradle_placement_method_(cradle_placement_method) + , config_ (config) + , mesh_idx_(mesh_idx) + { + } + + + std::optional getCradleLineOfIndex(LayerIndex requested_layer_idx, size_t cradle_line_idx) + { + if (cradle_line_idx < lines_.size()) + { + for (size_t height_idx = 0; height_idx < lines_[cradle_line_idx].size(); height_idx++) + { + if (lines_[cradle_line_idx][height_idx].layer_idx_ == requested_layer_idx) + { + return &lines_[cradle_line_idx][height_idx]; + } + } + } + return {}; + } + + Point2LL getCenter(LayerIndex layer_idx_req) + { + if (layer_idx_req < layer_idx_) + { + return centers_.front(); + } + size_t index = layer_idx_req - layer_idx_; + if (centers_.size() <= index) + { + return centers_.back(); + } + return centers_[index]; + } + + size_t getIndexForLineEnd(Point2LL line_end, LayerIndex layer_idx_req) + { + Point2LL current_direction = line_end - getCenter(layer_idx_req); + double angle = std::atan2(current_direction.Y, current_direction.X); + if(angle < 0) + { + angle = 2.0 * std::numbers::pi + angle; + } + double step_size = (2.0 * std::numbers::pi) / double(config_->cradle_line_count_); + size_t angle_idx = size_t(std::round(angle / step_size)) % config_->cradle_line_count_; + return angle_idx; + } + + void verifyLines() + { + for (size_t line_idx = 0; line_idx < lines_.size(); line_idx++) + { + if (lines_[line_idx].size() < config_->cradle_layers_min_) + { + lines_[line_idx].clear(); + continue; + } + LayerIndex previous_layer_idx; + + for (size_t up_idx = 0; up_idx < lines_[line_idx].size(); up_idx++) + { + if (! lines_[line_idx][up_idx].is_base_) + { + previous_layer_idx = lines_[line_idx][up_idx].layer_idx_; + if (lines_[line_idx][up_idx].layer_idx_ > previous_layer_idx + up_idx || lines_[line_idx][up_idx].line_.size() < 2 + || lines_[line_idx][up_idx].line_.length() < config_->cradle_length_min_) + { + lines_[line_idx].clear(); + } + break; + } + } + for (size_t up_idx = 1; up_idx < lines_[line_idx].size(); up_idx++) + { + if (! lines_[line_idx][up_idx].is_base_) + { + if (lines_[line_idx][up_idx].layer_idx_ > previous_layer_idx + up_idx || lines_[line_idx][up_idx].line_.size() < 2 + || lines_[line_idx][up_idx].line_.length() < config_->cradle_length_min_) + { + if (up_idx <= config_->cradle_layers_min_) + { + spdlog::debug( + "Removing cradle line of cradle on layer {} line at {}. Invalid line was on layer {}", + layer_idx_, + line_idx, + lines_[line_idx][up_idx].layer_idx_); + lines_[line_idx].clear(); + break; + } + else + { + spdlog::debug("Partially removing cradle line of cradle on layer {} line at {} at height {}", layer_idx_, line_idx, up_idx); + lines_[line_idx].resize(up_idx - 1); + break; + } + } + } + } + } + } +}; + +struct CradlePresenceInformation +{ + CradlePresenceInformation(TreeSupportCradle* cradle, LayerIndex layer_idx, size_t line_idx) + : cradle_(cradle) + , layer_idx_(layer_idx) + , line_idx_(line_idx) + { + } + TreeSupportCradle* cradle_; + LayerIndex layer_idx_; + size_t line_idx_; + + TreeSupportCradleLine* getCradleLine() + { + return cradle_->getCradleLineOfIndex(layer_idx_, line_idx_).value(); + } + + bool cradleLineExists() + { + return cradle_->getCradleLineOfIndex(layer_idx_, line_idx_).has_value(); + } +}; + +class SupportCradleGeneration +{ +public: + /*! + * \brief Add meshes to generate cradles. + * \param storage[in] The storage that the mesh is in. + * \param mesh_idx[in] The idx of the mesh that is currently processed. + */ + void addMeshToCradleCalculation(const SliceDataStorage& storage, size_t mesh_idx); + + /*! + * \brief Generates mesh-specific cradle data. Calls addMeshToCradleCalculation if mesh was not already added. Not threadsafe! + * \param storage[in] The storage that the mesh is in. + * \param mesh_idx[in] The idx of the mesh that is currently processed. + */ + void generateCradleForMesh(const SliceDataStorage& storage, size_t mesh_idx); + + /*! + * \brief Generate all cradle areas and line areas for the previously added meshes. Not threadsafe! Should only called once. + * \param storage[in] The storage that contains all meshes. Used to access mesh specific settings + */ + + void generate(const SliceDataStorage& storage); + + /*! + * \brief Retrieve cradles generated for a certain mesh. + * \param target[out] Data storage for the cradles. + * \param support_free_areas[out] Areas where support should be removed to ensure the pointy overhang to supported. + * \param mesh_idx[in] The idx of the mesh for which the cradles are retrieved. + */ + void pushCradleData(std::vector>& target, std::vector& support_free_areas, size_t mesh_idx); + + /*! + * \brief Give the largest layer index that contains the first layer of a cradle. Only considers meshes that were added with addMeshToCradleCalculation. + */ + LayerIndex getTopMostCradleLayer() + { + return top_most_cradle_layer_; + } + + SupportCradleGeneration(const SliceDataStorage& storage, TreeModelVolumes& volumes_); +private: + + struct UnsupportedAreaInformation + { + UnsupportedAreaInformation(const Shape area, LayerIndex layer_idx, size_t height, coord_t accumulated_supportable_overhang) + : area{ area } + , layer_idx{ layer_idx } + , height{ height } + , accumulated_supportable_overhang{ accumulated_supportable_overhang } + { + } + const Shape area; + LayerIndex layer_idx; + size_t height; + coord_t accumulated_supportable_overhang; + CradlePlacementMethod support_required = CradlePlacementMethod::NONE; + + // Contains identifiers of all cradles below + std::vector cradles_below; + + std::vector areas_above; + std::vector areas_below; + }; + + /*! + * \brief Provides areas that do not have a connection to the buildplate or any other non support material below it. + * \param mesh_idx[in] The idx of the mesh. + * \param layer_idx The layer said area is on. + * \return A vector containing the areas, how many layers of material they have below them (always 0) and the idx of each area usable to get the next one layer above. + */ + std::vector getFullyUnsupportedArea(size_t mesh_idx, LayerIndex layer_idx); + + /*! + * \brief Calculates which parts of the model to not connect with the buildplate and how many layers of material is below them (height). + * Results are stored in a cache. + * Only area up to the required maximum height are stored. + * \param storage[in] The storage meshes are in. + * \param mesh_idx[in] The idx of the mesh. + */ + void calculateFloatingParts(const SliceDataStorage& storage, size_t mesh_idx); + + /*! + * \brief Generate the center points of all generated cradles. + * \param mesh[in] The mesh that is currently processed. + * \param mesh_idx[in] The idx of the mesh. + * \returns The calculated cradle centers for the mesh. + */ +std::vector> generateCradleCenters(const SliceMeshStorage& mesh, size_t mesh_idx); + +/*! + * \brief Generate lines from center and model information + * Only area up to the required maximum height are stored. + * \param cradle_data_mesh[in] The calculated cradle centers for the mesh. + * \param mesh[in] The mesh that is currently processed. + */ +void generateCradleLines(std::vector>& cradle_data_mesh, const SliceMeshStorage& mesh); + +/*! + * \brief Ensures cradle-lines do not intersect with each other. + */ +void cleanCradleLineOverlaps(); + +/*! + * \brief Finishes the cradle areas that represent cradle base and lines and calculates overhang for them. + * \param storage[in] The storage that contains all meshes. Used to access mesh specific settings + */ +void generateCradleLineAreasAndBase(const SliceDataStorage& storage); + + + +/*! + * \brief Representation of all cradles ordered by mesh_idx and layer_idx. + */ +std::vector>> cradle_data_; + +/*! + * \brief Representation of areas that have to be removed to ensure lines below the pointy overhang. + */ +std::vector support_free_areas_; + +/*! + * \brief Generator for model collision, avoidance and internal guide volumes. + */ +TreeModelVolumes& volumes_; + +/*! + * \brief Whether only support that can rest on a flat surface should be supported. todo + */ +const bool only_gracious_ = false; + +LayerIndex top_most_cradle_layer_ = -1; + +mutable std::vector>> floating_parts_cache_; + +std::unique_ptr critical_floating_parts_cache_ = std::make_unique(); + +}; + + +} // namespace cura + +#endif // CURAENGINE_TREESUPPORTCRADLE_H diff --git a/include/TreeSupportElement.h b/include/TreeSupportElement.h index b633aa863c..1d38fae2b3 100644 --- a/include/TreeSupportElement.h +++ b/include/TreeSupportElement.h @@ -17,6 +17,7 @@ namespace cura { +struct CradlePresenceInformation; struct AreaIncreaseSettings { AreaIncreaseSettings() @@ -25,16 +26,18 @@ struct AreaIncreaseSettings , increase_radius_(false) , no_error_(false) , use_min_distance_(false) + , use_anti_preferred_(false) , move_(false) { } - AreaIncreaseSettings(AvoidanceType type, coord_t increase_speed, bool increase_radius, bool simplify, bool use_min_distance, bool move) + AreaIncreaseSettings(AvoidanceType type, coord_t increase_speed, bool increase_radius, bool simplify, bool use_min_distance, bool use_anti_preferred, bool move) : type_(type) , increase_speed_(increase_speed) , increase_radius_(increase_radius) , no_error_(simplify) , use_min_distance_(use_min_distance) + , use_anti_preferred_(use_anti_preferred) , move_(move) { } @@ -44,6 +47,7 @@ struct AreaIncreaseSettings bool increase_radius_; bool no_error_; bool use_min_distance_; + bool use_anti_preferred_; bool move_; bool operator==(const AreaIncreaseSettings& other) const @@ -64,16 +68,18 @@ struct TreeSupportElement bool use_min_xy_dist, size_t dont_move_until, bool supports_roof, + bool supports_cradle, bool can_use_safe_radius, bool force_tips_to_roof, bool skip_ovalisation, bool influence_area_limit_active, - coord_t influence_area_limit_range) + coord_t influence_area_limit_range, + double hidden_radius_increase) : target_height_(target_height) , target_position_(target_position) , next_position_(target_position) , next_height_(target_height) - , effective_radius_height_(distance_to_top) + , effective_radius_height_(0) , to_buildplate_(to_buildplate) , distance_to_top_(distance_to_top) , area_(nullptr) @@ -83,78 +89,32 @@ struct TreeSupportElement , buildplate_radius_increases_(0) , use_min_xy_dist_(use_min_xy_dist) , supports_roof_(supports_roof) + , supports_cradle_(supports_cradle) , dont_move_until_(dont_move_until) , can_use_safe_radius_(can_use_safe_radius) - , last_area_increase_(AreaIncreaseSettings(AvoidanceType::FAST, 0, false, false, false, false)) + , can_avoid_anti_preferred_(false) + , ensure_valid_anti_preferred_(false) + , last_area_increase_(AreaIncreaseSettings(AvoidanceType::FAST, 0, false, false, false, false, false)) , missing_roof_layers_(force_tips_to_roof ? dont_move_until : 0) + , roof_with_enforced_walls(false) , skip_ovalisation_(skip_ovalisation) , all_tips_({ target_position }) , influence_area_limit_active_(influence_area_limit_active) , influence_area_limit_range_(influence_area_limit_range) + , hidden_radius_increase_(hidden_radius_increase) { RecreateInfluenceLimitArea(); } - TreeSupportElement(const TreeSupportElement& elem, Shape* newArea = nullptr) - : // copy constructor with possibility to set a new area - target_height_(elem.target_height_) - , target_position_(elem.target_position_) - , next_position_(elem.next_position_) - , next_height_(elem.next_height_) - , effective_radius_height_(elem.effective_radius_height_) - , to_buildplate_(elem.to_buildplate_) - , distance_to_top_(elem.distance_to_top_) - , area_(newArea != nullptr ? newArea : elem.area_) - , result_on_layer_(elem.result_on_layer_) - , increased_to_model_radius_(elem.increased_to_model_radius_) - , to_model_gracious_(elem.to_model_gracious_) - , buildplate_radius_increases_(elem.buildplate_radius_increases_) - , use_min_xy_dist_(elem.use_min_xy_dist_) - , supports_roof_(elem.supports_roof_) - , dont_move_until_(elem.dont_move_until_) - , can_use_safe_radius_(elem.can_use_safe_radius_) - , last_area_increase_(elem.last_area_increase_) - , missing_roof_layers_(elem.missing_roof_layers_) - , skip_ovalisation_(elem.skip_ovalisation_) - , all_tips_(elem.all_tips_) - , influence_area_limit_active_(elem.influence_area_limit_active_) - , influence_area_limit_range_(elem.influence_area_limit_range_) - , influence_area_limit_area_(elem.influence_area_limit_area_) + TreeSupportElement(const TreeSupportElement& elem, Shape* new_area) + : // copy constructor that sets a new area + TreeSupportElement(elem) { - parents_.insert(parents_.begin(), elem.parents_.begin(), elem.parents_.end()); + area_ = new_area; + additional_ovalization_targets_.clear(); + cradle_line_ = nullptr; } - /*! - * \brief Create a new Element for one layer below the element of the pointer supplied. - */ - TreeSupportElement(TreeSupportElement* element_above) - : target_height_(element_above->target_height_) - , target_position_(element_above->target_position_) - , next_position_(element_above->next_position_) - , next_height_(element_above->next_height_) - , effective_radius_height_(element_above->effective_radius_height_) - , to_buildplate_(element_above->to_buildplate_) - , distance_to_top_(element_above->distance_to_top_ + 1) - , area_(element_above->area_) - , result_on_layer_(Point2LL(-1, -1)) - , // set to invalid as we are a new node on a new layer - increased_to_model_radius_(element_above->increased_to_model_radius_) - , to_model_gracious_(element_above->to_model_gracious_) - , buildplate_radius_increases_(element_above->buildplate_radius_increases_) - , use_min_xy_dist_(element_above->use_min_xy_dist_) - , supports_roof_(element_above->supports_roof_) - , dont_move_until_(element_above->dont_move_until_) - , can_use_safe_radius_(element_above->can_use_safe_radius_) - , last_area_increase_(element_above->last_area_increase_) - , missing_roof_layers_(element_above->missing_roof_layers_) - , skip_ovalisation_(false) - , all_tips_(element_above->all_tips_) - , influence_area_limit_active_(element_above->influence_area_limit_active_) - , influence_area_limit_range_(element_above->influence_area_limit_range_) - , influence_area_limit_area_(element_above->influence_area_limit_area_) - { - parents_ = { element_above }; - } // ONLY to be called in merge as it assumes a few assurances made by it. TreeSupportElement( @@ -173,9 +133,13 @@ struct TreeSupportElement , increased_to_model_radius_(increased_to_model_radius) , use_min_xy_dist_(first.use_min_xy_dist_ || second.use_min_xy_dist_) , supports_roof_(first.supports_roof_ || second.supports_roof_) + , supports_cradle_(first.supports_cradle_ || second.supports_cradle_) , dont_move_until_(std::max(first.dont_move_until_, second.dont_move_until_)) , can_use_safe_radius_(first.can_use_safe_radius_ || second.can_use_safe_radius_) + , can_avoid_anti_preferred_(first.can_avoid_anti_preferred_ || second.can_avoid_anti_preferred_) + , ensure_valid_anti_preferred_(first.ensure_valid_anti_preferred_ || second.ensure_valid_anti_preferred_) , missing_roof_layers_(std::min(first.missing_roof_layers_, second.missing_roof_layers_)) + , roof_with_enforced_walls(first.roof_with_enforced_walls && second.roof_with_enforced_walls) , skip_ovalisation_(false) { if (first.target_height_ > second.target_height_) @@ -208,6 +172,13 @@ struct TreeSupportElement // 'buildplate_radius_increases' has to be recalculated, as when a smaller tree with a larger buildplate_radius_increases merge with a larger branch, // the buildplate_radius_increases may have to be lower as otherwise the radius suddenly increases. This results often in a non integer value. buildplate_radius_increases_ = foot_increase_radius / (branch_radius * (diameter_scale_bp_radius - diameter_angle_scale_factor)); + + const coord_t hidden_increase_radius = std::abs( + std::max( + getRadius(second.effective_radius_height_, second.buildplate_radius_increases_ + second.hidden_radius_increase_), + getRadius(first.effective_radius_height_, first.buildplate_radius_increases_ + first.hidden_radius_increase_)) + - getRadius(effective_radius_height_, buildplate_radius_increases_)); + hidden_radius_increase_ = hidden_increase_radius / (branch_radius * (diameter_scale_bp_radius - diameter_angle_scale_factor)); } // set last settings to the best out of both parents. If this is wrong, it will only cause a small performance penalty instead of weird behavior. @@ -217,6 +188,7 @@ struct TreeSupportElement first.last_area_increase_.increase_radius_ || second.last_area_increase_.increase_radius_, first.last_area_increase_.no_error_ || second.last_area_increase_.no_error_, first.last_area_increase_.use_min_distance_ && second.last_area_increase_.use_min_distance_, + first.can_avoid_anti_preferred_ && second.can_avoid_anti_preferred_, first.last_area_increase_.move_ || second.last_area_increase_.move_); all_tips_ = first.all_tips_; @@ -308,6 +280,11 @@ struct TreeSupportElement */ bool supports_roof_; + /*! + * \brief True if this Element or any parent provides support to a cradle or cradle line. + */ + bool supports_cradle_; + /*! * \brief The element trys not to move until this dtt is reached, is set to 0 if the element had to move. */ @@ -318,6 +295,16 @@ struct TreeSupportElement */ bool can_use_safe_radius_; + /*! + * \brief An influence area can avoid anti-preferred when the difference with it is non empty. + */ + bool can_avoid_anti_preferred_; + + /*! + * \brief If the influence area ensures no collision with anti preferred on this layer. + */ + bool ensure_valid_anti_preferred_; + /*! * \brief Settings used to increase the influence area to its current state. */ @@ -328,6 +315,11 @@ struct TreeSupportElement */ size_t missing_roof_layers_; + /*! + * \brief True if interface with walls has to be used, even though regular interface does not have walls. + */ + bool roof_with_enforced_walls; + /*! * \brief Skip the ovalisation to parent and children when generating the final circles. */ @@ -358,6 +350,16 @@ struct TreeSupportElement */ std::vector additional_ovalization_targets_; + /*! + * \brief Pointer to the cradle line it supports if it does support a cradle line. + */ + std::shared_ptr cradle_line_; + + /*! + * \brief Counter about the times the radius was increased to reach the correct initial radius. Uses logic intended for buildplate_radius_increases_ + */ + double hidden_radius_increase_; + bool operator==(const TreeSupportElement& other) const { @@ -438,6 +440,21 @@ struct TreeSupportElement { return result_on_layer_ != Point2LL(-1, -1); } + /*! + * \brief Create a new Element for one layer below the element. + */ + + TreeSupportElement createNewElement() + { + TreeSupportElement result(*this); + result.parents_ = { this }; + result.distance_to_top_ += 1; + result.skip_ovalisation_ = false; + result.result_on_layer_ = Point2LL(-1, -1); + result.area_ = nullptr; + result.ensure_valid_anti_preferred_ = false; + return result; + } }; } // namespace cura diff --git a/include/TreeSupportEnums.h b/include/TreeSupportEnums.h index f3c959c8dd..ce80188fdd 100644 --- a/include/TreeSupportEnums.h +++ b/include/TreeSupportEnums.h @@ -28,6 +28,15 @@ enum class AvoidanceType COLLISION }; +enum class CradlePlacementMethod +{ + NONE, + AUTOMATIC_POINTY, + AUTOMATIC_SIDE, + MANUAL_POINTY, + MANUAL_SIDE +}; + } // namespace cura #endif // CURAENGINE_TREESUPPORTENUMS_H diff --git a/include/TreeSupportSettings.h b/include/TreeSupportSettings.h index 2e3162a71a..356119b128 100644 --- a/include/TreeSupportSettings.h +++ b/include/TreeSupportSettings.h @@ -20,6 +20,26 @@ namespace cura { +template +static A retrieveSetting(const Settings& settings, const std::string& key) +{ + if (settings.has(key)) + { + return settings.get(key); + } + else + { + for (std::string setting_key : settings.getKeys()) + { + if (setting_key.find(key) != std::string::npos) + { + return settings.get(setting_key); + } + } + return settings.get(key); // this will cause a crash, but that's the expected behaviour in this case anyway + } +} + /*! * \brief This struct contains settings used in the tree support. Thanks to this most functions do not need to know of meshes etc. Also makes the code shorter. */ @@ -54,7 +74,7 @@ struct TreeSupportSettings : RestPreference::BUILDPLATE) , xy_distance(mesh_group_settings.get("support_xy_distance")) , bp_radius(mesh_group_settings.get("support_tree_bp_diameter") / 2) - , diameter_scale_bp_radius(std::min(sin(0.7) * static_cast(layer_height / branch_radius), 1.0 / (branch_radius / (support_line_width / 2.0)))) + , diameter_scale_bp_radius(std::min(sin(0.7) * static_cast(layer_height) / static_cast(branch_radius), 1.0 / (branch_radius / (support_line_width / 2.0)))) , // Either 40° or as much as possible so that 2 lines will overlap by at least 50%, whichever is smaller. support_overrides(mesh_group_settings.get("support_xy_overrides_z")) , xy_min_distance(support_overrides == SupportDistPriority::Z_OVERRIDES_XY ? mesh_group_settings.get("support_xy_distance_overhang") : xy_distance) @@ -82,6 +102,9 @@ struct TreeSupportSettings , min_feature_size(mesh_group_settings.get("min_feature_size")) , min_wall_line_width(settings.get("min_wall_line_width")) , fill_outline_gaps(settings.get("fill_outline_gaps")) + , support_skin_layers(round_up_divide(retrieveSetting(mesh_group_settings, "support_tree_support_skin_height"), layer_height)) + , support_skin_line_distance(retrieveSetting(mesh_group_settings, "support_tree_support_skin_line_distance")) + , support_tree_skin_for_large_tips_radius_threshold(retrieveSetting(mesh_group_settings, "support_tree_skin_for_large_tips_threshold") / 2) , simplifier(Simplify(mesh_group_settings)) { layer_start_bp_radius = (bp_radius - branch_radius) / (branch_radius * diameter_scale_bp_radius); @@ -383,6 +406,21 @@ struct TreeSupportSettings */ bool fill_outline_gaps; + /*! + * \brief How many high density layers should be below roof and cradle. + */ + size_t support_skin_layers; + + /*! + * \brief Distance between lines of the high density line pattern. + */ + coord_t support_skin_line_distance; + + /*! + * \brief Tips with a radius of at least this should have skin. + */ + coord_t support_tree_skin_for_large_tips_radius_threshold; + /*! * \brief Simplifier to simplify polygons. */ @@ -412,8 +450,9 @@ struct TreeSupportSettings && zag_skip_count == other.zag_skip_count && connect_zigzags == other.connect_zigzags && interface_preference == other.interface_preference && min_feature_size == other.min_feature_size && // interface_preference should be identical to ensure the tree will correctly interact with the roof. support_rest_preference == other.support_rest_preference && max_radius == other.max_radius && min_wall_line_width == other.min_wall_line_width - && fill_outline_gaps == other.fill_outline_gaps && - // The infill class now wants the settings object and reads a lot of settings, and as the infill class is used to calculate support roof lines for + && fill_outline_gaps == other.fill_outline_gaps && support_skin_layers == other.support_skin_layers && support_skin_line_distance == other.support_skin_line_distance + && support_tree_skin_for_large_tips_radius_threshold == other.support_tree_skin_for_large_tips_radius_threshold && + // The infill class now wants the settings object and reads a lot of settings, and as the infill class is used to calculate support roof lines for// // interface-preference. Not all of these may be required to be identical, but as I am not sure, better safe than sorry (interface_preference == InterfacePreference::INTERFACE_AREA_OVERWRITES_SUPPORT || interface_preference == InterfacePreference::SUPPORT_AREA_OVERWRITES_INTERFACE || (settings.get("fill_outline_gaps") == other.settings.get("fill_outline_gaps") @@ -431,7 +470,9 @@ struct TreeSupportSettings /*! * \brief Get the Distance to top regarding the real radius this part will have. This is different from distance_to_top, which is can be used to calculate the top most layer of - * the branch. \param elem[in] The SupportElement one wants to know the effectiveDTT \return The Effective DTT. + * the branch. + * \param elem[in] The SupportElement one wants to know the effectiveDTT + * \return The Effective DTT. */ [[nodiscard]] inline size_t getEffectiveDTT(const TreeSupportElement& elem) const { @@ -463,7 +504,9 @@ struct TreeSupportSettings */ [[nodiscard]] inline coord_t getRadius(const TreeSupportElement& elem) const { - return getRadius(getEffectiveDTT(elem), (elem.isResultOnLayerSet() || ! support_rests_on_model) && elem.to_buildplate_ ? elem.buildplate_radius_increases_ : 0); + return getRadius( + getEffectiveDTT(elem), + elem.hidden_radius_increase_ + ((elem.isResultOnLayerSet() || ! support_rests_on_model) && elem.to_buildplate_ ? elem.buildplate_radius_increases_ : 0)); } /*! diff --git a/include/TreeSupportTipGenerator.h b/include/TreeSupportTipGenerator.h index e483500ea0..5cdb7bffbc 100644 --- a/include/TreeSupportTipGenerator.h +++ b/include/TreeSupportTipGenerator.h @@ -7,6 +7,7 @@ #include "TreeModelVolumes.h" #include "TreeSupport.h" #include "TreeSupportBaseCircle.h" +#include "TreeSupportCradle.h" #include "TreeSupportElement.h" #include "TreeSupportEnums.h" #include "TreeSupportSettings.h" @@ -34,16 +35,18 @@ class TreeSupportTipGenerator * \param storage[in] Background storage, required for adding roofs. * \param mesh[in] The mesh that is currently processed. Contains the overhangs. * \param move_bounds[out] The storage for the tips. - * \param additional_support_areas[out] Areas that should have been roofs, but are now support, as they would not generate any lines as roof. Should already be initialised. - * \param placed_fake_roof_areas[out] Areas where fake roof has to be placed. + * \param placed_support_lines_support_areas[out] Support-lines that were already placed represented as the area the lines will take when printed. + * \param support_free_areas[out] Areas where no support (including roof) of any kind is to be drawn. + * \param cradle_data[in] Generated cradle lines. * \return All lines of the \p polylines object, with information for each point regarding in which avoidance it is currently valid in. */ void generateTips( SliceDataStorage& storage, const SliceMeshStorage& mesh, std::vector>& move_bounds, - std::vector& additional_support_areas, - std::vector>& placed_fake_roof_areas); + std::vector>& placed_fake_roof_areas, + std::vector& support_free_areas, + std::vector>& cradle_data); private: enum class LineStatus @@ -56,6 +59,14 @@ class TreeSupportTipGenerator TO_BP_SAFE }; + enum class TipRoofType + { + NONE, + SUPPORTS_ROOF, + IS_ROOF + }; + + using LineInformation = std::vector>; /*! @@ -112,10 +123,9 @@ class TreeSupportTipGenerator * \param mesh[in] The mesh that is currently processed. * \param line_distance[in] The distance between the infill lines of the resulting infill * \param line_width[in] What is the width of a line used in the infill. - * \return A valid CrossInfillProvider. Has to be freed manually to avoid a memory leak. + * \return A valid CrossInfillProvider. */ - std::shared_ptr generateCrossFillProvider(const SliceMeshStorage& mesh, coord_t line_distance, coord_t line_width) const; - + std::shared_ptr getCrossFillProvider(const SliceMeshStorage& mesh, coord_t line_distance, coord_t line_width); /*! * \brief Drops overhang areas further down until they are valid (at most max_overhang_insert_lag layers) @@ -128,26 +138,31 @@ class TreeSupportTipGenerator /*! * \brief Calculates which areas should be supported with roof, and saves these in roof support_roof_drawn * \param mesh[in] The mesh that is currently processed. + * \param cradle_data[in] Cradles for this mesh if applicable */ - void calculateRoofAreas(const SliceMeshStorage& mesh); + void calculateRoofAreas(const SliceMeshStorage& mesh, std::vector>& cradle_data); /*! * \brief Add a point as a tip * \param move_bounds[out] The storage for the tips. * \param p[in] The point that will be added and its LineStatus. * \param dtt[in] The distance to top the added tip will have. + * \param hidden_radius_increase[in] Additional bp increases hidden from the collision calculation. Used to ensure branches are large enough to reach initial layer diameter. * \param insert_layer[in] The layer the tip will be on. * \param dont_move_until[in] Until which dtt the branch should not move if possible. * \param roof[in] Whether the tip supports a roof. * \param skip_ovalisation[in] Whether the tip may be ovalized when drawn later. + * \param additional_ovalization_targets[in] Additional targets the ovalization should reach. */ - void addPointAsInfluenceArea( + TreeSupportElement* addPointAsInfluenceArea( std::vector>& move_bounds, - std::pair p, + std::pair p, size_t dtt, + double hidden_radius_increase, LayerIndex insert_layer, size_t dont_move_until, - bool roof, + TipRoofType roof, + bool cradle, bool skip_ovalisation, std::vector additional_ovalization_targets = std::vector()); @@ -158,15 +173,18 @@ class TreeSupportTipGenerator * \param lines[in] The lines of which points will be added. * \param roof_tip_layers[in] Amount of layers the tip should be drawn as roof. * \param insert_layer_idx[in] The layer the tip will be on. + * \param tip_radius[in] Target radius of the tips. * \param supports_roof[in] Whether the tip supports a roof. * \param dont_move_until[in] Until which dtt the branch should not move if possible. + * \param connect_points [in] If the points of said line should be connected by ovalization. */ void addLinesAsInfluenceAreas( std::vector>& move_bounds, std::vector lines, size_t roof_tip_layers, LayerIndex insert_layer_idx, - bool supports_roof, + coord_t tip_radius, + OverhangInformation& overhang_data, size_t dont_move_until, bool connect_points); @@ -174,9 +192,9 @@ class TreeSupportTipGenerator * \brief Remove tips that should not have been added in the first place. * \param move_bounds[in,out] The already added tips * \param storage[in] Background storage, required for adding roofs. - * \param additional_support_areas[in] Areas that should have been roofs, but are now support, as they would not generate any lines as roof. + * \param cradle_data[in] Cradles for this mesh if applicable */ - void removeUselessAddedPoints(std::vector>& move_bounds, SliceDataStorage& storage, std::vector& additional_support_areas); + void removeUselessAddedPoints(std::vector>& move_bounds, SliceDataStorage& storage, std::vector>& cradle_data); /*! * \brief Contains config settings to avoid loading them in every function. This was done to improve readability of the code. @@ -209,14 +227,9 @@ class TreeSupportTipGenerator const size_t support_roof_layers_; /*! - * \brief Distance between tips, so that the tips form a lime. Is smaller than Tip Diameter. + * \brief Density the tips should have when supporting overhangs */ - const coord_t connect_length_; - - /*! - * \brief Distance between tips, if the tips support an overhang. - */ - const coord_t support_tree_branch_distance_; + const double support_tree_top_rate_; /*! * \brief Distance between support roof lines. Is required for generating roof patterns. @@ -234,11 +247,6 @@ class TreeSupportTipGenerator */ const coord_t roof_outset_; - /*! - * \brief Whether tips should be printed as roof - */ - const bool force_tip_to_roof_; - /*! * \brief Whether the maximum distance a branch should from a point they support should be limited. Can be violated if required. */ @@ -264,10 +272,6 @@ class TreeSupportTipGenerator */ size_t max_overhang_insert_lag_; - /*! - * \brief Area of a tip. - */ - const double tip_roof_size_; /*! * \brief Whether only support that can rest on a flat surface should be supported. @@ -280,15 +284,20 @@ class TreeSupportTipGenerator */ const bool force_minimum_roof_area_ = SUPPORT_TREE_MINIMUM_ROOF_AREA_HARD_LIMIT; + /*! + * \brief Whether tips should be larger to enable reaching initial_layer_diameter. + */ + const bool force_initial_layer_radius_; + /*! * \brief Distance between branches when the branches support a support pattern */ coord_t support_supporting_branch_distance_; /*! - * \brief Required to generate cross infill patterns + * \brief Required to generate cross infill patterns. Key: Distance between lines */ - std::shared_ptr cross_fill_provider_; + std::unordered_map, std::shared_ptr> cross_fill_providers_; /*! * \brief Map that saves locations of already inserted tips. Used to prevent tips far to close together from being added. @@ -301,19 +310,20 @@ class TreeSupportTipGenerator std::vector support_roof_drawn_; /*! - * \brief Areas that require fractional roof above it. + * \brief Areas that should have fractional roof above it. */ std::vector support_roof_drawn_fractional_; /*! - * \brief Areas that will be saved as support roof, originating from tips being replaced with roof areas. + * \brief If the cradle lines should also be supported by larger tips. */ - std::vector roof_tips_drawn_; + bool large_cradle_line_tips_; std::mutex critical_move_bounds_; - std::mutex critical_roof_tips_; + std::mutex critical_cross_fill_; + }; } // namespace cura -#endif /* TREESUPPORT_H */ +#endif /* TREESUPPORTTIPGENERATOR_H */ \ No newline at end of file diff --git a/include/TreeSupportUtils.h b/include/TreeSupportUtils.h index d11364fd2d..3f20ccfd81 100644 --- a/include/TreeSupportUtils.h +++ b/include/TreeSupportUtils.h @@ -90,8 +90,9 @@ class TreeSupportUtils * \param layer_idx[in] The current layer index. * \param support_infill_distance[in] The distance that should be between the infill lines. * \param cross_fill_provider[in] A SierpinskiFillProvider required for cross infill. - * \param include_walls[in] If the result should also contain walls, or only the infill. - * todo doku + * \param wall_count[in] Amount of walls the result should contain. + * \param special_pattern[in] Use a different pattern. None means the default pattern as in config will be used. + * \param disable_connect[in] If the connecting of Infill lines has to be disabled. * \return A Polygons object that represents the resulting infill lines. */ [[nodiscard]] static OpenLinesSet generateSupportInfillLines( @@ -101,20 +102,21 @@ class TreeSupportUtils LayerIndex layer_idx, coord_t support_infill_distance, std::shared_ptr cross_fill_provider, - bool include_walls, - bool generate_support_supporting = false) + size_t wall_count, + EFillMethod special_pattern = EFillMethod::NONE, + bool disable_connect = false) { Shape gaps; - // As we effectivly use lines to place our supportPoints we may use the Infill class for it, while not made for it, it works perfectly. + // As we effectively use lines to place our supportPoints we may use the Infill class for it, while not made for it, it works perfectly. - const EFillMethod pattern = generate_support_supporting ? EFillMethod::GRID : roof ? config.roof_pattern : config.support_pattern; + const EFillMethod pattern = (special_pattern != EFillMethod::NONE) ? special_pattern : roof ? config.roof_pattern : config.support_pattern; - const bool zig_zaggify_infill = roof ? pattern == EFillMethod::ZIG_ZAG : config.zig_zaggify_support; + const bool zig_zaggify_infill = ! disable_connect && (roof ? pattern == EFillMethod::ZIG_ZAG : config.zig_zaggify_support); const bool connect_polygons = false; constexpr coord_t support_roof_overlap = 0; constexpr size_t infill_multiplier = 1; const int support_shift = roof ? 0 : support_infill_distance / 2; - const size_t wall_line_count = include_walls ? (! roof ? config.support_wall_count : config.support_roof_wall_count) : 0; + const size_t wall_line_count = wall_count; constexpr coord_t narrow_area_width = 0; const Point2LL infill_origin; constexpr bool skip_stitching = false; @@ -202,11 +204,13 @@ class TreeSupportUtils * \param me[in] Shape object that has to be offset. * \param distance[in] The distance by which me should be offset. Expects values >=0. * \param collision[in] The area representing obstacles. - * \param last_step_offset_without_check[in] The most it is allowed to offset in one step. + * \param safe_step_size[in] The most it is allowed to offset in one step. + * \param last_step_offset_without_check[in] The amount of distance for which the collision could be violated at the end. * \param min_amount_offset[in] How many steps have to be done at least. As this uses round offset this increases the amount of vertices, which may be required if Shape get - * very small. Required as arcTolerance is not exposed in offset, which should result with a similar result, benefit may be eliminated by simplifying. \param - * min_offset_per_step Don't get below this amount of offset per step taken. Fine-tune tradeoff between speed and accuracy. \param simplifier[in] Pointer to Simplify object if - * the offset operation also simplify the Polygon. Improves performance. \return The resulting Shape object. + * very small. Required as arcTolerance is not exposed in offset, which should result with a similar result, benefit may be eliminated by simplifying. + * \param min_offset_per_step Don't get below this amount of offset per step taken. Fine-tune tradeoff between speed and accuracy. + * \param simplifier[in] Pointer to Simplify object if the offset operation also simplify the Polygon. Improves performance. + * \return The resulting Shape object. */ [[nodiscard]] static Shape safeOffsetInc( const Shape& me, diff --git a/include/geometry/LinesSet.h b/include/geometry/LinesSet.h index 7834b821eb..bb444f52cc 100644 --- a/include/geometry/LinesSet.h +++ b/include/geometry/LinesSet.h @@ -255,9 +255,10 @@ class LinesSet * not the shape. * \param outer_offset Offset relative to the original shape-outline towards the outside of the * shape. Comparable to normal offset. + * \param jt JoinType for the offsets. * \return The resulting polygons. */ - [[nodiscard]] Shape createTubeShape(const coord_t inner_offset, const coord_t outer_offset) const; + [[nodiscard]] Shape createTubeShape(const coord_t inner_offset, const coord_t outer_offset, const ClipperLib::JoinType jt = ClipperLib::jtMiter) const; void translate(const Point2LL& delta); diff --git a/include/geometry/Shape.h b/include/geometry/Shape.h index 552325676f..715e29f96c 100644 --- a/include/geometry/Shape.h +++ b/include/geometry/Shape.h @@ -103,6 +103,20 @@ class Shape : public LinesSet template OpenLinesSet intersection(const LinesSet& polylines, bool restitch = true, const coord_t max_stitch_distance = 10_mu) const; + /*! + * Subtract an area covered by the shape from polylines. + * + * \note Due to a clipper bug with polylines with nearly collinear segments, the polylines are cut up into separate polylines, and restitched back together at the end. + * + * \param polylines The polylines from which the the area of this Polygons object will be removed. + * \param restitch Whether to stitch the resulting segments into longer polylines, or leave every segment as a single segment + * \param max_stitch_distance The maximum distance for two polylines to be stitched together with a segment + * \return The resulting polylines from which the the area of this Polygons object was removed. + * \todo This should technically return a MixedLinesSet, because it can definitely contain open and closed polylines, but that is a heavy change + */ + template + OpenLinesSet difference(const LinesSet& polylines, bool restitch = true, const coord_t max_stitch_distance = 10_mu) const; + [[nodiscard]] Shape xorPolygons(const Shape& other, ClipperLib::PolyFillType pft = ClipperLib::pftEvenOdd) const; [[nodiscard]] Shape execute(ClipperLib::PolyFillType pft = ClipperLib::pftEvenOdd) const; diff --git a/include/settings/EnumSettings.h b/include/settings/EnumSettings.h index 650f6a06dc..a94403331e 100644 --- a/include/settings/EnumSettings.h +++ b/include/settings/EnumSettings.h @@ -70,6 +70,7 @@ enum class EZSeamType SHORTEST, USER_SPECIFIED, SHARPEST_CORNER, + INTERNAL_SPECIFIED, /* The 'Skirt/brim' type behaves like shortest, except it doesn't try to do tie-breaking for similar locations to * the last attempt, as that gives a different result when the seams are next to each other instead of on top. diff --git a/include/sliceDataStorage.h b/include/sliceDataStorage.h index 4b434aa5ae..cadb2b3bda 100644 --- a/include/sliceDataStorage.h +++ b/include/sliceDataStorage.h @@ -209,45 +209,82 @@ class SliceLayer class SupportLayer { public: + enum class PartsFilter{NoFilter, RegularParts, FractionalParts}; std::vector support_infill_parts; //!< a list of support infill parts Shape support_bottom; //!< Piece of support below the support and above the model. This must not overlap with any of the support_infill_parts or support_roof. - Shape support_roof; //!< Piece of support above the support and below the model. This must not overlap with any of the support_infill_parts or support_bottom. - // NOTE: This is _all_ of the support_roof, and as such, overlaps with support_fractional_roof! - Shape support_fractional_roof; //!< If the support distance is not exactly a multiple of the layer height, - // the first part of support just underneath the model needs to be printed at a fracional layer height. + std::vector support_roof; //!< Piece of support above the support and below the model. This must not overlap with any of the support_infill_parts or support_bottom. Shape support_mesh_drop_down; //!< Areas from support meshes which should be supported by more support Shape support_mesh; //!< Areas from support meshes which should NOT be supported by more support - Shape anti_overhang; //!< Areas where no overhang should be detected. + + Shape getTotalAreaFromParts(const std::vector& parts, PartsFilter filter = PartsFilter::NoFilter) const + { + Shape result; + for (const SupportInfillPart& part : parts) + { + if(filter == PartsFilter::NoFilter || + (part.use_fractional_config_ && filter == PartsFilter::FractionalParts) || + (!part.use_fractional_config_ && filter == PartsFilter::RegularParts)) + { + result.push_back(part.outline_); + } + } + return result.unionPolygons(); + } /*! * Exclude the given polygons from the support infill areas and update the SupportInfillParts. * + * \param parts[in,out] The vector of support parts from which the excluded polygons should be removed from. * \param exclude_polygons The polygons to exclude * \param exclude_polygons_boundary_box The boundary box for the polygons to exclude */ - void excludeAreasFromSupportInfillAreas(const Shape& exclude_polygons, const AABB& exclude_polygons_boundary_box); + void excludeAreasFromSupportInfillAreas(std::vector& parts, const Shape& exclude_polygons, const AABB& exclude_polygons_boundary_box); /* Fill up the infill parts for the support with the given support polygons. The support polygons will be split into parts. * * \param area The support polygon to fill up with infill parts. - * \param support_fill_per_layer The support polygons to fill up with infill parts. * \param support_line_width Line width of the support extrusions. * \param wall_line_count Wall-line count around the fill. * \param use_fractional_config (optional, default to false) If the area should be added as fractional support. * \param unionAll (optional, default to false) Wether to 'union all' for the split into parts bit. * \param custom_line_distance (optional, default to 0) Distance between lines of the infill pattern. custom_line_distance of 0 means use the default instead. + * \param custom_pattern (optional, default to EFillMethod::NONE) Set if a non default infill pattern should be used */ - void fillInfillParts( - const Shape& area, - const coord_t support_line_width, - const coord_t wall_line_count, - const bool use_fractional_config = false, - const bool unionAll = false, - const coord_t custom_line_distance = 0) + void fillInfillParts(const Shape& area, + const coord_t support_line_width, + const coord_t wall_line_count, + const bool use_fractional_config = false, + const bool unionAll = false, + const coord_t custom_line_distance = 0, + EFillMethod custom_pattern = EFillMethod::NONE) + { + for (const SingleShape& island_outline : area.splitIntoParts(unionAll)) + { + support_infill_parts.emplace_back(island_outline, support_line_width, use_fractional_config, wall_line_count, custom_line_distance, custom_pattern); + } + } + + /* Fill up the roof parts for the support with the given support polygons. The support polygons will be split into parts. + * + * \param area The support polygon to fill up with infill parts. + * \param support_line_width Line width of the support extrusions. + * \param wall_line_count Wall-line count around the fill. + * \param use_fractional_config (optional, default to false) If the area should be added as fractional support. + * \param unionAll (optional, default to false) Wether to 'union all' for the split into parts bit. + * \param custom_line_distance (optional, default to 0) Distance between lines of the infill pattern. custom_line_distance of 0 means use the default instead. + * \param custom_pattern (optional, default to EFillMethod::NONE) Set if a non default infill pattern should be used + */ + void fillRoofParts(const Shape& area, + const coord_t support_line_width, + const coord_t wall_line_count, + const bool use_fractional_config = false, + const bool unionAll = false, + const coord_t custom_line_distance = 0, + EFillMethod custom_pattern = EFillMethod::NONE) { for (const SingleShape& island_outline : area.splitIntoParts(unionAll)) { - support_infill_parts.emplace_back(island_outline, support_line_width, use_fractional_config, wall_line_count, custom_line_distance); + support_roof.emplace_back(island_outline, support_line_width, use_fractional_config, wall_line_count, custom_line_distance, custom_pattern); } } @@ -263,6 +300,7 @@ class SupportLayer * \param grow_layer_above (optional, default to 0) In cases where support shrinks per layer up, an appropriate offset may be nescesary. * \param unionAll (optional, default to false) Wether to 'union all' for the split into parts bit. * \param custom_line_distance (optional, default to 0) Distance between lines of the infill pattern. custom_line_distance of 0 means use the default instead. + * \param custom_pattern (optional, default to EFillMethod::NONE) Set if a non default infill pattern should be used */ void fillInfillParts( const LayerIndex layer_nr, @@ -273,7 +311,53 @@ class SupportLayer const coord_t wall_line_count, const coord_t grow_layer_above = 0, const bool unionAll = false, - const coord_t custom_line_distance = 0); + const coord_t custom_line_distance = 0, + EFillMethod custom_pattern = EFillMethod::NONE); + +}; + +class SupportGenerationModifier +{ + bool is_anti_support_; + bool is_cradle_modifier_; + bool is_anti_overhang_; + +public: + SupportGenerationModifier(Settings settings, size_t size) + : settings_(settings) + , areas_(size) + , is_anti_support_(settings.get("anti_support_mesh")) + , is_cradle_modifier_(settings.get("cradle_modifier_mesh")) + , is_anti_overhang_(! is_anti_support_ && ! is_cradle_modifier_ && settings.get("anti_overhang_mesh")) + { + } + + Settings settings_; + std::vector areas_; + + bool isAntiOverhang() const + { + return is_anti_overhang_; + } + + bool isAntiSupport() const + { + return is_anti_support_; + } + + bool isCradleModifier() const + { + return is_cradle_modifier_; + } + + void addArea(const Shape& area, LayerIndex layer_idx) + { + if (areas_.size() <= layer_idx) + { + areas_.resize(layer_idx + 1); + } + areas_[layer_idx].push_back(area); + } }; class SupportStorage @@ -289,6 +373,7 @@ class SupportStorage std::vector support_bottom_angles; //!< a list of angle values which is cycled through to determine the infill angle of each layer std::vector supportLayers; + std::vector supportGenerationModifiers; std::shared_ptr cross_fill_provider; //!< the fractal pattern for the cross (3d) filling pattern SupportStorage(); diff --git a/include/support.h b/include/support.h index 0d6eaeffa6..98e0b53d14 100644 --- a/include/support.h +++ b/include/support.h @@ -127,6 +127,15 @@ class AreaSupport */ static void generateOverhangAreasForMesh(SliceDataStorage& storage, SliceMeshStorage& mesh); + /*! + * \brief Generate cradles for pointy overhangs + * + * \param storage[in,out] Data storage containing the mesh object and used to store cradles as support. + * \param mesh_idx[in] The index of the mesh object for which to generate cradles. + * \param cradle_overhang_reserved_areas[out] Areas that need to be supported to support generated cradles. + */ + static void generateCradlesForMesh(SliceDataStorage& storage, size_t mesh_idx, std::vector& cradle_overhang_reserved_areas); + /*! * \brief Generate support polygons over all layers for one object. * @@ -148,6 +157,7 @@ class AreaSupport * the support. * \param bottom_settings The settings base to get the bottom interface of * the support. + * \param cradle_overhang_reserved_areas[in] Overhang from cradle areas, required to support said cradles. * \param mesh_idx The index of the object for which to generate support * areas. * \param layer_count Total number of layers. @@ -157,6 +167,7 @@ class AreaSupport const Settings& infill_settings, const Settings& roof_settings, const Settings& bottom_settings, + const std::vector& cradle_overhang_reserved_areas, const size_t mesh_idx, const size_t layer_count, std::vector& support_areas); @@ -185,9 +196,10 @@ class AreaSupport * \param storage Where to find the previously generated support areas and * where to output the new support roof areas. * \param mesh The mesh to generate support roof for. + * \param global_handled_by_cradle_areas_per_layer[in] Areas that are supported by cradles, to be excluded from roof generation. * \param global_support_areas_per_layer the global support areas on each layer. */ - static void generateSupportRoof(SliceDataStorage& storage, const SliceMeshStorage& mesh, std::vector& global_support_areas_per_layer); + static void generateSupportRoof(SliceDataStorage& storage, const SliceMeshStorage& mesh, std::vector& global_support_areas_per_layer, std::vector& global_handled_by_cradle_areas_per_layer); /*! * \brief Generate a single layer of support interface. diff --git a/include/utils/AABB.h b/include/utils/AABB.h index e3a93c678b..f51802042d 100644 --- a/include/utils/AABB.h +++ b/include/utils/AABB.h @@ -11,6 +11,7 @@ namespace cura class Polygon; class Shape; +class Polyline; /* Axis aligned boundary box */ class AABB @@ -22,9 +23,12 @@ class AABB AABB(const Point2LL& min, const Point2LL& max); //!< initializes with given min and max AABB(const Shape& shape); //!< Computes the boundary box for the given shape AABB(const Polygon& poly); //!< Computes the boundary box for the given polygons + AABB(const Polyline& line); //!< Computes the boundary box for the given polyline void calculate(const Shape& shape); //!< Calculates the aabb for the given shape (throws away old min and max data of this aabb) void calculate(const Polygon& poly); //!< Calculates the aabb for the given polygon (throws away old min and max data of this aabb) + void calculate(const Polyline& poly); //!< Calculates the aabb for the given polyline (throws away old min and max data of this aabb) + /*! * Whether the bounding box contains the specified point. diff --git a/include/utils/ThreadPool.h b/include/utils/ThreadPool.h index ed8db06c92..456e72f8d7 100644 --- a/include/utils/ThreadPool.h +++ b/include/utils/ThreadPool.h @@ -82,6 +82,8 @@ class ThreadPool } } + bool isInPool(); + private: void worker(); @@ -130,9 +132,27 @@ void parallel_for(T first, T last, F&& loop_body, size_t chunk_size_factor = 1, { return; } - const size_t nitems = dist; + // No need to move the work to another thread if there is only work for one thread. + if (dist <= 1) + { + loop_body(first); + return; + } ThreadPool* const thread_pool = Application::getInstance().thread_pool_; + + // This implementation does not really play nice if called nested, so lets not do that. + if (thread_pool->isInPool()) + { + for (T val = first; val < last; val++) + { + loop_body(val); + } + return; + } + + const size_t nitems = dist; + assert(thread_pool); const size_t nworkers = thread_pool->thread_count() + 1; // One task per std::thread + 1 for main thread diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 669bbfd5d8..2cf7ed25ed 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2604,10 +2604,11 @@ bool FffGcodeWriter::processInsets( if (! support_layer.support_roof.empty()) { - AABB support_roof_bb(support_layer.support_roof); + Shape roof = support_layer.getTotalAreaFromParts(support_layer.support_roof); + AABB support_roof_bb(roof); if (boundaryBox.hit(support_roof_bb)) { - outlines_below.push_back(support_layer.support_roof); + outlines_below.push_back(roof); } } else @@ -3021,10 +3022,11 @@ void FffGcodeWriter::processTopBottom( if (! support_layer->support_roof.empty()) { - AABB support_roof_bb(support_layer->support_roof); + Shape roofs = support_layer->getTotalAreaFromParts(support_layer->support_roof); + AABB support_roof_bb(roofs); if (skin_bb.hit(support_roof_bb)) { - supported = ! skin_part.skin_fill.intersection(support_layer->support_roof).empty(); + supported = ! skin_part.skin_fill.intersection(roofs).empty(); } } else @@ -3301,20 +3303,12 @@ bool FffGcodeWriter::addSupportToGCode(const SliceDataStorage& storage, LayerPla if (extruder_nr == support_roof_extruder_nr) { - support_added |= addSupportRoofsToGCode(storage, support_layer.support_fractional_roof, gcode_layer.configs_storage_.support_fractional_roof_config, gcode_layer); + support_added |= addSupportRoofsToGCode(storage, support_layer.support_roof, gcode_layer); } if (extruder_nr == support_infill_extruder_nr) { support_added |= processSupportInfill(storage, gcode_layer); } - if (extruder_nr == support_roof_extruder_nr) - { - support_added |= addSupportRoofsToGCode( - storage, - support_layer.support_roof.difference(support_layer.support_fractional_roof), - gcode_layer.configs_storage_.support_roof_config, - gcode_layer); - } if (extruder_nr == support_bottom_extruder_nr) { support_added |= addSupportBottomsToGCode(storage, gcode_layer); @@ -3431,8 +3425,10 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer 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); + const ZSeamConfig z_seam_config(part.start_near_location.has_value() ? EZSeamType::INTERNAL_SPECIFIED : EZSeamType::SHORTEST, + part.start_near_location.has_value() ? part.start_near_location.value() : 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") && (layer_nr >= 0)) { @@ -3517,7 +3513,7 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer constexpr bool skip_stitching = false; const bool fill_gaps = density_idx == 0; // Only fill gaps for one of the densities. Infill infill_comp( - support_pattern, + part.custom_line_pattern_ == EFillMethod::NONE ? support_pattern : part.custom_line_pattern_, zig_zaggify_infill, connect_polygons, area, @@ -3586,7 +3582,6 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer constexpr bool spiralize = false; constexpr Ratio flow_ratio = 1.0_r; constexpr bool always_retract = false; - const std::optional start_near_location = std::optional(); gcode_layer.addPolygonsByOptimizer( support_polygons, @@ -3597,7 +3592,7 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer flow_ratio, always_retract, alternate_layer_print_direction, - start_near_location); + part.start_near_location); added_something = true; } @@ -3606,7 +3601,6 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer constexpr bool enable_travel_optimization = false; constexpr coord_t wipe_dist = 0; constexpr Ratio flow_ratio = 1.0; - const std::optional near_start_location = std::optional(); constexpr double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT; gcode_layer.addLinesByOptimizer( @@ -3616,7 +3610,7 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer enable_travel_optimization, wipe_dist, flow_ratio, - near_start_location, + part.start_near_location, fan_speed, alternate_layer_print_direction); @@ -3665,8 +3659,10 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer } -bool FffGcodeWriter::addSupportRoofsToGCode(const SliceDataStorage& storage, const Shape& support_roof_outlines, const GCodePathConfig& current_roof_config, LayerPlan& gcode_layer) - const +bool FffGcodeWriter::addSupportRoofsToGCode( + const SliceDataStorage& storage, + const std::vector& support_roof_outlines, + LayerPlan& gcode_layer) const { const SupportLayer& support_layer = storage.support.supportLayers[std::max(LayerIndex{ 0 }, gcode_layer.getLayerNr())]; @@ -3678,124 +3674,150 @@ bool FffGcodeWriter::addSupportRoofsToGCode(const SliceDataStorage& storage, con const size_t roof_extruder_nr = Application::getInstance().current_slice_->scene.current_mesh_group->settings.get("support_roof_extruder_nr").extruder_nr_; const ExtruderTrain& roof_extruder = Application::getInstance().current_slice_->scene.extruders[roof_extruder_nr]; - const EFillMethod pattern = roof_extruder.settings_.get("support_roof_pattern"); - AngleDegrees fill_angle = 0; - if (! storage.support.support_roof_angles.empty()) + bool added_support = false; + + for(bool fractional : {true,false}) // Add all fractional roofs first. { - // handle negative layer numbers - int divisor = static_cast(storage.support.support_roof_angles.size()); - int index = ((gcode_layer.getLayerNr() % divisor) + divisor) % divisor; - fill_angle = storage.support.support_roof_angles.at(index); - } - const bool zig_zaggify_infill = pattern == EFillMethod::ZIG_ZAG; - const bool connect_polygons = false; // connections might happen in mid air in between the infill lines - constexpr coord_t support_roof_overlap = 0; // the roofs should never be expanded outwards - constexpr size_t infill_multiplier = 1; - constexpr coord_t extra_infill_shift = 0; - const auto wall_line_count = roof_extruder.settings_.get("support_roof_wall_count"); - const coord_t small_area_width = roof_extruder.settings_.get("min_even_wall_line_width") * 2; // Maximum width of a region that can still be filled with one wall. - const Point2LL infill_origin; - constexpr bool skip_stitching = false; - constexpr bool fill_gaps = true; - constexpr bool use_endpieces = true; - constexpr bool connected_zigzags = false; - constexpr bool skip_some_zags = false; - constexpr size_t zag_skip_count = 0; - constexpr coord_t pocket_size = 0; - const coord_t max_resolution = roof_extruder.settings_.get("meshfix_maximum_resolution"); - const coord_t max_deviation = roof_extruder.settings_.get("meshfix_maximum_deviation"); + for(const SupportInfillPart& roof_part: support_roof_outlines) + { - coord_t support_roof_line_distance = roof_extruder.settings_.get("support_roof_line_distance"); - const coord_t support_roof_line_width = roof_extruder.settings_.get("support_roof_line_width"); - if (gcode_layer.getLayerNr() == 0 && support_roof_line_distance < 2 * support_roof_line_width) - { // if roof is dense - support_roof_line_distance *= roof_extruder.settings_.get("initial_layer_line_width_factor"); - } + if(roof_part.use_fractional_config_ != fractional) + { + continue; + } - Shape infill_outline = support_roof_outlines; - Shape wall; - // make sure there is a wall if this is on the first layer - if (gcode_layer.getLayerNr() == 0) - { - wall = support_roof_outlines.offset(-support_roof_line_width / 2); - infill_outline = wall.offset(-support_roof_line_width / 2); - } - infill_outline = Simplify(roof_extruder.settings_).polygon(infill_outline); + const GCodePathConfig& current_roof_config = roof_part.use_fractional_config_ ? + gcode_layer.configs_storage_.support_fractional_roof_config : + gcode_layer.configs_storage_.support_roof_config; + const EFillMethod pattern = roof_part.custom_line_pattern_ == EFillMethod::NONE ? roof_extruder.settings_.get("support_roof_pattern") : roof_part.custom_line_pattern_ ; + AngleDegrees fill_angle = 0; + if (! storage.support.support_roof_angles.empty()) + { + // handle negative layer numbers + int divisor = static_cast(storage.support.support_roof_angles.size()); + int index = ((gcode_layer.getLayerNr() % divisor) + divisor) % divisor; + fill_angle = storage.support.support_roof_angles.at(index); + } + const bool zig_zaggify_infill = pattern == EFillMethod::ZIG_ZAG; + const bool connect_polygons = false; // connections might happen in mid air in between the infill lines + constexpr coord_t support_roof_overlap = 0; // the roofs should never be expanded outwards + constexpr size_t infill_multiplier = 1; + constexpr coord_t extra_infill_shift = 0; + const auto wall_line_count = roof_part.inset_count_to_generate_; + const coord_t small_area_width = roof_extruder.settings_.get("min_even_wall_line_width") * 2; // Maximum width of a region that can still be filled with one wall. + const Point2LL infill_origin; + constexpr bool skip_stitching = false; + constexpr bool fill_gaps = true; + constexpr bool use_endpieces = true; + constexpr bool connected_zigzags = false; + constexpr bool skip_some_zags = false; + constexpr size_t zag_skip_count = 0; + constexpr coord_t pocket_size = 0; + const coord_t max_resolution = roof_extruder.settings_.get("meshfix_maximum_resolution"); + const coord_t max_deviation = roof_extruder.settings_.get("meshfix_maximum_deviation"); + const ZSeamConfig z_seam_config(roof_part.start_near_location.has_value() ? EZSeamType::INTERNAL_SPECIFIED : EZSeamType::SHORTEST, + roof_part.start_near_location.has_value() ? roof_part.start_near_location.value() : gcode_layer.getLastPlannedPositionOrStartingPosition(), + EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE, + false); + + coord_t support_roof_line_distance = roof_part.custom_line_distance_ == 0 ? roof_extruder.settings_.get("support_roof_line_distance") : roof_part.custom_line_distance_; + const coord_t support_roof_line_width = roof_extruder.settings_.get("support_roof_line_width"); + if (gcode_layer.getLayerNr() == 0 && support_roof_line_distance < 2 * support_roof_line_width) + { // if roof is dense + support_roof_line_distance *= roof_extruder.settings_.get("initial_layer_line_width_factor"); + } - Infill roof_computation( - pattern, - zig_zaggify_infill, - connect_polygons, - infill_outline, - current_roof_config.getLineWidth(), - support_roof_line_distance, - support_roof_overlap, - infill_multiplier, - fill_angle, - gcode_layer.z_ + current_roof_config.z_offset, - extra_infill_shift, - max_resolution, - max_deviation, - wall_line_count, - small_area_width, - infill_origin, - skip_stitching, - fill_gaps, - connected_zigzags, - use_endpieces, - skip_some_zags, - zag_skip_count, - pocket_size); - Shape roof_polygons; - std::vector roof_paths; - OpenLinesSet roof_lines; - roof_computation.generate(roof_paths, roof_polygons, roof_lines, roof_extruder.settings_, gcode_layer.getLayerNr(), SectionType::SUPPORT); - if ((gcode_layer.getLayerNr() == 0 && wall.empty()) || (gcode_layer.getLayerNr() > 0 && roof_paths.empty() && roof_polygons.empty() && roof_lines.empty())) - { - return false; // We didn't create any support roof. - } - gcode_layer.setIsInside(false); // going to print stuff outside print object, i.e. support - if (gcode_layer.getLayerNr() == 0) - { - gcode_layer.addPolygonsByOptimizer(wall, current_roof_config); - } - if (! roof_polygons.empty()) - { - constexpr bool force_comb_retract = false; - gcode_layer.addTravel(roof_polygons[0][0], force_comb_retract); - gcode_layer.addPolygonsByOptimizer(roof_polygons, current_roof_config); - } - if (! roof_paths.empty()) - { - const GCodePathConfig& config = current_roof_config; - constexpr bool retract_before_outer_wall = false; - constexpr coord_t wipe_dist = 0; - const ZSeamConfig z_seam_config(EZSeamType::SHORTEST, gcode_layer.getLastPlannedPositionOrStartingPosition(), EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE, false); + Shape infill_outline = roof_part.getInfillArea(); + Shape wall; + // make sure there is a wall if this is on the first layer + if (gcode_layer.getLayerNr() == 0) + { + wall = infill_outline.offset(-support_roof_line_width / 2); + infill_outline = wall.offset(-support_roof_line_width / 2); + } + infill_outline = Simplify(roof_extruder.settings_).polygon(infill_outline); - InsetOrderOptimizer wall_orderer( - *this, - storage, - gcode_layer, - roof_extruder.settings_, - roof_extruder_nr, - config, - config, - config, - config, - config, - config, - retract_before_outer_wall, - wipe_dist, - wipe_dist, - roof_extruder_nr, - roof_extruder_nr, - z_seam_config, - roof_paths, - storage.getModelBoundingBox().flatten().getMiddle()); - wall_orderer.addToLayer(); + Infill roof_computation( + pattern, + zig_zaggify_infill, + connect_polygons, + infill_outline, + current_roof_config.getLineWidth(), + support_roof_line_distance, + support_roof_overlap, + infill_multiplier, + fill_angle, + gcode_layer.z_ + current_roof_config.z_offset, + extra_infill_shift, + max_resolution, + max_deviation, + wall_line_count, + small_area_width, + infill_origin, + skip_stitching, + fill_gaps, + connected_zigzags, + use_endpieces, + skip_some_zags, + zag_skip_count, + pocket_size); + Shape roof_polygons; + std::vector roof_paths; + OpenLinesSet roof_lines; + roof_computation.generate(roof_paths, roof_polygons, roof_lines, roof_extruder.settings_, gcode_layer.getLayerNr(), SectionType::SUPPORT); + + if ((gcode_layer.getLayerNr() == 0 && wall.empty()) || (gcode_layer.getLayerNr() > 0 && roof_paths.empty() && roof_polygons.empty() && roof_lines.empty())) + { + // We didn't create any support roof. + continue; + } + added_support = true; + + gcode_layer.setIsInside(false); // going to print stuff outside print object, i.e. support + if (gcode_layer.getLayerNr() == 0) + { + gcode_layer.addPolygonsByOptimizer(wall, current_roof_config, z_seam_config); + } + if (! roof_polygons.empty()) + { + constexpr bool force_comb_retract = false; + gcode_layer.addTravel(roof_polygons[0][0], force_comb_retract); + gcode_layer.addPolygonsByOptimizer(roof_polygons, current_roof_config, z_seam_config); + } + if (! roof_paths.empty()) + { + const GCodePathConfig& config = current_roof_config; + constexpr bool retract_before_outer_wall = false; + constexpr coord_t wipe_dist = 0; + + InsetOrderOptimizer wall_orderer( + *this, + storage, + gcode_layer, + roof_extruder.settings_, + roof_extruder_nr, + config, + config, + config, + config, + config, + config, + retract_before_outer_wall, + wipe_dist, + wipe_dist, + roof_extruder_nr, + roof_extruder_nr, + z_seam_config, + roof_paths, + storage.getModelBoundingBox().flatten().getMiddle());wall_orderer.addToLayer(); + } + constexpr bool enable_travel_optimization = false; + constexpr coord_t wipe_dist = 0; + constexpr Ratio flow_ratio = 1.0; + gcode_layer.addLinesByOptimizer(roof_lines, current_roof_config, (pattern == EFillMethod::ZIG_ZAG) ? SpaceFillType::PolyLines : SpaceFillType::Lines, enable_travel_optimization, wipe_dist, flow_ratio, roof_part.start_near_location); + } } - gcode_layer.addLinesByOptimizer(roof_lines, current_roof_config, (pattern == EFillMethod::ZIG_ZAG) ? SpaceFillType::PolyLines : SpaceFillType::Lines); - return true; + return added_support; } bool FffGcodeWriter::addSupportBottomsToGCode(const SliceDataStorage& storage, LayerPlan& gcode_layer) const diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index ad036f62b9..581933e125 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -183,6 +183,9 @@ std::optional InsetOrderOptimizer::insertSeamPoint(ExtrusionLine& closed case EZSeamType::USER_SPECIFIED: request_point = z_seam_config_.pos_; break; + case EZSeamType::INTERNAL_SPECIFIED: + request_point = z_seam_config_.pos_; + break; case EZSeamType::SHORTEST: request_point = gcode_layer_.getLastPlannedPositionOrStartingPosition(); break; @@ -195,7 +198,7 @@ std::optional InsetOrderOptimizer::insertSeamPoint(ExtrusionLine& closed size_t closest_junction_idx = 0; coord_t closest_distance_sqd = std::numeric_limits::max(); bool should_reclaculate_closest = false; - if (z_seam_config_.type_ == EZSeamType::USER_SPECIFIED) + if (z_seam_config_.type_ == EZSeamType::USER_SPECIFIED || z_seam_config_.type_ == EZSeamType::INTERNAL_SPECIFIED) { // For user-defined seams you usually don't _actually_ want the _closest_ point, per-se, // since you want the seam-line to be continuous in 3D space. diff --git a/src/PrimeTower.cpp b/src/PrimeTower.cpp index bc4e2a07a7..750e93795a 100644 --- a/src/PrimeTower.cpp +++ b/src/PrimeTower.cpp @@ -585,7 +585,7 @@ void PrimeTower::subtractFromSupport(SliceDataStorage& storage) AABB outside_polygon_boundary_box(outside_polygon); SupportLayer& support_layer = storage.support.supportLayers[layer]; // take the differences of the support infill parts and the prime tower area - support_layer.excludeAreasFromSupportInfillAreas(outside_polygon, outside_polygon_boundary_box); + support_layer.excludeAreasFromSupportParts(support_layer.support_infill_parts, outside_polygon, outside_polygon_boundary_box); } } diff --git a/src/PrimeTower/PrimeTower.cpp b/src/PrimeTower/PrimeTower.cpp index 074d1f501a..63d45ef8f1 100644 --- a/src/PrimeTower/PrimeTower.cpp +++ b/src/PrimeTower/PrimeTower.cpp @@ -291,7 +291,8 @@ void PrimeTower::subtractFromSupport(SliceDataStorage& storage) 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); + support_layer.excludeAreasFromSupportInfillAreas(support_layer.support_infill_parts, Shape(outside_polygon), outside_polygon_boundary_box); + support_layer.excludeAreasFromSupportInfillAreas(support_layer.support_roof, Shape(outside_polygon), outside_polygon_boundary_box); } } diff --git a/src/SkirtBrim.cpp b/src/SkirtBrim.cpp index ba399fb721..401feb904e 100644 --- a/src/SkirtBrim.cpp +++ b/src/SkirtBrim.cpp @@ -386,18 +386,15 @@ Shape SkirtBrim::getFirstLayerOutline(const int extruder_nr /* = -1 */) } AABB model_brim_covered_area_boundary_box(model_brim_covered_area); - support_layer.excludeAreasFromSupportInfillAreas(model_brim_covered_area, model_brim_covered_area_boundary_box); + support_layer.excludeAreasFromSupportInfillAreas(support_layer.support_infill_parts, model_brim_covered_area, model_brim_covered_area_boundary_box); // If the gap between the model and the BP is small enough, support starts with the interface instead, so remove it there as well: - support_layer.support_roof = support_layer.support_roof.difference(model_brim_covered_area); + support_layer.excludeAreasFromSupportInfillAreas(support_layer.support_roof, model_brim_covered_area, model_brim_covered_area_boundary_box); } - for (const SupportInfillPart& support_infill_part : support_layer.support_infill_parts) - { - first_layer_outline.push_back(support_infill_part.outline_); - } + first_layer_outline.push_back(support_layer.getTotalAreaFromParts(support_layer.support_infill_parts)); first_layer_outline.push_back(support_layer.support_bottom); - first_layer_outline.push_back(support_layer.support_roof); + first_layer_outline.push_back(support_layer.getTotalAreaFromParts(support_layer.support_roof)); } } constexpr coord_t join_distance = 20; @@ -677,7 +674,7 @@ void SkirtBrim::generateSupportBrim() support_outline.push_back(part.outline_); } const Shape brim_area = support_outline.difference(support_outline.offset(-brim_width)); - support_layer.excludeAreasFromSupportInfillAreas(brim_area, AABB(brim_area)); + support_layer.excludeAreasFromSupportInfillAreas(support_layer.support_infill_parts, brim_area, AABB(brim_area)); coord_t offset_distance = brim_line_width / 2; for (size_t skirt_brim_number = 0; skirt_brim_number < line_count; skirt_brim_number++) diff --git a/src/SupportInfillPart.cpp b/src/SupportInfillPart.cpp index fc8e788a81..9ab33d8ca2 100644 --- a/src/SupportInfillPart.cpp +++ b/src/SupportInfillPart.cpp @@ -8,13 +8,16 @@ using namespace cura; -SupportInfillPart::SupportInfillPart(const SingleShape& outline, coord_t support_line_width, bool use_fractional_config, int inset_count_to_generate, coord_t custom_line_distance) +SupportInfillPart::SupportInfillPart(const SingleShape& outline, coord_t support_line_width, bool use_fractional_config, int inset_count_to_generate, coord_t custom_line_distance, EFillMethod custom_line_pattern, std::optional start_near_location) : outline_(outline) , outline_boundary_box_(outline) , support_line_width_(support_line_width) , inset_count_to_generate_(inset_count_to_generate) , custom_line_distance_(custom_line_distance) + , custom_line_pattern_(custom_line_pattern) , use_fractional_config_(use_fractional_config) + , start_near_location(start_near_location) + { infill_area_per_combine_per_density_.clear(); } diff --git a/src/TreeModelVolumes.cpp b/src/TreeModelVolumes.cpp index 3dade07b66..442c03192f 100644 --- a/src/TreeModelVolumes.cpp +++ b/src/TreeModelVolumes.cpp @@ -24,6 +24,7 @@ TreeModelVolumes::TreeModelVolumes( const coord_t max_move, const coord_t max_move_slow, const coord_t min_offset_per_step, + const coord_t min_radius, size_t current_mesh_idx, double progress_multiplier, double progress_offset, @@ -33,10 +34,12 @@ TreeModelVolumes::TreeModelVolumes( max_move_slow_{ std::max(max_move_slow - 2, coord_t(0)) } , // -2 to avoid rounding errors min_offset_per_step_{ min_offset_per_step } + , radius_0_(min_radius) , progress_multiplier_{ progress_multiplier } , progress_offset_{ progress_offset } , machine_border_{ calculateMachineBorderCollision(storage.getMachineBorder()) } , machine_area_{ storage.getMachineBorder() } + , first_anti_preferred_layer_idx_{ storage.support.supportLayers.size() } { anti_overhang_ = std::vector(storage.support.supportLayers.size(), Shape()); std::unordered_map mesh_to_layeroutline_idx; @@ -69,17 +72,21 @@ TreeModelVolumes::TreeModelVolumes( } } + // Figure out the rest of the setting(-like variable)s relevant to the class a whole. + current_outline_idx_ = mesh_to_layeroutline_idx[current_mesh_idx]; + const TreeSupportSettings config(layer_outlines_[current_outline_idx_].first); + for (const auto data_pair : layer_outlines_) { support_rests_on_model_ |= data_pair.first.get("support_type") == ESupportType::EVERYWHERE; min_maximum_deviation = std::min(min_maximum_deviation, data_pair.first.get("meshfix_maximum_deviation")); min_maximum_resolution = std::min(min_maximum_resolution, data_pair.first.get("meshfix_maximum_resolution")); min_maximum_area_deviation = std::min(min_maximum_area_deviation, data_pair.first.get("meshfix_maximum_extrusion_area_deviation")); + max_cradle_dtt = std::max(max_cradle_dtt, config.tip_layers); // todo better estimation } // Figure out the rest of the setting(-like variable)s relevant to the class a whole. current_outline_idx_ = mesh_to_layeroutline_idx[current_mesh_idx]; - const TreeSupportSettings config(layer_outlines_[current_outline_idx_].first); if (config.support_overrides == SupportDistPriority::Z_OVERRIDES_XY) { @@ -143,10 +150,12 @@ TreeModelVolumes::TreeModelVolumes( { anti_overhang_[layer_idx].push_back(additional_excluded_areas[layer_idx]); } - - if (SUPPORT_TREE_AVOID_SUPPORT_BLOCKER) + for (const SupportGenerationModifier& support_modifier : storage.support.supportGenerationModifiers) { - anti_overhang_[layer_idx].push_back(storage.support.supportLayers[layer_idx].anti_overhang); + if (support_modifier.isAntiSupport() && layer_idx < support_modifier.areas_.size()) + { + anti_overhang_[layer_idx].push_back(support_modifier.areas_[layer_idx]); + } } if (storage.prime_tower_) @@ -165,12 +174,11 @@ TreeModelVolumes::TreeModelVolumes( } // Cache some handy settings in the object itself. - radius_0_ = config.getRadius(0); support_rest_preference_ = config.support_rest_preference; simplifier_ = Simplify(min_maximum_resolution, min_maximum_deviation, min_maximum_area_deviation); } -void TreeModelVolumes::precalculate(coord_t max_layer) +void TreeModelVolumes::precalculate(LayerIndex max_layer) { const auto t_start = std::chrono::high_resolution_clock::now(); precalculated_ = true; @@ -204,7 +212,7 @@ void TreeModelVolumes::precalculate(coord_t max_layer) // but as for every branch going towards the bp, the to model avoidance is required to check for possible merges with to model branches, this assumption is in-fact wrong. std::unordered_map radius_until_layer; // while it is possible to calculate, up to which layer the avoidance should be calculated, this simulation is easier to understand, and does not need to be adjusted if - // something of the radius calculation is changed. Tested overhead was neligable (milliseconds for thounds of layers). + // something of the radius calculation is changed. Tested overhead was negligible (milliseconds for thousands of layers). for (LayerIndex simulated_dtt = 0; simulated_dtt <= max_layer; simulated_dtt++) { const LayerIndex current_layer = max_layer - simulated_dtt; @@ -213,15 +221,22 @@ void TreeModelVolumes::precalculate(coord_t max_layer) const coord_t max_initial_layer_diameter_radius = ceilRadius(config.recommendedMinRadius(current_layer) + current_min_xy_dist_delta_); if (! radius_until_layer.count(max_regular_radius)) { - radius_until_layer[max_regular_radius] = current_layer; + radius_until_layer[max_regular_radius] = std::min(current_layer + max_cradle_dtt, max_layer); } if (! radius_until_layer.count(max_min_radius)) { - radius_until_layer[max_min_radius] = current_layer; + radius_until_layer[max_min_radius] = std::min(current_layer + max_cradle_dtt, max_layer); } - if (! radius_until_layer.count(max_initial_layer_diameter_radius)) + + // all radiis between max_min_radius and max_initial_layer_diameter_radius can also occur + coord_t current_ceil_radius = max_min_radius; + while (current_ceil_radius < max_initial_layer_diameter_radius) { - radius_until_layer[max_initial_layer_diameter_radius] = current_layer; + current_ceil_radius = ceilRadius(current_ceil_radius + 1); + if (! radius_until_layer.count(current_ceil_radius)) + { + radius_until_layer[current_ceil_radius] = std::min(current_layer + max_cradle_dtt, max_layer); + } } } @@ -231,6 +246,7 @@ void TreeModelVolumes::precalculate(coord_t max_layer) std::deque relevant_avoidance_radiis_to_model; relevant_avoidance_radiis.insert(relevant_avoidance_radiis.end(), radius_until_layer.begin(), radius_until_layer.end()); relevant_avoidance_radiis_to_model.insert(relevant_avoidance_radiis_to_model.end(), radius_until_layer.begin(), radius_until_layer.end()); + precalculated_avoidance_radii.insert(precalculated_avoidance_radii.end(), radius_until_layer.begin(), radius_until_layer.end()); // Append additional radiis needed for collision. @@ -313,6 +329,8 @@ void TreeModelVolumes::precalculate(coord_t max_layer) t_colAvo = std::chrono::high_resolution_clock::now(); } + calculateFake0Avoidances(max_layer); + precalculation_finished_ = true; const auto dur_col = 0.001 * std::chrono::duration_cast(t_coll - t_start).count(); const auto dur_acc = 0.001 * std::chrono::duration_cast(t_acc - t_coll).count(); @@ -347,7 +365,7 @@ const Shape& TreeModelVolumes::getCollision(coord_t radius, LayerIndex layer_idx RadiusLayerPair key{ radius, layer_idx }; { - std::lock_guard critical_section_support_max_layer_nr(*critical_avoidance_cache_); + std::lock_guard critical_critical_collision_cache_(*critical_collision_cache_); result = getArea(collision_cache_, key); } if (result) @@ -358,7 +376,11 @@ const Shape& TreeModelVolumes::getCollision(coord_t radius, LayerIndex layer_idx { spdlog::warn("Had to calculate collision at radius {} and layer {}, but precalculate was called. Performance may suffer!", key.first, key.second); } - calculateCollision(key); + + { + std::lock_guard critical_section(*getLockForCalculation(radius, CalculationType::COLLISION)); + calculateCollision(key); + } return getCollision(orig_radius, layer_idx, min_xy_dist); } @@ -388,7 +410,10 @@ const Shape& TreeModelVolumes::getCollisionHolefree(coord_t radius, LayerIndex l { spdlog::warn("Had to calculate collision holefree at radius {} and layer {}, but precalculate was called. Performance may suffer!", key.first, key.second); } - calculateCollisionHolefree(key); + { + std::lock_guard critical_section(*getLockForCalculation(radius, CalculationType::COLLISION_HOLEFREE)); + calculateCollisionHolefree(key); + } return getCollisionHolefree(orig_radius, layer_idx, min_xy_dist); } @@ -401,7 +426,10 @@ const Shape& TreeModelVolumes::getAccumulatedPlaceable0(LayerIndex layer_idx) return accumulated_placeables_cache_radius_0_[layer_idx]; } } - calculateAccumulatedPlaceable0(layer_idx); + { + std::lock_guard critical_section(*getLockForCalculation(0, CalculationType::PLACEABLE_ACCUMULATED)); + calculateAccumulatedPlaceable0(layer_idx); + } return getAccumulatedPlaceable0(layer_idx); } @@ -475,16 +503,25 @@ const Shape& TreeModelVolumes::getAvoidance(coord_t radius, LayerIndex layer_idx key.second, coord_t(type)); } - if (type == AvoidanceType::COLLISION) + + if (orig_radius == 0) { + std::lock_guard critical_section(*getLockForCalculation(radius, CalculationType::AVOIDANCE_0)); + calculateFake0Avoidances(layer_idx); + } + else if (type == AvoidanceType::COLLISION) + { + std::lock_guard critical_section(*getLockForCalculation(radius, CalculationType::AVOIDANCE_COLLISION)); calculateCollisionAvoidance(key); } else if (to_model) { + std::lock_guard critical_section(*getLockForCalculation(radius, CalculationType::AVOIDANCE_TO_MODEL)); calculateAvoidanceToModel(key); } else { + std::lock_guard critical_section(*getLockForCalculation(radius, CalculationType::AVOIDANCE)); calculateAvoidance(key); } return getAvoidance(orig_radius, layer_idx, type, to_model, min_xy_dist); // retrive failed and correct result was calculated. Now it has to be retrived. @@ -496,7 +533,6 @@ const Shape& TreeModelVolumes::getPlaceableAreas(coord_t radius, LayerIndex laye const coord_t orig_radius = radius; radius = ceilRadius(radius); RadiusLayerPair key{ radius, layer_idx }; - { std::lock_guard critical_section(*critical_placeable_areas_cache_); result = getArea(placeable_areas_cache_, key); @@ -511,11 +547,13 @@ const Shape& TreeModelVolumes::getPlaceableAreas(coord_t radius, LayerIndex laye } if (radius != 0) { + std::lock_guard critical_section(*getLockForCalculation(radius, CalculationType::PLACEABLE)); calculatePlaceables(key); } else { - getCollision(0, layer_idx, true); + std::lock_guard critical_section(*getLockForCalculation(radius, CalculationType::COLLISION)); + calculateCollision(key); } return getPlaceableAreas(orig_radius, layer_idx); } @@ -550,10 +588,238 @@ const Shape& TreeModelVolumes::getWallRestriction(coord_t radius, LayerIndex lay spdlog::warn("Had to calculate Wall restrictions at radius {} and layer {}, but precalculate was called. Performance may suffer!", key.first, key.second); } - calculateWallRestrictions(key); + { + std::lock_guard critical_section(*getLockForCalculation(radius, CalculationType::WALL_RESTRICTION)); + calculateWallRestrictions(key); + } return getWallRestriction(orig_radius, layer_idx, min_xy_dist); // Retrieve failed and correct result was calculated. Now it has to be retrieved. } + +void TreeModelVolumes::addAreaToAntiPreferred(const Shape area, LayerIndex layer_idx) +{ + RadiusLayerPair key(0, layer_idx); + std::lock_guard critical_section(*critical_anti_preferred_); + anti_preferred_[key] = anti_preferred_[key].unionPolygons(area); + first_anti_preferred_layer_idx_ = std::min(first_anti_preferred_layer_idx_, layer_idx); +} + + +void TreeModelVolumes::precalculateAntiPreferred() +{ + const TreeSupportSettings config(layer_outlines_[current_outline_idx_].first); + + cura::parallel_for( + 0, + precalculated_avoidance_radii.size(), + [&, precalculated_avoidance_radiis = precalculated_avoidance_radii](const size_t key_idx) + { + const coord_t radius = precalculated_avoidance_radiis[key_idx].first; + const LayerIndex max_required_layer = precalculated_avoidance_radiis[key_idx].second; + + const coord_t max_step_move = std::max(1.9 * radius, current_min_xy_dist_ * 1.9); + RadiusLayerPair key(radius, 0); + + Shape latest_avoidance; + Shape latest_avoidance_to_model; + Shape latest_avoidance_collision; + + LayerIndex start_layer = 0; + std::vector> data(max_required_layer + 1, std::pair(RadiusLayerPair(radius, -1), Shape())); + std::vector> data_to_model(max_required_layer + 1, std::pair(RadiusLayerPair(radius, -1), Shape())); + std::vector> data_collision(max_required_layer + 1, std::pair(RadiusLayerPair(radius, -1), Shape())); + std::vector> data_raw_anti(max_required_layer + 1, std::pair(RadiusLayerPair(radius, -1), Shape())); + + bool encountered_anti = false; + // ### main loop doing the calculation + for (const LayerIndex layer : ranges::views::iota(static_cast(start_layer), max_required_layer + 1UL)) + { + key.second = layer; + RadiusLayerPair key_0(0, layer); + Shape anti; + { + std::lock_guard critical_section(*critical_anti_preferred_); + anti = anti_preferred_[key_0]; + } + + if (! encountered_anti && ! anti.empty()) + { + encountered_anti = true; + if (support_rest_preference_ == RestPreference::BUILDPLATE) + { + latest_avoidance = getAvoidance(radius, layer, AvoidanceType::FAST_SAFE, false, true); + } + if (support_rests_on_model_) + { + latest_avoidance_to_model = getAvoidance(radius, layer, AvoidanceType::FAST_SAFE, true, true); + } + if (max_layer_idx_without_blocker_ <= layer && support_rests_on_model_) + { + latest_avoidance_collision = getAvoidance(radius, layer, AvoidanceType::COLLISION, true, true); + } + + std::lock_guard critical_section(*critical_anti_preferred_); + first_anti_preferred_layer_idx_ = layer; + } + if (! encountered_anti) + { + continue; + } + + Shape col = getCollisionHolefree(radius, layer, true); + anti = anti.unionPolygons().offset(std::max(radius, config.branch_radius)).unionPolygons(); + data_raw_anti[layer] = std::pair(key, anti); + + if (support_rest_preference_ == RestPreference::BUILDPLATE) + { + latest_avoidance = safeOffset(latest_avoidance, -max_move_, ClipperLib::jtRound, -max_step_move, col.unionPolygons(anti)); + Shape next_latest_avoidance = simplifier_.polygon(latest_avoidance); + latest_avoidance = next_latest_avoidance.unionPolygons(latest_avoidance); + latest_avoidance = latest_avoidance.unionPolygons(getAvoidance(radius, layer, AvoidanceType::FAST_SAFE, false, true)); + data[layer] = std::pair(key, latest_avoidance); + } + + if (support_rests_on_model_) + { + latest_avoidance_to_model = safeOffset(latest_avoidance_to_model, -max_move_, ClipperLib::jtRound, -max_step_move, col.unionPolygons(anti)); + Shape next_latest_avoidance_to_model = simplifier_.polygon(latest_avoidance_to_model); + latest_avoidance_to_model = next_latest_avoidance_to_model.unionPolygons(latest_avoidance_to_model); + latest_avoidance_to_model = latest_avoidance_to_model.difference(getPlaceableAreas(radius, layer)); + latest_avoidance_to_model = latest_avoidance_to_model.unionPolygons(getAvoidance(radius, layer, AvoidanceType::FAST_SAFE, true, true)); + data_to_model[layer] = std::pair(key, latest_avoidance_to_model); + } + + if (max_layer_idx_without_blocker_ <= layer && support_rests_on_model_) + { + latest_avoidance_collision = safeOffset(latest_avoidance_collision, -max_move_, ClipperLib::jtRound, -max_step_move, col.unionPolygons(anti)); + Shape placeable0RadiusCompensated = getAccumulatedPlaceable0(layer).offset(-std::max(radius, increase_until_radius_), ClipperLib::jtRound); + latest_avoidance_collision = latest_avoidance_collision.difference(placeable0RadiusCompensated).unionPolygons(getCollision(radius, layer, true)); + latest_avoidance_collision = latest_avoidance_collision.unionPolygons(getAvoidance(radius, layer, AvoidanceType::COLLISION, true, true)); + data_collision[layer] = std::pair(key, latest_avoidance_collision); + } + } + { + std::lock_guard critical_section(*critical_anti_preferred_caches); + anti_preferred_cache_.insert(data.begin(), data.end()); + anti_preferred_cache_to_model_.insert(data_to_model.begin(), data_to_model.end()); + anti_preferred_cache_collision.insert(data_collision.begin(), data_collision.end()); + } + { + std::lock_guard critical_section(*critical_anti_preferred_); + anti_preferred_.insert(data_raw_anti.begin(), data_raw_anti.end()); + } + }); +} + +const Shape& TreeModelVolumes::getAntiPreferredAreas(LayerIndex layer_idx, coord_t radius) +{ + coord_t ceiled_radius = ceilRadius(radius); + RadiusLayerPair key(ceilRadius(ceiled_radius), layer_idx); + std::optional> result; + + std::unordered_map* cache_ptr = &anti_preferred_; + { + std::lock_guard critical_section(*critical_anti_preferred_caches); + result = getArea(*cache_ptr, key); + } + + if (result) + { + return result.value().get(); + } + + { + key.first = 0; + std::lock_guard critical_section(*critical_anti_preferred_); + result = getArea(anti_preferred_, key); + } + + if (! result || result.value().get().empty()) // todo where and why are empty areas inserted? + { + return empty_polygon; + } + + if (precalculated_) + { + spdlog::warn("Missing anti preferred area at radius {} and layer {} Returning Empty! Result had area of {}", ceiled_radius, key.second, result.value().get().area()); + } + return empty_polygon; +} + + +const Shape& TreeModelVolumes::getAntiPreferredAvoidance(coord_t radius, LayerIndex layer_idx, AvoidanceType type, bool to_model, bool min_xy_dist) +{ + coord_t ceiled_radius = ceilRadius(radius, min_xy_dist); + RadiusLayerPair key(ceilRadius(ceiled_radius), layer_idx); + std::optional> result; + + std::unordered_map* cache_ptr = nullptr; + + if (type == AvoidanceType::COLLISION) + { + if (max_layer_idx_without_blocker_ <= layer_idx && to_model) + { + cache_ptr = &anti_preferred_cache_collision; + } + else + { + cache_ptr = &anti_preferred_; + } + } + else if (to_model) + { + cache_ptr = &anti_preferred_cache_to_model_; + } + else + { + cache_ptr = &anti_preferred_cache_; + } + + { + std::lock_guard critical_section(*critical_anti_preferred_caches); + result = getArea(*cache_ptr, key); + } + if (result) + { + return result.value().get(); + } + + { + key.first = 0; + std::lock_guard critical_section(*critical_anti_preferred_); + result = getArea(anti_preferred_, key); + } + + if (! result || result.value().get().empty()) + { + return getAvoidance(radius, layer_idx, type, to_model, min_xy_dist); + } + + if (precalculated_) + { + spdlog::warn( + "Missing anti preferred calculated at radius {} and layer {} and type {} to model {}, but precalculate was called. Returning Empty!", + ceiled_radius, + key.second, + type == AvoidanceType::COLLISION, + to_model); + } + return getAvoidance(radius, layer_idx, type, to_model, min_xy_dist); +} + +const Shape& TreeModelVolumes::getSupportBlocker(LayerIndex layer_idx) +{ + if (layer_idx < anti_overhang_.size()) + { + return anti_overhang_[layer_idx]; + } + else + { + return empty_polygon; + } +} + + coord_t TreeModelVolumes::ceilRadius(coord_t radius, bool min_xy_dist) const { return ceilRadius(radius + (min_xy_dist ? 0 : current_min_xy_dist_delta_)); @@ -564,6 +830,12 @@ coord_t TreeModelVolumes::getRadiusNextCeil(coord_t radius, bool min_xy_dist) co return ceilRadius(radius, min_xy_dist) - (min_xy_dist ? 0 : current_min_xy_dist_delta_); } +LayerIndex TreeModelVolumes::getFirstAntiPreferredLayerIdx() +{ + return first_anti_preferred_layer_idx_; +} + + bool TreeModelVolumes::checkSettingsEquality(const Settings& me, const Settings& other) const { return TreeSupportSettings(me) == TreeSupportSettings(other); @@ -638,18 +910,26 @@ void TreeModelVolumes::calculateCollision(const std::deque& key const coord_t xy_distance = outline_idx == current_outline_idx_ ? current_min_xy_dist_ : layer_outlines_[outline_idx].first.get("support_xy_distance"); // Technically this causes collision for the normal xy_distance to be larger by current_min_xy_dist_delta for all not currently processing meshes as this delta will // be added at request time. Avoiding this would require saving each collision for each outline_idx separately, - // and later for each avoidance... But avoidance calculation has to be for the whole scene and can NOT be done for each outline_idx separately and combined later. + // and later for each avoidance... But avoidance calculation has to be for the whole scene and can NOT be done for each outline_idx separately and combined later. // So avoiding this inaccuracy seems infeasible as it would require 2x the avoidance calculations => 0.5x the performance. coord_t min_layer_bottom; + coord_t min_layer_insert; { std::lock_guard critical_section(*critical_collision_cache_); - min_layer_bottom = getMaxCalculatedLayer(radius, collision_cache_) - z_distance_bottom_layers; + min_layer_insert = getMaxCalculatedLayer(radius, collision_cache_) + 1; + min_layer_bottom = min_layer_insert - z_distance_bottom_layers; } if (min_layer_bottom < 0) { min_layer_bottom = 0; } + + if (max_required_layer < min_layer_bottom) + { + continue; + } + for (const auto layer_idx : ranges::views::iota(min_layer_bottom, max_required_layer + 1)) { key.second = layer_idx; @@ -725,10 +1005,16 @@ void TreeModelVolumes::calculateCollision(const std::deque& key data[key] = data[key].unionPolygons(max_anti_overhang_layer >= layer_idx ? anti_overhang_[layer_idx].offset(radius) : Shape()); } - for (const auto layer_idx : ranges::views::iota(static_cast(keys[i].second) + 1UL, max_required_layer + 1UL) | ranges::views::reverse) + for (const auto layer_idx : ranges::views::iota(static_cast(keys[i].second) + 1UL, max_required_layer + 1UL)) { data.erase(RadiusLayerPair(radius, layer_idx)); // all these dont have the correct z_distance_top_layers as they can still have areas above them } + for (const auto layer_idx : ranges::views::iota(min_layer_bottom, min_layer_insert)) + { + // Those were already inserted + data.erase(RadiusLayerPair(radius, layer_idx)); + data_placeable.erase(RadiusLayerPair(radius, layer_idx)); + } for (auto pair : data) { @@ -757,13 +1043,39 @@ void TreeModelVolumes::calculateCollision(const std::deque& key { std::lock_guard critical_section(*critical_collision_cache_); - collision_cache_.insert(data_outer.begin(), data_outer.end()); + bool added_requested = false; + for (const std::pair& ins_elem : data_outer) + { + added_requested |= ins_elem.first.second == keys[i].second; + if (collision_cache_.contains(ins_elem.first)) + { + spdlog::warn("Recalculated collision for {} on layer {}", ins_elem.first.first, ins_elem.first.second); + } + else + { + collision_cache_[ins_elem.first] = ins_elem.second; + } + } + if (! added_requested && ! collision_cache_.contains(keys[i])) + { + printf("ERROR %d, %d\n\n", keys[i].second); + } } if (radius == 0) { { std::lock_guard critical_section(*critical_placeable_areas_cache_); - placeable_areas_cache_.insert(data_placeable_outer.begin(), data_placeable_outer.end()); + for (const std::pair& ins_elem : data_placeable_outer) + { + if (placeable_areas_cache_.contains(ins_elem.first)) + { + spdlog::warn("Recalculated placeable radius 0 for {} on layer {}", ins_elem.first.first, ins_elem.first.second); + } + else + { + placeable_areas_cache_[ins_elem.first] = ins_elem.second; + } + } } } }); @@ -771,20 +1083,33 @@ void TreeModelVolumes::calculateCollision(const std::deque& key void TreeModelVolumes::calculateCollisionHolefree(const std::deque& keys) { + std::vector min_layer_per_key(keys.size()); + LayerIndex min_layer = 0; LayerIndex max_layer = 0; for (long long unsigned int i = 0; i < keys.size(); i++) { max_layer = std::max(max_layer, keys[i].second); + LayerIndex start_layer; + { + std::lock_guard critical_section(*critical_collision_cache_holefree_); + start_layer = 1 + getMaxCalculatedLayer(keys[i].first, collision_cache_holefree_); + } + min_layer = std::max(min_layer, start_layer); + min_layer_per_key[i] = start_layer; } cura::parallel_for( - 0, + min_layer, LayerIndex(max_layer + 1), [&](const LayerIndex layer_idx) { std::unordered_map data; - for (RadiusLayerPair key : keys) + for (auto [key_idx, key] : keys | ranges::views::enumerate) { + if (layer_idx < min_layer_per_key[key_idx]) + { + continue; + } // Logically increase the collision by increase_until_radius const coord_t radius = key.first; const coord_t increase_radius_ceil = ceilRadius(increase_until_radius_, false) - ceilRadius(radius, true); @@ -796,7 +1121,17 @@ void TreeModelVolumes::calculateCollisionHolefree(const std::deque critical_section(*critical_collision_cache_holefree_); - collision_cache_holefree_.insert(data.begin(), data.end()); + for (const std::pair& ins_elem : data) + { + if (collision_cache_holefree_.contains(ins_elem.first)) + { + spdlog::warn("Recalculated collision holefree for {} on layer {}", ins_elem.first.first, ins_elem.first.second); + } + else + { + collision_cache_holefree_[ins_elem.first] = ins_elem.second; + } + } } }); } @@ -842,7 +1177,17 @@ void TreeModelVolumes::calculateAccumulatedPlaceable0(const LayerIndex max_layer }); { std::lock_guard critical_section(*critical_accumulated_placeables_cache_radius_0_); - accumulated_placeables_cache_radius_0_.insert(data.begin(), data.end()); + for (const std::pair& ins_elem : data) + { + if (accumulated_placeables_cache_radius_0_.contains(ins_elem.first)) + { + spdlog::warn("Recalculated accumulated_placeables_cache_radius_0_ on layer {}", ins_elem.first); + } + else + { + accumulated_placeables_cache_radius_0_[ins_elem.first] = ins_elem.second; + } + } } } @@ -892,7 +1237,17 @@ void TreeModelVolumes::calculateCollisionAvoidance(const std::deque critical_section(*critical_avoidance_cache_collision_); - avoidance_cache_collision_.insert(data.begin(), data.end()); + for (const std::pair& ins_elem : data) + { + if (avoidance_cache_collision_.contains(ins_elem.first)) + { + spdlog::warn("Recalculated collision avoidance for {} on layer {}", ins_elem.first.first, ins_elem.first.second); + } + else + { + avoidance_cache_collision_[ins_elem.first] = ins_elem.second; + } + } } }); } @@ -915,7 +1270,7 @@ Shape TreeModelVolumes::safeOffset(const Shape& me, coord_t distance, ClipperLib return ret.unionPolygons(collision); } -void TreeModelVolumes::calculateAvoidance(const std::deque& keys) +void TreeModelVolumes::calculateAvoidance(const std::deque& keys) // todo limit to certain avoidance types for better normal support performance { // For every RadiusLayer pair there are 3 avoidances that have to be calculate, calculated in the same paralell_for loop for better parallelization. const std::vector all_types = { AvoidanceType::SLOW, AvoidanceType::FAST_SAFE, AvoidanceType::FAST }; @@ -998,7 +1353,23 @@ void TreeModelVolumes::calculateAvoidance(const std::deque& key { std::lock_guard critical_section(*(slow ? critical_avoidance_cache_slow_ : holefree ? critical_avoidance_cache_holefree_ : critical_avoidance_cache_)); - (slow ? avoidance_cache_slow_ : holefree ? avoidance_cache_hole_ : avoidance_cache_).insert(data.begin(), data.end()); + auto* cache = &(slow ? avoidance_cache_slow_ : holefree ? avoidance_cache_hole_ : avoidance_cache_); + for (const std::pair& ins_elem : data) + { + if (ins_elem.first.second == -1) + { + // Layer was not calculated + continue; + } + if (cache->contains(ins_elem.first)) + { + spdlog::warn("Recalculated avoidance for {} on layer {} Slow:{} holefree:{}", ins_elem.first.first, ins_elem.first.second, slow, holefree); + } + else + { + (*cache)[ins_elem.first] = ins_elem.second; + } + } } }); } @@ -1007,6 +1378,7 @@ void TreeModelVolumes::calculatePlaceables(const std::deque& ke { // TODO: This should be a parallel for nowait (non-blocking), but as the parallel-for situation (as in, proper compiler support) continues to change, we're using the 'normal' // one right now. + cura::parallel_for( 0, keys.size(), @@ -1055,7 +1427,22 @@ void TreeModelVolumes::calculatePlaceables(const std::deque& ke { std::lock_guard critical_section(*critical_placeable_areas_cache_); - placeable_areas_cache_.insert(data.begin(), data.end()); + for (const std::pair& ins_elem : data) + { + if (ins_elem.first.second == -1) + { + // Layer was not calculated + continue; + } + if (placeable_areas_cache_.contains(ins_elem.first)) + { + spdlog::warn("Recalculated placeables for {} on layer {}", ins_elem.first.first, ins_elem.first.second); + } + else + { + placeable_areas_cache_[ins_elem.first] = ins_elem.second; + } + } } }); } @@ -1150,11 +1537,138 @@ void TreeModelVolumes::calculateAvoidanceToModel(const std::deque& ins_elem : data) + { + if (ins_elem.first.second == -1) + { + // Layer was not calculated + continue; + } + if (cache->contains(ins_elem.first)) + { + spdlog::warn("Recalculated avoidance to model for {} on layer {} Slow:{} holefree:{}", ins_elem.first.first, ins_elem.first.second, slow, holefree); + } + else + { + (*cache)[ins_elem.first] = ins_elem.second; + } + } } }); } +void TreeModelVolumes::calculateFake0Avoidances(const LayerIndex max_layer) +{ + LayerIndex start_layer; + { + std::lock_guard critical_section(*critical_avoidance_cache_); + start_layer = 1 + getMaxCalculatedLayer(0, avoidance_cache_); + } + const coord_t radius_offset = -radius_0_; + cura::parallel_for( + start_layer, + max_layer + 1, + [&](const LayerIndex layer_idx) + { + RadiusLayerPair key = RadiusLayerPair(0, layer_idx); + if (! precalculated_ || support_rest_preference_ == RestPreference::BUILDPLATE) + { + Shape smaller_avoidance_slow = getAvoidance(1, layer_idx, AvoidanceType::SLOW, false, true).offset(radius_offset, ClipperLib::jtRound); + Shape smaller_avoidance_fast = getAvoidance(1, layer_idx, AvoidanceType::FAST, false, true).offset(radius_offset, ClipperLib::jtRound); + Shape smaller_avoidance_fast_safe = getAvoidance(1, layer_idx, AvoidanceType::FAST_SAFE, false, true).offset(radius_offset, ClipperLib::jtRound); + { + std::lock_guard critical_section(*critical_avoidance_cache_slow_); + if (avoidance_cache_slow_.contains(key)) + { + spdlog::warn("Recalculated fake 0 avoidance slow for {} on layer {}", key.first, key.second); + } + else + { + avoidance_cache_slow_[key] = smaller_avoidance_slow; + } + } + { + std::lock_guard critical_section(*critical_avoidance_cache_); + if (avoidance_cache_.contains(key)) + { + spdlog::warn("Recalculated fake 0 avoidance for {} on layer {}", key.first, key.second); + } + else + { + avoidance_cache_[key] = smaller_avoidance_fast; + } + } + { + std::lock_guard critical_section(*critical_avoidance_cache_holefree_); + if (avoidance_cache_hole_.contains(key)) + { + spdlog::warn("Recalculated fake 0 avoidance holefree for {} on layer {}", key.first, key.second); + } + else + { + avoidance_cache_hole_[key] = smaller_avoidance_fast_safe; + } + } + } + + if (! precalculated_ || support_rests_on_model_) + { + Shape smaller_avoidance_to_model_slow = getAvoidance(1, layer_idx, AvoidanceType::SLOW, true, true).offset(radius_offset, ClipperLib::jtRound); + Shape smaller_avoidance_to_model_fast = getAvoidance(1, layer_idx, AvoidanceType::FAST, true, true).offset(radius_offset, ClipperLib::jtRound); + Shape smaller_avoidance_to_model_fast_safe = getAvoidance(1, layer_idx, AvoidanceType::FAST_SAFE, true, true).offset(radius_offset, ClipperLib::jtRound); + + { + std::lock_guard critical_section(*critical_avoidance_cache_to_model_slow_); + if (avoidance_cache_to_model_slow_.contains(key)) + { + spdlog::warn("Recalculated fake 0 avoidance to model slow for {} on layer {}", key.first, key.second); + } + else + { + avoidance_cache_to_model_slow_[key] = smaller_avoidance_to_model_slow; + } + } + { + std::lock_guard critical_section(*critical_avoidance_cache_to_model_); + if (avoidance_cache_to_model_.contains(key)) + { + spdlog::warn("Recalculated fake 0 avoidance to model for {} on layer {}", key.first, key.second); + } + else + { + avoidance_cache_to_model_[key] = smaller_avoidance_to_model_fast; + } + } + { + std::lock_guard critical_section(*critical_avoidance_cache_holefree_to_model_); + if (avoidance_cache_hole_to_model_.contains(key)) + { + spdlog::warn("Recalculated fake 0 avoidance to model holefree for {} on layer {}", key.first, key.second); + } + else + { + avoidance_cache_hole_to_model_[key] = smaller_avoidance_to_model_fast_safe; + } + } + if (layer_idx > max_layer_idx_without_blocker_) + { + Shape smaller_avoidance_collision = getAvoidance(1, layer_idx, AvoidanceType::COLLISION, true, true).offset(radius_offset, ClipperLib::jtRound); + std::lock_guard critical_section(*critical_avoidance_cache_collision_); + if (avoidance_cache_collision_.contains(key)) + { + spdlog::warn("Recalculated fake 0 collision avoidance for {} on layer {}", key.first, key.second); + } + else + { + avoidance_cache_collision_[key] = smaller_avoidance_collision; + } + } + } + }); +} + + void TreeModelVolumes::calculateWallRestrictions(const std::deque& keys) { // Wall restrictions are mainly important when they represent actual walls that are printed, and not "just" the configured z_distance, because technically valid placement is no @@ -1231,12 +1745,32 @@ void TreeModelVolumes::calculateWallRestrictions(const std::deque critical_section(*critical_wall_restrictions_cache_); - wall_restrictions_cache_.insert(data.begin(), data.end()); + for (const std::pair& ins_elem : data) + { + if (wall_restrictions_cache_.contains(ins_elem.first)) + { + spdlog::warn("Recalculated wall restriction for {} on layer {}", ins_elem.first.first, ins_elem.first.second); + } + else + { + wall_restrictions_cache_[ins_elem.first] = ins_elem.second; + } + } } { std::lock_guard critical_section(*critical_wall_restrictions_cache_min_); - wall_restrictions_cache_min_.insert(data_min.begin(), data_min.end()); + for (const std::pair& ins_elem : data) + { + if (wall_restrictions_cache_min_.contains(ins_elem.first)) + { + spdlog::warn("Recalculated wall restriction min for {} on layer {}", ins_elem.first.first, ins_elem.first.second); + } + else + { + wall_restrictions_cache_min_[ins_elem.first] = ins_elem.second; + } + } } }); } diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index 67a68e1cf6..560d54bd0c 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -95,6 +95,7 @@ TreeSupport::TreeSupport(const SliceDataStorage& storage) } fake_roof_areas = std::vector>(storage.support.supportLayers.size(), std::vector()); + support_free_areas = std::vector(storage.support.supportLayers.size(), Shape()); } void TreeSupport::generateSupportAreas(SliceDataStorage& storage) @@ -117,9 +118,6 @@ void TreeSupport::generateSupportAreas(SliceDataStorage& storage) storage.support.supportLayers .size()); // Value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in drawAreas. - additional_required_support_area = std::vector(storage.support.supportLayers.size(), Shape()); - - spdlog::info("Processing support tree mesh group {} of {} containing {} meshes.", counter + 1, grouped_meshes.size(), grouped_meshes[counter].second.size()); std::vector exclude(storage.support.supportLayers.size()); auto t_start = std::chrono::high_resolution_clock::now(); @@ -132,11 +130,8 @@ void TreeSupport::generateSupportAreas(SliceDataStorage& storage) { Shape exlude_at_layer; exlude_at_layer.push_back(storage.support.supportLayers[layer_idx].support_bottom); - exlude_at_layer.push_back(storage.support.supportLayers[layer_idx].support_roof); - for (auto part : storage.support.supportLayers[layer_idx].support_infill_parts) - { - exlude_at_layer.push_back(part.outline_); - } + exlude_at_layer.push_back(storage.support.supportLayers[layer_idx].getTotalAreaFromParts(storage.support.supportLayers[layer_idx].support_roof)); + exlude_at_layer.push_back(storage.support.supportLayers[layer_idx].getTotalAreaFromParts(storage.support.supportLayers[layer_idx].support_infill_parts)); exclude[layer_idx] = exlude_at_layer.unionPolygons(); scripta::log("tree_support_exclude", exclude[layer_idx], SectionType::SUPPORT, layer_idx); }); @@ -149,13 +144,24 @@ void TreeSupport::generateSupportAreas(SliceDataStorage& storage) config.maximum_move_distance, config.maximum_move_distance_slow, config.support_line_width / 2, + config.getRadius(0), processing.second.front(), progress_multiplier, progress_offset, exclude); + const auto t_cradle_init = std::chrono::high_resolution_clock::now(); + + // ### Evaluate cradle placement. Topmost cradle layer is needed tor precalculation + std::vector> cradle_data; + SupportCradleGeneration cradle_gen(storage, volumes_); + for (size_t mesh_idx : processing.second) + { + cradle_gen.addMeshToCradleCalculation(storage, mesh_idx); + } + // ### Precalculate avoidances, collision etc. - const LayerIndex max_required_layer = precalculate(storage, processing.second); + const LayerIndex max_required_layer = precalculate(storage, processing.second, cradle_gen.getTopMostCradleLayer()); if (max_required_layer < 0) { spdlog::info("Support tree mesh group {} does not have any overhang. Skipping tree support generation for this support tree mesh group.", counter + 1); @@ -163,15 +169,33 @@ void TreeSupport::generateSupportAreas(SliceDataStorage& storage) } const auto t_precalc = std::chrono::high_resolution_clock::now(); + for (size_t mesh_idx : processing.second) + { + cradle_gen.generateCradleForMesh(storage, mesh_idx); + } + cradle_gen.generate(storage); + const auto t_cradle = std::chrono::high_resolution_clock::now(); + // ### Place tips of the support tree for (size_t mesh_idx : processing.second) { - generateInitialAreas(*storage.meshes[mesh_idx], move_bounds, storage); + std::vector> cradle_data_mesh(move_bounds.size()); + cradle_gen.pushCradleData(cradle_data_mesh, support_free_areas, mesh_idx); // todo the support free areas here are HORRIBLE + generateInitialAreas(*storage.meshes[mesh_idx], move_bounds, storage, cradle_data_mesh); + if (cradle_data.size() < cradle_data_mesh.size()) + { + cradle_data.resize(cradle_data_mesh.size()); + } + for (LayerIndex layer_idx = 0; layer_idx < cradle_data.size(); layer_idx++) + { + cradle_data[layer_idx].insert(cradle_data[layer_idx].end(), cradle_data_mesh[layer_idx].begin(), cradle_data_mesh[layer_idx].end()); + } } + volumes_.precalculateAntiPreferred(); const auto t_gen = std::chrono::high_resolution_clock::now(); // ### Propagate the influence areas downwards. - createLayerPathing(move_bounds); + createLayerPathing(move_bounds, cradle_data); const auto t_path = std::chrono::high_resolution_clock::now(); // ### Set a point in each influence area @@ -179,27 +203,30 @@ void TreeSupport::generateSupportAreas(SliceDataStorage& storage) const auto t_place = std::chrono::high_resolution_clock::now(); // ### draw these points as circles - drawAreas(move_bounds, storage); + drawAreas(move_bounds, storage, cradle_data); const auto t_draw = std::chrono::high_resolution_clock::now(); - const auto dur_pre_gen = 0.001 * std::chrono::duration_cast(t_precalc - t_start).count(); - const auto dur_gen = 0.001 * std::chrono::duration_cast(t_gen - t_precalc).count(); + const auto dur_cradle_init = 0.001 * std::chrono::duration_cast(t_cradle_init - t_start).count(); + const auto dur_pre_gen = 0.001 * std::chrono::duration_cast(t_precalc - t_cradle_init).count(); + const auto dur_cradle = 0.001 * std::chrono::duration_cast(t_cradle - t_precalc).count(); + const auto dur_gen = 0.001 * std::chrono::duration_cast(t_gen - t_cradle).count(); const auto dur_path = 0.001 * std::chrono::duration_cast(t_path - t_gen).count(); const auto dur_place = 0.001 * std::chrono::duration_cast(t_place - t_path).count(); const auto dur_draw = 0.001 * std::chrono::duration_cast(t_draw - t_place).count(); const auto dur_total = 0.001 * std::chrono::duration_cast(t_draw - t_start).count(); spdlog::info( "Total time used creating Tree support for the currently grouped meshes: {} ms. Different subtasks:\n" - "Calculating Avoidance: {} ms Creating inital influence areas: {} ms Influence area creation: {} ms Placement of Points in InfluenceAreas: {} ms Drawing result as " + "Calculating Avoidance: {} ms Calculating Cradle: {} ms of which {} ms were initialising Creating initial influence areas: {} ms Influence area creation: {} ms Placement of Points in InfluenceAreas: {} ms Drawing result as " "support {} ms", dur_total, dur_pre_gen, + dur_cradle, + dur_cradle_init, dur_gen, dur_path, dur_place, dur_draw); - for (auto& layer : move_bounds) { for (auto elem : layer) @@ -213,10 +240,11 @@ void TreeSupport::generateSupportAreas(SliceDataStorage& storage) storage.support.generated = true; } -LayerIndex TreeSupport::precalculate(const SliceDataStorage& storage, std::vector currently_processing_meshes) +LayerIndex TreeSupport::precalculate(const SliceDataStorage& storage, std::vector currently_processing_meshes, LayerIndex top_most_cradle_layer_idx) { + // Calculate top most layer that is relevant for support. - LayerIndex max_layer = -1; + LayerIndex max_layer = top_most_cradle_layer_idx; for (size_t mesh_idx : currently_processing_meshes) { const SliceMeshStorage& mesh = *storage.meshes[mesh_idx]; @@ -253,10 +281,14 @@ LayerIndex TreeSupport::precalculate(const SliceDataStorage& storage, std::vecto } -void TreeSupport::generateInitialAreas(const SliceMeshStorage& mesh, std::vector>& move_bounds, SliceDataStorage& storage) +void TreeSupport::generateInitialAreas( + const SliceMeshStorage& mesh, + std::vector>& move_bounds, + SliceDataStorage& storage, + std::vector>& cradle_data_model) { TreeSupportTipGenerator tip_gen(mesh, volumes_); - tip_gen.generateTips(storage, mesh, move_bounds, additional_required_support_area, fake_roof_areas); + tip_gen.generateTips(storage, mesh, move_bounds, fake_roof_areas, support_free_areas, cradle_data_model); } void TreeSupport::mergeHelper( @@ -646,7 +678,7 @@ std::optional TreeSupport::increaseSingleArea( const coord_t overspeed, const bool mergelayer) { - TreeSupportElement current_elem(parent); // Also increases DTT by one. + TreeSupportElement current_elem = parent->createNewElement(); // Also increases DTT by one. Shape check_layer_data; if (settings.increase_radius_) { @@ -691,6 +723,16 @@ std::optional TreeSupport::increaseSingleArea( current_elem.to_buildplate_ = true; // sometimes nodes that can reach the buildplate are marked as cant reach, tainting subtrees. This corrects it. spdlog::debug("Corrected taint leading to a wrong to model value on layer {} targeting {} with radius {}", layer_idx - 1, current_elem.target_height_, radius); } + + // Sometimes the avoidance can contain holes that are smaller than 1, so in that case increase the area slightly, + // technically this makes the influence area is larger than it should be (as it overlaps with the avoidance slightly), + // but everything compensates for small rounding errors already, so it will be fine. + // Other solution would be to apply a morphological closure for the avoidances, but as this issue occurs very rarely it may not be worth the performance impact. + if (! settings.no_error_ && ! to_bp_data.empty() && to_bp_data.area() < 1) + { + to_bp_data = to_bp_data.unionPolygons(TreeSupportUtils::toPolylines(to_bp_data).offset(1)); + spdlog::warn("Detected very small influence area, possible caused by a small hole in the avoidance. Compensating."); + } } if (config.support_rests_on_model) { @@ -712,10 +754,96 @@ std::optional TreeSupport::increaseSingleArea( = TreeSupportUtils::safeUnion(increased.difference(volumes_.getAvoidance(radius, layer_idx - 1, AvoidanceType::COLLISION, true, settings.use_min_distance_))); } } + + // Sometimes the avoidance can contain holes that are smaller than 1, so in that case increase the area slightly, + // technically this makes the influence area is larger than it should be (as it overlaps with the avoidance slightly), + // but everything compensates for small rounding errors already, so it will be fine. + // Other solution would be to apply a morphological closure for the avoidances, but as this issue occurs very rarely it may not be worth the performance impact. + if (! settings.no_error_ && ! to_model_data.empty() && to_model_data.area() < 1) + { + to_model_data = to_model_data.unionPolygons(TreeSupportUtils::toPolylines(to_model_data).offset(1)); + spdlog::warn("Detected very small influence area, possible caused by a small hole in the avoidance. Compensating."); + } } + coord_t actual_radius = config.getRadius(current_elem); + // Removing cradle areas from influence areas if possible. + Shape anti_preferred_areas = volumes_.getAntiPreferredAreas(layer_idx - 1, actual_radius); + bool anti_preferred_exists = volumes_.getFirstAntiPreferredLayerIdx() < layer_idx; + if (! anti_preferred_areas.empty()) + { + bool is_fast = settings.type_ != AvoidanceType::SLOW; + // Ensure that branches can not lag through cradle lines. Proper way to do this would be in the beginning with custom increased areas. + coord_t anti_radius_extra = std::max(settings.increase_speed_ - volumes_.ceilRadius(actual_radius * 2, true), coord_t(0)); + if (anti_radius_extra) + { + anti_preferred_areas = anti_preferred_areas.offset(anti_radius_extra).unionPolygons(); + } + if (current_elem.to_buildplate_) + { + Shape to_bp_without_anti = to_bp_data.difference(anti_preferred_areas); + // If already moving fast there is not much to do. The anti preferred with collision radius will then later be subtracted if it is not subtracted here. + if (to_bp_without_anti.area() > EPSILON || (settings.use_anti_preferred_ && ! is_fast)) + { + to_bp_data = to_bp_without_anti; + Shape to_model_data_without_anti = to_model_data.difference(anti_preferred_areas); + to_model_data = to_model_data_without_anti; + Shape increased_without_anti = increased.difference(anti_preferred_areas); + increased = increased_without_anti; + current_elem.ensure_valid_anti_preferred_ = true; + } + } + else + { + Shape to_model_data_without_anti = to_model_data.difference(anti_preferred_areas); + if (to_model_data_without_anti.area() > EPSILON || (settings.use_anti_preferred_ && ! is_fast)) + { + to_model_data = to_model_data_without_anti; + Shape increased_without_anti = increased.difference(anti_preferred_areas); + increased = increased_without_anti; + current_elem.ensure_valid_anti_preferred_ = true; + } + } + } + else + { + current_elem.ensure_valid_anti_preferred_ = true; + } check_layer_data = current_elem.to_buildplate_ ? to_bp_data : to_model_data; + // Remove areas where the branch should not be if possible. + // Has to be also subtracted from increased, as otherwise a merge directly below the anti-preferred area may force a branch inside it. + if (anti_preferred_exists && settings.use_anti_preferred_) + { + const Shape& anti_preferred = volumes_.getAntiPreferredAvoidance(radius, layer_idx - 1, settings.type_, ! current_elem.to_buildplate_, settings.use_min_distance_); + if (current_elem.to_buildplate_) + { + to_bp_data = to_bp_data.difference(anti_preferred); + to_model_data = to_model_data.difference(anti_preferred); + } + else + { + to_model_data = to_model_data.difference(anti_preferred); + } + + if (! current_elem.ensure_valid_anti_preferred_) + { + increased = increased.difference(volumes_.getAntiPreferredAreas(layer_idx - 1, radius)); + } + + check_layer_data = current_elem.to_buildplate_ ? to_bp_data : to_model_data; + if (check_layer_data.area() > 1) + { + current_elem.can_avoid_anti_preferred_ = true; + } + } + + if (!anti_preferred_exists) + { + current_elem.can_avoid_anti_preferred_ = true; + current_elem.ensure_valid_anti_preferred_ = true; + } + if (settings.increase_radius_ && check_layer_data.area() > 1) { std::function validWithRadius = [&](coord_t next_radius) @@ -729,28 +857,65 @@ std::optional TreeSupport::increaseSingleArea( if (current_elem.to_buildplate_) { // Regular union as output will not be used later => this area should always be a subset of the safeUnion one. - to_bp_data_2 = increased.difference(volumes_.getAvoidance(next_radius, layer_idx - 1, settings.type_, false, settings.use_min_distance_)).unionPolygons(); + + to_bp_data_2 = increased; + bool avoidance_handled = false; + if (settings.use_anti_preferred_ && current_elem.can_use_safe_radius_ && anti_preferred_exists) + { + to_bp_data_2 = to_bp_data_2.difference( + volumes_.getAntiPreferredAvoidance(next_radius, layer_idx - 1, settings.type_, ! current_elem.to_buildplate_, settings.use_min_distance_)); + avoidance_handled = settings.type_ != AvoidanceType::SLOW; + } + else if (current_elem.ensure_valid_anti_preferred_ && next_radius > actual_radius && anti_preferred_exists) + { + to_bp_data_2 = to_bp_data_2.difference(volumes_.getAntiPreferredAreas(layer_idx - 1, next_radius)); + } + if (! avoidance_handled) + { + to_bp_data_2 = to_bp_data_2.difference(volumes_.getAvoidance(next_radius, layer_idx - 1, settings.type_, false, settings.use_min_distance_)); + } } Shape to_model_data_2; if (config.support_rests_on_model && ! current_elem.to_buildplate_) { - to_model_data_2 = increased - .difference(volumes_.getAvoidance( - next_radius, - layer_idx - 1, - current_elem.to_model_gracious_ ? settings.type_ : AvoidanceType::COLLISION, - true, - settings.use_min_distance_)) - .unionPolygons(); + to_model_data_2 = increased; + bool avoidance_handled = false; + if (settings.use_anti_preferred_ && current_elem.can_use_safe_radius_ && anti_preferred_exists) + { + to_model_data_2 = to_model_data_2.difference( + volumes_.getAntiPreferredAvoidance(next_radius, + layer_idx - 1, + current_elem.to_model_gracious_ ? settings.type_ : AvoidanceType::COLLISION, + true, + settings.use_min_distance_)); + avoidance_handled = settings.type_ != AvoidanceType::SLOW; // There is no slow anti-preferred avoidance. + } + else if (current_elem.ensure_valid_anti_preferred_ && next_radius > actual_radius && anti_preferred_exists) + { + to_model_data_2 = to_model_data_2.difference(volumes_.getAntiPreferredAreas(layer_idx - 1, next_radius)); + } + if (! avoidance_handled) + { + to_model_data_2 = to_model_data_2.difference(volumes_.getAvoidance( + next_radius, + layer_idx - 1, + current_elem.to_model_gracious_ ? settings.type_ : AvoidanceType::COLLISION, + true, + settings.use_min_distance_)); + } } Shape check_layer_data_2 = current_elem.to_buildplate_ ? to_bp_data_2 : to_model_data_2; return check_layer_data_2.area() > 1; }; coord_t ceil_radius_before = volumes_.ceilRadius(radius, settings.use_min_distance_); + coord_t ceil_actual_radius_before = volumes_.ceilRadius(actual_radius, settings.use_min_distance_); + // If the Collision Radius is smaller than the actual radius, check if it can catch up without violating the avoidance. - if (config.getCollisionRadius(current_elem) < config.increase_radius_until_radius && config.getCollisionRadius(current_elem) < config.getRadius(current_elem)) + const bool collision_radius_catch_up = config.getCollisionRadius(current_elem) < config.increase_radius_until_radius && + config.getCollisionRadius(current_elem) < config.getRadius(current_elem); + if (collision_radius_catch_up) { coord_t target_radius = std::min(config.getRadius(current_elem), config.increase_radius_until_radius); coord_t current_ceil_radius = volumes_.getRadiusNextCeil(radius, settings.use_min_distance_); @@ -795,6 +960,54 @@ std::optional TreeSupport::increaseSingleArea( } radius = config.getCollisionRadius(current_elem); + // If a hidden radius increase was used, also do some catching up. + const bool hidden_radius_catch_up = current_elem.hidden_radius_increase_ > 0; + if (hidden_radius_catch_up) + { + coord_t target_radius = config.getRadius(current_elem); + coord_t current_ceil_radius = volumes_.getRadiusNextCeil(radius, settings.use_min_distance_); + + while (current_ceil_radius < target_radius && validWithRadius(volumes_.getRadiusNextCeil(current_ceil_radius + 1, settings.use_min_distance_))) + { + current_ceil_radius = volumes_.getRadiusNextCeil(current_ceil_radius + 1, settings.use_min_distance_); + } + double resulting_hidden_increases = current_elem.hidden_radius_increase_; + while (resulting_hidden_increases > 0 + && config.getRadius(current_elem.effective_radius_height_, resulting_hidden_increases + current_elem.buildplate_radius_increases_) <= current_ceil_radius + && config.getRadius(current_elem.effective_radius_height_, resulting_hidden_increases + current_elem.buildplate_radius_increases_) + <= config.getRadius(current_elem)) + { + resulting_hidden_increases--; + } + double bp_increases = current_elem.hidden_radius_increase_ - std::max(0.0, resulting_hidden_increases); + current_elem.hidden_radius_increase_ = std::max(0.0, resulting_hidden_increases); + current_elem.buildplate_radius_increases_ += bp_increases; + + //Try to ensure the branch stays away from potential walls if possible. + if (config.getCollisionRadius(current_elem) < config.getRadius(current_elem)) + { + Shape new_to_bp_data; + Shape new_to_model_data; + + if (current_elem.to_buildplate_) + { + new_to_bp_data = to_bp_data.difference(volumes_.getCollision(config.getRadius(current_elem), layer_idx - 1, current_elem.use_min_xy_dist_)); + if (new_to_bp_data.area() > EPSILON) + { + to_bp_data = new_to_bp_data; + } + } + if (config.support_rests_on_model && (! current_elem.to_buildplate_ || mergelayer)) + { + new_to_model_data = to_model_data.difference(volumes_.getCollision(config.getRadius(current_elem), layer_idx - 1, current_elem.use_min_xy_dist_)); + if (new_to_model_data.area() > EPSILON) + { + to_model_data = new_to_model_data; + } + } + } + } + const coord_t foot_radius_increase = config.branch_radius * (std::max(config.diameter_scale_bp_radius - config.diameter_angle_scale_factor, 0.0)); const double planned_foot_increase = std::min(1.0, double(config.recommendedMinRadius(layer_idx - 1) - config.getRadius(current_elem)) / foot_radius_increase); // ^^^ Is nearly all of the time 1, but sometimes an increase of 1 could cause the radius to become bigger than recommendedMinRadius, which could cause the radius to become @@ -811,34 +1024,68 @@ std::optional TreeSupport::increaseSingleArea( const bool increase_bp_foot = planned_foot_increase > 0 && (current_elem.to_buildplate_ || (current_elem.to_model_gracious_ && config.support_rest_preference == RestPreference::GRACEFUL)); - if (increase_bp_foot && config.getRadius(current_elem) >= config.branch_radius && config.getRadius(current_elem) >= config.increase_radius_until_radius) { if (validWithRadius(config.getRadius(current_elem.effective_radius_height_, current_elem.buildplate_radius_increases_ + planned_foot_increase))) { current_elem.buildplate_radius_increases_ += planned_foot_increase; radius = config.getCollisionRadius(current_elem); + actual_radius = config.getRadius(current_elem); } } if (ceil_radius_before != volumes_.ceilRadius(radius, settings.use_min_distance_)) { + if (current_elem.ensure_valid_anti_preferred_ && ceil_actual_radius_before < volumes_.ceilRadius(actual_radius, settings.use_min_distance_) && anti_preferred_exists) + { + increased = increased.difference(volumes_.getAntiPreferredAreas(layer_idx - 1, actual_radius)); + } if (current_elem.to_buildplate_) { - to_bp_data = TreeSupportUtils::safeUnion(increased.difference(volumes_.getAvoidance(radius, layer_idx - 1, settings.type_, false, settings.use_min_distance_))); + bool avoidance_handled = false; + to_bp_data = increased; + if (settings.use_anti_preferred_ && current_elem.can_use_safe_radius_ && anti_preferred_exists) + { + to_bp_data = to_bp_data.difference( + volumes_.getAntiPreferredAvoidance(radius, layer_idx - 1, settings.type_, ! current_elem.to_buildplate_, settings.use_min_distance_)); + avoidance_handled = settings.type_ != AvoidanceType::SLOW; // There is no slow anti-preferred avoidance. + } + if (! avoidance_handled) + { + to_bp_data = to_bp_data.difference(volumes_.getAvoidance(radius, layer_idx - 1, settings.type_, false, settings.use_min_distance_)); + } + to_bp_data = TreeSupportUtils::safeUnion(to_bp_data); } if (config.support_rests_on_model && (! current_elem.to_buildplate_ || mergelayer)) { - to_model_data = TreeSupportUtils::safeUnion(increased.difference( - volumes_.getAvoidance(radius, layer_idx - 1, current_elem.to_model_gracious_ ? settings.type_ : AvoidanceType::COLLISION, true, settings.use_min_distance_))); + bool avoidance_handled = false; + to_model_data = increased; + if (settings.use_anti_preferred_ && current_elem.can_use_safe_radius_ && anti_preferred_exists) + { + to_model_data = to_model_data.difference(volumes_.getAntiPreferredAvoidance( + radius, + layer_idx - 1, + current_elem.to_model_gracious_ ? settings.type_ : AvoidanceType::COLLISION, + true, + settings.use_min_distance_)); + avoidance_handled = settings.type_ != AvoidanceType::SLOW; // There is no slow anti-preferred avoidance. + } + if (! avoidance_handled) + { + to_model_data = to_model_data.difference( + volumes_.getAvoidance(radius, layer_idx - 1, current_elem.to_model_gracious_ ? settings.type_ : AvoidanceType::COLLISION, true, settings.use_min_distance_)); + } + to_model_data = TreeSupportUtils::safeUnion(to_model_data); } check_layer_data = current_elem.to_buildplate_ ? to_bp_data : to_model_data; if (check_layer_data.area() < 1) { spdlog::error( - "Lost area by doing catch up from {} to radius {}", + "Lost area by doing catch up from {} to radius {} collision_radius_catch_up: {} hidden_radius_catch_up: {}", ceil_radius_before, - volumes_.ceilRadius(config.getCollisionRadius(current_elem), settings.use_min_distance_)); + volumes_.ceilRadius(config.getCollisionRadius(current_elem), settings.use_min_distance_), + collision_radius_catch_up, + hidden_radius_catch_up); } } } @@ -893,7 +1140,7 @@ void TreeSupport::increaseAreas( PropertyAreasUnordered& to_bp_areas, PropertyAreas& to_model_areas, PropertyAreas& influence_areas, - std::vector& bypass_merge_areas, + PropertyAreas& bypass_merge_areas, const std::vector& last_layer, const LayerIndex layer_idx, const bool mergelayer) @@ -905,7 +1152,7 @@ void TreeSupport::increaseAreas( [&](const size_t idx) { TreeSupportElement* parent = last_layer[idx]; - TreeSupportElement elem(parent); // Also increases dtt. + TreeSupportElement elem = parent->createNewElement(); // Also increases dtt. // Abstract representation of the model outline. If an influence area would move through it, it could teleport through a wall. const Shape wall_restriction = volumes_.getWallRestriction(config.getCollisionRadius(*parent), layer_idx, parent->use_min_xy_dist_); @@ -980,6 +1227,7 @@ void TreeSupport::increaseAreas( constexpr bool increase_radius = true; constexpr bool no_error = true; constexpr bool use_min_radius = true; + constexpr bool use_anti_preferred = true; constexpr bool move = true; // Determine in which order configurations are checked if they result in a valid influence area. Check will stop if a valid area is found @@ -1013,6 +1261,7 @@ void TreeSupport::increaseAreas( increase_radius, elem.last_area_increase_.no_error_, ! use_min_radius, + use_anti_preferred, elem.last_area_increase_.move_), true); insertSetting( @@ -1022,6 +1271,7 @@ void TreeSupport::increaseAreas( ! increase_radius, elem.last_area_increase_.no_error_, ! use_min_radius, + use_anti_preferred, elem.last_area_increase_.move_), true); } @@ -1030,31 +1280,64 @@ void TreeSupport::increaseAreas( { // If the radius until which it is always increased can not be guaranteed, move fast. This is to avoid holes smaller than the real branch radius. // This does not guarantee the avoidance of such holes, but ensures they are avoided if possible. - insertSetting(AreaIncreaseSettings(AvoidanceType::SLOW, slow_speed, increase_radius, no_error, ! use_min_radius, ! move), true); // Did we go through the hole. + insertSetting( + AreaIncreaseSettings(AvoidanceType::SLOW, slow_speed, increase_radius, no_error, ! use_min_radius, use_anti_preferred, ! move), + true); // Did we go through the hole. // In many cases the definition of hole is overly restrictive, so to avoid unnecessary fast movement in the tip, it is ignored there for a bit. // This CAN cause a branch to go though a hole it otherwise may have avoided. if (elem.distance_to_top_ < round_up_divide(config.tip_layers, 2)) { - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, slow_speed, increase_radius, no_error, ! use_min_radius, ! move), true); + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, slow_speed, increase_radius, no_error, ! use_min_radius, use_anti_preferred, ! move), true); } insertSetting( - AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, increase_radius, no_error, ! use_min_radius, ! move), + AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, increase_radius, no_error, ! use_min_radius, use_anti_preferred, ! move), true); // Did we manage to avoid the hole, - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, ! increase_radius, no_error, ! use_min_radius, move), true); - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, fast_speed, ! increase_radius, no_error, ! use_min_radius, move), true); + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, ! increase_radius, no_error, ! use_min_radius, use_anti_preferred, move), true); + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, fast_speed, ! increase_radius, no_error, ! use_min_radius, use_anti_preferred, move), true); } else { - insertSetting(AreaIncreaseSettings(AvoidanceType::SLOW, slow_speed, increase_radius, no_error, ! use_min_radius, move), true); + insertSetting(AreaIncreaseSettings(AvoidanceType::SLOW, slow_speed, increase_radius, no_error, ! use_min_radius, use_anti_preferred, move), true); // While moving fast to be able to increase the radius (b) may seems preferable (over a) this can cause the a sudden skip in movement, which looks similar to a // layer shift and can reduce stability. As such idx have chosen to only use the user setting for radius increases as a friendly recommendation. - insertSetting(AreaIncreaseSettings(AvoidanceType::SLOW, slow_speed, ! increase_radius, no_error, ! use_min_radius, move), true); // a (See above.) + insertSetting( + AreaIncreaseSettings(AvoidanceType::SLOW, slow_speed, ! increase_radius, no_error, ! use_min_radius, use_anti_preferred, move), + true); // a (See above.) if (elem.distance_to_top_ < config.tip_layers) { - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, slow_speed, increase_radius, no_error, ! use_min_radius, move), true); + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, slow_speed, increase_radius, no_error, ! use_min_radius, use_anti_preferred, move), true); + } + insertSetting( + AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, increase_radius, no_error, ! use_min_radius, use_anti_preferred, move), + true); // b (See above.) + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, ! increase_radius, no_error, ! use_min_radius, use_anti_preferred, move), true); + } + + if (! elem.can_avoid_anti_preferred_ && layer_idx > volumes_.getFirstAntiPreferredLayerIdx() ) + { + std::deque old_order = order; + for (AreaIncreaseSettings settings : old_order) + { + if (elem.effective_radius_height_ < config.increase_radius_until_dtt && ! settings.increase_radius_) + { + continue; + } + if (! settings.move_) + { + continue; + } + + insertSetting( + AreaIncreaseSettings( + settings.type_, + settings.increase_speed_, + settings.increase_radius_, + settings.no_error_, + use_min_radius, + ! settings.use_anti_preferred_, + settings.move_), + true); } - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, increase_radius, no_error, ! use_min_radius, move), true); // b (See above.) - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, ! increase_radius, no_error, ! use_min_radius, move), true); } if (elem.use_min_xy_dist_) @@ -1065,13 +1348,20 @@ void TreeSupport::increaseAreas( for (AreaIncreaseSettings settings : order) { new_order.emplace_back(settings); - new_order.emplace_back(settings.type_, settings.increase_speed_, settings.increase_radius_, settings.no_error_, use_min_radius, settings.move_); + new_order.emplace_back( + settings.type_, + settings.increase_speed_, + settings.increase_radius_, + settings.no_error_, + use_min_radius, + settings.use_anti_preferred_, + settings.move_); } order = new_order; } insertSetting( - AreaIncreaseSettings(AvoidanceType::FAST, fast_speed, ! increase_radius, ! no_error, elem.use_min_xy_dist_, move), + AreaIncreaseSettings(AvoidanceType::FAST, fast_speed, ! increase_radius, ! no_error, elem.use_min_xy_dist_, ! use_anti_preferred, move), true); // simplifying is very important for performance, but before an error is compensated by moving faster it makes sense to check to see if the simplifying has // caused issues @@ -1082,12 +1372,12 @@ void TreeSupport::increaseAreas( || (! elem.to_model_gracious_ && (parent->area_->intersection(volumes_.getAccumulatedPlaceable0(layer_idx)).empty()))) // Error case. { // It is normal that we won't be able to find a new area at some point in time if we won't be able to reach layer 0 aka have to connect with the model. - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, fast_speed * 1.5, ! increase_radius, ! no_error, elem.use_min_xy_dist_, move), true); + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, fast_speed * 1.5, ! increase_radius, ! no_error, elem.use_min_xy_dist_, ! use_anti_preferred, move), true); } if (elem.distance_to_top_ < elem.dont_move_until_ && elem.can_use_safe_radius_) // Only do not move when holes would be avoided in every case. { insertSetting( - AreaIncreaseSettings(AvoidanceType::SLOW, 0, increase_radius, no_error, ! use_min_radius, ! move), + AreaIncreaseSettings(AvoidanceType::SLOW, 0, increase_radius, no_error, ! use_min_radius, use_anti_preferred, ! move), false); // Only do not move when already in a no hole avoidance with the regular xy distance. } @@ -1208,10 +1498,9 @@ void TreeSupport::increaseAreas( radius = config.getCollisionRadius(elem); elem.last_area_increase_ = settings; add = true; - bypass_merge = ! settings.move_ - || (settings.use_min_distance_ - && elem.distance_to_top_ - < config.tip_layers); // Do not merge if the branch should not move or the priority has to be to get farther away from the model. + // Do not merge if the branch should not move or the priority has to be to get farther away from the model. + bypass_merge = ! settings.move_ || (settings.use_min_distance_ && elem.distance_to_top_ < config.tip_layers) + || ! elem.can_avoid_anti_preferred_; // todo less aggressive merge prevention? if (settings.move_) { elem.dont_move_until_ = 0; @@ -1250,21 +1539,19 @@ void TreeSupport::increaseAreas( std::lock_guard critical_section_newLayer(critical_sections); if (bypass_merge) { - Shape* new_area = new Shape(max_influence_area); - TreeSupportElement* next = new TreeSupportElement(elem, new_area); - bypass_merge_areas.emplace_back(next); + bypass_merge_areas.emplace(elem, max_influence_area); } else { influence_areas.emplace(elem, max_influence_area); - if (elem.to_buildplate_) - { - to_bp_areas.emplace(elem, to_bp_data); - } - if (config.support_rests_on_model) - { - to_model_areas.emplace(elem, to_model_data); - } + } + if (elem.to_buildplate_) + { + to_bp_areas.emplace(elem, to_bp_data); + } + if (config.support_rests_on_model) + { + to_model_areas.emplace(elem, to_model_data); } } } @@ -1278,16 +1565,139 @@ void TreeSupport::increaseAreas( }); } -void TreeSupport::createLayerPathing(std::vector>& move_bounds) +void TreeSupport::handleCradleLineValidity( + PropertyAreasUnordered& to_bp_areas, + PropertyAreas& to_model_areas, + PropertyAreas& influence_areas, + PropertyAreas& bypass_merge_areas, + LayerIndex layer_idx, + std::vector>& move_bounds, + std::vector>& cradle_data) +{ + // cant skip just because cradle_data is empty as there may be tips that have to be removed as the line they support was removed further up + if (cradle_data.size() <= layer_idx) + { + return; + } + + if(!cradle_data[layer_idx].empty()) + { + std::unordered_set removed_lines_idx; + // Evaluate which lines have to be removed for all influence areas to be valid. + // Goal is to remove as few lines as possible + // Correctly solving this is very hard. + // So for now any solution will do. Todo find a better way. Also parallelize + + std::vector all_elements_on_layer; + all_elements_on_layer.insert(all_elements_on_layer.end(), move_bounds[layer_idx].begin(), move_bounds[layer_idx].end()); + for (auto& elem_influence_pair : influence_areas) + { + all_elements_on_layer.emplace_back(&elem_influence_pair.first); + } + for (auto& elem_influence_pair : bypass_merge_areas) + { + all_elements_on_layer.emplace_back(&elem_influence_pair.first); + } + for (const TreeSupportElement* elem : all_elements_on_layer) + { + if (! elem->ensure_valid_anti_preferred_) + { + const coord_t safe_movement_distance = (elem->use_min_xy_dist_ ? config.xy_min_distance : config.xy_distance) + config.getCollisionRadius(*elem) + + (std::min(config.z_distance_top_layers, config.z_distance_bottom_layers) > 0 ? config.min_feature_size : 0); + + bool immutable = elem->area_ != nullptr; + bool to_bp = elem->to_buildplate_; + + Shape relevant_influence; + Shape full_influence; + if (! immutable) + { + relevant_influence = to_bp ? to_bp_areas[*elem] : to_model_areas[*elem]; + full_influence = bypass_merge_areas.contains(*elem) ? bypass_merge_areas[*elem] : influence_areas[*elem]; + } + else + { + relevant_influence = elem->area_->difference(volumes_.getCollision(config.getCollisionRadius(*elem), layer_idx, elem->use_min_xy_dist_)); + full_influence = relevant_influence; + } + AABB relevant_influence_aabb = AABB(relevant_influence); + + for (auto [cradle_idx, cradle] : cradle_data[layer_idx] | ranges::views::enumerate) + { + if (cradle.cradleLineExists() && ! cradle.getCradleLine()->is_base_ && ! removed_lines_idx.contains(cradle_idx)) + { + // The branch created by the influence area cant lag though the model... So the offset needs to be safe... + AABB cradle_area_aabb = AABB(cradle.getCradleLine()->area_); + cradle_area_aabb.expand(config.getRadius(*elem) + config.xy_distance); + if (cradle_area_aabb.hit(relevant_influence_aabb)) + { + Shape cradle_influence = TreeSupportUtils::safeOffsetInc( + cradle.getCradleLine()->area_, + config.getRadius(*elem) + config.xy_distance, + volumes_.getCollision(config.getCollisionRadius(*elem), layer_idx, true), + safe_movement_distance, + 0, + 1, + config.support_line_distance / 2, + &config.simplifier); + Shape next_relevant_influence = relevant_influence.difference(cradle_influence); + + if (next_relevant_influence.area() > EPSILON) + { + relevant_influence = TreeSupportUtils::safeUnion(next_relevant_influence); + full_influence = TreeSupportUtils::safeUnion(full_influence.difference(cradle_influence), relevant_influence); + } + else + { + // todo Check if non remove options are available eg shortening cradle line... + removed_lines_idx.emplace(cradle_idx); + cradle.getCradleLine()->addLineToRemoved(cradle.getCradleLine()->line_); + cradle.getCradleLine()->line_.clear(); + spdlog::debug("Flagging to remove cradle line {} {} ", cradle.layer_idx_, cradle.line_idx_); + } + } + } + } + if (! immutable) + { + (bypass_merge_areas.contains(*elem) ? bypass_merge_areas[*elem] : influence_areas[*elem]) = full_influence; + (to_bp ? to_bp_areas[*elem] : to_model_areas[*elem]) = relevant_influence; + } + } + } + for (auto [cradle_idx, cradle] : cradle_data[layer_idx] | ranges::views::enumerate) + { + if (cradle.cradleLineExists()) + { + cradle.cradle_->verifyLines(); + } + } + } + + // todo would be great if removed cradle lines could be eliminated from the avoidance... + + std::vector next_layer; + next_layer.insert(next_layer.begin(), move_bounds[layer_idx].begin(), move_bounds[layer_idx].end()); + for (TreeSupportElement* elem : next_layer) + { + if (elem->cradle_line_ && ! elem->cradle_line_->cradleLineExists()) + { + move_bounds[layer_idx].erase(elem); + delete elem->area_; + delete elem; + } + } +} + + +void TreeSupport::createLayerPathing(std::vector>& move_bounds, std::vector>& cradle_data) { const double data_size_inverse = 1 / double(move_bounds.size()); double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES; auto dur_inc = std::chrono::duration_values::zero(); auto dur_merge = std::chrono::duration_values::zero(); - - const auto dur_inc_recent = std::chrono::duration_values::zero(); - const auto dur_merge_recent = std::chrono::duration_values::zero(); + auto dur_cradle = std::chrono::duration_values::zero(); LayerIndex last_merge = move_bounds.size(); bool new_element = false; @@ -1298,6 +1708,23 @@ void TreeSupport::createLayerPathing(std::vector>& 3000 / config.layer_height); size_t merge_every_x_layers = 1; + + std::vector> all_cradles_with_line_presence(move_bounds.size()); + for (LayerIndex layer_idx = 0; layer_idx < cradle_data.size(); layer_idx++) + { + for (size_t cradle_idx = 0; cradle_idx < cradle_data[layer_idx].size(); cradle_idx++) + { + for (size_t line_idx = 0; line_idx < cradle_data[layer_idx][cradle_idx]->lines_.size(); line_idx++) + { + for (size_t height_idx = 0; height_idx < cradle_data[layer_idx][cradle_idx]->lines_[line_idx].size(); height_idx++) + { + LayerIndex cradle_layer_idx = cradle_data[layer_idx][cradle_idx]->lines_[line_idx][height_idx].layer_idx_; + all_cradles_with_line_presence[cradle_layer_idx].emplace_back(cradle_data[layer_idx][cradle_idx], cradle_layer_idx, line_idx); + } + } + } + } + // Calculate the influence areas for each layer below (Top down) // This is done by first increasing the influence area by the allowed movement distance, and merging them with other influence areas if possible for (const auto layer_idx : ranges::views::iota(1UL, move_bounds.size()) | ranges::views::reverse) @@ -1313,13 +1740,12 @@ void TreeSupport::createLayerPathing(std::vector>& PropertyAreas influence_areas; // Over this map will be iterated when merging, as such it has to be ordered to ensure deterministic results. PropertyAreas to_model_areas; // The area of these SupportElement is not set, to avoid to much allocation and deallocation on the heap. PropertyAreasUnordered to_bp_areas; // Same. - std::vector - bypass_merge_areas; // Different to the other maps of SupportElements as these here have the area already set, as they are already to be inserted into move_bounds. + PropertyAreas bypass_merge_areas; const auto time_a = std::chrono::high_resolution_clock::now(); std::vector last_layer; - last_layer.insert(last_layer.begin(), move_bounds[layer_idx].begin(), move_bounds[layer_idx].end()); + last_layer.insert(last_layer.end(), move_bounds[layer_idx].begin(), move_bounds[layer_idx].end()); // ### Increase the influence areas by the allowed movement distance increaseAreas(to_bp_areas, to_model_areas, influence_areas, bypass_merge_areas, last_layer, layer_idx, merge_this_layer); @@ -1339,12 +1765,19 @@ void TreeSupport::createLayerPathing(std::vector>& merge_every_x_layers = std::min(max_merge_every_x_layers, merge_every_x_layers + 1); } } + new_element = ! move_bounds[layer_idx - 1].empty(); const auto time_c = std::chrono::high_resolution_clock::now(); + // ### Cradle lines may be removed, causing tips to be removed. + if (layer_idx > 0) + { + handleCradleLineValidity(to_bp_areas, to_model_areas, influence_areas, bypass_merge_areas, layer_idx - 1, move_bounds, all_cradles_with_line_presence); + } + const auto time_d = std::chrono::high_resolution_clock::now(); + dur_inc += time_b - time_a; dur_merge += time_c - time_b; - - new_element = ! move_bounds[layer_idx - 1].empty(); + dur_cradle += time_d - time_c; // Save calculated elements to output, and allocate Polygons on heap, as they will not be changed again. for (std::pair tup : influence_areas) @@ -1361,20 +1794,27 @@ void TreeSupport::createLayerPathing(std::vector>& } // Place already fully constructed elements in the output. - for (TreeSupportElement* elem : bypass_merge_areas) + for (std::pair tup : bypass_merge_areas) { - if (elem->area_->area() < 1) + const TreeSupportElement elem = tup.first; + Shape* new_area = new Shape(TreeSupportUtils::safeUnion(tup.second)); + TreeSupportElement* next = new TreeSupportElement(elem, new_area); + move_bounds[layer_idx - 1].emplace(next); + if (new_area->area() < 1) { spdlog::error("Insert Error of Influence area bypass on layer {}.", layer_idx - 1); } - move_bounds[layer_idx - 1].emplace(elem); } progress_total += data_size_inverse * TREE_PROGRESS_AREA_CALC; Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * progress_multiplier + progress_offset, TREE_PROGRESS_TOTAL); } - spdlog::info("Time spent with creating influence areas' subtasks: Increasing areas {} ms merging areas: {} ms", dur_inc.count() / 1000000, dur_merge.count() / 1000000); + spdlog::info( + "Time spent with creating influence areas' subtasks: Increasing areas {} ms merging areas: {} ms CradleLineValidity: {} ms ", + dur_inc.count() / 1000000, + dur_merge.count() / 1000000, + dur_cradle.count() / 1000000); } void TreeSupport::setPointsOnAreas(const TreeSupportElement* elem) @@ -1503,7 +1943,7 @@ bool TreeSupport::setToModelContact(std::vector>& { Point2LL best = first_elem->next_position_; Shape valid_place_area - = first_elem->area_->difference(volumes_.getAvoidance(config.getCollisionRadius(first_elem), layer_idx, AvoidanceType::COLLISION, first_elem->use_min_xy_dist_)); + = first_elem->area_->difference(volumes_.getAvoidance(config.getCollisionRadius(*first_elem), layer_idx, AvoidanceType::COLLISION, first_elem->use_min_xy_dist_)); if (! valid_place_area.inside(best, true)) { @@ -1514,8 +1954,8 @@ bool TreeSupport::setToModelContact(std::vector>& else { bool found_partial_placement; - for (coord_t radius_offset : { -config.getCollisionRadius(first_elem), - -config.getCollisionRadius(first_elem) / 2, + for (coord_t radius_offset : { -config.getCollisionRadius(*first_elem), + -config.getCollisionRadius(*first_elem) / 2, coord_t(0) }) // Interestingly the first radius is working most of the time, even though it seems like it shouldn't. { valid_place_area = first_elem->area_->intersection(volumes_.getAccumulatedPlaceable0(layer_idx).offset(radius_offset)); @@ -1526,7 +1966,7 @@ bool TreeSupport::setToModelContact(std::vector>& "Not able to place branch fully on non support blocker at layer {} using offset {} for radius {}", layer_idx, radius_offset, - config.getCollisionRadius(first_elem)); + config.getCollisionRadius(*first_elem)); found_partial_placement = true; break; } @@ -1684,7 +2124,7 @@ void TreeSupport::generateBranchAreas( for (TreeSupportElement* parent : elem->parents_) { Point2LL movement = (parent->result_on_layer_ - elem->result_on_layer_); - movement_directions.emplace_back(movement, std::max(config.getRadius(parent), config.support_line_width)); + movement_directions.emplace_back(movement, std::max(config.getRadius(*parent), config.support_line_width)); parent_uses_min |= parent->use_min_xy_dist_; } @@ -1946,57 +2386,342 @@ void TreeSupport::dropNonGraciousAreas( }); } - -void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) +void TreeSupport::prepareSupportAreas( + std::vector& support_layer_storage, + std::vector& support_layer_storage_fractional, + std::vector& support_roof_storage, + std::vector& support_roof_extra_wall_storage, + std::vector& support_roof_storage_fractional, + std::vector& support_roof_extra_wall_storage_fractional, + std::vector& fake_roof_areas_combined, + std::vector& cradle_base_areas, + std::vector& cradle_support_line_areas, + SliceDataStorage& storage, + std::vector>& cradle_data) { + using ShapeWithStart = std::pair; const auto t_start = std::chrono::high_resolution_clock::now(); - - const coord_t closing_dist = config.support_line_width * config.support_wall_count; const coord_t open_close_distance = config.fill_outline_gaps ? config.min_feature_size / 2 - 5 : config.min_wall_line_width / 2 - 5; // based on calculation in WallToolPath const double small_area_length = INT2MM(static_cast(config.support_line_width) / 2); + const bool print_cradle_towards_model = true; //todo make setting - std::function reversePolygon = [&](Shape& poly) - { - for (size_t idx = 0; idx < poly.size(); idx++) - { - poly[idx].reverse(); - } - }; + std::vector> cradle_support_line_roof_areas_with_start(support_layer_storage.size()); // All cradle lines that have to be added as roof + std::vector> cradle_support_line_areas_with_start(support_layer_storage.size()); // All cradle lines that have to be added as roof + std::vector cradle_line_xy_distance_areas(support_layer_storage.size()); // All cradle lines offset by xy distance. + std::vector missing_cradle_line_xy_distance_areas(support_layer_storage.size()); // All missing (because of cradle z distance) cradle lines offset by xy distance. + + std::mutex critical_cradle_line_xy_distance_areas; + std::mutex critical_cradle_support_line_areas; + std::mutex critical_support_roof_storage; + std::mutex critical_support_layer_storage; - std::vector support_holes(support_layer_storage.size(), Shape()); - // Extract all holes as polygon objects cura::parallel_for( 0, - support_layer_storage.size(), + cradle_data.size(), [&](const LayerIndex layer_idx) { - support_layer_storage[layer_idx] = config.simplifier.polygon(PolygonUtils::unionManySmall(support_layer_storage[layer_idx].smooth(FUDGE_LENGTH))) - .offset(-open_close_distance) - .offset(open_close_distance * 2) - .offset(-open_close_distance); - support_layer_storage[layer_idx].removeSmallAreas(small_area_length * small_area_length, false); - - std::vector parts = support_layer_storage[layer_idx].sortByNesting(); - - if (parts.size() <= 1) + for (size_t cradle_idx = 0; cradle_idx < cradle_data[layer_idx].size(); cradle_idx++) { - return; - } + for (auto [base_idx, base] : cradle_data[layer_idx][cradle_idx]->base_below_ | ranges::views::enumerate) + { + if (cradle_data[layer_idx][cradle_idx]->is_roof_) + { + std::lock_guard critical_section_cradle(critical_support_roof_storage); + cradle_base_areas[layer_idx - base_idx].push_back(base); + (config.support_roof_wall_count ? support_roof_storage : support_roof_extra_wall_storage)[layer_idx - base_idx].push_back(base); + if (base_idx == 0 && config.z_distance_top % config.layer_height != 0 && layer_idx + 1 < support_roof_extra_wall_storage_fractional.size()) + { + (config.support_roof_wall_count ? support_roof_storage_fractional : support_roof_extra_wall_storage_fractional)[layer_idx + 1].push_back(base); + } + } + else + { + // Dead code. Currently, Cradles that are not roofs do not have a base area, just a tip. This is just here for the case that this changes + std::lock_guard critical_section_cradle(critical_support_layer_storage); + cradle_base_areas[layer_idx - base_idx].push_back(base); + support_layer_storage[layer_idx - base_idx].push_back(base); + if (base_idx == 0 && config.z_distance_top % config.layer_height != 0 && layer_idx + 1 < support_layer_storage_fractional.size()) + { + support_layer_storage_fractional[layer_idx + 1].push_back(base); + } + } + } - Shape holes_original; - for (const size_t idx : ranges::views::iota(1UL, parts.size())) - { - Shape area = parts[idx]; + for (size_t line_idx = 0; line_idx < cradle_data[layer_idx][cradle_idx]->lines_.size(); line_idx++) + { + if (! cradle_data[layer_idx][cradle_idx]->lines_[line_idx].empty()) + { + Shape previous_line_area = cradle_data[layer_idx][cradle_idx]->lines_[line_idx].back().area_; + LayerIndex previous_layer_idx = cradle_data[layer_idx][cradle_idx]->lines_[line_idx].back().layer_idx_; + for (int64_t height_idx = cradle_data[layer_idx][cradle_idx]->lines_[line_idx].size() - 1; height_idx >= 0; height_idx--) + { + if(cradle_data[layer_idx][cradle_idx]->lines_[line_idx][height_idx].area_.empty()) + { + continue; + } + + SingleShape line_area = cradle_data[layer_idx][cradle_idx]->lines_[line_idx][height_idx].area_.splitIntoParts(false).front(); //todo prettier. + bool is_roof = cradle_data[layer_idx][cradle_idx]->lines_[line_idx][height_idx].is_roof_; + LayerIndex cradle_line_layer_idx = cradle_data[layer_idx][cradle_idx]->lines_[line_idx][height_idx].layer_idx_; + bool is_base = cradle_data[layer_idx][cradle_idx]->lines_[line_idx][height_idx].is_base_; + bool was_line_above = height_idx + 1 < cradle_data[layer_idx][cradle_idx]->lines_[line_idx].size() && + ! cradle_data[layer_idx][cradle_idx]->lines_[line_idx][height_idx + 1].is_base_; + Point2LL front = cradle_data[layer_idx][cradle_idx]->lines_[line_idx][height_idx].line_.front(); + Point2LL back = cradle_data[layer_idx][cradle_idx]->lines_[line_idx][height_idx].line_.back(); + + if (was_line_above) + { + for (LayerIndex xy_dist_layer_idx = previous_layer_idx - 1; xy_dist_layer_idx > cradle_line_layer_idx; xy_dist_layer_idx--) + { + Shape line_areas = TreeSupportUtils::safeOffsetInc( + previous_line_area, + config.xy_distance, + volumes_.getCollision(0, xy_dist_layer_idx), + config.xy_min_distance + config.min_feature_size, + 0, + 0, + config.min_feature_size, + &config.simplifier); + std::lock_guard critical_section_cradle(critical_cradle_line_xy_distance_areas); + missing_cradle_line_xy_distance_areas[xy_dist_layer_idx].push_back(line_areas); + } + } + + if (is_roof) + { + std::lock_guard critical_section_cradle(critical_support_roof_storage); + + if (cradle_support_line_roof_areas_with_start.size() <= layer_idx) + { + cradle_support_line_roof_areas_with_start.resize(layer_idx + 1 + cradle_data[layer_idx][cradle_idx]->lines_[line_idx].size() - height_idx); + } + cradle_support_line_roof_areas_with_start[cradle_line_layer_idx].emplace_back(line_area, print_cradle_towards_model ? back : front); + } + else + { + std::lock_guard critical_section_cradle(critical_cradle_support_line_areas); + + if (cradle_support_line_areas.size() <= layer_idx) + { + cradle_support_line_areas.resize(layer_idx + 1 + cradle_data[layer_idx][cradle_idx]->lines_[line_idx].size() - height_idx); + } + cradle_support_line_areas[cradle_line_layer_idx].push_back(line_area); + + if (cradle_support_line_areas_with_start.size() <= layer_idx) + { + cradle_support_line_areas_with_start.resize(layer_idx + 1 + cradle_data[layer_idx][cradle_idx]->lines_[line_idx].size() - height_idx); + } + cradle_support_line_areas_with_start[cradle_line_layer_idx].emplace_back(line_area, print_cradle_towards_model ? back : front); + } + if (! is_base) + { + Shape line_areas = TreeSupportUtils::safeOffsetInc( + line_area, + config.xy_distance, + volumes_.getCollision(0, cradle_line_layer_idx), + config.xy_min_distance + config.min_feature_size, + 0, + 0, + config.min_feature_size, + &config.simplifier); + std::lock_guard critical_section_cradle(critical_cradle_line_xy_distance_areas); + cradle_line_xy_distance_areas[cradle_line_layer_idx].push_back(line_areas); + } + previous_layer_idx = cradle_line_layer_idx; + previous_line_area = line_area; + } + } + } + } + }); + + cura::parallel_for( + 0, + support_layer_storage.size(), + [&](const LayerIndex layer_idx) + { + Shape fake_roof; + Shape fake_roof_lines; + + for (FakeRoofArea& f_roof : fake_roof_areas[layer_idx]) + { + fake_roof.push_back(f_roof.area_); + fake_roof_lines.push_back( + TreeSupportUtils::generateSupportInfillLines(f_roof.area_, config, false, layer_idx, f_roof.line_distance_, storage.support.cross_fill_provider, 0) + .offset(config.support_line_width / 2)); + } + fake_roof_lines = fake_roof_lines.unionPolygons(); + fake_roof = fake_roof.unionPolygons(); + fake_roof_areas_combined[layer_idx] = fake_roof; + + + Shape remove_from_support = cradle_line_xy_distance_areas[layer_idx]; + remove_from_support.push_back(missing_cradle_line_xy_distance_areas[layer_idx]); + remove_from_support.push_back(fake_roof_lines); + remove_from_support.push_back(support_free_areas[layer_idx]); + remove_from_support = remove_from_support.unionPolygons(); + + support_layer_storage[layer_idx] = config.simplifier.polygon(PolygonUtils::unionManySmall(support_layer_storage[layer_idx].smooth(FUDGE_LENGTH))) + .offset(-open_close_distance) + .offset(open_close_distance * 2) + .offset(-open_close_distance); + support_layer_storage_fractional[layer_idx] = support_layer_storage_fractional[layer_idx].unionPolygons(); + Shape original_fractional = support_layer_storage_fractional[layer_idx]; + support_layer_storage_fractional[layer_idx] = support_layer_storage_fractional[layer_idx].difference(support_layer_storage[layer_idx]); + // ensure there is at lease one line space for fractional support. Overlap is removed later! + support_layer_storage_fractional[layer_idx] = support_layer_storage_fractional[layer_idx].offset(config.support_line_width).intersection(original_fractional); + + support_layer_storage[layer_idx] = support_layer_storage[layer_idx].difference(remove_from_support); + support_layer_storage_fractional[layer_idx] = support_layer_storage_fractional[layer_idx].difference(remove_from_support); + support_layer_storage[layer_idx].removeSmallAreas(small_area_length * small_area_length, false); + support_layer_storage_fractional[layer_idx].removeSmallAreas(small_area_length * small_area_length, false); + + + support_roof_storage[layer_idx] = support_roof_storage[layer_idx].unionPolygons(); + support_roof_storage_fractional[layer_idx] = support_roof_storage_fractional[layer_idx].unionPolygons(); + + support_roof_extra_wall_storage[layer_idx] = support_roof_extra_wall_storage[layer_idx].unionPolygons(); + support_roof_extra_wall_storage_fractional[layer_idx] = support_roof_extra_wall_storage_fractional[layer_idx].unionPolygons(); + + cradle_line_xy_distance_areas[layer_idx] = cradle_line_xy_distance_areas[layer_idx].unionPolygons(); + cradle_base_areas[layer_idx] = cradle_base_areas[layer_idx].unionPolygons(); + // If areas are overwriting others in can will influence where support skin will be generated. So the differences have to be calculated here. + if (! storage.support.supportLayers[layer_idx].support_roof.empty()) + { + switch (config.interface_preference) + { + case InterfacePreference::INTERFACE_AREA_OVERWRITES_SUPPORT: + { + Shape all_roof = storage.support.supportLayers[layer_idx].getTotalAreaFromParts(storage.support.supportLayers[layer_idx].support_roof); + all_roof.push_back(support_roof_storage[layer_idx]); + all_roof.push_back(support_roof_storage_fractional[layer_idx]); + all_roof.push_back(support_roof_extra_wall_storage[layer_idx]); + all_roof.push_back(support_roof_extra_wall_storage_fractional[layer_idx]); + all_roof = all_roof.unionPolygons(); + support_layer_storage[layer_idx] = support_layer_storage[layer_idx].difference(all_roof); + support_layer_storage_fractional[layer_idx] = support_layer_storage_fractional[layer_idx].difference(all_roof); + break; + } + + case InterfacePreference::SUPPORT_AREA_OVERWRITES_INTERFACE: + { + Shape existing_roof = storage.support.supportLayers[layer_idx].getTotalAreaFromParts(storage.support.supportLayers[layer_idx].support_roof); + Shape support_areas = support_layer_storage[layer_idx].unionPolygons(support_layer_storage_fractional[layer_idx]); + Shape invalid_roof = existing_roof.intersection(support_layer_storage[layer_idx]); + AABB invalid_roof_aabb = AABB(invalid_roof); + storage.support.supportLayers[layer_idx].excludeAreasFromSupportInfillAreas(storage.support.supportLayers[layer_idx].support_roof, invalid_roof, invalid_roof_aabb); + support_roof_storage[layer_idx] = support_roof_storage[layer_idx].difference(support_areas); + support_roof_extra_wall_storage[layer_idx] = support_roof_extra_wall_storage[layer_idx].difference(support_areas); + support_roof_storage_fractional[layer_idx] = support_roof_storage_fractional[layer_idx].difference(support_areas); + support_roof_extra_wall_storage_fractional[layer_idx] = support_roof_extra_wall_storage_fractional[layer_idx].difference(support_areas); + break; + } + default: + break; + } + } + Shape remove_from_next_roof = storage.support.supportLayers[layer_idx].getTotalAreaFromParts(storage.support.supportLayers[layer_idx].support_roof).unionPolygons(); + if (! support_free_areas[layer_idx].empty()) + { + remove_from_next_roof.push_back(support_free_areas[layer_idx]); + } + + //Add cradle lines. These need start hints, so its best done early! + for(ShapeWithStart& line : cradle_support_line_roof_areas_with_start[layer_idx]) + { + storage.support.supportLayers[layer_idx].support_roof.emplace_back(line.first, config.support_roof_line_width, false, std::min(1, config.support_roof_wall_count), 0, EFillMethod::NONE, std::optional(line.second)); + remove_from_next_roof.push_back(line.first); + } + + for(ShapeWithStart& line : cradle_support_line_areas_with_start[layer_idx]) + { + storage.support.supportLayers[layer_idx].support_infill_parts.emplace_back(line.first, config.support_line_width, false, std::min(1, config.support_wall_count), 0, EFillMethod::NONE, std::optional(line.second)); + } + + cradle_support_line_areas[layer_idx] = cradle_support_line_areas[layer_idx].unionPolygons().difference(remove_from_next_roof); + + //Collect remaining parts that non cradle line roof areas may not intersect with. + remove_from_next_roof.push_back(cradle_line_xy_distance_areas[layer_idx]); + remove_from_next_roof = remove_from_next_roof.unionPolygons(); + + Shape remove_from_next_fractional_roof = remove_from_next_roof; + remove_from_next_roof = remove_from_next_roof.unionPolygons(missing_cradle_line_xy_distance_areas[layer_idx]); + + Shape roof_extra_wall = support_roof_extra_wall_storage[layer_idx].difference(remove_from_next_roof); + Shape roof = support_roof_storage[layer_idx]; + if (config.support_roof_wall_count) + { + roof = roof.difference(remove_from_next_roof); + } + else + { + roof_extra_wall = roof_extra_wall.unionPolygons(); + roof = roof.difference(remove_from_next_roof.unionPolygons(roof_extra_wall)); + } + + storage.support.supportLayers[layer_idx].fillRoofParts(roof_extra_wall, config.support_roof_line_width, config.support_wall_count, false); + storage.support.supportLayers[layer_idx].fillRoofParts(roof, config.support_roof_line_width, config.support_roof_wall_count, false); + + remove_from_next_fractional_roof.push_back(roof_extra_wall); + remove_from_next_fractional_roof.push_back(roof); + remove_from_next_fractional_roof = remove_from_next_fractional_roof.unionPolygons(); + + Shape fractional_roof_extra_wall = support_roof_extra_wall_storage_fractional[layer_idx].difference(remove_from_next_fractional_roof); + storage.support.supportLayers[layer_idx].fillRoofParts(fractional_roof_extra_wall, config.support_roof_line_width, config.support_wall_count, true); + + Shape fractional_roof = support_roof_storage_fractional[layer_idx].difference(remove_from_next_fractional_roof.unionPolygons(fractional_roof_extra_wall)); + storage.support.supportLayers[layer_idx].fillRoofParts(fractional_roof, config.support_roof_line_width, config.support_roof_wall_count, true); + }); +} + + +void TreeSupport::calculateSupportHoles(std::vector& support_layer_storage, + std::vector>& hole_parts, + std::vector>& valid_holes, + std::vector>& non_removable_holes, + std::vector>>& hole_rest_map) +{ + + std::function reversePolygon = [&](Shape& poly) + { + for (size_t idx = 0; idx < poly.size(); idx++) + { + poly[idx].reverse(); + } + }; + + + std::vector support_holes(support_layer_storage.size(), Shape()); + + // Extract all holes as polygon objects + cura::parallel_for( + + 0, + support_layer_storage.size(), + [&](const LayerIndex layer_idx) + { + std::vector parts = support_layer_storage[layer_idx].sortByNesting(); + + if (parts.size() <= 1) + { + return; + } + + Shape holes_original; + for (const size_t idx : ranges::views::iota(1UL, parts.size())) + { + Shape area = parts[idx]; reversePolygon(area); holes_original.push_back(area); } support_holes[layer_idx] = holes_original; }); - const auto t_union = std::chrono::high_resolution_clock::now(); - - std::vector> holeparts(support_layer_storage.size()); + hole_parts.resize(support_layer_storage.size()); + valid_holes.resize(support_layer_storage.size()); + non_removable_holes.resize(support_layer_storage.size()); + hole_rest_map.resize(support_layer_storage.size()); // Split all holes into parts cura::parallel_for( 0, @@ -2005,12 +2730,9 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) { for (Shape hole : support_holes[layer_idx].splitIntoParts()) { - holeparts[layer_idx].emplace_back(hole); + hole_parts[layer_idx].emplace_back(hole); } }); - std::vector>> hole_rest_map(holeparts.size()); - std::vector> holes_resting_outside(holeparts.size()); - // Figure out which hole rests on which other hole cura::parallel_for( @@ -2018,37 +2740,32 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) support_layer_storage.size(), [&](const LayerIndex layer_idx) { - if (holeparts[layer_idx].empty()) + if (hole_parts[layer_idx].empty()) { return; } const Shape& relevant_forbidden = volumes_.getCollision(0, layer_idx, true); - Shape outer_walls = TreeSupportUtils::toPolylines(support_layer_storage[layer_idx - 1].getOutsidePolygons()).createTubeShape(closing_dist, 0); - - Shape holes_below; + Shape outer_walls + = TreeSupportUtils::toPolylines(support_layer_storage[layer_idx - 1].getOutsidePolygons()).createTubeShape(config.support_line_width * config.support_wall_count, 0); - for (auto poly : holeparts[layer_idx - 1]) - { - holes_below.push_back(poly); - } - for (auto [idx, hole] : holeparts[layer_idx] | ranges::views::enumerate) + for (auto [idx, hole] : hole_parts[layer_idx] | ranges::views::enumerate) { AABB hole_aabb = AABB(hole); hole_aabb.expand(EPSILON); if (! hole.intersection(PolygonUtils::clipPolygonWithAABB(outer_walls, hole_aabb)).empty()) { - holes_resting_outside[layer_idx].emplace(idx); - } - else if (hole.intersection(PolygonUtils::clipPolygonWithAABB(relevant_forbidden, hole_aabb)).area() > hole.length() * EPSILON) - { - holes_resting_outside[layer_idx].emplace( - idx); // technically not resting outside, also not valid, but the alternative is potentially having lines go though the model + valid_holes[layer_idx].emplace(idx); } else { - for (auto [idx2, hole2] : holeparts[layer_idx - 1] | ranges::views::enumerate) + if(!hole.intersection(PolygonUtils::clipPolygonWithAABB(relevant_forbidden, hole_aabb)).offset(- config.xy_min_distance / 2).empty()) + { + non_removable_holes[layer_idx].emplace(idx); // technically not resting outside, also not valid, but the alternative is potentially having lines go though the model + } + + for (auto [idx2, hole2] : hole_parts[layer_idx - 1] | ranges::views::enumerate) { if (hole_aabb.hit(AABB(hole2)) && ! hole.intersection(hole2).empty()) // TODO should technically be outline: Check if this is fine either way as it would save an offset @@ -2059,20 +2776,258 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) } } }); +} + + + +void TreeSupport::generateSupportSkin( + std::vector& support_layer_storage, + std::vector& support_skin_storage, + std::vector& fake_roof_areas_combined, + std::vector& cradle_base_areas, + std::vector& cradle_support_line_areas, + std::vector>& hole_parts, + std::vector>& valid_holes, + std::vector>& non_removable_holes, + std::vector>>& hole_rest_map, + SliceDataStorage& storage, + std::vector>& layer_tree_polygons) +{ + std::mutex critical_support_layer_storage; + + if (config.support_skin_layers) + { + cura::parallel_for( + 0, + support_layer_storage.size(), + [&](const LayerIndex layer_idx) + { + if (support_layer_storage[layer_idx].empty()) + { + return; + } + + const coord_t roof_stable_range_after_contact = config.support_roof_line_width * (config.support_roof_wall_count + 0.5); + Shape support_shell_capable_of_supporting_roof + = support_layer_storage[layer_idx] + .getOutsidePolygons() + .createTubeShape( + config.support_line_width * (config.support_wall_count + 0.5) + roof_stable_range_after_contact, + roof_stable_range_after_contact, + ClipperLib::JoinType::jtRound) + .unionPolygons() + .offset(-config.support_line_width / 4) + .offset(config.support_line_width / 4); // Getting rid of small rounding errors. If an area thinner than 1/2 line-width said it needs skin, it is lying. + Shape needs_supporting; + if (storage.support.supportLayers.size() > layer_idx + 1) + { + Shape roof_above = storage.support.supportLayers[layer_idx + 1].getTotalAreaFromParts(storage.support.supportLayers[layer_idx + 1].support_roof); - const auto t_hole_rest_ordering = std::chrono::high_resolution_clock::now(); + needs_supporting.push_back(roof_above.difference(support_shell_capable_of_supporting_roof)); + needs_supporting.push_back(fake_roof_areas_combined[layer_idx + 1].difference(support_shell_capable_of_supporting_roof)); + needs_supporting.push_back(cradle_support_line_areas[layer_idx + 1]); + for(size_t hole_idx : non_removable_holes[layer_idx + 1]) + { + bool rests_on_another = false; + for(size_t hole_idx_below : hole_rest_map[layer_idx + 1][hole_idx]) + { + if(valid_holes[layer_idx].contains(hole_idx_below) || non_removable_holes[layer_idx].contains(hole_idx_below)) + { + rests_on_another = true; + break; + } + } + + if(!rests_on_another) // Assuming that because of xy distance any hole below will be large enough to support this hole. + { + //Offset required as it is not the hole that needs support, but the surrounding walls! + needs_supporting.push_back(hole_parts[layer_idx + 1][hole_idx].offset(config.support_line_width * config.support_wall_count + FUDGE_LENGTH)); + } + } + + } + needs_supporting.push_back(cradle_base_areas[layer_idx]); // cradle bases should be skin. + + needs_supporting = needs_supporting.unionPolygons(); + + Shape existing_roof = storage.support.supportLayers[layer_idx].getTotalAreaFromParts(storage.support.supportLayers[layer_idx].support_roof); + + Shape already_supports; + already_supports.push_back(existing_roof); // roof + already_supports.push_back(fake_roof_areas_combined[layer_idx]); + already_supports.push_back(support_layer_storage[layer_idx].getOutsidePolygons().createTubeShape(config.support_line_width * config.support_wall_count, 0)); + already_supports.push_back(cradle_support_line_areas[layer_idx]); + already_supports = already_supports.unionPolygons().offset(FUDGE_LENGTH).unionPolygons(); + + Shape may_need_skin_area_topmost = needs_supporting.difference(already_supports); + + for (std::pair data_pair : layer_tree_polygons[layer_idx]) + { + bool has_parent_roof = false; + + if (data_pair.first->supports_roof_) + { + for (auto parent : data_pair.first->parents_) + { + has_parent_roof |= (data_pair.first->missing_roof_layers_ > data_pair.first->distance_to_top_); + } + } + + bool element_viable_for_skin = has_parent_roof; + element_viable_for_skin + |= data_pair.first->parents_.empty() && (config.getRadius(*data_pair.first) >= config.support_tree_skin_for_large_tips_radius_threshold); + + if (element_viable_for_skin) + { + may_need_skin_area_topmost.push_back(data_pair.second); + } + } + + may_need_skin_area_topmost = may_need_skin_area_topmost.unionPolygons(); + Shape may_need_skin_area = may_need_skin_area_topmost; + + for (LayerIndex support_skin_ctr = 0; support_skin_ctr < std::min(LayerIndex(config.support_skin_layers), layer_idx); support_skin_ctr++) + { + Shape next_skin; + Shape support_on_layer; + + Shape remaining_regular_areas; + + { + std::lock_guard critical_section_cradle(critical_support_layer_storage); + support_on_layer = support_layer_storage[layer_idx - support_skin_ctr]; + } + + if (support_skin_ctr > 0) + { + may_need_skin_area_topmost = may_need_skin_area_topmost.intersection(support_on_layer); + } + + for (Shape part : support_on_layer.splitIntoParts()) + { + Shape part_outline = part.getOutsidePolygons().difference(volumes_.getCollision(0, layer_idx - support_skin_ctr, true)); + if (! PolygonUtils::clipPolygonWithAABB(may_need_skin_area, AABB(part_outline)).empty()) + { + // Use line infill to scan which area the line infill has to occupy to reach the outer outline of a branch. + OpenLinesSet scan_lines = TreeSupportUtils::generateSupportInfillLines( + part_outline, + config, + false, + layer_idx - support_skin_ctr, + config.support_skin_line_distance, + nullptr, + 0, + EFillMethod::LINES, + true); + OpenLinesSet intersecting_lines; + for (auto line : scan_lines) + { + bool valid_for_may_need_skin = PolygonUtils::polygonCollidesWithLineSegment(may_need_skin_area, line.front(), line.back()) + || may_need_skin_area.inside(line.front()) || may_need_skin_area.inside(line.back()); + bool valid_for_may_need_skin_area_topmost = PolygonUtils::polygonCollidesWithLineSegment(may_need_skin_area_topmost, line.front(), line.back()) + || may_need_skin_area_topmost.inside(line.front()) || may_need_skin_area_topmost.inside(line.back()); + if (valid_for_may_need_skin && valid_for_may_need_skin_area_topmost) + { + intersecting_lines.addSegment(line.front(), line.back()); + } + } + Shape partial_skin_area + = intersecting_lines.offset(config.support_skin_line_distance + FUDGE_LENGTH).unionPolygons().intersection(part_outline); + + // If a some scan lines had contact with two parts of part_outline, but the second part is outside of may_need_skin_area it could cause a separate skin + // area, that then cuts a branch in half that could have been completely normal. This area that does not need to be skin will be filtered out here. + { + Shape filtered_partial_skin_area; + for (auto p_skin : partial_skin_area.splitIntoParts()) + { + if (! PolygonUtils::clipPolygonWithAABB(may_need_skin_area, AABB(p_skin)).intersection(p_skin).empty()) + { + filtered_partial_skin_area.push_back(p_skin); + } + } + partial_skin_area = filtered_partial_skin_area; + } + + double part_area = part.area(); + part = part.difference(partial_skin_area); + Shape remaining_part; + for (auto sub_part : part.splitIntoParts()) + { + // Prevent small slivers of a branch to generate as support. The heuristic to detect if a part is too small or thin could maybe be improved. + if ((sub_part.area() < part_area / 5 && sub_part.area() * 2 < std::numbers::pi * pow(config.branch_radius, 2)) + || sub_part.offset(-config.support_line_width).area() < 1) + { + partial_skin_area = partial_skin_area.unionPolygons(sub_part); + } + else + { + remaining_part.push_back(sub_part); + } + } + part = remaining_part; + next_skin.push_back(partial_skin_area.intersection(may_need_skin_area_topmost)); + std::lock_guard critical_section_cradle(critical_support_layer_storage); + support_skin_storage[layer_idx - support_skin_ctr].push_back(partial_skin_area); + } + + remaining_regular_areas.push_back(part); + } + may_need_skin_area = next_skin.unionPolygons(); + } + }); + } + + cura::parallel_for( + 0, + support_layer_storage.size(), + [&](const LayerIndex layer_idx) + { + support_skin_storage[layer_idx] = support_skin_storage[layer_idx].unionPolygons(); + support_layer_storage[layer_idx] = support_layer_storage[layer_idx].unionPolygons().difference(support_skin_storage[layer_idx]); + + if(layer_idx > 0) + { + for (auto [idx, hole] : hole_parts[layer_idx] | ranges::views::enumerate) + { + AABB hole_aabb = AABB(hole); + hole_aabb.expand(EPSILON); + if(hole.difference(PolygonUtils::clipPolygonWithAABB(support_skin_storage[layer_idx], hole_aabb)).empty()) + { + // The Hole was replaced by skin, remove it. + // All holes that rest on this are valid, but that is already ensured below + hole_parts[layer_idx][idx] = Shape(); + } + else if (! hole.intersection(PolygonUtils::clipPolygonWithAABB(support_skin_storage[layer_idx - 1], hole_aabb)).empty()) + { + valid_holes[layer_idx].emplace(idx); + } + } + } + }); +} + +void TreeSupport::removeFloatingHoles(std::vector& support_layer_storage, + std::vector>& hole_parts, + std::vector>& valid_holes, + std::vector>& non_removable_holes, + std::vector>>& hole_rest_map) +{ std::unordered_set removed_holes_by_idx; - std::vector valid_holes(support_holes.size(), Shape()); + std::vector valid_holes_areas(hole_parts.size(), Shape()); // Check which holes have to be removed as they do not rest on anything. Only keep holes that have to be removed - for (const size_t layer_idx : ranges::views::iota(1UL, support_holes.size())) + for (const size_t layer_idx : ranges::views::iota(1UL, hole_parts.size())) { std::unordered_set next_removed_holes_by_idx; - for (auto [idx, hole] : holeparts[layer_idx] | ranges::views::enumerate) + for (auto [idx, hole] : hole_parts[layer_idx] | ranges::views::enumerate) { bool found = false; - if (holes_resting_outside[layer_idx].contains(idx)) + if (valid_holes[layer_idx].contains(idx)) + { + found = true; + } + else if (non_removable_holes[layer_idx].contains(idx)) { found = true; } @@ -2096,8 +3051,8 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) } else { - valid_holes[layer_idx].push_back(hole); - holeparts[layer_idx][idx] = Shape(); // all remaining holes will have to be removed later, so removing the hole means it is confirmed valid! + valid_holes_areas[layer_idx].push_back(hole); + hole_parts[layer_idx][idx] = Shape(); // all remaining holes will have to be removed later, so removing the hole means it is confirmed valid! } } removed_holes_by_idx = next_removed_holes_by_idx; @@ -2111,36 +3066,20 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) support_layer_storage.size(), [&](const LayerIndex layer_idx) { - if (holeparts[layer_idx].empty()) + if (hole_parts[layer_idx].empty()) { return; } - support_layer_storage[layer_idx] = support_layer_storage[layer_idx].getOutsidePolygons(); - reversePolygon(valid_holes[layer_idx]); - support_layer_storage[layer_idx].push_back(valid_holes[layer_idx]); + //Because the support_layer_storage could be modified (e.g. because the holes are now skin), just adding back the reversed holes is no longer working. Need to do a real difference instead. + support_layer_storage[layer_idx] = support_layer_storage[layer_idx].getOutsidePolygons().difference(valid_holes_areas[layer_idx]); }); - - const auto t_end = std::chrono::high_resolution_clock::now(); - - const auto dur_union = 0.001 * std::chrono::duration_cast(t_union - t_start).count(); - const auto dur_hole_rest_ordering = 0.001 * std::chrono::duration_cast(t_hole_rest_ordering - t_union).count(); - const auto dur_hole_removal_tagging = 0.001 * std::chrono::duration_cast(t_hole_removal_tagging - t_hole_rest_ordering).count(); - - const auto dur_hole_removal = 0.001 * std::chrono::duration_cast(t_end - t_hole_removal_tagging).count(); - spdlog::debug( - "Time to union areas: {} ms Time to evaluate which hole rest on which other hole: {} ms Time to see which holes are not resting on anything valid: {} ms remove all holes " - "that are invalid and not close enough to a valid hole: {} ms", - dur_union, - dur_hole_rest_ordering, - dur_hole_removal_tagging, - dur_hole_removal); } void TreeSupport::finalizeInterfaceAndSupportAreas( std::vector& support_layer_storage, - std::vector& support_roof_storage, + std::vector& support_skin_storage, std::vector& support_layer_storage_fractional, SliceDataStorage& storage) { @@ -2155,50 +3094,59 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( support_layer_storage.size(), [&](const LayerIndex layer_idx) { - Shape fake_roof_lines; - - for (FakeRoofArea& f_roof : fake_roof_areas[layer_idx]) - { - fake_roof_lines.push_back( - TreeSupportUtils::generateSupportInfillLines(f_roof.area_, config, false, layer_idx, f_roof.line_distance_, storage.support.cross_fill_provider, false) - .offset(config.support_line_width / 2)); - } - fake_roof_lines = fake_roof_lines.unionPolygons(); - - support_layer_storage[layer_idx] = support_layer_storage[layer_idx].difference(fake_roof_lines); - - // Subtract support lines of the branches from the roof - storage.support.supportLayers[layer_idx].support_roof = storage.support.supportLayers[layer_idx].support_roof.unionPolygons(support_roof_storage[layer_idx]); - if (! storage.support.supportLayers[layer_idx].support_roof.empty() - && support_layer_storage[layer_idx].intersection(storage.support.supportLayers[layer_idx].support_roof).area() > 1) + if (! storage.support.supportLayers[layer_idx].support_roof.empty()) { switch (interface_pref) { case InterfacePreference::INTERFACE_AREA_OVERWRITES_SUPPORT: - support_layer_storage[layer_idx] = support_layer_storage[layer_idx].difference(storage.support.supportLayers[layer_idx].support_roof); + { + Shape existing_roof = storage.support.supportLayers[layer_idx].getTotalAreaFromParts(storage.support.supportLayers[layer_idx].support_roof); + support_layer_storage[layer_idx] = support_layer_storage[layer_idx].difference(existing_roof); + support_skin_storage[layer_idx] = support_skin_storage[layer_idx].difference(existing_roof); + support_layer_storage_fractional[layer_idx] = support_layer_storage_fractional[layer_idx].difference(existing_roof); break; + } case InterfacePreference::SUPPORT_AREA_OVERWRITES_INTERFACE: - storage.support.supportLayers[layer_idx].support_roof = storage.support.supportLayers[layer_idx].support_roof.difference(support_layer_storage[layer_idx]); + { + Shape existing_roof = storage.support.supportLayers[layer_idx].getTotalAreaFromParts(storage.support.supportLayers[layer_idx].support_roof); + Shape support_areas = support_layer_storage[layer_idx]; + support_areas.push_back(support_skin_storage[layer_idx]); + support_areas.push_back(support_layer_storage_fractional[layer_idx]); + Shape invalid_roof = existing_roof.intersection(support_areas.unionPolygons()); + AABB invalid_roof_aabb = AABB(invalid_roof); + storage.support.supportLayers[layer_idx].excludeAreasFromSupportInfillAreas(storage.support.supportLayers[layer_idx].support_roof, invalid_roof, invalid_roof_aabb); break; + } case InterfacePreference::INTERFACE_LINES_OVERWRITE_SUPPORT: { - Shape interface_lines = TreeSupportUtils::generateSupportInfillLines( - storage.support.supportLayers[layer_idx].support_roof, + Shape interface_lines; + + for (SupportInfillPart& roof_part : storage.support.supportLayers[layer_idx].support_roof) + { + interface_lines.push_back(TreeSupportUtils::generateSupportInfillLines( + roof_part.outline_, config, true, layer_idx, - config.support_roof_line_distance, + roof_part.custom_line_distance_ == 0 ? config.support_roof_line_distance : roof_part.custom_line_distance_, storage.support.cross_fill_provider, - true) - .offset(config.support_roof_line_width / 2); + roof_part.inset_count_to_generate_, + roof_part.custom_line_pattern_) + .offset(config.support_roof_line_width / 2)); + } + interface_lines = interface_lines.unionPolygons(); support_layer_storage[layer_idx] = support_layer_storage[layer_idx].difference(interface_lines); + support_skin_storage[layer_idx] = support_skin_storage[layer_idx].difference(interface_lines); + support_layer_storage_fractional[layer_idx] = support_layer_storage_fractional[layer_idx].difference(interface_lines); } break; case InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE: { + Shape existing_roof = storage.support.supportLayers[layer_idx].getTotalAreaFromParts(storage.support.supportLayers[layer_idx].support_roof); + Shape tree_lines; tree_lines = tree_lines.unionPolygons(TreeSupportUtils::generateSupportInfillLines( support_layer_storage[layer_idx], @@ -2207,9 +3155,24 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( layer_idx, config.support_line_distance, storage.support.cross_fill_provider, - true) + config.support_wall_count) .offset(config.support_line_width / 2)); - storage.support.supportLayers[layer_idx].support_roof = storage.support.supportLayers[layer_idx].support_roof.difference(tree_lines); + + Shape support_skin_lines; + support_skin_lines = support_skin_lines.unionPolygons(TreeSupportUtils::generateSupportInfillLines( + support_skin_storage[layer_idx], + config, + false, + layer_idx, + config.support_skin_line_distance, + storage.support.cross_fill_provider, + std::max(0, config.support_wall_count - 1), + EFillMethod::LINES) + .offset(config.support_line_width / 2)); + + Shape invalid_roof = existing_roof.intersection(support_skin_lines.unionPolygons(tree_lines)); + AABB invalid_roof_aabb = AABB(invalid_roof); + storage.support.supportLayers[layer_idx].excludeAreasFromSupportInfillAreas(storage.support.supportLayers[layer_idx].support_roof, invalid_roof, invalid_roof_aabb); // Do not draw roof where the tree is. I prefer it this way as otherwise the roof may cut of a branch from its support below. } break; @@ -2220,10 +3183,13 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( } // Subtract support floors from the support area and add them to the support floor instead. - if (config.support_bottom_layers > 0 && ! support_layer_storage[layer_idx].empty()) + if (config.support_bottom_layers > 0 && ! (support_layer_storage[layer_idx].empty() || support_skin_storage[layer_idx].empty())) { Shape floor_layer = storage.support.supportLayers[layer_idx].support_bottom; - Shape layer_outset = support_layer_storage[layer_idx].offset(config.support_bottom_offset).difference(volumes_.getCollision(0, layer_idx, false)); + Shape layer_outset = support_layer_storage[layer_idx] + .unionPolygons(support_skin_storage[layer_idx]) + .offset(config.support_bottom_offset) + .difference(volumes_.getCollision(0, layer_idx, false)); size_t layers_below = 0; while (layers_below <= config.support_bottom_layers) { @@ -2245,6 +3211,7 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( floor_layer = floor_layer.unionPolygons(); storage.support.supportLayers[layer_idx].support_bottom = storage.support.supportLayers[layer_idx].support_bottom.unionPolygons(floor_layer); support_layer_storage[layer_idx] = support_layer_storage[layer_idx].difference(floor_layer.offset(10)); // Subtract the support floor from the normal support. + support_skin_storage[layer_idx] = support_skin_storage[layer_idx].difference(floor_layer.offset(10)); // Subtract the support floor from the normal support. } }); @@ -2254,26 +3221,81 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( [&](const LayerIndex layer_idx) { constexpr bool convert_every_part = true; // Convert every part into a SingleShape for the support. - - storage.support.supportLayers[layer_idx] - .fillInfillParts(support_layer_storage[layer_idx], config.support_line_width, config.support_wall_count, false, convert_every_part); - - // This only works because fractional support is always just projected upwards regular support or skin. // Also technically violates skin height, but there is no good way to prevent that. Shape fractional_support; + Shape fractional_skin; + Shape support_areas = support_layer_storage[layer_idx]; + Shape skin_areas = support_skin_storage[layer_idx]; if (layer_idx > 0) { fractional_support = support_layer_storage_fractional[layer_idx].intersection(support_layer_storage[layer_idx - 1]); + fractional_skin = support_layer_storage_fractional[layer_idx].intersection(support_skin_storage[layer_idx - 1]); + + // To remove the lines it needs to be known what the lines are. This can not be done in the loop above, so it needs to be done here again for fractional support. + // todo deduplicate code + if (interface_pref == InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE) + { + Shape existing_roof = storage.support.supportLayers[layer_idx].getTotalAreaFromParts(storage.support.supportLayers[layer_idx].support_roof); + Shape tree_lines; + tree_lines = tree_lines.unionPolygons(TreeSupportUtils::generateSupportInfillLines( + fractional_support, + config, + false, + layer_idx, + config.support_line_distance, + storage.support.cross_fill_provider, + config.support_wall_count) + .offset(config.support_line_width / 2)); + + Shape support_skin_lines; + support_skin_lines = support_skin_lines.unionPolygons(TreeSupportUtils::generateSupportInfillLines( + fractional_skin, + config, + false, + layer_idx, + config.support_skin_line_distance, + storage.support.cross_fill_provider, + std::max(0, config.support_wall_count - 1), + EFillMethod::LINES) + .offset(config.support_line_width / 2)); + Shape invalid_roof = existing_roof.intersection(tree_lines); + AABB invalid_roof_aabb = AABB(invalid_roof); + storage.support.supportLayers[layer_idx].excludeAreasFromSupportInfillAreas(storage.support.supportLayers[layer_idx].support_roof, invalid_roof, invalid_roof_aabb); + } + + // Remove overlap between fractional and regular support that may have been created in generateSupportSkin. + support_areas = support_areas.difference(support_layer_storage_fractional[layer_idx]); + skin_areas = skin_areas.difference(support_layer_storage_fractional[layer_idx]); } else { fractional_support = support_layer_storage_fractional[layer_idx]; } + storage.support.supportLayers[layer_idx].fillInfillParts(support_areas, config.support_line_width, config.support_wall_count, false, convert_every_part); + + storage.support.supportLayers[layer_idx].fillInfillParts( + skin_areas, + config.support_line_width, + std::max(config.support_wall_count - 1, 0), + false, + convert_every_part, + config.support_skin_line_distance, + EFillMethod::ZIG_ZAG); + + storage.support.supportLayers[layer_idx].fillInfillParts(fractional_support, config.support_line_width, config.support_wall_count, true, convert_every_part); + storage.support.supportLayers[layer_idx].fillInfillParts( + fractional_skin, + config.support_line_width, + std::max(config.support_wall_count - 1, 0), + true, + convert_every_part, + config.support_skin_line_distance, + EFillMethod::ZIG_ZAG); for (FakeRoofArea& fake_roof : fake_roof_areas[layer_idx]) { @@ -2281,7 +3303,6 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( .fillInfillParts(fake_roof.area_, config.support_line_width, 0, fake_roof.fractional_, convert_every_part, fake_roof.line_distance_); } - { std::lock_guard critical_section_progress(critical_sections); progress_total += TREE_PROGRESS_FINALIZE_BRANCH_AREAS / support_layer_storage.size(); @@ -2298,12 +3319,22 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( }); } -void TreeSupport::drawAreas(std::vector>& move_bounds, SliceDataStorage& storage) +void TreeSupport::drawAreas(std::vector>& move_bounds, SliceDataStorage& storage, std::vector>& cradle_data) { std::vector support_layer_storage(move_bounds.size()); std::vector support_layer_storage_fractional(move_bounds.size()); std::vector support_roof_storage_fractional(move_bounds.size()); + std::vector support_roof_extra_wall_storage_fractional(move_bounds.size()); + std::vector support_skin_storage(move_bounds.size()); std::vector support_roof_storage(move_bounds.size()); + std::vector support_roof_extra_wall_storage(move_bounds.size()); + std::vector> hole_parts(move_bounds.size()); + std::vector> valid_holes(move_bounds.size()); + std::vector> non_removable_holes(move_bounds.size()); + std::vector>> hole_rest_map(move_bounds.size()); + std::vector fake_roof_areas_combined(move_bounds.size()); + std::vector cradle_base_areas(move_bounds.size()); + std::vector cradle_support_line_areas(move_bounds.size()); std::map inverse_tree_order; // In the tree structure only the parents can be accessed. Inverse this to be able to access the children. std::vector> @@ -2369,83 +3400,155 @@ void TreeSupport::drawAreas(std::vector>& move_bou { for (std::pair data_pair : layer_tree_polygons[layer_idx]) { - if (data_pair.first->missing_roof_layers_ > data_pair.first->distance_to_top_ - && TreeSupportUtils::generateSupportInfillLines(data_pair.second, config, true, layer_idx, config.support_roof_line_distance, nullptr, true).empty()) + if (data_pair.first->missing_roof_layers_ > data_pair.first->distance_to_top_ && config.support_roof_wall_count == 0) { - std::vector to_disable_roofs; - to_disable_roofs.emplace_back(data_pair.first); - while (! to_disable_roofs.empty()) + OpenLinesSet roof_lines = TreeSupportUtils::generateSupportInfillLines( + data_pair.second, + config, + true, + layer_idx, + config.support_roof_line_distance, + nullptr, + config.support_roof_wall_count); + if (roof_lines.length() < data_pair.second.length()) // arbitrary threshold to check if the interface pattern is propper. { - std::vector to_disable_roofs_next; - for (TreeSupportElement* elem : to_disable_roofs) + std::vector to_disable_roofs; + to_disable_roofs.emplace_back(data_pair.first); + while (! to_disable_roofs.empty()) { - elem->missing_roof_layers_ = 0; - if (data_pair.first->missing_roof_layers_ > data_pair.first->distance_to_top_ + 1) + std::vector to_disable_roofs_next; + for (TreeSupportElement* elem : to_disable_roofs) { - to_disable_roofs_next.emplace_back(inverse_tree_order[elem]); + elem->roof_with_enforced_walls = true; + if (data_pair.first->missing_roof_layers_ > data_pair.first->distance_to_top_ + 1) + { + to_disable_roofs_next.emplace_back(inverse_tree_order[elem]); + } } + to_disable_roofs = to_disable_roofs_next; } - to_disable_roofs = to_disable_roofs_next; } } } }); - cura::parallel_for( - 0, - layer_tree_polygons.size(), - [&](const size_t layer_idx) + for (const auto layer_idx : ranges::views::iota(0UL, layer_tree_polygons.size())) + { + for (std::pair data_pair : layer_tree_polygons[layer_idx]) { - for (std::pair data_pair : layer_tree_polygons[layer_idx]) + if (data_pair.first->parents_.empty() && ! data_pair.first->supports_roof_ && ! data_pair.first->cradle_line_ && layer_idx + 1 < support_roof_storage_fractional.size() + && config.z_distance_top % config.layer_height > 0) { - if (data_pair.first->parents_.empty() && ! data_pair.first->supports_roof_ && layer_idx + 1 < support_roof_storage_fractional.size() - && config.z_distance_top % config.layer_height > 0) + if (data_pair.first->missing_roof_layers_ > data_pair.first->distance_to_top_) { - if (data_pair.first->missing_roof_layers_ > data_pair.first->distance_to_top_) + if (data_pair.first->roof_with_enforced_walls) { - support_roof_storage_fractional[layer_idx + 1].push_back(data_pair.second); + support_roof_extra_wall_storage_fractional[layer_idx + 1].push_back(data_pair.second); } else { - support_layer_storage_fractional[layer_idx + 1].push_back(data_pair.second); + support_roof_storage_fractional[layer_idx + 1].push_back(data_pair.second); } } - - ((data_pair.first->missing_roof_layers_ > data_pair.first->distance_to_top_) ? support_roof_storage : support_layer_storage)[layer_idx].push_back(data_pair.second); + else + { + support_layer_storage_fractional[layer_idx + 1].push_back(data_pair.second); + } } - if (layer_idx + 1 < support_roof_storage_fractional.size()) + if (data_pair.first->missing_roof_layers_ > data_pair.first->distance_to_top_) { - support_roof_storage_fractional[layer_idx + 1] = support_roof_storage_fractional[layer_idx + 1].unionPolygons(); - support_layer_storage_fractional[layer_idx + 1] = support_layer_storage_fractional[layer_idx + 1].unionPolygons(); + if (data_pair.first->roof_with_enforced_walls) + { + support_roof_extra_wall_storage[layer_idx].push_back(data_pair.second); + } + else + { + support_roof_storage[layer_idx].push_back(data_pair.second); + } } - }); - - for (const auto layer_idx : ranges::views::iota(0UL, additional_required_support_area.size())) - { - if (support_layer_storage.size() > layer_idx) + else + { + support_layer_storage[layer_idx].push_back(data_pair.second); + } + } + if (layer_idx + 1 < support_roof_storage_fractional.size()) { - support_layer_storage[layer_idx].push_back(additional_required_support_area[layer_idx]); + support_roof_storage_fractional[layer_idx + 1] = support_roof_storage_fractional[layer_idx + 1].unionPolygons(); + support_layer_storage_fractional[layer_idx + 1] = support_layer_storage_fractional[layer_idx + 1].unionPolygons(); + support_roof_extra_wall_storage_fractional[layer_idx + 1] = support_roof_extra_wall_storage_fractional[layer_idx + 1].unionPolygons(); } + } + + prepareSupportAreas(support_layer_storage, + support_layer_storage_fractional, + support_roof_storage, + support_roof_extra_wall_storage, + support_roof_storage_fractional, + support_roof_extra_wall_storage_fractional, + fake_roof_areas_combined, + cradle_base_areas, + cradle_support_line_areas, + storage, + cradle_data); + + const auto t_union = std::chrono::high_resolution_clock::now(); + + calculateSupportHoles(support_layer_storage, + hole_parts, + valid_holes, + non_removable_holes, + hole_rest_map); + + const auto t_holes = std::chrono::high_resolution_clock::now(); + + generateSupportSkin( + support_layer_storage, + support_skin_storage, + fake_roof_areas_combined, + cradle_base_areas, + cradle_support_line_areas, + hole_parts, + valid_holes, + non_removable_holes, + hole_rest_map, + storage, + layer_tree_polygons); + + for (const auto layer_idx : ranges::views::iota(0UL, support_layer_storage.size())) + { scripta::log("tree_support_layer_storage", support_layer_storage[layer_idx], SectionType::SUPPORT, layer_idx); + // todo maybe also log support_skin_storage ? } - filterFloatingLines(support_layer_storage); + const auto t_skin = std::chrono::high_resolution_clock::now(); + removeFloatingHoles(support_layer_storage, + hole_parts, + valid_holes, + non_removable_holes, + hole_rest_map); const auto t_filter = std::chrono::high_resolution_clock::now(); - finalizeInterfaceAndSupportAreas(support_layer_storage, support_roof_storage, support_layer_storage_fractional, storage); + finalizeInterfaceAndSupportAreas(support_layer_storage, support_skin_storage, support_layer_storage_fractional, storage); const auto t_end = std::chrono::high_resolution_clock::now(); const auto dur_gen_tips = 0.001 * std::chrono::duration_cast(t_generate - t_start).count(); const auto dur_smooth = 0.001 * std::chrono::duration_cast(t_smooth - t_generate).count(); const auto dur_drop = 0.001 * std::chrono::duration_cast(t_drop - t_smooth).count(); - const auto dur_filter = 0.001 * std::chrono::duration_cast(t_filter - t_drop).count(); + const auto dur_union = 0.001 * std::chrono::duration_cast(t_union - t_drop).count(); + const auto dur_holes = 0.001 * std::chrono::duration_cast(t_holes - t_union).count(); + const auto dur_skin = 0.001 * std::chrono::duration_cast(t_skin - t_holes).count(); + const auto dur_filter = 0.001 * std::chrono::duration_cast(t_filter - t_skin).count(); const auto dur_finalize = 0.001 * std::chrono::duration_cast(t_end - t_filter).count(); spdlog::info( - "Time used for drawing subfuctions: generateBranchAreas: {} ms smoothBranchAreas: {} ms dropNonGraciousAreas: {} ms filterFloatingLines: {} ms " - "finalizeInterfaceAndSupportAreas {} ms", + "Time used for drawing subfuctions: generateBranchAreas: {} ms smoothBranchAreas: {} ms dropNonGraciousAreas: {} ms " + "prepareSupportAreas: {} ms calculateSupportHoles: {} ms generateSupportSkin {} ms " + "filterFloatingHoles: {} ms finalizeInterfaceAndSupportAreas {} ms", dur_gen_tips, dur_smooth, dur_drop, + dur_union, + dur_holes, + dur_skin, dur_filter, dur_finalize); } diff --git a/src/TreeSupportCradle.cpp b/src/TreeSupportCradle.cpp new file mode 100644 index 0000000000..af214965f9 --- /dev/null +++ b/src/TreeSupportCradle.cpp @@ -0,0 +1,1265 @@ + +#include "TreeSupportCradle.h" + +#include "TreeSupportUtils.h" +#include "utils/ThreadPool.h" +#include "utils/linearAlg2D.h" +#include "utils/math.h" //For round_up_divide and PI. +#include "utils/polygonUtils.h" //For moveInside. + +namespace cura +{ + + +SupportCradleGeneration::SupportCradleGeneration(const SliceDataStorage& storage, TreeModelVolumes& volumes_s) + : volumes_(volumes_s) + , cradle_data_(storage.meshes.size(), std::vector>(storage.print_layer_count)) + , floating_parts_cache_(storage.meshes.size()) + , support_free_areas_(storage.print_layer_count) +{ + +} + + +void SupportCradleGeneration::calculateFloatingParts(const SliceDataStorage& storage, size_t mesh_idx) +{ + const SliceMeshStorage& mesh = *storage.meshes[mesh_idx]; + const coord_t layer_height = mesh.settings.get("layer_height"); + const coord_t min_wall_line_width = mesh.settings.get("min_wall_line_width"); + const size_t cradle_layers = retrieveSetting(mesh.settings, "support_tree_cradle_height") / layer_height; + const double cradle_area_threshold = 1000 * 1000 * retrieveSetting(mesh.settings, "support_tree_maximum_pointy_area"); + + LayerIndex max_layer = mesh.layers.size(); + + LayerIndex start_layer = 1; + floating_parts_cache_[mesh_idx].resize(max_layer + 1); + std::mutex critical_floating_parts_cache; + std::mutex critical_manual_cradle_map; + std::vector> manual_cradle_map(storage.support.supportGenerationModifiers.size()); + + for (LayerIndex layer_idx = start_layer; layer_idx < max_layer; layer_idx++) + { + // As the collision contains z distance and xy distance it cant be used to clearly identify which parts of the model are connected to the buildplate. + Shape layer = mesh.layers[layer_idx].getOutlines(); + const std::vector layer_parts = layer.splitIntoParts(); + cura::parallel_for( + 0, + layer_parts.size(), + [&](const size_t part_idx) + { + const SingleShape& part = layer_parts[part_idx]; + AABB part_aabb(part); + // Use all models not only this mesh to determine if it rests on something as otherwise cutting meshes lead to issues + bool has_support_below = ! PolygonUtils::clipPolygonWithAABB(storage.getLayerOutlines(layer_idx - 1, false, false), part_aabb).intersection(part).empty(); + + Shape overhang = mesh.overhang_areas[layer_idx].intersection(part); + coord_t overhang_area = std::max(overhang.area(), std::numbers::pi * min_wall_line_width * min_wall_line_width); + if (! has_support_below || layer_idx == start_layer) + { + UnsupportedAreaInformation* area_info = new UnsupportedAreaInformation(part, layer_idx, 0, overhang_area); + bool add_manual_cradle = false; + if (! has_support_below) + { + for (auto [idx, support_modifier] : storage.support.supportGenerationModifiers | ranges::views::enumerate) + { + bool possible_manual_cradle = support_modifier.isCradleModifier() && layer_idx < support_modifier.areas_.size() + && ! PolygonUtils::clipPolygonWithAABB(support_modifier.areas_[layer_idx], part_aabb).intersection(part).empty(); + + if (possible_manual_cradle) + { + add_manual_cradle = true; + std::lock_guard critical_section_add(critical_manual_cradle_map); + manual_cradle_map[idx].emplace(area_info); + } + } + } + bool add_automatic_cradle = ! has_support_below && overhang_area < cradle_area_threshold; + if (add_manual_cradle) + { + area_info->support_required = CradlePlacementMethod::MANUAL_POINTY; + ; + top_most_cradle_layer_ = layer_idx + cradle_layers + 1; + } + else if (add_automatic_cradle) + { + area_info->support_required = CradlePlacementMethod::AUTOMATIC_POINTY; + ; + top_most_cradle_layer_ = layer_idx + cradle_layers + 1; + } + std::lock_guard critical_section_add(critical_floating_parts_cache); + floating_parts_cache_[mesh_idx][layer_idx].emplace_back(area_info); + return; + } + + size_t min_resting_on_layers = 0; + coord_t supported_overhang_area = 0; + bool add = false; + std::vector idx_of_floating_below; + std::vector cradles_below; + for (auto [idx, floating] : floating_parts_cache_[mesh_idx][layer_idx - 1] | ranges::views::enumerate) + { + if (layer_idx > 1 && ! floating->area.intersection(part).empty()) + { + idx_of_floating_below.emplace_back(idx); + + supported_overhang_area += floating->accumulated_supportable_overhang; + min_resting_on_layers = std::max(min_resting_on_layers, floating->height); + cradles_below.insert(cradles_below.end(), floating->cradles_below.begin(), floating->cradles_below.end()); + if (floating->support_required != CradlePlacementMethod::NONE) + { + cradles_below.emplace_back(floating); + } + add = true; + } + } + bool add_manual_cradle = false; + std::vector manual_cradle_causes; + for (auto [idx, support_modifier] : storage.support.supportGenerationModifiers | ranges::views::enumerate) + { + bool possible_manual_cradle = support_modifier.isCradleModifier() && layer_idx < support_modifier.areas_.size(); + LayerIndex previous_cradle_layer_idx = -1; + if (possible_manual_cradle) + { + { + std::lock_guard critical_section_add(critical_manual_cradle_map); + for (UnsupportedAreaInformation* cradle_below : cradles_below) + { + previous_cradle_layer_idx = std::max(previous_cradle_layer_idx, cradle_below->layer_idx); + if (manual_cradle_map[idx].contains(cradle_below)) + { + possible_manual_cradle = false; + break; + } + } + } + } + possible_manual_cradle &= previous_cradle_layer_idx == -1 || previous_cradle_layer_idx + cradle_layers < layer_idx; + Shape clipped_modifier = PolygonUtils::clipPolygonWithAABB(support_modifier.areas_[layer_idx], part_aabb); + if (possible_manual_cradle && ! clipped_modifier.intersection(part).empty()) + { + std::lock_guard critical_section_add(critical_manual_cradle_map); + for (UnsupportedAreaInformation* cradle_below : cradles_below) + { + manual_cradle_map[idx].emplace(cradle_below); + } + add_manual_cradle = true; + manual_cradle_causes.emplace_back(idx); + } + } + if (add) + { + std::lock_guard critical_section_add(critical_floating_parts_cache); + UnsupportedAreaInformation* area_info = new UnsupportedAreaInformation(part, layer_idx, min_resting_on_layers + 1, overhang_area + supported_overhang_area); + if (add_manual_cradle) + { + top_most_cradle_layer_ = layer_idx + cradle_layers + 1; + area_info->support_required = CradlePlacementMethod::MANUAL_SIDE; + std::lock_guard critical_section_add_to_manual_cradle_map(critical_manual_cradle_map); + for (size_t manual_cradle_idx : manual_cradle_causes) + { + manual_cradle_map[manual_cradle_idx].emplace(area_info); + } + spdlog::debug("Adding manual side-cradle at layer {}", layer_idx); + } + for (size_t idx : idx_of_floating_below) + { + area_info->areas_below.emplace_back(floating_parts_cache_[mesh_idx][layer_idx - 1][idx]); + area_info->cradles_below = cradles_below; + floating_parts_cache_[mesh_idx][layer_idx - 1][idx]->areas_above.emplace_back(area_info); + } + floating_parts_cache_[mesh_idx][layer_idx].emplace_back(area_info); + } + }); + } +} + +std::vector SupportCradleGeneration::getFullyUnsupportedArea(size_t mesh_idx, LayerIndex layer_idx) +{ + std::vector result; + + if (layer_idx == 0) + { + return result; + } + + bool has_result = false; + { + std::lock_guard critical_section(*critical_floating_parts_cache_); + has_result = layer_idx < floating_parts_cache_[mesh_idx].size(); + } + + if (has_result) + { + std::lock_guard critical_section(*critical_floating_parts_cache_); + for (auto [idx, floating_data] : floating_parts_cache_[mesh_idx][layer_idx] | ranges::views::enumerate) + { + if (floating_data->support_required != CradlePlacementMethod::NONE) + { + result.emplace_back(floating_data); + } + } + } + else + { + spdlog::error("Requested not calculated unsupported area.", layer_idx); + return result; + } + return result; +} + +std::vector> SupportCradleGeneration::generateCradleCenters(const SliceMeshStorage& mesh, size_t mesh_idx) +{ + std::shared_ptr cradle_config = std::make_shared(mesh, mesh.settings.get("support_roof_enable")); + + const size_t z_distance_top_layers = round_up_divide(mesh.settings.get("support_top_distance"), mesh.settings.get("layer_height")); + const size_t z_distance_delta_(std::min(z_distance_top_layers + 1, mesh.overhang_areas.size())); + const bool support_moves = mesh.settings.get("support_structure") != ESupportStructure::NORMAL; + coord_t maximum_move_distance = 0; + coord_t maximum_move_distance_slow = 0; + if(support_moves) + { + TreeSupportSettings config(mesh.settings); + maximum_move_distance = config.maximum_move_distance; + maximum_move_distance_slow = config.maximum_move_distance_slow; + } + std::mutex critical_dedupe; + std::vector> dedupe(mesh.overhang_areas.size()); + std::vector> result(mesh.overhang_areas.size()); + cura::parallel_for( + 1, + mesh.layers.size() - (z_distance_delta_ + 1), + [&](const LayerIndex layer_idx) + { + if (getFullyUnsupportedArea(mesh_idx, layer_idx + z_distance_delta_).empty()) + { + return; + } + + for (auto pointy_info : getFullyUnsupportedArea(mesh_idx, layer_idx + z_distance_delta_)) + { + if (pointy_info->height == 0) + { + AABB overhang_aabb(mesh.overhang_areas[layer_idx + z_distance_delta_]); + if (PolygonUtils::clipPolygonWithAABB(mesh.overhang_areas[layer_idx + z_distance_delta_], overhang_aabb).intersection(pointy_info->area).empty()) + { + // It will be assumed that if it touches this mesh's overhang, it will be part of that mesh. + continue; + } + } + + std::vector accumulated_model(std::min(cradle_config->cradle_layers_ + cradle_config->cradle_z_distance_layers_ + 1, mesh.overhang_areas.size() - layer_idx), Shape()); + std::vector all_pointy{ pointy_info }; + + Point2LL center_prev = Polygon(pointy_info->area.getOutsidePolygons()[0]).centerOfMass(); + std::vector additional_centers; + TreeSupportCradle* cradle_main + = new TreeSupportCradle(layer_idx, center_prev, cradle_config->cradle_base_roof_, pointy_info->support_required, cradle_config, mesh_idx); + for (size_t z_distance = 0; z_distance < z_distance_top_layers; z_distance++) + { + accumulated_model[z_distance] = pointy_info->area; + cradle_main->centers_.emplace_back(center_prev); + } + Shape shadow; // A combination of all outlines of the model that will be supported with a cradle. + bool aborted = false; + bool contacted_other_pointy = false; + std::vector unsupported_model(accumulated_model.size()); + for (size_t cradle_up_layer = 0; cradle_up_layer < accumulated_model.size() - z_distance_top_layers; cradle_up_layer++) + { + // shadow model up => not cradle where model + // then drop cradle down + // cut into parts => get close to original pointy that are far enough from each other. + std::vector next_pointy; + Shape model_outline; + bool blocked_by_dedupe = false; + // The cradle base is below the bottommost unsupported and the first cradle layer is around it, so this will be needed only for the second one and up + if (cradle_up_layer > 1) + { + for (UnsupportedAreaInformation* pointy : all_pointy) + { + for (auto next_pointy_data : pointy->areas_above) + { + if (next_pointy_data->height + != (cradle_up_layer - 1) + pointy_info->height) // If the area belongs to another pointy overhang stop and let this other overhang handle it + { + contacted_other_pointy = true; + continue; + } + unsupported_model[cradle_up_layer].push_back(next_pointy_data->area); + // Ensure each area is only handles once + std::lock_guard critical_section_cradle(critical_dedupe); + if (! dedupe[layer_idx + cradle_up_layer].contains(next_pointy_data)) + { + dedupe[layer_idx + cradle_up_layer].emplace(next_pointy_data); + model_outline.push_back(next_pointy_data->area); + next_pointy.emplace_back(next_pointy_data); + } + else + { + blocked_by_dedupe = true; + } + + std::vector all_pointy_below{ next_pointy_data }; + for (int64_t cradle_down_layer = cradle_up_layer; cradle_down_layer > 0 && ! all_pointy_below.empty(); cradle_down_layer--) + { + std::vector next_all_pointy_below; + + for (UnsupportedAreaInformation* pointy_below : all_pointy_below) + { + for (UnsupportedAreaInformation* prev_pointy_data : pointy_below->areas_below) + { + if (prev_pointy_data != pointy || cradle_down_layer != cradle_up_layer) + { + // Only add if area below does not have it's own cradle. + if (prev_pointy_data->height < cradle_config->cradle_layers_min_) + { + accumulated_model[cradle_down_layer].push_back(prev_pointy_data->area); + next_all_pointy_below.emplace_back(prev_pointy_data); + } + } + } + } + all_pointy_below = next_all_pointy_below; + accumulated_model[cradle_down_layer] = accumulated_model[cradle_down_layer].unionPolygons(); + } + } + } + all_pointy = next_pointy; + } + else + { + model_outline.push_back(pointy_info->area); + } + + if (model_outline.empty()) + { + if (cradle_up_layer < cradle_config->cradle_layers_min_) + { + aborted = true; + break; + } + + if (! blocked_by_dedupe) + { + // The model is surrounded with cradle based on the area above (z distance). + // When an area that should have a cradle merges with a buildplate supported area above, it will no longer exist for a cradle. + // But if the cradle stops there will be z distance layer between the end of the cradle and said merge. + // To reduce the impact an area is estimated where the cradle should be for these areas. + Shape previous_area = shadow; + for (size_t cradle_up_layer_z_distance = cradle_up_layer; + cradle_up_layer_z_distance < std::min(cradle_up_layer + cradle_config->cradle_z_distance_layers_, accumulated_model.size() - z_distance_top_layers); + cradle_up_layer_z_distance++) + { + accumulated_model[cradle_up_layer_z_distance + z_distance_top_layers] = unsupported_model[cradle_up_layer_z_distance].unionPolygons(); + } + } + break; + } + + model_outline = model_outline.unionPolygons(); + shadow = shadow.offset(-maximum_move_distance).unionPolygons(model_outline); + accumulated_model[cradle_up_layer + z_distance_top_layers] = shadow; + + if (cradle_up_layer > 0) + { + Point2LL shadow_center = Polygon(shadow.getOutsidePolygons()[0]).centerOfMass(); + coord_t center_move_distance = support_moves ? std::min(maximum_move_distance_slow, cradle_config->cradle_line_width_ / 3) : cradle_config->cradle_line_width_ / 3; + center_move_distance = std::min(center_move_distance, vSize(shadow_center - center_prev)); + center_prev = center_prev + normal(shadow_center - center_prev, center_move_distance); + cradle_main->centers_.emplace_back(center_prev); + } + } + + if (aborted) + { + // If aborted remove all model information for the cradle generation except the pointy overhang, as it may be needed to cut a small hole in the large interface + // base. todo reimplement that + + Shape cradle_0 = accumulated_model[0]; + accumulated_model.clear(); + accumulated_model.emplace_back(cradle_0); + delete cradle_main; + } + else + { + cradle_main->shadow_ = accumulated_model; + result[layer_idx].emplace_back(cradle_main); + } + } + }); + return result; +} + +void SupportCradleGeneration::generateCradleLines(std::vector>& cradle_data_mesh, const SliceMeshStorage& mesh) +{ + + const size_t z_distance_top_layers = round_up_divide(mesh.settings.get("support_top_distance"), mesh.settings.get("layer_height")); + const size_t z_distance_bottom_layers = round_up_divide(mesh.settings.get("support_bottom_distance"), mesh.settings.get("layer_height")); + const bool support_rests_on_model = mesh.settings.get("support_type") == ESupportType::EVERYWHERE; + const coord_t support_line_width = mesh.settings.get("support_line_width"); + const coord_t xy_distance = mesh.settings.get("support_xy_distance"); + const bool xy_overrides = mesh.settings.get("support_xy_overrides_z") == SupportDistPriority::XY_OVERRIDES_Z; + const coord_t xy_min_distance = !xy_overrides ? mesh.settings.get("support_xy_distance_overhang") : xy_distance; + const bool support_moves = mesh.settings.get("support_structure") != ESupportStructure::NORMAL; + + const coord_t minimum_area_to_be_supportable = support_moves ? mesh.settings.get("support_tree_tip_diameter") / 2 : 0; + cura::parallel_for( + 1, + cradle_data_mesh.size(), + [&](const LayerIndex layer_idx) + { + for (auto [center_idx, cradle] : cradle_data_mesh[layer_idx] | ranges::views::enumerate) + { + const coord_t max_cradle_jump_length_forward = cradle->config_->cradle_length_ / 3; + constexpr bool ignore_xy_dist_for_jumps = true; + const coord_t max_cradle_xy_distance = *std::max_element(cradle->config_->cradle_xy_distance_.begin(), cradle->config_->cradle_xy_distance_.end()); + std::vector removed_directions(cradle->config_->cradle_line_count_); + const auto& accumulated_model = cradle->shadow_; + for (auto [idx, model_shadow] : accumulated_model | ranges::views::enumerate) + { + Point2LL center = cradle->getCenter(layer_idx + idx); + const coord_t current_cradle_xy_distance = cradle->config_->cradle_xy_distance_[idx]; + const coord_t previous_cradle_xy_distance = idx > 0 ? cradle->config_->cradle_xy_distance_[idx-1] : current_cradle_xy_distance; + const coord_t current_cradle_length = cradle->config_->cradle_length_ + max_cradle_xy_distance - current_cradle_xy_distance; + + if (cradle->lines_.empty()) + { + cradle->lines_.resize(cradle->config_->cradle_line_count_); + } + + if (idx > cradle->config_->cradle_z_distance_layers_ && ! model_shadow.empty()) + { + Shape relevant_forbidden = volumes_.getAvoidance( + 0, + layer_idx + idx, + (only_gracious_ || ! support_rests_on_model) ? AvoidanceType::FAST : AvoidanceType::COLLISION, + support_rests_on_model, + true); + + Shape this_part_influence = model_shadow.offset(current_cradle_xy_distance + cradle->config_->cradle_line_width_ / 2); + + for (size_t layer_offset = 1; layer_offset <= z_distance_bottom_layers && layer_offset <= idx; layer_offset++) + { + this_part_influence.push_back(accumulated_model[idx - layer_offset].offset(current_cradle_xy_distance + cradle->config_->cradle_line_width_ / 2)); + } + + for (coord_t layer_offset = 1; layer_offset <= z_distance_top_layers && layer_offset + idx < accumulated_model.size(); layer_offset++) + { + const coord_t required_range_x = coord_t( + (current_cradle_xy_distance + cradle->config_->cradle_line_width_ / 2) + - ((layer_offset - (z_distance_top_layers == 1 ? 0.5 : 0)) * (current_cradle_xy_distance + cradle->config_->cradle_line_width_ / 2) + / z_distance_top_layers)); + this_part_influence.push_back(accumulated_model[idx + layer_offset].offset(required_range_x)); + } + + this_part_influence = this_part_influence.unionPolygons(); + + coord_t cradle_min_xy_distance_delta = std::max(xy_min_distance - current_cradle_xy_distance, coord_t(0)); + + // Somewhere, Somehow there is a small rounding error which causes small slivers of collision of the model to remain. + // To prevent this offset my the delta before removing the influence of the model. + relevant_forbidden = relevant_forbidden.offset(-cradle_min_xy_distance_delta) + .difference(this_part_influence.offset(cradle_min_xy_distance_delta + EPSILON).unionPolygons()) + .offset(cradle_min_xy_distance_delta + cradle->config_->cradle_line_width_ / 2 + FUDGE_LENGTH) + .unionPolygons(this_part_influence) + .unionPolygons(volumes_.getSupportBlocker(layer_idx + idx).offset(cradle->config_->cradle_line_width_ / 2)); + coord_t max_distance2 = 0; + for (auto line : model_shadow) + { + for (Point2LL p : line) + { + max_distance2 = std::max(max_distance2, vSize2(center - p)); + } + } + + ClosedPolyline max_outer_points = PolygonUtils::makeCircle( + center, + sqrt(max_distance2) + current_cradle_length * 2.0, + cradle->config_->cradle_line_count_); + + // create lines that go from the furthest possible location to the center + OpenLinesSet lines_to_center; + for (Point2LL p : max_outer_points) + { + Point2LL direction = p - center; + lines_to_center.addSegment(p, center + normal(direction, support_line_width)); + } + + // Subtract the model shadow up until this layer from the lines. + if (idx > 0) + { + lines_to_center = model_shadow.offset(current_cradle_xy_distance + cradle->config_->cradle_line_width_ / 2).unionPolygons().difference(lines_to_center, false); + } + + // shorten lines to be at most SUPPORT_TREE_CRADLE_WIDTH long, with the location closest to the center not changing + OpenLinesSet shortened_lines_to_center; + for (auto [line_idx, line] : lines_to_center | ranges::views::enumerate) + { + bool front_closer = vSize2(line.front() - center) < vSize2(line.back() - center); + Point2LL closer = front_closer ? line.front() : line.back(); + Point2LL further = front_closer ? line.back() : line.front(); + coord_t cradle_line_length = line.length(); + if (cradle_line_length < cradle->config_->cradle_length_min_) + { + continue; + } + if (line.length() <= current_cradle_length) + { + shortened_lines_to_center.push_back(line); + } + else + { + double scale = (double(current_cradle_length) / double(vSize(further - closer))); + Point2LL correct_length = closer + (further - closer) * scale; + shortened_lines_to_center.addSegment(correct_length, closer); + } + } + // If a line is drawn, but half of it removed as it would collide with the collision, there may not actually be a print line. The offset should prevent + // this. + shortened_lines_to_center = relevant_forbidden.difference(shortened_lines_to_center, false); + std::vector ordered_lines_to_center(cradle->config_->cradle_line_count_); + + // Evaluate which lines are still valid after the avoidance was subtracted + for (auto [line_idx, line] : shortened_lines_to_center | ranges::views::enumerate) + { + bool front_closer = vSize2(line.front() - center) < vSize2(line.back() - center); + Point2LL closer = front_closer ? line.front() : line.back(); + Point2LL further = front_closer ? line.back() : line.front(); + Point2LL current_direction = further - closer; + coord_t distance_from_center = vSize(closer - center); + size_t angle_idx = cradle->getIndexForLineEnd(further, layer_idx + idx); + + if (removed_directions[angle_idx]) + { + continue; + } + + bool keep_line = false; + bool found_candidate = false; + bool too_short = (vSize(closer - further)) < cradle->config_->cradle_length_min_; + bool too_long_jump = false; + Point2LL closest_on_prev_segment; + if(! cradle->lines_[angle_idx].empty()) + { + closest_on_prev_segment = LinearAlg2D::getClosestOnLineSegment(closer, cradle->lines_[angle_idx].back().line_.front(), cradle->lines_[angle_idx].back().line_.back()); + Point2LL closest_on_prev_line = LinearAlg2D::getClosestOnLine(closer, cradle->lines_[angle_idx].back().line_.front(), cradle->lines_[angle_idx].back().line_.back()); + coord_t xy_distance_jump = std::max(coord_t(0), previous_cradle_xy_distance - current_cradle_xy_distance); + //todo if jump too long check if line could be shortened. Do that above! + too_long_jump = vSize(closest_on_prev_segment - closest_on_prev_line) - (ignore_xy_dist_for_jumps? xy_distance_jump : 0) > + max_cradle_jump_length_forward; //todo better non arbitrary limit + } + // a cradle line should also be removed if there will no way to support it + if (idx >= cradle->config_->cradle_z_distance_layers_ + 1) + { + const Shape& actually_forbidden = volumes_.getAvoidance( + minimum_area_to_be_supportable, + layer_idx + idx - (cradle->config_->cradle_z_distance_layers_ + 1), + (only_gracious_ || ! support_rests_on_model) ? AvoidanceType::FAST : AvoidanceType::COLLISION, + support_rests_on_model, + true); + OpenLinesSet current_shortened; + current_shortened.push_back(shortened_lines_to_center[line_idx]); + too_short |= actually_forbidden.difference(current_shortened).length() < support_line_width; + } + if (! too_short && ! too_long_jump) + { + if (ordered_lines_to_center[angle_idx].empty()) + { + ordered_lines_to_center[angle_idx].push_back(closer); + ordered_lines_to_center[angle_idx].push_back(further); + } + else + { + // Prefer lines not touching the center. Otherwise, prefer the line closest to the center + if (distance_from_center > FUDGE_LENGTH) + { + if (vSize(ordered_lines_to_center[angle_idx].front() - center) < FUDGE_LENGTH) + { + ordered_lines_to_center[angle_idx].clear(); + ordered_lines_to_center[angle_idx].push_back(closer); + ordered_lines_to_center[angle_idx].push_back(further); + } + else if (distance_from_center < vSize(ordered_lines_to_center[angle_idx].front() - center)) + { + ordered_lines_to_center[angle_idx].clear(); + ordered_lines_to_center[angle_idx].push_back(closer); + ordered_lines_to_center[angle_idx].push_back(further); + } + } + } + } + } + + for (auto [angle_idx, next_line] : ordered_lines_to_center | ranges::views::enumerate) + { + if (next_line.empty()) + { + removed_directions[angle_idx] = true; + continue; + } + OpenPolyline line(next_line); + // Handle cradle_z_distance_layers by overwriting first element in the vector until valid distance is reached. + if (idx <= cradle->config_->cradle_z_distance_layers_ + 1 && ! cradle->lines_[angle_idx].empty()) + { + cradle->lines_[angle_idx][0] = TreeSupportCradleLine(line, layer_idx + idx, cradle->config_->cradle_lines_roof_); + } + else + { + cradle->lines_[angle_idx].emplace_back(TreeSupportCradleLine(line, layer_idx + idx, cradle->config_->cradle_lines_roof_)); + } + } + } + } + + // enlarge cradle lines below to minimize overhang of cradle lines. + for (auto [line_idx, cradle_lines] : cradle->lines_ | ranges::views::enumerate) + { + if (! cradle_lines.empty()) + { + Point2LL line_end = cradle_lines.back().line_.back(); + for (auto [up_idx, line] : cradle_lines | ranges::views::enumerate | ranges::views::reverse) + { + Point2LL center = cradle->getCenter(line.layer_idx_); + if (vSize2(line_end - center) > vSize2(line.line_.back() - center)) + { + OpenLinesSet line_extension; + line_extension.addSegment(line.line_.back(), line_end); + coord_t line_length_before = line_extension.length(); + Shape actually_forbidden = volumes_.getAvoidance( + 0, + line.layer_idx_, + (only_gracious_ || ! support_rests_on_model) ? AvoidanceType::FAST : AvoidanceType::COLLISION, + support_rests_on_model, + true); + line_extension = actually_forbidden.difference(line_extension); + + if (line_extension.length() + EPSILON < line_length_before) + { + for (auto line_part : line_extension) + { + bool front_closer = vSize2(line_part.front() - center) < vSize2(line_part.back() - center); + Point2LL closer = front_closer ? line_part.front() : line_part.back(); + Point2LL further = front_closer ? line_part.back() : line_part.front(); + + if (vSize2(closer - line.line_.back() < EPSILON * EPSILON)) + { + line_end = further; + } + } + } + // As the center can move there is no guarantee that the point of the current line lies on the line below. + line_end = LinearAlg2D::getClosestOnLine(line_end, line.line_.front(), line.line_.back()); + line.line_.back() = line_end; + } + } + } + } + } + }); +} + + +void SupportCradleGeneration::cleanCradleLineOverlaps() +{ + std::vector> all_cradles_per_layer(cradle_data_.front().size()); + for(size_t mesh_idx = 0; mesh_idx < cradle_data_.size(); mesh_idx++) + { + for (LayerIndex layer_idx = 0; layer_idx < cradle_data_[mesh_idx].size(); layer_idx++) + { + for (size_t cradle_idx = 0; cradle_idx < cradle_data_[mesh_idx][layer_idx].size(); cradle_idx++) + { + for (size_t cradle_line_idx = 0; cradle_line_idx < cradle_data_[mesh_idx][layer_idx][cradle_idx]->lines_.size(); cradle_line_idx++) + { + for (size_t height = 0; height < cradle_data_[mesh_idx][layer_idx][cradle_idx]->lines_[cradle_line_idx].size(); height++) + { + LayerIndex cradle_line_layer_idx = cradle_data_[mesh_idx][layer_idx][cradle_idx]->lines_[cradle_line_idx][height].layer_idx_; + if(all_cradles_per_layer.size() <= layer_idx + height) + { + all_cradles_per_layer.resize(cradle_data_[mesh_idx][layer_idx][cradle_idx]->config_->cradle_layers_ + 1); + } + all_cradles_per_layer[layer_idx + height].emplace_back(cradle_data_[mesh_idx][layer_idx][cradle_idx], cradle_line_layer_idx, cradle_line_idx); + } + } + } + } + } + + cura::parallel_for( + 1, + all_cradles_per_layer.size(), + [&](const LayerIndex layer_idx) + { + std::vector& all_cradles_on_layer = all_cradles_per_layer[layer_idx]; + + std::function handleNewEnd = [&](size_t cradle_line_idx, Point2LL new_end) + { + TreeSupportCradleLine* cradle_line = all_cradles_on_layer[cradle_line_idx].getCradleLine(); + if (LinearAlg2D::pointIsProjectedBeyondLine(new_end, cradle_line->line_.front(), cradle_line->line_.back()) || vSize(new_end - cradle_line->line_.front()) < all_cradles_on_layer[cradle_line_idx].cradle_->config_->cradle_length_min_) + { + cradle_line->addLineToRemoved(cradle_line->line_); + cradle_line->line_.clear(); + } + else + { + cradle_line->line_.back() = new_end; + } + }; + + for (size_t cradle_idx = 0; cradle_idx < all_cradles_on_layer.size(); cradle_idx++) + { + TreeSupportCradleLine* cradle_line = all_cradles_on_layer[cradle_idx].getCradleLine(); + + AABB bounding_box_outer = AABB(cradle_line->line_); + for (size_t cradle_idx_inner = cradle_idx + 1; cradle_idx_inner < all_cradles_on_layer.size(); cradle_idx_inner++) + { + TreeSupportCradleLine* cradle_line_inner = all_cradles_on_layer[cradle_idx_inner].getCradleLine(); + + const coord_t min_distance_between_lines + = std::max(all_cradles_on_layer[cradle_idx].cradle_->config_->cradle_line_width_, all_cradles_on_layer[cradle_idx_inner].cradle_->config_->cradle_line_width_) + + std::max(all_cradles_on_layer[cradle_idx].cradle_->config_->cradle_line_distance_, all_cradles_on_layer[cradle_idx_inner].cradle_->config_->cradle_line_distance_); + + AABB bounding_box_current = bounding_box_outer; + bounding_box_current.expand(min_distance_between_lines); + + if (cradle_line_inner->line_.empty() || cradle_line->line_.empty()) + { + continue; + } + if (bounding_box_current.hit(AABB(cradle_line_inner->line_))) + { + OpenPolyline& outer_line = cradle_line->line_; + OpenPolyline& inner_line = cradle_line_inner->line_; + Point2LL intersect; + if (LinearAlg2D::lineLineIntersection(outer_line.front(), outer_line.back(), inner_line.front(), inner_line.back(), intersect) + && ! LinearAlg2D::pointIsProjectedBeyondLine(intersect, outer_line.front(), outer_line.back()) + && ! LinearAlg2D::pointIsProjectedBeyondLine(intersect, inner_line.front(), inner_line.back())) + { + coord_t inner_intersect_dist = vSize(inner_line.front() - intersect); + coord_t outer_intersect_dist = vSize(outer_line.front() - intersect); + + if (inner_intersect_dist > outer_intersect_dist) + { + // this does not ensure that the line ends will not touch. Line ends not touching is handled later + Point2LL new_end_inner = intersect + normal((inner_line.front() - intersect), min_distance_between_lines); + handleNewEnd(cradle_idx_inner, new_end_inner); + } + if (outer_intersect_dist > inner_intersect_dist) + { + Point2LL new_end_outer = intersect + normal((outer_line.front() - intersect), min_distance_between_lines); + handleNewEnd(cradle_idx, new_end_outer); + } + } + + if (! outer_line.empty() && ! inner_line.empty()) + { + // Touching lines have the same issue Lines touch if the end is to close to another line + Point2LL inner_direction = inner_line.back() - inner_line.front(); + Point2LL outer_direction = outer_line.back() - outer_line.front(); + double cosine = std::abs((dot(inner_direction, outer_direction)) / double(vSize(outer_direction) * vSize(inner_direction))); + // If both lines point in the same/opposite direction check that them being to close is not the end line of one to the start of the other + if (cosine < 0.99 || vSize2(outer_line.back() - inner_line.back()) + EPSILON * EPSILON < vSize2(outer_line.front() - inner_line.front())) + { + coord_t inner_end_to_outer_distance = sqrt(LinearAlg2D::getDist2FromLineSegment(outer_line.front(), inner_line.back(), outer_line.back())); + if (inner_end_to_outer_distance < min_distance_between_lines && inner_end_to_outer_distance < vSize(outer_line.front() - inner_line.front())) + { + Point2LL new_end_inner + = inner_line.back() + normal(inner_line.front() - inner_line.back(), min_distance_between_lines - inner_end_to_outer_distance); + double error = min_distance_between_lines - sqrt(LinearAlg2D::getDist2FromLineSegment(outer_line.front(), new_end_inner, outer_line.back())); + double error_correction_factor = 1.0 + error / double(min_distance_between_lines - inner_end_to_outer_distance); + new_end_inner + = inner_line.back() + + normal(inner_line.front() - inner_line.back(), (min_distance_between_lines - inner_end_to_outer_distance) * error_correction_factor); + handleNewEnd(cradle_idx_inner, new_end_inner); + } + else + { + coord_t outer_end_to_inner_distance = sqrt(LinearAlg2D::getDist2FromLineSegment(inner_line.front(), outer_line.back(), inner_line.back())); + if (outer_end_to_inner_distance < min_distance_between_lines && outer_end_to_inner_distance < vSize(outer_line.front() - inner_line.front())) + { + Point2LL new_end_outer + = outer_line.back() + normal(outer_line.front() - outer_line.back(), min_distance_between_lines - outer_end_to_inner_distance); + double error = min_distance_between_lines - sqrt(LinearAlg2D::getDistFromLine(new_end_outer, outer_line.front(), outer_line.back())); + double error_correction_factor = 1.0 + error / double(min_distance_between_lines - outer_end_to_inner_distance); + new_end_outer + = outer_line.back() + + normal(outer_line.front() - outer_line.back(), (min_distance_between_lines - outer_end_to_inner_distance) * error_correction_factor); + handleNewEnd(cradle_idx, new_end_outer); + } + } + } + } + } + } + } + }); + for(size_t mesh_idx = 0; mesh_idx < cradle_data_.size(); mesh_idx++) + { + cura::parallel_for( + 1, + cradle_data_[mesh_idx].size(), + [&](const LayerIndex layer_idx) + { + for (size_t cradle_idx = 0; cradle_idx < cradle_data_[mesh_idx][layer_idx].size(); cradle_idx++) + { + TreeSupportCradle* cradle = cradle_data_[mesh_idx][layer_idx][cradle_idx]; + const coord_t max_cradle_xy_distance = *std::max_element(cradle->config_->cradle_xy_distance_.begin(), cradle->config_->cradle_xy_distance_.end()); + + cradle->verifyLines(); + // As cradle lines (causing lines below to be longer) may have been removed to prevent them intersecting, all cradle lines are now shortened again if required. + for (auto [line_idx, cradle_lines] : cradle->lines_ | ranges::views::enumerate) + { + if (! cradle_lines.empty()) + { + Point2LL line_end = cradle_lines.back().line_.back(); + Point2LL line_front_uppermost = cradle_lines.back().line_.front(); + + if (vSize2(line_end - cradle_lines.back().line_.front()) > cradle->config_->cradle_length_ * cradle->config_->cradle_length_) + { + coord_t current_cradle_xy_distance = cradle->config_->cradle_xy_distance_[cradle_lines.back().layer_idx_ - layer_idx]; + coord_t current_cradle_length = cradle->config_->cradle_length_ + max_cradle_xy_distance - current_cradle_xy_distance; + cradle_lines.back().line_.back() = line_front_uppermost + normal(line_end - line_front_uppermost, current_cradle_length); + for (auto [up_idx, line] : cradle_lines | ranges::views::enumerate | ranges::views::reverse) + { + Point2LL center = cradle->getCenter(line.layer_idx_); + Point2LL line_back_inner = line.line_.back(); + Point2LL line_front_inner = line.line_.front(); + if (vSize2(line_back_inner - line_front_inner) > cradle->config_->cradle_length_ * cradle->config_->cradle_length_) + { + // As the center can move there is no guarantee that the point of the current line lies on the line below. + Point2LL projected_line_end = LinearAlg2D::getClosestOnLine(line_end, line.line_.front(), line.line_.back()); + current_cradle_xy_distance = cradle->config_->cradle_xy_distance_[line.layer_idx_ - layer_idx]; + current_cradle_length = cradle->config_->cradle_length_ + max_cradle_xy_distance - current_cradle_xy_distance; + if (vSize2(line_front_inner - projected_line_end) > current_cradle_length * current_cradle_length) + { + line.line_.back() = projected_line_end; + } + else + { + line.line_.back() = line_front_inner + normal(line_back_inner - line_front_inner, current_cradle_length); + } + } + line_end = line.line_.back(); + } + } + } + } + } + }); + } +} + + +void SupportCradleGeneration::generateCradleLineAreasAndBase(const SliceDataStorage& storage) +{ + + std::mutex critical_support_free_areas_and_cradle_areas; + + for(size_t mesh_idx = 0; mesh_idx < cradle_data_.size(); mesh_idx++) + { + + if(cradle_data_[mesh_idx].empty()) + { + continue; + + } + + const SliceMeshStorage& mesh = *storage.meshes[mesh_idx]; + const coord_t layer_height = mesh.settings.get("layer_height"); + const size_t z_distance_top_layers = round_up_divide(mesh.settings.get("support_top_distance"), layer_height); + const bool support_rests_on_model = mesh.settings.get("support_type") == ESupportType::EVERYWHERE; + const coord_t support_line_width = mesh.settings.get("support_line_width"); + const size_t support_roof_layers = mesh.settings.get("support_roof_enable") ? round_divide(mesh.settings.get("support_roof_height"), layer_height) : 0; + const bool xy_overrides = mesh.settings.get("support_xy_overrides_z") == SupportDistPriority::XY_OVERRIDES_Z; + const coord_t xy_distance = mesh.settings.get("support_xy_distance"); + const coord_t xy_min_distance = !xy_overrides ? mesh.settings.get("support_xy_distance_overhang") : xy_distance; + const coord_t roof_outset = support_roof_layers > 0 ? mesh.settings.get("support_roof_offset") : 0; + const bool support_moves = mesh.settings.get("support_structure") != ESupportStructure::NORMAL; + + const coord_t max_roof_movement = support_moves ? TreeSupportSettings(mesh.settings).maximum_move_distance_slow : 0; //todo without TreeSupportSettings + Simplify simplifyer(mesh.settings); + + cura::parallel_for( + 0, + cradle_data_[mesh_idx].size(), + [&](const LayerIndex layer_idx) + { + std::vector valid_cradles; + for (size_t cradle_idx = 0; cradle_idx < cradle_data_[mesh_idx][layer_idx].size(); cradle_idx++) + { + TreeSupportCradle& cradle = *cradle_data_[mesh_idx][layer_idx][cradle_idx]; + const coord_t min_distance_between_lines = FUDGE_LENGTH + cradle.config_->min_distance_between_lines_areas_; + // Some angles needed to move cradle lines outwards to prevent them from toughing. + double center_angle = (2.0 * std::numbers::pi) / double(cradle.config_->cradle_line_count_); + double outer_angle = (std::numbers::pi - center_angle) / 2; + coord_t outer_radius = (double(min_distance_between_lines + support_line_width) / sin(center_angle)) * sin(outer_angle); + const coord_t small_hole_size = EPSILON + cradle.config_->min_distance_between_lines_areas_; // based on calculation in WallToolPath + + for (size_t cradle_height = 0; cradle_height <= cradle.config_->cradle_layers_; cradle_height++) + { + std::vector> all_tips_center; + // generate trapezoid line tip with front width of support line width, back cradle_width. + + for (size_t line_idx = 0; line_idx < cradle.lines_.size(); line_idx++) + { + std::optional line_opt = cradle.getCradleLineOfIndex(layer_idx + cradle_height, line_idx); + if (! line_opt) + { + continue; + } + TreeSupportCradleLine* cradle_line = line_opt.value(); + OpenPolyline line = cradle_line->line_; + + coord_t current_cradle_line_width = cradle.config_->cradle_line_width_; + + double assumed_half_center_angle = std::numbers::pi / (1.5 * cradle.config_->cradle_line_count_); + coord_t triangle_length + = cradle.config_->cradle_line_count_ <= 2 ? 0 : ((current_cradle_line_width - support_line_width) / 2) * tan(std::numbers::pi / 2 - assumed_half_center_angle); + + const coord_t line_length = line.length(); + if (triangle_length >= line_length + cradle.config_->cradle_line_width_) + { + triangle_length = line_length + cradle.config_->cradle_line_width_; + current_cradle_line_width = support_line_width + 2 * triangle_length * tan(assumed_half_center_angle); + } + + Point2LL direction = line.back() - line.front(); + Point2LL center_front = line.front() - normal(direction, cradle.config_->cradle_line_width_ / 2); + + Point2LL direction_up_center = normal(rotate(direction, std::numbers::pi / 2), support_line_width / 2); + Point2LL center_up = center_front + direction_up_center; + Point2LL center_down = center_front - direction_up_center; + + coord_t tip_shift = 0; + for (auto existing_center : all_tips_center) + { + Point2LL intersect; + bool centers_touch = LinearAlg2D::lineLineIntersection(center_up, center_down, existing_center.first, existing_center.second, intersect) + && ! LinearAlg2D::pointIsProjectedBeyondLine(intersect, existing_center.first, existing_center.second) + && ! LinearAlg2D::pointIsProjectedBeyondLine(intersect, center_up, center_down); + if (centers_touch || std::min(vSize(center_down - existing_center.first), vSize(center_up - existing_center.second)) < min_distance_between_lines) + { + // This cradle line is to close to another. + // Move it back todo If line gets smaller than min length => abort + coord_t tip_shift_here = outer_radius - vSize(center_front - cradle.getCenter(cradle_line->layer_idx_)); + tip_shift += tip_shift_here; + center_front = center_front + normal(direction, tip_shift_here); + center_up = center_front + direction_up_center; + center_down = center_front - direction_up_center; + } + } + + Point2LL back_center = center_front + normal(direction, triangle_length); + Point2LL direction_up_back = normal(rotate(direction, std::numbers::pi / 2), current_cradle_line_width / 2); + + Point2LL back_up = back_center + direction_up_back; + Point2LL back_down = back_center - direction_up_back; + + line.front() = back_center + normal(direction, current_cradle_line_width / 2 - FUDGE_LENGTH / 2); + all_tips_center.emplace_back(center_up, center_down); + + Polygon line_tip; + line_tip.push_back(back_down); + if (current_cradle_line_width == cradle.config_->cradle_line_width_) + { + coord_t distance_end_front = line_length - triangle_length + cradle.config_->cradle_line_width_ - tip_shift; + Point2LL line_end_down = back_down + normal(direction, distance_end_front); + Point2LL line_end_up = back_up + normal(direction, distance_end_front); + line_tip.push_back(line_end_down); + line_tip.push_back(line_end_up); + } + line_tip.push_back(back_up); + line_tip.push_back(center_up); + line_tip.push_back(center_down); + if (line_tip.area() < 0) + { + line_tip.reverse(); + } + cradle_line->area_.push_back(line_tip); + + Shape anti_preferred = cradle_line->area_.offset(xy_distance); + std::lock_guard critical_section_cradle(critical_support_free_areas_and_cradle_areas); + for (size_t z_distance_idx = 0; z_distance_idx < z_distance_top_layers; z_distance_idx++) + { + volumes_.addAreaToAntiPreferred(anti_preferred, layer_idx + cradle_height + z_distance_idx); + } + } + } + + bool is_side_cradle = cradle.cradle_placement_method_ == CradlePlacementMethod::MANUAL_SIDE || cradle.cradle_placement_method_ == CradlePlacementMethod::AUTOMATIC_SIDE; + Shape cradle_base = is_side_cradle ? Shape() : cradle.shadow_[0]; + + if (support_roof_layers && ! is_side_cradle) + { + Shape cut_line_base; + Shape first_cradle_areas; + if (cradle.config_->large_cradle_base_) + { + // If a large cradle base is used there needs to be a small hole cut into it to ensure that there will be a line for the pointy overhang to rest + // on. This line is just a diagonal though the original pointy overhang area (from min to max). Technically this line is covering more than just + // the overhang, but that should not cause any issues. + AABB cradle_base_aabb = AABB(cradle_base); + OpenLinesSet rest_line; + rest_line.addSegment(cradle_base_aabb.min_, cradle_base_aabb.max_); + cut_line_base = rest_line.offset(small_hole_size); + } + { + std::lock_guard critical_section_cradle(critical_support_free_areas_and_cradle_areas); + for (size_t interface_down = 0; interface_down < layer_idx && interface_down < support_roof_layers; interface_down++) + { + support_free_areas_[layer_idx - interface_down].push_back(cut_line_base); + } + } + if (cradle.config_->large_cradle_base_) + { + cradle_base = cradle_base.offset(cradle.config_->cradle_support_base_area_radius_, ClipperLib::jtRound); + Shape center_removed = cradle_base.difference(cut_line_base); + if (center_removed.area() > 1) + { + cradle_base = center_removed; + } + } + else if (cradle.config_->cradle_base_roof_) + { + // collect all inner points and connect to center for thin cradle base + OpenLinesSet connected_cradle_base; + for (size_t line_idx = 0; line_idx < cradle.lines_.size(); line_idx++) + { + std::optional line_opt = cradle.getCradleLineOfIndex(layer_idx + cradle.config_->cradle_z_distance_layers_ + 1, line_idx); + if (line_opt) + { + connected_cradle_base.addSegment(cradle.getCenter(line_opt.value()->layer_idx_), line_opt.value()->line_.front()); + } + } + cradle_base = connected_cradle_base.offset(cradle.config_->cradle_line_width_ / 2 + EPSILON).unionPolygons(cradle_base); + } + } + + cradle_base = cradle_base.unionPolygons(); + + if (cradle.config_->cradle_lines_roof_) + { + Shape forbidden_here = volumes_.getAvoidance( + 0, + layer_idx, + (only_gracious_ || ! support_rests_on_model) ? AvoidanceType::FAST : AvoidanceType::COLLISION, + support_rests_on_model, + ! xy_overrides); + std::vector> roofs; + + if(cradle.is_roof_) + { + roofs.emplace_back(cradle_base, -1); + } + + for (size_t line_idx = 0; line_idx < cradle.lines_.size(); line_idx++) + { + std::optional line_opt = cradle.getCradleLineOfIndex(layer_idx + cradle.config_->cradle_z_distance_layers_ + 1, line_idx); + if (! line_opt) + { + continue; + } + + coord_t line_base_offset = cradle.config_->large_cradle_base_ ? std::max(coord_t(0), cradle.config_->cradle_support_base_area_radius_ - cradle.config_->cradle_line_width_ / 2) : 0; + roofs.emplace_back(line_opt.value()->area_.offset(line_base_offset, ClipperLib::jtRound), line_idx); + } + + + for (auto roof_area_pair : roofs) + { + Shape full_overhang_area = TreeSupportUtils::safeOffsetInc( + roof_area_pair.first, + roof_outset, + forbidden_here, + xy_min_distance * 2, + 0, + 1, + support_line_width, + &simplifyer); + Shape roof_area_before = full_overhang_area; + for (LayerIndex dtt_roof = 0; dtt_roof <= support_roof_layers && layer_idx - dtt_roof >= 1; dtt_roof++) + { + const Shape forbidden_next = volumes_.getAvoidance( + 0, + layer_idx - (dtt_roof + 1), + (only_gracious_ || ! support_rests_on_model) ? AvoidanceType::FAST : AvoidanceType::COLLISION, + support_rests_on_model, + ! xy_overrides); + + full_overhang_area = full_overhang_area.difference(forbidden_next); + + if (full_overhang_area.area() > EPSILON && dtt_roof < support_roof_layers) + { + if (roof_area_pair.second != -1) // If is_line + { + TreeSupportCradleLine roof_base_line(cradle.lines_[roof_area_pair.second].front()); + roof_base_line.area_ = full_overhang_area; + roof_base_line.is_base_ = true; + roof_base_line.layer_idx_ = layer_idx - dtt_roof; + cradle.lines_[roof_area_pair.second].emplace_front(roof_base_line); + } + else + { + if (dtt_roof < cradle.base_below_.size()) + { + cradle.base_below_[dtt_roof].push_back(full_overhang_area); + } + else + { + cradle.base_below_.emplace_back(full_overhang_area); + } + } + const Shape forbidden_before = volumes_.getAvoidance( + 0, + layer_idx - dtt_roof, + (only_gracious_ || ! support_rests_on_model) ? AvoidanceType::FAST : AvoidanceType::COLLISION, + support_rests_on_model, + ! xy_overrides); + + Shape supported_by_roof_below = TreeSupportUtils::safeOffsetInc( //todo better safeStepOffset values to improve performance + full_overhang_area, + max_roof_movement, + forbidden_before, + 2 * xy_min_distance, + 0, + 1, + support_line_width, + &simplifyer); + Shape overhang_part = roof_area_before.difference(supported_by_roof_below); + if (overhang_part.area() > EPSILON) + { + OverhangInformation cradle_overhang(overhang_part, false, cradle_data_[mesh_idx][layer_idx][cradle_idx], layer_idx - dtt_roof, roof_area_pair.second); + cradle.overhang_[layer_idx - dtt_roof].emplace_back(cradle_overhang); + } + } + else + { + + if(dtt_roof == 0 && roof_area_pair.second < 0) // There was no roof base! + { + cradle.is_roof_ = false; //Try a regular base + } + else if (roof_area_before.area() > 1) + { + LayerIndex line_layer_idx + = roof_area_pair.second < 0 ? LayerIndex(-1) : cradle_data_[mesh_idx][layer_idx][cradle_idx]->lines_[roof_area_pair.second].front().layer_idx_; + OverhangInformation cradle_overhang(roof_area_before, true, cradle_data_[mesh_idx][layer_idx][cradle_idx], line_layer_idx, roof_area_pair.second); + cradle.overhang_[layer_idx - dtt_roof].emplace_back(cradle_overhang); + } + break; + } + roof_area_before = full_overhang_area; + } + } + } + + if(!cradle.is_roof_) + { + Shape forbidden_here = volumes_.getAvoidance( + 0, + layer_idx, + (only_gracious_ || ! support_rests_on_model) ? AvoidanceType::FAST : AvoidanceType::COLLISION, + support_rests_on_model, + ! xy_overrides); + + coord_t offset_radius = cradle.config_->cradle_support_base_area_radius_; + Shape cradle_base_offset = cradle_base.offset(offset_radius, ClipperLib::jtRound); + if (cradle_base_offset.difference(forbidden_here).area() > 1) + { + OverhangInformation cradle_overhang(support_moves ? cradle_base : cradle_base_offset, false, cradle_data_[mesh_idx][layer_idx][cradle_idx]); + cradle.overhang_[layer_idx].emplace_back(cradle_overhang); + } + } + if(!cradle.config_->cradle_lines_roof_) + { + for (size_t line_idx = 0; line_idx < cradle.lines_.size(); line_idx++) + { + if (! cradle.lines_[line_idx].empty()) + { + LayerIndex support_cradle_on_layer_idx = cradle.lines_[line_idx].front().layer_idx_ - (cradle.config_->cradle_z_distance_layers_ + 1); + OverhangInformation line_overhang( + cradle.lines_[line_idx].front().area_, + false, + cradle_data_[mesh_idx][layer_idx][cradle_idx], + cradle.lines_[line_idx].front().layer_idx_, + line_idx); + cradle.overhang_[support_cradle_on_layer_idx].emplace_back(line_overhang); + } + } + } + if (! cradle.overhang_.empty()) + { + valid_cradles.emplace_back(cradle_data_[mesh_idx][layer_idx][cradle_idx]); + } + else + { + delete cradle_data_[mesh_idx][layer_idx][cradle_idx]; + } + } + cradle_data_[mesh_idx][layer_idx] = valid_cradles; + }); + } +} + + +void SupportCradleGeneration::addMeshToCradleCalculation(const SliceDataStorage& storage, size_t mesh_idx) +{ + const SliceMeshStorage& mesh = *storage.meshes[mesh_idx]; + if (! retrieveSetting(mesh.settings, "support_tree_cradle_enable")) + { + return; + } + calculateFloatingParts(storage, mesh_idx); +} + +void SupportCradleGeneration::generateCradleForMesh(const SliceDataStorage& storage, size_t mesh_idx) +{ + const SliceMeshStorage& mesh = *storage.meshes[mesh_idx]; + if (! retrieveSetting(mesh.settings, "support_tree_cradle_enable")) + { + return; + } + if (floating_parts_cache_[mesh_idx].empty()) + { + addMeshToCradleCalculation(storage, mesh_idx); + } + std::vector> cradle_data_mesh = generateCradleCenters(mesh, mesh_idx); + generateCradleLines(cradle_data_mesh, mesh); + if(cradle_data_[mesh_idx].size() < cradle_data_mesh.size()) + { + cradle_data_[mesh_idx].resize(cradle_data_mesh.size()); + } + for (auto [layer_idx, cradles_on_layer] :cradle_data_mesh | ranges::views::enumerate) + { + cradle_data_[mesh_idx][layer_idx].insert(cradle_data_[mesh_idx][layer_idx].end(), cradles_on_layer.begin(), cradles_on_layer.end()); + } +} + +void SupportCradleGeneration::generate(const SliceDataStorage& storage) +{ + cleanCradleLineOverlaps(); + generateCradleLineAreasAndBase(storage); +} + +void SupportCradleGeneration::pushCradleData(std::vector>& target, std::vector& support_free_areas, size_t mesh_idx) +{ + + if(target.size() < cradle_data_[mesh_idx].size()) + { + target.resize(cradle_data_[mesh_idx].size()); + } + + for (auto [layer_idx, cradles_on_layer] :cradle_data_[mesh_idx] | ranges::views::enumerate) + { + target[layer_idx].insert(target[layer_idx].end(), cradles_on_layer.begin(), cradles_on_layer.end()); + } + + if(support_free_areas.size() < support_free_areas_.size()) + { + support_free_areas.resize(support_free_areas_.size()); + } + for (auto [layer_idx, support_free_on_layer] : support_free_areas_ | ranges::views::enumerate) + { + support_free_areas[layer_idx].push_back(support_free_on_layer); + } +} + +} //End Namespace \ No newline at end of file diff --git a/src/TreeSupportTipGenerator.cpp b/src/TreeSupportTipGenerator.cpp index 0a2af0e233..04e3dfe198 100644 --- a/src/TreeSupportTipGenerator.cpp +++ b/src/TreeSupportTipGenerator.cpp @@ -38,29 +38,26 @@ TreeSupportTipGenerator::TreeSupportTipGenerator(const SliceMeshStorage& mesh, T mesh.settings.get("support_roof_enable") ? round_divide(mesh.settings.get("support_roof_height"), config_.layer_height) : use_fake_roof_ ? SUPPORT_TREE_MINIMUM_FAKE_ROOF_LAYERS : 0) - , connect_length_( - (config_.support_line_width * 100 / mesh.settings.get("support_tree_top_rate")) + std::max(2 * config_.min_radius - 1.0 * config_.support_line_width, 0.0)) - , support_tree_branch_distance_((config_.support_pattern == EFillMethod::TRIANGLES ? 3 : (config_.support_pattern == EFillMethod::GRID ? 2 : 1)) * connect_length_) + , support_tree_top_rate_(mesh.settings.get("support_tree_top_rate")) , support_roof_line_distance_( use_fake_roof_ ? (config_.support_pattern == EFillMethod::TRIANGLES ? 3 : (config_.support_pattern == EFillMethod::GRID ? 2 : 1)) * (config_.support_line_width * 100 / mesh.settings.get("support_tree_top_rate")) : mesh.settings.get("support_roof_line_distance")) - , // todo propper - support_outset_(0) + , support_outset_(0) , // Since we disable support offset when tree support is enabled we use an offset of 0 rather than the setting value mesh.settings.get("support_offset") roof_outset_(use_fake_roof_ ? support_outset_ : mesh.settings.get("support_roof_offset")) - , force_tip_to_roof_((config_.min_radius * config_.min_radius * std::numbers::pi > minimum_roof_area_ * (1000 * 1000)) && support_roof_layers_ && ! use_fake_roof_) , support_tree_limit_branch_reach_(mesh.settings.get("support_tree_limit_branch_reach")) , support_tree_branch_reach_limit_(support_tree_limit_branch_reach_ ? mesh.settings.get("support_tree_branch_reach_limit") : 0) , z_distance_delta_(std::min(config_.z_distance_top_layers + 1, mesh.overhang_areas.size())) , xy_overrides_(config_.support_overrides == SupportDistPriority::XY_OVERRIDES_Z) - , tip_roof_size_(force_tip_to_roof_ ? INT2MM2(config_.min_radius * config_.min_radius) * std::numbers::pi : 0) - , force_minimum_roof_area_(use_fake_roof_ || SUPPORT_TREE_MINIMUM_ROOF_AREA_HARD_LIMIT) , already_inserted_(mesh.overhang_areas.size()) , support_roof_drawn_(mesh.overhang_areas.size(), Shape()) , support_roof_drawn_fractional_(mesh.overhang_areas.size(), Shape()) - , roof_tips_drawn_(mesh.overhang_areas.size(), Shape()) + , force_minimum_roof_area_(use_fake_roof_ || SUPPORT_TREE_MINIMUM_ROOF_AREA_HARD_LIMIT) + , force_initial_layer_radius_(retrieveSetting(mesh.settings, "support_tree_enforce_initial_layer_diameter")) + , large_cradle_line_tips_(retrieveSetting(mesh.settings, "support_tree_large_cradle_line_tips")) { + const double support_overhang_angle = mesh.settings.get("support_angle"); const coord_t max_overhang_speed = (support_overhang_angle < TAU / 4) ? (coord_t)(tan(support_overhang_angle) * config_.layer_height) : std::numeric_limits::max(); @@ -77,7 +74,8 @@ TreeSupportTipGenerator::TreeSupportTipGenerator(const SliceMeshStorage& mesh, T // would take to travel xy_distance, nothing reasonable will come from it. The 2*z_distance_delta is only a catch for when the support angle is very high. } - cross_fill_provider_ = generateCrossFillProvider(mesh, support_tree_branch_distance_, config_.support_line_width); + coord_t connect_length = (config_.support_line_width * 100 / support_tree_top_rate_) + std::max(2 * config_.min_radius - 1.0 * config_.support_line_width, 0.0); + coord_t support_tree_branch_distance = (config_.support_pattern == EFillMethod::TRIANGLES ? 3 : (config_.support_pattern == EFillMethod::GRID ? 2 : 1)) * connect_length; std::vector known_z(mesh.layers.size()); @@ -380,10 +378,17 @@ OpenLinesSet TreeSupportTipGenerator::ensureMaximumDistancePolyline(const OpenLi } -std::shared_ptr TreeSupportTipGenerator::generateCrossFillProvider(const SliceMeshStorage& mesh, coord_t line_distance, coord_t line_width) const +std::shared_ptr TreeSupportTipGenerator::getCrossFillProvider(const SliceMeshStorage& mesh, coord_t line_distance, coord_t line_width) { if (config_.support_pattern == EFillMethod::CROSS || config_.support_pattern == EFillMethod::CROSS_3D) { + std::pair key = std::pair(line_distance, line_width); + if (cross_fill_providers_.contains(key)) + { + return cross_fill_providers_[key]; + } + std::lock_guard critical_section_generate_cross_fill(critical_cross_fill_); + AABB3D aabb; if (mesh.settings.get("infill_mesh") || mesh.settings.get("anti_overhang_mesh")) { @@ -429,8 +434,9 @@ void TreeSupportTipGenerator::dropOverhangAreas(const SliceMeshStorage& mesh, st // Technically this also makes support blocker smaller, which is wrong as they do not have a xy_distance, but it should be good enough. Shape model_outline = volumes_.getCollision(0, layer_idx, ! xy_overrides_).offset(-config_.xy_min_distance, ClipperLib::jtRound); - Shape overhang_regular = TreeSupportUtils::safeOffsetInc( - mesh.overhang_areas[layer_idx + z_distance_delta_], + // Use full_overhang to ensure dropped overhang will overlap with overhang further down. not leaving a small hole between model and roof where support could creep into. + Shape overhang_full = TreeSupportUtils::safeOffsetInc( + mesh.full_overhang_areas[layer_idx + z_distance_delta_], roof ? roof_outset_ : support_outset_, relevant_forbidden, config_.min_radius * 1.75 + config_.xy_min_distance, @@ -438,9 +444,9 @@ void TreeSupportTipGenerator::dropOverhangAreas(const SliceMeshStorage& mesh, st 1, config_.support_line_distance / 2, &config_.simplifier); - Shape remaining_overhang = mesh.overhang_areas[layer_idx + z_distance_delta_] + Shape remaining_overhang = mesh.full_overhang_areas[layer_idx + z_distance_delta_] .offset(roof ? roof_outset_ : support_outset_) - .difference(overhang_regular) + .difference(overhang_full) .intersection(relevant_forbidden) .difference(model_outline); for (size_t lag_ctr = 1; lag_ctr <= max_overhang_insert_lag_ && layer_idx - coord_t(lag_ctr) >= 1 && ! remaining_overhang.empty(); lag_ctr++) @@ -456,6 +462,7 @@ void TreeSupportTipGenerator::dropOverhangAreas(const SliceMeshStorage& mesh, st }); cura::parallel_for( + 0, result.size(), [&](const LayerIndex layer_idx) @@ -464,7 +471,7 @@ void TreeSupportTipGenerator::dropOverhangAreas(const SliceMeshStorage& mesh, st }); } -void TreeSupportTipGenerator::calculateRoofAreas(const cura::SliceMeshStorage& mesh) +void TreeSupportTipGenerator::calculateRoofAreas(const cura::SliceMeshStorage& mesh, std::vector>& cradle_data) { std::vector potential_support_roofs(mesh.overhang_areas.size(), Shape()); std::mutex critical_potential_support_roofs; @@ -475,11 +482,34 @@ void TreeSupportTipGenerator::calculateRoofAreas(const cura::SliceMeshStorage& m dropOverhangAreas(mesh, dropped_overhangs, true); } + std::vector all_cradle_areas(cradle_data.size()); // All cradle areas. Later offset by min xy distance. + for (LayerIndex layer_idx = 0; layer_idx < cradle_data.size(); layer_idx++) + { + for (size_t cradle_idx = 0; cradle_idx < cradle_data[layer_idx].size(); cradle_idx++) + { + for (auto [line_idx, lines] : cradle_data[layer_idx][cradle_idx]->lines_ | ranges::views::enumerate) + { + for (auto [height_idx, line] : lines | ranges::views::enumerate) + { + all_cradle_areas[line.layer_idx_].push_back(line.area_); + } + } + for (auto [base_idx, base] : cradle_data[layer_idx][cradle_idx]->base_below_ | ranges::views::enumerate) + { + all_cradle_areas[layer_idx - base_idx].push_back(base); + } + } + } + cura::parallel_for( 0, mesh.overhang_areas.size() - z_distance_delta_, [&](const LayerIndex layer_idx) { + + // Needed for fuzzy roof search below. + all_cradle_areas[layer_idx] = all_cradle_areas[layer_idx].unionPolygons().offset(config_.xy_distance + FUDGE_LENGTH).unionPolygons(); + if (mesh.overhang_areas[layer_idx + z_distance_delta_].empty()) { return; // This is a continue if imagined in a loop context. @@ -487,16 +517,14 @@ void TreeSupportTipGenerator::calculateRoofAreas(const cura::SliceMeshStorage& m // Roof does not have a radius, so remove it using offset. Note that there is no 0 radius avoidance, and it would not be identical with the avoidance offset with // -radius. This is intentional here, as support roof is still valid if only a part of the tip may reach it. - Shape forbidden_here = volumes_ - .getAvoidance( - config_.getRadius(0), - layer_idx, - (only_gracious_ || ! config_.support_rests_on_model) ? AvoidanceType::FAST : AvoidanceType::COLLISION, - config_.support_rests_on_model, - ! xy_overrides_) - .offset(-config_.getRadius(0), ClipperLib::jtRound); - - // todo Since arachnea the assumption that an area smaller then line_width is not printed is no longer true all such safeOffset should have config.support_line_width + Shape forbidden_here = volumes_.getAvoidance( + 0, + layer_idx, + (only_gracious_ || ! config_.support_rests_on_model) ? AvoidanceType::FAST : AvoidanceType::COLLISION, + config_.support_rests_on_model, + ! xy_overrides_); + + // todo Since arachnea the assumption that an area smaller then line_width is not printed is no longer true all such safeOffset should have config_.support_line_width // replaced with another setting. It should still work in most cases, but it should be possible to create a situation where a overhang outset lags though a wall. I will // take a look at this later. Shape full_overhang_area = TreeSupportUtils::safeOffsetInc( @@ -509,19 +537,21 @@ void TreeSupportTipGenerator::calculateRoofAreas(const cura::SliceMeshStorage& m config_.support_line_distance / 2, &config_.simplifier); + // If an area is already supported by cradle don't put roof there. + full_overhang_area = full_overhang_area.difference(all_cradle_areas[layer_idx]); + for (LayerIndex dtt_roof = 0; dtt_roof < support_roof_layers_ && layer_idx - dtt_roof >= 1; dtt_roof++) { - const Shape forbidden_next = volumes_ - .getAvoidance( - config_.getRadius(0), - layer_idx - (dtt_roof + 1), - (only_gracious_ || ! config_.support_rests_on_model) ? AvoidanceType::FAST : AvoidanceType::COLLISION, - config_.support_rests_on_model, - ! xy_overrides_) - .offset(-config_.getRadius(0), ClipperLib::jtRound); + const Shape forbidden_next = volumes_.getAvoidance( + 0, + layer_idx - (dtt_roof + 1), + (only_gracious_ || ! config_.support_rests_on_model) ? AvoidanceType::FAST : AvoidanceType::COLLISION, + config_.support_rests_on_model, + ! xy_overrides_); full_overhang_area = full_overhang_area.difference(forbidden_next); + if (force_minimum_roof_area_) { full_overhang_area.removeSmallAreas(minimum_roof_area_); @@ -550,8 +580,9 @@ void TreeSupportTipGenerator::calculateRoofAreas(const cura::SliceMeshStorage& m { // Now, because the avoidance/collision was subtracted above, the overhang parts that are of xy distance were removed, so to merge areas that should have been one // offset by xy_min_distance and then undo it. In a perfect world the offset here would be of a mode that makes sure that - // area.offset(config.xy_min_distance).unionPolygons().offset(-config.xy_min_distance) = area if there is only one polygon in said area. I have not encountered issues + // area.offset(config_.xy_min_distance).unionPolygons().offset(-config_.xy_min_distance) = area if there is only one polygon in said area. I have not encountered issues // with using the default mitered here. Could be that i just have not encountered an issue with it yet though. + potential_support_roofs[layer_idx] = potential_support_roofs[layer_idx] .unionPolygons() .offset(config_.xy_min_distance) @@ -571,14 +602,12 @@ void TreeSupportTipGenerator::calculateRoofAreas(const cura::SliceMeshStorage& m { // Roof does not have a radius, so remove it using offset. Note that there is no 0 radius avoidance, and it would not be identical with the avoidance offset with // -radius. This is intentional here, as support roof is still valid if only a part of the tip may reach it. - Shape forbidden_here = volumes_ - .getAvoidance( - config_.getRadius(0), - layer_idx, - (only_gracious_ || ! config_.support_rests_on_model) ? AvoidanceType::FAST : AvoidanceType::COLLISION, - config_.support_rests_on_model, - ! xy_overrides_) - .offset(-(config_.getRadius(0)), ClipperLib::jtRound); + Shape forbidden_here = volumes_.getAvoidance( + 0, + layer_idx, + (only_gracious_ || ! config_.support_rests_on_model) ? AvoidanceType::FAST : AvoidanceType::COLLISION, + config_.support_rests_on_model, + ! xy_overrides_).unionPolygons(all_cradle_areas[layer_idx]); if (! force_minimum_roof_area_) { @@ -594,7 +623,7 @@ void TreeSupportTipGenerator::calculateRoofAreas(const cura::SliceMeshStorage& m fuzzy_area.push_back(potential_support_roofs[layer_idx + layer_offset]); } fuzzy_area = fuzzy_area.unionPolygons(); - fuzzy_area.removeSmallAreas(std::max(minimum_roof_area_, tip_roof_size_)); + fuzzy_area.removeSmallAreas(minimum_roof_area_); for (Shape potential_roof : potential_support_roofs[layer_idx].difference(forbidden_here).splitIntoParts()) { @@ -607,13 +636,14 @@ void TreeSupportTipGenerator::calculateRoofAreas(const cura::SliceMeshStorage& m else { Shape valid_roof = potential_support_roofs[layer_idx].difference(forbidden_here); - valid_roof.removeSmallAreas(std::max(minimum_roof_area_, tip_roof_size_)); + valid_roof.removeSmallAreas(minimum_roof_area_); additional_support_roofs[layer_idx].push_back(valid_roof); } } }); cura::parallel_for( + 0, additional_support_roofs.size(), [&](const LayerIndex layer_idx) @@ -623,13 +653,15 @@ void TreeSupportTipGenerator::calculateRoofAreas(const cura::SliceMeshStorage& m } -void TreeSupportTipGenerator::addPointAsInfluenceArea( +TreeSupportElement* TreeSupportTipGenerator::addPointAsInfluenceArea( std::vector>& move_bounds, std::pair p, size_t dtt, + double hidden_radius_increase, LayerIndex insert_layer, size_t dont_move_until, - bool roof, + TipRoofType roof, + bool cradle, bool skip_ovalisation, std::vector additional_ovalization_targets) { @@ -639,7 +671,7 @@ void TreeSupportTipGenerator::addPointAsInfluenceArea( if (! config_.support_rests_on_model && ! to_bp) { spdlog::warn("Tried to add an invalid support point"); - return; + return nullptr; } Polygon circle; Polygon base_circle = TreeSupportBaseCircle::getBaseCircle(); @@ -661,23 +693,26 @@ void TreeSupportTipGenerator::addPointAsInfluenceArea( to_bp, gracious, ! xy_overrides_, - dont_move_until, - roof, + dont_move_until + dtt, + roof == TipRoofType::SUPPORTS_ROOF, + cradle, safe_radius, - ! roof && force_tip_to_roof_, + roof == TipRoofType::IS_ROOF && ! use_fake_roof_, skip_ovalisation, support_tree_limit_branch_reach_, - support_tree_branch_reach_limit_); + support_tree_branch_reach_limit_, + hidden_radius_increase); elem->area_ = new Shape(area); - for (Point2LL target : additional_ovalization_targets) { elem->additional_ovalization_targets_.emplace_back(target); } move_bounds[insert_layer].emplace(elem); + return elem; } } + return nullptr; } @@ -686,118 +721,107 @@ void TreeSupportTipGenerator::addLinesAsInfluenceAreas( std::vector lines, size_t roof_tip_layers, LayerIndex insert_layer_idx, - bool supports_roof, + coord_t tip_radius, + OverhangInformation& overhang_data, size_t dont_move_until, bool connect_points) { - // Add tip area as roof (happens when minimum roof area > minimum tip area) if possible. This is required as there is no guarantee that if support_roof_wall_count == 0 that a - // certain roof area will actually have lines. - size_t dtt_roof_tip = 0; - if (config_.support_roof_wall_count == 0) + for (LineInformation line : lines) { - for (dtt_roof_tip = 0; dtt_roof_tip < roof_tip_layers && insert_layer_idx - dtt_roof_tip >= 1; dtt_roof_tip++) + // If a line consists of enough tips, the assumption is that it is not a single tip, but part of a simulated support pattern. + // Ovalisation should be disabled if they may be placed close to each other to prevent tip-areas merging. If the tips has to turn into roof, the area is most likely not + // large enough for this to cause issues. todo does this still make sense? + const bool disable_ovalization = ! connect_points && config_.min_radius < 3 * config_.support_line_width && roof_tip_layers == 0; + for (auto [idx, point_data] : line | ranges::views::enumerate) { - std::function)> evaluateRoofWillGenerate = [&](std::pair p) + std::vector additional_ovalization_targets; + if (connect_points) // If the radius is to large then the ovalization would cause the area to float in the air. { - Polygon roof_circle; - for (const Point2LL& corner : TreeSupportBaseCircle::getBaseCircle()) + if (idx != 0) { - roof_circle.push_back(p.first + corner * std::max(config_.min_radius / TreeSupportBaseCircle::base_radius, coord_t(1))); + additional_ovalization_targets.emplace_back(line[idx - 1].first); } - Shape area = roof_circle.offset(0); - return ! TreeSupportUtils::generateSupportInfillLines(area, config_, true, insert_layer_idx - dtt_roof_tip, support_roof_line_distance_, cross_fill_provider_, true) - .empty(); - }; - - std::pair, std::vector> split - = splitLines(lines, getEvaluatePointForNextLayerFunction(insert_layer_idx - dtt_roof_tip)); // Keep all lines that are still valid on the next layer. - - for (LineInformation line : split.second) // Add all points that would not be valid. - { - for (std::pair point_data : line) + if (idx != line.size() - 1) { - addPointAsInfluenceArea(move_bounds, point_data, 0, insert_layer_idx - dtt_roof_tip, roof_tip_layers - dtt_roof_tip, dtt_roof_tip != 0, false); + additional_ovalization_targets.emplace_back(line[idx + 1].first); } } - // Not all roofs are guaranteed to actually generate lines, so filter these out and add them as points. - split = splitLines(split.first, evaluateRoofWillGenerate); - lines = split.first; - - for (LineInformation line : split.second) + if (overhang_data.is_cradle_ && tip_radius < overhang_data.cradle_->config_->cradle_support_base_area_radius_) { - for (std::pair point_data : line) + if ((overhang_data.isCradleLine() && large_cradle_line_tips_) || (! overhang_data.isCradleLine() && (! overhang_data.is_roof_ || large_cradle_line_tips_))) { - addPointAsInfluenceArea(move_bounds, point_data, 0, insert_layer_idx - dtt_roof_tip, roof_tip_layers - dtt_roof_tip, dtt_roof_tip != 0, false); + tip_radius = overhang_data.cradle_->config_->cradle_support_base_area_radius_; } } - // Add all tips as roof to the roof storage. - Shape added_roofs; - for (LineInformation line : lines) + size_t tip_dtt = tip_radius >= config_.branch_radius ? config_.tip_layers + : (config_.tip_layers * (tip_radius - config_.min_radius)) / (config_.branch_radius - config_.min_radius); + + coord_t hidden_radius = tip_radius > config_.branch_radius ? tip_radius - config_.branch_radius : 0; + double hidden_radius_increases = hidden_radius / (config_.branch_radius * (std::max(config_.diameter_scale_bp_radius - config_.diameter_angle_scale_factor, 0.0))); + + TipRoofType roof_type = TipRoofType::NONE; + if (! overhang_data.is_cradle_ && roof_tip_layers > 0) { - for (std::pair p : line) - { - Polygon roof_circle; - for (const Point2LL& corner : TreeSupportBaseCircle::getBaseCircle()) - { - roof_circle.push_back(p.first + corner * std::max(config_.min_radius / TreeSupportBaseCircle::base_radius, coord_t(1))); - } - added_roofs.push_back(roof_circle); - } + roof_type = TipRoofType::IS_ROOF; } - added_roofs = added_roofs.unionPolygons(); + else if (overhang_data.is_roof_) { - std::lock_guard critical_section_roof(critical_roof_tips_); - roof_tips_drawn_[insert_layer_idx - dtt_roof_tip].push_back(added_roofs); + roof_type = TipRoofType::SUPPORTS_ROOF; + } - if (dtt_roof_tip == 0) + if (! overhang_data.is_roof_ && overhang_data.is_cradle_ && ! overhang_data.isCradleLine() && overhang_data.cradle_->is_roof_) + { + roof_type = TipRoofType::IS_ROOF; + // No information about the amount of missing roof layers for cradle bases is stored. + // So it is only known that there could be one layer of roof. So to keep consistent behaviour only add one roof tip layer. + // todo change that in the future? + dont_move_until = 1; + } + + + TreeSupportElement* elem = addPointAsInfluenceArea( + move_bounds, + point_data, + tip_dtt, + hidden_radius_increases, + insert_layer_idx, + dont_move_until, + roof_type, + overhang_data.is_cradle_, + disable_ovalization, + additional_ovalization_targets); + + if (elem) + { + if (overhang_data.isCradleLine()) { - support_roof_drawn_fractional_[insert_layer_idx].push_back(added_roofs); + elem->cradle_line_ = std::make_shared(overhang_data.cradle_, overhang_data.cradle_layer_idx_, overhang_data.cradle_line_idx_); } } } } +} - for (LineInformation line : lines) + +void TreeSupportTipGenerator::removeUselessAddedPoints(std::vector>& move_bounds, SliceDataStorage& storage, std::vector>& cradle_data) +{ + std::vector all_cradle_roofs(storage.support.supportLayers.size()); + for (auto [layer_idx, cradles] : cradle_data | ranges::views::enumerate) { - // If a line consists of enough tips, the assumption is that it is not a single tip, but part of a simulated support pattern. - // Ovalisation should be disabled if they may be placed close to each other to prevent tip-areas merging. If the tips has to turn into roof, the area is most likely not - // large enough for this to cause issues. - const bool disable_ovalization = ! connect_points && config_.min_radius < 3 * config_.support_line_width && roof_tip_layers == 0 && dtt_roof_tip == 0; - for (auto [idx, point_data] : line | ranges::views::enumerate) + for (auto cradle : cradles) { - std::vector additional_ovalization_targets; - if (connect_points) // If the radius is to large then the ovalization would cause the area to float in the air. + if (cradle->is_roof_) { - if (idx != 0) - { - additional_ovalization_targets.emplace_back(line[idx - 1].first); - } - if (idx != line.size() - 1) + for (auto [base_idx, base] : cradle->base_below_ | ranges::views::enumerate) { - additional_ovalization_targets.emplace_back(line[idx + 1].first); + all_cradle_roofs[layer_idx - base_idx].push_back(base); } } - addPointAsInfluenceArea( - move_bounds, - point_data, - 0, - insert_layer_idx - dtt_roof_tip, - dont_move_until > dtt_roof_tip ? dont_move_until - dtt_roof_tip : 0, - dtt_roof_tip != 0 || supports_roof, - disable_ovalization, - additional_ovalization_targets); } } -} - -void TreeSupportTipGenerator::removeUselessAddedPoints( - std::vector>& move_bounds, - SliceDataStorage& storage, - std::vector& additional_support_areas) -{ cura::parallel_for( 0, move_bounds.size(), @@ -806,10 +830,9 @@ void TreeSupportTipGenerator::removeUselessAddedPoints( if (layer_idx + 1 < storage.support.supportLayers.size()) { std::vector to_be_removed; - Shape roof_on_layer_above = use_fake_roof_ ? support_roof_drawn_[layer_idx + 1] - : storage.support.supportLayers[layer_idx + 1].support_roof.unionPolygons(additional_support_areas[layer_idx + 1]); - Shape roof_on_layer - = use_fake_roof_ ? support_roof_drawn_[layer_idx] : storage.support.supportLayers[layer_idx].support_roof.unionPolygons(additional_support_areas[layer_idx]); + Shape roof_on_layer_above = support_roof_drawn_[layer_idx + 1]; + roof_on_layer_above = roof_on_layer_above.unionPolygons(all_cradle_roofs[layer_idx + 1]); + Shape roof_on_layer = support_roof_drawn_[layer_idx]; for (TreeSupportElement* elem : move_bounds[layer_idx]) { @@ -817,17 +840,21 @@ void TreeSupportTipGenerator::removeUselessAddedPoints( { to_be_removed.emplace_back(elem); } - else if (elem->supports_roof_) + else if (elem->supports_roof_ && elem->cradle_line_.get() == nullptr) { Point2LL from = elem->result_on_layer_; PolygonUtils::moveInside(roof_on_layer_above, from); - // Remove branches should have interface above them, but dont. Should never happen. + // Remove branches should have interface above them, but don't. Should never happen. if (roof_on_layer_above.empty() || (! roof_on_layer_above.inside(elem->result_on_layer_) - && vSize2(from - elem->result_on_layer_) > config_.getRadius(0) * config_.getRadius(0) + FUDGE_LENGTH * FUDGE_LENGTH)) + && vSize2(from - elem->result_on_layer_) > std::pow(config_.getRadius(*elem) + FUDGE_LENGTH, 2))) { to_be_removed.emplace_back(elem); - spdlog::warn("Removing already placed tip that should have roof above it?"); + spdlog::warn( + "Removing already placed tip that should have roof above it. Distance from roof is {}. Radius is {} on layer {}.", + vSize(from - elem->result_on_layer_), + config_.getRadius(*elem), + layer_idx); } } } @@ -847,9 +874,11 @@ void TreeSupportTipGenerator::generateTips( SliceDataStorage& storage, const SliceMeshStorage& mesh, std::vector>& move_bounds, - std::vector& additional_support_areas, - std::vector>& placed_fake_roof_areas) + std::vector>& placed_fake_roof_areas, + std::vector& support_free_areas, + std::vector>& cradle_data) { + const auto t_start = std::chrono::high_resolution_clock::now(); std::vector> new_tips(move_bounds.size()); const coord_t circle_length_to_half_linewidth_change @@ -863,9 +892,51 @@ void TreeSupportTipGenerator::generateTips( // ^^^ Extra support offset to compensate for larger tip radiis. Also outset a bit more when z overwrites xy, because supporting something with a part of a support line is // better than not supporting it at all. + + if(cradle_data.size() < mesh.overhang_areas.size()) + { + cradle_data.resize(mesh.overhang_areas.size()); + } + if (support_roof_layers_) { - calculateRoofAreas(mesh); + calculateRoofAreas(mesh, cradle_data); + } + const auto t_roof = std::chrono::high_resolution_clock::now(); + + + std::vector> all_cradles_requiring_support(cradle_data.size()); + std::vector all_cradle_areas(cradle_data.size()); + for (LayerIndex layer_idx = 0; layer_idx < cradle_data.size(); layer_idx++) + { + for (size_t cradle_idx = 0; cradle_idx < cradle_data[layer_idx].size(); cradle_idx++) + { + for (auto overhang_pair : cradle_data[layer_idx][cradle_idx]->overhang_) + { + if(all_cradles_requiring_support.size() <= overhang_pair.first) //should not happen, but just to be sure + { + all_cradles_requiring_support.resize(overhang_pair.first+1); + } + all_cradles_requiring_support[overhang_pair.first].emplace_back(cradle_data[layer_idx][cradle_idx]); + } + + for (auto [line_idx, lines] : cradle_data[layer_idx][cradle_idx]->lines_ | ranges::views::enumerate) + { + for (auto [height_idx, line] : lines | ranges::views::enumerate) + { + if(all_cradle_areas.size() <= line.layer_idx_) //should not happen, but just to be sure + { + all_cradle_areas.resize(line.layer_idx_+1); + } + + all_cradle_areas[line.layer_idx_].push_back(line.area_); + } + } + for (auto [base_idx, base] : cradle_data[layer_idx][cradle_idx]->base_below_ | ranges::views::enumerate) + { + all_cradle_areas[layer_idx - base_idx].push_back(base); + } + } } cura::parallel_for( @@ -873,11 +944,19 @@ void TreeSupportTipGenerator::generateTips( mesh.overhang_areas.size() - z_distance_delta_, [&](const LayerIndex layer_idx) { - if (mesh.overhang_areas[layer_idx + z_distance_delta_].empty() && (layer_idx + 1 >= support_roof_drawn_.size() || support_roof_drawn_[layer_idx + 1].empty())) + if (mesh.overhang_areas[layer_idx + z_distance_delta_].empty() && (layer_idx + 1 >= support_roof_drawn_.size() || support_roof_drawn_[layer_idx + 1].empty()) + && (layer_idx >= all_cradles_requiring_support.size() || all_cradles_requiring_support[layer_idx].empty())) { return; // This is a continue if imagined in a loop context. } + coord_t current_tip_radius + = (force_initial_layer_radius_ && config_.recommendedMinRadius(layer_idx) > config_.min_radius) ? config_.recommendedMinRadius(layer_idx) : config_.min_radius; + coord_t connect_length = (config_.support_line_width * 100 / support_tree_top_rate_) + std::max(2 * current_tip_radius - 1.0 * config_.support_line_width, 0.0); + coord_t support_tree_branch_distance + = (config_.support_pattern == EFillMethod::TRIANGLES ? 3 : (config_.support_pattern == EFillMethod::GRID ? 2 : 1)) * connect_length; + bool force_tip_to_roof = (current_tip_radius * current_tip_radius * std::numbers::pi > minimum_roof_area_ * (1000 * 1000)) && ! use_fake_roof_ && support_roof_layers_; + Shape relevant_forbidden = volumes_.getAvoidance( config_.getRadius(0), layer_idx, @@ -889,10 +968,18 @@ void TreeSupportTipGenerator::generateTips( = relevant_forbidden.offset(EPSILON) .unionPolygons(); // Prevent rounding errors down the line, points placed directly on the line of the forbidden area may not be added otherwise. + std::function generateLines = [&](const Shape& area, bool roof, LayerIndex generate_layer_idx) { + // todo ensure larger tips have reasonable density. How would one do that though? + // If tips are 7mm thick, does 20% fill mean a distance of 35mm between tips? Does not make sense... coord_t upper_line_distance = support_supporting_branch_distance_; - coord_t line_distance = std::max(roof ? support_roof_line_distance_ : support_tree_branch_distance_, upper_line_distance); + coord_t line_distance = std::max(roof ? support_roof_line_distance_ : support_tree_branch_distance, upper_line_distance); + coord_t current_tip_radius_generate = (force_initial_layer_radius_ && config_.recommendedMinRadius(generate_layer_idx) > config_.min_radius) + ? config_.recommendedMinRadius(generate_layer_idx) + : config_.min_radius; + + bool use_grid = line_distance == upper_line_distance; return TreeSupportUtils::generateSupportInfillLines( area, @@ -900,13 +987,13 @@ void TreeSupportTipGenerator::generateTips( roof && ! use_fake_roof_, generate_layer_idx, line_distance, - cross_fill_provider_, - roof && ! use_fake_roof_, - line_distance == upper_line_distance); + roof ? nullptr : getCrossFillProvider(mesh, line_distance, current_tip_radius_generate), + (roof && ! use_fake_roof_) ? config_.support_roof_wall_count : 0, + use_grid ? EFillMethod::GRID : EFillMethod::NONE); }; - std::vector> overhang_processing; + std::vector overhang_processing; // ^^^ Every overhang has saved if a roof should be generated for it. // This can NOT be done in the for loop as an area may NOT have a roof even if it is larger than the minimum_roof_area when it is only larger because of the support // horizontal expansion and it would not have a roof if the overhang is offset by support roof horizontal expansion instead. (At least this is the current behavior @@ -917,9 +1004,9 @@ void TreeSupportTipGenerator::generateTips( if (support_roof_layers_ && layer_idx + 1 < support_roof_drawn_.size()) { - core_overhang = core_overhang.difference(support_roof_drawn_[layer_idx]); + core_overhang = core_overhang.difference(support_roof_drawn_[layer_idx].offset(config_.support_roof_line_width / 2).unionPolygons()); for (Shape roof_part : support_roof_drawn_[layer_idx + 1] - .difference(support_roof_drawn_[layer_idx]) + .difference(support_roof_drawn_[layer_idx].offset(config_.maximum_move_distance_slow).unionPolygons()) .splitIntoParts(true)) // If there is a roof, the roof will be one layer above the tips. { //^^^Technically one should also subtract the avoidance of radius 0 (similarly how calculated in calculateRoofArea), as there can be some rounding errors @@ -927,6 +1014,18 @@ void TreeSupportTipGenerator::generateTips( overhang_processing.emplace_back(roof_part, true); } } + all_cradle_areas[layer_idx] = all_cradle_areas[layer_idx].unionPolygons().offset(EPSILON).unionPolygons(); + core_overhang = core_overhang.difference(support_free_areas[layer_idx]); + core_overhang = core_overhang.difference(all_cradle_areas[layer_idx].offset(config_.xy_distance + FUDGE_LENGTH).unionPolygons()); // todo what do if large tips? + + for (size_t cradle_idx = 0; cradle_idx < all_cradles_requiring_support[layer_idx].size(); cradle_idx++) + { + for (size_t cradle_overhang_idx = 0; cradle_overhang_idx < all_cradles_requiring_support[layer_idx][cradle_idx]->overhang_[layer_idx].size(); cradle_overhang_idx++) + { + overhang_processing.emplace_back(all_cradles_requiring_support[layer_idx][cradle_idx]->overhang_[layer_idx][cradle_overhang_idx]); + core_overhang = core_overhang.difference(all_cradles_requiring_support[layer_idx][cradle_idx]->overhang_[layer_idx][cradle_overhang_idx].overhang_); + } + } Shape overhang_regular = TreeSupportUtils::safeOffsetInc( core_overhang, @@ -944,34 +1043,41 @@ void TreeSupportTipGenerator::generateTips( coord_t extra_total_offset_acc = 0; // Offset the area to compensate for large tip radiis. Offset happens in multiple steps to ensure the tip is as close to the original overhang as possible. - while (extra_total_offset_acc + config_.support_line_width / 8 - < extra_outset) //+mesh_config.support_line_width / 8 to avoid calculating very small (useless) offsets because of rounding errors. + { - coord_t offset_current_step = extra_total_offset_acc + 2 * config_.support_line_width > config_.min_radius - ? std::min(config_.support_line_width / 8, extra_outset - extra_total_offset_acc) - : std::min(circle_length_to_half_linewidth_change, extra_outset - extra_total_offset_acc); - extra_total_offset_acc += offset_current_step; - Shape overhang_offset = TreeSupportUtils::safeOffsetInc( - overhang_regular, - 1.5 * extra_total_offset_acc, - volumes_.getCollision(0, layer_idx, true), - config_.xy_min_distance + config_.support_line_width, - 0, - 1, - config_.support_line_distance / 2, - &config_.simplifier); - remaining_overhang = remaining_overhang.difference(overhang_offset.unionPolygons(support_roof_drawn_[layer_idx].offset(1.5 * extra_total_offset_acc))) - .unionPolygons(); // overhang_offset is combined with roof, as all area that has a roof, is already supported by said roof. - Shape next_overhang = TreeSupportUtils::safeOffsetInc( - remaining_overhang, - extra_total_offset_acc, - volumes_.getCollision(0, layer_idx, true), - config_.xy_min_distance + config_.support_line_width, - 0, - 1, - config_.support_line_distance / 2, - &config_.simplifier); - overhang_regular = overhang_regular.unionPolygons(next_overhang.difference(relevant_forbidden)); + Shape already_supported = support_roof_drawn_[layer_idx]; + already_supported.push_back(all_cradle_areas[layer_idx]); + already_supported.push_back(support_free_areas[layer_idx]); // While point there are not supported, there may be no support anyway. + already_supported = already_supported.unionPolygons(); + while (extra_total_offset_acc + config_.support_line_width / 8 + < extra_outset) //+mesh_config_.support_line_width / 8 to avoid calculating very small (useless) offsets because of rounding errors. + { + coord_t offset_current_step = extra_total_offset_acc + 2 * config_.support_line_width > config_.min_radius + ? std::min(config_.support_line_width / 8, extra_outset - extra_total_offset_acc) + : std::min(circle_length_to_half_linewidth_change, extra_outset - extra_total_offset_acc); + extra_total_offset_acc += offset_current_step; + Shape overhang_offset = TreeSupportUtils::safeOffsetInc( + overhang_regular, + 1.5 * extra_total_offset_acc, + volumes_.getCollision(0, layer_idx, true), + config_.xy_min_distance + config_.support_line_width, + 0, + 1, + config_.support_line_distance / 2, + &config_.simplifier); + remaining_overhang = remaining_overhang.difference(overhang_offset.unionPolygons(already_supported.offset(1.5 * extra_total_offset_acc))) + .unionPolygons(); // overhang_offset is combined with roof, as all area that has a roof, is already supported by said roof. + Shape next_overhang = TreeSupportUtils::safeOffsetInc( + remaining_overhang, + extra_total_offset_acc, + volumes_.getCollision(0, layer_idx, true), + config_.xy_min_distance + config_.support_line_width, + 0, + 1, + config_.support_line_distance / 2, + &config_.simplifier); + overhang_regular = overhang_regular.unionPolygons(next_overhang.difference(relevant_forbidden)); + } } // If the xy distance overrides the z distance, some support needs to be inserted further down. @@ -985,17 +1091,16 @@ void TreeSupportTipGenerator::generateTips( { continue; } - std::vector overhang_lines; - OpenLinesSet polylines = ensureMaximumDistancePolyline(generateLines(remaining_overhang_part, false, layer_idx), config_.min_radius, 1, false); + OpenLinesSet polylines = ensureMaximumDistancePolyline(generateLines(remaining_overhang_part, false, layer_idx), current_tip_radius, 1, false); // ^^^ Support_line_width to form a line here as otherwise most will be unsupported. // Technically this violates branch distance, but not only is this the only reasonable choice, // but it ensures consistent behavior as some infill patterns generate each line segment as its own polyline part causing a similar line forming behavior. - // Also it is assumed that the area that is valid a layer below is to small for support roof. + // Also it is assumed that the area that is valid a layer below is too small for support roof. if (polylines.pointCount() <= 3) { // Add the outer wall to ensure it is correct supported instead. - polylines = ensureMaximumDistancePolyline(TreeSupportUtils::toPolylines(remaining_overhang_part), connect_length_, 3, true); + polylines = ensureMaximumDistancePolyline(TreeSupportUtils::toPolylines(remaining_overhang_part), connect_length, 3, true); } for (auto line : polylines) @@ -1010,6 +1115,9 @@ void TreeSupportTipGenerator::generateTips( for (size_t lag_ctr = 1; lag_ctr <= max_overhang_insert_lag_ && ! overhang_lines.empty() && layer_idx - coord_t(lag_ctr) >= 1; lag_ctr++) { + LayerIndex layer_idx_lag = layer_idx - lag_ctr; + coord_t current_tip_radius_lag + = (force_initial_layer_radius_ && config_.recommendedMinRadius(layer_idx_lag) > config_.min_radius) ? config_.recommendedMinRadius(layer_idx_lag) : config_.min_radius; // get least restricted avoidance for layer_idx-lag_ctr Shape relevant_forbidden_below = volumes_.getAvoidance( config_.getRadius(0), @@ -1024,28 +1132,32 @@ void TreeSupportTipGenerator::generateTips( return relevant_forbidden_below.inside(p.first, true); }; - if (support_roof_layers_) + Shape already_supported = support_roof_drawn_[layer_idx - lag_ctr]; + already_supported.push_back(support_free_areas[layer_idx - lag_ctr]); // While point there are not supported, there may be no support anyway. + already_supported = already_supported.unionPolygons(); + + // Remove all points that are for some reason are already supported + std::function)> evaluateAlreadySupported = [&](std::pair p) { - // Remove all points that are for some reason part of a roof area, as the point is already supported by roof - std::function)> evaluatePartOfRoof = [&](std::pair p) - { - return support_roof_drawn_[layer_idx - lag_ctr].inside(p.first, true); - }; + return already_supported.inside(p.first, true); + }; + + overhang_lines = splitLines(overhang_lines, evaluateAlreadySupported).second; - overhang_lines = splitLines(overhang_lines, evaluatePartOfRoof).second; - } std::pair, std::vector> split = splitLines(overhang_lines, evaluatePoint); // Keep all lines that are invalid. overhang_lines = split.first; std::vector fresh_valid_points = convertLinesToInternal(convertInternalToLines(split.second), layer_idx - lag_ctr); // ^^^ Set all now valid lines to their correct LineStatus. Easiest way is to just discard Avoidance information for each point and evaluate them again. + OverhangInformation dummy_overhang_info(remaining_overhang_part, false); addLinesAsInfluenceAreas( new_tips, fresh_valid_points, - (force_tip_to_roof_ && lag_ctr <= support_roof_layers_) ? support_roof_layers_ : 0, + (force_tip_to_roof && lag_ctr <= support_roof_layers_) ? support_roof_layers_ : 0, layer_idx - lag_ctr, - false, + current_tip_radius_lag, + dummy_overhang_info, support_roof_layers_, false); } @@ -1059,24 +1171,38 @@ void TreeSupportTipGenerator::generateTips( overhang_processing.emplace_back(support_part, false); } - for (std::pair overhang_pair : overhang_processing) + for (OverhangInformation overhang_data : overhang_processing) { - const bool roof_allowed_for_this_part = overhang_pair.second; - Shape overhang_outset = overhang_pair.first; - const size_t min_support_points = std::max(coord_t(1), std::min(coord_t(EPSILON), overhang_outset.length() / connect_length_)); + Shape overhang_outset = overhang_data.overhang_; + const size_t min_support_points = std::max(coord_t(2), std::min(coord_t(EPSILON), overhang_outset.length() / connect_length)); std::vector overhang_lines; bool only_lines = true; - + OpenLinesSet polylines; // The tip positions are determined here. - // todo can cause inconsistent support density if a line exactly aligns with the model - OpenLinesSet polylines = ensureMaximumDistancePolyline( - generateLines(overhang_outset, roof_allowed_for_this_part, layer_idx + roof_allowed_for_this_part), - ! roof_allowed_for_this_part ? config_.min_radius * 2 - : use_fake_roof_ ? support_supporting_branch_distance_ - : connect_length_, - 1, - false); + if (overhang_data.isCradleLine() && + overhang_data.cradle_->config_->cradle_line_width_ / 2 < std::max(current_tip_radius, overhang_data.cradle_->config_->cradle_support_base_area_radius_)) + { + std::optional cradle_line_opt + = overhang_data.cradle_->getCradleLineOfIndex(overhang_data.cradle_layer_idx_, overhang_data.cradle_line_idx_); + if(cradle_line_opt) + { + OpenLinesSet line; + line.push_back(cradle_line_opt.value()->line_); + coord_t cradle_line_tip_radius = std::max(current_tip_radius, overhang_data.cradle_->config_->cradle_support_base_area_radius_); + polylines = ensureMaximumDistancePolyline(line, cradle_line_tip_radius, 2, false); + } + } + else + { + // todo can cause inconsistent support density if a line exactly aligns with the model + polylines = ensureMaximumDistancePolyline( + generateLines(overhang_outset, overhang_data.is_roof_, layer_idx + overhang_data.is_roof_), + ! overhang_data.is_roof_ ? current_tip_radius * 2 + : (use_fake_roof_ && ! overhang_data.isCradleLine() ? support_supporting_branch_distance_ : connect_length), + 1, + false); + } // support_line_width to form a line here as otherwise most will be unsupported. @@ -1084,7 +1210,7 @@ void TreeSupportTipGenerator::generateTips( // but it ensures consistent behaviour as some infill patterns generate each line segment as its own polyline part causing a similar line forming behaviour. // This is not done when a roof is above as the roof will support the model and the trees only need to support the roof - if (polylines.pointCount() <= min_support_points) + if (polylines.pointCount() <= min_support_points && (!overhang_data.isCradleLine() || polylines.pointCount() < 2)) { only_lines = false; // Add the outer wall (of the overhang) to ensure it is correct supported instead. @@ -1095,19 +1221,24 @@ void TreeSupportTipGenerator::generateTips( // (If this is not done it may only be half). This WILL NOT be the case when supporting an angle of about < 60� so there is a fallback, as some support is // better than none.) if (! reduced_overhang_outset.empty() - && overhang_outset.difference(reduced_overhang_outset.offset(std::max(config_.support_line_width, connect_length_))).area() < 1) + && overhang_outset.difference(reduced_overhang_outset.offset(std::max(config_.support_line_width, connect_length))).area() < 1) { - polylines = ensureMaximumDistancePolyline(TreeSupportUtils::toPolylines(reduced_overhang_outset), connect_length_, min_support_points, true); + polylines = ensureMaximumDistancePolyline(TreeSupportUtils::toPolylines(reduced_overhang_outset), connect_length, min_support_points, true); } else { - polylines = ensureMaximumDistancePolyline(TreeSupportUtils::toPolylines(overhang_outset), connect_length_, min_support_points, true); + polylines = ensureMaximumDistancePolyline(TreeSupportUtils::toPolylines(overhang_outset), connect_length, min_support_points, true); } } - if (roof_allowed_for_this_part) // Some roof may only be supported by a part of a tip + // Some roof may only be supported by a part of a tip. + // Cradle lines may move further, because the tips of cradle lines are generated from the (center) line. + if (overhang_data.is_roof_ || overhang_data.is_cradle_) { - polylines = TreeSupportUtils::movePointsOutside(polylines, relevant_forbidden, config_.getRadius(0) + FUDGE_LENGTH / 2); + polylines = TreeSupportUtils::movePointsOutside( + polylines, + relevant_forbidden, + (overhang_data.isCradleLine() ? overhang_data.cradle_->config_->cradle_line_width_ / 2 : 0) + config_.getRadius(0) + FUDGE_LENGTH / 2); } overhang_lines = convertLinesToInternal(polylines, layer_idx); @@ -1115,7 +1246,7 @@ void TreeSupportTipGenerator::generateTips( if (overhang_lines.empty()) // some error handling and logging { Shape enlarged_overhang_outset = overhang_outset.offset(config_.getRadius(0) + FUDGE_LENGTH / 2, ClipperLib::jtRound).difference(relevant_forbidden); - polylines = ensureMaximumDistancePolyline(TreeSupportUtils::toPolylines(enlarged_overhang_outset), connect_length_, min_support_points, true); + polylines = ensureMaximumDistancePolyline(TreeSupportUtils::toPolylines(enlarged_overhang_outset), connect_length, min_support_points, true); overhang_lines = convertLinesToInternal(polylines, layer_idx); if (! overhang_lines.empty()) @@ -1124,91 +1255,109 @@ void TreeSupportTipGenerator::generateTips( } else { - spdlog::warn("Overhang area has no valid tips! Was roof: {} On Layer: {}", roof_allowed_for_this_part, layer_idx); + spdlog::warn( + "Overhang area has no valid tips! Was roof: {} Was Cradle {} Was Line {} On Layer: {} Area size was {}", + overhang_data.is_roof_, + overhang_data.is_cradle_, + overhang_data.isCradleLine(), + layer_idx, + overhang_data.overhang_.area()); } } - size_t dont_move_for_layers = support_roof_layers_ ? (force_tip_to_roof_ ? support_roof_layers_ : (roof_allowed_for_this_part ? 0 : support_roof_layers_)) : 0; + size_t dont_move_for_layers = support_roof_layers_ ? (force_tip_to_roof ? support_roof_layers_ : (overhang_data.is_roof_ ? 0 : support_roof_layers_)) : 0; addLinesAsInfluenceAreas( new_tips, overhang_lines, - force_tip_to_roof_ ? support_roof_layers_ : 0, + (! overhang_data.is_roof_ && force_tip_to_roof) ? support_roof_layers_ : 0, layer_idx, - roof_allowed_for_this_part, + current_tip_radius, + overhang_data, dont_move_for_layers, only_lines); } }); + const auto t_influenced = std::chrono::high_resolution_clock::now(); cura::parallel_for( 0, support_roof_drawn_.size(), [&](const LayerIndex layer_idx) { - // Sometimes roofs could be empty as the pattern does not generate lines if the area is narrow enough. - // If there is a roof could have zero lines in its area (as it has no wall), rand a support area would very likely be printed (because there are walls for the support - // areas), replace non printable roofs with support - if (! use_fake_roof_ && config_.support_wall_count > 0 && config_.support_roof_wall_count == 0) + if (use_fake_roof_) + { + placed_fake_roof_areas[layer_idx].emplace_back(support_roof_drawn_[layer_idx], support_roof_line_distance_, false); + + if (config_.z_distance_top % config_.layer_height != 0 && layer_idx > 0) + { + Shape all_roof_fractional = support_roof_drawn_fractional_[layer_idx - 1]; + placed_fake_roof_areas[layer_idx].emplace_back(all_roof_fractional, support_roof_line_distance_, true); + } + } + else { - for (auto roof_area : support_roof_drawn_[layer_idx].unionPolygons(roof_tips_drawn_[layer_idx]).splitIntoParts()) + if (config_.z_distance_top % config_.layer_height != 0 && layer_idx > 0) { - // technically there is no guarantee that a drawn roof tip has lines, as it could be unioned with another roof area that has, but this has to be enough - // hopefully. - if (layer_idx < additional_support_areas.size() - && TreeSupportUtils::generateSupportInfillLines(roof_area, config_, true, layer_idx, support_roof_line_distance_, cross_fill_provider_, false).empty()) + Shape all_roof_below = support_roof_drawn_[layer_idx - 1]; + Shape all_roof_fractional = support_roof_drawn_fractional_[layer_idx - 1].intersection(all_roof_below).difference(support_roof_drawn_[layer_idx]); + + if (config_.support_wall_count > 0 && config_.support_roof_wall_count == 0) // Need to check every area whether it has lines { - additional_support_areas[layer_idx].push_back(roof_area); + for (auto roof_area : all_roof_fractional.splitIntoParts()) + { + // technically there is no guarantee that a drawn roof tip has lines. If it does not add walls + size_t roof_wall_count = 0; + if (TreeSupportUtils::generateSupportInfillLines(roof_area, config_, true, layer_idx, support_roof_line_distance_, nullptr, 0).empty()) + { + roof_wall_count = config_.support_wall_count; + } + storage.support.supportLayers[layer_idx].fillRoofParts(roof_area, config_.support_roof_line_width, roof_wall_count, true); + } } else { - storage.support.supportLayers[layer_idx].support_roof.push_back(roof_area); + storage.support.supportLayers[layer_idx].fillRoofParts(all_roof_fractional, config_.support_roof_line_width, config_.support_roof_wall_count, true); } } - additional_support_areas[layer_idx] = additional_support_areas[layer_idx].unionPolygons(); - storage.support.supportLayers[layer_idx].support_roof = storage.support.supportLayers[layer_idx].support_roof.unionPolygons(); - } - else - { - if (use_fake_roof_) + if (config_.support_wall_count > 0 && config_.support_roof_wall_count == 0) { - placed_fake_roof_areas[layer_idx].emplace_back(support_roof_drawn_[layer_idx], support_roof_line_distance_, false); - - if (config_.z_distance_top % config_.layer_height != 0 && layer_idx > 0) + for (auto roof_area : support_roof_drawn_[layer_idx].splitIntoParts()) // Need to check every area whether it has lines { - // Fake roof tips would just be tips, so no need to add them here as all polygons in roof_tips_drawn_ will be empty! - Shape all_roof_fractional = support_roof_drawn_fractional_[layer_idx - 1].intersection(support_roof_drawn_[layer_idx - 1]); - placed_fake_roof_areas[layer_idx].emplace_back(all_roof_fractional, support_roof_line_distance_, true); + // technically there is no guarantee that a drawn roof tip has lines. If it does not add walls + size_t roof_wall_count = 0; + if (TreeSupportUtils::generateSupportInfillLines(roof_area, config_, true, layer_idx, support_roof_line_distance_, nullptr, 0).empty()) + { + roof_wall_count = config_.support_wall_count; + } + storage.support.supportLayers[layer_idx].fillRoofParts(roof_area, config_.support_roof_line_width, roof_wall_count, false); } } else { - if (config_.z_distance_top % config_.layer_height != 0 && layer_idx > 0) - { - Shape all_roof_below = support_roof_drawn_[layer_idx - 1].unionPolygons(roof_tips_drawn_[layer_idx - 1]); - Shape all_roof_fractional = support_roof_drawn_fractional_[layer_idx - 1].intersection(all_roof_below); - storage.support.supportLayers[layer_idx].support_fractional_roof - = storage.support.supportLayers[layer_idx].support_fractional_roof.unionPolygons(all_roof_fractional); - - // Fractional roof is a modifier applied to a roof area, which means if only the fractional roof area is set, there will be nothing as there is no roof to - // modify. Because of that the fractional roof has ALSO to be added to the roof. - storage.support.supportLayers[layer_idx].support_roof.push_back(all_roof_fractional); - } - - Shape all_roof = support_roof_drawn_[layer_idx].unionPolygons(roof_tips_drawn_[layer_idx]); - storage.support.supportLayers[layer_idx].support_roof = storage.support.supportLayers[layer_idx].support_roof.unionPolygons(all_roof); + storage.support.supportLayers[layer_idx].fillRoofParts(support_roof_drawn_[layer_idx], config_.support_roof_line_width, config_.support_roof_wall_count, false); } } }); - removeUselessAddedPoints(new_tips, storage, additional_support_areas); + removeUselessAddedPoints(new_tips, storage, cradle_data); for (auto [layer_idx, tips_on_layer] : new_tips | ranges::views::enumerate) { move_bounds[layer_idx].insert(tips_on_layer.begin(), tips_on_layer.end()); } -} + const auto t_end = std::chrono::high_resolution_clock::now(); + const auto dur_roof = 0.001 * std::chrono::duration_cast(t_roof - t_start).count(); + const auto dur_inf = 0.001 * std::chrono::duration_cast(t_influenced - t_roof).count(); + const auto dur_clean = 0.001 * std::chrono::duration_cast(t_end - t_influenced).count(); + spdlog::info( + "Subtasks of generateInitialAreas: Generating roof: {} ms Creating Influence Areas: {} ms Checking result validity: {} ms ", + dur_roof, + dur_inf, + dur_clean); + +} } // namespace cura diff --git a/src/bridge.cpp b/src/bridge.cpp index 55b7b8e8cd..848ce3852e 100644 --- a/src/bridge.cpp +++ b/src/bridge.cpp @@ -72,12 +72,13 @@ double bridgeAngle( if (! support_layer->support_roof.empty()) { - AABB support_roof_bb(support_layer->support_roof); + Shape all_roofs = support_layer->getTotalAreaFromParts(support_layer->support_roof); + AABB support_roof_bb(all_roofs); if (boundary_box.hit(support_roof_bb)) { - prev_layer_outline.push_back(support_layer->support_roof); // not intersected with skin + prev_layer_outline.push_back(all_roofs); // not intersected with skin - Shape supported_skin(skin_outline.intersection(support_layer->support_roof)); + Shape supported_skin(skin_outline.intersection(all_roofs)); if (! supported_skin.empty()) { supported_regions.push_back(supported_skin); diff --git a/src/geometry/LinesSet.cpp b/src/geometry/LinesSet.cpp index ccfd7259a6..d0347a779e 100644 --- a/src/geometry/LinesSet.cpp +++ b/src/geometry/LinesSet.cpp @@ -130,9 +130,9 @@ coord_t LinesSet::length() const } template -Shape LinesSet::createTubeShape(const coord_t inner_offset, const coord_t outer_offset) const +Shape LinesSet::createTubeShape(const coord_t inner_offset, const coord_t outer_offset, const ClipperLib::JoinType jt) const { - return offset(outer_offset).difference(offset(-inner_offset)); + return offset(outer_offset, jt).difference(offset(-inner_offset, jt)); } template @@ -315,7 +315,7 @@ template void OpenLinesSet::removeAt(size_t index); template void OpenLinesSet::splitIntoSegments(OpenLinesSet& result) const; template OpenLinesSet OpenLinesSet::splitIntoSegments() const; template coord_t OpenLinesSet::length() const; -template Shape OpenLinesSet::createTubeShape(const coord_t inner_offset, const coord_t outer_offset) const; +template Shape OpenLinesSet::createTubeShape(const coord_t inner_offset, const coord_t outer_offset, const ClipperLib::JoinType jt) const; template void OpenLinesSet::translate(const Point2LL& delta); template void OpenLinesSet::removeDegenerateVerts(); template void OpenLinesSet::addPaths(ClipperLib::Clipper& clipper, ClipperLib::PolyType PolyTyp) const; @@ -329,7 +329,7 @@ template void ClosedLinesSet::removeAt(size_t index); template void ClosedLinesSet::splitIntoSegments(OpenLinesSet& result) const; template OpenLinesSet ClosedLinesSet::splitIntoSegments() const; template coord_t ClosedLinesSet::length() const; -template Shape ClosedLinesSet::createTubeShape(const coord_t inner_offset, const coord_t outer_offset) const; +template Shape ClosedLinesSet::createTubeShape(const coord_t inner_offset, const coord_t outer_offset, const ClipperLib::JoinType jt) const; template void ClosedLinesSet::translate(const Point2LL& delta); template void ClosedLinesSet::removeDegenerateVerts(); template void ClosedLinesSet::addPaths(ClipperLib::Clipper& clipper, ClipperLib::PolyType PolyTyp) const; @@ -344,7 +344,7 @@ template void LinesSet::removeAt(size_t index); template void LinesSet::splitIntoSegments(OpenLinesSet& result) const; template OpenLinesSet LinesSet::splitIntoSegments() const; template coord_t LinesSet::length() const; -template Shape LinesSet::createTubeShape(const coord_t inner_offset, const coord_t outer_offset) const; +template Shape LinesSet::createTubeShape(const coord_t inner_offset, const coord_t outer_offset, const ClipperLib::JoinType jt) const; template void LinesSet::translate(const Point2LL& delta); template void LinesSet::removeDegenerateVerts(); template void LinesSet::addPaths(ClipperLib::Clipper& clipper, ClipperLib::PolyType PolyTyp) const; diff --git a/src/geometry/Shape.cpp b/src/geometry/Shape.cpp index 61d029c4e2..3388b75b81 100644 --- a/src/geometry/Shape.cpp +++ b/src/geometry/Shape.cpp @@ -366,6 +366,49 @@ OpenLinesSet Shape::intersection(const LinesSet& polylines, bool resti return result_lines; } +template +OpenLinesSet Shape::difference(const LinesSet& polylines, bool restitch, const coord_t max_stitch_distance) const +{ + + OpenLinesSet split_polylines = polylines.splitIntoSegments(); + + ClipperLib::PolyTree result; + ClipperLib::Clipper clipper(clipper_init); + split_polylines.addPaths(clipper, ClipperLib::ptSubject); + addPaths(clipper, ClipperLib::ptClip); + clipper.Execute(ClipperLib::ctDifference, result); + ClipperLib::Paths result_paths; + ClipperLib::OpenPathsFromPolyTree(result, result_paths); + + OpenLinesSet result_lines(std::move(result_paths)); + + if (restitch) + { + OpenLinesSet result_open_lines; + Shape result_closed_lines; + + const coord_t snap_distance = 10_mu; + OpenPolylineStitcher::stitch(result_lines, result_open_lines, result_closed_lines, max_stitch_distance, snap_distance); + + result_lines = std::move(result_open_lines); + // if open polylines got stitched into closed polylines, split them back up into open polylines again, because the result only admits open polylines + for (ClosedPolyline& closed_line : result_closed_lines) + { + if (! closed_line.empty()) + { + if (closed_line.size() > 2) + { + closed_line.push_back(closed_line.front()); + } + result_lines.emplace_back(std::move(closed_line.getPoints())); + } + } + } + + return result_lines; +} + + Shape Shape::xorPolygons(const Shape& other, ClipperLib::PolyFillType pft) const { if (empty()) @@ -993,4 +1036,8 @@ template OpenLinesSet Shape::intersection(const OpenLinesSet& polylines, bool re template OpenLinesSet Shape::intersection(const ClosedLinesSet& polylines, bool restitch, const coord_t max_stitch_distance) const; template OpenLinesSet Shape::intersection(const LinesSet& polylines, bool restitch, const coord_t max_stitch_distance) const; +template OpenLinesSet Shape::difference(const OpenLinesSet& polylines, bool restitch, const coord_t max_stitch_distance) const; +template OpenLinesSet Shape::difference(const ClosedLinesSet& polylines, bool restitch, const coord_t max_stitch_distance) const; +template OpenLinesSet Shape::difference(const LinesSet& polylines, bool restitch, const coord_t max_stitch_distance) const; + } // namespace cura diff --git a/src/settings/Settings.cpp b/src/settings/Settings.cpp index cd6d2719f4..46d31aa954 100644 --- a/src/settings/Settings.cpp +++ b/src/settings/Settings.cpp @@ -782,6 +782,19 @@ std::vector Settings::get>(const std::string& key) const return values_ints; } +template<> +std::vector Settings::get>(const std::string& key) const +{ + std::vector values_doubles = get>(key); + std::vector values_ints; + values_ints.reserve(values_doubles.size()); + for (double value : values_doubles) + { + values_ints.push_back(std::round(MM2INT(value))); // Round to nearest integer. + } + return values_ints; +} + template<> std::vector Settings::get>(const std::string& key) const { diff --git a/src/sliceDataStorage.cpp b/src/sliceDataStorage.cpp index 697721ccd0..14e8e3e74b 100644 --- a/src/sliceDataStorage.cpp +++ b/src/sliceDataStorage.cpp @@ -368,7 +368,10 @@ Shape SliceDataStorage::getLayerOutlines( total.push_back(support_infill_part.outline_); } total.push_back(support_layer.support_bottom); - total.push_back(support_layer.support_roof); + for (const SupportInfillPart& support_infill_part : support_layer.support_roof) + { + total.push_back(support_infill_part.outline_); + } } } if (include_prime_tower && prime_tower_) @@ -712,15 +715,15 @@ void SliceDataStorage::initializePrimeTower() prime_tower_ = PrimeTower::createPrimeTower(*this); } -void SupportLayer::excludeAreasFromSupportInfillAreas(const Shape& exclude_polygons, const AABB& exclude_polygons_boundary_box) +void SupportLayer::excludeAreasFromSupportInfillAreas(std::vector& parts, const Shape& exclude_polygons, const AABB& exclude_polygons_boundary_box) { // record the indexes that need to be removed and do that after std::list to_remove_part_indices; // LIFO for removing - unsigned int part_count_to_check = support_infill_parts.size(); // note that support_infill_parts.size() changes during the computation below + unsigned int part_count_to_check = parts.size(); // note that parts.size() changes during the computation below for (size_t part_idx = 0; part_idx < part_count_to_check; ++part_idx) { - SupportInfillPart& support_infill_part = support_infill_parts[part_idx]; + SupportInfillPart& support_infill_part = parts[part_idx]; // if the areas don't overlap, do nothing if (! exclude_polygons_boundary_box.hit(support_infill_part.outline_boundary_box_)) @@ -747,13 +750,13 @@ void SupportLayer::excludeAreasFromSupportInfillAreas(const Shape& exclude_polyg // there are one or more smaller parts. // we first replace the current part with one of the smaller parts, - // the rest we add to the support_infill_parts (but after part_count_to_check) + // the rest we add to the parts (but after part_count_to_check) support_infill_part.outline_ = smaller_support_islands[0]; for (size_t support_island_idx = 1; support_island_idx < smaller_support_islands.size(); ++support_island_idx) { const SingleShape& smaller_island = smaller_support_islands[support_island_idx]; - support_infill_parts.emplace_back(smaller_island, support_infill_part.support_line_width_, support_infill_part.inset_count_to_generate_); + parts.emplace_back(smaller_island, support_infill_part.support_line_width_, support_infill_part.use_fractional_config_, support_infill_part.inset_count_to_generate_, support_infill_part.custom_line_distance_, support_infill_part.custom_line_pattern_, support_infill_part.start_near_location); } } @@ -762,15 +765,15 @@ void SupportLayer::excludeAreasFromSupportInfillAreas(const Shape& exclude_polyg { const size_t remove_idx = to_remove_part_indices.back(); to_remove_part_indices.pop_back(); - if (support_infill_parts.empty()) + if (parts.empty()) { continue; } - if (remove_idx < support_infill_parts.size() - 1) + if (remove_idx < parts.size() - 1) { // move last element to the to-be-removed element so that we can erase the last place in the vector - support_infill_parts[remove_idx] = std::move(support_infill_parts.back()); + parts[remove_idx] = std::move(parts.back()); } - support_infill_parts.pop_back(); // always erase last place in the vector + parts.pop_back(); // always erase last place in the vector } } @@ -783,7 +786,8 @@ void SupportLayer::fillInfillParts( const coord_t wall_line_count, const coord_t grow_layer_above /*has default 0*/, const bool unionAll /*has default false*/, - const coord_t custom_line_distance /*has default 0*/) + const coord_t custom_line_distance /*has default false*/, + const EFillMethod custom_pattern /*has default None*/) { // Find the model exactly z-distance above the support layer. Shape overhang_z_dist_above; @@ -806,7 +810,7 @@ void SupportLayer::fillInfillParts( { for (const SingleShape& island_outline : support_areas.splitIntoParts(unionAll)) { - support_infill_parts.emplace_back(island_outline, support_line_width, use_fractional_config, wall_line_count, custom_line_distance); + support_infill_parts.emplace_back(island_outline, support_line_width, use_fractional_config, wall_line_count, custom_line_distance, custom_pattern); } use_fractional_config = false; } diff --git a/src/support.cpp b/src/support.cpp index 17a55d855a..3414fffa04 100644 --- a/src/support.cpp +++ b/src/support.cpp @@ -24,6 +24,7 @@ #include "ExtruderTrain.h" #include "SkeletalTrapezoidation.h" #include "Slice.h" +#include "TreeSupportCradle.h" #include "infill.h" #include "infill/SierpinskiFillProvider.h" #include "infill/UniformDensityProvider.h" @@ -53,8 +54,17 @@ bool AreaSupport::handleSupportModifierMesh(SliceDataStorage& storage, const Set SUPPORT_DROP_DOWN, SUPPORT_VANILLA }; - ModifierType modifier_type - = (mesh_settings.get("anti_overhang_mesh")) ? ANTI_OVERHANG : ((mesh_settings.get("support_mesh_drop_down")) ? SUPPORT_DROP_DOWN : SUPPORT_VANILLA); + ModifierType modifier_type; + if (mesh_settings.get("anti_overhang_mesh")) // todo [TR] refactor to support_modifier_mesh + { + modifier_type = ANTI_OVERHANG; + storage.support.supportGenerationModifiers.emplace_back(mesh_settings, slicer->layers.size()); + } + else + { + modifier_type = mesh_settings.get("support_mesh_drop_down") ? SUPPORT_DROP_DOWN : SUPPORT_VANILLA; + } + for (LayerIndex layer_nr = 0; layer_nr < slicer->layers.size(); layer_nr++) { SupportLayer& support_layer = storage.support.supportLayers[layer_nr]; @@ -62,7 +72,7 @@ bool AreaSupport::handleSupportModifierMesh(SliceDataStorage& storage, const Set switch (modifier_type) { case ANTI_OVERHANG: - support_layer.anti_overhang.push_back(slicer_layer.polygons_); + storage.support.supportGenerationModifiers.back().addArea(slicer_layer.polygons_, layer_nr); break; case SUPPORT_DROP_DOWN: support_layer.support_mesh_drop_down.push_back(slicer_layer.polygons_); @@ -106,8 +116,6 @@ void AreaSupport::splitGlobalSupportAreasIntoSupportInfillParts(SliceDataStorage const Shape& global_support_areas = global_support_areas_per_layer[layer_nr]; if (global_support_areas.size() == 0 || layer_nr < min_layer || layer_nr > max_layer) { - // Initialize support_infill_parts empty - storage.support.supportLayers[layer_nr].support_infill_parts.clear(); continue; } @@ -609,6 +617,7 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage) { std::vector global_support_areas_per_layer; global_support_areas_per_layer.resize(storage.print_layer_count); + std::vector global_handled_by_cradle_areas_per_layer(storage.print_layer_count); int max_layer_nr_support_mesh_filled; for (max_layer_nr_support_mesh_filled = storage.support.supportLayers.size() - 1; max_layer_nr_support_mesh_filled >= 0; max_layer_nr_support_mesh_filled--) @@ -623,7 +632,6 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage) for (int layer_nr = 0; layer_nr < max_layer_nr_support_mesh_filled; layer_nr++) { SupportLayer& support_layer = storage.support.supportLayers[layer_nr]; - support_layer.anti_overhang = support_layer.anti_overhang.unionPolygons(); support_layer.support_mesh_drop_down = support_layer.support_mesh_drop_down.unionPolygons(); support_layer.support_mesh = support_layer.support_mesh.unionPolygons(); } @@ -672,11 +680,21 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage) } std::vector mesh_support_areas_per_layer; mesh_support_areas_per_layer.resize(storage.print_layer_count, Shape()); - - generateSupportAreasForMesh(storage, *infill_settings, *roof_settings, *bottom_settings, mesh_idx, storage.print_layer_count, mesh_support_areas_per_layer); + std::vector cradle_overhang_reserved_areas(storage.print_layer_count); + generateCradlesForMesh(storage, mesh_idx, cradle_overhang_reserved_areas); + generateSupportAreasForMesh( + storage, + *infill_settings, + *roof_settings, + *bottom_settings, + cradle_overhang_reserved_areas, + mesh_idx, + storage.print_layer_count, + mesh_support_areas_per_layer); for (size_t layer_idx = 0; layer_idx < storage.print_layer_count; layer_idx++) { global_support_areas_per_layer[layer_idx].push_back(mesh_support_areas_per_layer[layer_idx]); + global_handled_by_cradle_areas_per_layer[layer_idx].push_back(cradle_overhang_reserved_areas[layer_idx]); } } @@ -695,7 +713,7 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage) if (mesh->settings.get("support_roof_enable")) { - generateSupportRoof(storage, *mesh, global_support_areas_per_layer); + generateSupportRoof(storage, *mesh, global_support_areas_per_layer, global_handled_by_cradle_areas_per_layer); } if (mesh->settings.get("support_bottom_enable")) { @@ -967,6 +985,233 @@ Shape AreaSupport::generateVaryingXYDisallowedArea(const SliceMeshStorage& stora return varying_xy_disallowed_areas; } +void AreaSupport::generateCradlesForMesh(SliceDataStorage& storage, size_t mesh_idx, std::vector& cradle_overhang_reserved_areas) +{ + SliceMeshStorage& mesh = *storage.meshes[mesh_idx]; + const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings; + const bool is_support_mesh_place_holder = mesh.settings.get("support_mesh"); + const coord_t support_line_width = mesh_group_settings.get("support_infill_extruder_nr").settings_.get("support_line_width"); + const coord_t layer_thickness = mesh_group_settings.get("layer_height"); + const coord_t z_distance_top = mesh.settings.get("support_top_distance"); + const size_t layer_z_distance_top = (z_distance_top / layer_thickness) + 1; + const size_t support_roof_wall_count = mesh.settings.get("support_roof_wall_count"); + const coord_t support_roof_line_width = mesh.settings.get("support_roof_line_width"); + const size_t support_wall_count = mesh.settings.get("support_wall_count"); + const bool fractional_support_present = z_distance_top % layer_thickness != 0; + + const ESupportStructure support_structure = mesh.settings.get("support_structure"); + if ((! mesh.settings.get("support_enable") || support_structure != ESupportStructure::NORMAL) && ! is_support_mesh_place_holder) + { + return; + } + + TreeModelVolumes volumes = TreeModelVolumes( + storage, + 0, + 0, + support_line_width / 2, + std::min(support_line_width, support_roof_line_width), + mesh_idx, + 0, + 0, + std::vector(storage.support.supportLayers.size())); + // Don't precalculate TreeModelVolumes. Most will not be used so using it lazily generating areas should be faster. + SupportCradleGeneration cradle_gen(storage, volumes); + cradle_gen.addMeshToCradleCalculation(storage, mesh_idx); + cradle_gen.generateCradleForMesh(storage, mesh_idx); + cradle_gen.generate(storage); + std::vector> cradle_data_mesh(storage.support.supportLayers.size()); + std::vector support_free_areas = std::vector(storage.support.supportLayers.size(), Shape()); + cradle_gen.pushCradleData(cradle_data_mesh, support_free_areas, mesh_idx); + + // todo figure out what to do if regular support collides with cradle. make it anti_support? + // todo figure out what to do if two cradles block each other with regular support caused by one of them + + std::vector support_layer_extra_wall_storage(cradle_data_mesh.size()); + std::vector support_layer_storage_fractional(cradle_data_mesh.size()); + std::vector support_roof_extra_wall_storage(cradle_data_mesh.size()); + std::vector support_roof_extra_wall_storage_fractional(cradle_data_mesh.size()); + std::vector support_roof_storage(cradle_data_mesh.size()); + std::vector support_roof_storage_fractional(cradle_data_mesh.size()); + cradle_overhang_reserved_areas.resize(cradle_data_mesh.size()); + std::mutex critical_support_roof_storage; + std::mutex critical_support_layer_storage; + std::mutex critical_cradle_reserved_areas; + + cura::parallel_for( + 0, + cradle_data_mesh.size(), + [&](const LayerIndex layer_idx) + { + for (size_t cradle_idx = 0; cradle_idx < cradle_data_mesh[layer_idx].size(); cradle_idx++) + { + TreeSupportCradle* cradle = cradle_data_mesh[layer_idx][cradle_idx]; + for (auto overhang_pair : cradle->overhang_) + { + if(overhang_pair.second.empty()) + { + continue; + } + Shape overhang_area_regular; + Point2LL center = cradle_data_mesh[layer_idx][cradle_idx]->getCenter(layer_idx); + Polygon overhang_outer_area_part; + bool includes_lines = false; + for (OverhangInformation& overhang : overhang_pair.second) + { + overhang_area_regular.push_back(overhang.overhang_); + if (overhang.isCradleLine()) + { + std::optional cradle_line_opt = cradle->getCradleLineOfIndex(overhang.cradle_layer_idx_, overhang.cradle_line_idx_); + if (cradle_line_opt) + { + overhang_outer_area_part.emplace_back(cradle_line_opt.value()->line_.back()); + includes_lines = true; + } + } + else + { + if (fractional_support_present) + { + support_layer_storage_fractional[overhang_pair.first + 1].push_back(overhang.overhang_); + } + } + } + Shape line_support; + line_support.push_back(overhang_outer_area_part); + line_support.makeConvex(); + if (includes_lines) + { + bool large_base_roof = cradle->config_->large_cradle_base_ && cradle->config_->cradle_lines_roof_; + coord_t offset_distance = std::max(cradle->config_->cradle_line_width_ / 2, (large_base_roof ? cradle->config_->cradle_support_base_area_radius_ : 0)); + line_support = line_support.offset(offset_distance, ClipperLib::jtRound); + } + overhang_area_regular = overhang_area_regular.unionPolygons(line_support); + overhang_area_regular = overhang_area_regular.difference(volumes.getCollision(0, overhang_pair.first, true)); + + std::lock_guard critical_section_cradle(critical_cradle_reserved_areas); + cradle_overhang_reserved_areas[overhang_pair.first].push_back(overhang_area_regular); + + } + + for (auto [base_idx, base] : cradle_data_mesh[layer_idx][cradle_idx]->base_below_ | ranges::views::enumerate) + { + if (cradle_data_mesh[layer_idx][cradle_idx]->is_roof_) + { + std::lock_guard critical_section_cradle(critical_support_roof_storage); + (support_roof_wall_count ? support_roof_storage : support_roof_extra_wall_storage)[layer_idx - base_idx].push_back(base); + if (base_idx == 0 && z_distance_top % layer_thickness != 0 && layer_idx + 1 < support_roof_extra_wall_storage_fractional.size()) + { + (support_roof_wall_count ? support_roof_storage_fractional : support_roof_extra_wall_storage_fractional)[layer_idx + 1].push_back(base); + } + } + else + { + // Dead code. Currently, Cradles that are not roofs do not have a base area, just a tip. This is just here for the case that this changes + std::lock_guard critical_section_cradle(critical_support_layer_storage); + support_layer_extra_wall_storage[layer_idx - base_idx].push_back(base); + if (base_idx == 0 && z_distance_top % layer_thickness != 0 && layer_idx + 1 < support_layer_storage_fractional.size()) + { + support_layer_storage_fractional[layer_idx + 1].push_back(base); + } + } + } + + for (size_t line_idx = 0; line_idx < cradle_data_mesh[layer_idx][cradle_idx]->lines_.size(); line_idx++) + { + if (! cradle_data_mesh[layer_idx][cradle_idx]->lines_[line_idx].empty()) + { + for (int64_t height_idx = cradle_data_mesh[layer_idx][cradle_idx]->lines_[line_idx].size() - 1; height_idx >= 0; height_idx--) + { + Shape line_area = cradle_data_mesh[layer_idx][cradle_idx]->lines_[line_idx][height_idx].area_; + bool is_roof = cradle_data_mesh[layer_idx][cradle_idx]->lines_[line_idx][height_idx].is_roof_; + LayerIndex cradle_line_layer_idx = cradle_data_mesh[layer_idx][cradle_idx]->lines_[line_idx][height_idx].layer_idx_; + bool is_base = cradle_data_mesh[layer_idx][cradle_idx]->lines_[line_idx][height_idx].is_base_; + + if (is_roof) + { + std::lock_guard critical_section_cradle(critical_support_roof_storage); + support_roof_extra_wall_storage[cradle_line_layer_idx].push_back(line_area); + } + else + { + std::lock_guard critical_section_cradle(critical_support_layer_storage); + support_layer_extra_wall_storage[cradle_line_layer_idx].push_back(line_area); + } + } + } + } + } + }); + + cura::parallel_for( + 0, + cradle_data_mesh.size(), + [&](const LayerIndex layer_idx) + { + // union everything and add + + support_layer_storage_fractional[layer_idx] = support_layer_storage_fractional[layer_idx].unionPolygons(); + support_layer_extra_wall_storage[layer_idx] = support_layer_extra_wall_storage[layer_idx].unionPolygons(); + + support_roof_storage[layer_idx] = support_roof_storage[layer_idx].unionPolygons(); + support_roof_storage_fractional[layer_idx] = support_roof_storage_fractional[layer_idx].unionPolygons(); + + support_roof_extra_wall_storage[layer_idx] = support_roof_extra_wall_storage[layer_idx].unionPolygons(); + support_roof_extra_wall_storage_fractional[layer_idx] = support_roof_extra_wall_storage_fractional[layer_idx].unionPolygons(); + + cradle_overhang_reserved_areas[layer_idx] = cradle_overhang_reserved_areas[layer_idx].unionPolygons(); + + Shape remove_from_next_roof = storage.support.supportLayers[layer_idx].getTotalAreaFromParts(storage.support.supportLayers[layer_idx].support_roof).unionPolygons(); + if (! support_free_areas[layer_idx].empty()) + { + remove_from_next_roof.push_back(support_free_areas[layer_idx]); + } + + remove_from_next_roof = remove_from_next_roof.unionPolygons(); + + Shape remove_from_next_fractional_roof = remove_from_next_roof; + + Shape roof_extra_wall = support_roof_extra_wall_storage[layer_idx].difference(remove_from_next_roof); + Shape roof = support_roof_storage[layer_idx]; + if (support_roof_wall_count) + { + roof = roof.difference(remove_from_next_roof); + roof = roof.unionPolygons(roof_extra_wall); + roof_extra_wall.clear(); + } + else + { + roof = roof.difference(remove_from_next_roof.unionPolygons(roof_extra_wall)); + } + + storage.support.supportLayers[layer_idx].fillRoofParts(roof_extra_wall, support_roof_line_width, std::max(size_t(1), support_roof_wall_count), false); + storage.support.supportLayers[layer_idx].fillRoofParts(roof, support_roof_line_width, support_roof_wall_count, false); + + remove_from_next_fractional_roof.push_back(roof_extra_wall); + remove_from_next_fractional_roof.push_back(roof); + remove_from_next_fractional_roof = remove_from_next_fractional_roof.unionPolygons(); + + Shape fractional_roof_extra_wall = support_roof_extra_wall_storage_fractional[layer_idx].difference(remove_from_next_fractional_roof); + storage.support.supportLayers[layer_idx].fillRoofParts(fractional_roof_extra_wall, support_roof_line_width, std::max(size_t(1), support_roof_wall_count), true); + + Shape fractional_roof = support_roof_storage_fractional[layer_idx].difference(remove_from_next_fractional_roof.unionPolygons(fractional_roof_extra_wall)); + storage.support.supportLayers[layer_idx].fillRoofParts(fractional_roof, support_roof_line_width, support_roof_wall_count, true); + + Shape fractional_base = support_layer_storage_fractional[layer_idx].difference(remove_from_next_fractional_roof); + + storage.support.supportLayers[layer_idx] + .fillInfillParts(support_layer_extra_wall_storage[layer_idx], support_line_width, std::max(size_t(1), support_wall_count), false, true); + storage.support.supportLayers[layer_idx].fillInfillParts(fractional_base, support_line_width, support_wall_count, true, true); + if (! (fractional_base.empty() && support_layer_extra_wall_storage[layer_idx].empty() && fractional_roof.empty() && fractional_roof_extra_wall.empty() && roof.empty() + && roof_extra_wall.empty())) + { + std::lock_guard critical_section_storage(critical_support_layer_storage); + storage.support.layer_nr_max_filled_layer = std::max(layer_idx, LayerIndex(storage.support.layer_nr_max_filled_layer)); + } + }); +} + + /* * Algorithm: * From top layer to bottom layer: @@ -984,6 +1229,7 @@ void AreaSupport::generateSupportAreasForMesh( const Settings& infill_settings, const Settings& roof_settings, const Settings& bottom_settings, + const std::vector& cradle_overhang_reserved_areas, const size_t mesh_idx, const size_t layer_count, std::vector& support_areas) @@ -1034,7 +1280,13 @@ void AreaSupport::generateSupportAreasForMesh( xy_disallowed_per_layer[0] = storage.getLayerOutlines(0, no_support, no_prime_tower).offset(xy_distance); // The maximum width of an odd wall = 2 * minimum even wall width. - auto half_min_feature_width = min_even_wall_line_width + 10; + auto half_min_feature_width = min_even_wall_line_width / 2 + 5; + + bool anti_support_meshes_present = false; + for (const SupportGenerationModifier& support_modifier : storage.support.supportGenerationModifiers) + { + anti_support_meshes_present |= support_modifier.isAntiSupport(); + } cura::parallel_for( 1, @@ -1084,6 +1336,25 @@ void AreaSupport::generateSupportAreasForMesh( } }); + std::vector anti_support_areas(mesh.layers.size()); + if (anti_support_meshes_present) + { + cura::parallel_for( + 1, + layer_count, + [&](const size_t layer_idx) + { + for (const SupportGenerationModifier& support_modifier : storage.support.supportGenerationModifiers) + { + if (support_modifier.isAntiSupport() && layer_idx < support_modifier.areas_.size()) + { + anti_support_areas[layer_idx].push_back(support_modifier.areas_[layer_idx]); + } + } + anti_support_areas[layer_idx] = anti_support_areas[layer_idx].unionPolygons(); + }); + } + std::vector tower_roofs; Shape stair_removal; // polygons to subtract from support because of stair-stepping @@ -1129,6 +1400,10 @@ void AreaSupport::generateSupportAreasForMesh( for (size_t layer_idx = layer_count - 1 - layer_z_distance_top; layer_idx != static_cast(-1); layer_idx--) { Shape layer_this = mesh.full_overhang_areas[layer_idx + layer_z_distance_top]; + if (layer_idx < cradle_overhang_reserved_areas.size()) + { + layer_this = layer_this.unionPolygons(cradle_overhang_reserved_areas[layer_idx]); + } if (extension_offset && ! is_support_mesh_place_holder) { @@ -1171,6 +1446,11 @@ void AreaSupport::generateSupportAreasForMesh( layer_this = layer_this.unionPolygons(storage.support.supportLayers[layer_idx].support_mesh); } layer_this = AreaSupport::join(storage, *layer_above, layer_this).difference(model_mesh_on_layer); + // Ensure support does not continue below anti support + if (anti_support_meshes_present && ! anti_support_areas[layer_idx].empty()) + { + layer_this = layer_this.difference(anti_support_areas[layer_idx]); + } } // make towers for small support @@ -1237,9 +1517,10 @@ void AreaSupport::generateSupportAreasForMesh( } // do stuff for when support on buildplate only - if (support_type == ESupportType::PLATFORM_ONLY) + if (! is_support_mesh_nondrop_place_holder && (support_type == ESupportType::PLATFORM_ONLY || anti_support_meshes_present)) { Shape touching_buildplate = support_areas[0]; // TODO: not working for conical support! + Shape touching_anti_support; const AngleRadians conical_support_angle = infill_settings.get("support_conical_angle"); coord_t conical_support_offset; if (conical_support_angle > 0) @@ -1258,6 +1539,7 @@ void AreaSupport::generateSupportAreasForMesh( if (conical_support) { // with conical support the next layer is allowed to be larger than the previous touching_buildplate = touching_buildplate.offset(std::abs(conical_support_offset) + 10, ClipperLib::jtMiter, 10); + touching_anti_support = touching_anti_support.offset(-(std::abs(conical_support_offset) + 10), ClipperLib::jtMiter, 10); // + 10 and larger miter limit cause performing an outward offset after an inward offset can disregard sharp corners // // conical support can make @@ -1274,9 +1556,17 @@ void AreaSupport::generateSupportAreasForMesh( // } - touching_buildplate = layer.intersection(touching_buildplate); // from bottom to top, support areas can only decrease! - - support_areas[layer_idx] = touching_buildplate; + if (support_type == ESupportType::PLATFORM_ONLY) + { + touching_buildplate = layer.intersection(touching_buildplate); // from bottom to top, support areas can only decrease! + support_areas[layer_idx] = touching_buildplate; + } + if (anti_support_meshes_present) + { + touching_anti_support.push_back(anti_support_areas[layer_idx]); + touching_anti_support = touching_anti_support.unionPolygons().difference(storage.getLayerOutlines(layer_idx, false, false)); + support_areas[layer_idx] = support_areas[layer_idx].difference(touching_anti_support); + } } } @@ -1464,11 +1754,20 @@ std::pair AreaSupport::computeBasicAndFullOverhang(const SliceData Shape basic_overhang = outlines.difference(outlines_below); const SupportLayer& support_layer = storage.support.supportLayers[layer_idx]; - if (! support_layer.anti_overhang.empty()) + + Shape anti_overhang; + for (const SupportGenerationModifier& support_modifier : storage.support.supportGenerationModifiers) + { + if (support_modifier.isAntiOverhang() && layer_idx < support_modifier.areas_.size()) + { + anti_overhang.push_back(support_modifier.areas_[layer_idx]); + } + } + if (! anti_overhang.empty()) { // Merge anti overhang into one polygon, otherwise overlapping polygons // will create opposite effect. - Shape merged_polygons = support_layer.anti_overhang.unionPolygons(); + Shape merged_polygons = anti_overhang.unionPolygons(); basic_overhang = basic_overhang.difference(merged_polygons); } @@ -1494,6 +1793,16 @@ void AreaSupport::detectOverhangPoints(const SliceDataStorage& storage, SliceMes const SliceLayer& layer = mesh.layers[layer_idx]; const SliceLayer& layer_below = mesh.layers[layer_idx - 1]; + Shape anti_overhang; + for (const SupportGenerationModifier& support_modifier : storage.support.supportGenerationModifiers) + { + if (support_modifier.isAntiOverhang() && layer_idx < support_modifier.areas_.size()) + { + anti_overhang.push_back(support_modifier.areas_[layer_idx]); + } + } + anti_overhang = anti_overhang.unionPolygons(); + for (const SliceLayerPart& part : layer.parts) { if (part.outline.empty()) @@ -1512,7 +1821,7 @@ void AreaSupport::detectOverhangPoints(const SliceDataStorage& storage, SliceMes continue; } - const Shape overhang = part.outline.difference(storage.support.supportLayers[layer_idx].anti_overhang); + const Shape overhang = part.outline.difference(anti_overhang); if (! overhang.empty()) { scripta::log("support_overhangs", overhang, SectionType::SUPPORT, layer_idx); @@ -1703,7 +2012,11 @@ void AreaSupport::generateSupportBottom(SliceDataStorage& storage, const SliceMe } } -void AreaSupport::generateSupportRoof(SliceDataStorage& storage, const SliceMeshStorage& mesh, std::vector& global_support_areas_per_layer) +void AreaSupport::generateSupportRoof( + SliceDataStorage& storage, + const SliceMeshStorage& mesh, + std::vector& global_support_areas_per_layer, + std::vector& global_handled_by_cradle_areas_per_layer) { const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings; const coord_t layer_height = mesh_group_settings.get("layer_height"); @@ -1712,35 +2025,49 @@ void AreaSupport::generateSupportRoof(SliceDataStorage& storage, const SliceMesh { return; } - const coord_t z_distance_top = round_up_divide(mesh.settings.get("support_top_distance"), layer_height); // Number of layers between support roof and model. + const coord_t z_distance_top = mesh.settings.get("support_top_distance"); + const coord_t z_distance_top_layers = round_up_divide(z_distance_top, layer_height); // Number of layers between support roof and model. const coord_t roof_line_width = mesh_group_settings.get("support_roof_extruder_nr").settings_.get("support_roof_line_width"); const coord_t roof_outline_offset = mesh_group_settings.get("support_roof_extruder_nr").settings_.get("support_roof_offset"); + const coord_t roof_line_distance = mesh.settings.get("support_roof_line_distance"); + const coord_t roof_wall_line_count = mesh.settings.get("support_roof_wall_count"); const double minimum_roof_area = mesh.settings.get("minimum_roof_area"); std::vector& support_layers = storage.support.supportLayers; - for (LayerIndex layer_idx = static_cast(support_layers.size() - z_distance_top) - 1; layer_idx >= 0; --layer_idx) + Shape roofs_before; + for (LayerIndex layer_idx = static_cast(support_layers.size() - z_distance_top_layers) - 1; layer_idx >= 0; --layer_idx) { const LayerIndex top_layer_idx_above{ - std::min(LayerIndex{ support_layers.size() - 1 }, LayerIndex{ layer_idx + roof_layer_count + z_distance_top }) + std::min(LayerIndex{ support_layers.size() - 1 }, LayerIndex{ layer_idx + roof_layer_count + z_distance_top_layers }) }; // Maximum layer of the model that generates support roof. Shape mesh_outlines; - for (auto layer_idx_above = top_layer_idx_above; layer_idx_above > layer_idx + z_distance_top - 1; layer_idx_above -= 1) + for (auto layer_idx_above = top_layer_idx_above; layer_idx_above > layer_idx + z_distance_top_layers - 1; layer_idx_above -= 1) { mesh_outlines.push_back(mesh.layers[layer_idx_above].getOutlines()); } + + // Todo [TR] this is a rough way to prevent not wanted roofs. But there may be a good reason why one would want roof on a part of the cradle-line supporting support. + // Do more investigation into if this is good enough! + for (auto layer_idx_above = top_layer_idx_above; layer_idx_above > layer_idx + z_distance_top_layers - 1; layer_idx_above -= 1) + { + mesh_outlines = mesh_outlines.difference(global_handled_by_cradle_areas_per_layer[layer_idx_above]); + } + Shape roofs; generateSupportInterfaceLayer(global_support_areas_per_layer[layer_idx], mesh_outlines, roof_line_width, roof_outline_offset, minimum_roof_area, roofs); - support_layers[layer_idx].support_roof.push_back(roofs); - if (layer_idx > 0 && layer_idx < support_layers.size() - 1) + // If roof is added as fractional, even though non can exist because remaining z distance is 0 it will be regular roof. + support_layers[layer_idx].fillRoofParts(layer_idx > 0 && z_distance_top % layer_height != 0? roofs.intersection(roofs_before) : roofs, roof_line_width, roof_wall_line_count); + if (layer_idx > 0 && z_distance_top % layer_height != 0) { - support_layers[layer_idx].support_fractional_roof.push_back(roofs.difference(support_layers[layer_idx + 1].support_roof)); + support_layers[layer_idx].fillRoofParts(roofs.difference(roofs_before), roof_line_width, roof_wall_line_count, true); } + roofs_before = roofs; scripta::log("support_interface_roofs", roofs, SectionType::SUPPORT, layer_idx); } // Remove support in between the support roof and the model. Subtracts the roof polygons from the support polygons on the layers above it. - for (auto [layer_idx, support_layer] : support_layers | ranges::views::enumerate | ranges::views::drop(1) | ranges::views::drop_last(z_distance_top)) + for (auto [layer_idx, support_layer] : support_layers | ranges::views::enumerate | ranges::views::drop(1) | ranges::views::drop_last(z_distance_top_layers)) { if (support_layer.support_roof.empty()) { @@ -1748,10 +2075,10 @@ void AreaSupport::generateSupportRoof(SliceDataStorage& storage, const SliceMesh } int lower = static_cast(layer_idx); - int upper = std::min(static_cast(layer_idx + roof_layer_count + z_distance_top + 5), static_cast(global_support_areas_per_layer.size()) - 1); + int upper = std::min(static_cast(layer_idx + roof_layer_count + z_distance_top_layers + 5), static_cast(global_support_areas_per_layer.size()) - 1); for (Shape& global_support : global_support_areas_per_layer | ranges::views::slice(lower, upper)) { - global_support = global_support.difference(support_layer.support_roof); + global_support = global_support.difference(support_layer.getTotalAreaFromParts(support_layer.support_roof)); } } } diff --git a/src/utils/AABB.cpp b/src/utils/AABB.cpp index 6707775c3f..3378706eaf 100644 --- a/src/utils/AABB.cpp +++ b/src/utils/AABB.cpp @@ -5,6 +5,7 @@ #include +#include "geometry/Polyline.h" #include "geometry/Polygon.h" #include "geometry/Shape.h" #include "utils/linearAlg2D.h" @@ -39,6 +40,13 @@ AABB::AABB(const Polygon& poly) calculate(poly); } +AABB::AABB(const Polyline& line) + : min_(POINT_MAX, POINT_MAX) + , max_(POINT_MIN, POINT_MIN) +{ + calculate(line); +} + Point2LL AABB::getMiddle() const { return (min_ + max_) / 2; @@ -92,6 +100,16 @@ void AABB::calculate(const Polygon& poly) } } +void AABB::calculate(const Polyline& line) +{ + min_ = Point2LL(POINT_MAX, POINT_MAX); + max_ = Point2LL(POINT_MIN, POINT_MIN); + for (const Point2LL& p : line) + { + include(p); + } +} + bool AABB::contains(const Point2LL& point) const { return point.X >= min_.X && point.X <= max_.X && point.Y >= min_.Y && point.Y <= max_.Y; diff --git a/src/utils/ThreadPool.cpp b/src/utils/ThreadPool.cpp index 0b13f0d7da..eaf6da72b8 100644 --- a/src/utils/ThreadPool.cpp +++ b/src/utils/ThreadPool.cpp @@ -15,6 +15,18 @@ ThreadPool::ThreadPool(size_t nthreads) } } +bool ThreadPool::isInPool() +{ + for (size_t i = 0; i < threads.size(); i++) + { + if (threads[i].get_id() == std::this_thread::get_id()) + { + return true; + } + } + return false; +} + void ThreadPool::worker() { lock_t lock = get_lock();