void PrimeTower::addPurgeMove(LayerPlan& gcode_layer, int extruder_nr, const ExtruderTrain *train, const Point& start_pos, const Point& end_pos, double purge_volume) const
{
    // Find out how much purging needs to be done.
    const GCodePathConfig& current_gcode_path_config = gcode_layer.configs_storage.prime_tower_config_per_extruder[extruder_nr];
    const coord_t purge_move_length = vSize(start_pos - end_pos);
    const unsigned int line_width = current_gcode_path_config.getLineWidth();
    const double layer_height_mm = (gcode_layer.getLayerNr() == 0) ? train->getSettingInMillimeters("layer_height_0") : train->getSettingInMillimeters("layer_height");
    const double normal_volume = INT2MM(INT2MM(purge_move_length * line_width)) * layer_height_mm; // Volume extruded on the "normal" move
    float purge_flow = purge_volume / normal_volume;

    const double purge_move_length_mm = INT2MM(purge_move_length);
    const double purge_move_time = purge_move_length_mm / current_gcode_path_config.getSpeed();
    const double purge_extrusion_speed_mm3_per_sec = purge_volume / purge_move_time;
    const double max_possible_extursion_speed_mm3_per_sec = 3.0;

    const double speed = current_gcode_path_config.getSpeed();
    double speed_factor = 1.0;

    if (purge_extrusion_speed_mm3_per_sec > max_possible_extursion_speed_mm3_per_sec)
    {
        // compensate the travel speed for the large extrusion amount
        const double min_time_needed_for_extrusion = purge_volume / max_possible_extursion_speed_mm3_per_sec;
        const double compensated_speed = purge_move_length_mm / min_time_needed_for_extrusion;
        speed_factor = compensated_speed / speed;
    }

    // As we need a plan, which can't have a stationary extrusion, we use an extrusion move to prime.
    // This has the added benefit that it will evenly spread the primed material inside the tower.
    gcode_layer.addExtrusionMove(end_pos, current_gcode_path_config, SpaceFillType::None, purge_flow, false, speed_factor);
}
void PrimeTower::addToGcode_denseInfill(const SliceDataStorage& storage, LayerPlan& gcode_layer, const int extruder_nr) const
{
    const ExtrusionMoves& pattern = (gcode_layer.getLayerNr() == -Raft::getFillerLayerCount(storage))
        ? pattern_per_extruder_layer0[extruder_nr]
        : patterns_per_extruder[extruder_nr][((gcode_layer.getLayerNr() % 2) + 2) % 2]; // +2) %2 to handle negative layer numbers

    const GCodePathConfig& config = gcode_layer.configs_storage.prime_tower_config_per_extruder[extruder_nr];

    gcode_layer.addPolygonsByOptimizer(pattern.polygons, config);
    gcode_layer.addLinesByOptimizer(pattern.lines, config, SpaceFillType::Lines);
}
void PrimeTower::addToGcode(const SliceDataStorage& storage, LayerPlan& gcode_layer, const GCodeExport& gcode, const int prev_extruder, const int new_extruder) const
{
    if (!enabled)
    {
        return;
    }
    if (gcode_layer.getPrimeTowerIsPlanned())
    { // don't print the prime tower if it has been printed already
        return;
    }

    if (gcode_layer.getLayerNr() > storage.max_print_height_second_to_last_extruder + 1)
    {
        return;
    }

    bool pre_wipe = storage.meshgroup->getExtruderTrain(new_extruder)->getSettingBoolean("dual_pre_wipe");
    bool post_wipe = storage.meshgroup->getExtruderTrain(prev_extruder)->getSettingBoolean("prime_tower_wipe_enabled");

    if (prev_extruder == new_extruder)
    {
        pre_wipe = false;
        post_wipe = false;
    }
    // pre-wipe:
    if (pre_wipe)
    {
        preWipeAndPurge(storage, gcode_layer, new_extruder);
    }

    addToGcode_denseInfill(storage, gcode_layer, new_extruder);

    // post-wipe:
    if (post_wipe)
    { //Make sure we wipe the old extruder on the prime tower.
        gcode_layer.addTravel(post_wipe_point - gcode.getExtruderOffset(prev_extruder) + gcode.getExtruderOffset(new_extruder));
    }

    gcode_layer.setPrimeTowerIsPlanned();
}
void PrimeTower::preWipeAndPurge(const SliceDataStorage& storage, LayerPlan& gcode_layer, const int extruder_nr) const
{
    int current_pre_wipe_location_idx = (pre_wipe_location_skip * gcode_layer.getLayerNr()) % number_of_pre_wipe_locations;
    const ClosestPolygonPoint wipe_location = pre_wipe_locations[current_pre_wipe_location_idx];

    const ExtruderTrain* train = storage.meshgroup->getExtruderTrain(extruder_nr);
    const int inward_dist = train->getSettingInMicrons("machine_nozzle_size") * 3 / 2 ;
    const int start_dist = train->getSettingInMicrons("machine_nozzle_size") * 2;
    const Point prime_end = PolygonUtils::moveInsideDiagonally(wipe_location, inward_dist);
    const Point outward_dir = wipe_location.location - prime_end;
    const Point prime_start = wipe_location.location + normal(outward_dir, start_dist);

    const double purge_volume = std::max(0.0, train->getSettingInCubicMillimeters("prime_tower_purge_volume")); // Volume to be primed
    if (wipe_from_middle)
    {
        // for hollow wipe tower:
        // start from above
        // go to wipe start
        // go to the Z height of the previous/current layer
        // wipe
        // go to normal layer height (automatically on the next extrusion move)...
        GCodePath& toward_middle = gcode_layer.addTravel(middle);
        toward_middle.perform_z_hop = true;
        gcode_layer.forceNewPathStart();

        if (purge_volume > 0)
        {
            addPurgeMove(gcode_layer, extruder_nr, train, middle, prime_start, purge_volume);
        }
        else
        {
            // Normal move behavior to wipe start location.
            GCodePath& toward_wipe_start = gcode_layer.addTravel_simple(prime_start);
            toward_wipe_start.perform_z_hop = false;
            toward_wipe_start.retract = true;
        }
    }
    else
    {
        if (purge_volume > 0)
        {
            // Find location to start purge (we're purging right outside of the tower)
            const Point purge_start = prime_start + normal(outward_dir, start_dist);
            gcode_layer.addTravel(purge_start);

            addPurgeMove(gcode_layer, extruder_nr, train, purge_start, prime_start, purge_volume);
        }
        gcode_layer.addTravel(prime_start);
    }

    float flow = 0.0001; // Force this path being interpreted as an extrusion path, so that no Z hop will occur (TODO: really separately handle travel and extrusion moves)
    gcode_layer.addExtrusionMove(prime_end, gcode_layer.configs_storage.prime_tower_config_per_extruder[extruder_nr], SpaceFillType::None, flow);
}
bool SpaghettiInfillPathGenerator::processSpaghettiInfill(const SliceDataStorage& storage, const FffGcodeWriter& fff_gcode_writer, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const int extruder_nr, const PathConfigStorage::MeshPathConfigs& mesh_config, const SliceLayerPart& part, int infill_line_distance, int infill_overlap, int infill_angle, const Point& infill_origin)
{
    if (extruder_nr != mesh.getSettingAsExtruderNr("infill_extruder_nr"))
    {
        return false;
    }
    bool added_something = false;
    const GCodePathConfig& config = mesh_config.infill_config[0];
    const EFillMethod pattern = mesh.getSettingAsFillMethod("infill_pattern");
    const bool zig_zaggify_infill = mesh.getSettingBoolean("zig_zaggify_infill");
    const bool connect_polygons = true; // spaghetti infill should have as least as possible travel moves
    const unsigned int infill_line_width = config.getLineWidth();
    constexpr int infill_multiplier = 1;
    const int64_t infill_shift = 0;
    constexpr int wall_line_count = 0;
    const int64_t outline_offset = 0;
    const double layer_height_mm = (gcode_layer.getLayerNr() == 0) ? mesh.getSettingInMillimeters("layer_height_0") : mesh.getSettingInMillimeters("layer_height");

    // For each part on this layer which is used to fill that part and parts below:
    for (const std::pair<Polygons, double>& filling_area : part.spaghetti_infill_volumes)
    {
        Polygons infill_lines;
        Polygons infill_polygons;

        const Polygons& area = filling_area.first; // Area of the top within which to move while extruding (might be empty if the spaghetti_inset was too large)
        const double total_volume = filling_area.second * mesh.getSettingAsRatio("spaghetti_flow") + mesh.getSettingInCubicMillimeters("spaghetti_infill_extra_volume"); // volume to be extruded
        if (total_volume <= 0.0)
        {
            continue;
        }

        // generate zigzag print head paths
        Polygons* perimeter_gaps_output = nullptr;
        const bool connected_zigzags = true;
        const bool use_endpieces = false;
        Infill infill_comp(pattern, zig_zaggify_infill, connect_polygons, area, outline_offset
            , infill_line_width, infill_line_distance, infill_overlap, infill_multiplier, infill_angle, gcode_layer.z,
            infill_shift, wall_line_count, infill_origin, perimeter_gaps_output, connected_zigzags, use_endpieces
            , mesh.getSettingInMicrons("cross_infill_pocket_size"));
        // cross_fill_patterns is only generated when spaghetti infill is not used,
        // so we pass nullptr here.
        infill_comp.generate(infill_polygons, infill_lines, nullptr, &mesh);

        // add paths to plan with a higher flow ratio in order to extrude the required amount.
        const coord_t total_length = infill_polygons.polygonLength() + infill_lines.polyLineLength();
        if (total_length > 0)
        { // zigzag path generation actually generated paths
            // calculate the normal volume extruded when using the layer height and line width to calculate extrusion
            const double normal_volume = INT2MM(INT2MM(total_length * infill_line_width)) * layer_height_mm;
            assert(normal_volume > 0.0);
            const float flow_ratio = total_volume / normal_volume;
            assert(flow_ratio / mesh.getSettingAsRatio("spaghetti_flow") >= 0.9);
            assert(!std::isnan(flow_ratio) && !std::isinf(flow_ratio));

            if (!infill_polygons.empty() || !infill_lines.empty())
            {
                added_something = true;
                fff_gcode_writer.setExtruder_addPrime(storage, gcode_layer, extruder_nr);
                if (!infill_polygons.empty())
                {
                    constexpr bool force_comb_retract = false;
                    gcode_layer.addTravel(infill_polygons[0][0], force_comb_retract);
                    gcode_layer.addPolygonsByOptimizer(infill_polygons, config, nullptr, ZSeamConfig(), 0, false, flow_ratio);
                }
                const bool is_zigzag = mesh.getSettingBoolean("zig_zaggify_infill") || pattern == EFillMethod::ZIG_ZAG;
                const coord_t wipe_dist = is_zigzag ? 0 : -mesh.getSettingInMicrons("infill_wipe_dist");
                const SpaceFillType line_type = is_zigzag ? SpaceFillType::Lines : SpaceFillType::PolyLines;
                gcode_layer.addLinesByOptimizer(infill_lines, config, line_type, false, wipe_dist, flow_ratio);
            }
        }
        else
        { // zigzag path generation couldn't generate paths, probably because the area was too small
            // generate small path near the middle of the filling area
            // note that we need a path with positive length because that is currently the only way to insert an extrusion in a layer plan
            constexpr int path_length = 10;
            Point middle = AABB(area).getMiddle();
            if (!area.inside(middle))
            {
                PolygonUtils::ensureInsideOrOutside(area, middle, infill_line_width / 2);
            }
            const double normal_volume = INT2MM(INT2MM(path_length * infill_line_width)) * layer_height_mm;
            const float flow_ratio = total_volume / normal_volume;
            gcode_layer.addTravel(middle);
            gcode_layer.addExtrusionMove(middle + Point(0, path_length), config, SpaceFillType::Lines, flow_ratio);
        }
    }
    return added_something;
}