diff --git a/include/FffGcodeWriter.h b/include/FffGcodeWriter.h index ac0a3ca274..2b8cc92399 100644 --- a/include/FffGcodeWriter.h +++ b/include/FffGcodeWriter.h @@ -144,6 +144,16 @@ class FffGcodeWriter : public NoCopy TimeKeeper::RegisteredTimes stages_times; }; + struct RoofingFlooringSettingsNames + { + std::string extruder_nr; + std::string pattern; + std::string monotonic; + }; + + static const RoofingFlooringSettingsNames roofing_settings_names; + static const RoofingFlooringSettingsNames flooring_settings_names; + /*! * \brief Set the FffGcodeWriter::fan_speed_layer_time_settings by * retrieving all settings from the global/per-meshgroup settings. @@ -501,7 +511,7 @@ class FffGcodeWriter : public NoCopy const SkinPart& skin_part) const; /*! - * Add the roofing which is the area inside the innermost skin inset which has air 'directly' above + * Add the roofing/flooring which is the area inside the innermost skin inset which has air 'directly' above or below * * \param[in] storage where the slice data is stored. * \param gcode_layer The initial planning of the gcode of the layer. @@ -511,13 +521,15 @@ class FffGcodeWriter : public NoCopy * \param skin_part The skin part for which to create gcode * \param[out] added_something Whether this function added anything to the layer plan */ - void processRoofing( + void processRoofingFlooring( const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, - const MeshPathConfigs& mesh_config, - const SkinPart& skin_part, + const RoofingFlooringSettingsNames& settings_names, + const Shape& fill, + const GCodePathConfig& config, + const std::vector& angles, bool& added_something) const; /*! diff --git a/include/InsetOrderOptimizer.h b/include/InsetOrderOptimizer.h index 80c0856f3d..2ab4e3454b 100644 --- a/include/InsetOrderOptimizer.h +++ b/include/InsetOrderOptimizer.h @@ -47,6 +47,8 @@ class InsetOrderOptimizer const GCodePathConfig& inset_X_default_config, const GCodePathConfig& inset_0_roofing_config, const GCodePathConfig& inset_X_roofing_config, + const GCodePathConfig& inset_0_flooring_config, + const GCodePathConfig& inset_X_flooring_config, const GCodePathConfig& inset_0_bridge_config, const GCodePathConfig& inset_X_bridge_config, const bool retract_before_outer_wall, @@ -101,6 +103,8 @@ class InsetOrderOptimizer const GCodePathConfig& inset_X_default_config_; const GCodePathConfig& inset_0_roofing_config_; const GCodePathConfig& inset_X_roofing_config_; + const GCodePathConfig& inset_0_flooring_config_; + const GCodePathConfig& inset_X_flooring_config_; const GCodePathConfig& inset_0_bridge_config_; const GCodePathConfig& inset_X_bridge_config_; const bool retract_before_outer_wall_; diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 47cea1aa19..0948efd53e 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -117,7 +117,8 @@ class LayerPlan : public NoCopy Shape bridge_wall_mask_; //!< The regions of a layer part that are not supported, used for bridging Shape overhang_mask_; //!< The regions of a layer part where the walls overhang 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 + 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 min_layer_time_used = false; //!< Wether or not the minimum layer time (cool_min_layer_time) was actually used in this layerplan. @@ -320,6 +321,13 @@ class LayerPlan : public NoCopy */ void setRoofingMask(const Shape& polys); + /*! + * Set flooring_mask. + * + * \param shape The areas of the part currently being processed that will require flooring. + */ + void setFlooringMask(const Shape& shape); + /*! * Travel to a certain point, with all of the procedures necessary to do so. * @@ -473,6 +481,8 @@ class LayerPlan : public NoCopy * that are not spanning a bridge or are exposed to air. * \param roofing_config The config with which to print the wall lines * that are exposed to air. + * \param flooring_config The config with which to print the wall lines + * that are exposed to air below. * \param bridge_config The config with which to print the wall lines that * are spanning a bridge. * \param flow The ratio with which to multiply the extrusion amount. @@ -491,6 +501,7 @@ class LayerPlan : public NoCopy const Settings& settings, const GCodePathConfig& default_config, const GCodePathConfig& roofing_config, + const GCodePathConfig& flooring_config, const GCodePathConfig& bridge_config, double flow, const Ratio width_factor, @@ -508,6 +519,8 @@ class LayerPlan : public NoCopy * that are not spanning a bridge or are exposed to air. * \param roofing_config The config with which to print the wall lines * that are exposed to air. + * \param flooring_config The config with which to print the wall lines + * that are exposed to air below. * \param wall_0_wipe_dist The distance to travel along the wall after it * has been laid down, in order to wipe the start and end of the wall * \param flow_ratio The ratio with which to multiply the extrusion amount. @@ -520,6 +533,7 @@ class LayerPlan : public NoCopy const Settings& settings, const GCodePathConfig& default_config, const GCodePathConfig& roofing_config, + const GCodePathConfig& flooring_config, const GCodePathConfig& bridge_config, coord_t wall_0_wipe_dist, double flow_ratio, @@ -534,6 +548,8 @@ class LayerPlan : public NoCopy * that are not spanning a bridge or are exposed to air. * \param roofing_config The config with which to print the wall lines * that are exposed to air. + * \param flooring_config The config with which to print the wall lines + * that are exposed to air below. * \param bridge_config The config with which to print the wall lines that * are spanning a bridge * \param wall_0_wipe_dist The distance to travel along the wall after it @@ -554,6 +570,7 @@ class LayerPlan : public NoCopy const Settings& settings, const GCodePathConfig& default_config, const GCodePathConfig& roofing_config, + const GCodePathConfig& flooring_config, const GCodePathConfig& bridge_config, coord_t wall_0_wipe_dist, double flow_ratio, @@ -579,7 +596,9 @@ class LayerPlan : public NoCopy * \param default_config The config with which to print the wall lines * that are not spanning a bridge or are exposed to air. * \param roofing_config The config with which to print the wall lines - * that are exposed to air. + * that are exposed to air above. + * \param flooring_config The config with which to print the wall lines + * that are exposed to air below. * \param bridge_config The config with which to print the wall lines that are spanning a bridge * \param z_seam_config Optional configuration for z-seam * \param wall_0_wipe_dist The distance to travel along each wall after it has been laid down, in order to wipe the start and end of the wall together @@ -592,6 +611,7 @@ class LayerPlan : public NoCopy const Settings& settings, const GCodePathConfig& default_config, const GCodePathConfig& roofing_config, + const GCodePathConfig& flooring_config, const GCodePathConfig& bridge_config, const ZSeamConfig& z_seam_config = ZSeamConfig(), coord_t wall_0_wipe_dist = 0, diff --git a/include/settings/MeshPathConfigs.h b/include/settings/MeshPathConfigs.h index 61c993b9a8..e6cf6ca2ad 100644 --- a/include/settings/MeshPathConfigs.h +++ b/include/settings/MeshPathConfigs.h @@ -17,6 +17,8 @@ struct MeshPathConfigs GCodePathConfig insetX_config{}; GCodePathConfig inset0_roofing_config{}; GCodePathConfig insetX_roofing_config{}; + GCodePathConfig inset0_flooring_config{}; + GCodePathConfig insetX_flooring_config{}; GCodePathConfig bridge_inset0_config{}; GCodePathConfig bridge_insetX_config{}; GCodePathConfig skin_config{}; @@ -24,6 +26,7 @@ struct MeshPathConfigs GCodePathConfig bridge_skin_config2{}; // used for second bridge layer GCodePathConfig bridge_skin_config3{}; // used for third bridge layer GCodePathConfig roofing_config{}; + GCodePathConfig flooring_config{}; std::vector infill_config{}; GCodePathConfig ironing_config{}; diff --git a/include/skin.h b/include/skin.h index 558ca59f64..db1140fa09 100644 --- a/include/skin.h +++ b/include/skin.h @@ -4,6 +4,8 @@ #ifndef SKIN_H #define SKIN_H +#include + #include "settings/types/LayerIndex.h" #include "utils/Coord_t.h" @@ -127,12 +129,12 @@ class SkinInfillAreaComputation void generateInfill(SliceLayerPart& part); /*! - * Remove the areas which are 'directly' under air from the \ref SkinPart::inner_infill and - * save them in the \ref SkinPart::roofing_fill of the \p part. + * Remove the areas which are 'directly' under/over air from the \ref SkinPart::inner_infill and + * save them in the \ref SkinPart::roofing_fill and \ref SkinPart::flooring_fill of the \p part. * - * \param[in,out] part Where to get the SkinParts to get the outline info from and to store the roofing areas + * \param[in,out] part Where to get the SkinParts to get the outline info from and to store the roofing/flooring areas */ - void generateRoofingFillAndSkinFill(SliceLayerPart& part); + void generateSkinRoofingFlooringFill(SliceLayerPart& part); /*! * Generate the top and bottom-most surfaces of the given \p part, i.e. the surfaces that have nothing above or below @@ -152,8 +154,9 @@ class SkinInfillAreaComputation * * \param part Where to get the SkinParts to get the outline info from * \param flooring_layer_count The number of layers below the layer which we are looking into + * \return The area that contains mesh parts below, or nullopt if the build plate is below, which actually means everything is considered supported */ - Shape generateFilledAreaBelow(SliceLayerPart& part, size_t flooring_layer_count); + std::optional generateFilledAreaBelow(SliceLayerPart& part, size_t flooring_layer_count); protected: LayerIndex layer_nr_; //!< The index of the layer for which to generate the skins and infill. diff --git a/include/sliceDataStorage.h b/include/sliceDataStorage.h index cb35355b4a..590ce5297a 100644 --- a/include/sliceDataStorage.h +++ b/include/sliceDataStorage.h @@ -45,6 +45,7 @@ class SkinPart //!< roofing and non-roofing. Shape skin_fill; //!< The part of the skin which is not roofing. Shape roofing_fill; //!< The inner infill which has air directly above + Shape flooring_fill; //!< The inner infill which has air directly below }; /*! @@ -309,6 +310,7 @@ class SliceMeshStorage std::vector infill_angles; //!< a list of angle values which is cycled through to determine the infill angle of each layer std::vector roofing_angles; //!< a list of angle values which is cycled through to determine the roofing angle of each layer + std::vector flooring_angles; //!< a list of angle values which is cycled through to determine the flooring angle of each layer std::vector skin_angles; //!< a list of angle values which is cycled through to determine the skin angle of each layer std::vector overhang_areas; //!< For each layer the areas that are classified as overhang on this mesh. std::vector full_overhang_areas; //!< For each layer the full overhang without the tangent of the overhang angle removed, such that the overhang area adjoins the diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 58e72d8019..c992bd7ffd 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -43,6 +43,9 @@ namespace cura { constexpr coord_t EPSILON = 5; +const FffGcodeWriter::RoofingFlooringSettingsNames FffGcodeWriter::roofing_settings_names = { "roofing_extruder_nr", "roofing_pattern", "roofing_monotonic" }; +const FffGcodeWriter::RoofingFlooringSettingsNames FffGcodeWriter::flooring_settings_names = { "flooring_extruder_nr", "flooring_pattern", "flooring_monotonic" }; + FffGcodeWriter::FffGcodeWriter() : max_object_height(0) , layer_plan_buffer(gcode) @@ -480,6 +483,17 @@ void FffGcodeWriter::setInfillAndSkinAngles(SliceMeshStorage& mesh) } } + if (mesh.flooring_angles.size() == 0) + { + mesh.flooring_angles = mesh.settings.get>("flooring_angles"); + if (mesh.flooring_angles.size() == 0) + { + // user has not specified any infill angles so use defaults + mesh.flooring_angles.push_back(45); + mesh.flooring_angles.push_back(135); + } + } + if (mesh.skin_angles.size() == 0) { mesh.skin_angles = mesh.settings.get>("skin_angles"); @@ -705,6 +719,8 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) config, config, config, + config, + config, retract_before_outer_wall, wipe_dist, wipe_dist, @@ -866,6 +882,8 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) config, config, config, + config, + config, retract_before_outer_wall, wipe_dist, wipe_dist, @@ -1035,6 +1053,8 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) config, config, config, + config, + config, retract_before_outer_wall, wipe_dist, wipe_dist, @@ -2732,6 +2752,8 @@ bool FffGcodeWriter::processSingleLayerInfill( mesh_config.infill_config[0], mesh_config.infill_config[0], mesh_config.infill_config[0], + mesh_config.infill_config[0], + mesh_config.infill_config[0], retract_before_outer_wall, wipe_dist, wipe_dist, @@ -3098,6 +3120,8 @@ bool FffGcodeWriter::processInsets( gcode_layer.setOverhangMask(get_overhang_region(mesh.settings.get("wall_overhang_angle"))); gcode_layer.setSeamOverhangMask(get_overhang_region(mesh.settings.get("seam_overhang_angle"))); + const auto wall_line_width_0 = mesh.settings.get("wall_line_width_0"); + 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")); @@ -3109,7 +3133,6 @@ bool FffGcodeWriter::processInsets( return roofing_mask; } - const auto wall_line_width_0 = mesh.settings.get("wall_line_width_0"); for (const auto& layer_part : mesh.layers[gcode_layer.getLayerNr() + roofing_layer_count].parts) { if (boundaryBox.hit(layer_part.boundaryBox)) @@ -3118,9 +3141,32 @@ bool FffGcodeWriter::processInsets( } } return roofing_mask; - }(); + }; + + gcode_layer.setRoofingMask(roofing_mask_fn()); + + 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")); + + auto flooring_mask = storage.getMachineBorder(mesh.settings.get("wall_0_extruder_nr").extruder_nr_); + + if (gcode_layer.getLayerNr() - flooring_layer_count < 0) + { + return flooring_mask; + } + + for (const auto& layer_part : mesh.layers[gcode_layer.getLayerNr() - flooring_layer_count].parts) + { + if (boundaryBox.hit(layer_part.boundaryBox)) + { + flooring_mask = flooring_mask.difference(layer_part.outline.offset(-wall_line_width_0 / 4)); + } + } + return flooring_mask; + }; - gcode_layer.setRoofingMask(roofing_mask_fn); + gcode_layer.setFlooringMask(flooring_mask_fn()); } else { @@ -3132,6 +3178,8 @@ bool FffGcodeWriter::processInsets( 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 (spiralize && extruder_nr == mesh.settings.get("wall_0_extruder_nr").extruder_nr_ && ! part.spiral_wall.empty()) @@ -3149,7 +3197,7 @@ bool FffGcodeWriter::processInsets( else { // Print the spiral walls of other parts as single walls without Z gradient. - gcode_layer.addWalls(part.spiral_wall, mesh.settings, mesh_config.inset0_config, mesh_config.inset0_config, mesh_config.inset0_config); + gcode_layer.addWalls(part.spiral_wall, mesh.settings, mesh_config.inset0_config, mesh_config.inset0_config, mesh_config.inset0_config, mesh_config.inset0_config); } } else @@ -3174,6 +3222,8 @@ bool FffGcodeWriter::processInsets( 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("travel_retract_before_outer_wall"), @@ -3236,9 +3286,12 @@ bool FffGcodeWriter::processSkin( { const size_t top_bottom_extruder_nr = mesh.settings.get("top_bottom_extruder_nr").extruder_nr_; const size_t roofing_extruder_nr = mesh.settings.get("roofing_extruder_nr").extruder_nr_; + const size_t flooring_extruder_nr = mesh.settings.get("flooring_extruder_nr").extruder_nr_; const size_t wall_0_extruder_nr = mesh.settings.get("wall_0_extruder_nr").extruder_nr_; const size_t roofing_layer_count = std::min(mesh.settings.get("roofing_layer_count"), mesh.settings.get("top_layers")); - if (extruder_nr != top_bottom_extruder_nr && extruder_nr != wall_0_extruder_nr && (extruder_nr != roofing_extruder_nr || roofing_layer_count <= 0)) + const size_t flooring_layer_count = std::min(mesh.settings.get("flooring_layer_count"), mesh.settings.get("bottom_layers")); + if (extruder_nr != top_bottom_extruder_nr && extruder_nr != wall_0_extruder_nr && (extruder_nr != roofing_extruder_nr || roofing_layer_count == 0) + && (extruder_nr != flooring_extruder_nr || flooring_layer_count == 0)) { return false; } @@ -3273,51 +3326,60 @@ bool FffGcodeWriter::processSkinPart( gcode_layer.mode_skip_agressive_merge_ = true; - processRoofing(storage, gcode_layer, mesh, extruder_nr, mesh_config, skin_part, added_something); + processRoofingFlooring( + storage, + gcode_layer, + mesh, + extruder_nr, + roofing_settings_names, + skin_part.roofing_fill, + mesh_config.roofing_config, + mesh.roofing_angles, + added_something); + processRoofingFlooring( + storage, + gcode_layer, + mesh, + extruder_nr, + flooring_settings_names, + skin_part.flooring_fill, + mesh_config.flooring_config, + mesh.flooring_angles, + added_something); processTopBottom(storage, gcode_layer, mesh, extruder_nr, mesh_config, skin_part, added_something); gcode_layer.mode_skip_agressive_merge_ = false; return added_something; } -void FffGcodeWriter::processRoofing( +void FffGcodeWriter::processRoofingFlooring( const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, - const MeshPathConfigs& mesh_config, - const SkinPart& skin_part, + const RoofingFlooringSettingsNames& settings_names, + const Shape& fill, + const GCodePathConfig& config, + const std::vector& angles, bool& added_something) const { - const size_t roofing_extruder_nr = mesh.settings.get("roofing_extruder_nr").extruder_nr_; - if (extruder_nr != roofing_extruder_nr) + const size_t skin_extruder_nr = mesh.settings.get(settings_names.extruder_nr).extruder_nr_; + if (extruder_nr != skin_extruder_nr) { return; } - const EFillMethod pattern = mesh.settings.get("roofing_pattern"); + const EFillMethod pattern = mesh.settings.get(settings_names.pattern); AngleDegrees roofing_angle = 45; - if (mesh.roofing_angles.size() > 0) + if (angles.size() > 0) { - roofing_angle = mesh.roofing_angles.at(gcode_layer.getLayerNr() % mesh.roofing_angles.size()); + roofing_angle = angles.at(gcode_layer.getLayerNr() % angles.size()); } 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 bool monotonic = mesh.settings.get("roofing_monotonic"); - processSkinPrintFeature( - storage, - gcode_layer, - mesh, - extruder_nr, - skin_part.roofing_fill, - mesh_config.roofing_config, - pattern, - roofing_angle, - skin_overlap, - skin_density, - monotonic, - added_something); + const bool monotonic = mesh.settings.get(settings_names.monotonic); + processSkinPrintFeature(storage, gcode_layer, mesh, extruder_nr, fill, config, pattern, roofing_angle, skin_overlap, skin_density, monotonic, added_something); } void FffGcodeWriter::processTopBottom( @@ -3584,38 +3646,36 @@ void FffGcodeWriter::processSkinPrintFeature( if (! skin_paths.empty()) { // Add skin-walls a.k.a. skin-perimeters, skin-insets. - const size_t skin_extruder_nr = mesh.settings.get("top_bottom_extruder_nr").extruder_nr_; - if (extruder_nr == skin_extruder_nr) - { - constexpr bool retract_before_outer_wall = false; - constexpr coord_t wipe_dist = 0; - const ZSeamConfig z_seam_config( - mesh.settings.get("z_seam_type"), - mesh.getZSeamHint(), - mesh.settings.get("z_seam_corner"), - config.getLineWidth() * 2); - InsetOrderOptimizer wall_orderer( - *this, - storage, - gcode_layer, - mesh.settings, - extruder_nr, - config, - config, - config, - config, - config, - config, - retract_before_outer_wall, - wipe_dist, - wipe_dist, - skin_extruder_nr, - skin_extruder_nr, - z_seam_config, - skin_paths, - mesh.bounding_box.flatten().getMiddle()); - added_something |= wall_orderer.addToLayer(); - } + constexpr bool retract_before_outer_wall = false; + constexpr coord_t wipe_dist = 0; + const ZSeamConfig z_seam_config( + mesh.settings.get("z_seam_type"), + mesh.getZSeamHint(), + mesh.settings.get("z_seam_corner"), + config.getLineWidth() * 2); + InsetOrderOptimizer wall_orderer( + *this, + storage, + gcode_layer, + mesh.settings, + extruder_nr, + config, + config, + config, + config, + config, + config, + config, + config, + retract_before_outer_wall, + wipe_dist, + wipe_dist, + extruder_nr, + extruder_nr, + z_seam_config, + skin_paths, + mesh.bounding_box.flatten().getMiddle()); + added_something |= wall_orderer.addToLayer(); } if (! skin_polygons.empty()) { @@ -3898,6 +3958,8 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer config, config, config, + config, + config, retract_before_outer_wall, wipe_dist, wipe_dist, @@ -4084,6 +4146,8 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer config, config, config, + config, + config, retract_before_outer_wall, wipe_dist, wipe_dist, @@ -4220,6 +4284,8 @@ bool FffGcodeWriter::addSupportRoofsToGCode(const SliceDataStorage& storage, con config, config, config, + config, + config, retract_before_outer_wall, wipe_dist, wipe_dist, @@ -4334,6 +4400,8 @@ bool FffGcodeWriter::addSupportBottomsToGCode(const SliceDataStorage& storage, L config, config, config, + config, + config, retract_before_outer_wall, wipe_dist, wipe_dist, diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index 7c8723bb95..78d671c40c 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -42,6 +42,8 @@ InsetOrderOptimizer::InsetOrderOptimizer( const GCodePathConfig& inset_X_default_config, const GCodePathConfig& inset_0_roofing_config, const GCodePathConfig& inset_X_roofing_config, + const GCodePathConfig& inset_0_flooring_config, + const GCodePathConfig& inset_X_flooring_config, const GCodePathConfig& inset_0_bridge_config, const GCodePathConfig& inset_X_bridge_config, const bool retract_before_outer_wall, @@ -65,6 +67,8 @@ InsetOrderOptimizer::InsetOrderOptimizer( , inset_X_default_config_(inset_X_default_config) , inset_0_roofing_config_(inset_0_roofing_config) , inset_X_roofing_config_(inset_X_roofing_config) + , inset_0_flooring_config_(inset_0_flooring_config) + , inset_X_flooring_config_(inset_X_flooring_config) , inset_0_bridge_config_(inset_0_bridge_config) , inset_X_bridge_config_(inset_X_bridge_config) , retract_before_outer_wall_(retract_before_outer_wall) @@ -152,6 +156,7 @@ bool InsetOrderOptimizer::addToLayer() const bool is_gap_filler = path.vertices_->is_odd_; const GCodePathConfig& default_config = is_outer_wall ? inset_0_default_config_ : inset_X_default_config_; const GCodePathConfig& roofing_config = is_outer_wall ? inset_0_roofing_config_ : inset_X_roofing_config_; + const GCodePathConfig& flooring_config = is_outer_wall ? inset_0_flooring_config_ : inset_X_flooring_config_; const GCodePathConfig& bridge_config = is_outer_wall ? inset_0_bridge_config_ : inset_X_bridge_config_; const coord_t wipe_dist = is_outer_wall && ! is_gap_filler ? wall_0_wipe_dist_ : wall_x_wipe_dist_; const bool retract_before = is_outer_wall ? retract_before_outer_wall_ : false; @@ -171,6 +176,7 @@ bool InsetOrderOptimizer::addToLayer() settings_, default_config, roofing_config, + flooring_config, bridge_config, wipe_dist, flow, diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index de3594c58d..4940a57f75 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -716,6 +716,7 @@ void LayerPlan::addWallLine( const Settings& settings, const GCodePathConfig& default_config, const GCodePathConfig& roofing_config, + const GCodePathConfig& flooring_config, const GCodePathConfig& bridge_config, double flow, const Ratio width_factor, @@ -836,41 +837,41 @@ void LayerPlan::addWallLine( } }; - const auto use_roofing_config = [&]() -> bool + const auto use_skin_config = [&default_config, &p0, &p1](const Shape& mask, const GCodePathConfig& config) -> bool { - if (roofing_config == default_config) + if (config == default_config) { // if the roofing config and normal config are the same any way there is no need to check // what part of the line segment will be printed with what config. return false; } - return PolygonUtils::polygonCollidesWithLineSegment(roofing_mask_, p0.toPoint2LL(), p1.toPoint2LL()) || roofing_mask_.inside(p1.toPoint2LL(), true); - }(); + return PolygonUtils::polygonCollidesWithLineSegment(mask, p0.toPoint2LL(), p1.toPoint2LL()) || mask.inside(p1.toPoint2LL(), true); + }; - if (use_roofing_config) + const auto add_skin_extrusion = [&](const Shape& mask, const GCodePathConfig& config) -> void { - // The line segment is wholly or partially in the roofing area. The line is intersected - // with the roofing area into line segments. Each line segment left in this intersection - // will be printed using the roofing config, all removed segments will be printed using + // 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 + // will be printed using the skin config, all removed segments will be printed using // the default_config. Since the original line segment was straight we can simply print // to the first and last point of the intersected line segments alternating between - // roofing and default_config's. + // skin and default_config's. OpenLinesSet line_polys; line_polys.addSegment(p0.toPoint2LL(), p1.toPoint2LL()); constexpr bool restitch = false; // only a single line doesn't need stitching - auto roofing_line_segments = roofing_mask_.intersection(line_polys, restitch); + auto skin_line_segments = mask.intersection(line_polys, restitch); - if (roofing_line_segments.empty()) + if (skin_line_segments.empty()) { - // roofing_line_segments should never be empty since we already checked that the line segment - // intersects with the roofing area. But if it is empty then just print the line segment + // skin_line_segments should never be empty since we already checked that the line segment + // intersects with the skin area. But if it is empty then just print the line segment // using the default_config. addExtrusionMove(p1, default_config, SpaceFillType::Polygons, flow, width_factor, spiralize, 1.0_r, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); } else { // reorder all the line segments so all lines start at p0 and end at p1 - for (auto& line_poly : roofing_line_segments) + for (auto& line_poly : skin_line_segments) { const Point2LL& line_p0 = line_poly.front(); const Point2LL& line_p1 = line_poly.back(); @@ -880,15 +881,15 @@ void LayerPlan::addWallLine( } } std::sort( - roofing_line_segments.begin(), - roofing_line_segments.end(), + skin_line_segments.begin(), + skin_line_segments.end(), [&](auto& a, auto& b) { return vSize2(a.front() - p0) < vSize2(b.front() - p0); }); // add intersected line segments, alternating between roofing and default_config - for (const auto& line_poly : roofing_line_segments) + for (const auto& line_poly : skin_line_segments) { // This is only relevant for the very fist iteration of the loop // if the start of the line segment is not at minimum distance from p0 @@ -906,15 +907,24 @@ void LayerPlan::addWallLine( travel_to_z); } - addExtrusionMove(line_poly.back(), roofing_config, SpaceFillType::Polygons, flow, width_factor, spiralize, 1.0_r, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); + addExtrusionMove(line_poly.back(), config, SpaceFillType::Polygons, flow, width_factor, spiralize, 1.0_r, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); } // if the last point is not yet at a minimum distance from p1 then add a move to p1 - if (vSize2(roofing_line_segments.back().back() - p1) > min_line_len * min_line_len) + if (vSize2(skin_line_segments.back().back() - p1) > min_line_len * min_line_len) { addExtrusionMove(p1, default_config, SpaceFillType::Polygons, flow, width_factor, spiralize, 1.0_r, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); } } + }; + + if (use_skin_config(roofing_mask_, roofing_config)) + { + add_skin_extrusion(roofing_mask_, roofing_config); + } + else if (use_skin_config(flooring_mask_, flooring_config)) + { + add_skin_extrusion(flooring_mask_, flooring_config); } else if (bridge_wall_mask_.empty()) { @@ -1026,6 +1036,7 @@ void LayerPlan::addWall( const Settings& settings, const GCodePathConfig& default_config, const GCodePathConfig& roofing_config, + const GCodePathConfig& flooring_config, const GCodePathConfig& bridge_config, coord_t wall_0_wipe_dist, double flow_ratio, @@ -1054,7 +1065,20 @@ void LayerPlan::addWall( constexpr bool is_closed = true; constexpr bool is_reversed = false; constexpr bool is_linked_path = false; - addWall(ewall, start_idx, settings, default_config, roofing_config, bridge_config, wall_0_wipe_dist, flow_ratio, always_retract, is_closed, is_reversed, is_linked_path); + addWall( + ewall, + start_idx, + settings, + default_config, + roofing_config, + flooring_config, + bridge_config, + wall_0_wipe_dist, + flow_ratio, + always_retract, + is_closed, + is_reversed, + is_linked_path); } template @@ -1605,6 +1629,7 @@ void LayerPlan::addWall( const Settings& settings, const GCodePathConfig& default_config, const GCodePathConfig& roofing_config, + const GCodePathConfig& flooring_config, const GCodePathConfig& bridge_config, coord_t wall_0_wipe_dist, const double flow_ratio, @@ -1651,6 +1676,7 @@ void LayerPlan::addWall( settings, default_config, roofing_config, + flooring_config, bridge_config, actual_flow_ratio, line_width_ratio, @@ -1699,6 +1725,7 @@ void LayerPlan::addWalls( const Settings& settings, const GCodePathConfig& default_config, const GCodePathConfig& roofing_config, + const GCodePathConfig& flooring_config, const GCodePathConfig& bridge_config, const ZSeamConfig& z_seam_config, coord_t wall_0_wipe_dist, @@ -1714,7 +1741,7 @@ void LayerPlan::addWalls( orderOptimizer.optimize(); for (const PathOrdering& path : orderOptimizer.paths_) { - addWall(*path.vertices_, path.start_vertex_, settings, default_config, roofing_config, bridge_config, wall_0_wipe_dist, flow_ratio, always_retract); + addWall(*path.vertices_, path.start_vertex_, settings, default_config, roofing_config, flooring_config, bridge_config, wall_0_wipe_dist, flow_ratio, always_retract); } } @@ -3190,6 +3217,11 @@ void LayerPlan::setRoofingMask(const Shape& polys) roofing_mask_ = polys; } +void LayerPlan::setFlooringMask(const Shape& shape) +{ + flooring_mask_ = shape; +} + template void LayerPlan::addLinesByOptimizer( const OpenLinesSet& lines, const GCodePathConfig& config, diff --git a/src/SkirtBrim.cpp b/src/SkirtBrim.cpp index ba399fb721..9b068046ed 100644 --- a/src/SkirtBrim.cpp +++ b/src/SkirtBrim.cpp @@ -407,7 +407,7 @@ Shape SkirtBrim::getFirstLayerOutline(const int extruder_nr /* = -1 */) first_layer_outline = Simplify(smallest_line_length, largest_error_of_removed_point, 0).polygon(first_layer_outline); if (first_layer_outline.empty()) { - spdlog::error("Couldn't generate skirt / brim! No polygons on first layer."); + spdlog::warn("Couldn't generate skirt / brim! No polygons on first layer for extruder {}.", extruder_nr); } return first_layer_outline; } diff --git a/src/TopSurface.cpp b/src/TopSurface.cpp index beb6154d3e..5c6056c68d 100644 --- a/src/TopSurface.cpp +++ b/src/TopSurface.cpp @@ -179,6 +179,8 @@ bool TopSurface::ironing(const SliceDataStorage& storage, const SliceMeshStorage line_config, line_config, line_config, + line_config, + line_config, retract_before_outer_wall, wipe_dist, wipe_dist, diff --git a/src/settings/MeshPathConfigs.cpp b/src/settings/MeshPathConfigs.cpp index fffc0d311a..7c9edf4a0f 100644 --- a/src/settings/MeshPathConfigs.cpp +++ b/src/settings/MeshPathConfigs.cpp @@ -48,6 +48,26 @@ MeshPathConfigs::MeshPathConfigs(const SliceMeshStorage& mesh, const coord_t lay .speed_derivatives = { .speed = mesh.settings.get("speed_wall_x_roofing"), .acceleration = mesh.settings.get("acceleration_wall_x_roofing"), .jerk = mesh.settings.get("jerk_wall_x_roofing") } } + , inset0_flooring_config{ .type = PrintFeatureType::OuterWall, + .line_width = static_cast( + mesh.settings.get("wall_line_width_0") + * line_width_factor_per_extruder[mesh.settings.get("wall_0_extruder_nr").extruder_nr_]), + .layer_thickness = layer_thickness, + .flow = mesh.settings.get("wall_0_material_flow_flooring") + * (layer_nr == 0 ? mesh.settings.get("wall_0_material_flow_layer_0") : Ratio{ 1.0 }), + .speed_derivatives = { .speed = mesh.settings.get("speed_wall_0_flooring"), + .acceleration = mesh.settings.get("acceleration_wall_0_flooring"), + .jerk = mesh.settings.get("jerk_wall_0_flooring") } } + , insetX_flooring_config{ .type = PrintFeatureType::InnerWall, + .line_width = static_cast( + mesh.settings.get("wall_line_width_x") + * line_width_factor_per_extruder[mesh.settings.get("wall_x_extruder_nr").extruder_nr_]), + .layer_thickness = layer_thickness, + .flow = mesh.settings.get("wall_x_material_flow_flooring") + * (layer_nr == 0 ? mesh.settings.get("wall_x_material_flow_layer_0") : Ratio{ 1.0 }), + .speed_derivatives = { .speed = mesh.settings.get("speed_wall_x_flooring"), + .acceleration = mesh.settings.get("acceleration_wall_x_flooring"), + .jerk = mesh.settings.get("jerk_wall_x_flooring") } } , bridge_inset0_config{ .type = PrintFeatureType::OuterWall, .line_width = static_cast( mesh.settings.get("wall_line_width_0") @@ -118,6 +138,13 @@ MeshPathConfigs::MeshPathConfigs(const SliceMeshStorage& mesh, const coord_t lay .speed_derivatives = { .speed = mesh.settings.get("speed_roofing"), .acceleration = mesh.settings.get("acceleration_roofing"), .jerk = mesh.settings.get("jerk_roofing") } } + , flooring_config{ .type = PrintFeatureType::Skin, + .line_width = mesh.settings.get("flooring_line_width"), + .layer_thickness = layer_thickness, + .flow = mesh.settings.get("flooring_material_flow") * (layer_nr == 0 ? mesh.settings.get("material_flow_layer_0") : Ratio{ 1.0 }), + .speed_derivatives = { .speed = mesh.settings.get("speed_flooring"), + .acceleration = mesh.settings.get("acceleration_flooring"), + .jerk = mesh.settings.get("jerk_flooring") } } , ironing_config{ .type = PrintFeatureType::Skin, .line_width = mesh.settings.get("ironing_line_spacing"), .layer_thickness = layer_thickness, diff --git a/src/skin.cpp b/src/skin.cpp index 987e39a8b9..def6af7a28 100644 --- a/src/skin.cpp +++ b/src/skin.cpp @@ -5,6 +5,8 @@ #include // std::ceil +#include + #include "Application.h" //To get settings. #include "ExtruderTrain.h" #include "Slice.h" @@ -91,7 +93,7 @@ void SkinInfillAreaComputation::generateSkinsAndInfill() for (SliceLayerPart& part : layer->parts) { - generateRoofingFillAndSkinFill(part); + generateSkinRoofingFlooringFill(part); generateTopAndBottomMostSurfaces(part); } @@ -324,22 +326,36 @@ void SkinInfillAreaComputation::generateInfill(SliceLayerPart& part) * * this function may only read/write the skin and infill from the *current* layer. */ -void SkinInfillAreaComputation::generateRoofingFillAndSkinFill(SliceLayerPart& part) +void SkinInfillAreaComputation::generateSkinRoofingFlooringFill(SliceLayerPart& part) { for (SkinPart& skin_part : part.skin_parts) { const size_t roofing_layer_count = std::min(mesh_.settings.get("roofing_layer_count"), mesh_.settings.get("top_layers")); + const size_t flooring_layer_count = std::min(mesh_.settings.get("flooring_layer_count"), mesh_.settings.get("bottom_layers")); const coord_t skin_overlap = mesh_.settings.get("skin_overlap_mm"); - Shape filled_area_above = generateFilledAreaAbove(part, roofing_layer_count); + const Shape filled_area_above = generateFilledAreaAbove(part, roofing_layer_count); + const std::optional filled_area_below = generateFilledAreaBelow(part, flooring_layer_count); + // An area that would have nothing below nor above is considered a roof skin_part.roofing_fill = skin_part.outline.difference(filled_area_above); - skin_part.skin_fill = skin_part.outline.intersection(filled_area_above); + if (filled_area_below.has_value()) + { + skin_part.flooring_fill = skin_part.outline.intersection(filled_area_above).difference(*filled_area_below); + skin_part.skin_fill = skin_part.outline.intersection(filled_area_above).intersection(*filled_area_below); + } + else + { + // Mesh part is just above build plate, so it is completely supported + // skin_part.flooring_fill = Shape(); + skin_part.skin_fill = skin_part.outline.intersection(filled_area_above); + } - // We remove offsets areas from roofing_fill anywhere they overlap with skin_fill. - // Otherwise, adjacent skin_fill and roofing_fill would have doubled offset areas. Since they both offset into each other. - skin_part.skin_fill = skin_part.skin_fill.offset(skin_overlap).difference(skin_part.roofing_fill); + // We remove offsets areas from roofing and flooring anywhere they overlap with skin_fill. + // Otherwise, adjacent skin_fill and roofing/flooring would have doubled offset areas. Since they both offset into each other. + skin_part.skin_fill = skin_part.skin_fill.offset(skin_overlap).difference(skin_part.roofing_fill).difference(skin_part.flooring_fill); skin_part.roofing_fill = skin_part.roofing_fill.offset(skin_overlap); + skin_part.flooring_fill = skin_part.flooring_fill.offset(skin_overlap).difference(skin_part.roofing_fill); } } @@ -401,12 +417,13 @@ Shape SkinInfillAreaComputation::generateFilledAreaAbove(SliceLayerPart& part, s * * this function may only read the skin and infill from the *current* layer. */ -Shape SkinInfillAreaComputation::generateFilledAreaBelow(SliceLayerPart& part, size_t flooring_layer_count) +std::optional SkinInfillAreaComputation::generateFilledAreaBelow(SliceLayerPart& part, size_t flooring_layer_count) { if (layer_nr_ < flooring_layer_count) { - return {}; + return std::nullopt; } + const int lowest_flooring_layer = layer_nr_ - flooring_layer_count; Shape filled_area_below = getOutlineOnLayer(part, lowest_flooring_layer); diff --git a/src/sliceDataStorage.cpp b/src/sliceDataStorage.cpp index 729b9845b9..d26e2aaa8a 100644 --- a/src/sliceDataStorage.cpp +++ b/src/sliceDataStorage.cpp @@ -150,6 +150,11 @@ bool SliceMeshStorage::getExtruderIsUsed(const size_t extruder_nr) const { return true; } + const size_t flooring_layer_count = std::min(settings.get("flooring_layer_count"), settings.get("bottom_layers")); + if (flooring_layer_count > 0 && settings.get("flooring_extruder_nr").extruder_nr_ == extruder_nr) + { + return true; + } return false; } @@ -227,6 +232,19 @@ bool SliceMeshStorage::getExtruderIsUsed(const size_t extruder_nr, const LayerIn } } } + if (settings.get("flooring_extruder_nr").extruder_nr_ == extruder_nr) + { + for (const SliceLayerPart& part : layer.parts) + { + for (const SkinPart& skin_part : part.skin_parts) + { + if (! skin_part.flooring_fill.empty()) + { + return true; + } + } + } + } return false; } diff --git a/tests/test_default_settings.txt b/tests/test_default_settings.txt index 34b5d53147..e0f20d872c 100644 --- a/tests/test_default_settings.txt +++ b/tests/test_default_settings.txt @@ -1241,4 +1241,21 @@ cooling=0 material_final_print_temperature=250 infill_randomize_start_location=False extruder_nr=0 - +flooring_extruder_nr=0 +flooring_layer_count=0 +flooring_line_width=0.4 +flooring_pattern=lines +flooring_monotonic=True +flooring_angles=[ ] +wall_0_material_flow_flooring=100 +wall_x_material_flow_flooring=100 +flooring_material_flow=100 +speed_wall_0_flooring=30.0 +speed_wall_x_flooring=30.0 +speed_flooring=30.0 +acceleration_wall_0_flooring=3000 +acceleration_wall_x_flooring=3000 +acceleration_flooring=3000 +jerk_wall_0_flooring=20 +jerk_wall_x_flooring=20 +jerk_flooring=20 \ No newline at end of file