diff --git a/include/FffGcodeWriter.h b/include/FffGcodeWriter.h index 5603c7886a..0da487aaec 100644 --- a/include/FffGcodeWriter.h +++ b/include/FffGcodeWriter.h @@ -560,7 +560,7 @@ class FffGcodeWriter : public NoCopy * \param mesh The mesh for which to add to the layer plan \p gcode_layer. * \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 skin_part The skin part for which to create gcode + * \param skin_fill The shape of the skin for which to create gcode * \param[out] added_something Whether this function added anything to the layer plan */ void processTopBottom( @@ -569,7 +569,7 @@ class FffGcodeWriter : public NoCopy const SliceMeshStorage& mesh, const size_t extruder_nr, const MeshPathConfigs& mesh_config, - const SkinPart& skin_part, + const Shape& skin_fill, bool& added_something) const; /*! diff --git a/include/utils/polygonUtils.h b/include/utils/polygonUtils.h index 22c97e6372..40f42181ca 100644 --- a/include/utils/polygonUtils.h +++ b/include/utils/polygonUtils.h @@ -676,6 +676,32 @@ class PolygonUtils */ static Shape clipPolygonWithAABB(const Shape& src, const AABB& aabb); + /*! + * Merges all parts of a destination area with a source area whenever the destination area is thin enough (w.r.t. a given maximum width). + * This is done in-place as much as possible (hence no return value)! + * \param max_dist The width below which an area is considered 'too thin'. + * \param[in, out] source The source area that is allowed to grow. + * \param[in, out] destination The destination area that the source is allowed to grow into. + * \param allow_thin_areas_grow Whether the thin areas of the source are allowed to grow. + */ + static void mergeThinOverlap(const coord_t max_dist, Shape& source, Shape& destination, const bool allow_thin_areas_grow); + + /*! + * Extract the thin parts of a shape + * @param shape The shape we want the thin parts extracted from + * @param max_width The maximum width of the parts to be kept + * @return The parts of the shape that are thinner than the given maximum. + */ + static Shape getThinAreas(const Shape& shape, const coord_t max_width); + + /*! + * Extract the wide parts of a shape + * @param shape The shape we want the wide parts extracted from + * @param min_width The minimum width of the parts to be kept + * @return The parts of the shape that are wider than the given minimum. + */ + static Shape getWideAreas(const Shape& shape, const coord_t min_width); + /*! * Generate a few outset circles around a base, according to the given line width * @@ -712,6 +738,15 @@ class PolygonUtils * \return The point on the polygon closest to \p from */ static ClosestPointPolygon _moveInside2(const ClosestPointPolygon& closest_polygon_point, const int distance, Point2LL& from, const int64_t max_dist2); + + /*! + * Extract the wide parts of a shape + * @param shape The shape we want the wide parts extracted from + * @param min_width The minimum width of the parts to be kept + * @param extra_widen An extra widening value to be applied (since we end-up by an offset anyway) + * @return The parts of the shape that are wider than the given minimum. Note that the returned shape may go beyond the original one. + */ + static Shape getRawWideAreas(const Shape& shape, const coord_t min_width, const coord_t extra_widen = EPSILON); }; } // namespace cura diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 98932ab463..510488e7e7 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -3040,7 +3040,7 @@ bool FffGcodeWriter::processSkinPart( mesh_config.flooring_config, mesh.flooring_angles, added_something); - processTopBottom(storage, gcode_layer, mesh, extruder_nr, mesh_config, skin_part, added_something); + processTopBottom(storage, gcode_layer, mesh, extruder_nr, mesh_config, skin_part.skin_fill, added_something); return added_something; } @@ -3055,6 +3055,11 @@ void FffGcodeWriter::processRoofingFlooring( const std::vector& angles, bool& added_something) const { + if (fill.empty()) + { + return; + } + const size_t skin_extruder_nr = mesh.settings.get(settings_names.extruder_nr).extruder_nr_; if (extruder_nr != skin_extruder_nr) { @@ -3094,10 +3099,10 @@ void FffGcodeWriter::processTopBottom( const SliceMeshStorage& mesh, const size_t extruder_nr, const MeshPathConfigs& mesh_config, - const SkinPart& skin_part, + const Shape& skin_fill, bool& added_something) const { - if (skin_part.skin_fill.empty()) + if (skin_fill.empty()) { return; // bridgeAngle requires a non-empty skin_fill. } @@ -3152,9 +3157,9 @@ void FffGcodeWriter::processTopBottom( Shape supported_skin_part_regions; - const std::optional bridge_angle = bridgeAngle(mesh, skin_part.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, supported_skin_part_regions); - if (bridge_angle.has_value() || (support_threshold > 0 && (supported_skin_part_regions.area() / (skin_part.skin_fill.area() + 1) < support_threshold))) + if (bridge_angle.has_value() || (support_threshold > 0 && (supported_skin_part_regions.area() / (skin_fill.area() + 1) < support_threshold))) { if (bridge_angle.has_value()) { @@ -3218,7 +3223,7 @@ void FffGcodeWriter::processTopBottom( { // skin isn't a bridge but is it above support and we need to modify the fan speed? - AABB skin_bb(skin_part.skin_fill); + AABB skin_bb(skin_fill); support_layer = &storage.support.supportLayers[support_layer_nr]; @@ -3229,7 +3234,7 @@ void FffGcodeWriter::processTopBottom( AABB support_roof_bb(support_layer->support_roof); if (skin_bb.hit(support_roof_bb)) { - supported = ! skin_part.skin_fill.intersection(support_layer->support_roof).empty(); + supported = ! skin_fill.intersection(support_layer->support_roof).empty(); } } else @@ -3239,7 +3244,7 @@ void FffGcodeWriter::processTopBottom( AABB support_part_bb(support_part.getInfillArea()); if (skin_bb.hit(support_part_bb)) { - supported = ! skin_part.skin_fill.intersection(support_part.getInfillArea()).empty(); + supported = ! skin_fill.intersection(support_part.getInfillArea()).empty(); if (supported) { @@ -3275,7 +3280,7 @@ void FffGcodeWriter::processTopBottom( gcode_layer, mesh, extruder_nr, - skin_part.skin_fill, + skin_fill, *skin_config, pattern, skin_angle, diff --git a/src/skin.cpp b/src/skin.cpp index 62e3460a74..40a652127a 100644 --- a/src/skin.cpp +++ b/src/skin.cpp @@ -353,6 +353,7 @@ void SkinInfillAreaComputation::generateSkinRoofingFlooringFill(SliceLayerPart& const bool has_flooring = flooring_layer_count > 0; const coord_t skin_overlap = mesh_.settings.get("skin_overlap_mm"); const coord_t roofing_expansion = mesh_.settings.get("roofing_expansion"); + const coord_t top_bottom_skin_merge_distance = mesh_.settings.get("top_bottom_skin_merge_distance"); const SliceDataStorage slice_data; const Shape build_plate = slice_data.getRawMachineBorder(); @@ -365,7 +366,11 @@ void SkinInfillAreaComputation::generateSkinRoofingFlooringFill(SliceLayerPart& if (has_roofing) { const Shape below_inside = skin_part.outline.intersection(filled_area_below); - skin_part.roofing_fill = below_inside.difference(filled_area_above).offset(roofing_expansion).intersection(below_inside); + skin_part.roofing_fill = below_inside.difference(filled_area_above); + if (roofing_expansion > 0) + { + skin_part.roofing_fill = skin_part.roofing_fill.offset(roofing_expansion).intersection(below_inside); + } } if (has_flooring) @@ -376,6 +381,16 @@ void SkinInfillAreaComputation::generateSkinRoofingFlooringFill(SliceLayerPart& skin_part.skin_fill = skin_part.outline.difference(skin_part.roofing_fill).intersection(filled_area_below); + // In order to avoid thin areas, we allow merging skin into roofing/flooring, the first operation having a higher precedence. + // Given the precedence, the ordering of the following operations matter, and also whether thin areas of the source are allowed to merge into the destination. + if (top_bottom_skin_merge_distance > 0) + { + // Skin can grow over flooring and roofing given the top_bottom_skin_merge_distance. + constexpr bool allow_thin_areas_grow = true; + PolygonUtils::mergeThinOverlap(top_bottom_skin_merge_distance, skin_part.skin_fill, skin_part.roofing_fill, allow_thin_areas_grow); + PolygonUtils::mergeThinOverlap(top_bottom_skin_merge_distance, skin_part.skin_fill, skin_part.flooring_fill, allow_thin_areas_grow); + } + // 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); diff --git a/src/utils/polygonUtils.cpp b/src/utils/polygonUtils.cpp index 9817c3f1d9..e9a43d35bc 100644 --- a/src/utils/polygonUtils.cpp +++ b/src/utils/polygonUtils.cpp @@ -1518,6 +1518,56 @@ Shape PolygonUtils::clipPolygonWithAABB(const Shape& src, const AABB& aabb) return out; } +void PolygonUtils::mergeThinOverlap(const coord_t max_dist, Shape& source, Shape& destination, const bool allow_thin_areas_grow) +{ + if (source.empty() || destination.empty()) + { + return; + } + + // Get the thin areas of the destination, which we are allowed to grow over + const Shape allow_grow_area = getThinAreas(destination, max_dist); + if (allow_grow_area.empty()) + { + return; + } + + // If necessary, remove the thin parts of the source to not allow them to grow + const Shape source_grow_part = allow_thin_areas_grow ? source : getWideAreas(source, max_dist); + if (source_grow_part.empty()) + { + return; + } + + // Now calculate the actual growing area, which is the intersection of the offset source with the allowed growing area + const Shape actual_grow_area = source_grow_part.offset(max_dist).intersection(allow_grow_area).offset(EPSILON); + if (actual_grow_area.empty()) + { + return; + } + + // Finally, append the growing area to the source and remove it from the destination + source = source.unionPolygons(actual_grow_area); + destination = destination.difference(actual_grow_area); +} + +Shape PolygonUtils::getThinAreas(const Shape& shape, const coord_t max_width) +{ + // Extract the wide areas, then do a difference to actually keep only the thin areas + return shape.difference(getRawWideAreas(shape, max_width, EPSILON)); +} + +Shape PolygonUtils::getWideAreas(const Shape& shape, const coord_t min_width) +{ + // Extract the raw wide areas, then do an intersection to keep them inside the original shape + return shape.intersection(getRawWideAreas(shape, min_width, EPSILON)); +} + +Shape PolygonUtils::getRawWideAreas(const Shape& shape, const coord_t min_width, const coord_t extra_widen) +{ + return shape.offset(-min_width / 2).offset(min_width / 2 + extra_widen); +} + std::tuple PolygonUtils::generateCirculatOutset(const Point2LL& center, const coord_t inner_radius, const coord_t outer_radius, coord_t line_width, const size_t circle_definition) {