diff --git a/conandata.yml b/conandata.yml index 7120dbd622..6a88f5e4b3 100644 --- a/conandata.yml +++ b/conandata.yml @@ -13,4 +13,4 @@ requirements_plugins: requirements_cura_resources: - "cura_resources/5.14.0-alpha.0@ultimaker/testing" requirements_curaviz: - - "curaviz_grpc_definitions/1.0.0" + - "curaviz_grpc_definitions/1.0.0@internal/testing" diff --git a/include/ExtruderPlan.h b/include/ExtruderPlan.h index 63d03b98ed..f0158408a6 100644 --- a/include/ExtruderPlan.h +++ b/include/ExtruderPlan.h @@ -144,6 +144,8 @@ class ExtruderPlan */ AABB calculateExtrusionBoundingBox() const; + coord_t calculateMaxOverhangLength() const; + private: LayerIndex layer_nr_{ 0 }; //!< The layer number at which we are currently printing. bool is_initial_layer_{ false }; //!< Whether this extruder plan is printed on the very first layer (which might be raft) diff --git a/include/FffGcodeWriter.h b/include/FffGcodeWriter.h index 6eeb6cc249..96be23510e 100644 --- a/include/FffGcodeWriter.h +++ b/include/FffGcodeWriter.h @@ -120,7 +120,6 @@ class FffGcodeWriter : public NoCopy { std::shared_ptr walls_optimizer{}; // Contains the ready-to-add optimized insets bool spiralize{ false }; // Indicates whether this layer is a regular or a spiral layer - std::vector overhang_masks{}; // Overhang speed masks to be applied only while drawing the walls }; static const RoofingFlooringSettingsNames roofing_settings_names; @@ -447,7 +446,7 @@ class FffGcodeWriter : public NoCopy * \param end_infill_close_to_seam Indicates whether the infill will end close to the seam, in which case we can slightly adjust the insets ordering * \return The content to be later inserted */ - InsetsPreprocessResult preProcessInsets( + std::optional preProcessInsets( const SliceDataStorage& storage, LayerPlan& gcodeLayer, const SliceMeshStorage& mesh, @@ -456,6 +455,9 @@ class FffGcodeWriter : public NoCopy SliceLayerPart& part, const bool end_infill_close_to_seam) const; + OverrideAreas + makeLayerMasks(const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const MeshPathConfigs& mesh_config, SliceLayerPart& part) const; + /*! * Inserts the previously processed insets * @param preprocess_result The pre-processed insets to be added @@ -470,6 +472,7 @@ class FffGcodeWriter : public NoCopy */ bool endProcessInsets( InsetsPreprocessResult& preprocess_result, + const OverrideAreas overhang_areas, const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, @@ -609,6 +612,7 @@ class FffGcodeWriter : public NoCopy const size_t extruder_nr, const Shape& area, const GCodePathConfig& config, + const GCodePathConfig* bridge_config, EFillMethod pattern, const AngleDegrees skin_angle, const coord_t skin_overlap, @@ -618,7 +622,7 @@ class FffGcodeWriter : public NoCopy bool& added_something, double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT, std::optional forced_small_area_width = std::nullopt, - const PrintSegmentAttributes& print_attributes = {}) const; + const OverrideAreas& override_areas = {}) const; /*! * see if we can avoid printing a lines or zig zag style skin part in multiple segments by moving to diff --git a/include/InsetOrderOptimizer.h b/include/InsetOrderOptimizer.h index a61043f453..e3de212013 100644 --- a/include/InsetOrderOptimizer.h +++ b/include/InsetOrderOptimizer.h @@ -6,7 +6,7 @@ #include -#include "PrintSegmentAttributes.h" +#include "OverrideAreas.h" #include "settings/ZSeamConfig.h" #include "sliceDataStorage.h" @@ -75,7 +75,7 @@ class InsetOrderOptimizer * \param retract_before_outer_wall The retraction behavior to be applied when moving to outer walls * \return Whether anything was added to the layer plan. */ - bool addToLayer(const RetractBeforeOuterWall retract_before_outer_wall = RetractBeforeOuterWall::AUTOMATIC, const PrintSegmentAttributes& print_attributes = {}); + bool addToLayer(const RetractBeforeOuterWall retract_before_outer_wall = RetractBeforeOuterWall::AUTOMATIC, const OverrideAreas& override_areas = {}); /*! * Get the order constraints of the insets when printing walls per region / hole. diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 008a24a852..4419c7f37d 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -7,6 +7,7 @@ #include "ExtruderPlan.h" #include "FanSpeedLayerTime.h" #include "InsetOrderOptimizer.h" +#include "OverrideAreas.h" #include "PathOrderOptimizer.h" #include "SpaceFillType.h" #include "gcode_export/gcodeExport.h" @@ -158,17 +159,11 @@ class LayerPlan : public NoCopy coord_t comb_move_inside_distance_; //!< Whenever using the minimum boundary for combing it tries to move the coordinates inside by this distance after calculating the combing. Shape bridge_wall_mask_; //!< The regions of a layer part that are not supported, used for bridging AABB bridge_wall_mask_bb_; //!< Cached bounding box for the above value. - std::vector overhang_masks_; //!< The regions of a layer part where the walls overhang, calculated for multiple overhang angles. The latter is the most - //!< overhanging. For a visual explanation of the result, see doc/gradual_overhang_speed.svg Shape seam_overhang_mask_; //!< The regions of a layer part where the walls overhang, specifically as defined for the seam Shape roofing_mask_; //!< The regions of a layer part where the walls are exposed to the air above Shape flooring_mask_; //!< The regions of a layer part where the walls are exposed to the air below - bool currently_overhanging_{ false }; //!< Indicates whether the last extrusion move was overhanging - coord_t current_overhang_length_{ 0 }; //!< When doing consecutive overhanging moves, this is the current accumulated overhanging length - coord_t max_overhang_length_{ 0 }; //!< From all consecutive overhanging moves in the layer, this is the longest one - bool min_layer_time_used = false; //!< Wether or not the minimum layer time (cool_min_layer_time) was actually used in this layerplan. std::map infill_lines_; //!< Infill lines generated for this layer @@ -350,13 +345,6 @@ class LayerPlan : public NoCopy */ void setBridgeWallMask(const Shape& polys); - /*! - * Set overhang_masks. - * - * \param masks The overhung areas of the part currently being processed that will require modified print settings - */ - void setOverhangMasks(const std::vector& masks); - /*! * Set seam_overhang_mask. * @@ -462,17 +450,17 @@ class LayerPlan : public NoCopy const bool travel_to_z = true, const PrintSegmentAttributes& print_attributes = {}); - void addExtrusionMoveWithGradualOverhang( + void addExtrusionMove( const Point3LL& p, const GCodePathConfig& config, + const OverrideAreas& override_areas, const SpaceFillType space_fill_type, const Ratio& flow = 1.0_r, const Ratio width_factor = 1.0_r, const bool spiralize = false, const Ratio speed_factor = 1.0_r, const double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT, - const bool travel_to_z = true, - const PrintSegmentAttributes& print_attributes = {}); + const bool travel_to_z = true); /*! * Adds an extrusion move that may go through a skin area @@ -497,7 +485,7 @@ class LayerPlan : public NoCopy const Ratio& width_factor, const bool spiralize, const bool travel_to_z, - const PrintSegmentAttributes& print_attributes = {}); + const OverrideAreas& override_areas = {}); /*! * Add polygon to the gcode starting at vertex \p startIdx @@ -525,7 +513,7 @@ class LayerPlan : public NoCopy const ForceRetract force_retract = ForceRetract::AUTOMATIC, bool scarf_seam = false, bool smooth_speed = false, - const PrintSegmentAttributes& print_attributes = {}); + const OverrideAreas& override_areas = {}); /*! * Add polygons to the gcode with optimized order. @@ -563,7 +551,7 @@ class LayerPlan : public NoCopy const Shape& polygons, const GCodePathConfig& config, const Settings& settings, - const PrintSegmentAttributes& print_attributes = {}, + const OverrideAreas& override_areas = {}, const ZSeamConfig& z_seam_config = ZSeamConfig(), coord_t wall_0_wipe_dist = 0, bool spiralize = false, @@ -630,10 +618,6 @@ class LayerPlan : public NoCopy */ void addWallLine( const std::vector>& bridging_subsegments, - const PathAdapter& wall, - const size_t segment_index, - const Ratio& segment_start_ratio, - const Ratio& segment_end_ratio, const Point3LL& p0, const Point3LL& p1, const Settings& settings, @@ -647,7 +631,7 @@ class LayerPlan : public NoCopy Ratio speed_factor, double distance_to_bridge_start, const bool travel_to_z = true, - const PrintSegmentAttributes& print_attributes = {}); + const OverrideAreas& override_areas = {}); /*! * Add a wall to the g-code starting at vertex \p start_idx @@ -720,7 +704,7 @@ class LayerPlan : public NoCopy const bool is_linked_path, const bool scarf_seam = false, const bool smooth_speed = false, - const PrintSegmentAttributes& print_attributes = {}); + const OverrideAreas& override_areas = {}); /*! * Add an infill wall to the g-code @@ -786,7 +770,7 @@ class LayerPlan : public NoCopy const Ratio flow_ratio = 1.0, const std::optional near_start_location = std::optional(), const double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT, - const PrintSegmentAttributes& print_attributes = {}, + const OverrideAreas& override_areas = {}, const bool reverse_print_direction = false, const std::unordered_multimap& order_requirements = PathOrderOptimizer::no_order_requirements_, const coord_t extra_inwards_start_move_length = 0, @@ -852,7 +836,7 @@ class LayerPlan : public NoCopy const Ratio flow_ratio = 1.0_r, const double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT, const bool interlaced = false, - const PrintSegmentAttributes& print_attributes = {}); + const OverrideAreas& override_areas = {}); /*! * Add a spiralized slice of wall that is interpolated in X/Y between \p last_wall and \p wall. @@ -951,6 +935,12 @@ class LayerPlan : public NoCopy const bool empty() const; private: + struct PartialExtrusionSegment + { + Point3LL p1; + const OverrideArea* area; + }; + /*! * \brief Compute the preferred or minimum combing boundary * @@ -989,7 +979,7 @@ class LayerPlan : public NoCopy const coord_t wipe_dist, const Ratio flow_ratio, const double fan_speed, - const PrintSegmentAttributes& print_attributes = {}, + const OverrideAreas& override_areas = {}, const coord_t extra_inwards_start_move_length = 0, const coord_t extra_inwards_end_move_length = 0, const MendedShape& extra_inwards_move_contour = MendedShape()); @@ -1029,7 +1019,7 @@ class LayerPlan : public NoCopy bool reverse_order = false, bool scarf_seam = false, bool smooth_speed = false, - const PrintSegmentAttributes& print_attributes = {}); + const OverrideAreas& override_areas = {}); /*! * @brief Send a GCodePath line to the communication object, applying proper Z offsets @@ -1092,10 +1082,6 @@ class LayerPlan : public NoCopy template using AddExtrusionSegmentFunction = std::function>& bridging_subsegments, - const PathAdapter& wall, - const size_t segment_index, - const Ratio& segment_start_ratio, - const Ratio& segment_end_ratio, const Point3LL& start, const Point3LL& end, const Ratio& speed_factor, @@ -1243,12 +1229,11 @@ class LayerPlan : public NoCopy struct BridgeLocation { - public: ptrdiff_t wall_idx_start; ptrdiff_t wall_idx_end; coord_t start_dist; // distance from the point indicated by wall_idx_start to the start of the bridge-segment - coord_t end_dist; // distance from the point indicated by [wall_idx_end - direction] (which equals wall_idx_start if the bridge is within a single wall-segment) to the end - // of the bridge-segment + coord_t end_dist; // distance from the point indicated by [wall_idx_end - direction] (which equals wall_idx_start if the bridge is within a single wall-segment) to the + // end of the bridge-segment coord_t bridge_len; // length of the bridge (which is only the same as end - start distance, in case the start and end wall indices are exactly 1 apart) coord_t backwards_end_dist; // distance from the point indicated by wall_idx_end to the end of the bridge-segment (so, backwards from the last point of the line-segment) coord_t from_start_of_wall; // distance to the start of the bridge segment from the start of the entire wall @@ -1360,6 +1345,8 @@ class LayerPlan : public NoCopy const Velocity& speed, const size_t point_index); + coord_t calculateMaxOverhangLength() const; + /*! * Generates an extrusion move that goes as inwards as possible given a skeletized contour, starting from the given point * @param trapezoidal_edges The edges of the skeletal trapezoidation for the contour @@ -1369,6 +1356,8 @@ class LayerPlan : public NoCopy * already inwards the contour enough. */ static OpenPolyline makeInwardsMove(const std::list& trapezoidal_edges, const Point2LL& start_point, const coord_t move_inwards_length); + + static std::vector splitExtrusionSegment(const Point3LL& start, const Point3LL& end, const OverrideAreas& override_areas); }; } // namespace cura diff --git a/include/OverrideAreas.h b/include/OverrideAreas.h new file mode 100644 index 0000000000..d418c50c0c --- /dev/null +++ b/include/OverrideAreas.h @@ -0,0 +1,29 @@ +// Copyright (c) 2026 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#pragma once + +#include + +#include "PrintSegmentAttributes.h" +#include "geometry/Shape.h" +#include "settings/types/Ratio.h" + +namespace cura +{ + +struct GCodePathConfig; + +/*! Represents an area where the extrusion line should be printed with specific settings */ +struct OverrideArea +{ + Shape area; // The area where the extrusion lines are to be printed with specific settings + PrintSegmentAttributes print_attributes; // Extra print attributes to be set for extrusion segments that are printed inside the area + const GCodePathConfig* config{ nullptr }; // Configuration to be used when printing extrusion segments in this area, or nullptr + Ratio speed_factor{ 1.0_r }; // Extra speed ratio to be applied when printing extrusion segments in this area, or 1.0 to have no effect +}; + +// A vector or override areas is to be applied as a stack: the last area takes precedences over the previous one, and so on. +using OverrideAreas = std::vector; + +} // namespace cura diff --git a/include/bridge/bridge.h b/include/bridge/bridge.h index c1ed0fb5e9..17a16f2759 100644 --- a/include/bridge/bridge.h +++ b/include/bridge/bridge.h @@ -22,8 +22,9 @@ class AngleDegrees; class LayerPlan; template class PathAdapter; -struct Ratio; struct ExtrusionLine; +struct MeshPathConfigs; +class Settings; /*! * \brief Computes the angle that lines have to take to bridge a certain shape @@ -47,7 +48,7 @@ std::optional bridgeAngle( const unsigned layer_nr, const unsigned bridge_layer, const SupportLayer* support_layer, - Shape& supported_regions); + Shape& bridging_area); /*! * @brief Make sure the bridging above infill (below skin) is properly printable by expanding the area below the skin so that the bridging would always provide anchoring points diff --git a/include/utils/CuraViz.h b/include/utils/CuraViz.h index 8592441590..96cdff44cd 100644 --- a/include/utils/CuraViz.h +++ b/include/utils/CuraViz.h @@ -5,20 +5,25 @@ #define CURAVIZ_H #ifdef ENABLE_CURAVIZ -#include +#include +#include +#include "ExtrusionLine.h" #include "geometry/Point2LL.h" +#include "geometry/Point3LL.h" namespace cura_viz { class Message; class GeometricElement; class Polyline2LL; +class Segment2LL; } // namespace cura_viz namespace cura { +class OpenLinesSet; class MixedLinesSet; class Shape; class Polyline; @@ -54,14 +59,24 @@ class CuraViz public: static void send(const Point2LL& point, const std::string& name = "", const std::string& step_name = ""); + static void send(const Point2LL& start, const Point2LL& end, const std::string& name = "", const std::string& step_name = ""); + + static void send(const Point3LL& start, const Point3LL& end, const std::string& name = "", const std::string& step_name = ""); + + static void send(const Polyline& line, const std::string& name = "", const std::string& step_name = ""); + static void send(const Shape& shape, const std::string& name = "", const std::string& step_name = ""); static void send(const std::vector& shapes, const std::string& name = "", const std::string& step_name = ""); + static void send(const OpenLinesSet& lines, const std::string& name = "", const std::string& step_name = ""); + static void send(const MixedLinesSet& lines_set, const std::string& name = "", const std::string& step_name = ""); static void send(const std::vector& lines_sets, const std::string& name = "", const std::string& step_name = ""); + static void send(const std::vector& lines, const std::string& name = "", const std::string& step_name = ""); + private: /*! Convenience class that stores the message and handles its actual sending and destruction when appropriate */ class MessageToSend @@ -91,12 +106,16 @@ class CuraViz static void setup(const Polyline& polyline, cura_viz::Polyline2LL* polyline_message); + static void setup(const ExtrusionLine& line, cura_viz::Polyline2LL* polyline_message); + + static void setup(const Point2LL& start, const Point2LL& end, cura_viz::Segment2LL* segment_message); + private: static CuraViz* instance_; static std::mutex mutex_; - asio::io_context io_context_; - asio::ip::tcp::socket socket_; + boost::asio::io_context io_context_; + boost::asio::ip::tcp::socket socket_; }; } // namespace cura diff --git a/include/utils/Flags.h b/include/utils/Flags.h index bc021cc083..ffb7662ad0 100644 --- a/include/utils/Flags.h +++ b/include/utils/Flags.h @@ -33,6 +33,11 @@ class Flags return value_; } + bool isSet(const EnumClass value) const + { + return *this & value; + } + Flags operator|(const Flags& other) const { return Flags(static_cast(value_ | other.value_)); diff --git a/src/ExtruderPlan.cpp b/src/ExtruderPlan.cpp index 0a33aa92d9..c49e63c6e7 100644 --- a/src/ExtruderPlan.cpp +++ b/src/ExtruderPlan.cpp @@ -138,4 +138,43 @@ AABB ExtruderPlan::calculateExtrusionBoundingBox() const return bounding_box; } +coord_t ExtruderPlan::calculateMaxOverhangLength() const +{ + coord_t max_overhang_length = 0; + bool overhanging = false; + coord_t current_overhang_length = 0; + std::optional current_position; + + for (const GCodePath& path : paths_) + { + const bool new_overhanging = path.print_attributes.isSet(PrintSegmentAttribute::Overhanging); + if (new_overhanging != overhanging) + { + if (overhanging) + { + max_overhang_length = std::max(max_overhang_length, current_overhang_length); + } + + current_overhang_length = 0; + } + + if (new_overhanging) + { + for (const Point3LL& position : path.points) + { + if (current_position.has_value()) + { + current_overhang_length += vSize(position.toPoint2LL() - current_position.value()); + } + current_position = position.toPoint2LL(); + } + } + + current_position = path.points.back().toPoint2LL(); + overhanging = new_overhanging; + } + + return std::max(max_overhang_length, current_overhang_length); +} + } // namespace cura diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 6bbf30b404..ef96eeeaa2 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -719,7 +719,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) constexpr auto enable_travel_optimization = false; constexpr auto always_retract = ForceRetract::AUTOMATIC; constexpr auto reverse_order = false; - constexpr PrintSegmentAttributes print_attributes; + constexpr OverrideAreas override_areas; gcode_layer.addLinesByOptimizer( raft_lines, @@ -734,7 +734,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) raft_polygons, gcode_layer.configs_storage_.raft_base_config, mesh_group_settings, - print_attributes, + override_areas, ZSeamConfig(), wipe_dist, spiralize, @@ -882,7 +882,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) constexpr auto enable_travel_optimization = false; constexpr auto always_retract = ForceRetract::AUTOMATIC; constexpr auto reverse_order = false; - constexpr PrintSegmentAttributes print_attributes; + constexpr OverrideAreas override_areas; gcode_layer.addLinesByOptimizer( raft_lines, @@ -897,7 +897,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) raft_polygons, gcode_layer.configs_storage_.raft_interface_config, mesh_group_settings, - print_attributes, + override_areas, ZSeamConfig(), wipe_dist, spiralize, @@ -1057,7 +1057,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) constexpr auto enable_travel_optimization = false; constexpr auto always_retract = ForceRetract::AUTOMATIC; constexpr auto reverse_order = false; - constexpr PrintSegmentAttributes print_attributes; + constexpr OverrideAreas override_areas; if (monotonic) { @@ -1082,7 +1082,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) raft_polygons, gcode_layer.configs_storage_.raft_surface_config, mesh_group_settings, - print_attributes, + override_areas, ZSeamConfig(), wipe_dist, spiralize, @@ -1756,13 +1756,13 @@ void FffGcodeWriter::addMeshLayerToGCode_meshSurfaceMode(const SliceMeshStorage& const std::optional start_near_location = std::nullopt; constexpr bool scarf_seam = true; constexpr bool smooth_speed = true; - constexpr PrintSegmentAttributes print_attributes; + constexpr OverrideAreas override_areas; gcode_layer.addPolygonsByOptimizer( polygons, mesh_config.inset0_config, mesh.settings, - print_attributes, + override_areas, z_seam_config, mesh.settings.get("wall_0_wipe_dist"), spiralize, @@ -1864,23 +1864,26 @@ void FffGcodeWriter::addMeshPartToGCode( const bool end_infill_close_to_seam = infill_before_walls && mesh.settings.get("infill_start_end_preference") == InfillStartEndPreference::END_CLOSE_TO_SEAM; + const OverrideAreas overhang_areas = makeLayerMasks(storage, gcode_layer, mesh, mesh_config, part); + // Pre-process the insets without actually adding them, so that we know where they are going to start printing - InsetsPreprocessResult insets_preprocess_result = preProcessInsets(storage, gcode_layer, mesh, extruder_nr, mesh_config, part, end_infill_close_to_seam); + std::optional insets_preprocess_result = preProcessInsets(storage, gcode_layer, mesh, extruder_nr, mesh_config, part, end_infill_close_to_seam); bool infill_added = false; if (infill_before_walls) { std::optional near_end_location; - if (end_infill_close_to_seam && insets_preprocess_result.walls_optimizer) + if (end_infill_close_to_seam && insets_preprocess_result.has_value() && insets_preprocess_result->walls_optimizer) { - near_end_location = insets_preprocess_result.walls_optimizer->getStartPosition(); + near_end_location = insets_preprocess_result->walls_optimizer->getStartPosition(); } infill_added = processInfill(storage, gcode_layer, mesh, extruder_nr, mesh_config, part, near_end_location); added_something = added_something | infill_added; } - added_something |= endProcessInsets(insets_preprocess_result, storage, gcode_layer, mesh, extruder_nr, mesh_config, part, infill_added); + added_something |= insets_preprocess_result.has_value() + && endProcessInsets(*insets_preprocess_result, overhang_areas, storage, gcode_layer, mesh, extruder_nr, mesh_config, part, infill_added); if (! infill_before_walls) { @@ -2073,7 +2076,7 @@ bool FffGcodeWriter::processMultiLayerInfill( const bool enable_travel_optimization = mesh.settings.get("infill_enable_travel_optimization"); constexpr Ratio flow_ratio = 1.0_r; constexpr double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT; - constexpr PrintSegmentAttributes print_attributes; + constexpr OverrideAreas override_areas; const std::unordered_multimap order_requirements = PathOrderOptimizer::no_order_requirements_; gcode_layer.addLinesByOptimizer( @@ -2085,7 +2088,7 @@ bool FffGcodeWriter::processMultiLayerInfill( flow_ratio, near_start_location, fan_speed, - print_attributes, + override_areas, reverse_print_direction, order_requirements, start_move_inwards_length, @@ -2578,7 +2581,7 @@ void FffGcodeWriter::processSpiralizedWall( } } -FffGcodeWriter::InsetsPreprocessResult FffGcodeWriter::preProcessInsets( +std::optional FffGcodeWriter::preProcessInsets( const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, @@ -2589,11 +2592,11 @@ FffGcodeWriter::InsetsPreprocessResult FffGcodeWriter::preProcessInsets( { if (extruder_nr != mesh.settings.get("wall_0_extruder_nr").extruder_nr_ && extruder_nr != mesh.settings.get("wall_x_extruder_nr").extruder_nr_) { - return {}; + return std::nullopt; } if (mesh.settings.get("wall_line_count") <= 0) { - return {}; + return std::nullopt; } InsetsPreprocessResult result; @@ -2607,90 +2610,149 @@ FffGcodeWriter::InsetsPreprocessResult FffGcodeWriter::preProcessInsets( // nothing to do return {}; } - if (gcode_layer.getLayerNr() >= initial_bottom_layers) + if (layer_nr >= initial_bottom_layers) { result.spiralize = true; } } - // for non-spiralized layers, determine the shape of the unsupported areas below this part - if (! result.spiralize && gcode_layer.getLayerNr() > 0) + if (! result.spiralize || extruder_nr != mesh.settings.get("wall_0_extruder_nr").extruder_nr_ || part.spiral_wall.empty()) { - // accumulate the outlines of all of the parts that are on the layer below + // Main case: Optimize the insets with the InsetOrderOptimizer. + const coord_t wall_x_wipe_dist = 0; + const ZSeamConfig z_seam_config( + mesh.settings.get("z_seam_type"), + mesh.getZSeamHint(), + mesh.settings.get("z_seam_corner"), + mesh.settings.get("wall_line_width_0") * 2); + const Shape disallowed_areas_for_seams; + constexpr bool scarf_seam = true; + constexpr bool smooth_speed = true; - Shape outlines_below; - AABB boundaryBox(part.outline); - for (const std::shared_ptr& mesh_ptr : storage.meshes) + result.walls_optimizer = std::make_shared( + storage, + gcode_layer, + mesh.settings, + extruder_nr, + mesh_config.inset0_config, + mesh_config.insetX_config, + mesh_config.inset0_roofing_config, + mesh_config.insetX_roofing_config, + mesh_config.inset0_flooring_config, + mesh_config.insetX_flooring_config, + mesh_config.bridge_inset0_config, + mesh_config.bridge_insetX_config, + mesh.settings.get("wall_0_wipe_dist"), + wall_x_wipe_dist, + mesh.settings.get("wall_0_extruder_nr").extruder_nr_, + mesh.settings.get("wall_x_extruder_nr").extruder_nr_, + z_seam_config, + part.wall_toolpaths, + mesh.bounding_box.flatten().getMiddle(), + disallowed_areas_for_seams, + scarf_seam, + smooth_speed, + gcode_layer.getSeamOverhangMask(), + mesh.layers[gcode_layer.getLayerNr()].texture_data_provider_, + end_infill_close_to_seam); + result.walls_optimizer->optimize(); + } + + return result; +} + +OverrideAreas + FffGcodeWriter::makeLayerMasks(const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const MeshPathConfigs& mesh_config, SliceLayerPart& part) + const +{ + // Start by clearing all the masks since this is done per-mesh + gcode_layer.setBridgeWallMask(Shape()); + gcode_layer.setSeamOverhangMask(Shape()); + gcode_layer.setRoofingMask(Shape()); + gcode_layer.setFlooringMask(Shape()); + + if (Application::getInstance().current_slice_->scene.current_mesh_group->settings.get("magic_spiralize") || gcode_layer.getLayerNr() == 0) + { + return {}; + } + + // accumulate the outlines of all the parts that are on the layer below + + Shape outlines_below; + AABB boundaryBox(part.outline); + for (const std::shared_ptr& mesh_ptr : storage.meshes) + { + const auto& mesh_below = *mesh_ptr; + if (mesh_below.isPrinted()) { - const auto& m = *mesh_ptr; - if (m.isPrinted()) + for (const SliceLayerPart& prevLayerPart : mesh_below.layers[gcode_layer.getLayerNr() - 1].parts) { - for (const SliceLayerPart& prevLayerPart : m.layers[gcode_layer.getLayerNr() - 1].parts) + if (boundaryBox.hit(prevLayerPart.boundaryBox)) { - if (boundaryBox.hit(prevLayerPart.boundaryBox)) - { - outlines_below.push_back(prevLayerPart.outline); - } + outlines_below.push_back(prevLayerPart.outline); } } } - const Shape non_support_outlines_below = outlines_below; + } - const coord_t layer_height = mesh_config.inset0_config.getLayerThickness(); + const Shape mesh_outlines_below = outlines_below; + const coord_t layer_height = mesh_config.inset0_config.getLayerThickness(); + + // If support is enabled, add the support outlines also so we don't generate bridges over support. + const auto& mesh_group = Application::getInstance().current_slice_->scene.current_mesh_group; + const Settings& mesh_group_settings = mesh_group->settings; + if (mesh_group_settings.get("support_enable") || mesh_group->has_painted_support) + { + const coord_t z_distance_top = mesh.settings.get("support_top_distance"); + const size_t z_distance_top_layers = (z_distance_top / layer_height) + 1; + const int support_layer_nr = gcode_layer.getLayerNr() - z_distance_top_layers; - // If support is enabled & the proper setting is on, add the support outlines also so we don't generate bridges over support. - const auto& mesh_group = Application::getInstance().current_slice_->scene.current_mesh_group; - const Settings& mesh_group_settings = mesh_group->settings; - if ((mesh_group_settings.get("support_enable") || mesh_group->has_painted_support) && mesh_group_settings.get("bridge_allow_support_anchor")) + if (support_layer_nr > 0) { - const coord_t z_distance_top = mesh.settings.get("support_top_distance"); - const size_t z_distance_top_layers = (z_distance_top / layer_height) + 1; - const int support_layer_nr = gcode_layer.getLayerNr() - z_distance_top_layers; + const SupportLayer& support_layer = storage.support.supportLayers[support_layer_nr]; - if (support_layer_nr > 0) + if (! support_layer.support_roof.empty()) { - const SupportLayer& support_layer = storage.support.supportLayers[support_layer_nr]; - - if (! support_layer.support_roof.empty()) + AABB support_roof_bb(support_layer.support_roof); + if (boundaryBox.hit(support_roof_bb)) { - AABB support_roof_bb(support_layer.support_roof); - if (boundaryBox.hit(support_roof_bb)) - { - outlines_below.push_back(support_layer.support_roof); - } + outlines_below.push_back(support_layer.support_roof); } - else + } + else + { + for (const SupportInfillPart& support_part : support_layer.support_infill_parts) { - for (const SupportInfillPart& support_part : support_layer.support_infill_parts) + AABB support_part_bb(support_part.outline_); + if (boundaryBox.hit(support_part_bb)) { - AABB support_part_bb(support_part.outline_); - if (boundaryBox.hit(support_part_bb)) - { - outlines_below.push_back(support_part.outline_); - } + outlines_below.push_back(support_part.outline_); } } } } + } - const int half_outer_wall_width = mesh_config.inset0_config.getLineWidth() / 2; + const coord_t outer_wall_width = mesh_config.inset0_config.getLineWidth(); + const coord_t half_outer_wall_width = outer_wall_width / 2; - // remove those parts of the layer below that are narrower than a wall line width as they will not be printed + // remove those parts of the layer below that are narrower than a wall line width as they will not be printed - outlines_below = outlines_below.offset(-half_outer_wall_width).offset(half_outer_wall_width); + outlines_below = outlines_below.offset(-half_outer_wall_width).offset(half_outer_wall_width); + { // Bridge mask if (mesh.settings.get("bridge_settings_enabled")) { // max_air_gap is the max allowed width of the unsupported region below the wall line // if the unsupported region is wider than max_air_gap, the wall line will be printed using bridge settings const coord_t overhang_width = layer_height * boundedTan(mesh.settings.get("wall_overhang_angle")); - const coord_t max_air_gap = std::max(coord_t{ half_outer_wall_width }, overhang_width); + const coord_t max_air_gap = std::clamp(overhang_width, half_outer_wall_width, outer_wall_width); // subtract the outlines of the parts below this part to give the shapes of the unsupported regions and then // shrink those shapes so that any that are narrower than two times max_air_gap will be removed - Shape compressed_air = part.outline.difference(outlines_below).offset(-max_air_gap); + const Shape compressed_air = part.outline.difference(outlines_below).offset(-max_air_gap); // now expand the air regions by the same amount as they were shrunk (completing the morphological opening operation) // also, if the bridge-flow is light enough, compensate for the fact that the wall-vertices aren't exactly on the outline @@ -2717,33 +2779,31 @@ FffGcodeWriter::InsetsPreprocessResult FffGcodeWriter::preProcessInsets( skin_part.roofing_fill = skin_part.roofing_fill.difference(skin_part.skin_fill).offset(-10).offset(skin_overlap + 10).intersection(skin_part.roofing_fill); } } - else - { - // clear to disable use of bridging settings - gcode_layer.setBridgeWallMask(Shape()); - } + } + + Shape model_supported_region = mesh_outlines_below.offset(-half_outer_wall_width); + // remove those parts of the layer below that are narrower than a wall line width as they will not be printed + model_supported_region = model_supported_region.offset(-half_outer_wall_width).offset(half_outer_wall_width); - Shape model_supported_region = non_support_outlines_below.offset(-half_outer_wall_width); - // remove those parts of the layer below that are narrower than a wall line width as they will not be printed - model_supported_region = model_supported_region.offset(-half_outer_wall_width).offset(half_outer_wall_width); - const auto get_supported_region = [&model_supported_region, &layer_height](const AngleDegrees& overhang_angle) -> Shape + const auto get_supported_region = [&model_supported_region, &layer_height](const AngleDegrees& overhang_angle) -> Shape + { + // the overhang mask is set to the area of the current part's outline minus the region that is considered to be supported + // the supported region is made up of those areas that are supported by the model on the layer below + // expanded to take into account the overhang angle, the greater the overhang angle, the larger the supported area is + // considered to be + if (overhang_angle < 90.0) { - // the overhang mask is set to the area of the current part's outline minus the region that is considered to be supported - // the supported region is made up of those areas that are supported by the model on the layer below - // expanded to take into account the overhang angle, the greater the overhang angle, the larger the supported area is - // considered to be - if (overhang_angle < 90.0) - { - const coord_t overhang_width = layer_height * std::tan(AngleRadians(overhang_angle)); - return model_supported_region.offset(overhang_width + 10); - } + const coord_t overhang_width = layer_height * std::tan(AngleRadians(overhang_angle)); + return model_supported_region.offset(overhang_width + 10); + } - return Shape(); - }; + return Shape(); + }; - // Build supported regions for all the overhang speeds. For a visual explanation of the result, see doc/gradual_overhang_speed.svg - std::vector overhang_masks; + // Build supported regions for all the overhang speeds. For a visual explanation of the result, see doc/gradual_overhang_speed.svg + OverrideAreas overhang_areas; + { // Overhang masks const auto overhang_speed_factors = mesh.settings.get>("wall_overhang_speed_factors"); const size_t overhang_angles_count = overhang_speed_factors.size(); const auto wall_overhang_angle = mesh.settings.get("wall_overhang_angle"); @@ -2757,23 +2817,19 @@ FffGcodeWriter::InsetsPreprocessResult FffGcodeWriter::preProcessInsets( }; // Create raw speed regions + const Shape full_buildplate = storage.getRawMachineBorder(); const AngleDegrees overhang_step = (90.0 - wall_overhang_angle) / static_cast(overhang_angles_count); std::vector speed_regions; speed_regions.reserve(overhang_angles_count + 2); - constexpr bool dont_chunk_first = false; // Never merge internal region in order to detect actual overhanging - speed_regions.push_back(SpeedRegion{ wall_overhang_angle, 1.0_r, dont_chunk_first }); // Initial internal region, always 100% speed factor - - for (size_t angle_index = 1; angle_index <= overhang_angles_count; ++angle_index) + for (size_t angle_index = 0; angle_index < overhang_angles_count; ++angle_index) { const AngleDegrees actual_wall_overhang_angle = wall_overhang_angle + static_cast(angle_index) * overhang_step; - const Ratio speed_factor = overhang_speed_factors[angle_index - 1]; + const Ratio speed_factor = overhang_speed_factors[angle_index]; - speed_regions.push_back(SpeedRegion{ actual_wall_overhang_angle, speed_factor }); + speed_regions.emplace_back(actual_wall_overhang_angle, speed_factor); } - speed_regions.push_back(SpeedRegion{ 90.0, overhang_speed_factors.back() }); // Final "everything else" speed region - // Now merge regions that have similar speed factors (saves calculations and avoid generating micro-segments) auto merged_regions = speed_regions | ranges::views::chunk_by( @@ -2788,12 +2844,14 @@ FffGcodeWriter::InsetsPreprocessResult FffGcodeWriter::preProcessInsets( for (const auto& regions : merged_regions) { const SpeedRegion& last_region = *ranges::prev(regions.end()); - overhang_masks.push_back(LayerPlan::OverhangMask{ get_supported_region(last_region.overhang_angle), last_region.speed_factor }); + const Shape overhang_region = full_buildplate.difference(get_supported_region(last_region.overhang_angle)); + overhang_areas.emplace_back(overhang_region, PrintSegmentAttribute::Overhanging, nullptr, last_region.speed_factor); } } } - result.overhang_masks = overhang_masks; + } + { // Seam overhang mask // the seam overhang mask is set to the area of the current part's outline minus the region that is considered to be supported, // which will then be empty if everything is considered supported i.r.t. the angle const AngleDegrees seam_overhang_angle = mesh.settings.get("seam_overhang_angle"); @@ -2803,13 +2861,11 @@ FffGcodeWriter::InsetsPreprocessResult FffGcodeWriter::preProcessInsets( = storage.getMachineBorder(mesh.settings.get("wall_0_extruder_nr").extruder_nr_).difference(get_supported_region(seam_overhang_angle)); gcode_layer.setSeamOverhangMask(seam_overhang_mask); } - else - { - gcode_layer.setSeamOverhangMask(Shape()); - } + } - const auto wall_line_width_0 = mesh.settings.get("wall_line_width_0"); + const auto wall_line_width_0 = mesh.settings.get("wall_line_width_0"); + { // Roofing mask const auto roofing_mask_fn = [&]() -> Shape { const size_t roofing_layer_count = std::min(mesh.settings.get("roofing_layer_count"), mesh.settings.get("top_layers")); @@ -2832,7 +2888,9 @@ FffGcodeWriter::InsetsPreprocessResult FffGcodeWriter::preProcessInsets( }; gcode_layer.setRoofingMask(roofing_mask_fn()); + } + { // Flooring mask const auto flooring_mask_fn = [&]() -> Shape { const size_t flooring_layer_count = std::min(mesh.settings.get("flooring_layer_count"), mesh.settings.get("bottom_layers")); @@ -2856,67 +2914,13 @@ FffGcodeWriter::InsetsPreprocessResult FffGcodeWriter::preProcessInsets( gcode_layer.setFlooringMask(flooring_mask_fn()); } - else - { - // clear to disable use of bridging settings - gcode_layer.setBridgeWallMask(Shape()); - // clear to ensure no stale overhang masks from a previous part are applied - gcode_layer.setOverhangMasks({}); - // clear to disable overhang detection - gcode_layer.setSeamOverhangMask(Shape()); - // clear to disable use of roofing settings - gcode_layer.setRoofingMask(Shape()); - // clear to disable use of flooring settings - gcode_layer.setFlooringMask(Shape()); - } - - if (! result.spiralize || extruder_nr != mesh.settings.get("wall_0_extruder_nr").extruder_nr_ || part.spiral_wall.empty()) - { - // Main case: Optimize the insets with the InsetOrderOptimizer. - const coord_t wall_x_wipe_dist = 0; - const ZSeamConfig z_seam_config( - mesh.settings.get("z_seam_type"), - mesh.getZSeamHint(), - mesh.settings.get("z_seam_corner"), - mesh.settings.get("wall_line_width_0") * 2); - const Shape disallowed_areas_for_seams; - constexpr bool scarf_seam = true; - constexpr bool smooth_speed = true; - result.walls_optimizer = std::make_shared( - storage, - gcode_layer, - mesh.settings, - extruder_nr, - mesh_config.inset0_config, - mesh_config.insetX_config, - mesh_config.inset0_roofing_config, - mesh_config.insetX_roofing_config, - mesh_config.inset0_flooring_config, - mesh_config.insetX_flooring_config, - mesh_config.bridge_inset0_config, - mesh_config.bridge_insetX_config, - mesh.settings.get("wall_0_wipe_dist"), - wall_x_wipe_dist, - mesh.settings.get("wall_0_extruder_nr").extruder_nr_, - mesh.settings.get("wall_x_extruder_nr").extruder_nr_, - z_seam_config, - part.wall_toolpaths, - mesh.bounding_box.flatten().getMiddle(), - disallowed_areas_for_seams, - scarf_seam, - smooth_speed, - gcode_layer.getSeamOverhangMask(), - mesh.layers[gcode_layer.getLayerNr()].texture_data_provider_, - end_infill_close_to_seam); - result.walls_optimizer->optimize(); - } - - return result; + return overhang_areas; } bool FffGcodeWriter::endProcessInsets( InsetsPreprocessResult& preprocess_result, + const OverrideAreas overhang_areas, const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, @@ -2973,10 +2977,7 @@ bool FffGcodeWriter::endProcessInsets( travel_retract_before_outer_wall = RetractBeforeOuterWall::AUTOMATIC; } - gcode_layer.setOverhangMasks(preprocess_result.overhang_masks); - const bool added = preprocess_result.walls_optimizer->addToLayer(travel_retract_before_outer_wall); - gcode_layer.setOverhangMasks({}); - return added; + return preprocess_result.walls_optimizer->addToLayer(travel_retract_before_outer_wall, overhang_areas); } return false; @@ -3115,6 +3116,7 @@ void FffGcodeWriter::processRoofingFlooring( roofing_angle = angles.at(gcode_layer.getLayerNr() % angles.size()); } + const GCodePathConfig* bridge_config = nullptr; const Ratio skin_density = 1.0; const coord_t skin_overlap = 0; // skinfill already expanded over the roofing areas; don't overlap with perimeters const LinesOrderingMethod ordering = mesh.settings.get(settings_names.monotonic) ? LinesOrderingMethod::Monotonic : LinesOrderingMethod::Basic; @@ -3126,6 +3128,7 @@ void FffGcodeWriter::processRoofingFlooring( extruder_nr, fill, config, + bridge_config, pattern, roofing_angle, skin_overlap, @@ -3168,21 +3171,21 @@ void FffGcodeWriter::processTopBottom( // generate skin_polygons and skin_lines const GCodePathConfig* skin_config = &mesh_config.skin_config; + const GCodePathConfig* bridge_config = nullptr; Ratio skin_density = 1.0; - const coord_t skin_overlap = 0; // Skin overlap offset is applied in skin.cpp more overlap might be beneficial for curved bridges, but makes it worse in general. + constexpr coord_t skin_overlap = 0; // Skin overlap offset is applied in skin.cpp more overlap might be beneficial for curved bridges, but makes it worse in general. const bool bridge_settings_enabled = mesh.settings.get("bridge_settings_enabled"); const bool bridge_enable_more_layers = bridge_settings_enabled && mesh.settings.get("bridge_enable_more_layers"); const Ratio support_threshold = bridge_settings_enabled ? mesh.settings.get("bridge_skin_support_threshold") : 0.0_r; const size_t bottom_layers = mesh.settings.get("bottom_layers"); + const auto support_enable = mesh_group_settings.get("support_enable"); std::optional forced_small_area_width; - PrintSegmentAttributes print_attributes; - - // if support is enabled, consider the support outlines so we don't generate bridges over support + OverrideAreas bridging_areas; int support_layer_nr = -1; const SupportLayer* support_layer = nullptr; - if (mesh_group_settings.get("support_enable") || mesh_group->has_painted_support) + if (support_enable || mesh_group->has_painted_support) { const coord_t layer_height = mesh_config.inset0_config.getLayerThickness(); const coord_t z_distance_top = mesh.settings.get("support_top_distance"); @@ -3199,48 +3202,43 @@ void FffGcodeWriter::processTopBottom( support_layer = &storage.support.supportLayers[support_layer_nr - (bridge_layer - 1)]; } - Shape supported_skin_part_regions; + Shape bridging_area; - const std::optional bridge_angle = bridgeAngle(mesh, skin_fill, storage, layer_nr, bridge_layer, support_layer, supported_skin_part_regions); + const std::optional bridge_angle = bridgeAngle(mesh, skin_fill, storage, layer_nr, bridge_layer, support_layer, bridging_area); - if (bridge_angle.has_value() || (support_threshold > 0 && (supported_skin_part_regions.area() / (skin_fill.area() + 1) < support_threshold))) + if (bridge_angle.has_value()) { - if (bridge_angle.has_value()) + switch (bridge_layer) { - switch (bridge_layer) - { - default: - case 1: - skin_angle = bridge_angle.value(); - break; - - case 2: - if (bottom_layers > 2) - { - // orientate second bridge skin at +45 deg to first - skin_angle = bridge_angle.value() + 45; - } - else - { - // orientate second bridge skin at 90 deg to first - skin_angle = bridge_angle.value() + 90; - } - break; + default: + case 1: + skin_angle = bridge_angle.value(); + break; - case 3: - // orientate third bridge skin at 135 (same result as -45) deg to first - skin_angle = bridge_angle.value() + 135; - break; + case 2: + if (bottom_layers > 2) + { + // orientate second bridge skin at +45 deg to first + skin_angle = bridge_angle.value() + 45; + } + else + { + // orientate second bridge skin at 90 deg to first + skin_angle = bridge_angle.value() + 90; } + break; + + case 3: + // orientate third bridge skin at 135 (same result as -45) deg to first + skin_angle = bridge_angle.value() + 135; + break; } + forced_small_area_width = 0; pattern = EFillMethod::LINES; // force lines pattern when bridging - if (bridge_settings_enabled) - { - print_attributes |= PrintSegmentAttribute::Bridging; - skin_config = config; - skin_density = density; - } + bridging_areas.emplace_back(bridging_area, PrintSegmentAttribute::Bridging, config); + bridge_config = config; + skin_density = density; return true; } @@ -3328,6 +3326,7 @@ void FffGcodeWriter::processTopBottom( extruder_nr, skin_fill, *skin_config, + bridge_config, pattern, skin_angle, skin_overlap, @@ -3337,7 +3336,7 @@ void FffGcodeWriter::processTopBottom( added_something, fan_speed, forced_small_area_width, - print_attributes); + bridging_areas); } void FffGcodeWriter::processSkinPrintFeature( @@ -3347,6 +3346,7 @@ void FffGcodeWriter::processSkinPrintFeature( const size_t extruder_nr, const Shape& area, const GCodePathConfig& config, + const GCodePathConfig* bridge_config, EFillMethod pattern, const AngleDegrees skin_angle, const coord_t skin_overlap, @@ -3356,7 +3356,7 @@ void FffGcodeWriter::processSkinPrintFeature( bool& added_something, double fan_speed, std::optional forced_small_area_width, - const PrintSegmentAttributes& print_attributes) const + const OverrideAreas& override_areas) const { Shape skin_polygons; OpenLinesSet skin_lines; @@ -3446,8 +3446,8 @@ void FffGcodeWriter::processSkinPrintFeature( config, config, config, - config, - config, + bridge_config ? *bridge_config : config, + bridge_config ? *bridge_config : config, wipe_dist, wipe_dist, extruder_nr, @@ -3455,18 +3455,17 @@ void FffGcodeWriter::processSkinPrintFeature( z_seam_config, skin_paths, mesh.bounding_box.flatten().getMiddle()); - added_something |= wall_orderer.addToLayer(retract_before_outer_wall, print_attributes); + added_something |= wall_orderer.addToLayer(retract_before_outer_wall, override_areas); } if (! skin_polygons.empty()) { gcode_layer.addTravel(skin_polygons[0][0]); - gcode_layer.addPolygonsByOptimizer(skin_polygons, config, mesh.settings, print_attributes); + gcode_layer.addPolygonsByOptimizer(skin_polygons, config, mesh.settings, override_areas); } if (ordering == LinesOrderingMethod::Monotonic || ordering == LinesOrderingMethod::Interlaced) { const coord_t exclude_distance = config.getLineWidth() * 0.8; - const bool interlaced = ordering == LinesOrderingMethod::Interlaced; const AngleRadians monotonic_direction = AngleRadians(skin_angle); constexpr Ratio flow = 1.0_r; @@ -3488,7 +3487,7 @@ void FffGcodeWriter::processSkinPrintFeature( flow, fan_speed, interlaced, - print_attributes); + override_areas); } else { @@ -3506,7 +3505,7 @@ void FffGcodeWriter::processSkinPrintFeature( flow, fan_speed, interlaced, - print_attributes); + override_areas); } } else @@ -3534,13 +3533,13 @@ void FffGcodeWriter::processSkinPrintFeature( flow, near_start_location, fan_speed, - print_attributes); + override_areas); } else { SpaceFillType space_fill_type = (actual_pattern == EFillMethod::ZIG_ZAG) ? SpaceFillType::PolyLines : SpaceFillType::Lines; constexpr coord_t wipe_dist = 0; - gcode_layer.addLinesByOptimizer(skin_lines, config, space_fill_type, enable_travel_optimization, wipe_dist, flow, near_start_location, fan_speed, print_attributes); + gcode_layer.addLinesByOptimizer(skin_lines, config, space_fill_type, enable_travel_optimization, wipe_dist, flow, near_start_location, fan_speed, override_areas); } } } @@ -3873,7 +3872,7 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer const bool alternate_inset_direction = infill_extruder.settings_.get("material_alternate_walls"); const bool alternate_layer_print_direction = alternate_inset_direction && gcode_layer.getLayerNr() % 2 == 1; - constexpr PrintSegmentAttributes print_attributes; + constexpr OverrideAreas override_areas; if (! support_polygons.empty()) { @@ -3890,7 +3889,7 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer support_polygons, configs[combine_idx], mesh_group_settings, - print_attributes, + override_areas, z_seam_config, wall_0_wipe_dist, spiralize, @@ -3918,7 +3917,7 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer flow_ratio, near_start_location, fan_speed, - print_attributes, + override_areas, alternate_layer_print_direction); added_something = true; diff --git a/src/InfillOrderOptimizer.cpp b/src/InfillOrderOptimizer.cpp index aeb3f15227..a42fb3f503 100644 --- a/src/InfillOrderOptimizer.cpp +++ b/src/InfillOrderOptimizer.cpp @@ -453,7 +453,7 @@ void InfillOrderOptimizer::addInfillLinesToLayer( wipe_dist = 0; } - constexpr PrintSegmentAttributes print_attributes; + constexpr OverrideAreas override_areas; layer_plan.addLinesByOptimizer( lines, mesh_config.infill_config[0], @@ -463,7 +463,7 @@ void InfillOrderOptimizer::addInfillLinesToLayer( flow_ratio, near_start_location, fan_speed, - print_attributes, + override_areas, reverse_print_direction, order_requirements, start_move_inwards_length, @@ -487,7 +487,7 @@ void InfillOrderOptimizer::addSkinSupportLinesToLayer( const auto skin_support_fan_speed = settings.get("cool_fan_enabled") ? settings.get("skin_support_fan_speed") : GCodePathConfig::FAN_SPEED_DEFAULT; constexpr SpaceFillType skin_support_space_fill_type = SpaceFillType::Lines; constexpr coord_t skin_support_wipe_dist = 0; - constexpr PrintSegmentAttributes print_attributes; + constexpr OverrideAreas override_areas; const auto skin_support_interlace_lines = settings.get("skin_support_interlace_lines"); if (skin_support_interlace_lines) { @@ -518,7 +518,7 @@ void InfillOrderOptimizer::addSkinSupportLinesToLayer( flow_ratio, near_start_location, skin_support_fan_speed, - print_attributes, + override_areas, reverse_print_direction); } } diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index 07fdc39090..bdfaa36fbb 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -145,7 +145,7 @@ void InsetOrderOptimizer::optimize() path_optimizer_->optimize(); } -bool InsetOrderOptimizer::addToLayer(const RetractBeforeOuterWall retract_before_outer_wall, const PrintSegmentAttributes& print_attributes) +bool InsetOrderOptimizer::addToLayer(const RetractBeforeOuterWall retract_before_outer_wall, const OverrideAreas& override_areas) { if (path_optimizer_ == nullptr) { @@ -212,7 +212,7 @@ bool InsetOrderOptimizer::addToLayer(const RetractBeforeOuterWall retract_before linked_path, scarf_seam, smooth_speed, - print_attributes); + override_areas); added_something = true; if (retract_before_outer_wall == RetractBeforeOuterWall::NOT_RETRACTED_FROM_INFILL) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 1e80b2a5ed..cedb79c927 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -600,209 +601,38 @@ void LayerPlan::addExtrusionMove( last_planned_position_ = p; } -void LayerPlan::addExtrusionMoveWithGradualOverhang( +void LayerPlan::addExtrusionMove( const Point3LL& p, const GCodePathConfig& config, + const OverrideAreas& override_areas, const SpaceFillType space_fill_type, const Ratio& flow, const Ratio width_factor, const bool spiralize, const Ratio speed_factor, const double fan_speed, - const bool travel_to_z, - const PrintSegmentAttributes& print_attributes) + const bool travel_to_z) { - const auto add_extrusion_move = [&](const Point3LL& target, const std::optional speed_region_index = std::nullopt) + if (last_planned_position_.has_value() && ! override_areas.empty()) { - PrintSegmentAttributes final_attributes = print_attributes; - if (speed_region_index.has_value() && speed_region_index.value() > 0) - { - final_attributes |= PrintSegmentAttribute::Overhanging; - } - const Ratio overhang_speed_factor = speed_region_index.has_value() ? overhang_masks_[speed_region_index.value()].speed_ratio : 1.0_r; - addExtrusionMove(target, config, space_fill_type, flow, width_factor, spiralize, speed_factor * overhang_speed_factor, fan_speed, travel_to_z, final_attributes); - }; - - const auto update_is_overhanging = [this](const Point3LL& target, std::optional current_position, const bool is_overhanging = false) - { - if (is_overhanging != currently_overhanging_) - { - max_overhang_length_ = std::max(current_overhang_length_, max_overhang_length_); - current_overhang_length_ = 0; - } - - if (is_overhanging && current_position.has_value()) - { - current_overhang_length_ += (target - current_position.value()).vSize(); - } - - currently_overhanging_ = is_overhanging; - }; - - if (overhang_masks_.empty() || ! last_planned_position_.has_value()) - { - // Unable to apply gradual overhanging (probably just disabled), just add the basic extrusion move - update_is_overhanging(p, last_planned_position_); - add_extrusion_move(p); - return; - } - - // First, find the speed region where the segment starts - const Point3LL start = last_planned_position_.value(); - const Point2LL start_flat = start.toPoint2LL(); - size_t actual_speed_region_index = overhang_masks_.size() - 1; // Default to last region, which is infinity and beyond - for (const auto& [index, overhang_region] : overhang_masks_ | ranges::views::drop_last(1) | ranges::views::enumerate) - { - if (overhang_region.supported_region.inside(start_flat, true)) - { - actual_speed_region_index = index; - break; - } - } - - // Pre-calculate the intersections of the segment with all regions (except last one, you cannot intersect an infinite plane) - const Point3LL end = p; - const Point2LL end_flat = end.toPoint2LL(); - const Point3LL vector = end - start; - std::vector> speed_regions_intersections; - speed_regions_intersections.reserve(overhang_masks_.size() - 1); - for (const OverhangMask& overhang_region : overhang_masks_ | ranges::views::drop_last(1)) - { - std::vector intersections = overhang_region.supported_region.intersectionsWithSegment(start_flat, end_flat); - ranges::stable_sort(intersections); - speed_regions_intersections.push_back(intersections); - } - - const auto remove_previous_intersections = [&speed_regions_intersections](const float current_intersection) - { - for (std::vector& intersections : speed_regions_intersections) - { - auto iterator = ranges::find_if( - intersections, - [¤t_intersection](const float next_intersection) - { - return next_intersection > current_intersection; - }); - - intersections.erase(intersections.begin(), iterator); - } - }; - - struct SegmentExtrusionMove - { - Point3LL position; - size_t speed_region_index; - }; - - std::vector extrusion_moves; - - // Now move along segment and split it where we cross speed regions - while (true) - { - // First, see if we cross either the border or our current region (go out) or the border of the inner region (go in) - auto get_first_intersection = [](const std::vector* intersections) -> std::optional - { - return intersections != nullptr && ! intersections->empty() ? std::make_optional(intersections->front()) : std::nullopt; - }; - - std::vector* intersections_current_region - = actual_speed_region_index < speed_regions_intersections.size() ? &speed_regions_intersections[actual_speed_region_index] : nullptr; - const std::optional first_intersection_current_region = get_first_intersection(intersections_current_region); - - std::vector* intersections_inner_region = actual_speed_region_index > 0 ? &speed_regions_intersections[actual_speed_region_index - 1] : nullptr; - const std::optional first_intersection_inner_region = get_first_intersection(intersections_inner_region); - - if (first_intersection_current_region.has_value() || first_intersection_inner_region.has_value()) + for (const PartialExtrusionSegment& partial_segment : splitExtrusionSegment(last_planned_position_.value(), p, override_areas)) { - float intersection_parameter; - size_t next_speed_region_index; - - if (first_intersection_current_region.has_value() - && (! first_intersection_inner_region.has_value() || first_intersection_inner_region.value() > first_intersection_current_region.value())) - { - // We crossed the border of the current region, which means we are getting out of it to an outer region - intersection_parameter = first_intersection_current_region.value(); - next_speed_region_index = actual_speed_region_index + 1; - } - else - { - // We crossed the border of the inner region, which means we are getting inside of it - intersection_parameter = first_intersection_inner_region.value(); - next_speed_region_index = actual_speed_region_index - 1; - } - - // Move to intersection at current region speed - const Point3LL split_position = start + vector * intersection_parameter; - extrusion_moves.push_back(SegmentExtrusionMove{ split_position, actual_speed_region_index }); - - // Prepare for next move in different region - actual_speed_region_index = next_speed_region_index; - remove_previous_intersections(intersection_parameter); - } - else - { - // We cross no border, which means we can reach the end of the segment within the current speed region, so we are done - extrusion_moves.push_back(SegmentExtrusionMove{ p, actual_speed_region_index }); - break; - } - } - - // Filter out micro-segments - std::vector extrusion_moves_filtered; - extrusion_moves_filtered.reserve(extrusion_moves.size()); - Point3LL current_position = start; - for (const SegmentExtrusionMove& extrusion_move : extrusion_moves | ranges::views::drop_last(1)) - { - if ((extrusion_move.position - current_position).vSize2() >= MINIMUM_SQUARED_LINE_LENGTH) - { - extrusion_moves_filtered.push_back(extrusion_move); + addExtrusionMove( + partial_segment.p1, + partial_segment.area != nullptr && partial_segment.area->config != nullptr ? *partial_segment.area->config : config, + space_fill_type, + flow, + width_factor, + spiralize, + partial_segment.area != nullptr ? speed_factor * partial_segment.area->speed_factor : speed_factor, + fan_speed, + travel_to_z, + partial_segment.area != nullptr ? partial_segment.area->print_attributes : PrintSegmentAttributes{}); } - - current_position = extrusion_move.position; - } - - if (extrusion_moves_filtered.empty() || (extrusion_moves.back().position - current_position).vSize2() >= MINIMUM_SQUARED_LINE_LENGTH) - { - extrusion_moves_filtered.push_back(extrusion_moves.back()); } else { - extrusion_moves_filtered.back().position = extrusion_moves.back().position; - } - - // Calculate max consecutive overhanging segment length - current_position = start; - for (const SegmentExtrusionMove& extrusion_move : extrusion_moves_filtered) - { - const bool is_overhanging = extrusion_move.speed_region_index > 0; - update_is_overhanging(extrusion_move.position, current_position, is_overhanging); - current_position = extrusion_move.position; - } - - // Merge consecutive sub-segments that in the end have the same speed - std::vector extrusion_moves_merged; - extrusion_moves_merged.reserve(extrusion_moves_filtered.size()); - extrusion_moves_merged.push_back(extrusion_moves_filtered.front()); - - for (const SegmentExtrusionMove& extrusion_move : extrusion_moves_filtered | ranges::views::drop(1)) - { - const Ratio previous_speed_factor = overhang_masks_[extrusion_moves_merged.back().speed_region_index].speed_ratio; - const Ratio next_speed_factor = overhang_masks_[extrusion_move.speed_region_index].speed_ratio; - - if (next_speed_factor == previous_speed_factor) - { - extrusion_moves_merged.back().position = extrusion_move.position; - } - else - { - extrusion_moves_merged.push_back(extrusion_move); - } - } - - // Finally, add extrusion moves - for (const SegmentExtrusionMove& extrusion_move : extrusion_moves_merged) - { - add_extrusion_move(extrusion_move.position, extrusion_move.speed_region_index); + addExtrusionMove(p, config, space_fill_type, flow, width_factor, spiralize, speed_factor, fan_speed, travel_to_z); } } @@ -816,7 +646,7 @@ void LayerPlan::addSkinExtrusion( const Ratio& width_factor, const bool spiralize, bool travel_to_z, - const PrintSegmentAttributes& print_attributes) + const OverrideAreas& override_areas) { // The line segment is wholly or partially in the skin area. The line is intersected // with the skin area into line segments. Each line segment left in this intersection @@ -867,14 +697,14 @@ void LayerPlan::addSkinExtrusion( addExtrusionMove( target_position, inside_skin ? skin_config : default_config, + override_areas, SpaceFillType::Polygons, flow, width_factor, spiralize, 1.0_r, GCodePathConfig::FAN_SPEED_DEFAULT, - travel_to_z, - print_attributes); + travel_to_z); travel_to_z = false; // Only travel to Z for the first sub-segment } @@ -935,7 +765,7 @@ void LayerPlan::addPolygon( const ForceRetract force_retract, bool scarf_seam, bool smooth_speed, - const PrintSegmentAttributes& print_attributes) + const OverrideAreas& override_areas) { constexpr bool is_closed = true; constexpr bool is_candidate_small_feature = false; @@ -956,12 +786,8 @@ void LayerPlan::addPolygon( is_candidate_small_feature, scarf_seam, smooth_speed, - [this, &config, &spiralize, &print_attributes]( + [this, &config, &spiralize, &override_areas]( const std::vector>& /*bridging_subsegments*/, - const PathAdapter& /*wall*/, - const size_t /*segment_index*/, - const Ratio& /*segment_start_ratio*/, - const Ratio& /*segment_end_ratio*/, const Point3LL& /*start*/, const Point3LL& end, const Ratio& speed_factor, @@ -973,7 +799,7 @@ void LayerPlan::addPolygon( constexpr double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT; constexpr bool travel_to_z = false; - addExtrusionMove(end, config, SpaceFillType::Polygons, actual_flow_ratio, line_width_ratio, spiralize, speed_factor, fan_speed, travel_to_z, print_attributes); + addExtrusionMove(end, config, override_areas, SpaceFillType::Polygons, actual_flow_ratio, line_width_ratio, spiralize, speed_factor, fan_speed, travel_to_z); }); @@ -991,7 +817,7 @@ void LayerPlan::addPolygonsByOptimizer( const Shape& polygons, const GCodePathConfig& config, const Settings& settings, - const PrintSegmentAttributes& print_attributes, + const OverrideAreas& override_areas, const ZSeamConfig& z_seam_config, coord_t wall_0_wipe_dist, bool spiralize, @@ -1046,7 +872,7 @@ void LayerPlan::addPolygonsByOptimizer( reverse_order, scarf_seam, smooth_speed, - print_attributes); + override_areas); } void LayerPlan::addInfillPolygonsByOptimizer( @@ -1095,12 +921,164 @@ void LayerPlan::addInfillPolygonsByOptimizer( static constexpr double max_non_bridge_line_volume = MM2INT(100); // limit to accumulated "volume" of non-bridge lines which is proportional to distance x extrusion rate +std::vector LayerPlan::splitExtrusionSegment(const Point3LL& start, const Point3LL& end, const std::vector& override_areas) +{ + if (override_areas.empty()) + { + return { PartialExtrusionSegment{ end, nullptr } }; + } + + // Useful values + const Point3LL vector = end - start; + const coord_t vector_length = vector.vSize(); + + if (vector_length <= EPSILON) + { + return { PartialExtrusionSegment{ end, nullptr } }; + } + + const float epsilon_factor = float{ EPSILON } / vector_length; + + // Utility functions + const auto make_partial_segment = [&override_areas](const Point3LL& p1, const std::optional area_index) -> PartialExtrusionSegment + { + if (area_index.has_value()) + { + const OverrideArea& area = override_areas.at(*area_index); + return PartialExtrusionSegment{ p1, &area }; + } + else + { + return PartialExtrusionSegment{ p1, nullptr }; + } + }; + + const auto get_position = [&start, &vector](const float factor) -> Point3LL + { + return start + factor * vector; + }; + + // Pre-calculate the intersections of the segment with all regions + std::vector> override_areas_intersections(override_areas.size()); + boost::dynamic_bitset areas_under_segments(override_areas.size()); // Stack of the areas currently under the part of the segment + for (const auto& [index, override_area] : override_areas | ranges::views::enumerate) + { + // Ignore intersections very close to the tips + std::vector intersections = override_area.area.intersectionsWithSegment(get_position(epsilon_factor).toPoint2LL(), get_position(1.0 - epsilon_factor).toPoint2LL()); + ranges::stable_sort(intersections); + + // Calculate whether this area is under the segment start + constexpr bool border_result = true; + const Point2LL first_subsegment_average_pos = get_position(intersections.empty() ? 0.5 : intersections.front() * 0.5).toPoint2LL(); + if (override_area.area.inside(first_subsegment_average_pos, border_result)) + { + areas_under_segments.set(index); + } + + override_areas_intersections[index] = std::move(intersections); + } + + const auto get_topmost_area = [&areas_under_segments]() -> std::optional + { + size_t current = areas_under_segments.find_first(); + if (current == areas_under_segments.npos) + { + // No bit is set, we are in the infinite area + return std::nullopt; + } + + while (true) + { + size_t next = areas_under_segments.find_next(current); + if (next == areas_under_segments.npos) + { + return current; + } + + current = next; + } + }; + + std::optional current_topmost_area = get_topmost_area(); + std::vector partial_extrusion_segments; + + // Now move along segment and split it where we cross relevant intersections + while (true) + { + if (ranges::all_of( + override_areas_intersections, + [](const std::vector& intersections) + { + return intersections.empty(); + })) + { + // All intersections have been treated, return the remaining segment part + partial_extrusion_segments.push_back(make_partial_segment(end, current_topmost_area)); + break; + } + + // Find the next intersection among all the lists + std::optional next_intersection; + size_t next_intersection_index; + for (const auto& [index, intersections] : override_areas_intersections | ranges::views::enumerate) + { + if (intersections.empty()) + { + continue; + } + + const float first_intersection = intersections.front(); + if (! next_intersection.has_value() || next_intersection.value() < first_intersection) + { + next_intersection = first_intersection; + next_intersection_index = index; + } + } + + // Erase the treated intersection, and flip the associated area containing bit + override_areas_intersections[next_intersection_index].erase(override_areas_intersections[next_intersection_index].begin()); + areas_under_segments.flip(next_intersection_index); + + // Now see if this intersection got us to a different topmost area, or if it happened under + std::optional new_topmost_area = get_topmost_area(); + if (new_topmost_area != current_topmost_area) + { + // We are either moving out of the area of moving in to a higher-level area, so end the current segment + const Point3LL next_intersection_position = get_position(*next_intersection); + partial_extrusion_segments.push_back(make_partial_segment(next_intersection_position, current_topmost_area)); + + current_topmost_area = new_topmost_area; + } + } + + // Filter out micro-segments + std::vector partial_extrusion_segments_filtered; + partial_extrusion_segments_filtered.reserve(partial_extrusion_segments.size()); + Point3LL current_position = start; + for (const PartialExtrusionSegment& extrusion_move : partial_extrusion_segments | ranges::views::drop_last(1)) + { + if ((extrusion_move.p1 - current_position).vSize2() >= MINIMUM_SQUARED_LINE_LENGTH) + { + partial_extrusion_segments_filtered.push_back(extrusion_move); + } + + current_position = extrusion_move.p1; + } + + if (partial_extrusion_segments_filtered.empty() || (partial_extrusion_segments.back().p1 - current_position).vSize2() >= MINIMUM_SQUARED_LINE_LENGTH) + { + partial_extrusion_segments_filtered.push_back(partial_extrusion_segments.back()); + } + else + { + partial_extrusion_segments_filtered.back().p1 = partial_extrusion_segments.back().p1; + } + + return partial_extrusion_segments_filtered; +} + void LayerPlan::addWallLine( const std::vector>& bridging_subsegments, - const PathAdapter& wall, - const size_t segment_index, - const Ratio& segment_start_ratio, - const Ratio& segment_end_ratio, const Point3LL& p0, const Point3LL& p1, const Settings& settings, @@ -1114,13 +1092,12 @@ void LayerPlan::addWallLine( Ratio speed_factor, double distance_to_bridge_start, const bool travel_to_z, - const PrintSegmentAttributes& print_attributes) + const OverrideAreas& override_areas) { constexpr double acceleration_segment_len = MM2INT(1); // accelerate using segments of this length constexpr double acceleration_factor = 0.75; // must be < 1, the larger the value, the slower the acceleration constexpr bool spiralize = false; - const coord_t min_bridge_line_len = std::max(EPSILON, settings.get("bridge_wall_min_length")); const Ratio bridge_wall_coast = settings.get("bridge_wall_coast"); Point3LL cur_point = p0; @@ -1172,14 +1149,14 @@ void LayerPlan::addWallLine( addExtrusionMove( segment_end + coast_dist * (cur_point - segment_end) / len, default_config, + override_areas, SpaceFillType::Polygons, segment_flow, width_factor, spiralize, speed_factor, GCodePathConfig::FAN_SPEED_DEFAULT, - travel_to_z, - print_attributes); + travel_to_z); } // then coast to start of bridge segment constexpr Ratio no_flow = 0.0_r; // Coasting has no flow rate. @@ -1188,17 +1165,17 @@ void LayerPlan::addWallLine( else { // no coasting required, just normal segment using non-bridge config - addExtrusionMoveWithGradualOverhang( + addExtrusionMove( segment_end, default_config, + override_areas, SpaceFillType::Polygons, segment_flow, width_factor, spiralize, speed_factor, GCodePathConfig::FAN_SPEED_DEFAULT, - travel_to_z, - print_attributes); + travel_to_z); } distance_to_bridge_start -= len; @@ -1206,17 +1183,17 @@ void LayerPlan::addWallLine( else { // no coasting required, just normal segment using non-bridge config - addExtrusionMoveWithGradualOverhang( + addExtrusionMove( segment_end, default_config, + override_areas, SpaceFillType::Polygons, segment_flow, width_factor, spiralize, speed_factor, GCodePathConfig::FAN_SPEED_DEFAULT, - travel_to_z, - print_attributes); + travel_to_z); } non_bridge_line_volume += (cur_point - segment_end).vSize() * segment_flow * width_factor * speed_factor * default_config.getSpeed(); cur_point = segment_end; @@ -1242,26 +1219,16 @@ void LayerPlan::addWallLine( if (use_skin_config(roofing_mask_, roofing_config)) { - addSkinExtrusion(p0, p1, roofing_mask_, roofing_config, default_config, flow, width_factor, spiralize, travel_to_z, print_attributes); + addSkinExtrusion(p0, p1, roofing_mask_, roofing_config, default_config, flow, width_factor, spiralize, travel_to_z, override_areas); } else if (bridge_wall_mask_.empty()) { // no bridges required - addExtrusionMoveWithGradualOverhang( - p1, - default_config, - SpaceFillType::Polygons, - flow, - width_factor, - spiralize, - speed_factor, - GCodePathConfig::FAN_SPEED_DEFAULT, - travel_to_z, - print_attributes); + addExtrusionMove(p1, default_config, override_areas, SpaceFillType::Polygons, flow, width_factor, spiralize, speed_factor, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); } else if (! bridging_subsegments.empty()) { - const PrintSegmentAttributes attributes_bridging = print_attributes | PrintSegmentAttribute::Bridging; + const PrintSegmentAttributes attributes_bridging = PrintSegmentAttribute::Bridging; // the line crosses the boundary between supported and non-supported regions so one or more bridges are required for (const std::tuple& bridging_subsegment : bridging_subsegments) @@ -1293,7 +1260,7 @@ void LayerPlan::addWallLine( } else if (use_skin_config(flooring_mask_, flooring_config)) { - addSkinExtrusion(p0, p1, flooring_mask_, flooring_config, default_config, flow, width_factor, spiralize, travel_to_z, print_attributes); + addSkinExtrusion(p0, p1, flooring_mask_, flooring_config, default_config, flow, width_factor, spiralize, travel_to_z, override_areas); } else { @@ -1565,10 +1532,6 @@ std::tuple LayerPlan::addSplitWall( const size_t pt_idx = point_index(wall, actual_point_index - 1); func_add_segment( bridging_subsections_per_segment[pt_idx], - wall, - pt_idx, - static_cast(segment_processed_distance) / line_length, - static_cast(segment_processed_distance + length_to_process) / line_length, split_origin, split_destination, accelerate_speed_factor * decelerate_speed_factor, @@ -2118,7 +2081,7 @@ void LayerPlan::addWall( const bool is_linked_path, const bool scarf_seam, const bool smooth_speed, - const PrintSegmentAttributes& print_attributes) + const OverrideAreas& override_areas) { if (wall.empty()) { @@ -2126,7 +2089,6 @@ void LayerPlan::addWall( } double non_bridge_line_volume = max_non_bridge_line_volume; // assume extruder is fully pressurised before first non-bridge line is output - const coord_t min_bridge_line_len = settings.get("bridge_wall_min_length"); const PathAdapter path_adapter(wall); const std::tuple add_wall_result = addWallWithScarfSeam( @@ -2142,10 +2104,6 @@ void LayerPlan::addWall( scarf_seam, smooth_speed, [&](const std::vector>& bridging_subsegments, - const PathAdapter& wall, - const size_t segment_index, - const Ratio& segment_start_ratio, - const Ratio& segment_end_ratio, const Point3LL& start, const Point3LL& end, const Ratio& speed_factor, @@ -2156,10 +2114,6 @@ void LayerPlan::addWall( { addWallLine( bridging_subsegments, - wall, - segment_index, - segment_start_ratio, - segment_end_ratio, start, end, settings, @@ -2173,7 +2127,7 @@ void LayerPlan::addWall( speed_factor, distance_to_bridge_start, travel_to_z, - print_attributes); + override_areas); }); if (wall.size() >= 2) @@ -2420,7 +2374,7 @@ void LayerPlan::addLinesByOptimizer( const Ratio flow_ratio, const std::optional near_start_location, const double fan_speed, - const PrintSegmentAttributes& print_attributes, + const OverrideAreas& override_areas, const bool reverse_print_direction, const std::unordered_multimap& order_requirements, const coord_t extra_inwards_start_move_length, @@ -2480,7 +2434,7 @@ void LayerPlan::addLinesByOptimizer( wipe_dist, flow_ratio, fan_speed, - print_attributes, + override_areas, extra_inwards_start_move_length, extra_inwards_end_move_length, extra_inwards_move_contour); @@ -2552,7 +2506,7 @@ void LayerPlan::addLinesInGivenOrder( const coord_t wipe_dist, const Ratio flow_ratio, const double fan_speed, - const PrintSegmentAttributes& print_attributes, + const OverrideAreas& override_areas, const coord_t extra_inwards_start_move_length, const coord_t extra_inwards_end_move_length, const MendedShape& extra_inwards_move_contour) @@ -2646,7 +2600,7 @@ void LayerPlan::addLinesInGivenOrder( constexpr bool spiralize = false; constexpr Ratio speed_factor = 1.0_r; constexpr bool travel_to_z = true; - addExtrusionMove(start, config, space_fill_type, flow, width_factor, spiralize, speed_factor, fan_speed, travel_to_z, print_attributes); + addExtrusionMove(start, config, override_areas, space_fill_type, flow, width_factor, spiralize, speed_factor, fan_speed, travel_to_z); } else { @@ -2684,7 +2638,7 @@ void LayerPlan::addLinesInGivenOrder( constexpr bool spiralize = false; constexpr Ratio speed_factor = 1.0_r; constexpr bool travel_to_z = true; - addExtrusionMove(p1, config, space_fill_type, flow_ratio, width_factor, spiralize, speed_factor, fan_speed, travel_to_z, print_attributes); + addExtrusionMove(p1, config, override_areas, space_fill_type, flow_ratio, width_factor, spiralize, speed_factor, fan_speed, travel_to_z); p0 = p1; } } @@ -2724,7 +2678,7 @@ void LayerPlan::addLinesInGivenOrder( constexpr bool spiralize = false; constexpr Ratio speed_factor = 1.0_r; constexpr bool travel_to_z = true; - addExtrusionMove(p1 + normal(p1 - p0, wipe_dist), config, space_fill_type, flow, width_factor, spiralize, speed_factor, fan_speed, travel_to_z, print_attributes); + addExtrusionMove(p1 + normal(p1 - p0, wipe_dist), config, override_areas, space_fill_type, flow, width_factor, spiralize, speed_factor, fan_speed, travel_to_z); } } } @@ -2742,9 +2696,9 @@ void LayerPlan::addPolygonsInGivenOrder( bool reverse_order, bool scarf_seam, bool smooth_speed, - const PrintSegmentAttributes& print_attributes) + const OverrideAreas& override_areas) { - const auto add_polygons = [this, &config, &settings, &wall_0_wipe_dist, &spiralize, &flow_ratio, &force_retract, &scarf_seam, &smooth_speed, &print_attributes]( + const auto add_polygons = [this, &config, &settings, &wall_0_wipe_dist, &spiralize, &flow_ratio, &force_retract, &scarf_seam, &smooth_speed, &override_areas]( const auto& iterator_begin, const auto& iterator_end) { @@ -2762,7 +2716,7 @@ void LayerPlan::addPolygonsInGivenOrder( force_retract, scarf_seam, smooth_speed, - print_attributes); + override_areas); } }; @@ -3031,7 +2985,8 @@ void LayerPlan::writeTravelSegment( travel_retraction_state = TravelRetractionState::Priming; writeTravelRelativeZ(gcode, priming_amounts->segment_split_position, speed, path.z_offset); - writeTravelSegment(travel_retraction_state, gcode, path, retraction_amounts, priming_amounts, speed, point_index); // Do the priming part now we have changed the state + writeTravelSegment(travel_retraction_state, gcode, path, retraction_amounts, priming_amounts, speed, point_index); // Do the priming part now we have changed the + // state } else { @@ -3057,6 +3012,17 @@ void LayerPlan::writeTravelSegment( } } +coord_t LayerPlan::calculateMaxOverhangLength() const +{ + return ranges::max( + extruder_plans_ + | ranges::views::transform( + [](const ExtruderPlan& extruder_plan) -> coord_t + { + return extruder_plan.calculateMaxOverhangLength(); + })); +} + void LayerPlan::sendLineTo(const GCodePath& path, const Point3LL& position, const double extrude_speed, const std::optional& line_thickness) { Application::getInstance().communication_->sendLineTo( @@ -3111,7 +3077,7 @@ void LayerPlan::addLinesMonotonic( const Ratio flow_ratio, const double fan_speed, const bool interlaced, - const PrintSegmentAttributes& print_attributes) + const OverrideAreas& override_areas) { const Shape exclude_areas = area.createTubeShape(exclude_distance, exclude_distance); const coord_t exclude_dist2 = exclude_distance * exclude_distance; @@ -3157,10 +3123,10 @@ void LayerPlan::addLinesMonotonic( order.optimize(); // Read out and process the monotonically ordered lines. - addLinesInGivenOrder(order.paths_, config, space_fill_type, wipe_dist, flow_ratio, fan_speed, print_attributes); + addLinesInGivenOrder(order.paths_, config, space_fill_type, wipe_dist, flow_ratio, fan_speed, override_areas); // Add all lines in the excluded areas the 'normal' way. - addLinesByOptimizer(left_over, config, space_fill_type, true, wipe_dist, flow_ratio, getLastPlannedPositionOrStartingPosition(), fan_speed, print_attributes); + addLinesByOptimizer(left_over, config, space_fill_type, true, wipe_dist, flow_ratio, getLastPlannedPositionOrStartingPosition(), fan_speed, override_areas); } void LayerPlan::spiralizeWallSlice( @@ -3578,7 +3544,7 @@ void LayerPlan::processFanSpeedAndMinimalLayerTime(Point2LL starting_position) } const FanSpeedLayerTimeSettings& settings = extruder_plan.fan_speed_layer_time_settings_; - const bool apply_minimum_layer_time_overhang = max_overhang_length_ > settings.cool_min_layer_time_overhang_min_segment_length; + const bool apply_minimum_layer_time_overhang = calculateMaxOverhangLength() > settings.cool_min_layer_time_overhang_min_segment_length; maximum_cool_min_layer_time = std::max(maximum_cool_min_layer_time, apply_minimum_layer_time_overhang ? settings.cool_min_layer_time_overhang : settings.cool_min_layer_time); @@ -4389,11 +4355,6 @@ void LayerPlan::setBridgeWallMask(const Shape& polys) bridge_wall_mask_bb_ = AABB(polys); } -void LayerPlan::setOverhangMasks(const std::vector& masks) -{ - overhang_masks_ = masks; -} - void LayerPlan::setSeamOverhangMask(const Shape& polys) { seam_overhang_mask_ = polys; @@ -4423,7 +4384,7 @@ template void LayerPlan::addLinesByOptimizer( const Ratio flow_ratio, const std::optional near_start_location, const double fan_speed, - const PrintSegmentAttributes& print_attributes, + const OverrideAreas& override_areas, const bool reverse_print_direction, const std::unordered_multimap& order_requirements, const coord_t extra_inwards_start_move_length, @@ -4439,7 +4400,7 @@ template void LayerPlan::addLinesByOptimizer( const Ratio flow_ratio, const std::optional near_start_location, const double fan_speed, - const PrintSegmentAttributes& print_attributes, + const OverrideAreas& override_areas, const bool reverse_print_direction, const std::unordered_multimap& order_requirements, const coord_t extra_inwards_start_move_length, diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index b8277a6a36..62d5dcdc1f 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -307,7 +307,7 @@ std::optional bridgeAngle( const unsigned layer_nr, const unsigned bridge_layer, const SupportLayer* support_layer, - Shape& supported_regions) + Shape& bridging_area) { const Settings& settings = mesh.settings; const bool bridge_settings_enabled = settings.get("bridge_settings_enabled"); @@ -322,10 +322,9 @@ std::optional bridgeAngle( // To detect if we have a bridge, first calculate the intersection of the current layer with the previous layer. // This gives us the islands that the layer rests on. - Shape islands; - Shape prev_layer_outline; // we also want the complete outline of the previous layer Shape prev_layer_infill; + Shape supported_regions; // include parts from all meshes for (const std::shared_ptr& mesh_ptr : storage.meshes) @@ -351,20 +350,13 @@ std::optional bridgeAngle( if (! boundary_box.hit(prev_layer_part.boundaryBox)) continue; - islands.push_back(skin_outline.intersection(solid_below)); + supported_regions.push_back(skin_outline.intersection(solid_below)); } } } - supported_regions = islands; if (support_layer) { - // add the regions of the skin that have support below them to supportedRegions - // but don't add these regions to islands because that can actually cause the code - // below to consider the skin a bridge when it isn't (e.g. a skin that is supported by - // the model on one side but the remainder of the skin is above support would look like - // a bridge because it would have two islands) - FIXME more work required here? - if (! support_layer->support_roof.empty()) { AABB support_roof_bb(support_layer->support_roof); @@ -406,6 +398,8 @@ std::optional bridgeAngle( return std::nullopt; } + bridging_area = skin_outline.difference(supported_regions.offset(EPSILON)); + prev_layer_infill = skin_outline.intersection(prev_layer_infill); const Ratio infill_ratio = prev_layer_infill.area() / (skin_outline.area() + 1); if (infill_ratio > 0.5) // In practice, the ratio should always be close to 0 or 1, so 0.5 should be good enough diff --git a/src/utils/CuraViz.cpp b/src/utils/CuraViz.cpp index b11d4e836c..4330797360 100644 --- a/src/utils/CuraViz.cpp +++ b/src/utils/CuraViz.cpp @@ -12,12 +12,15 @@ #include #include +#include #include #include #include "Application.h" #include "Slice.h" #include "geometry/MixedLinesSet.h" +#include "geometry/OpenLinesSet.h" +#include "geometry/OpenPolyline.h" #include "geometry/Polygon.h" #include "geometry/Shape.h" #include "settings/Settings.h" @@ -34,10 +37,10 @@ CuraViz::CuraViz() try { constexpr uint16_t port = 49673; - socket_.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), port)); + socket_.connect(boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address("127.0.0.1"), port)); spdlog::info("Connected to CuraViz"); } - catch (asio::system_error error) + catch (boost::system::system_error error) { socket_.close(); spdlog::warn("CuraViz could not connect to vizualiser: {}", error.what()); @@ -92,8 +95,8 @@ void CuraViz::send(const cura_viz::Message& message, const bool should_lock) lock = std::unique_lock(mutex_); } - asio::write(socket_, asio::buffer(&size, sizeof(size))); - asio::write(socket_, asio::buffer(serialized)); + boost::asio::write(socket_, boost::asio::buffer(&size, sizeof(size))); + boost::asio::write(socket_, boost::asio::buffer(serialized)); } CuraViz* CuraViz::getInstance() @@ -139,20 +142,59 @@ void CuraViz::setup(const Polyline& polyline, cura_viz::Polyline2LL* polyline_me for (auto iterator = polyline.beginSegments(); iterator != polyline.endSegments(); ++iterator) { - cura_viz::Segment2LL* segment_message = polyline_message->add_segments(); - segment_message->mutable_start()->set_x((*iterator).start.X); - segment_message->mutable_start()->set_y((*iterator).start.Y); - segment_message->mutable_end()->set_x((*iterator).end.X); - segment_message->mutable_end()->set_y((*iterator).end.Y); + setup((*iterator).start, (*iterator).end, polyline_message->add_segments()); } } +void CuraViz::setup(const ExtrusionLine& line, cura_viz::Polyline2LL* polyline_message) +{ + polyline_message->set_surface(false); + + if (line.junctions_.empty()) + { + return; + } + + Point2LL p0 = line.junctions_[0].p_; + for (const ExtrusionJunction& junction : line.junctions_ | ranges::views::drop(1)) + { + const Point2LL& p1 = junction.p_; + setup(p0, p1, polyline_message->add_segments()); + p0 = p1; + } +} + +void CuraViz::setup(const Point2LL& start, const Point2LL& end, cura_viz::Segment2LL* segment_message) +{ + segment_message->mutable_start()->set_x(start.X); + segment_message->mutable_start()->set_y(start.Y); + segment_message->mutable_end()->set_x(end.X); + segment_message->mutable_end()->set_y(end.Y); +} + void CuraViz::send(const Point2LL& point, const std::string& name, const std::string& step_name) { MessageToSend message(step_name); setup(point, message.addGeometricElement(name)); } +void CuraViz::send(const Point2LL& start, const Point2LL& end, const std::string& name, const std::string& step_name) +{ + const OpenPolyline segment({ start, end }); + send(segment, name, step_name); +} + +void CuraViz::send(const Point3LL& start, const Point3LL& end, const std::string& name, const std::string& step_name) +{ + send(start.toPoint2LL(), end.toPoint2LL(), name, step_name); +} + +void CuraViz::send(const Polyline& line, const std::string& name, const std::string& step_name) +{ + MessageToSend message(step_name); + setup(line, message.addGeometricElement(name)->mutable_data()->mutable_lines_set2ll()->add_lines()); +} + void CuraViz::send(const Shape& shape, const std::string& name, const std::string& step_name) { MessageToSend message(step_name); @@ -168,6 +210,15 @@ void CuraViz::send(const std::vector& shapes, const std::string& name, co } } +void CuraViz::send(const OpenLinesSet& lines, const std::string& name, const std::string& step_name) +{ + MessageToSend message(step_name); + for (const auto& [line_index, line] : lines.getLines() | ranges::views::enumerate) + { + setup(line, message.addGeometricElement(fmt::format("{}_{}", name, line_index))->mutable_data()->mutable_lines_set2ll()->add_lines()); + } +} + void CuraViz::send(const MixedLinesSet& lines_set, const std::string& name, const std::string& step_name) { MessageToSend message(step_name); @@ -183,6 +234,19 @@ void CuraViz::send(const std::vector& lines_sets, const std::stri } } +void CuraViz::send(const std::vector& lines, const std::string& name, const std::string& step_name) +{ + MessageToSend message(step_name); + for (const auto& [index, lines_set] : lines | ranges::views::enumerate) + { + cura_viz::GeometricElement* geometric_element = message.addGeometricElement(fmt::format("{}_{}", name, index)); + for (const ExtrusionLine& line : lines_set) + { + setup(line, geometric_element->mutable_data()->mutable_lines_set2ll()->add_lines()); + } + } +} + CuraViz::MessageToSend::MessageToSend(const std::string& step_name) : message_(std::make_shared()) {