OrcaSlicer/src/libslic3r/GCodeWriter.cpp
Lukas Matena 8c89bf748b Implemented new acceleration control behaviour for the new Marlin firmware flavor:
- show extra travel acceleration settings in 'Machine limits' page in Printer Settings
    when the new firmware flavor is selected

- updated tooltips on the config values (they were basically wrong even in the current version)

- 'Marlin (legacy)' firmware flavor behaviour should not change: it exports M204 Pa Rb Ta
    (where a, b are the values from machine limits) at the beginning of gcode and it uses
    M204 S... for feature type dependent acceleration settings (legacy variant of M204 P.. T..)

- new Marlin Firmware exports M204 Pa Rb Tc (where a,b,c are the values from machine limits).
    Feature type dependent acceleration is set using M204 P..., not overriding the travel acceleration.
2021-04-06 15:45:49 +02:00

539 lines
18 KiB
C++

#include "GCodeWriter.hpp"
#include "CustomGCode.hpp"
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <map>
#include <assert.h>
#define FLAVOR_IS(val) this->config.gcode_flavor == val
#define FLAVOR_IS_NOT(val) this->config.gcode_flavor != val
#define COMMENT(comment) if (this->config.gcode_comments && !comment.empty()) gcode << " ; " << comment;
#define PRECISION(val, precision) std::fixed << std::setprecision(precision) << (val)
#define XYZF_NUM(val) PRECISION(val, 3)
#define E_NUM(val) PRECISION(val, 5)
namespace Slic3r {
void GCodeWriter::apply_print_config(const PrintConfig &print_config)
{
this->config.apply(print_config, true);
m_extrusion_axis = this->config.get_extrusion_axis();
m_single_extruder_multi_material = print_config.single_extruder_multi_material.value;
bool is_marlin = print_config.gcode_flavor.value == gcfMarlinLegacy || print_config.gcode_flavor.value == gcfMarlinFirmware;
m_max_acceleration = std::lrint((is_marlin && print_config.machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) ?
print_config.machine_max_acceleration_extruding.values.front() : 0);
}
void GCodeWriter::set_extruders(std::vector<unsigned int> extruder_ids)
{
std::sort(extruder_ids.begin(), extruder_ids.end());
m_extruders.clear();
m_extruders.reserve(extruder_ids.size());
for (unsigned int extruder_id : extruder_ids)
m_extruders.emplace_back(Extruder(extruder_id, &this->config));
/* we enable support for multiple extruder if any extruder greater than 0 is used
(even if prints only uses that one) since we need to output Tx commands
first extruder has index 0 */
this->multiple_extruders = (*std::max_element(extruder_ids.begin(), extruder_ids.end())) > 0;
}
std::string GCodeWriter::preamble()
{
std::ostringstream gcode;
if (FLAVOR_IS_NOT(gcfMakerWare)) {
gcode << "G21 ; set units to millimeters\n";
gcode << "G90 ; use absolute coordinates\n";
}
if (FLAVOR_IS(gcfRepRapSprinter) ||
FLAVOR_IS(gcfRepRapFirmware) ||
FLAVOR_IS(gcfMarlinLegacy) ||
FLAVOR_IS(gcfMarlinFirmware) ||
FLAVOR_IS(gcfTeacup) ||
FLAVOR_IS(gcfRepetier) ||
FLAVOR_IS(gcfSmoothie))
{
if (this->config.use_relative_e_distances) {
gcode << "M83 ; use relative distances for extrusion\n";
} else {
gcode << "M82 ; use absolute distances for extrusion\n";
}
gcode << this->reset_e(true);
}
return gcode.str();
}
std::string GCodeWriter::postamble() const
{
std::ostringstream gcode;
if (FLAVOR_IS(gcfMachinekit))
gcode << "M2 ; end of program\n";
return gcode.str();
}
std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, int tool) const
{
if (wait && (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)))
return "";
std::string code, comment;
if (wait && FLAVOR_IS_NOT(gcfTeacup) && FLAVOR_IS_NOT(gcfRepRapFirmware)) {
code = "M109";
comment = "set temperature and wait for it to be reached";
} else {
if (FLAVOR_IS(gcfRepRapFirmware)) { // M104 is deprecated on RepRapFirmware
code = "G10";
} else {
code = "M104";
}
comment = "set temperature";
}
std::ostringstream gcode;
gcode << code << " ";
if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) {
gcode << "P";
} else {
gcode << "S";
}
gcode << temperature;
bool multiple_tools = this->multiple_extruders && ! m_single_extruder_multi_material;
if (tool != -1 && (multiple_tools || FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) ) {
if (FLAVOR_IS(gcfRepRapFirmware)) {
gcode << " P" << tool;
} else {
gcode << " T" << tool;
}
}
gcode << " ; " << comment << "\n";
if ((FLAVOR_IS(gcfTeacup) || FLAVOR_IS(gcfRepRapFirmware)) && wait)
gcode << "M116 ; wait for temperature to be reached\n";
return gcode.str();
}
std::string GCodeWriter::set_bed_temperature(unsigned int temperature, bool wait)
{
if (temperature == m_last_bed_temperature && (! wait || m_last_bed_temperature_reached))
return std::string();
m_last_bed_temperature = temperature;
m_last_bed_temperature_reached = wait;
std::string code, comment;
if (wait && FLAVOR_IS_NOT(gcfTeacup)) {
if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) {
code = "M109";
} else {
code = "M190";
}
comment = "set bed temperature and wait for it to be reached";
} else {
code = "M140";
comment = "set bed temperature";
}
std::ostringstream gcode;
gcode << code << " ";
if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) {
gcode << "P";
} else {
gcode << "S";
}
gcode << temperature << " ; " << comment << "\n";
if (FLAVOR_IS(gcfTeacup) && wait)
gcode << "M116 ; wait for bed temperature to be reached\n";
return gcode.str();
}
std::string GCodeWriter::set_fan(unsigned int speed, bool dont_save)
{
std::ostringstream gcode;
if (m_last_fan_speed != speed || dont_save) {
if (!dont_save) m_last_fan_speed = speed;
if (speed == 0) {
if (FLAVOR_IS(gcfTeacup)) {
gcode << "M106 S0";
} else if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) {
gcode << "M127";
} else {
gcode << "M107";
}
if (this->config.gcode_comments) gcode << " ; disable fan";
gcode << "\n";
} else {
if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) {
gcode << "M126";
} else {
gcode << "M106 ";
if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) {
gcode << "P";
} else {
gcode << "S";
}
gcode << (255.0 * speed / 100.0);
}
if (this->config.gcode_comments) gcode << " ; enable fan";
gcode << "\n";
}
}
return gcode.str();
}
std::string GCodeWriter::set_acceleration(unsigned int acceleration)
{
// Clamp the acceleration to the allowed maximum.
if (m_max_acceleration > 0 && acceleration > m_max_acceleration)
acceleration = m_max_acceleration;
if (acceleration == 0 || acceleration == m_last_acceleration)
return std::string();
m_last_acceleration = acceleration;
std::ostringstream gcode;
if (FLAVOR_IS(gcfRepetier)) {
// M201: Set max printing acceleration
gcode << "M201 X" << acceleration << " Y" << acceleration;
if (this->config.gcode_comments) gcode << " ; adjust acceleration";
gcode << "\n";
// M202: Set max travel acceleration
gcode << "M202 X" << acceleration << " Y" << acceleration;
} else if (FLAVOR_IS(gcfRepRapFirmware)) {
// M204: Set default acceleration
gcode << "M204 P" << acceleration;
} else if (FLAVOR_IS(gcfMarlinFirmware)) {
// This is new MarlinFirmware with separated print/retraction/travel acceleration.
// Use M204 P, we don't want to override travel acc by M204 S (which is deprecated anyway).
gcode << "M204 P" << acceleration;
} else {
// M204: Set default acceleration
gcode << "M204 S" << acceleration;
}
if (this->config.gcode_comments) gcode << " ; adjust acceleration";
gcode << "\n";
return gcode.str();
}
std::string GCodeWriter::reset_e(bool force)
{
if (FLAVOR_IS(gcfMach3)
|| FLAVOR_IS(gcfMakerWare)
|| FLAVOR_IS(gcfSailfish))
return "";
if (m_extruder != nullptr) {
if (m_extruder->E() == 0. && ! force)
return "";
m_extruder->reset_E();
}
if (! m_extrusion_axis.empty() && ! this->config.use_relative_e_distances) {
std::ostringstream gcode;
gcode << "G92 " << m_extrusion_axis << "0";
if (this->config.gcode_comments) gcode << " ; reset extrusion distance";
gcode << "\n";
return gcode.str();
} else {
return "";
}
}
std::string GCodeWriter::update_progress(unsigned int num, unsigned int tot, bool allow_100) const
{
if (FLAVOR_IS_NOT(gcfMakerWare) && FLAVOR_IS_NOT(gcfSailfish))
return "";
unsigned int percent = (unsigned int)floor(100.0 * num / tot + 0.5);
if (!allow_100) percent = std::min(percent, (unsigned int)99);
std::ostringstream gcode;
gcode << "M73 P" << percent;
if (this->config.gcode_comments) gcode << " ; update progress";
gcode << "\n";
return gcode.str();
}
std::string GCodeWriter::toolchange_prefix() const
{
return FLAVOR_IS(gcfMakerWare) ? "M135 T" :
FLAVOR_IS(gcfSailfish) ? "M108 T" : "T";
}
std::string GCodeWriter::toolchange(unsigned int extruder_id)
{
// set the new extruder
auto it_extruder = Slic3r::lower_bound_by_predicate(m_extruders.begin(), m_extruders.end(), [extruder_id](const Extruder &e) { return e.id() < extruder_id; });
assert(it_extruder != m_extruders.end() && it_extruder->id() == extruder_id);
m_extruder = &*it_extruder;
// return the toolchange command
// if we are running a single-extruder setup, just set the extruder and return nothing
std::ostringstream gcode;
if (this->multiple_extruders) {
gcode << this->toolchange_prefix() << extruder_id;
if (this->config.gcode_comments)
gcode << " ; change extruder";
gcode << "\n";
gcode << this->reset_e(true);
}
return gcode.str();
}
std::string GCodeWriter::set_speed(double F, const std::string &comment, const std::string &cooling_marker) const
{
assert(F > 0.);
assert(F < 100000.);
std::ostringstream gcode;
gcode << "G1 F" << XYZF_NUM(F);
COMMENT(comment);
gcode << cooling_marker;
gcode << "\n";
return gcode.str();
}
std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &comment)
{
m_pos(0) = point(0);
m_pos(1) = point(1);
std::ostringstream gcode;
gcode << "G1 X" << XYZF_NUM(point(0))
<< " Y" << XYZF_NUM(point(1))
<< " F" << XYZF_NUM(this->config.travel_speed.value * 60.0);
COMMENT(comment);
gcode << "\n";
return gcode.str();
}
std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &comment)
{
/* If target Z is lower than current Z but higher than nominal Z we
don't perform the Z move but we only move in the XY plane and
adjust the nominal Z by reducing the lift amount that will be
used for unlift. */
if (!this->will_move_z(point(2))) {
double nominal_z = m_pos(2) - m_lifted;
m_lifted -= (point(2) - nominal_z);
// In case that retract_lift == layer_height we could end up with almost zero in_m_lifted
// and a retract could be skipped (https://github.com/prusa3d/PrusaSlicer/issues/2154
if (std::abs(m_lifted) < EPSILON)
m_lifted = 0.;
return this->travel_to_xy(to_2d(point));
}
/* In all the other cases, we perform an actual XYZ move and cancel
the lift. */
m_lifted = 0;
m_pos = point;
std::ostringstream gcode;
gcode << "G1 X" << XYZF_NUM(point(0))
<< " Y" << XYZF_NUM(point(1))
<< " Z" << XYZF_NUM(point(2))
<< " F" << XYZF_NUM(this->config.travel_speed.value * 60.0);
COMMENT(comment);
gcode << "\n";
return gcode.str();
}
std::string GCodeWriter::travel_to_z(double z, const std::string &comment)
{
/* If target Z is lower than current Z but higher than nominal Z
we don't perform the move but we only adjust the nominal Z by
reducing the lift amount that will be used for unlift. */
if (!this->will_move_z(z)) {
double nominal_z = m_pos(2) - m_lifted;
m_lifted -= (z - nominal_z);
if (std::abs(m_lifted) < EPSILON)
m_lifted = 0.;
return "";
}
/* In all the other cases, we perform an actual Z move and cancel
the lift. */
m_lifted = 0;
return this->_travel_to_z(z, comment);
}
std::string GCodeWriter::_travel_to_z(double z, const std::string &comment)
{
m_pos(2) = z;
std::ostringstream gcode;
gcode << "G1 Z" << XYZF_NUM(z)
<< " F" << XYZF_NUM(this->config.travel_speed.value * 60.0);
COMMENT(comment);
gcode << "\n";
return gcode.str();
}
bool GCodeWriter::will_move_z(double z) const
{
/* If target Z is lower than current Z but higher than nominal Z
we don't perform an actual Z move. */
if (m_lifted > 0) {
double nominal_z = m_pos(2) - m_lifted;
if (z >= nominal_z && z <= m_pos(2))
return false;
}
return true;
}
std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string &comment)
{
m_pos(0) = point(0);
m_pos(1) = point(1);
m_extruder->extrude(dE);
std::ostringstream gcode;
gcode << "G1 X" << XYZF_NUM(point(0))
<< " Y" << XYZF_NUM(point(1))
<< " " << m_extrusion_axis << E_NUM(m_extruder->E());
COMMENT(comment);
gcode << "\n";
return gcode.str();
}
std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment)
{
m_pos = point;
m_lifted = 0;
m_extruder->extrude(dE);
std::ostringstream gcode;
gcode << "G1 X" << XYZF_NUM(point(0))
<< " Y" << XYZF_NUM(point(1))
<< " Z" << XYZF_NUM(point(2))
<< " " << m_extrusion_axis << E_NUM(m_extruder->E());
COMMENT(comment);
gcode << "\n";
return gcode.str();
}
std::string GCodeWriter::retract(bool before_wipe)
{
double factor = before_wipe ? m_extruder->retract_before_wipe() : 1.;
assert(factor >= 0. && factor <= 1. + EPSILON);
return this->_retract(
factor * m_extruder->retract_length(),
factor * m_extruder->retract_restart_extra(),
"retract"
);
}
std::string GCodeWriter::retract_for_toolchange(bool before_wipe)
{
double factor = before_wipe ? m_extruder->retract_before_wipe() : 1.;
assert(factor >= 0. && factor <= 1. + EPSILON);
return this->_retract(
factor * m_extruder->retract_length_toolchange(),
factor * m_extruder->retract_restart_extra_toolchange(),
"retract for toolchange"
);
}
std::string GCodeWriter::_retract(double length, double restart_extra, const std::string &comment)
{
std::ostringstream gcode;
/* If firmware retraction is enabled, we use a fake value of 1
since we ignore the actual configured retract_length which
might be 0, in which case the retraction logic gets skipped. */
if (this->config.use_firmware_retraction) length = 1;
// If we use volumetric E values we turn lengths into volumes */
if (this->config.use_volumetric_e) {
double d = m_extruder->filament_diameter();
double area = d * d * PI/4;
length = length * area;
restart_extra = restart_extra * area;
}
double dE = m_extruder->retract(length, restart_extra);
if (dE != 0) {
if (this->config.use_firmware_retraction) {
if (FLAVOR_IS(gcfMachinekit))
gcode << "G22 ; retract\n";
else
gcode << "G10 ; retract\n";
} else {
gcode << "G1 " << m_extrusion_axis << E_NUM(m_extruder->E())
<< " F" << XYZF_NUM(m_extruder->retract_speed() * 60.);
COMMENT(comment);
gcode << "\n";
}
}
if (FLAVOR_IS(gcfMakerWare))
gcode << "M103 ; extruder off\n";
return gcode.str();
}
std::string GCodeWriter::unretract()
{
std::ostringstream gcode;
if (FLAVOR_IS(gcfMakerWare))
gcode << "M101 ; extruder on\n";
double dE = m_extruder->unretract();
if (dE != 0) {
if (this->config.use_firmware_retraction) {
if (FLAVOR_IS(gcfMachinekit))
gcode << "G23 ; unretract\n";
else
gcode << "G11 ; unretract\n";
gcode << this->reset_e();
} else {
// use G1 instead of G0 because G0 will blend the restart with the previous travel move
gcode << "G1 " << m_extrusion_axis << E_NUM(m_extruder->E())
<< " F" << XYZF_NUM(m_extruder->deretract_speed() * 60.);
if (this->config.gcode_comments) gcode << " ; unretract";
gcode << "\n";
}
}
return gcode.str();
}
/* If this method is called more than once before calling unlift(),
it will not perform subsequent lifts, even if Z was raised manually
(i.e. with travel_to_z()) and thus _lifted was reduced. */
std::string GCodeWriter::lift()
{
// check whether the above/below conditions are met
double target_lift = 0;
{
double above = this->config.retract_lift_above.get_at(m_extruder->id());
double below = this->config.retract_lift_below.get_at(m_extruder->id());
if (m_pos(2) >= above && (below == 0 || m_pos(2) <= below))
target_lift = this->config.retract_lift.get_at(m_extruder->id());
}
if (m_lifted == 0 && target_lift > 0) {
m_lifted = target_lift;
return this->_travel_to_z(m_pos(2) + target_lift, "lift Z");
}
return "";
}
std::string GCodeWriter::unlift()
{
std::string gcode;
if (m_lifted > 0) {
gcode += this->_travel_to_z(m_pos(2) - m_lifted, "restore layer Z");
m_lifted = 0;
}
return gcode;
}
}