From 3ccf9453caeec1e14f5d56c93c1e9a23f1c76dab Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Mon, 16 Dec 2024 21:37:00 +0100 Subject: [PATCH] Add infill generator CURA-12250 --- CMakeLists.txt | 1 + .../feature_generation/MeshInfillGenerator.h | 60 ++ .../feature_generation/MeshInsetsGenerator.h | 2 - include/geometry/ClosedPolyline.h | 6 + include/geometry/OpenPolyline.h | 6 + include/geometry/Polyline.h | 5 + .../ContinuousExtruderMoveSequence.h | 10 +- include/print_operation/FeatureExtrusion.h | 3 + src/FffGcodeWriter.cpp | 2 + .../MeshInfillGenerator.cpp | 552 ++++++++++++++++++ .../MeshInsetsGenerator.cpp | 19 +- .../ContinuousExtruderMoveSequence.cpp | 32 + 12 files changed, 678 insertions(+), 20 deletions(-) create mode 100644 include/feature_generation/MeshInfillGenerator.h create mode 100644 src/feature_generation/MeshInfillGenerator.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 914a351841..08d4204037 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,6 +99,7 @@ set(engine_SRCS # Except main.cpp. src/feature_generation/FeatureGenerator.cpp include/feature_generation/FeatureGenerator.h src/feature_generation/MeshFeatureGenerator.cpp include/feature_generation/MeshFeatureGenerator.h + src/feature_generation/MeshInfillGenerator.cpp include/feature_generation/MeshInfillGenerator.h src/feature_generation/MeshInsetsGenerator.cpp include/feature_generation/MeshInsetsGenerator.h src/feature_generation/SkirtBrimAppender.cpp include/feature_generation/SkirtBrimAppender.h diff --git a/include/feature_generation/MeshInfillGenerator.h b/include/feature_generation/MeshInfillGenerator.h new file mode 100644 index 0000000000..750c0f6197 --- /dev/null +++ b/include/feature_generation/MeshInfillGenerator.h @@ -0,0 +1,60 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#pragma once + +#include + +#include "feature_generation/MeshFeatureGenerator.h" + +namespace cura +{ + +class Shape; + +class MeshInfillGenerator : public MeshFeatureGenerator +{ +public: + explicit MeshInfillGenerator(const std::shared_ptr& mesh); + + bool isActive() const override; + +protected: + void generateFeatures(const SliceDataStorage& storage, const LayerPlanPtr& layer_plan, const std::vector& extruder_plans, const SliceLayerPart& part) + const override; + +private: + /*! + * \brief Add thicker (multiple layers) sparse infill for a given part in a + * layer plan. + * + * \param gcodeLayer The initial planning of the gcode of the layer. + * \param mesh The mesh for which to add to the layer plan \p gcodeLayer. + * \param extruder_nr The extruder for which to print all features of the + * mesh which should be printed with this extruder. + * \param mesh_config The line config with which to print a print feature. + * \param part The part for which to create gcode. + * \return Whether this function added anything to the layer plan. + */ + void processMultiLayerInfill(const LayerPlanPtr& layer_plan, const ExtruderPlanPtr& extruder_plan, const SliceLayerPart& part) const; + + /*! + * \brief Add normal sparse infill for a given part in a layer. + * \param gcodeLayer The initial planning of the gcode of the layer. + * \param mesh The mesh for which to add to the layer plan \p gcodeLayer. + * \param extruder_nr The extruder for which to print all features of the + * mesh which should be printed with this extruder + * \param mesh_config The line config with which to print a print feature. + * \param part The part for which to create gcode. + * \return Whether this function added anything to the layer plan. + */ + void processSingleLayerInfill(const SliceDataStorage& storage, const LayerPlanPtr& layer_plan, const ExtruderPlanPtr& extruder_plan, const SliceLayerPart& part) const; + + bool + partitionInfillBySkinAbove(Shape& infill_below_skin, Shape& infill_not_below_skin, const LayerPlanPtr& layer_plan, const SliceLayerPart& part, coord_t infill_line_width) const; + +private: + const coord_t infill_line_distance_; +}; + +} // namespace cura \ No newline at end of file diff --git a/include/feature_generation/MeshInsetsGenerator.h b/include/feature_generation/MeshInsetsGenerator.h index 6c86406021..df14eff750 100644 --- a/include/feature_generation/MeshInsetsGenerator.h +++ b/include/feature_generation/MeshInsetsGenerator.h @@ -8,8 +8,6 @@ namespace cura { -class SliceLayerPart; - class MeshInsetsGenerator : public MeshFeatureGenerator { public: diff --git a/include/geometry/ClosedPolyline.h b/include/geometry/ClosedPolyline.h index 979ed6f29c..b4fafb1469 100644 --- a/include/geometry/ClosedPolyline.h +++ b/include/geometry/ClosedPolyline.h @@ -93,6 +93,12 @@ class ClosedPolyline : public Polyline return ! explicitely_closed_; } + /*! @see Polyline::isClosed() */ + [[nodiscard]] bool isClosed() const override + { + return true; + } + /*! @see Polyline::addClosingSegment() */ [[nodiscard]] size_t segmentsCount() const override; diff --git a/include/geometry/OpenPolyline.h b/include/geometry/OpenPolyline.h index 2a6b0faef2..bd9a71d273 100644 --- a/include/geometry/OpenPolyline.h +++ b/include/geometry/OpenPolyline.h @@ -72,6 +72,12 @@ class OpenPolyline : public Polyline return false; // Definitely not } + /*! @see Polyline::isClosed() */ + [[nodiscard]] bool isClosed() const override + { + return false; + } + /*! @see Polyline::segmentsCount() */ [[nodiscard]] size_t segmentsCount() const override { diff --git a/include/geometry/Polyline.h b/include/geometry/Polyline.h index a26c942fcf..95134841b7 100644 --- a/include/geometry/Polyline.h +++ b/include/geometry/Polyline.h @@ -74,6 +74,11 @@ class Polyline : public PointsSet */ [[nodiscard]] virtual bool hasClosingSegment() const = 0; + /*! + * \brief Indicates whether this polyline represents a closed or an open line + */ + [[nodiscard]] virtual bool isClosed() const = 0; + /*! * \brief Gets the total number of "full" segments in the polyline. Calling this is also safe if * there are not enough points to make a valid polyline, so it can also be a good diff --git a/include/print_operation/ContinuousExtruderMoveSequence.h b/include/print_operation/ContinuousExtruderMoveSequence.h index 332b720951..a541da8565 100644 --- a/include/print_operation/ContinuousExtruderMoveSequence.h +++ b/include/print_operation/ContinuousExtruderMoveSequence.h @@ -4,11 +4,15 @@ #pragma once #include "geometry/Point3LL.h" +#include "print_operation/ContinuousExtruderMoveSequencePtr.h" #include "print_operation/PrintOperationSequence.h" namespace cura { - +struct ExtrusionLine; +struct Velocity; +struct ExtrusionJunction; +class Polyline; class ExtruderMove; class SliceMeshStorage; class PlanExporter; @@ -20,6 +24,10 @@ class ContinuousExtruderMoveSequence : public PrintOperationSequence public: explicit ContinuousExtruderMoveSequence(bool closed, const Point3LL& start_position = Point3LL()); + static ContinuousExtruderMoveSequencePtr makeFrom(const ExtrusionLine& extrusion_line, const Velocity& speed); + + static ContinuousExtruderMoveSequencePtr makeFrom(const Polyline& polyline, const coord_t line_width, const Velocity& speed); + void appendExtruderMove(const std::shared_ptr& extruder_move); std::optional findStartPosition() const override; diff --git a/include/print_operation/FeatureExtrusion.h b/include/print_operation/FeatureExtrusion.h index 3196f1e80d..dba0104b72 100644 --- a/include/print_operation/FeatureExtrusion.h +++ b/include/print_operation/FeatureExtrusion.h @@ -6,11 +6,14 @@ #include "GCodePathConfig.h" #include "print_operation/ContinuousExtruderMoveSequencePtr.h" +#include "print_operation/FeatureExtrusionPtr.h" #include "print_operation/PrintOperationSequence.h" namespace cura { +class Shape; + class FeatureExtrusion : public PrintOperationSequence { public: diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index f098fe5c43..f39eef69b7 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -29,6 +29,7 @@ #include "bridge.h" #include "communication/Communication.h" //To send layer view data. #include "feature_generation/FeatureGenerator.h" +#include "feature_generation/MeshInfillGenerator.h" #include "feature_generation/MeshInsetsGenerator.h" #include "geometry/LinesSet.h" #include "geometry/OpenPolyline.h" @@ -194,6 +195,7 @@ void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keep for (const std::shared_ptr& mesh : storage.meshes) { feature_generators_.push_back(std::make_shared(mesh)); + feature_generators_.push_back(std::make_shared(mesh)); } // Filter out generators that are actually useless in this context. Not highly useful, but helps for debugging. diff --git a/src/feature_generation/MeshInfillGenerator.cpp b/src/feature_generation/MeshInfillGenerator.cpp new file mode 100644 index 0000000000..e3d5b6c68a --- /dev/null +++ b/src/feature_generation/MeshInfillGenerator.cpp @@ -0,0 +1,552 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#include "feature_generation/MeshInfillGenerator.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "print_operation/ContinuousExtruderMoveSequence.h" +#include "print_operation/ExtruderPlan.h" +#include "print_operation/LayerPlan.h" +#include "print_operation/MeshFeatureExtrusion.h" +#include "utils/math.h" + + +namespace cura +{ + +MeshInfillGenerator::MeshInfillGenerator(const std::shared_ptr& mesh) + : MeshFeatureGenerator(mesh) + , infill_line_distance_(mesh->settings.get("infill_line_distance")) +{ +} +bool MeshInfillGenerator::isActive() const +{ + if (! MeshFeatureGenerator::isActive()) + { + return false; + } + + if (infill_line_distance_ <= 0) + { + return false; + } + + return true; +} + +void MeshInfillGenerator::generateFeatures( + const SliceDataStorage& storage, + const LayerPlanPtr& layer_plan, + const std::vector& extruder_plans, + const SliceLayerPart& part) const +{ + const size_t infill_extruder_nr = getMesh()->settings.get("infill_extruder_nr").extruder_nr_; + ExtruderPlanPtr extruder_plan_infill = ExtruderPlan::find(extruder_plans, infill_extruder_nr); + assert(extruder_plan_infill && "Unable to find extruder plan for infill"); + + processMultiLayerInfill(layer_plan, extruder_plan_infill, part); + processSingleLayerInfill(storage, layer_plan, extruder_plan_infill, part); + +#warning factorize the functions +} + +void MeshInfillGenerator::processMultiLayerInfill(const LayerPlanPtr& layer_plan, const ExtruderPlanPtr& extruder_plan, const SliceLayerPart& part) const +{ + const Settings& settings = getMesh()->settings; + coord_t max_resolution = settings.get("meshfix_maximum_resolution"); + coord_t max_deviation = settings.get("meshfix_maximum_deviation"); + AngleDegrees infill_angle = 45; // Original default. This will get updated to an element from mesh->infill_angles. + if (! getMesh()->infill_angles.empty()) + { + const size_t combined_infill_layers + = std::max(uint64_t(1), round_divide(settings.get("infill_sparse_thickness"), std::max(settings.get("layer_height"), coord_t(1)))); + infill_angle = getMesh()->infill_angles.at((layer_plan->getLayerIndex() / combined_infill_layers) % getMesh()->infill_angles.size()); + } + const Point3LL mesh_middle = getMesh()->bounding_box.getMiddle(); + const Point2LL infill_origin(mesh_middle.x_ + settings.get("infill_offset_x"), mesh_middle.y_ + settings.get("infill_offset_y")); + const MeshPathConfigs& mesh_configs = layer_plan->getConfigsStorage()->mesh_configs.at(getMesh()); + + // Print the thicker infill lines first. (double or more layer thickness, infill combined with previous layers) + for (unsigned int combine_idx = 1; combine_idx < part.infill_area_per_combine_per_density[0].size(); combine_idx++) + { + const GCodePathConfig& infill_config = mesh_configs.infill_config[combine_idx]; + const coord_t infill_line_width = infill_config.getLineWidth(); + const EFillMethod infill_pattern = settings.get("infill_pattern"); + const bool zig_zaggify_infill = settings.get("zig_zaggify_infill") || infill_pattern == EFillMethod::ZIG_ZAG; + const bool connect_polygons = settings.get("connect_infill_polygons"); + const size_t infill_multiplier = settings.get("infill_multiplier"); + Shape infill_polygons; + OpenLinesSet infill_lines; + std::vector infill_paths = part.infill_wall_toolpaths; + for (size_t density_idx = part.infill_area_per_combine_per_density.size() - 1; (int)density_idx >= 0; density_idx--) + { // combine different density infill areas (for gradual infill) + size_t density_factor = 2 << density_idx; // == pow(2, density_idx + 1) + coord_t infill_line_distance_here = infill_line_distance_ * density_factor; // the highest density infill combines with the next to create a grid with density_factor + const coord_t infill_shift = infill_line_distance_here / 2; + if (density_idx == part.infill_area_per_combine_per_density.size() - 1 || infill_pattern == EFillMethod::CROSS || infill_pattern == EFillMethod::CROSS_3D) + { + infill_line_distance_here /= 2; + } + + constexpr size_t wall_line_count = 0; // wall toolpaths are when gradual infill areas are determined + const coord_t small_area_width = 0; + const coord_t infill_overlap = settings.get("infill_overlap_mm"); + constexpr bool skip_stitching = false; + constexpr bool connected_zigzags = false; + constexpr bool use_endpieces = true; + constexpr bool skip_some_zags = false; + constexpr size_t zag_skip_count = 0; + const bool fill_gaps = density_idx == 0; // Only fill gaps for the lowest density. + + std::shared_ptr lightning_layer = nullptr; + if (getMesh()->lightning_generator) + { +#warning set the generator as a class variable of this + lightning_layer = std::make_shared(getMesh()->lightning_generator->getTreesForLayer(layer_plan->getLayerIndex())); + } + Infill infill_comp( + infill_pattern, + zig_zaggify_infill, + connect_polygons, + part.infill_area_per_combine_per_density[density_idx][combine_idx], + infill_line_width, + infill_line_distance_here, + infill_overlap, + infill_multiplier, + infill_angle, + layer_plan->getZ(), + infill_shift, + max_resolution, + max_deviation, + wall_line_count, + small_area_width, + infill_origin, + skip_stitching, + fill_gaps, + connected_zigzags, + use_endpieces, + skip_some_zags, + zag_skip_count, + settings.get("cross_infill_pocket_size")); + infill_comp.generate( + infill_paths, + infill_polygons, + infill_lines, + settings, + layer_plan->getLayerIndex(), + SectionType::INFILL, + getMesh()->cross_fill_provider, + lightning_layer, + getMesh().get()); + } + + auto feature_extrusion = std::make_shared(PrintFeatureType::Infill, infill_line_width, getMesh()); + + for (const Polygon& polygon : infill_polygons) + { + feature_extrusion->appendExtruderMoveSequence(ContinuousExtruderMoveSequence::makeFrom(polygon, infill_line_width, infill_config.getSpeed())); + } + + for (const OpenPolyline& polyline : infill_lines) + { + feature_extrusion->appendExtruderMoveSequence(ContinuousExtruderMoveSequence::makeFrom(polyline, infill_line_width, infill_config.getSpeed())); + } + + extruder_plan->appendFeatureExtrusion(feature_extrusion); + } +} + +void MeshInfillGenerator::processSingleLayerInfill( + const SliceDataStorage& storage, + const LayerPlanPtr& layer_plan, + const ExtruderPlanPtr& extruder_plan, + const SliceLayerPart& part) const +{ + const MeshPathConfigs& mesh_configs = layer_plan->getConfigsStorage()->mesh_configs.at(getMesh()); + const GCodePathConfig& infill_config = mesh_configs.infill_config[0]; + const coord_t infill_line_width = infill_config.getLineWidth(); + + // Combine the 1 layer thick infill with the top/bottom skin and print that as one thing. + Shape infill_polygons; + std::vector> wall_tool_paths; // All wall toolpaths binned by inset_idx (inner) and by density_idx (outer) + OpenLinesSet infill_lines; + + const Settings& settings = getMesh()->settings; + const auto pattern = settings.get("infill_pattern"); + const bool zig_zaggify_infill = settings.get("zig_zaggify_infill") || pattern == EFillMethod::ZIG_ZAG; + const bool connect_polygons = settings.get("connect_infill_polygons"); + const auto infill_overlap = settings.get("infill_overlap_mm"); + const auto infill_multiplier = settings.get("infill_multiplier"); + const auto wall_line_count = settings.get("infill_wall_line_count"); + const size_t last_idx = part.infill_area_per_combine_per_density.size() - 1; + const auto max_resolution = settings.get("meshfix_maximum_resolution"); + const auto max_deviation = settings.get("meshfix_maximum_deviation"); + AngleDegrees infill_angle = 45; // Original default. This will get updated to an element from mesh->infill_angles. + if (! getMesh()->infill_angles.empty()) + { + const size_t combined_infill_layers + = std::max(uint64_t(1), round_divide(settings.get("infill_sparse_thickness"), std::max(settings.get("layer_height"), coord_t(1)))); + infill_angle = getMesh()->infill_angles.at((layer_plan->getLayerIndex() / combined_infill_layers) % getMesh()->infill_angles.size()); + } + const Point3LL mesh_middle = getMesh()->bounding_box.getMiddle(); + const Point2LL infill_origin(mesh_middle.x_ + settings.get("infill_offset_x"), mesh_middle.y_ + settings.get("infill_offset_y")); + + auto get_cut_offset = [](const bool zig_zaggify, const coord_t line_width, const size_t line_count) + { + if (zig_zaggify) + { + return -line_width / 2 - static_cast(line_count) * line_width - 5; + } + return -static_cast(line_count) * line_width; + }; + + Shape sparse_in_outline = part.infill_area_per_combine_per_density[last_idx][0]; + + // if infill walls are required below the boundaries of skin regions above, partition the infill along the + // boundary edge + Shape infill_below_skin; + Shape infill_not_below_skin; + const bool hasSkinEdgeSupport = partitionInfillBySkinAbove(infill_below_skin, infill_not_below_skin, layer_plan, part, infill_line_width); + + const auto pocket_size = settings.get("cross_infill_pocket_size"); + constexpr bool skip_stitching = false; + constexpr bool connected_zigzags = false; + const bool use_endpieces = part.infill_area_per_combine_per_density.size() == 1; // Only use endpieces when not using gradual infill, since they will then overlap. + constexpr bool skip_some_zags = false; + constexpr int zag_skip_count = 0; + + for (size_t density_idx = last_idx; static_cast(density_idx) >= 0; density_idx--) + { + // Only process dense areas when they're initialized + if (part.infill_area_per_combine_per_density[density_idx][0].empty()) + { + continue; + } + + OpenLinesSet infill_lines_here; + Shape infill_polygons_here; + + // the highest density infill combines with the next to create a grid with density_factor 1 + int infill_line_distance_here = infill_line_distance_ << (density_idx + 1); + int infill_shift = infill_line_distance_here / 2; + + /* infill shift explanation: [>]=shift ["]=line_dist + + : | : | : | : | > furthest from top + : | | | : | | | : | | | : | | | > further from top + : | | | | | | | : | | | | | | | : | | | | | | | : | | | | | | | > near top + >>""""" + : | : | : | : | > furthest from top + : | | | : | | | : | | | : | | | > further from top + : | | | | | | | : | | | | | | | : | | | | | | | : | | | | | | | > near top + >>>>""""""""" + : | : | : | : | > furthest from top + : | | | : | | | : | | | : | | | > further from top + : | | | | | | | : | | | | | | | : | | | | | | | : | | | | | | | > near top + >>>>>>>>""""""""""""""""" + */ + + // All of that doesn't hold for the Cross patterns; they should just always be multiplied by 2. + if (density_idx == part.infill_area_per_combine_per_density.size() - 1 || pattern == EFillMethod::CROSS || pattern == EFillMethod::CROSS_3D) + { + /* the least dense infill should fill up all remaining gaps + : | : | : | : | : > furthest from top + : | | | : | | | : | | | : | | | : > further from top + : | | | | | | | : | | | | | | | : | | | | | | | : | | | | | | | : > near top + . . . . . . . . + : : : : : : : : + `"""' `"""""""' `"""""""""""""""' `"""""""' + ^ new line distance for lowest density infill + ^ infill_line_distance_here for lowest density infill up till here + ^ middle density line dist + ^ highest density line dist*/ + + // All of that doesn't hold for the Cross patterns; they should just always be multiplied by 2 for every density index. + infill_line_distance_here /= 2; + } + + Shape in_outline = part.infill_area_per_combine_per_density[density_idx][0]; + + std::shared_ptr lightning_layer; + if (getMesh()->lightning_generator) + { + lightning_layer = std::make_shared(getMesh()->lightning_generator->getTreesForLayer(layer_plan->getLayerIndex())); + } + + const bool fill_gaps = density_idx == 0; // Only fill gaps in the lowest infill density pattern. + if (hasSkinEdgeSupport) + { + // infill region with skin above has to have at least one infill wall line + const size_t min_skin_below_wall_count = wall_line_count > 0 ? wall_line_count : 1; + const size_t skin_below_wall_count = density_idx == last_idx ? min_skin_below_wall_count : 0; + const coord_t small_area_width = 0; + wall_tool_paths.emplace_back(std::vector()); + const coord_t overlap = infill_overlap - (density_idx == last_idx ? 0 : wall_line_count * infill_line_width); + Infill infill_comp( + pattern, + zig_zaggify_infill, + connect_polygons, + infill_below_skin, + infill_line_width, + infill_line_distance_here, + overlap, + infill_multiplier, + infill_angle, + layer_plan->getZ(), + infill_shift, + max_resolution, + max_deviation, + skin_below_wall_count, + small_area_width, + infill_origin, + skip_stitching, + fill_gaps, + connected_zigzags, + use_endpieces, + skip_some_zags, + zag_skip_count, + pocket_size); + infill_comp.generate( + wall_tool_paths.back(), + infill_polygons, + infill_lines, + settings, + layer_plan->getLayerIndex(), + SectionType::INFILL, + getMesh()->cross_fill_provider, + lightning_layer, + getMesh().get()); + if (density_idx < last_idx) + { + const coord_t cut_offset = get_cut_offset(zig_zaggify_infill, infill_line_width, min_skin_below_wall_count); + Shape tool = infill_below_skin.offset(static_cast(cut_offset)); + infill_lines_here = tool.intersection(infill_lines_here); + } + infill_lines.push_back(infill_lines_here); + // normal processing for the infill that isn't below skin + in_outline = infill_not_below_skin; + if (density_idx == last_idx) + { + sparse_in_outline = infill_not_below_skin; + } + } + + const coord_t circumference = in_outline.length(); + // Originally an area of 0.4*0.4*2 (2 line width squares) was found to be a good threshold for removal. + // However we found that this doesn't scale well with polygons with larger circumference (https://github.com/Ultimaker/Cura/issues/3992). + // Given that the original test worked for approximately 2x2cm models, this scaling by circumference should make it work for any size. + constexpr double minimum_small_area_factor = 0.4 * 0.4 / 40000; + const double minimum_small_area = minimum_small_area_factor * circumference; + + // This is only for density infill, because after generating the infill might appear unnecessary infill on walls + // especially on vertical surfaces + in_outline.removeSmallAreas(minimum_small_area); + + constexpr size_t wall_line_count_here = 0; // Wall toolpaths were generated in generateGradualInfill for the sparsest density, denser parts don't have walls by default + const coord_t small_area_width = 0; + const coord_t overlap = settings.get("infill_overlap_mm"); + + wall_tool_paths.emplace_back(); + Infill infill_comp( + pattern, + zig_zaggify_infill, + connect_polygons, + in_outline, + infill_line_width, + infill_line_distance_here, + overlap, + infill_multiplier, + infill_angle, + layer_plan->getZ(), + infill_shift, + max_resolution, + max_deviation, + wall_line_count_here, + small_area_width, + infill_origin, + skip_stitching, + fill_gaps, + connected_zigzags, + use_endpieces, + skip_some_zags, + zag_skip_count, + pocket_size); + infill_comp.generate( + wall_tool_paths.back(), + infill_polygons, + infill_lines, + settings, + layer_plan->getLayerIndex(), + SectionType::INFILL, + getMesh()->cross_fill_provider, + lightning_layer, + getMesh().get()); + if (density_idx < last_idx) + { + const coord_t cut_offset = get_cut_offset(zig_zaggify_infill, infill_line_width, wall_line_count); + Shape tool = sparse_in_outline.offset(static_cast(cut_offset)); + infill_lines_here = tool.intersection(infill_lines_here); + } + infill_lines.push_back(infill_lines_here); + infill_polygons.push_back(infill_polygons_here); + } + + wall_tool_paths.emplace_back(part.infill_wall_toolpaths); // The extra infill walls were generated separately. Add these too. + + if (settings.get("wall_line_count") // Disable feature if no walls - it can leave dangling lines at edges + && pattern != EFillMethod::LIGHTNING // Lightning doesn't make enclosed regions + && pattern != EFillMethod::CONCENTRIC // Doesn't handle 'holes' in infill lines very well + && pattern != EFillMethod::CROSS // Ditto + && pattern != EFillMethod::CROSS_3D) // Ditto + { + // addExtraLinesToSupportSurfacesAbove(infill_lines, infill_polygons, wall_tool_paths, part, infill_line_width, gcode_layer, mesh); + } + + auto feature_extrusion = std::make_shared(PrintFeatureType::Infill, infill_line_width, getMesh()); + + for (const Polygon& polygon : infill_polygons) + { + feature_extrusion->appendExtruderMoveSequence(ContinuousExtruderMoveSequence::makeFrom(polygon, infill_line_width, infill_config.getSpeed())); + } + + for (const OpenPolyline& polyline : infill_lines) + { + feature_extrusion->appendExtruderMoveSequence(ContinuousExtruderMoveSequence::makeFrom(polyline, infill_line_width, infill_config.getSpeed())); + } + + for (const std::vector& wall_tool_path : wall_tool_paths) + { + for (const VariableWidthLines& extrusion_lines : wall_tool_path) + { + for (const ExtrusionLine& extrusion_line : extrusion_lines) + { + feature_extrusion->appendExtruderMoveSequence(ContinuousExtruderMoveSequence::makeFrom(extrusion_line, infill_config.getSpeed())); + } + } + + extruder_plan->appendFeatureExtrusion(feature_extrusion); + +#warning Handle infill_randomize_start_location in scheduler + // if (settings.get("infill_randomize_start_location")) + } +} + +bool MeshInfillGenerator::partitionInfillBySkinAbove( + Shape& infill_below_skin, + Shape& infill_not_below_skin, + const LayerPlanPtr& layer_plan, + const SliceLayerPart& part, + coord_t infill_line_width) const +{ + constexpr coord_t tiny_infill_offset = 20; + const auto skin_edge_support_layers = getMesh()->settings.get("skin_edge_support_layers"); + Shape skin_above_combined; // skin regions on the layers above combined with small gaps between + + // working from the highest layer downwards, combine the regions of skin on all the layers + // but don't let the regions merge together + // otherwise "terraced" skin regions on separate layers will look like a single region of unbroken skin + for (size_t i = skin_edge_support_layers; i > 0; --i) + { + const size_t skin_layer_nr = layer_plan->getLayerIndex() + i; + if (skin_layer_nr < getMesh()->layers.size()) + { + for (const SliceLayerPart& part_i : getMesh()->layers[skin_layer_nr].parts) + { + for (const SkinPart& skin_part : part_i.skin_parts) + { + // Limit considered areas to the ones that should have infill underneath at the current layer. + const Shape relevant_outline = skin_part.outline.intersection(part.getOwnInfillArea()); + + if (! skin_above_combined.empty()) + { + // does this skin part overlap with any of the skin parts on the layers above? + const Shape overlap = skin_above_combined.intersection(relevant_outline); + if (! overlap.empty()) + { + // yes, it overlaps, need to leave a gap between this skin part and the others + if (i > 1) // this layer is the 2nd or higher layer above the layer whose infill we're printing + { + // looking from the side, if the combined regions so far look like this... + // + // ---------------------------------- + // + // and the new skin part looks like this... + // + // ------------------------------------- + // + // the result should be like this... + // + // ------- -------------------------- ---------- + + // expand the overlap region slightly to make a small gap + const Shape overlap_expanded = overlap.offset(tiny_infill_offset); + // subtract the expanded overlap region from the regions accumulated from higher layers + skin_above_combined = skin_above_combined.difference(overlap_expanded); + // subtract the expanded overlap region from this skin part and add the remainder to the overlap region + skin_above_combined.push_back(relevant_outline.difference(overlap_expanded)); + // and add the overlap area as well + skin_above_combined.push_back(overlap); + } + else // this layer is the 1st layer above the layer whose infill we're printing + { + // add this layer's skin region without subtracting the overlap but still make a gap between this skin region and what has been accumulated so + // far we do this so that these skin region edges will definitely have infill walls below them + + // looking from the side, if the combined regions so far look like this... + // + // ---------------------------------- + // + // and the new skin part looks like this... + // + // ------------------------------------- + // + // the result should be like this... + // + // ------- ------------------------------------- + + skin_above_combined = skin_above_combined.difference(relevant_outline.offset(tiny_infill_offset)); + skin_above_combined.push_back(relevant_outline); + } + } + else // no overlap + { + skin_above_combined.push_back(relevant_outline); + } + } + else // this is the first skin region we have looked at + { + skin_above_combined.push_back(relevant_outline); + } + } + } + } + + // the shrink/expand here is to remove regions of infill below skin that are narrower than the width of the infill walls otherwise the infill walls could merge and form + // a bump + infill_below_skin = skin_above_combined.intersection(part.infill_area_per_combine_per_density.back().front()).offset(-infill_line_width).offset(infill_line_width); + + constexpr bool remove_small_holes_from_infill_below_skin = true; + constexpr double min_area_multiplier = 25; + const double min_area = INT2MM(infill_line_width) * INT2MM(infill_line_width) * min_area_multiplier; + infill_below_skin.removeSmallAreas(min_area, remove_small_holes_from_infill_below_skin); + + // there is infill below skin, is there also infill that isn't below skin? + infill_not_below_skin = part.infill_area_per_combine_per_density.back().front().difference(infill_below_skin); + infill_not_below_skin.removeSmallAreas(min_area); + } + + // need to take skin/infill overlap that was added in SkinInfillAreaComputation::generateInfill() into account + const coord_t infill_skin_overlap = getMesh()->settings.get((part.wall_toolpaths.size() > 1) ? "wall_line_width_x" : "wall_line_width_0") / 2; + const Shape infill_below_skin_overlap = infill_below_skin.offset(-(infill_skin_overlap + tiny_infill_offset)); + + return ! infill_below_skin_overlap.empty() && ! infill_not_below_skin.empty(); +} + +} // namespace cura \ No newline at end of file diff --git a/src/feature_generation/MeshInsetsGenerator.cpp b/src/feature_generation/MeshInsetsGenerator.cpp index 38b9dd295e..1136b8cd04 100644 --- a/src/feature_generation/MeshInsetsGenerator.cpp +++ b/src/feature_generation/MeshInsetsGenerator.cpp @@ -89,23 +89,8 @@ void MeshInsetsGenerator::generateFeatures( { for (const ExtrusionLine& extrusion_line : toolpath) { - if (extrusion_line.empty()) - { - continue; - } - - auto move_sequence = std::make_shared(extrusion_line.is_closed_, extrusion_line.front().p_); - - for (const auto& extrusion_junctions : extrusion_line.junctions_ | ranges::views::sliding(2)) - { - const ExtrusionJunction& start = extrusion_junctions[0]; - const ExtrusionJunction& end = extrusion_junctions[1]; - - const GCodePathConfig& config = extrusion_line.inset_idx_ == 0 ? mesh_configs.inset0_config : mesh_configs.insetX_config; - - move_sequence->appendExtruderMove(std::make_shared(end.p_, start.w_, config.getSpeed(), end.w_)); - } - + const GCodePathConfig& config = extrusion_line.inset_idx_ == 0 ? mesh_configs.inset0_config : mesh_configs.insetX_config; + auto move_sequence = ContinuousExtruderMoveSequence::makeFrom(extrusion_line, config.getSpeed()); find_or_make_feature_extrusion(extrusion_line.inset_idx_)->appendExtruderMoveSequence(move_sequence); } } diff --git a/src/print_operation/ContinuousExtruderMoveSequence.cpp b/src/print_operation/ContinuousExtruderMoveSequence.cpp index 7606af8931..1ad85ac1ef 100644 --- a/src/print_operation/ContinuousExtruderMoveSequence.cpp +++ b/src/print_operation/ContinuousExtruderMoveSequence.cpp @@ -3,6 +3,11 @@ #include "print_operation/ContinuousExtruderMoveSequence.h" +#include +#include + +#include + #include "print_operation/ExtrusionMove.h" namespace cura @@ -14,6 +19,33 @@ ContinuousExtruderMoveSequence::ContinuousExtruderMoveSequence(bool closed, cons { } +ContinuousExtruderMoveSequencePtr ContinuousExtruderMoveSequence::makeFrom(const ExtrusionLine& extrusion_line, const Velocity& speed) +{ + auto move_sequence = std::make_shared(extrusion_line.is_closed_, extrusion_line.junctions_.empty() ? Point3LL() : extrusion_line.front().p_); + + for (const auto& extrusion_junctions : extrusion_line.junctions_ | ranges::views::sliding(2)) + { + const ExtrusionJunction& start = extrusion_junctions[0]; + const ExtrusionJunction& end = extrusion_junctions[1]; + + move_sequence->appendExtruderMove(std::make_shared(end.p_, start.w_, speed, end.w_)); + } + + return move_sequence; +} + +ContinuousExtruderMoveSequencePtr ContinuousExtruderMoveSequence::makeFrom(const Polyline& polyline, const coord_t line_width, const Velocity& speed) +{ + auto move_sequence = std::make_shared(polyline.isClosed(), polyline.empty() ? Point3LL() : polyline.front()); + + for (auto iterator = polyline.beginSegments(); iterator != polyline.endSegments(); ++iterator) + { + move_sequence->appendExtruderMove(std::make_shared((*iterator).end, line_width, speed)); + } + + return move_sequence; +} + std::optional ContinuousExtruderMoveSequence::findStartPosition() const { return closed_ ? PrintOperationSequence::findEndPosition() : start_position_;