<commit_before>//Copyright (c) 2013 Ultimaker
//Copyright (c) 2017 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.

#include <cmath> // std::ceil

#include "skin.h"
#include "utils/math.h"
#include "utils/polygonUtils.h"

#define MIN_AREA_SIZE (0.4 * 0.4) 

namespace cura 
{

coord_t SkinInfillAreaComputation::getSkinLineWidth(const SliceDataStorage& storage, const SliceMeshStorage& mesh, int layer_nr)
{
    coord_t skin_line_width = mesh.getSettingInMicrons("skin_line_width");
    if (layer_nr == 0)
    {
        const ExtruderTrain& train_skin = *storage.meshgroup->getExtruderTrain(mesh.getSettingAsExtruderNr("top_bottom_extruder_nr"));
        skin_line_width *= train_skin.getSettingAsRatio("initial_layer_line_width_factor");
    }
    return skin_line_width;
}

coord_t SkinInfillAreaComputation::getWallLineWidth0(const SliceDataStorage& storage, const SliceMeshStorage& mesh, int layer_nr)
{
    coord_t wall_line_width_0 = mesh.getSettingInMicrons("wall_line_width_0");
    if (layer_nr == 0)
    {
        const ExtruderTrain& train_wall_0 = *storage.meshgroup->getExtruderTrain(mesh.getSettingAsExtruderNr("wall_0_extruder_nr"));
        wall_line_width_0 *= train_wall_0.getSettingAsRatio("initial_layer_line_width_factor");
    }
    return wall_line_width_0;
}
coord_t SkinInfillAreaComputation::getWallLineWidthX(const SliceDataStorage& storage, const SliceMeshStorage& mesh, int layer_nr)
{
    coord_t wall_line_width_x = mesh.getSettingInMicrons("wall_line_width_x");
    if (layer_nr == 0)
    {
        const ExtruderTrain& train_wall_x = *storage.meshgroup->getExtruderTrain(mesh.getSettingAsExtruderNr("wall_x_extruder_nr"));
        wall_line_width_x *= train_wall_x.getSettingAsRatio("initial_layer_line_width_factor");
    }
    return wall_line_width_x;
}
coord_t SkinInfillAreaComputation::getInfillSkinOverlap(const SliceDataStorage& storage, const SliceMeshStorage& mesh, int layer_nr, coord_t innermost_wall_line_width)
{
    coord_t infill_skin_overlap = 0;
    { // compute infill_skin_overlap
        const ExtruderTrain& train_infill = *storage.meshgroup->getExtruderTrain(mesh.getSettingAsExtruderNr("infill_extruder_nr"));
        const coord_t infill_line_width_factor = (layer_nr == 0) ? train_infill.getSettingAsRatio("initial_layer_line_width_factor") : 1.0;
        const bool infill_is_dense = mesh.getSettingInMicrons("infill_line_distance") < mesh.getSettingInMicrons("infill_line_width") * infill_line_width_factor + 10;
        if (!infill_is_dense && mesh.getSettingAsFillMethod("infill_pattern") != EFillMethod::CONCENTRIC)
        {
            infill_skin_overlap = innermost_wall_line_width / 2;
        }
    }
    return infill_skin_overlap;
}

SkinInfillAreaComputation::SkinInfillAreaComputation(int layer_nr, const SliceDataStorage& storage, SliceMeshStorage& mesh, bool process_infill)
: layer_nr(layer_nr)
, mesh(mesh)
, bottom_layer_count(mesh.getSettingAsCount("bottom_layers"))
, top_layer_count(mesh.getSettingAsCount("top_layers"))
, wall_line_count(mesh.getSettingAsCount("wall_line_count"))
, skin_line_width(getSkinLineWidth(storage, mesh, layer_nr))
, wall_line_width_0(getWallLineWidth0(storage, mesh, layer_nr))
, wall_line_width_x(getWallLineWidthX(storage, mesh, layer_nr))
, innermost_wall_line_width((wall_line_count == 1) ? wall_line_width_0 : wall_line_width_x)
, infill_skin_overlap(getInfillSkinOverlap(storage, mesh, layer_nr, innermost_wall_line_width))
, skin_inset_count(mesh.getSettingAsCount("skin_outline_count"))
, no_small_gaps_heuristic(mesh.getSettingBoolean("skin_no_small_gaps_heuristic"))
, process_infill(process_infill)
, top_reference_wall_expansion(mesh.getSettingInMicrons("top_skin_preshrink"))
, bottom_reference_wall_expansion(mesh.getSettingInMicrons("bottom_skin_preshrink"))
, top_skin_expand_distance(mesh.getSettingInMicrons("top_skin_expand_distance"))
, bottom_skin_expand_distance(mesh.getSettingInMicrons("bottom_skin_expand_distance"))
, top_reference_wall_idx(getReferenceWallIdx(top_reference_wall_expansion))
, bottom_reference_wall_idx(getReferenceWallIdx(bottom_reference_wall_expansion))
{
}

/*
 * This function is executed in a parallel region based on layer_nr.
 * When modifying make sure any changes does not introduce data races.
 *
 * this function may only read/write the skin and infill from the *current* layer.
 */
Polygons SkinInfillAreaComputation::getWalls(const SliceLayerPart& part_here, int layer2_nr, unsigned int wall_idx)
{
    Polygons result;
    if (layer2_nr >= static_cast<int>(mesh.layers.size()))
    {
        return result;
    }
    const SliceLayer& layer2 = mesh.layers[layer2_nr];
    for (const SliceLayerPart& part2 : layer2.parts)
    {
        if (part_here.boundaryBox.hit(part2.boundaryBox))
        {
            if (wall_idx <= 0)
            {
                result.add(part2.outline);
            }
            else if (wall_idx <= part2.insets.size())
            {
                result.add(part2.insets[wall_idx - 1]); // -1 because it's a 1-based index
            }
        }
    }
    return result;
};

int SkinInfillAreaComputation::getReferenceWallIdx(coord_t& preshrink) const
{
    for (int wall_idx = wall_line_count; wall_idx > 0; wall_idx--)
    {
        coord_t wall_line_width = (wall_idx > 1)? wall_line_width_x : wall_line_width_0;
        int next_wall_idx = wall_idx - 1;
        coord_t next_wall_line_width = (next_wall_idx > 1)? wall_line_width_x : (next_wall_idx == 0)? 0 : wall_line_width_0;
        coord_t diff_to_next_wall = (wall_line_width + next_wall_line_width) / 2;
        if (std::abs(preshrink - diff_to_next_wall) <= 10)
        { // snap preshrink to closest wall
            preshrink = 0;
            return next_wall_idx;
        }
        if (preshrink < diff_to_next_wall)
        {
            return wall_idx;
        }
        preshrink -= diff_to_next_wall;
    }
    return 0;
}

/*
 * This function is executed in a parallel region based on layer_nr.
 * When modifying make sure any changes does not introduce data races.
 *
 * generateSkinAreas reads data from mesh.layers.parts[*].insets and writes to mesh.layers[n].parts[*].skin_parts
 * generateSkinInsets only read/writes the skin_parts from the current layer.
 *
 * generateSkins therefore reads (depends on) data from mesh.layers[*].parts[*].insets and writes mesh.layers[n].parts[*].skin_parts
 */
void SkinInfillAreaComputation::generateSkinsAndInfill()
{
    generateSkinAndInfillAreas();

    SliceLayer* layer = &mesh.layers[layer_nr];
    for (unsigned int part_nr = 0; part_nr < layer->parts.size(); part_nr++)
    {
        SliceLayerPart& part = layer->parts[part_nr];
        generateSkinInsetsAndInnerSkinInfill(&part);

        generateRoofing(part);
    }
}

/*
 * This function is executed in a parallel region based on layer_nr.
 * When modifying make sure any changes does not introduce data races.
 *
 * generateSkinAreas reads data from mesh.layers[*].parts[*].insets and writes to mesh.layers[n].parts[*].skin_parts
 */
void SkinInfillAreaComputation::generateSkinAndInfillAreas()
{
    SliceLayer& layer = mesh.layers[layer_nr];

    if (!process_infill && bottom_layer_count == 0 && top_layer_count == 0)
    {
        return;
    }

    for (unsigned int part_nr = 0; part_nr < layer.parts.size(); part_nr++)
    {
        SliceLayerPart& part = layer.parts[part_nr];

        if (static_cast<int>(part.insets.size()) < wall_line_count)
        {
            continue; // the last wall is not present, the part should only get inter perimeter gaps, but no skin or infill.
        }
        generateSkinAndInfillAreas(part);
    }
}

/*
 * This function is executed in a parallel region based on layer_nr.
 * When modifying make sure any changes does not introduce data races.
 *
 * generateSkinAreas reads data from mesh.layers[*].parts[*].insets and writes to mesh.layers[n].parts[*].skin_parts
 */
void SkinInfillAreaComputation::generateSkinAndInfillAreas(SliceLayerPart& part)
{
    int min_infill_area = mesh.getSettingInMillimeters("min_infill_area");

    Polygons original_outline = part.insets.back().offset(-innermost_wall_line_width / 2);

    // make a copy of the outline which we later intersect and union with the resized skins to ensure the resized skin isn't too large or removed completely.
    Polygons upskin;
    if (top_layer_count > 0)
    {
        upskin = Polygons(original_outline);
    }
    Polygons downskin;
    if (bottom_layer_count > 0)
    {
        downskin = Polygons(original_outline);
    }

    calculateBottomSkin(part, min_infill_area, downskin);

    calculateTopSkin(part, min_infill_area, upskin);

    applySkinExpansion(original_outline, upskin, downskin);

    // now combine the resized upskin and downskin
    Polygons skin = upskin.unionPolygons(downskin);

    skin.removeSmallAreas(MIN_AREA_SIZE);

    if (process_infill)
    { // process infill when infill density > 0
        // or when other infill meshes want to modify this infill
        generateInfill(part, skin);
    }

    for (PolygonsPart& skin_area_part : skin.splitIntoParts())
    {
        part.skin_parts.emplace_back();
        part.skin_parts.back().outline = skin_area_part;
    }
}

/*
 * This function is executed in a parallel region based on layer_nr.
 * When modifying make sure any changes does not introduce data races.
 *
 * this function may only read/write the skin and infill from the *current* layer.
 */
void SkinInfillAreaComputation::calculateBottomSkin(const SliceLayerPart& part, int min_infill_area, Polygons& downskin)
{
    if (static_cast<int>(layer_nr - bottom_layer_count) >= 0 && bottom_layer_count > 0)
    {
        Polygons not_air = getWalls(part, layer_nr - bottom_layer_count, bottom_reference_wall_idx).offset(bottom_reference_wall_expansion);
        if (!no_small_gaps_heuristic)
        {
            for (int downskin_layer_nr = layer_nr - bottom_layer_count + 1; downskin_layer_nr < layer_nr; downskin_layer_nr++)
            {
                not_air = not_air.intersection(getWalls(part, downskin_layer_nr, bottom_reference_wall_idx).offset(bottom_reference_wall_expansion));
            }
        }
        if (min_infill_area > 0)
        {
            not_air.removeSmallAreas(min_infill_area);
        }
        downskin = downskin.difference(not_air); // skin overlaps with the walls
    }
}

void SkinInfillAreaComputation::calculateTopSkin(const SliceLayerPart& part, int min_infill_area, Polygons& upskin)
{
    if (static_cast<int>(layer_nr + top_layer_count) < static_cast<int>(mesh.layers.size()) && top_layer_count > 0)
    {
        Polygons not_air = getWalls(part, layer_nr + top_layer_count, top_reference_wall_idx).offset(top_reference_wall_expansion);
        if (!no_small_gaps_heuristic)
        {
            for (int upskin_layer_nr = layer_nr + 1; upskin_layer_nr < layer_nr + top_layer_count; upskin_layer_nr++)
            {
                not_air = not_air.intersection(getWalls(part, upskin_layer_nr, top_reference_wall_idx).offset(top_reference_wall_expansion));
            }
        }
        if (min_infill_area > 0)
        {
            not_air.removeSmallAreas(min_infill_area);
        }
        upskin = upskin.difference(not_air); // skin overlaps with the walls
    }
}

/*
 * This function is executed in a parallel region based on layer_nr.
 * When modifying make sure any changes does not introduce data races.
 *
 * this function may only read/write the skin and infill from the *current* layer.
 */
void SkinInfillAreaComputation::applySkinExpansion(const Polygons& original_outline, Polygons& upskin, Polygons& downskin)
{
    // First we set the amount of distance we want to expand, as indicated in settings
    coord_t top_outset = top_skin_expand_distance;
    coord_t bottom_outset = bottom_skin_expand_distance;

    coord_t top_min_width = mesh.getSettingInMicrons("min_skin_width_for_expansion") / 2;
    coord_t bottom_min_width = top_min_width;

    // Compensate for the pre-shrink applied because of the Skin Removal Width.
    // The skin removal width is satisfied by applying a close operation and
    // it's done in the calculateTopSkin and calculateBottomSkin, by expanding the infill.
    // The inset of that close operation is applied in calculateTopSkin and calculateBottomSkin
    // The outset of the close operation is applied at the same time as the skin expansion.
    top_outset += top_reference_wall_expansion;
    bottom_outset += bottom_reference_wall_expansion;<commit_msg>return back the top skin<commit_after>//Copyright (c) 2013 Ultimaker
//Copyright (c) 2017 <NAME>.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.

#include <cmath> // std::ceil

#include "skin.h"
#include "utils/math.h"
#include "utils/polygonUtils.h"

#define MIN_AREA_SIZE (0.4 * 0.4) 

namespace cura 
{

coord_t SkinInfillAreaComputation::getSkinLineWidth(const SliceDataStorage& storage, const SliceMeshStorage& mesh, int layer_nr)
{
    coord_t skin_line_width = mesh.getSettingInMicrons("skin_line_width");
    if (layer_nr == 0)
    {
        const ExtruderTrain& train_skin = *storage.meshgroup->getExtruderTrain(mesh.getSettingAsExtruderNr("top_bottom_extruder_nr"));
        skin_line_width *= train_skin.getSettingAsRatio("initial_layer_line_width_factor");
    }
    return skin_line_width;
}

coord_t SkinInfillAreaComputation::getWallLineWidth0(const SliceDataStorage& storage, const SliceMeshStorage& mesh, int layer_nr)
{
    coord_t wall_line_width_0 = mesh.getSettingInMicrons("wall_line_width_0");
    if (layer_nr == 0)
    {
        const ExtruderTrain& train_wall_0 = *storage.meshgroup->getExtruderTrain(mesh.getSettingAsExtruderNr("wall_0_extruder_nr"));
        wall_line_width_0 *= train_wall_0.getSettingAsRatio("initial_layer_line_width_factor");
    }
    return wall_line_width_0;
}
coord_t SkinInfillAreaComputation::getWallLineWidthX(const SliceDataStorage& storage, const SliceMeshStorage& mesh, int layer_nr)
{
    coord_t wall_line_width_x = mesh.getSettingInMicrons("wall_line_width_x");
    if (layer_nr == 0)
    {
        const ExtruderTrain& train_wall_x = *storage.meshgroup->getExtruderTrain(mesh.getSettingAsExtruderNr("wall_x_extruder_nr"));
        wall_line_width_x *= train_wall_x.getSettingAsRatio("initial_layer_line_width_factor");
    }
    return wall_line_width_x;
}
coord_t SkinInfillAreaComputation::getInfillSkinOverlap(const SliceDataStorage& storage, const SliceMeshStorage& mesh, int layer_nr, coord_t innermost_wall_line_width)
{
    coord_t infill_skin_overlap = 0;
    { // compute infill_skin_overlap
        const ExtruderTrain& train_infill = *storage.meshgroup->getExtruderTrain(mesh.getSettingAsExtruderNr("infill_extruder_nr"));
        const coord_t infill_line_width_factor = (layer_nr == 0) ? train_infill.getSettingAsRatio("initial_layer_line_width_factor") : 1.0;
        const bool infill_is_dense = mesh.getSettingInMicrons("infill_line_distance") < mesh.getSettingInMicrons("infill_line_width") * infill_line_width_factor + 10;
        if (!infill_is_dense && mesh.getSettingAsFillMethod("infill_pattern") != EFillMethod::CONCENTRIC)
        {
            infill_skin_overlap = innermost_wall_line_width / 2;
        }
    }
    return infill_skin_overlap;
}

SkinInfillAreaComputation::SkinInfillAreaComputation(int layer_nr, const SliceDataStorage& storage, SliceMeshStorage& mesh, bool process_infill)
: layer_nr(layer_nr)
, mesh(mesh)
, bottom_layer_count(mesh.getSettingAsCount("bottom_layers"))
, top_layer_count(mesh.getSettingAsCount("top_layers"))
, wall_line_count(mesh.getSettingAsCount("wall_line_count"))
, skin_line_width(getSkinLineWidth(storage, mesh, layer_nr))
, wall_line_width_0(getWallLineWidth0(storage, mesh, layer_nr))
, wall_line_width_x(getWallLineWidthX(storage, mesh, layer_nr))
, innermost_wall_line_width((wall_line_count == 1) ? wall_line_width_0 : wall_line_width_x)
, infill_skin_overlap(getInfillSkinOverlap(storage, mesh, layer_nr, innermost_wall_line_width))
, skin_inset_count(mesh.getSettingAsCount("skin_outline_count"))
, no_small_gaps_heuristic(mesh.getSettingBoolean("skin_no_small_gaps_heuristic"))
, process_infill(process_infill)
, top_reference_wall_expansion(mesh.getSettingInMicrons("top_skin_preshrink"))
, bottom_reference_wall_expansion(mesh.getSettingInMicrons("bottom_skin_preshrink"))
, top_skin_expand_distance(mesh.getSettingInMicrons("top_skin_expand_distance"))
, bottom_skin_expand_distance(mesh.getSettingInMicrons("bottom_skin_expand_distance"))
, top_reference_wall_idx(getReferenceWallIdx(top_reference_wall_expansion))
, bottom_reference_wall_idx(getReferenceWallIdx(bottom_reference_wall_expansion))
{
}

/*
 * This function is executed in a parallel region based on layer_nr.
 * When modifying make sure any changes does not introduce data races.
 *
 * this function may only read/write the skin and infill from the *current* layer.
 */
Polygons SkinInfillAreaComputation::getWalls(const SliceLayerPart& part_here, int layer2_nr, unsigned int wall_idx)
{
    Polygons result;
    if (layer2_nr >= static_cast<int>(mesh.layers.size()))
    {
        return result;
    }
    const SliceLayer& layer2 = mesh.layers[layer2_nr];
    for (const SliceLayerPart& part2 : layer2.parts)
    {
        if (part_here.boundaryBox.hit(part2.boundaryBox))
        {
            if (wall_idx <= 0)
            {
                result.add(part2.outline);
            }
            else if (wall_idx <= part2.insets.size())
            {
                result.add(part2.insets[wall_idx - 1]); // -1 because it's a 1-based index
            }
        }
    }
    return result;
};

int SkinInfillAreaComputation::getReferenceWallIdx(coord_t& preshrink) const
{
    for (int wall_idx = wall_line_count; wall_idx > 0; wall_idx--)
    {
        coord_t wall_line_width = (wall_idx > 1)? wall_line_width_x : wall_line_width_0;
        int next_wall_idx = wall_idx - 1;
        coord_t next_wall_line_width = (next_wall_idx > 1)? wall_line_width_x : (next_wall_idx == 0)? 0 : wall_line_width_0;
        coord_t diff_to_next_wall = (wall_line_width + next_wall_line_width) / 2;
        if (std::abs(preshrink - diff_to_next_wall) <= 10)
        { // snap preshrink to closest wall
            preshrink = 0;
            return next_wall_idx;
        }
        if (preshrink < diff_to_next_wall)
        {
            return wall_idx;
        }
        preshrink -= diff_to_next_wall;
    }
    return 0;
}

/*
 * This function is executed in a parallel region based on layer_nr.
 * When modifying make sure any changes does not introduce data races.
 *
 * generateSkinAreas reads data from mesh.layers.parts[*].insets and writes to mesh.layers[n].parts[*].skin_parts
 * generateSkinInsets only read/writes the skin_parts from the current layer.
 *
 * generateSkins therefore reads (depends on) data from mesh.layers[*].parts[*].insets and writes mesh.layers[n].parts[*].skin_parts
 */
void SkinInfillAreaComputation::generateSkinsAndInfill()
{
    generateSkinAndInfillAreas();

    SliceLayer* layer = &mesh.layers[layer_nr];
    for (unsigned int part_nr = 0; part_nr < layer->parts.size(); part_nr++)
    {
        SliceLayerPart& part = layer->parts[part_nr];
        generateSkinInsetsAndInnerSkinInfill(&part);

        generateRoofing(part);
    }
}

/*
 * This function is executed in a parallel region based on layer_nr.
 * When modifying make sure any changes does not introduce data races.
 *
 * generateSkinAreas reads data from mesh.layers[*].parts[*].insets and writes to mesh.layers[n].parts[*].skin_parts
 */
void SkinInfillAreaComputation::generateSkinAndInfillAreas()
{
    SliceLayer& layer = mesh.layers[layer_nr];

    if (!process_infill && bottom_layer_count == 0 && top_layer_count == 0)
    {
        return;
    }

    for (unsigned int part_nr = 0; part_nr < layer.parts.size(); part_nr++)
    {
        SliceLayerPart& part = layer.parts[part_nr];

        if (static_cast<int>(part.insets.size()) < wall_line_count)
        {
            continue; // the last wall is not present, the part should only get inter perimeter gaps, but no skin or infill.
        }
        generateSkinAndInfillAreas(part);
    }
}

/*
 * This function is executed in a parallel region based on layer_nr.
 * When modifying make sure any changes does not introduce data races.
 *
 * generateSkinAreas reads data from mesh.layers[*].parts[*].insets and writes to mesh.layers[n].parts[*].skin_parts
 */
void SkinInfillAreaComputation::generateSkinAndInfillAreas(SliceLayerPart& part)
{
    int min_infill_area = mesh.getSettingInMillimeters("min_infill_area");

    Polygons original_outline = part.insets.back().offset(-innermost_wall_line_width / 2);

    // make a copy of the outline which we later intersect and union with the resized skins to ensure the resized skin isn't too large or removed completely.
    Polygons upskin;
    if (top_layer_count > 0)
    {
        upskin = Polygons(original_outline);
    }
    Polygons downskin;
    if (bottom_layer_count > 0)
    {
        downskin = Polygons(original_outline);
    }

    calculateBottomSkin(part, min_infill_area, downskin);

    calculateTopSkin(part, min_infill_area, upskin);

    applySkinExpansion(original_outline, upskin, downskin);

    // now combine the resized upskin and downskin
    Polygons skin = upskin.unionPolygons(downskin);

    skin.removeSmallAreas(MIN_AREA_SIZE);

    if (process_infill)
    { // process infill when infill density > 0
        // or when other infill meshes want to modify this infill
        generateInfill(part, skin);
    }

    for (PolygonsPart& skin_area_part : skin.splitIntoParts())
    {
        part.skin_parts.emplace_back();
        part.skin_parts.back().outline = skin_area_part;
    }
}

/*
 * This function is executed in a parallel region based on layer_nr.
 * When modifying make sure any changes does not introduce data races.
 *
 * this function may only read/write the skin and infill from the *current* layer.
 */
void SkinInfillAreaComputation::calculateBottomSkin(const SliceLayerPart& part, int min_infill_area, Polygons& downskin)
{
    if (static_cast<int>(layer_nr - bottom_layer_count) >= 0 && bottom_layer_count > 0)
    {
        Polygons not_air = getWalls(part, layer_nr - bottom_layer_count, bottom_reference_wall_idx).offset(bottom_reference_wall_expansion);
        if (!no_small_gaps_heuristic)
        {
            for (int downskin_layer_nr = layer_nr - bottom_layer_count + 1; downskin_layer_nr < layer_nr; downskin_layer_nr++)
            {
                not_air = not_air.intersection(getWalls(part, downskin_layer_nr, bottom_reference_wall_idx).offset(bottom_reference_wall_expansion));
            }
        }
        if (min_infill_area > 0)
        {
            not_air.removeSmallAreas(min_infill_area);
        }
        downskin = downskin.difference(not_air); // skin overlaps with the walls
    }
}

void SkinInfillAreaComputation::calculateTopSkin(const SliceLayerPart& part, int min_infill_area, Polygons& upskin)
{
    if (static_cast<int>(layer_nr + top_layer_count) < static_cast<int>(mesh.layers.size()) && top_layer_count > 0)
    {
        Polygons not_air = getWalls(part, layer_nr + top_layer_count, top_reference_wall_idx).offset(top_reference_wall_expansion);
        if (!no_small_gaps_heuristic)
        {
            for (int upskin_layer_nr = layer_nr + 1; upskin_layer_nr < layer_nr + top_layer_count; upskin_layer_nr++)
            {
                not_air = not_air.intersection(getWalls(part, upskin_layer_nr, top_reference_wall_idx).offset(top_reference_wall_expansion));
            }
        }

        // Prevent removing top skip layers
        Polygons upskin_before(upskin.difference(not_air));
        
        if (min_infill_area > 0)
        {
            not_air.removeSmallAreas(min_infill_area);
        }

        upskin = upskin.difference(not_air); // skin overlaps with the walls
        upskin = upskin.unionPolygons(upskin_before); // in some cases the top skin layer might be removed. To prevent it add them back
    }
}

/*
 * This function is executed in a parallel region based on layer_nr.
 * When modifying make sure any changes does not introduce data races.
 *
 * this function may only read/write the skin and infill from the *current* layer.
 */
void SkinInfillAreaComputation::applySkinExpansion(const Polygons& original_outline, Polygons& upskin, Polygons& downskin)
{
    // First we set the amount of distance we want to expand, as indicated in settings
    coord_t top_outset = top_skin_expand_distance;
    coord_t bottom_outset = bottom_skin_expand_distance;

    coord_t top_min_width = mesh.getSettingInMicrons("min_skin_width_for_expansion") / 2;
    coord_t bottom_min_width = top_min_width;

    // Compensate for the pre-shrink applied because of the Skin Removal Width.
    // The skin removal width is satisfied by applying a close operation and
    // it's done in the calculateTopSkin and calculateBottomSkin, by expanding the infill.
    // The inset of that close operation is applied in calculateTopSkin and calculateBottomSkin
    // The outset of the close operation is applied at the same time as the skin expansion.
    top_outset += top_reference_wall_expansion;
    bottom_outset += bottom_reference_wall_expansion;
{'fraction_non_alphanumeric': 0.09043917452551001, 'fraction_numerical': 0.006155545910113246, 'mean_word_length': 3.9830908375933936, 'pattern_counts': {'":': 0, '<': 38, '<?xml version=': 0, '>': 52, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3337664', 'n_tokens_mistral': 8374, 'n_tokens_neox': 8076, 'n_words': 1757}