Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions include/FffGcodeWriter.h
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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;

/*!
Expand Down
35 changes: 35 additions & 0 deletions include/utils/polygonUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down Expand Up @@ -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
Expand Down
23 changes: 14 additions & 9 deletions src/FffGcodeWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -3055,6 +3055,11 @@ void FffGcodeWriter::processRoofingFlooring(
const std::vector<AngleDegrees>& angles,
bool& added_something) const
{
if (fill.empty())
{
return;
}

const size_t skin_extruder_nr = mesh.settings.get<ExtruderTrain&>(settings_names.extruder_nr).extruder_nr_;
if (extruder_nr != skin_extruder_nr)
{
Expand Down Expand Up @@ -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.
}
Expand Down Expand Up @@ -3152,9 +3157,9 @@ void FffGcodeWriter::processTopBottom(

Shape supported_skin_part_regions;

const std::optional<AngleDegrees> bridge_angle = bridgeAngle(mesh, skin_part.skin_fill, storage, layer_nr, bridge_layer, support_layer, supported_skin_part_regions);
const std::optional<AngleDegrees> 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())
{
Expand Down Expand Up @@ -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];

Expand All @@ -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
Expand All @@ -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)
{
Expand Down Expand Up @@ -3275,7 +3280,7 @@ void FffGcodeWriter::processTopBottom(
gcode_layer,
mesh,
extruder_nr,
skin_part.skin_fill,
skin_fill,
*skin_config,
pattern,
skin_angle,
Expand Down
37 changes: 36 additions & 1 deletion src/skin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ void SkinInfillAreaComputation::generateSkinRoofingFlooringFill(SliceLayerPart&
const bool has_flooring = flooring_layer_count > 0;
const coord_t skin_overlap = mesh_.settings.get<coord_t>("skin_overlap_mm");
const coord_t roofing_expansion = mesh_.settings.get<coord_t>("roofing_expansion");
const coord_t top_bottom_skin_merge_distance = mesh_.settings.get<coord_t>("top_bottom_skin_merge_distance");

const SliceDataStorage slice_data;
const Shape build_plate = slice_data.getRawMachineBorder();
Expand All @@ -365,7 +366,7 @@ 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 (has_flooring)
Expand All @@ -376,6 +377,40 @@ 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 roofing/flooring into skin and vice-verse, 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.

// Flooring has highest precedence and can only grow over skin given the top_bottom_skin_merge_distance
if (top_bottom_skin_merge_distance > 0)
{
constexpr bool allow_thin_areas_grow = true;
PolygonUtils::mergeThinOverlap(top_bottom_skin_merge_distance, skin_part.flooring_fill, skin_part.skin_fill, allow_thin_areas_grow);
}

// Roofing can grow over skin given the top_bottom_skin_merge_distance or the roofing_expansion
if (top_bottom_skin_merge_distance > 0 || roofing_expansion > 0)
{
const Shape expansion_grow_area = roofing_expansion > 0 ? skin_part.roofing_fill.offset(roofing_expansion).intersection(skin_part.skin_fill).offset(EPSILON) : Shape();
const Shape merge_grow_area = top_bottom_skin_merge_distance > 0 ? PolygonUtils::getThinAreas(skin_part.skin_fill, top_bottom_skin_merge_distance) : Shape();
if (! expansion_grow_area.empty() || ! merge_grow_area.empty())
{
const Shape total_roofing_grow_area = expansion_grow_area.unionPolygons(merge_grow_area);
if (! total_roofing_grow_area.empty())
{
skin_part.roofing_fill = skin_part.roofing_fill.unionPolygons(total_roofing_grow_area);
skin_part.skin_fill = skin_part.skin_fill.difference(total_roofing_grow_area);
}
Comment on lines +397 to +402
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[[nitpick]] total_ prefix is a bit nugatory

Suggested change
const Shape total_roofing_grow_area = expansion_grow_area.unionPolygons(merge_grow_area);
if (! total_roofing_grow_area.empty())
{
skin_part.roofing_fill = skin_part.roofing_fill.unionPolygons(total_roofing_grow_area);
skin_part.skin_fill = skin_part.skin_fill.difference(total_roofing_grow_area);
}
const Shape roofing_grow_area = expansion_grow_area.unionPolygons(merge_grow_area);
if (! roofing_grow_area.empty())
{
skin_part.roofing_fill = skin_part.roofing_fill.unionPolygons(roofing_grow_area);
skin_part.skin_fill = skin_part.skin_fill.difference(roofing_grow_area);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is somehow a "total" in the sense that it contains 2 united areas. Would an other wording make more sense to you ?

}
}

// Skin can grow over flooring and roofing given the top_bottom_skin_merge_distance, but not considering the thin parts
if (top_bottom_skin_merge_distance > 0)
{
constexpr bool dont_allow_thin_areas_grow = false;
PolygonUtils::mergeThinOverlap(top_bottom_skin_merge_distance, skin_part.skin_fill, skin_part.roofing_fill, dont_allow_thin_areas_grow);
PolygonUtils::mergeThinOverlap(top_bottom_skin_merge_distance, skin_part.skin_fill, skin_part.flooring_fill, dont_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);
Expand Down
50 changes: 50 additions & 0 deletions src/utils/polygonUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since both source_grow_part and allow_grow_area already include EPSILON, I think you can add the epsilon to the max_dist instead here, thus avoiding the final offset operation (as those aren't the cheapest).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I like the idea, but I think that would defeat the purpose of this offset. If I do it before the intersection, the final shape will not go outside the allow_grow_area that it intersects with, which is required to properly make the subsequent union with the source.

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));
Comment thread
casperlamboo marked this conversation as resolved.
}

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));
Comment thread
casperlamboo marked this conversation as resolved.
}

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<ClosedLinesSet, coord_t>
PolygonUtils::generateCirculatOutset(const Point2LL& center, const coord_t inner_radius, const coord_t outer_radius, coord_t line_width, const size_t circle_definition)
{
Expand Down
Loading