<commit_before>/** Copyright (C) 2015 Ultimaker - Released under terms of the AGPLv3 License */

#include "LayerPlanBuffer.h"
#include "gcodeExport.h"
#include "utils/logoutput.h"
#include "FffProcessor.h"

namespace cura {




void LayerPlanBuffer::setPreheatConfig(MeshGroup& settings)
{
    preheat_config.setConfig(settings);
}

void LayerPlanBuffer::push(LayerPlan& layer_plan)
{
    buffer.push_back(&layer_plan);
}

void LayerPlanBuffer::handle(LayerPlan& layer_plan, GCodeExport& gcode)
{
    push(layer_plan);
    LayerPlan* to_be_written = processBuffer();
    if (to_be_written)
    {
        to_be_written->writeGCode(gcode);
        delete to_be_written;
    }
}

LayerPlan* LayerPlanBuffer::processBuffer()
{
    if (buffer.empty())
    {
        return nullptr;
    }
    processFanSpeedLayerTime();
    if (buffer.size() >= 2)
    {
        addConnectingTravelMove(*--(--buffer.end()), *--buffer.end());
    }
    if (buffer.size() > 0)
    {
        insertTempCommands(); // insert preheat commands of the just completed layer plan (not the newly emplaced one)
    }
    if (buffer.size() > buffer_size)
    {
        LayerPlan* ret = buffer.front();
        if (CommandSocket::isInstantiated())
        {
            CommandSocket::getInstance()->flushGcode();
        }
        buffer.pop_front();
        return ret;
    }
    return nullptr;
}

void LayerPlanBuffer::flush()
{
    if (buffer.size() > 0)
    {
        insertTempCommands(); // insert preheat commands of the very last layer
    }
    while (!buffer.empty())
    {
        buffer.front()->writeGCode(gcode);
        if (CommandSocket::isInstantiated())
        {
            CommandSocket::getInstance()->flushGcode();
        }
        buffer.pop_front();
    }
}

void LayerPlanBuffer::addConnectingTravelMove(LayerPlan* prev_layer, const LayerPlan* newest_layer)
{
    std::optional<std::pair<Point, bool>> new_layer_destination_state = newest_layer->getFirstTravelDestinationState();

    if (!new_layer_destination_state)
    {
        logWarning("There are empty layers (or layers with empty extruder plans) in the print! Temperature control and cross layer travel moves might suffer.\n");
        return;
    }

    Point first_location_new_layer = new_layer_destination_state->first;

    assert(newest_layer->extruder_plans.front().extruder == prev_layer->extruder_plans.back().extruder);
    assert(newest_layer->extruder_plans.front().paths.size() > 0);
    assert(newest_layer->extruder_plans.front().paths[0].points.size() == 1);
    assert(newest_layer->extruder_plans.front().paths[0].points[0] == first_location_new_layer);


    // if the last planned position in the previous layer isn't the same as the first location of the new layer, travel to the new location
    if (!prev_layer->last_planned_position || *prev_layer->last_planned_position != first_location_new_layer)
    {
        prev_layer->setIsInside(new_layer_destination_state->second);
        prev_layer->addTravel(first_location_new_layer);
    }
}

void LayerPlanBuffer::processFanSpeedLayerTime()
{
    assert(buffer.size() > 0);
    auto newest_layer_it = --buffer.end();
    // Assume the print head is homed at the start of a meshgroup.
    // This introduces small inaccuracies for the naive layer time estimates of the first layer of the second meshgroup.
    // It's not that bad, though. They are naive estimates any way.
    Point starting_position(0, 0);
    if (buffer.size() >= 2)
    {
        auto prev_layer_it = newest_layer_it;
        prev_layer_it--;
        const LayerPlan* prev_layer = *prev_layer_it;
        starting_position = prev_layer->getLastPosition();
    }
    LayerPlan* newest_layer = *newest_layer_it;
    newest_layer->processFanSpeedAndMinimalLayerTime(starting_position);
}


void LayerPlanBuffer::insertPreheatCommand(ExtruderPlan& extruder_plan_before, double time_after_extruder_plan_start, int extruder, double temp)
{
    double acc_time = 0.0;
    for (unsigned int path_idx = extruder_plan_before.paths.size() - 1; int(path_idx) != -1 ; path_idx--)
    {
        GCodePath& path = extruder_plan_before.paths[path_idx];
        const double time_this_path = path.estimates.getTotalTime();
        acc_time += time_this_path;
        if (acc_time > time_after_extruder_plan_start)
        {
            const double time_before_path_end = acc_time - time_after_extruder_plan_start;
            bool wait = false;
            extruder_plan_before.insertCommand(path_idx, extruder, temp, wait, time_this_path - time_before_path_end);
            return;
        }
    }
    bool wait = false;
    unsigned int path_idx = 0;
    extruder_plan_before.insertCommand(path_idx, extruder, temp, wait); // insert at start of extruder plan if time_after_extruder_plan_start > extruder_plan.time
}

Preheat::WarmUpResult LayerPlanBuffer::computeStandbyTempPlan(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx)
{
    ExtruderPlan& extruder_plan = *extruder_plans[extruder_plan_idx];
    int extruder = extruder_plan.extruder;
    double initial_print_temp = extruder_plan.required_start_temperature;

    double in_between_time = 0.0; // the duration during which the extruder isn't used
    for (unsigned int extruder_plan_before_idx = extruder_plan_idx - 1; int(extruder_plan_before_idx) >= 0; extruder_plan_before_idx--)
    { // find a previous extruder plan where the same extruder is used to see what time this extruder wasn't used
        ExtruderPlan& extruder_plan_before = *extruder_plans[extruder_plan_before_idx];
        if (extruder_plan_before.extruder == extruder)
        {
            double temp_before = preheat_config.getFinalPrintTemp(extruder);
            if (temp_before == 0)
            {
                temp_before = extruder_plan_before.extrusion_temperature.value_or(initial_print_temp);
            }
            constexpr bool during_printing = false;
            Preheat::WarmUpResult warm_up = preheat_config.getWarmUpPointAfterCoolDown(in_between_time, extruder, temp_before, preheat_config.getStandbyTemp(extruder), initial_print_temp, during_printing);
            warm_up.heating_time = std::min(in_between_time, warm_up.heating_time + extra_preheat_time);
            return warm_up;
        }
        in_between_time += extruder_plan_before.estimates.getTotalTime();
    }
    // The last extruder plan with the same extruder falls outside of the buffer
    // assume the nozzle has cooled down to strandby temperature already.
    Preheat::WarmUpResult warm_up;
    warm_up.total_time_window = in_between_time;
    warm_up.lowest_temperature = preheat_config.getStandbyTemp(extruder);
    constexpr bool during_printing = false;
    warm_up.heating_time = preheat_config.getTimeToGoFromTempToTemp(extruder, warm_up.lowest_temperature, initial_print_temp, during_printing);
    if (warm_up.heating_time > in_between_time)
    {
        warm_up.heating_time = in_between_time;
        warm_up.lowest_temperature = initial_print_temp - in_between_time / preheat_config.getTimeToHeatup1Degree(extruder, during_printing);
    }
    warm_up.heating_time = warm_up.heating_time + extra_preheat_time;
    return warm_up;
}

void LayerPlanBuffer::insertPreheatCommand_singleExtrusion(ExtruderPlan& prev_extruder_plan, int extruder, double required_temp)
{
    if (!gcode.getExtruderUsesTemp(extruder))
    {
        return;
    }
    // time_before_extruder_plan_end is halved, so that at the layer change the temperature will be half way betewen the two requested temperatures
    constexpr bool during_printing = true;
    double time_before_extruder_plan_end = 0.5 * preheat_config.getTimeToGoFromTempToTemp(extruder, prev_extruder_plan.required_start_temperature, required_temp, during_printing);
    time_before_extruder_plan_end = std::min(prev_extruder_plan.estimates.getTotalTime(), time_before_extruder_plan_end);

    insertPreheatCommand(prev_extruder_plan, time_before_extruder_plan_end, extruder, required_temp);
}


void LayerPlanBuffer::handleStandbyTemp(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx, double standby_temp)
{
    ExtruderPlan& extruder_plan = *extruder_plans[extruder_plan_idx];
    int extruder = extruder_plan.extruder;
    for (unsigned int extruder_plan_before_idx = extruder_plan_idx - 2; int(extruder_plan_before_idx) >= 0; extruder_plan_before_idx--)
    {
        if (extruder_plans[extruder_plan_before_idx]->extruder == extruder)
        {
            extruder_plans[extruder_plan_before_idx + 1]->prev_extruder_standby_temp = standby_temp;
            return;
        }
    }<commit_msg>fix: cross layer extrusion temp change now uses extrusion temp rather than initial temp (CURA-3537)<commit_after>/** Copyright (C) 2015 Ultimaker - Released under terms of the AGPLv3 License */

#include "LayerPlanBuffer.h"
#include "gcodeExport.h"
#include "utils/logoutput.h"
#include "FffProcessor.h"

namespace cura {




void LayerPlanBuffer::setPreheatConfig(MeshGroup& settings)
{
    preheat_config.setConfig(settings);
}

void LayerPlanBuffer::push(LayerPlan& layer_plan)
{
    buffer.push_back(&layer_plan);
}

void LayerPlanBuffer::handle(LayerPlan& layer_plan, GCodeExport& gcode)
{
    push(layer_plan);
    LayerPlan* to_be_written = processBuffer();
    if (to_be_written)
    {
        to_be_written->writeGCode(gcode);
        delete to_be_written;
    }
}

LayerPlan* LayerPlanBuffer::processBuffer()
{
    if (buffer.empty())
    {
        return nullptr;
    }
    processFanSpeedLayerTime();
    if (buffer.size() >= 2)
    {
        addConnectingTravelMove(*--(--buffer.end()), *--buffer.end());
    }
    if (buffer.size() > 0)
    {
        insertTempCommands(); // insert preheat commands of the just completed layer plan (not the newly emplaced one)
    }
    if (buffer.size() > buffer_size)
    {
        LayerPlan* ret = buffer.front();
        if (CommandSocket::isInstantiated())
        {
            CommandSocket::getInstance()->flushGcode();
        }
        buffer.pop_front();
        return ret;
    }
    return nullptr;
}

void LayerPlanBuffer::flush()
{
    if (buffer.size() > 0)
    {
        insertTempCommands(); // insert preheat commands of the very last layer
    }
    while (!buffer.empty())
    {
        buffer.front()->writeGCode(gcode);
        if (CommandSocket::isInstantiated())
        {
            CommandSocket::getInstance()->flushGcode();
        }
        buffer.pop_front();
    }
}

void LayerPlanBuffer::addConnectingTravelMove(LayerPlan* prev_layer, const LayerPlan* newest_layer)
{
    std::optional<std::pair<Point, bool>> new_layer_destination_state = newest_layer->getFirstTravelDestinationState();

    if (!new_layer_destination_state)
    {
        logWarning("There are empty layers (or layers with empty extruder plans) in the print! Temperature control and cross layer travel moves might suffer.\n");
        return;
    }

    Point first_location_new_layer = new_layer_destination_state->first;

    assert(newest_layer->extruder_plans.front().extruder == prev_layer->extruder_plans.back().extruder);
    assert(newest_layer->extruder_plans.front().paths.size() > 0);
    assert(newest_layer->extruder_plans.front().paths[0].points.size() == 1);
    assert(newest_layer->extruder_plans.front().paths[0].points[0] == first_location_new_layer);


    // if the last planned position in the previous layer isn't the same as the first location of the new layer, travel to the new location
    if (!prev_layer->last_planned_position || *prev_layer->last_planned_position != first_location_new_layer)
    {
        prev_layer->setIsInside(new_layer_destination_state->second);
        prev_layer->addTravel(first_location_new_layer);
    }
}

void LayerPlanBuffer::processFanSpeedLayerTime()
{
    assert(buffer.size() > 0);
    auto newest_layer_it = --buffer.end();
    // Assume the print head is homed at the start of a meshgroup.
    // This introduces small inaccuracies for the naive layer time estimates of the first layer of the second meshgroup.
    // It's not that bad, though. They are naive estimates any way.
    Point starting_position(0, 0);
    if (buffer.size() >= 2)
    {
        auto prev_layer_it = newest_layer_it;
        prev_layer_it--;
        const LayerPlan* prev_layer = *prev_layer_it;
        starting_position = prev_layer->getLastPosition();
    }
    LayerPlan* newest_layer = *newest_layer_it;
    newest_layer->processFanSpeedAndMinimalLayerTime(starting_position);
}


void LayerPlanBuffer::insertPreheatCommand(ExtruderPlan& extruder_plan_before, double time_after_extruder_plan_start, int extruder, double temp)
{
    double acc_time = 0.0;
    for (unsigned int path_idx = extruder_plan_before.paths.size() - 1; int(path_idx) != -1 ; path_idx--)
    {
        GCodePath& path = extruder_plan_before.paths[path_idx];
        const double time_this_path = path.estimates.getTotalTime();
        acc_time += time_this_path;
        if (acc_time > time_after_extruder_plan_start)
        {
            const double time_before_path_end = acc_time - time_after_extruder_plan_start;
            bool wait = false;
            extruder_plan_before.insertCommand(path_idx, extruder, temp, wait, time_this_path - time_before_path_end);
            return;
        }
    }
    bool wait = false;
    unsigned int path_idx = 0;
    extruder_plan_before.insertCommand(path_idx, extruder, temp, wait); // insert at start of extruder plan if time_after_extruder_plan_start > extruder_plan.time
}

Preheat::WarmUpResult LayerPlanBuffer::computeStandbyTempPlan(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx)
{
    ExtruderPlan& extruder_plan = *extruder_plans[extruder_plan_idx];
    int extruder = extruder_plan.extruder;
    double initial_print_temp = extruder_plan.required_start_temperature;

    double in_between_time = 0.0; // the duration during which the extruder isn't used
    for (unsigned int extruder_plan_before_idx = extruder_plan_idx - 1; int(extruder_plan_before_idx) >= 0; extruder_plan_before_idx--)
    { // find a previous extruder plan where the same extruder is used to see what time this extruder wasn't used
        ExtruderPlan& extruder_plan_before = *extruder_plans[extruder_plan_before_idx];
        if (extruder_plan_before.extruder == extruder)
        {
            double temp_before = preheat_config.getFinalPrintTemp(extruder);
            if (temp_before == 0)
            {
                temp_before = extruder_plan_before.extrusion_temperature.value_or(initial_print_temp);
            }
            constexpr bool during_printing = false;
            Preheat::WarmUpResult warm_up = preheat_config.getWarmUpPointAfterCoolDown(in_between_time, extruder, temp_before, preheat_config.getStandbyTemp(extruder), initial_print_temp, during_printing);
            warm_up.heating_time = std::min(in_between_time, warm_up.heating_time + extra_preheat_time);
            return warm_up;
        }
        in_between_time += extruder_plan_before.estimates.getTotalTime();
    }
    // The last extruder plan with the same extruder falls outside of the buffer
    // assume the nozzle has cooled down to strandby temperature already.
    Preheat::WarmUpResult warm_up;
    warm_up.total_time_window = in_between_time;
    warm_up.lowest_temperature = preheat_config.getStandbyTemp(extruder);
    constexpr bool during_printing = false;
    warm_up.heating_time = preheat_config.getTimeToGoFromTempToTemp(extruder, warm_up.lowest_temperature, initial_print_temp, during_printing);
    if (warm_up.heating_time > in_between_time)
    {
        warm_up.heating_time = in_between_time;
        warm_up.lowest_temperature = initial_print_temp - in_between_time / preheat_config.getTimeToHeatup1Degree(extruder, during_printing);
    }
    warm_up.heating_time = warm_up.heating_time + extra_preheat_time;
    return warm_up;
}

void LayerPlanBuffer::insertPreheatCommand_singleExtrusion(ExtruderPlan& prev_extruder_plan, int extruder, double required_temp)
{
    if (!gcode.getExtruderUsesTemp(extruder))
    {
        return;
    }
    // time_before_extruder_plan_end is halved, so that at the layer change the temperature will be half way betewen the two requested temperatures
    constexpr bool during_printing = true;
    const double prev_extrusion_temp = prev_extruder_plan.extrusion_temperature.value_or(prev_extruder_plan.required_start_temperature);
    double time_before_extruder_plan_end = 0.5 * preheat_config.getTimeToGoFromTempToTemp(extruder, prev_extrusion_temp, required_temp, during_printing);
    time_before_extruder_plan_end = std::min(prev_extruder_plan.estimates.getTotalTime(), time_before_extruder_plan_end);

    insertPreheatCommand(prev_extruder_plan, time_before_extruder_plan_end, extruder, required_temp);
}


void LayerPlanBuffer::handleStandbyTemp(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx, double standby_temp)
{
    ExtruderPlan& extruder_plan = *extruder_plans[extruder_plan_idx];
    int extruder = extruder_plan.extruder;
    for (unsigned int extruder_plan_before_idx = extruder_plan_idx - 2; int(extruder_plan_before_idx) >= 0; extruder_plan_before_idx--)
    {
        if (extruder_plans[extruder_plan_before_idx]->extruder == extruder)
        {
            extruder_plans[extruder_plan_before_idx + 1]->prev_extruder_standby_temp = standby_temp;
            return;
        }
    }
{'fraction_non_alphanumeric': 0.0917589034811034, 'fraction_numerical': 0.004014452027298274, 'mean_word_length': 3.8641562064156205, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 75, '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': '2064129', 'n_tokens_mistral': 5758, 'n_tokens_neox': 5439, 'n_words': 1100}