942 linhas
37 KiB
C++
942 linhas
37 KiB
C++
#include <cstring>
|
|
#include "gcodePlanner.h"
|
|
#include "pathOrderOptimizer.h"
|
|
#include "sliceDataStorage.h"
|
|
#include "debug.h" // debugging
|
|
#include "utils/polygonUtils.h"
|
|
#include "MergeInfillLines.h"
|
|
|
|
namespace cura {
|
|
|
|
TimeMaterialEstimates TimeMaterialEstimates::operator-(const TimeMaterialEstimates& other)
|
|
{
|
|
return TimeMaterialEstimates(extrude_time - other.extrude_time,unretracted_travel_time - other.unretracted_travel_time,retracted_travel_time - other.retracted_travel_time,material - other.material);
|
|
}
|
|
|
|
TimeMaterialEstimates& TimeMaterialEstimates::operator-=(const TimeMaterialEstimates& other)
|
|
{
|
|
extrude_time -= other.extrude_time;
|
|
unretracted_travel_time -= other.unretracted_travel_time;
|
|
retracted_travel_time -= other.retracted_travel_time;
|
|
material -= other.material;
|
|
return *this;
|
|
}
|
|
|
|
GCodePath* GCodePlanner::getLatestPathWithConfig(GCodePathConfig* config, SpaceFillType space_fill_type, float flow, bool spiralize)
|
|
{
|
|
std::vector<GCodePath>& paths = extruder_plans.back().paths;
|
|
if (paths.size() > 0 && paths.back().config == config && !paths.back().done && paths.back().flow == flow) // spiralize can only change when a travel path is in between
|
|
return &paths.back();
|
|
paths.emplace_back();
|
|
GCodePath* ret = &paths.back();
|
|
ret->retract = false;
|
|
ret->config = config;
|
|
ret->done = false;
|
|
ret->flow = flow;
|
|
ret->spiralize = spiralize;
|
|
ret->space_fill_type = space_fill_type;
|
|
if (!config->isTravelPath())
|
|
{
|
|
last_retraction_config = config->retraction_config;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void GCodePlanner::forceNewPathStart()
|
|
{
|
|
std::vector<GCodePath>& paths = extruder_plans.back().paths;
|
|
if (paths.size() > 0)
|
|
paths[paths.size()-1].done = true;
|
|
}
|
|
|
|
GCodePlanner::GCodePlanner(SliceDataStorage& storage, unsigned int layer_nr, int z, int layer_thickness, Point last_position, int current_extruder, bool is_inside_mesh, FanSpeedLayerTimeSettings& fan_speed_layer_time_settings, CombingMode combing_mode, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance)
|
|
: storage(storage)
|
|
, layer_nr(layer_nr)
|
|
, z(z)
|
|
, layer_thickness(layer_thickness)
|
|
, start_position(last_position)
|
|
, lastPosition(last_position)
|
|
, comb_boundary_inside(computeCombBoundaryInside(combing_mode))
|
|
, fan_speed_layer_time_settings(fan_speed_layer_time_settings)
|
|
{
|
|
extruder_plans.reserve(storage.meshgroup->getExtruderCount());
|
|
extruder_plans.emplace_back(current_extruder);
|
|
comb = nullptr;
|
|
was_inside = is_inside_mesh;
|
|
is_inside = false; // assumes the next move will not be to inside a layer part (overwritten just before going into a layer part)
|
|
last_retraction_config = &storage.retraction_config_per_extruder[current_extruder]; // start with general config
|
|
setExtrudeSpeedFactor(1.0);
|
|
setTravelSpeedFactor(1.0);
|
|
extraTime = 0.0;
|
|
totalPrintTime = 0.0;
|
|
if (combing_mode != CombingMode::OFF)
|
|
{
|
|
comb = new Comb(storage, layer_nr, comb_boundary_inside, comb_boundary_offset, travel_avoid_other_parts, travel_avoid_distance);
|
|
}
|
|
else
|
|
comb = nullptr;
|
|
}
|
|
|
|
GCodePlanner::~GCodePlanner()
|
|
{
|
|
if (comb)
|
|
delete comb;
|
|
}
|
|
|
|
Polygons GCodePlanner::computeCombBoundaryInside(CombingMode combing_mode)
|
|
{
|
|
if (combing_mode == CombingMode::OFF)
|
|
{
|
|
return Polygons();
|
|
}
|
|
if (layer_nr < 0)
|
|
{ // when a raft is present
|
|
if (combing_mode == CombingMode::NO_SKIN)
|
|
{
|
|
return Polygons();
|
|
}
|
|
else
|
|
{
|
|
return storage.raftOutline.offset(MM2INT(0.1));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Polygons layer_walls;
|
|
for (SliceMeshStorage& mesh : storage.meshes)
|
|
{
|
|
SliceLayer& layer = mesh.layers[layer_nr];
|
|
if (mesh.getSettingAsCombingMode("retraction_combing") == CombingMode::NO_SKIN)
|
|
{
|
|
for (SliceLayerPart& part : layer.parts)
|
|
{
|
|
layer_walls.add(part.infill_area);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
layer.getSecondOrInnermostWalls(layer_walls);
|
|
}
|
|
}
|
|
return layer_walls;
|
|
}
|
|
}
|
|
|
|
void GCodePlanner::setIsInside(bool _is_inside)
|
|
{
|
|
is_inside = _is_inside;
|
|
}
|
|
|
|
bool GCodePlanner::setExtruder(int extruder)
|
|
{
|
|
if (extruder == extruder_plans.back().extruder)
|
|
{
|
|
return false;
|
|
}
|
|
setIsInside(false);
|
|
{ // handle end position of the prev extruder
|
|
SettingsBase* train = storage.meshgroup->getExtruderTrain(extruder_plans.back().extruder);
|
|
bool end_pos_absolute = train->getSettingBoolean("machine_extruder_end_pos_abs");
|
|
Point end_pos(train->getSettingInMicrons("machine_extruder_end_pos_x"), train->getSettingInMicrons("machine_extruder_end_pos_y"));
|
|
if (!end_pos_absolute)
|
|
{
|
|
end_pos += lastPosition;
|
|
}
|
|
else
|
|
{
|
|
Point extruder_offset(train->getSettingInMicrons("machine_nozzle_offset_x"), train->getSettingInMicrons("machine_nozzle_offset_y"));
|
|
end_pos += extruder_offset; // absolute end pos is given as a head position
|
|
}
|
|
addTravel(end_pos); // + extruder_offset cause it
|
|
}
|
|
if (extruder_plans.back().paths.empty() && extruder_plans.back().inserts.empty())
|
|
{ // first extruder plan in a layer might be empty, cause it is made with the last extruder planned in the previous layer
|
|
extruder_plans.back().extruder = extruder;
|
|
}
|
|
else
|
|
{
|
|
extruder_plans.emplace_back(extruder);
|
|
}
|
|
|
|
// forceNewPathStart(); // automatic by the fact that we start a new ExtruderPlan
|
|
|
|
{ // handle starting pos of the new extruder
|
|
SettingsBase* train = storage.meshgroup->getExtruderTrain(extruder);
|
|
bool start_pos_absolute = train->getSettingBoolean("machine_extruder_start_pos_abs");
|
|
Point start_pos(train->getSettingInMicrons("machine_extruder_start_pos_x"), train->getSettingInMicrons("machine_extruder_start_pos_y"));
|
|
if (!start_pos_absolute)
|
|
{
|
|
start_pos += lastPosition;
|
|
}
|
|
else
|
|
{
|
|
Point extruder_offset(train->getSettingInMicrons("machine_nozzle_offset_x"), train->getSettingInMicrons("machine_nozzle_offset_y"));
|
|
start_pos += extruder_offset; // absolute start pos is given as a head position
|
|
}
|
|
lastPosition = start_pos;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void GCodePlanner::moveInsideCombBoundary(int distance)
|
|
{
|
|
int max_dist2 = MM2INT(2.0) * MM2INT(2.0); // if we are further than this distance, we conclude we are not inside even though we thought we were.
|
|
// this function is to be used to move from the boudary of a part to inside the part
|
|
Point p = lastPosition; // copy, since we are going to move p
|
|
if (PolygonUtils::moveInside(comb_boundary_inside, p, distance, max_dist2) != NO_INDEX)
|
|
{
|
|
//Move inside again, so we move out of tight 90deg corners
|
|
PolygonUtils::moveInside(comb_boundary_inside, p, distance, max_dist2);
|
|
if (comb_boundary_inside.inside(p))
|
|
{
|
|
addTravel_simple(p);
|
|
//Make sure the that any retraction happens after this move, not before it by starting a new move path.
|
|
forceNewPathStart();
|
|
}
|
|
}
|
|
}
|
|
|
|
void GCodePlanner::addTravel(Point p)
|
|
{
|
|
GCodePath* path = nullptr;
|
|
GCodePathConfig& travel_config = storage.travel_config_per_extruder[extruder_plans.back().extruder];
|
|
|
|
bool combed = false;
|
|
|
|
if (comb != nullptr && lastPosition != no_point)
|
|
{
|
|
CombPaths combPaths;
|
|
combed = comb->calc(lastPosition, p, combPaths, was_inside, is_inside, last_retraction_config->retraction_min_travel_distance);
|
|
if (combed)
|
|
{
|
|
bool retract = combPaths.size() > 1;
|
|
if (!retract)
|
|
{ // check whether we want to retract
|
|
if (combPaths.throughAir)
|
|
{
|
|
retract = true;
|
|
}
|
|
else
|
|
{
|
|
for (CombPath& combPath : combPaths)
|
|
{ // retract when path moves through a boundary
|
|
if (combPath.cross_boundary)
|
|
{
|
|
retract = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (combPaths.size() == 1)
|
|
{
|
|
CombPath path = combPaths[0];
|
|
if (combPaths.throughAir && !path.cross_boundary && path.size() == 2 && path[0] == lastPosition && path[1] == p)
|
|
{ // limit the retractions from support to support, which didn't cross anything
|
|
retract = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (retract && last_retraction_config->zHop > 0)
|
|
{ // TODO: stop comb calculation early! (as soon as we see we don't end in the same part as we began)
|
|
path = getLatestPathWithConfig(&travel_config, SpaceFillType::None);
|
|
if (!shorterThen(lastPosition - p, last_retraction_config->retraction_min_travel_distance))
|
|
{
|
|
path->retract = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (CombPath& combPath : combPaths)
|
|
{ // add all comb paths (don't do anything special for paths which are moving through air)
|
|
if (combPath.size() == 0)
|
|
{
|
|
continue;
|
|
}
|
|
path = getLatestPathWithConfig(&travel_config, SpaceFillType::None);
|
|
path->retract = retract;
|
|
for (Point& combPoint : combPath)
|
|
{
|
|
path->points.push_back(combPoint);
|
|
}
|
|
lastPosition = combPath.back();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!combed) {
|
|
// no combing? always retract!
|
|
if (!shorterThen(lastPosition - p, last_retraction_config->retraction_min_travel_distance))
|
|
{
|
|
if (was_inside) // when the previous location was from printing something which is considered inside (not support or prime tower etc)
|
|
{ // then move inside the printed part, so that we don't ooze on the outer wall while retraction, but on the inside of the print.
|
|
ExtruderTrain* extr = storage.meshgroup->getExtruderTrain(getExtruder());
|
|
assert (extr != nullptr);
|
|
moveInsideCombBoundary(extr->getSettingInMicrons((extr->getSettingAsCount("wall_line_count") > 1) ? "wall_line_width_x" : "wall_line_width_0") * 1);
|
|
}
|
|
path = getLatestPathWithConfig(&travel_config, SpaceFillType::None);
|
|
path->retract = true;
|
|
}
|
|
}
|
|
|
|
addTravel_simple(p, path);
|
|
was_inside = is_inside;
|
|
}
|
|
|
|
void GCodePlanner::addTravel_simple(Point p, GCodePath* path)
|
|
{
|
|
if (path == nullptr)
|
|
{
|
|
path = getLatestPathWithConfig(&storage.travel_config_per_extruder[extruder_plans.back().extruder], SpaceFillType::None);
|
|
}
|
|
path->points.push_back(p);
|
|
lastPosition = p;
|
|
}
|
|
|
|
|
|
void GCodePlanner::addExtrusionMove(Point p, GCodePathConfig* config, SpaceFillType space_fill_type, float flow, bool spiralize)
|
|
{
|
|
getLatestPathWithConfig(config, space_fill_type, flow, spiralize)->points.push_back(p);
|
|
lastPosition = p;
|
|
}
|
|
|
|
void GCodePlanner::addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation, bool spiralize)
|
|
{
|
|
Point p0 = polygon[startIdx];
|
|
addTravel(p0);
|
|
for(unsigned int i=1; i<polygon.size(); i++)
|
|
{
|
|
Point p1 = polygon[(startIdx + i) % polygon.size()];
|
|
float flow = (wall_overlap_computation)? wall_overlap_computation->getFlow(p0, p1) : 1.0;
|
|
addExtrusionMove(p1, config, SpaceFillType::Polygons, flow, spiralize);
|
|
p0 = p1;
|
|
}
|
|
if (polygon.size() > 2)
|
|
{
|
|
Point& p1 = polygon[startIdx];
|
|
float flow = (wall_overlap_computation)? wall_overlap_computation->getFlow(p0, p1) : 1.0;
|
|
addExtrusionMove(p1, config, SpaceFillType::Polygons, flow, spiralize);
|
|
}
|
|
else
|
|
{
|
|
logWarning("WARNING: line added as polygon! (gcodePlanner)\n");
|
|
}
|
|
}
|
|
|
|
void GCodePlanner::addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation, EZSeamType z_seam_type, bool spiralize)
|
|
{
|
|
if (polygons.size() == 0)
|
|
{
|
|
return;
|
|
}
|
|
PathOrderOptimizer orderOptimizer(lastPosition, z_seam_type);
|
|
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
|
|
{
|
|
orderOptimizer.addPolygon(polygons[poly_idx]);
|
|
}
|
|
orderOptimizer.optimize();
|
|
for (unsigned int poly_idx : orderOptimizer.polyOrder)
|
|
{
|
|
addPolygon(polygons[poly_idx], orderOptimizer.polyStart[poly_idx], config, wall_overlap_computation, spiralize);
|
|
}
|
|
}
|
|
void GCodePlanner::addLinesByOptimizer(Polygons& polygons, GCodePathConfig* config, SpaceFillType space_fill_type, int wipe_dist)
|
|
{
|
|
LineOrderOptimizer orderOptimizer(lastPosition);
|
|
for (unsigned int line_idx = 0; line_idx < polygons.size(); line_idx++)
|
|
{
|
|
orderOptimizer.addPolygon(polygons[line_idx]);
|
|
}
|
|
orderOptimizer.optimize();
|
|
for (int poly_idx : orderOptimizer.polyOrder)
|
|
{
|
|
PolygonRef polygon = polygons[poly_idx];
|
|
int start = orderOptimizer.polyStart[poly_idx];
|
|
int end = 1 - start;
|
|
Point& p0 = polygon[start];
|
|
addTravel(p0);
|
|
Point& p1 = polygon[end];
|
|
addExtrusionMove(p1, config, space_fill_type);
|
|
if (wipe_dist != 0)
|
|
{
|
|
int line_width = config->getLineWidth();
|
|
if (vSize2(p1-p0) > line_width * line_width * 4)
|
|
{ // otherwise line will get optimized by combining multiple into a single extrusion move
|
|
addExtrusionMove(p1 + normal(p1-p0, wipe_dist), config, space_fill_type, 0.0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GCodePlanner::forceMinimalLayerTime(double minTime, double minimalSpeed, double travelTime, double extrudeTime)
|
|
{
|
|
double totalTime = travelTime + extrudeTime;
|
|
if (totalTime < minTime && extrudeTime > 0.0)
|
|
{
|
|
double minExtrudeTime = minTime - travelTime;
|
|
if (minExtrudeTime < 1)
|
|
minExtrudeTime = 1;
|
|
double factor = extrudeTime / minExtrudeTime;
|
|
for(ExtruderPlan& extr_plan : extruder_plans)
|
|
{
|
|
for (GCodePath& path : extr_plan.paths)
|
|
{
|
|
if (path.isTravelPath())
|
|
continue;
|
|
double speed = path.config->getSpeed() * factor;
|
|
if (speed < minimalSpeed)
|
|
factor = minimalSpeed / path.config->getSpeed();
|
|
}
|
|
}
|
|
|
|
//Only slow down for the minimal time if that will be slower.
|
|
assert(getExtrudeSpeedFactor() == 1.0); // The extrude speed factor is assumed not to be changed yet
|
|
if (factor < 1.0)
|
|
{
|
|
setExtrudeSpeedFactor(factor);
|
|
}
|
|
else
|
|
{
|
|
factor = 1.0;
|
|
}
|
|
|
|
double inv_factor = 1.0 / factor; // cause multiplication is faster than division
|
|
|
|
// Adjust stored naive time estimates
|
|
for(ExtruderPlan& extr_plan : extruder_plans)
|
|
{
|
|
extr_plan.estimates.extrude_time *= inv_factor;
|
|
for (GCodePath& path : extr_plan.paths)
|
|
{
|
|
path.estimates.extrude_time *= inv_factor;
|
|
}
|
|
}
|
|
|
|
if (minTime - (extrudeTime * inv_factor) - travelTime > 0.1)
|
|
{
|
|
this->extraTime = minTime - (extrudeTime * inv_factor) - travelTime;
|
|
}
|
|
this->totalPrintTime = (extrudeTime * inv_factor) + travelTime;
|
|
}else{
|
|
this->totalPrintTime = totalTime;
|
|
}
|
|
}
|
|
|
|
TimeMaterialEstimates GCodePlanner::computeNaiveTimeEstimates()
|
|
{
|
|
TimeMaterialEstimates ret;
|
|
Point p0 = start_position;
|
|
|
|
bool was_retracted = false; // wrong assumption; won't matter that much. (TODO)
|
|
RetractionConfig* last_retraction_config = nullptr;
|
|
for(ExtruderPlan& extr_plan : extruder_plans)
|
|
{
|
|
for (GCodePath& path : extr_plan.paths)
|
|
{
|
|
bool is_extrusion_path = false;
|
|
double* path_time_estimate;
|
|
double& material_estimate = path.estimates.material;
|
|
if (!path.isTravelPath())
|
|
{
|
|
is_extrusion_path = true;
|
|
path_time_estimate = &path.estimates.extrude_time;
|
|
}
|
|
else
|
|
{
|
|
if (path.retract)
|
|
{
|
|
path_time_estimate = &path.estimates.retracted_travel_time;
|
|
}
|
|
else
|
|
{
|
|
path_time_estimate = &path.estimates.unretracted_travel_time;
|
|
}
|
|
if (path.retract != was_retracted && last_retraction_config != nullptr)
|
|
{ // handle retraction times
|
|
double retract_unretract_time;
|
|
assert(last_retraction_config != nullptr);
|
|
RetractionConfig& retraction_config = *last_retraction_config;
|
|
if (path.retract)
|
|
{
|
|
retract_unretract_time = retraction_config.distance / retraction_config.speed;
|
|
}
|
|
else
|
|
{
|
|
retract_unretract_time = retraction_config.distance / retraction_config.primeSpeed;
|
|
}
|
|
path.estimates.retracted_travel_time += 0.5 * retract_unretract_time;
|
|
path.estimates.unretracted_travel_time += 0.5 * retract_unretract_time;
|
|
}
|
|
}
|
|
for(Point& p1 : path.points)
|
|
{
|
|
double length = vSizeMM(p0 - p1);
|
|
if (is_extrusion_path)
|
|
{
|
|
material_estimate += length * INT2MM(layer_thickness) * INT2MM(path.config->getLineWidth());
|
|
}
|
|
double thisTime = length / path.config->getSpeed();
|
|
*path_time_estimate += thisTime;
|
|
p0 = p1;
|
|
}
|
|
extr_plan.estimates += path.estimates;
|
|
if (is_extrusion_path)
|
|
{
|
|
last_retraction_config = path.config->retraction_config;
|
|
}
|
|
}
|
|
ret += extr_plan.estimates;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void GCodePlanner::processFanSpeedAndMinimalLayerTime()
|
|
{
|
|
FanSpeedLayerTimeSettings& fsml = fan_speed_layer_time_settings;
|
|
TimeMaterialEstimates estimates = computeNaiveTimeEstimates();
|
|
forceMinimalLayerTime(fsml.cool_min_layer_time, fsml.cool_min_speed, estimates.getTravelTime(), estimates.getExtrudeTime());
|
|
|
|
/*
|
|
min layer time
|
|
:
|
|
: min layer time fan speed min
|
|
| : :
|
|
^ max..|__: :
|
|
| \ :
|
|
fan | \ :
|
|
speed min..|... \:___________
|
|
|________________
|
|
layer time >
|
|
|
|
|
|
*/
|
|
// interpolate fan speed (for cool_fan_full_layer and for cool_min_layer_time_fan_speed_max)
|
|
fan_speed = fsml.cool_fan_speed_min;
|
|
double totalLayerTime = estimates.unretracted_travel_time + estimates.extrude_time;
|
|
if (totalLayerTime < fsml.cool_min_layer_time)
|
|
{
|
|
fan_speed = fsml.cool_fan_speed_max;
|
|
}
|
|
else if (totalLayerTime < fsml.cool_min_layer_time_fan_speed_max)
|
|
{
|
|
// when forceMinimalLayerTime didn't change the extrusionSpeedFactor, we adjust the fan speed
|
|
double fan_speed_diff = fsml.cool_fan_speed_max - fsml.cool_fan_speed_min;
|
|
double layer_time_diff = fsml.cool_min_layer_time_fan_speed_max - fsml.cool_min_layer_time;
|
|
double fraction_of_slope = (totalLayerTime - fsml.cool_min_layer_time) / layer_time_diff;
|
|
fan_speed = fsml.cool_fan_speed_max - fan_speed_diff * fraction_of_slope;
|
|
}
|
|
/*
|
|
Supposing no influence of minimal layer time; i.e. layer time > min layer time fan speed min:
|
|
|
|
max.. fan 'full' on layer
|
|
| :
|
|
| :
|
|
^ min..|..:________________
|
|
fan | /
|
|
speed | /
|
|
zero..|/__________________
|
|
layer nr >
|
|
|
|
|
|
*/
|
|
if (layer_nr < fsml.cool_fan_full_layer)
|
|
{
|
|
//Slow down the fan on the layers below the [cool_fan_full_layer], where layer 0 is speed 0.
|
|
fan_speed = fan_speed * layer_nr / fsml.cool_fan_full_layer;
|
|
}
|
|
}
|
|
|
|
|
|
void GCodePlanner::writeGCode(GCodeExport& gcode)
|
|
{
|
|
completeConfigs();
|
|
|
|
gcode.setLayerNr(layer_nr);
|
|
|
|
gcode.writeLayerComment(layer_nr);
|
|
|
|
gcode.setZ(z);
|
|
|
|
gcode.writeFanCommand(fan_speed);
|
|
|
|
GCodePathConfig* last_extrusion_config = nullptr;
|
|
int extruder = gcode.getExtruderNr();
|
|
|
|
for(unsigned int extruder_plan_idx = 0; extruder_plan_idx < extruder_plans.size(); extruder_plan_idx++)
|
|
{
|
|
ExtruderPlan& extruder_plan = extruder_plans[extruder_plan_idx];
|
|
if (extruder != extruder_plan.extruder)
|
|
{
|
|
extruder = extruder_plan.extruder;
|
|
gcode.switchExtruder(extruder);
|
|
}
|
|
std::vector<GCodePath>& paths = extruder_plan.paths;
|
|
|
|
extruder_plan.inserts.sort([](const NozzleTempInsert& a, const NozzleTempInsert& b) -> bool {
|
|
return a.path_idx < b.path_idx;
|
|
} );
|
|
|
|
for(unsigned int path_idx = 0; path_idx < paths.size(); path_idx++)
|
|
{
|
|
extruder_plan.handleInserts(path_idx, gcode);
|
|
|
|
GCodePath& path = paths[path_idx];
|
|
if (path.retract)
|
|
{
|
|
writeRetraction(gcode, extruder_plan_idx, path_idx);
|
|
}
|
|
if (!path.config->isTravelPath() && last_extrusion_config != path.config)
|
|
{
|
|
gcode.writeTypeComment(path.config->type);
|
|
last_extrusion_config = path.config;
|
|
}
|
|
double speed = path.config->getSpeed();
|
|
|
|
if (path.isTravelPath())// Only apply the extrudeSpeed to extrusion moves
|
|
speed *= getTravelSpeedFactor();
|
|
else
|
|
speed *= getExtrudeSpeedFactor();
|
|
|
|
int64_t nozzle_size = 400; // TODO
|
|
|
|
if (MergeInfillLines(gcode, layer_nr, paths, extruder_plan, storage.travel_config_per_extruder[extruder], nozzle_size).mergeInfillLines(speed, path_idx)) // !! has effect on path_idx !!
|
|
{ // !! has effect on path_idx !!
|
|
// works when path_idx is the index of the travel move BEFORE the infill lines to be merged
|
|
continue;
|
|
}
|
|
|
|
if (path.config->isTravelPath())
|
|
{ // early comp for travel paths, which are handled more simply
|
|
for(unsigned int point_idx = 0; point_idx < path.points.size(); point_idx++)
|
|
{
|
|
gcode.writeMove(path.points[point_idx], speed, path.getExtrusionMM3perMM());
|
|
if (point_idx == path.points.size() - 1)
|
|
{
|
|
gcode.setZ(z); // go down to extrusion level when we spiralized before on this layer
|
|
gcode.writeMove(gcode.getPositionXY(), speed, path.getExtrusionMM3perMM());
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
bool spiralize = path.spiralize;
|
|
if (!spiralize) // normal (extrusion) move (with coasting
|
|
{
|
|
CoastingConfig& coasting_config = storage.coasting_config[extruder];
|
|
bool coasting = coasting_config.coasting_enable;
|
|
if (coasting)
|
|
{
|
|
coasting = writePathWithCoasting(gcode, extruder_plan_idx, path_idx, layer_thickness, coasting_config.coasting_volume, coasting_config.coasting_speed, coasting_config.coasting_min_volume);
|
|
}
|
|
if (! coasting) // not same as 'else', cause we might have changed [coasting] in the line above...
|
|
{ // normal path to gcode algorithm
|
|
if ( // change infill |||||| to /\/\/\/\/ ...
|
|
false &&
|
|
path_idx + 2 < paths.size() // has a next move
|
|
&& paths[path_idx+1].points.size() == 1 // is single extruded line
|
|
&& paths[path_idx+1].config->isTravelPath() // next move is extrusion
|
|
&& paths[path_idx+2].config->isTravelPath() // next next move is travel
|
|
&& shorterThen(path.points.back() - gcode.getPositionXY(), 2 * nozzle_size) // preceding extrusion is close by
|
|
&& shorterThen(paths[path_idx+1].points.back() - path.points.back(), 2 * nozzle_size) // extrusion move is small
|
|
&& shorterThen(paths[path_idx+2].points.back() - paths[path_idx+1].points.back(), 2 * nozzle_size) // consecutive extrusion is close by
|
|
)
|
|
{
|
|
sendPolygon(paths[path_idx+2].config->type, gcode.getPositionXY(), paths[path_idx+2].points.back(), paths[path_idx+2].getLineWidth());
|
|
gcode.writeMove(paths[path_idx+2].points.back(), speed, paths[path_idx+1].getExtrusionMM3perMM());
|
|
path_idx += 2;
|
|
}
|
|
else
|
|
{
|
|
for(unsigned int point_idx = 0; point_idx < path.points.size(); point_idx++)
|
|
{
|
|
sendPolygon(path.config->type, gcode.getPositionXY(), path.points[point_idx], path.getLineWidth());
|
|
gcode.writeMove(path.points[point_idx], speed, path.getExtrusionMM3perMM());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{ // SPIRALIZE
|
|
//If we need to spiralize then raise the head slowly by 1 layer as this path progresses.
|
|
float totalLength = 0.0;
|
|
Point p0 = gcode.getPositionXY();
|
|
for (unsigned int _path_idx = path_idx; _path_idx < paths.size() && !paths[_path_idx].isTravelPath(); _path_idx++)
|
|
{
|
|
GCodePath& _path = paths[_path_idx];
|
|
for (unsigned int point_idx = 0; point_idx < _path.points.size(); point_idx++)
|
|
{
|
|
Point p1 = _path.points[point_idx];
|
|
totalLength += vSizeMM(p0 - p1);
|
|
p0 = p1;
|
|
}
|
|
}
|
|
|
|
float length = 0.0;
|
|
p0 = gcode.getPositionXY();
|
|
for (; path_idx < paths.size() && paths[path_idx].spiralize; path_idx++)
|
|
{ // handle all consecutive spiralized paths > CHANGES path_idx!
|
|
GCodePath& path = paths[path_idx];
|
|
for (unsigned int point_idx = 0; point_idx < path.points.size(); point_idx++)
|
|
{
|
|
Point p1 = path.points[point_idx];
|
|
length += vSizeMM(p0 - p1);
|
|
p0 = p1;
|
|
gcode.setZ(z + layer_thickness * length / totalLength);
|
|
sendPolygon(path.config->type, gcode.getPositionXY(), path.points[point_idx], path.getLineWidth());
|
|
gcode.writeMove(path.points[point_idx], speed, path.getExtrusionMM3perMM());
|
|
}
|
|
}
|
|
path_idx--; // the last path_idx didnt spiralize, so it's not part of the current spiralize path
|
|
}
|
|
}
|
|
|
|
extruder_plan.handleAllRemainingInserts(gcode);
|
|
}
|
|
|
|
gcode.updateTotalPrintTime();
|
|
if (storage.getSettingBoolean("cool_lift_head") && extraTime > 0.0)
|
|
{
|
|
gcode.writeComment("Small layer, adding delay");
|
|
if (last_extrusion_config)
|
|
{
|
|
bool extruder_switch_retract = false;// TODO: check whether we should do a retractoin_extruderSwitch; is the next path with a different extruder?
|
|
writeRetraction(gcode, extruder_switch_retract, last_extrusion_config->retraction_config);
|
|
}
|
|
gcode.setZ(gcode.getPositionZ() + MM2INT(3.0));
|
|
gcode.writeMove(gcode.getPositionXY(), storage.travel_config_per_extruder[extruder].getSpeed(), 0);
|
|
gcode.writeMove(gcode.getPositionXY() - Point(-MM2INT(20.0), 0), storage.travel_config_per_extruder[extruder].getSpeed(), 0); // TODO: is this safe?! wouldn't the head move into the sides then?!
|
|
gcode.writeDelay(extraTime);
|
|
}
|
|
}
|
|
|
|
void GCodePlanner::completeConfigs()
|
|
{
|
|
storage.support_config.setLayerHeight(layer_thickness);
|
|
storage.support_roof_config.setLayerHeight(layer_thickness);
|
|
|
|
for (SliceMeshStorage& mesh : storage.meshes)
|
|
{
|
|
mesh.inset0_config.setLayerHeight(layer_thickness);
|
|
|
|
mesh.insetX_config.setLayerHeight(layer_thickness);
|
|
mesh.skin_config.setLayerHeight(layer_thickness);
|
|
for(unsigned int idx=0; idx<MAX_INFILL_COMBINE; idx++)
|
|
{
|
|
mesh.infill_config[idx].setLayerHeight(layer_thickness);
|
|
}
|
|
}
|
|
|
|
storage.primeTower.setConfigs(storage.meshgroup, layer_thickness);
|
|
|
|
processInitialLayersSpeedup();
|
|
}
|
|
|
|
|
|
void GCodePlanner::processInitialLayersSpeedup()
|
|
{
|
|
int initial_speedup_layers = storage.getSettingAsCount("speed_slowdown_layers");
|
|
if (static_cast<int>(layer_nr) < initial_speedup_layers)
|
|
{
|
|
double initial_layer_speed;
|
|
int extruder_nr_support_infill = storage.getSettingAsIndex((layer_nr == 0)? "support_extruder_nr_layer_0" : "support_infill_extruder_nr");
|
|
initial_layer_speed = storage.meshgroup->getExtruderTrain(extruder_nr_support_infill)->getSettingInMillimetersPerSecond("speed_layer_0");
|
|
storage.support_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
|
|
|
|
int extruder_nr_support_roof = storage.getSettingAsIndex("support_roof_extruder_nr");
|
|
initial_layer_speed = storage.meshgroup->getExtruderTrain(extruder_nr_support_roof)->getSettingInMillimetersPerSecond("speed_layer_0");
|
|
storage.support_roof_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
|
|
for (SliceMeshStorage& mesh : storage.meshes)
|
|
{
|
|
initial_layer_speed = mesh.getSettingInMillimetersPerSecond("speed_layer_0");
|
|
mesh.inset0_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
|
|
mesh.insetX_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
|
|
mesh.skin_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
|
|
for (unsigned int idx = 0; idx < MAX_INFILL_COMBINE; idx++)
|
|
{
|
|
mesh.infill_config[idx].smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
|
|
}
|
|
}
|
|
}
|
|
else if (static_cast<int>(layer_nr) == initial_speedup_layers)
|
|
{
|
|
storage.support_config.setSpeedIconic();
|
|
storage.support_roof_config.setSpeedIconic();
|
|
for (SliceMeshStorage& mesh : storage.meshes)
|
|
{
|
|
mesh.inset0_config.setSpeedIconic();
|
|
mesh.insetX_config.setSpeedIconic();
|
|
mesh.skin_config.setSpeedIconic();
|
|
for (unsigned int idx = 0; idx < MAX_INFILL_COMBINE; idx++)
|
|
{
|
|
mesh.infill_config[idx].setSpeedIconic();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GCodePlanner::writeRetraction(GCodeExport& gcode, unsigned int extruder_plan_idx, unsigned int path_idx_travel_after)
|
|
{
|
|
if (makeRetractSwitchRetract(gcode, extruder_plan_idx, path_idx_travel_after))
|
|
{
|
|
gcode.writeRetraction_extruderSwitch();
|
|
}
|
|
else
|
|
{
|
|
writeRetraction(gcode, false, last_retraction_config);
|
|
}
|
|
}
|
|
void GCodePlanner::writeRetraction(GCodeExport& gcode, bool extruder_switch_retract, RetractionConfig* retraction_config)
|
|
{
|
|
assert(retraction_config != nullptr);
|
|
if (extruder_switch_retract)
|
|
{
|
|
gcode.writeRetraction_extruderSwitch();
|
|
}
|
|
else
|
|
{
|
|
gcode.writeRetraction(retraction_config);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool GCodePlanner::makeRetractSwitchRetract(GCodeExport& gcode, unsigned int extruder_plan_idx, unsigned int path_idx)
|
|
{
|
|
std::vector<GCodePath>& paths = extruder_plans[extruder_plan_idx].paths;
|
|
for (unsigned int path_idx2 = path_idx + 1; path_idx2 < paths.size(); path_idx2++)
|
|
{
|
|
if (paths[path_idx2].getExtrusionMM3perMM() > 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (extruder_plans.size() <= extruder_plan_idx+1)
|
|
{
|
|
return false; // TODO: check first extruder of the next layer! (generally only on the last layer of the second extruder)
|
|
}
|
|
|
|
if (extruder_plans[extruder_plan_idx + 1].extruder != extruder_plans[extruder_plan_idx].extruder)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool GCodePlanner::writePathWithCoasting(GCodeExport& gcode, unsigned int extruder_plan_idx, unsigned int path_idx, int64_t layerThickness, double coasting_volume, double coasting_speed, double coasting_min_volume)
|
|
{
|
|
if (coasting_volume <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
std::vector<GCodePath>& paths = extruder_plans[extruder_plan_idx].paths;
|
|
GCodePath& path = paths[path_idx];
|
|
if (path_idx + 1 >= paths.size()
|
|
||
|
|
! (!path.isTravelPath() && paths[path_idx + 1].config->isTravelPath())
|
|
||
|
|
path.points.size() < 2
|
|
)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int64_t coasting_min_dist_considered = 100; // hardcoded setting for when to not perform coasting
|
|
|
|
|
|
double extrude_speed = path.config->getSpeed() * getExtrudeSpeedFactor(); // travel speed
|
|
|
|
int64_t coasting_dist = MM2INT(MM2_2INT(coasting_volume) / layerThickness) / path.config->getLineWidth(); // closing brackets of MM2INT at weird places for precision issues
|
|
int64_t coasting_min_dist = MM2INT(MM2_2INT(coasting_min_volume + coasting_volume) / layerThickness) / path.config->getLineWidth(); // closing brackets of MM2INT at weird places for precision issues
|
|
// /\ the minimal distance when coasting will coast the full coasting volume instead of linearly less with linearly smaller paths
|
|
|
|
|
|
std::vector<int64_t> accumulated_dist_per_point; // the first accumulated dist is that of the last point! (that of the last point is always zero...)
|
|
accumulated_dist_per_point.push_back(0);
|
|
|
|
int64_t accumulated_dist = 0;
|
|
|
|
bool length_is_less_than_min_dist = true;
|
|
|
|
unsigned int acc_dist_idx_gt_coast_dist = NO_INDEX; // the index of the first point with accumulated_dist more than coasting_dist (= index into accumulated_dist_per_point)
|
|
// == the point printed BEFORE the start point for coasting
|
|
|
|
|
|
Point* last = &path.points[path.points.size() - 1];
|
|
for (unsigned int backward_point_idx = 1; backward_point_idx < path.points.size(); backward_point_idx++)
|
|
{
|
|
Point& point = path.points[path.points.size() - 1 - backward_point_idx];
|
|
int64_t dist = vSize(point - *last);
|
|
accumulated_dist += dist;
|
|
accumulated_dist_per_point.push_back(accumulated_dist);
|
|
|
|
if (acc_dist_idx_gt_coast_dist == NO_INDEX && accumulated_dist >= coasting_dist)
|
|
{
|
|
acc_dist_idx_gt_coast_dist = backward_point_idx; // the newly added point
|
|
}
|
|
|
|
if (accumulated_dist >= coasting_min_dist)
|
|
{
|
|
length_is_less_than_min_dist = false;
|
|
break;
|
|
}
|
|
|
|
last = &point;
|
|
}
|
|
|
|
if (accumulated_dist < coasting_min_dist_considered)
|
|
{
|
|
return false;
|
|
}
|
|
int64_t actual_coasting_dist = coasting_dist;
|
|
if (length_is_less_than_min_dist)
|
|
{
|
|
// in this case accumulated_dist is the length of the whole path
|
|
actual_coasting_dist = accumulated_dist * coasting_dist / coasting_min_dist;
|
|
for (acc_dist_idx_gt_coast_dist = 0 ; acc_dist_idx_gt_coast_dist < accumulated_dist_per_point.size() ; acc_dist_idx_gt_coast_dist++)
|
|
{ // search for the correct coast_dist_idx
|
|
if (accumulated_dist_per_point[acc_dist_idx_gt_coast_dist] > actual_coasting_dist)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
assert (acc_dist_idx_gt_coast_dist < accumulated_dist_per_point.size()); // something has gone wrong; coasting_min_dist < coasting_dist ?
|
|
|
|
unsigned int point_idx_before_start = path.points.size() - 1 - acc_dist_idx_gt_coast_dist;
|
|
|
|
Point start;
|
|
{ // computation of begin point of coasting
|
|
int64_t residual_dist = actual_coasting_dist - accumulated_dist_per_point[acc_dist_idx_gt_coast_dist - 1];
|
|
Point& a = path.points[point_idx_before_start];
|
|
Point& b = path.points[point_idx_before_start + 1];
|
|
start = b + normal(a-b, residual_dist);
|
|
}
|
|
|
|
{ // write normal extrude path:
|
|
for(unsigned int point_idx = 0; point_idx <= point_idx_before_start; point_idx++)
|
|
{
|
|
sendPolygon(path.config->type, gcode.getPositionXY(), path.points[point_idx], path.getLineWidth());
|
|
gcode.writeMove(path.points[point_idx], extrude_speed, path.getExtrusionMM3perMM());
|
|
}
|
|
sendPolygon(path.config->type, gcode.getPositionXY(), start, path.getLineWidth());
|
|
gcode.writeMove(start, extrude_speed, path.getExtrusionMM3perMM());
|
|
}
|
|
|
|
// write coasting path
|
|
for (unsigned int point_idx = point_idx_before_start + 1; point_idx < path.points.size(); point_idx++)
|
|
{
|
|
gcode.writeMove(path.points[point_idx], coasting_speed * path.config->getSpeed(), 0);
|
|
}
|
|
|
|
gcode.addLastCoastedVolume(path.getExtrusionMM3perMM() * INT2MM(actual_coasting_dist));
|
|
return true;
|
|
}
|
|
|
|
}//namespace cura
|