2019-03-05 15:03:56 +01:00
#include "libslic3r.h"
2019-08-02 16:49:22 +02:00
#include "I18N.hpp"
2015-07-01 20:14:05 +02:00
#include "GCode.hpp"
2020-09-14 16:27:55 +02:00
#include "Exception.hpp"
2015-07-01 23:14:40 +02:00
#include "ExtrusionEntity.hpp"
2016-09-12 16:25:15 +02:00
#include "EdgeGrid.hpp"
2017-05-03 18:28:22 +02:00
#include "Geometry.hpp"
2017-09-12 09:01:48 +02:00
#include "GCode/PrintExtents.hpp"
2019-06-17 10:16:07 +02:00
#include "GCode/WipeTower.hpp"
2019-09-26 16:39:50 +02:00
#include "ShortestPath.hpp"
2020-05-26 11:09:38 +02:00
#include "Print.hpp"
2017-10-30 18:15:41 +01:00
#include "Utils.hpp"
2020-11-20 14:22:24 +01:00
#include "ClipperUtils.hpp"
2020-01-22 10:50:14 +01:00
#include "libslic3r.h"
2017-05-03 18:28:22 +02:00
2015-07-02 18:57:40 +02:00
#include <algorithm>
2015-07-02 20:24:16 +02:00
#include <cstdlib>
2021-03-10 09:00:54 +01:00
#include <chrono>
2015-09-30 16:22:49 +03:00
#include <math.h>
2020-01-21 09:55:36 +01:00
#include <string_view>
2015-07-01 20:14:05 +02:00
2017-05-03 18:28:22 +02:00
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/find.hpp>
#include <boost/foreach.hpp>
2018-09-17 12:01:02 +02:00
#include <boost/filesystem.hpp>
2018-02-14 22:25:09 +01:00
#include <boost/log/trivial.hpp>
2019-10-25 12:18:10 +02:00
#include <boost/beast/core/detail/base64.hpp>
2017-05-03 18:28:22 +02:00
2017-08-03 17:31:31 +02:00
#include <boost/nowide/iostream.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/nowide/cstdlib.hpp>
2016-09-12 16:25:15 +02:00
#include "SVG.hpp"
2019-08-23 13:06:29 +02:00
#include <tbb/parallel_for.h>
2018-01-03 17:29:49 +01:00
#include <Shiny/Shiny.h>
2019-11-04 15:38:15 +01:00
#include "miniz_extension.hpp"
2020-01-21 09:55:36 +01:00
using namespace std :: literals :: string_view_literals ;
2016-09-12 16:25:15 +02:00
#if 0
// Enable debugging and asserts, even in the release build.
#define DEBUG
#define _DEBUG
#undef NDEBUG
#endif
#include <assert.h>
2015-07-01 20:14:05 +02:00
namespace Slic3r {
2017-11-30 16:01:47 +01:00
2020-07-16 11:09:21 +02:00
//! macro used to mark string used at localization,
//! return same string
2019-08-02 16:49:22 +02:00
#define L(s) (s)
#define _(s) Slic3r::I18N::translate(s)
2017-11-30 16:01:47 +01:00
// Only add a newline in case the current G-code does not end with a newline.
2020-07-16 11:09:21 +02:00
static inline void check_add_eol ( std :: string & gcode )
{
if ( ! gcode . empty () && gcode . back () != '\n' )
gcode += '\n' ;
2019-09-09 12:59:17 +02:00
}
2019-08-25 09:01:01 +02:00
2020-07-16 11:09:21 +02:00
// Return true if tch_prefix is found in custom_gcode
static bool custom_gcode_changes_tool ( const std :: string & custom_gcode , const std :: string & tch_prefix , unsigned next_extruder )
{
bool ok = false ;
size_t from_pos = 0 ;
size_t pos = 0 ;
while (( pos = custom_gcode . find ( tch_prefix , from_pos )) != std :: string :: npos ) {
if ( pos + 1 == custom_gcode . size ())
break ;
from_pos = pos + 1 ;
// only whitespace is allowed before the command
while ( -- pos < custom_gcode . size () && custom_gcode [ pos ] != '\n' ) {
if ( ! std :: isspace ( custom_gcode [ pos ]))
goto NEXT ;
2019-01-14 19:57:41 +01:00
}
2020-07-16 11:09:21 +02:00
{
// we should also check that the extruder changes to what was expected
std :: istringstream ss ( custom_gcode . substr ( from_pos , std :: string :: npos ));
unsigned num = 0 ;
if ( ss >> num )
ok = ( num == next_extruder );
}
NEXT :;
2015-07-01 23:00:52 +02:00
}
2020-07-16 11:09:21 +02:00
return ok ;
2019-06-14 12:28:24 +02:00
}
2019-07-19 12:59:56 +02:00
2020-07-16 11:09:21 +02:00
std :: string OozePrevention :: pre_toolchange ( GCode & gcodegen )
{
std :: string gcode ;
// move to the nearest standby point
if ( ! this -> standby_points . empty ()) {
// get current position in print coordinates
Vec3d writer_pos = gcodegen . writer (). get_position ();
Point pos = Point :: new_scale ( writer_pos ( 0 ), writer_pos ( 1 ));
// find standby point
Point standby_point ;
pos . nearest_point ( this -> standby_points , & standby_point );
/* We don't call gcodegen.travel_to() because we don't need retraction (it was already
triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates
of the destination point must not be transformed by origin nor current extruder offset. */
gcode += gcodegen . writer (). travel_to_xy ( unscale ( standby_point ),
"move to standby position" );
}
if ( gcodegen . config (). standby_temperature_delta . value != 0 ) {
// we assume that heating is always slower than cooling, so no need to block
gcode += gcodegen . writer (). set_temperature
( this -> _get_temp ( gcodegen ) + gcodegen . config (). standby_temperature_delta . value , false , gcodegen . writer (). extruder () -> id ());
}
return gcode ;
}
std :: string OozePrevention :: post_toolchange ( GCode & gcodegen )
{
return ( gcodegen . config (). standby_temperature_delta . value != 0 ) ?
gcodegen . writer (). set_temperature ( this -> _get_temp ( gcodegen ), true , gcodegen . writer (). extruder () -> id ()) :
std :: string ();
}
int
OozePrevention :: _get_temp ( GCode & gcodegen )
{
return ( gcodegen . layer () != NULL && gcodegen . layer () -> id () == 0 )
? gcodegen . config (). first_layer_temperature . get_at ( gcodegen . writer (). extruder () -> id ())
: gcodegen . config (). temperature . get_at ( gcodegen . writer (). extruder () -> id ());
}
std :: string Wipe :: wipe ( GCode & gcodegen , bool toolchange )
{
std :: string gcode ;
/* Reduce feedrate a bit; travel speed is often too high to move on existing material.
Too fast = ripping of existing material; too slow = short wipe path, thus more blob. */
double wipe_speed = gcodegen . writer (). config . travel_speed . value * 0.8 ;
// get the retraction length
double length = toolchange
? gcodegen . writer (). extruder () -> retract_length_toolchange ()
: gcodegen . writer (). extruder () -> retract_length ();
// Shorten the retraction length by the amount already retracted before wipe.
length *= ( 1. - gcodegen . writer (). extruder () -> retract_before_wipe ());
if ( length > 0 ) {
/* Calculate how long we need to travel in order to consume the required
amount of retraction. In other words, how far do we move in XY at wipe_speed
for the time needed to consume retract_length at retract_speed? */
double wipe_dist = scale_ ( length / gcodegen . writer (). extruder () -> retract_speed () * wipe_speed );
/* Take the stored wipe path and replace first point with the current actual position
(they might be different, for example, in case of loop clipping). */
Polyline wipe_path ;
wipe_path . append ( gcodegen . last_pos ());
wipe_path . append (
this -> path . points . begin () + 1 ,
this -> path . points . end ()
);
wipe_path . clip_end ( wipe_path . length () - wipe_dist );
// subdivide the retraction in segments
if ( ! wipe_path . empty ()) {
2020-11-21 10:36:10 +01:00
// add tag for processor
2021-02-18 14:34:40 +01:00
#if ENABLE_VALIDATE_CUSTOM_GCODE
gcode += ";" + GCodeProcessor :: reserved_tag ( GCodeProcessor :: ETags :: Wipe_Start ) + " \n " ;
#else
2020-11-21 10:36:10 +01:00
gcode += ";" + GCodeProcessor :: Wipe_Start_Tag + " \n " ;
2021-02-18 14:34:40 +01:00
#endif // ENABLE_VALIDATE_CUSTOM_GCODE
2020-07-16 11:09:21 +02:00
for ( const Line & line : wipe_path . lines ()) {
double segment_length = line . length ();
/* Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one
due to rounding (TODO: test and/or better math for this) */
double dE = length * ( segment_length / wipe_dist ) * 0.95 ;
//FIXME one shall not generate the unnecessary G1 Fxxx commands, here wipe_speed is a constant inside this cycle.
// Is it here for the cooling markers? Or should it be outside of the cycle?
gcode += gcodegen . writer (). set_speed ( wipe_speed * 60 , "" , gcodegen . enable_cooling_markers () ? ";_WIPE" : "" );
gcode += gcodegen . writer (). extrude_to_xy (
gcodegen . point_to_gcode ( line . b ),
- dE ,
"wipe and retract"
);
}
2020-11-21 10:36:10 +01:00
// add tag for processor
2021-02-18 14:34:40 +01:00
#if ENABLE_VALIDATE_CUSTOM_GCODE
gcode += ";" + GCodeProcessor :: reserved_tag ( GCodeProcessor :: ETags :: Wipe_End ) + " \n " ;
#else
2020-11-21 10:36:10 +01:00
gcode += ";" + GCodeProcessor :: Wipe_End_Tag + " \n " ;
2021-02-18 14:34:40 +01:00
#endif // ENABLE_VALIDATE_CUSTOM_GCODE
2020-07-16 11:09:21 +02:00
gcodegen . set_last_pos ( wipe_path . points . back ());
}
// prevent wiping again on same path
this -> reset_path ();
}
return gcode ;
}
static inline Point wipe_tower_point_to_object_point ( GCode & gcodegen , const Vec2f & wipe_tower_pt )
{
return Point ( scale_ ( wipe_tower_pt . x () - gcodegen . origin ()( 0 )), scale_ ( wipe_tower_pt . y () - gcodegen . origin ()( 1 )));
}
std :: string WipeTowerIntegration :: append_tcr ( GCode & gcodegen , const WipeTower :: ToolChangeResult & tcr , int new_extruder_id , double z ) const
{
if ( new_extruder_id != - 1 && new_extruder_id != tcr . new_tool )
2020-09-14 16:27:55 +02:00
throw Slic3r :: InvalidArgument ( "Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect." );
2020-07-16 11:09:21 +02:00
std :: string gcode ;
// Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines)
// We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position
float alpha = m_wipe_tower_rotation / 180.f * float ( M_PI );
2020-09-21 13:43:47 +02:00
auto transform_wt_pt = [ & alpha , this ]( const Vec2f & pt ) -> Vec2f {
Vec2f out = Eigen :: Rotation2Df ( alpha ) * pt ;
out += m_wipe_tower_pos ;
return out ;
};
2020-07-16 11:09:21 +02:00
Vec2f start_pos = tcr . start_pos ;
Vec2f end_pos = tcr . end_pos ;
2020-09-21 14:25:53 +02:00
if ( ! tcr . priming ) {
2020-09-21 13:43:47 +02:00
start_pos = transform_wt_pt ( start_pos );
end_pos = transform_wt_pt ( end_pos );
2020-07-16 11:09:21 +02:00
}
Vec2f wipe_tower_offset = tcr . priming ? Vec2f :: Zero () : m_wipe_tower_pos ;
float wipe_tower_rotation = tcr . priming ? 0.f : alpha ;
std :: string tcr_rotated_gcode = post_process_wipe_tower_moves ( tcr , wipe_tower_offset , wipe_tower_rotation );
2020-09-21 14:25:53 +02:00
if ( ! tcr . priming ) {
2020-07-16 11:09:21 +02:00
// Move over the wipe tower.
// Retract for a tool change, using the toolchange retract value and setting the priming extra length.
gcode += gcodegen . retract ( true );
2020-11-17 10:42:27 +01:00
gcodegen . m_avoid_crossing_perimeters . use_external_mp_once ();
2020-07-16 11:09:21 +02:00
gcode += gcodegen . travel_to (
wipe_tower_point_to_object_point ( gcodegen , start_pos ),
erMixed ,
"Travel to a Wipe Tower" );
gcode += gcodegen . unretract ();
}
double current_z = gcodegen . writer (). get_position (). z ();
if ( z == - 1. ) // in case no specific z was provided, print at current_z pos
z = current_z ;
2020-09-21 14:25:53 +02:00
if ( ! is_approx ( z , current_z )) {
2020-07-16 11:09:21 +02:00
gcode += gcodegen . writer (). retract ();
gcode += gcodegen . writer (). travel_to_z ( z , "Travel down to the last wipe tower layer." );
gcode += gcodegen . writer (). unretract ();
}
// Process the end filament gcode.
std :: string end_filament_gcode_str ;
if ( gcodegen . writer (). extruder () != nullptr ) {
// Process the custom end_filament_gcode in case of single_extruder_multi_material.
unsigned int old_extruder_id = gcodegen . writer (). extruder () -> id ();
const std :: string & end_filament_gcode = gcodegen . config (). end_filament_gcode . get_at ( old_extruder_id );
if ( gcodegen . writer (). extruder () != nullptr && ! end_filament_gcode . empty ()) {
end_filament_gcode_str = gcodegen . placeholder_parser_process ( "end_filament_gcode" , end_filament_gcode , old_extruder_id );
check_add_eol ( end_filament_gcode_str );
}
}
// Process the custom toolchange_gcode. If it is empty, provide a simple Tn command to change the filament.
// Otherwise, leave control to the user completely.
std :: string toolchange_gcode_str ;
2020-09-21 14:25:53 +02:00
const std :: string & toolchange_gcode = gcodegen . config (). toolchange_gcode . value ;
2020-12-01 08:23:46 +01:00
// m_max_layer_z = std::max(m_max_layer_z, tcr.print_z);
2020-09-21 14:25:53 +02:00
if ( ! toolchange_gcode . empty ()) {
DynamicConfig config ;
int previous_extruder_id = gcodegen . writer (). extruder () ? ( int ) gcodegen . writer (). extruder () -> id () : - 1 ;
config . set_key_value ( "previous_extruder" , new ConfigOptionInt ( previous_extruder_id ));
config . set_key_value ( "next_extruder" , new ConfigOptionInt (( int ) new_extruder_id ));
config . set_key_value ( "layer_num" , new ConfigOptionInt ( gcodegen . m_layer_index ));
config . set_key_value ( "layer_z" , new ConfigOptionFloat ( tcr . print_z ));
2021-03-05 13:54:29 +01:00
config . set_key_value ( "toolchange_z" , new ConfigOptionFloat ( z ));
2020-12-01 08:23:46 +01:00
// config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
2020-09-21 14:25:53 +02:00
toolchange_gcode_str = gcodegen . placeholder_parser_process ( "toolchange_gcode" , toolchange_gcode , new_extruder_id , & config );
check_add_eol ( toolchange_gcode_str );
}
2020-07-16 11:09:21 +02:00
2020-09-21 14:25:53 +02:00
std :: string toolchange_command ;
if ( tcr . priming || ( new_extruder_id >= 0 && gcodegen . writer (). need_toolchange ( new_extruder_id )))
toolchange_command = gcodegen . writer (). toolchange ( new_extruder_id );
if ( ! custom_gcode_changes_tool ( toolchange_gcode_str , gcodegen . writer (). toolchange_prefix (), new_extruder_id ))
toolchange_gcode_str += toolchange_command ;
else {
// We have informed the m_writer about the current extruder_id, we can ignore the generated G-code.
2020-07-16 11:09:21 +02:00
}
gcodegen . placeholder_parser (). set ( "current_extruder" , new_extruder_id );
// Process the start filament gcode.
std :: string start_filament_gcode_str ;
const std :: string & start_filament_gcode = gcodegen . config (). start_filament_gcode . get_at ( new_extruder_id );
if ( ! start_filament_gcode . empty ()) {
// Process the start_filament_gcode for the active filament only.
2019-06-12 15:47:05 +02:00
DynamicConfig config ;
2020-07-16 11:09:21 +02:00
config . set_key_value ( "filament_extruder_id" , new ConfigOptionInt ( new_extruder_id ));
start_filament_gcode_str = gcodegen . placeholder_parser_process ( "start_filament_gcode" , start_filament_gcode , new_extruder_id , & config );
check_add_eol ( start_filament_gcode_str );
2019-06-12 15:47:05 +02:00
}
2020-07-16 11:09:21 +02:00
// Insert the end filament, toolchange, and start filament gcode into the generated gcode.
2019-01-07 15:12:40 +01:00
DynamicConfig config ;
2020-07-16 11:09:21 +02:00
config . set_key_value ( "end_filament_gcode" , new ConfigOptionString ( end_filament_gcode_str ));
config . set_key_value ( "toolchange_gcode" , new ConfigOptionString ( toolchange_gcode_str ));
config . set_key_value ( "start_filament_gcode" , new ConfigOptionString ( start_filament_gcode_str ));
std :: string tcr_gcode , tcr_escaped_gcode = gcodegen . placeholder_parser_process ( "tcr_rotated_gcode" , tcr_rotated_gcode , new_extruder_id , & config );
unescape_string_cstyle ( tcr_escaped_gcode , tcr_gcode );
gcode += tcr_gcode ;
check_add_eol ( toolchange_gcode_str );
// A phony move to the end position at the wipe tower.
gcodegen . writer (). travel_to_xy ( end_pos . cast < double > ());
gcodegen . set_last_pos ( wipe_tower_point_to_object_point ( gcodegen , end_pos ));
if ( ! is_approx ( z , current_z )) {
gcode += gcodegen . writer (). retract ();
gcode += gcodegen . writer (). travel_to_z ( current_z , "Travel back up to the topmost object layer." );
gcode += gcodegen . writer (). unretract ();
}
else {
// Prepare a future wipe.
2020-09-21 13:43:47 +02:00
gcodegen . m_wipe . reset_path ();
for ( const Vec2f & wipe_pt : tcr . wipe_path )
gcodegen . m_wipe . path . points . emplace_back ( wipe_tower_point_to_object_point ( gcodegen , transform_wt_pt ( wipe_pt )));
2020-07-16 11:09:21 +02:00
}
// Let the planner know we are traveling between objects.
2020-11-17 10:42:27 +01:00
gcodegen . m_avoid_crossing_perimeters . use_external_mp_once ();
2020-07-16 11:09:21 +02:00
return gcode ;
2017-11-30 16:01:47 +01:00
}
2019-06-12 15:47:05 +02:00
2020-07-16 11:09:21 +02:00
// This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode
// Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate)
std :: string WipeTowerIntegration :: post_process_wipe_tower_moves ( const WipeTower :: ToolChangeResult & tcr , const Vec2f & translation , float angle ) const
{
Vec2f extruder_offset = m_extruder_offsets [ tcr . initial_tool ]. cast < float > ();
2019-05-03 00:17:24 -04:00
2020-07-16 11:09:21 +02:00
std :: istringstream gcode_str ( tcr . gcode );
std :: string gcode_out ;
std :: string line ;
Vec2f pos = tcr . start_pos ;
Vec2f transformed_pos = pos ;
Vec2f old_pos ( - 1000.1f , - 1000.1f );
2019-05-03 00:17:24 -04:00
2020-07-16 11:09:21 +02:00
while ( gcode_str ) {
std :: getline ( gcode_str , line ); // we read the gcode line by line
// All G1 commands should be translated and rotated. X and Y coords are
// only pushed to the output when they differ from last time.
// WT generator can override this by appending the never_skip_tag
if ( line . find ( "G1 " ) == 0 ) {
bool never_skip = false ;
auto it = line . find ( WipeTower :: never_skip_tag ());
if ( it != std :: string :: npos ) {
// remove the tag and remember we saw it
never_skip = true ;
line . erase ( it , it + WipeTower :: never_skip_tag (). size ());
}
std :: ostringstream line_out ;
std :: istringstream line_str ( line );
line_str >> std :: noskipws ; // don't skip whitespace
char ch = 0 ;
while ( line_str >> ch ) {
if ( ch == 'X' || ch == 'Y' )
line_str >> ( ch == 'X' ? pos . x () : pos . y ());
else
line_out << ch ;
}
transformed_pos = Eigen :: Rotation2Df ( angle ) * pos + translation ;
if ( transformed_pos != old_pos || never_skip ) {
line = line_out . str ();
std :: ostringstream oss ;
oss << std :: fixed << std :: setprecision ( 3 ) << "G1 " ;
if ( transformed_pos . x () != old_pos . x () || never_skip )
oss << " X" << transformed_pos . x () - extruder_offset . x ();
if ( transformed_pos . y () != old_pos . y () || never_skip )
oss << " Y" << transformed_pos . y () - extruder_offset . y ();
oss << " " ;
line . replace ( line . find ( "G1 " ), 3 , oss . str ());
old_pos = transformed_pos ;
}
}
gcode_out += line + " \n " ;
// If this was a toolchange command, we should change current extruder offset
if ( line == "[toolchange_gcode]" ) {
extruder_offset = m_extruder_offsets [ tcr . new_tool ]. cast < float > ();
// If the extruder offset changed, add an extra move so everything is continuous
if ( extruder_offset != m_extruder_offsets [ tcr . initial_tool ]. cast < float > ()) {
std :: ostringstream oss ;
oss << std :: fixed << std :: setprecision ( 3 )
<< "G1 X" << transformed_pos . x () - extruder_offset . x ()
<< " Y" << transformed_pos . y () - extruder_offset . y ()
<< " \n " ;
gcode_out += oss . str ();
}
}
}
return gcode_out ;
2019-09-19 14:58:04 +02:00
}
2017-05-25 22:27:53 +02:00
2020-07-16 11:09:21 +02:00
std :: string WipeTowerIntegration :: prime ( GCode & gcodegen )
{
std :: string gcode ;
for ( const WipeTower :: ToolChangeResult & tcr : m_priming ) {
2020-09-21 14:25:53 +02:00
if ( ! tcr . extrusions . empty ())
2020-07-16 11:09:21 +02:00
gcode += append_tcr ( gcodegen , tcr , tcr . new_tool );
}
return gcode ;
}
std :: string WipeTowerIntegration :: tool_change ( GCode & gcodegen , int extruder_id , bool finish_layer )
{
std :: string gcode ;
assert ( m_layer_idx >= 0 );
2021-03-22 14:13:53 +01:00
if ( gcodegen . writer (). need_toolchange ( extruder_id ) || finish_layer ) {
2020-07-16 11:09:21 +02:00
if ( m_layer_idx < ( int ) m_tool_changes . size ()) {
if ( ! ( size_t ( m_tool_change_idx ) < m_tool_changes [ m_layer_idx ]. size ()))
2020-09-14 16:27:55 +02:00
throw Slic3r :: RuntimeError ( "Wipe tower generation failed, possibly due to empty first layer." );
2020-07-16 11:09:21 +02:00
// Calculate where the wipe tower layer will be printed. -1 means that print z will not change,
// resulting in a wipe tower with sparse layers.
double wipe_tower_z = - 1 ;
bool ignore_sparse = false ;
if ( gcodegen . config (). wipe_tower_no_sparse_layers . value ) {
wipe_tower_z = m_last_wipe_tower_print_z ;
2021-03-22 14:13:53 +01:00
ignore_sparse = ( m_tool_changes [ m_layer_idx ]. size () == 1 && m_tool_changes [ m_layer_idx ]. front (). initial_tool == m_tool_changes [ m_layer_idx ]. front (). new_tool );
2020-07-16 11:09:21 +02:00
if ( m_tool_change_idx == 0 && ! ignore_sparse )
wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes [ m_layer_idx ]. front (). layer_height ;
}
if ( ! ignore_sparse ) {
gcode += append_tcr ( gcodegen , m_tool_changes [ m_layer_idx ][ m_tool_change_idx ++ ], extruder_id , wipe_tower_z );
m_last_wipe_tower_print_z = wipe_tower_z ;
}
}
2019-09-19 14:58:04 +02:00
}
2020-07-16 11:09:21 +02:00
return gcode ;
2017-05-25 22:27:53 +02:00
}
2020-07-16 11:09:21 +02:00
// Print is finished. Now it remains to unload the filament safely with ramming over the wipe tower.
std :: string WipeTowerIntegration :: finalize ( GCode & gcodegen )
{
std :: string gcode ;
if ( std :: abs ( gcodegen . writer (). get_position ()( 2 ) - m_final_purge . print_z ) > EPSILON )
gcode += gcodegen . change_layer ( m_final_purge . print_z );
gcode += append_tcr ( gcodegen , m_final_purge , - 1 );
return gcode ;
2018-07-27 15:56:27 +02:00
}
2017-05-18 16:53:19 +02:00
2020-07-16 11:09:21 +02:00
const std :: vector < std :: string > ColorPrintColors :: Colors = { "#C0392B" , "#E67E22" , "#F1C40F" , "#27AE60" , "#1ABC9C" , "#2980B9" , "#9B59B6" };
2020-05-07 10:49:12 +02:00
2017-06-22 12:59:23 +02:00
#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.extruder()->id())
2015-07-02 14:31:21 +02:00
2017-05-23 17:09:43 +02:00
// Collect pairs of object_layer + support_layer sorted by print_z.
// object_layer & support_layer are considered to be on the same print_z, if they are not further than EPSILON.
2020-07-21 09:48:41 +02:00
std :: vector < GCode :: LayerToPrint > GCode :: collect_layers_to_print ( const PrintObject & object )
2017-05-23 17:09:43 +02:00
{
std :: vector < GCode :: LayerToPrint > layers_to_print ;
2018-09-11 14:04:47 +02:00
layers_to_print . reserve ( object . layers (). size () + object . support_layers (). size ());
2017-05-23 17:09:43 +02:00
2020-07-21 09:48:41 +02:00
// Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um.
// This is the same logic as in support generator.
//FIXME should we use the printing extruders instead?
double gap_over_supports = object . config (). support_material_contact_distance ;
// FIXME should we test object.config().support_material_synchronize_layers ? Currently the support layers are synchronized with object layers iff soluble supports.
2021-02-23 15:31:08 +01:00
assert ( ! object . has_support () || gap_over_supports != 0. || object . config (). support_material_synchronize_layers );
2019-09-13 15:47:00 +02:00
if ( gap_over_supports != 0. ) {
gap_over_supports = std :: max ( 0. , gap_over_supports );
2020-07-21 09:48:41 +02:00
// Not a soluble support,
double support_layer_height_min = 1000000. ;
for ( auto lh : object . print () -> config (). min_layer_height . values )
support_layer_height_min = std :: min ( support_layer_height_min , std :: max ( 0.01 , lh ));
gap_over_supports += support_layer_height_min ;
2017-05-23 17:09:43 +02:00
}
// Pair the object layers with the support layers by z.
2020-07-21 09:48:41 +02:00
size_t idx_object_layer = 0 ;
2017-05-23 17:09:43 +02:00
size_t idx_support_layer = 0 ;
2019-08-07 12:00:36 +02:00
const LayerToPrint * last_extrusion_layer = nullptr ;
2018-09-11 14:04:47 +02:00
while ( idx_object_layer < object . layers (). size () || idx_support_layer < object . support_layers (). size ()) {
2017-05-23 17:09:43 +02:00
LayerToPrint layer_to_print ;
2020-07-21 09:48:41 +02:00
layer_to_print . object_layer = ( idx_object_layer < object . layers (). size ()) ? object . layers ()[ idx_object_layer ++ ] : nullptr ;
layer_to_print . support_layer = ( idx_support_layer < object . support_layers (). size ()) ? object . support_layers ()[ idx_support_layer ++ ] : nullptr ;
2017-05-23 17:09:43 +02:00
if ( layer_to_print . object_layer && layer_to_print . support_layer ) {
if ( layer_to_print . object_layer -> print_z < layer_to_print . support_layer -> print_z - EPSILON ) {
layer_to_print . support_layer = nullptr ;
2020-07-21 09:48:41 +02:00
-- idx_support_layer ;
}
else if ( layer_to_print . support_layer -> print_z < layer_to_print . object_layer -> print_z - EPSILON ) {
2017-05-23 17:09:43 +02:00
layer_to_print . object_layer = nullptr ;
2020-07-21 09:48:41 +02:00
-- idx_object_layer ;
2020-07-16 11:09:21 +02:00
}
2017-05-23 17:09:43 +02:00
}
2019-08-07 12:00:36 +02:00
layers_to_print . emplace_back ( layer_to_print );
2017-05-23 17:09:43 +02:00
2020-07-21 08:43:20 +02:00
bool has_extrusions = ( layer_to_print . object_layer && layer_to_print . object_layer -> has_extrusions ())
2020-07-21 09:48:41 +02:00
|| ( layer_to_print . support_layer && layer_to_print . support_layer -> has_extrusions ());
2020-07-21 08:43:20 +02:00
2020-07-16 15:42:30 +02:00
// Check that there are extrusions on the very first layer.
if ( layers_to_print . size () == 1u ) {
2020-07-21 09:48:41 +02:00
if ( ! has_extrusions )
2020-09-23 12:58:58 +02:00
throw Slic3r :: SlicingError ( _ ( L ( "There is an object with no extrusions on the first layer." )));
2017-05-23 17:09:43 +02:00
}
2019-08-07 10:54:36 +02:00
// In case there are extrusions on this layer, check there is a layer to lay it on.
if (( layer_to_print . object_layer && layer_to_print . object_layer -> has_extrusions ())
2020-07-21 09:48:41 +02:00
// Allow empty support layers, as the support generator may produce no extrusions for non-empty support regions.
|| ( layer_to_print . support_layer /* && layer_to_print.support_layer->has_extrusions() */ )) {
2019-08-07 12:00:36 +02:00
double support_contact_z = ( last_extrusion_layer && last_extrusion_layer -> support_layer )
2020-07-21 09:48:41 +02:00
? gap_over_supports
: 0. ;
2019-08-07 12:00:36 +02:00
double maximal_print_z = ( last_extrusion_layer ? last_extrusion_layer -> print_z () : 0. )
2020-07-21 09:48:41 +02:00
+ layer_to_print . layer () -> height
+ support_contact_z ;
2019-08-13 11:02:58 +02:00
// Negative support_contact_z is not taken into account, it can result in false positives in cases
// where previous layer has object extrusions too (https://github.com/prusa3d/PrusaSlicer/issues/2752)
2020-07-16 15:42:30 +02:00
if ( has_extrusions && layer_to_print . print_z () > maximal_print_z + 2. * EPSILON ) {
const_cast < Print *> ( object . print ()) -> active_step_add_warning ( PrintStateBase :: WarningLevel :: CRITICAL ,
_ ( L ( "Empty layers detected, the output would not be printable." )) + " \n\n " +
2019-12-06 08:59:25 +01:00
_ ( L ( "Object name" )) + ": " + object . model_object () -> name + " \n " + _ ( L ( "Print z" )) + ": " +
2019-08-19 15:50:49 +02:00
std :: to_string ( layers_to_print . back (). print_z ()) + " \n\n " + _ ( L ( "This is "
2020-07-21 09:48:41 +02:00
"usually caused by negligibly small extrusions or by a faulty model. Try to repair "
"the model or change its orientation on the bed." )));
2020-07-16 15:42:30 +02:00
}
2019-08-07 12:00:36 +02:00
// Remember last layer with extrusions.
2020-07-16 15:42:30 +02:00
if ( has_extrusions )
last_extrusion_layer = & layers_to_print . back ();
2019-08-07 10:54:36 +02:00
}
2020-07-16 11:09:21 +02:00
}
2017-05-23 17:09:43 +02:00
return layers_to_print ;
}
2020-09-01 18:33:56 +02:00
// Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z
2017-05-24 15:20:20 +02:00
// will be printed for all objects at once.
// Return a list of <print_z, per object LayerToPrint> items.
2020-07-21 09:48:41 +02:00
std :: vector < std :: pair < coordf_t , std :: vector < GCode :: LayerToPrint >>> GCode :: collect_layers_to_print ( const Print & print )
2017-05-23 17:09:43 +02:00
{
struct OrderingItem {
coordf_t print_z ;
size_t object_idx ;
size_t layer_idx ;
};
2018-07-23 15:58:08 +02:00
2018-11-08 14:23:17 +01:00
std :: vector < std :: vector < LayerToPrint >> per_object ( print . objects (). size (), std :: vector < LayerToPrint > ());
2017-05-23 17:09:43 +02:00
std :: vector < OrderingItem > ordering ;
2018-11-08 14:23:17 +01:00
for ( size_t i = 0 ; i < print . objects (). size (); ++ i ) {
per_object [ i ] = collect_layers_to_print ( * print . objects ()[ i ]);
2017-05-23 17:09:43 +02:00
OrderingItem ordering_item ;
ordering_item . object_idx = i ;
2017-05-24 15:20:20 +02:00
ordering . reserve ( ordering . size () + per_object [ i ]. size ());
2020-07-21 09:48:41 +02:00
const LayerToPrint & front = per_object [ i ]. front ();
for ( const LayerToPrint & ltp : per_object [ i ]) {
ordering_item . print_z = ltp . print_z ();
2017-05-23 17:09:43 +02:00
ordering_item . layer_idx = & ltp - & front ;
ordering . emplace_back ( ordering_item );
}
}
2020-07-21 09:48:41 +02:00
std :: sort ( ordering . begin (), ordering . end (), []( const OrderingItem & oi1 , const OrderingItem & oi2 ) { return oi1 . print_z < oi2 . print_z ; });
2017-05-23 17:09:43 +02:00
std :: vector < std :: pair < coordf_t , std :: vector < LayerToPrint >>> layers_to_print ;
2020-08-03 15:36:55 +02:00
2017-05-23 17:09:43 +02:00
// Merge numerically very close Z values.
for ( size_t i = 0 ; i < ordering . size ();) {
// Find the last layer with roughly the same print_z.
size_t j = i + 1 ;
coordf_t zmax = ordering [ i ]. print_z + EPSILON ;
2020-07-21 09:48:41 +02:00
for (; j < ordering . size () && ordering [ j ]. print_z <= zmax ; ++ j );
2017-05-23 17:09:43 +02:00
// Merge into layers_to_print.
std :: pair < coordf_t , std :: vector < LayerToPrint >> merged ;
// Assign an average print_z to the set of layers with nearly equal print_z.
2020-07-21 09:48:41 +02:00
merged . first = 0.5 * ( ordering [ i ]. print_z + ordering [ j - 1 ]. print_z );
2018-11-08 14:23:17 +01:00
merged . second . assign ( print . objects (). size (), LayerToPrint ());
2018-07-23 15:58:08 +02:00
for (; i < j ; ++ i ) {
2020-07-21 09:48:41 +02:00
const OrderingItem & oi = ordering [ i ];
2017-05-23 17:09:43 +02:00
assert ( merged . second [ oi . object_idx ]. layer () == nullptr );
merged . second [ oi . object_idx ] = std :: move ( per_object [ oi . object_idx ][ oi . layer_idx ]);
}
layers_to_print . emplace_back ( std :: move ( merged ));
}
return layers_to_print ;
}
2020-08-07 15:30:08 +02:00
// free functions called by GCode::do_export()
namespace DoExport {
2021-05-07 14:46:10 +02:00
// static void update_print_estimated_times_stats(const GCodeProcessor& processor, PrintStatistics& print_statistics)
// {
// const GCodeProcessor::Result& result = processor.get_result();
// print_statistics.estimated_normal_print_time = get_time_dhms(result.print_statistics.modes[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].time);
// print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ?
// get_time_dhms(result.print_statistics.modes[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Stealth)].time) : "N/A";
// }
2021-04-21 14:57:43 +02:00
static void update_print_estimated_stats ( const GCodeProcessor & processor , const std :: vector < Extruder >& extruders , PrintStatistics & print_statistics )
{
const GCodeProcessor :: Result & result = processor . get_result ();
print_statistics . estimated_normal_print_time = get_time_dhms ( result . print_statistics . modes [ static_cast < size_t > ( PrintEstimatedStatistics :: ETimeMode :: Normal )]. time );
print_statistics . estimated_silent_print_time = processor . is_stealth_time_estimator_enabled () ?
get_time_dhms ( result . print_statistics . modes [ static_cast < size_t > ( PrintEstimatedStatistics :: ETimeMode :: Stealth )]. time ) : "N/A" ;
// update filament statictics
double total_extruded_volume = 0.0 ;
double total_used_filament = 0.0 ;
double total_weight = 0.0 ;
double total_cost = 0.0 ;
for ( auto volume : result . print_statistics . volumes_per_extruder ) {
total_extruded_volume += volume . second ;
size_t extruder_id = volume . first ;
auto extruder = std :: find_if ( extruders . begin (), extruders . end (), [ extruder_id ]( const Extruder & extr ) { return extr . id () == extruder_id ; });
if ( extruder == extruders . end ())
continue ;
double s = PI * sqr ( 0.5 * extruder -> filament_diameter ());
double weight = volume . second * extruder -> filament_density () * 0.001 ;
total_used_filament += volume . second / s ;
total_weight += weight ;
total_cost += weight * extruder -> filament_cost () * 0.001 ;
}
print_statistics . total_extruded_volume = total_extruded_volume ;
print_statistics . total_used_filament = total_used_filament ;
print_statistics . total_weight = total_weight ;
print_statistics . total_cost = total_cost ;
print_statistics . filament_stats = result . print_statistics . volumes_per_extruder ;
2020-08-07 15:30:08 +02:00
}
2021-02-18 14:34:40 +01:00
#if ENABLE_VALIDATE_CUSTOM_GCODE
// if any reserved keyword is found, returns a std::vector containing the first MAX_COUNT keywords found
// into pairs containing:
// first: source
// second: keyword
// to be shown in the warning notification
// The returned vector is empty if no keyword has been found
static std :: vector < std :: pair < std :: string , std :: string >> validate_custom_gcode ( const Print & print ) {
2021-02-18 15:35:38 +01:00
static const unsigned int MAX_TAGS_COUNT = 5 ;
2021-02-18 14:34:40 +01:00
std :: vector < std :: pair < std :: string , std :: string >> ret ;
2021-02-18 15:35:38 +01:00
auto check = [ & ret ]( const std :: string & source , const std :: string & gcode ) {
2021-02-18 14:34:40 +01:00
std :: vector < std :: string > tags ;
2021-02-18 15:35:38 +01:00
if ( GCodeProcessor :: contains_reserved_tags ( gcode , MAX_TAGS_COUNT , tags )) {
2021-02-18 14:34:40 +01:00
if ( ! tags . empty ()) {
size_t i = 0 ;
2021-02-18 15:35:38 +01:00
while ( ret . size () < MAX_TAGS_COUNT && i < tags . size ()) {
2021-02-18 14:34:40 +01:00
ret . push_back ({ source , tags [ i ] });
++ i ;
}
}
}
};
const GCodeConfig & config = print . config ();
check ( _ ( L ( "Start G-code" )), config . start_gcode . value );
2021-02-18 15:35:38 +01:00
if ( ret . size () < MAX_TAGS_COUNT ) check ( _ ( L ( "End G-code" )), config . end_gcode . value );
if ( ret . size () < MAX_TAGS_COUNT ) check ( _ ( L ( "Before layer change G-code" )), config . before_layer_gcode . value );
if ( ret . size () < MAX_TAGS_COUNT ) check ( _ ( L ( "After layer change G-code" )), config . layer_gcode . value );
if ( ret . size () < MAX_TAGS_COUNT ) check ( _ ( L ( "Tool change G-code" )), config . toolchange_gcode . value );
if ( ret . size () < MAX_TAGS_COUNT ) check ( _ ( L ( "Between objects G-code (for sequential printing)" )), config . between_objects_gcode . value );
if ( ret . size () < MAX_TAGS_COUNT ) check ( _ ( L ( "Color Change G-code" )), config . color_change_gcode . value );
if ( ret . size () < MAX_TAGS_COUNT ) check ( _ ( L ( "Pause Print G-code" )), config . pause_print_gcode . value );
if ( ret . size () < MAX_TAGS_COUNT ) check ( _ ( L ( "Template Custom G-code" )), config . template_custom_gcode . value );
if ( ret . size () < MAX_TAGS_COUNT ) {
2021-02-18 14:34:40 +01:00
for ( const std :: string & value : config . start_filament_gcode . values ) {
check ( _ ( L ( "Filament Start G-code" )), value );
2021-02-18 15:35:38 +01:00
if ( ret . size () == MAX_TAGS_COUNT )
2021-02-18 14:34:40 +01:00
break ;
}
}
2021-02-18 15:35:38 +01:00
if ( ret . size () < MAX_TAGS_COUNT ) {
2021-02-18 14:34:40 +01:00
for ( const std :: string & value : config . end_filament_gcode . values ) {
check ( _ ( L ( "Filament End G-code" )), value );
2021-02-18 15:35:38 +01:00
if ( ret . size () == MAX_TAGS_COUNT )
2021-02-18 14:34:40 +01:00
break ;
}
}
2021-02-25 08:23:45 +01:00
if ( ret . size () < MAX_TAGS_COUNT ) {
const CustomGCode :: Info & custom_gcode_per_print_z = print . model (). custom_gcode_per_print_z ;
for ( const auto & gcode : custom_gcode_per_print_z . gcodes ) {
check ( _ ( L ( "Custom G-code" )), gcode . extra );
if ( ret . size () == MAX_TAGS_COUNT )
break ;
}
}
2021-02-18 14:34:40 +01:00
return ret ;
}
#endif // ENABLE_VALIDATE_CUSTOM_GCODE
2020-08-07 15:30:08 +02:00
} // namespace DoExport
2020-05-07 10:49:12 +02:00
void GCode :: do_export ( Print * print , const char * path , GCodeProcessor :: Result * result , ThumbnailsGeneratorCallback thumbnail_cb )
2017-08-03 17:31:31 +02:00
{
2018-01-03 17:29:49 +01:00
PROFILE_CLEAR ();
2018-09-17 12:01:02 +02:00
// Does the file exist? If so, we hope that it is still valid.
if ( print -> is_step_done ( psGCodeExport ) && boost :: filesystem :: exists ( boost :: filesystem :: path ( path )))
return ;
2018-09-14 09:28:00 +02:00
2019-09-24 16:01:01 +02:00
print -> set_started ( psGCodeExport );
2018-09-14 09:28:00 +02:00
2021-02-18 14:34:40 +01:00
#if ENABLE_VALIDATE_CUSTOM_GCODE
// check if any custom gcode contains keywords used by the gcode processor to
// produce time estimation and gcode toolpaths
std :: vector < std :: pair < std :: string , std :: string >> validation_res = DoExport :: validate_custom_gcode ( * print );
if ( ! validation_res . empty ()) {
std :: string reports ;
for ( const auto & [ source , keyword ] : validation_res ) {
reports += source + ": \" " + keyword + " \"\n " ;
}
print -> active_step_add_warning ( PrintStateBase :: WarningLevel :: NON_CRITICAL ,
_ ( L ( "Found reserved keyword(s) into custom g-code:" )) + " \n " +
reports +
_ ( L ( "This may cause problems in g-code visualization and printing time estimation." )));
}
#endif // ENABLE_VALIDATE_CUSTOM_GCODE
2018-12-18 11:31:41 +01:00
BOOST_LOG_TRIVIAL ( info ) << "Exporting G-code..." << log_memory_info ();
2018-02-14 22:25:09 +01:00
2017-08-03 17:31:31 +02:00
// Remove the old g-code if it exists.
boost :: nowide :: remove ( path );
std :: string path_tmp ( path );
path_tmp += ".tmp" ;
FILE * file = boost :: nowide :: fopen ( path_tmp . c_str (), "wb" );
if ( file == nullptr )
2020-09-14 16:27:55 +02:00
throw Slic3r :: RuntimeError ( std :: string ( "G-code export to " ) + path + " failed. \n Cannot open the file for writing. \n " );
2017-08-03 17:31:31 +02:00
2018-03-28 17:05:31 +02:00
try {
2018-09-11 14:04:47 +02:00
m_placeholder_parser_failed_templates . clear ();
2019-11-22 12:39:03 +01:00
this -> _do_export ( * print , file , thumbnail_cb );
2018-03-28 17:05:31 +02:00
fflush ( file );
if ( ferror ( file )) {
fclose ( file );
boost :: nowide :: remove ( path_tmp . c_str ());
2020-09-14 16:27:55 +02:00
throw Slic3r :: RuntimeError ( std :: string ( "G-code export to " ) + path + " failed \n Is the disk full? \n " );
2018-03-28 17:05:31 +02:00
}
2018-11-06 15:31:26 +01:00
} catch ( std :: exception & /* ex */ ) {
2018-03-28 17:05:31 +02:00
// Rethrow on any exception. std::runtime_exception and CanceledException are expected to be thrown.
// Close and remove the file.
2017-12-05 18:40:46 +01:00
fclose ( file );
2017-08-03 17:31:31 +02:00
boost :: nowide :: remove ( path_tmp . c_str ());
2018-03-28 17:05:31 +02:00
throw ;
2017-12-05 15:54:24 +01:00
}
2017-12-05 18:40:46 +01:00
fclose ( file );
2018-05-30 12:08:03 +02:00
2018-09-11 14:04:47 +02:00
if ( ! m_placeholder_parser_failed_templates . empty ()) {
2017-12-05 15:54:24 +01:00
// G-code export proceeded, but some of the PlaceholderParser substitutions failed.
2020-12-03 12:50:18 +01:00
//FIXME localize!
2017-12-05 15:54:24 +01:00
std :: string msg = std :: string ( "G-code export to " ) + path + " failed due to invalid custom G-code sections: \n\n " ;
2020-12-03 12:50:18 +01:00
for ( const auto & name_and_error : m_placeholder_parser_failed_templates )
msg += name_and_error . first + " \n " + name_and_error . second + " \n " ;
2017-12-05 15:54:24 +01:00
msg += " \n Please inspect the file " ;
msg += path_tmp + " for error messages enclosed between \n " ;
msg += " !!!!! Failed to process the custom G-code template ... \n " ;
msg += "and \n " ;
msg += " !!!!! End of an error report for the custom G-code template ... \n " ;
2020-12-03 12:50:18 +01:00
msg += "for all macro processing errors." ;
2020-12-03 11:03:03 +01:00
throw Slic3r :: PlaceholderParserError ( msg );
2017-12-05 15:54:24 +01:00
}
2017-08-03 17:31:31 +02:00
2020-09-11 15:19:23 +02:00
BOOST_LOG_TRIVIAL ( debug ) << "Start processing gcode, " << log_memory_info ();
2020-11-05 14:02:43 +01:00
m_processor . process_file ( path_tmp , true , [ print ]() { print -> throw_if_canceled (); });
2021-04-21 14:57:43 +02:00
// DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics);
DoExport :: update_print_estimated_stats ( m_processor , m_writer . extruders (), print -> m_print_statistics );
2021-03-02 10:01:06 +01:00
#if ENABLE_GCODE_WINDOW
if ( result != nullptr ) {
* result = std :: move ( m_processor . extract_result ());
// set the filename to the correct value
result -> filename = path ;
}
#else
2020-08-03 13:57:10 +02:00
if ( result != nullptr )
2020-03-02 15:13:23 +01:00
* result = std :: move ( m_processor . extract_result ());
2021-03-02 10:01:06 +01:00
#endif // ENABLE_GCODE_WINDOW
2020-09-11 15:19:23 +02:00
BOOST_LOG_TRIVIAL ( debug ) << "Finished processing gcode, " << log_memory_info ();
2018-12-18 17:34:21 +01:00
2019-08-20 16:19:30 +02:00
if ( rename_file ( path_tmp , path ))
2020-09-14 16:27:55 +02:00
throw Slic3r :: RuntimeError (
2017-12-05 15:54:24 +01:00
std :: string ( "Failed to rename the output G-code file from " ) + path_tmp + " to " + path + '\n' +
"Is " + path_tmp + " locked?" + '\n' );
2018-01-03 17:29:49 +01:00
2018-12-18 11:31:41 +01:00
BOOST_LOG_TRIVIAL ( info ) << "Exporting G-code finished" << log_memory_info ();
2020-09-01 18:33:56 +02:00
print -> set_done ( psGCodeExport );
2018-02-14 22:25:09 +01:00
2018-01-03 17:29:49 +01:00
// Write the profiler measurements to file
PROFILE_UPDATE ();
PROFILE_OUTPUT ( debug_out_path ( "gcode-export-profile.txt" ). c_str ());
2017-08-03 17:31:31 +02:00
}
2020-01-10 11:26:52 +01:00
// free functions called by GCode::_do_export()
namespace DoExport {
2020-08-05 15:43:46 +02:00
static void init_gcode_processor ( const PrintConfig & config , GCodeProcessor & processor , bool & silent_time_estimator_enabled )
2020-05-07 10:49:12 +02:00
{
2021-03-30 13:30:18 +02:00
silent_time_estimator_enabled = ( config . gcode_flavor == gcfMarlinLegacy || config . gcode_flavor == gcfMarlinFirmware )
2021-03-30 12:46:09 +02:00
&& config . silent_mode ;
2020-05-07 10:49:12 +02:00
processor . reset ();
processor . apply_config ( config );
2020-07-16 11:09:21 +02:00
processor . enable_stealth_time_estimator ( silent_time_estimator_enabled );
2020-05-07 10:49:12 +02:00
}
2020-03-02 15:13:23 +01:00
2020-01-10 11:26:52 +01:00
static double autospeed_volumetric_limit ( const Print & print )
{
// get the minimum cross-section used in the print
std :: vector < double > mm3_per_mm ;
for ( auto object : print . objects ()) {
2021-05-05 16:21:55 +02:00
for ( size_t region_id = 0 ; region_id < object -> num_printing_regions (); ++ region_id ) {
const PrintRegion & region = object -> printing_region ( region_id );
2020-01-10 11:26:52 +01:00
for ( auto layer : object -> layers ()) {
const LayerRegion * layerm = layer -> regions ()[ region_id ];
2021-05-05 16:21:55 +02:00
if ( region . config (). get_abs_value ( "perimeter_speed" ) == 0 ||
region . config (). get_abs_value ( "small_perimeter_speed" ) == 0 ||
region . config (). get_abs_value ( "external_perimeter_speed" ) == 0 ||
region . config (). get_abs_value ( "bridge_speed" ) == 0 )
2020-01-10 11:26:52 +01:00
mm3_per_mm . push_back ( layerm -> perimeters . min_mm3_per_mm ());
2021-05-05 16:21:55 +02:00
if ( region . config (). get_abs_value ( "infill_speed" ) == 0 ||
region . config (). get_abs_value ( "solid_infill_speed" ) == 0 ||
region . config (). get_abs_value ( "top_solid_infill_speed" ) == 0 ||
region . config (). get_abs_value ( "bridge_speed" ) == 0 )
2021-01-04 13:22:08 +01:00
{
// Minimal volumetric flow should not be calculated over ironing extrusions.
// Use following lambda instead of the built-it method.
// https://github.com/prusa3d/PrusaSlicer/issues/5082
auto min_mm3_per_mm_no_ironing = []( const ExtrusionEntityCollection & eec ) -> double {
double min = std :: numeric_limits < double >:: max ();
for ( const ExtrusionEntity * ee : eec . entities )
if ( ee -> role () != erIroning )
min = std :: min ( min , ee -> min_mm3_per_mm ());
return min ;
};
mm3_per_mm . push_back ( min_mm3_per_mm_no_ironing ( layerm -> fills ));
}
2020-01-10 11:26:52 +01:00
}
}
if ( object -> config (). get_abs_value ( "support_material_speed" ) == 0 ||
object -> config (). get_abs_value ( "support_material_interface_speed" ) == 0 )
for ( auto layer : object -> support_layers ())
mm3_per_mm . push_back ( layer -> support_fills . min_mm3_per_mm ());
}
// filter out 0-width segments
mm3_per_mm . erase ( std :: remove_if ( mm3_per_mm . begin (), mm3_per_mm . end (), []( double v ) { return v < 0.000001 ; }), mm3_per_mm . end ());
double volumetric_speed = 0. ;
if ( ! mm3_per_mm . empty ()) {
// In order to honor max_print_speed we need to find a target volumetric
// speed that we can use throughout the print. So we define this target
// volumetric speed as the volumetric speed produced by printing the
// smallest cross-section at the maximum speed: any larger cross-section
// will need slower feedrates.
volumetric_speed = * std :: min_element ( mm3_per_mm . begin (), mm3_per_mm . end ()) * print . config (). max_print_speed . value ;
// limit such volumetric speed with max_volumetric_speed if set
if ( print . config (). max_volumetric_speed . value > 0 )
volumetric_speed = std :: min ( volumetric_speed , print . config (). max_volumetric_speed . value );
}
return volumetric_speed ;
}
2020-09-01 18:33:56 +02:00
static void init_ooze_prevention ( const Print & print , OozePrevention & ooze_prevention )
2020-01-10 11:26:52 +01:00
{
// Calculate wiping points if needed
if ( print . config (). ooze_prevention . value && ! print . config (). single_extruder_multi_material ) {
Points skirt_points ;
for ( const ExtrusionEntity * ee : print . skirt (). entities )
for ( const ExtrusionPath & path : dynamic_cast < const ExtrusionLoop *> ( ee ) -> paths )
append ( skirt_points , path . polyline . points );
if ( ! skirt_points . empty ()) {
Polygon outer_skirt = Slic3r :: Geometry :: convex_hull ( skirt_points );
Polygons skirts ;
for ( unsigned int extruder_id : print . extruders ()) {
const Vec2d & extruder_offset = print . config (). extruder_offset . get_at ( extruder_id );
Polygon s ( outer_skirt );
s . translate ( Point :: new_scale ( - extruder_offset ( 0 ), - extruder_offset ( 1 )));
skirts . emplace_back ( std :: move ( s ));
}
ooze_prevention . enable = true ;
2020-01-30 13:00:51 +01:00
ooze_prevention . standby_points = offset ( Slic3r :: Geometry :: convex_hull ( skirts ), float ( scale_ ( 3. ))). front (). equally_spaced_points ( float ( scale_ ( 10. )));
2020-01-10 11:26:52 +01:00
#if 0
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"ooze_prevention.svg",
red_polygons => \@skirts,
polygons => [$outer_skirt],
points => $gcodegen->ooze_prevention->standby_points,
);
#endif
}
}
}
template < typename WriteToOutput , typename ThrowIfCanceledCallback >
2020-01-10 11:53:39 +01:00
static void export_thumbnails_to_file ( ThumbnailsGeneratorCallback & thumbnail_cb , const std :: vector < Vec2d > & sizes , WriteToOutput output , ThrowIfCanceledCallback throw_if_canceled )
2020-01-10 11:26:52 +01:00
{
// Write thumbnails using base64 encoding
if ( thumbnail_cb != nullptr )
{
const size_t max_row_length = 78 ;
2021-05-04 16:07:25 +02:00
ThumbnailsList thumbnails = thumbnail_cb ( ThumbnailsParams { sizes , true , true , true , true });
2020-01-10 11:26:52 +01:00
for ( const ThumbnailData & data : thumbnails )
{
if ( data . is_valid ())
{
size_t png_size = 0 ;
void * png_data = tdefl_write_image_to_png_file_in_memory_ex (( const void * ) data . pixels . data (), data . width , data . height , 4 , & png_size , MZ_DEFAULT_LEVEL , 1 );
if ( png_data != nullptr )
{
std :: string encoded ;
encoded . resize ( boost :: beast :: detail :: base64 :: encoded_size ( png_size ));
encoded . resize ( boost :: beast :: detail :: base64 :: encode (( void * ) & encoded [ 0 ], ( const void * ) png_data , png_size ));
output (( boost :: format ( " \n ; \n ; thumbnail begin %dx%d %d \n " ) % data . width % data . height % encoded . size ()). str (). c_str ());
unsigned int row_count = 0 ;
while ( encoded . size () > max_row_length )
{
output (( boost :: format ( "; %s \n " ) % encoded . substr ( 0 , max_row_length )). str (). c_str ());
encoded = encoded . substr ( max_row_length );
++ row_count ;
}
if ( encoded . size () > 0 )
output (( boost :: format ( "; %s \n " ) % encoded ). str (). c_str ());
output ( "; thumbnail end \n ; \n " );
mz_free ( png_data );
}
}
throw_if_canceled ();
}
}
}
// Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section.
2020-07-16 11:09:21 +02:00
static std :: string update_print_stats_and_format_filament_stats (
2020-08-05 15:43:46 +02:00
const bool has_wipe_tower ,
2020-01-10 11:26:52 +01:00
const WipeTowerData & wipe_tower_data ,
const std :: vector < Extruder > & extruders ,
PrintStatistics & print_statistics )
2020-07-16 11:09:21 +02:00
{
2020-01-10 11:26:52 +01:00
std :: string filament_stats_string_out ;
print_statistics . clear ();
2020-07-08 13:33:50 +02:00
print_statistics . total_toolchanges = std :: max ( 0 , wipe_tower_data . number_of_toolchanges );
2020-01-10 11:26:52 +01:00
if ( ! extruders . empty ()) {
std :: pair < std :: string , unsigned int > out_filament_used_mm ( "; filament used [mm] = " , 0 );
std :: pair < std :: string , unsigned int > out_filament_used_cm3 ( "; filament used [cm3] = " , 0 );
std :: pair < std :: string , unsigned int > out_filament_used_g ( "; filament used [g] = " , 0 );
std :: pair < std :: string , unsigned int > out_filament_cost ( "; filament cost = " , 0 );
for ( const Extruder & extruder : extruders ) {
double used_filament = extruder . used_filament () + ( has_wipe_tower ? wipe_tower_data . used_filament [ extruder . id ()] : 0.f );
double extruded_volume = extruder . extruded_volume () + ( has_wipe_tower ? wipe_tower_data . used_filament [ extruder . id ()] * 2.4052f : 0.f ); // assumes 1.75mm filament diameter
double filament_weight = extruded_volume * extruder . filament_density () * 0.001 ;
double filament_cost = filament_weight * extruder . filament_cost () * 0.001 ;
2021-01-29 18:08:04 +01:00
auto append = [ & extruder ]( std :: pair < std :: string , unsigned int > & dst , const char * tmpl , double value ) {
2020-01-10 11:26:52 +01:00
while ( dst . second < extruder . id ()) {
// Fill in the non-printing extruders with zeros.
dst . first += ( dst . second > 0 ) ? ", 0" : "0" ;
++ dst . second ;
}
if ( dst . second > 0 )
dst . first += ", " ;
char buf [ 64 ];
sprintf ( buf , tmpl , value );
dst . first += buf ;
++ dst . second ;
};
2020-10-28 10:46:59 +01:00
append ( out_filament_used_mm , "%.2lf" , used_filament );
append ( out_filament_used_cm3 , "%.2lf" , extruded_volume * 0.001 );
2020-01-10 11:26:52 +01:00
if ( filament_weight > 0. ) {
print_statistics . total_weight = print_statistics . total_weight + filament_weight ;
2020-10-28 10:46:59 +01:00
append ( out_filament_used_g , "%.2lf" , filament_weight );
2020-01-10 11:26:52 +01:00
if ( filament_cost > 0. ) {
print_statistics . total_cost = print_statistics . total_cost + filament_cost ;
2020-10-28 10:46:59 +01:00
append ( out_filament_cost , "%.2lf" , filament_cost );
2020-01-10 11:26:52 +01:00
}
}
print_statistics . total_used_filament += used_filament ;
print_statistics . total_extruded_volume += extruded_volume ;
print_statistics . total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder . used_filament () : 0. ;
print_statistics . total_wipe_tower_cost += has_wipe_tower ? ( extruded_volume - extruder . extruded_volume ()) * extruder . filament_density () * 0.001 * extruder . filament_cost () * 0.001 : 0. ;
}
filament_stats_string_out += out_filament_used_mm . first ;
2020-02-14 12:50:26 +01:00
filament_stats_string_out += " \n " + out_filament_used_cm3 . first ;
2020-09-01 18:33:56 +02:00
if ( out_filament_used_g . second )
2020-02-14 12:50:26 +01:00
filament_stats_string_out += " \n " + out_filament_used_g . first ;
2020-09-01 18:33:56 +02:00
if ( out_filament_cost . second )
2020-02-14 12:50:26 +01:00
filament_stats_string_out += " \n " + out_filament_cost . first ;
2020-09-01 18:33:56 +02:00
}
return filament_stats_string_out ;
}
2020-01-10 11:26:52 +01:00
}
2021-02-09 19:23:58 +01:00
#if 0
2020-01-22 10:50:14 +01:00
// Sort the PrintObjects by their increasing Z, likely useful for avoiding colisions on Deltas during sequential prints.
2020-01-23 09:53:06 +01:00
static inline std::vector<const PrintInstance*> sort_object_instances_by_max_z(const Print &print)
2020-01-22 10:50:14 +01:00
{
std::vector<const PrintObject*> objects(print.objects().begin(), print.objects().end());
2020-09-01 18:33:56 +02:00
std::sort(objects.begin(), objects.end(), [](const PrintObject *po1, const PrintObject *po2) { return po1->height() < po2->height(); });
std::vector<const PrintInstance*> instances;
instances.reserve(objects.size());
for (const PrintObject *object : objects)
for (size_t i = 0; i < object->instances().size(); ++ i)
instances.emplace_back(&object->instances()[i]);
return instances;
2020-01-22 10:50:14 +01:00
}
2021-02-09 19:23:58 +01:00
#endif
2020-01-22 10:50:14 +01:00
// Produce a vector of PrintObjects in the order of their respective ModelObjects in print.model().
2020-02-03 11:44:26 +01:00
std :: vector < const PrintInstance *> sort_object_instances_by_model_order ( const Print & print )
2020-01-22 10:50:14 +01:00
{
2020-01-23 09:53:06 +01:00
// Build up map from ModelInstance* to PrintInstance*
std :: vector < std :: pair < const ModelInstance * , const PrintInstance *>> model_instance_to_print_instance ;
model_instance_to_print_instance . reserve ( print . num_object_instances ());
for ( const PrintObject * print_object : print . objects ())
for ( const PrintInstance & print_instance : print_object -> instances ())
model_instance_to_print_instance . emplace_back ( print_instance . model_instance , & print_instance );
std :: sort ( model_instance_to_print_instance . begin (), model_instance_to_print_instance . end (), []( auto & l , auto & r ) { return l . first < r . first ; });
std :: vector < const PrintInstance *> instances ;
instances . reserve ( model_instance_to_print_instance . size ());
for ( const ModelObject * model_object : print . model (). objects )
for ( const ModelInstance * model_instance : model_object -> instances ) {
auto it = std :: lower_bound ( model_instance_to_print_instance . begin (), model_instance_to_print_instance . end (), std :: make_pair ( model_instance , nullptr ), []( auto & l , auto & r ) { return l . first < r . first ; });
if ( it != model_instance_to_print_instance . end () && it -> first == model_instance )
instances . emplace_back ( it -> second );
}
return instances ;
2020-01-22 10:50:14 +01:00
}
2019-11-22 12:39:03 +01:00
void GCode :: _do_export ( Print & print , FILE * file , ThumbnailsGeneratorCallback thumbnail_cb )
2015-07-01 23:00:52 +02:00
{
2018-01-03 17:29:49 +01:00
PROFILE_FUNC ();
2020-08-05 15:43:46 +02:00
// modifies m_silent_time_estimator_enabled
2020-07-16 11:09:21 +02:00
DoExport :: init_gcode_processor ( print . config (), m_processor , m_silent_time_estimator_enabled );
2019-07-12 12:38:00 +02:00
2018-01-08 13:44:10 +01:00
// resets analyzer's tracking data
2020-12-01 08:23:46 +01:00
m_last_height = 0.f ;
m_last_layer_z = 0.f ;
m_max_layer_z = 0.f ;
2020-12-08 15:55:53 +01:00
m_last_width = 0.f ;
2020-08-17 10:06:41 +02:00
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
2020-12-01 08:23:46 +01:00
m_last_mm3_per_mm = 0. ;
2020-08-17 10:06:41 +02:00
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
2018-01-08 13:44:10 +01:00
2017-05-03 18:28:22 +02:00
// How many times will be change_layer() called?
// change_layer() in turn increments the progress bar status.
m_layer_count = 0 ;
2018-09-11 14:04:47 +02:00
if ( print . config (). complete_objects . value ) {
2017-05-10 11:25:57 +02:00
// Add each of the object's layers separately.
2018-11-08 14:23:17 +01:00
for ( auto object : print . objects ()) {
2017-05-10 11:25:57 +02:00
std :: vector < coordf_t > zs ;
2018-09-11 14:04:47 +02:00
zs . reserve ( object -> layers (). size () + object -> support_layers (). size ());
for ( auto layer : object -> layers ())
2017-05-10 11:25:57 +02:00
zs . push_back ( layer -> print_z );
2018-09-11 14:04:47 +02:00
for ( auto layer : object -> support_layers ())
2017-05-10 11:25:57 +02:00
zs . push_back ( layer -> print_z );
std :: sort ( zs . begin (), zs . end ());
2020-01-23 09:53:06 +01:00
m_layer_count += ( unsigned int )( object -> instances (). size () * ( std :: unique ( zs . begin (), zs . end ()) - zs . begin ()));
2017-05-10 11:25:57 +02:00
}
2020-01-10 11:26:52 +01:00
} else {
2017-05-10 11:25:57 +02:00
// Print all objects with the same print_z together.
std :: vector < coordf_t > zs ;
2018-11-08 14:23:17 +01:00
for ( auto object : print . objects ()) {
2018-09-11 14:04:47 +02:00
zs . reserve ( zs . size () + object -> layers (). size () + object -> support_layers (). size ());
for ( auto layer : object -> layers ())
2017-05-10 11:25:57 +02:00
zs . push_back ( layer -> print_z );
2018-09-11 14:04:47 +02:00
for ( auto layer : object -> support_layers ())
2017-05-10 11:25:57 +02:00
zs . push_back ( layer -> print_z );
}
std :: sort ( zs . begin (), zs . end ());
m_layer_count = ( unsigned int )( std :: unique ( zs . begin (), zs . end ()) - zs . begin ());
}
2018-03-28 17:05:31 +02:00
print . throw_if_canceled ();
2017-05-03 18:28:22 +02:00
m_enable_cooling_markers = true ;
2018-09-11 14:04:47 +02:00
this -> apply_print_config ( print . config ());
2019-11-22 12:39:03 +01:00
2020-01-10 11:26:52 +01:00
m_volumetric_speed = DoExport :: autospeed_volumetric_limit ( print );
2018-03-28 17:05:31 +02:00
print . throw_if_canceled ();
2019-11-22 12:39:03 +01:00
2017-05-03 18:57:33 +02:00
m_cooling_buffer = make_unique < CoolingBuffer > ( * this );
2018-09-11 14:04:47 +02:00
if ( print . config (). spiral_vase . value )
m_spiral_vase = make_unique < SpiralVase > ( print . config ());
2019-01-29 18:07:45 +01:00
#ifdef HAS_PRESSURE_EQUALIZER
2018-09-11 14:04:47 +02:00
if ( print . config (). max_volumetric_extrusion_rate_slope_positive . value > 0 ||
print . config (). max_volumetric_extrusion_rate_slope_negative . value > 0 )
m_pressure_equalizer = make_unique < PressureEqualizer > ( & print . config ());
2017-05-03 18:28:22 +02:00
m_enable_extrusion_role_markers = ( bool ) m_pressure_equalizer ;
2019-01-29 18:07:45 +01:00
#else /* HAS_PRESSURE_EQUALIZER */
m_enable_extrusion_role_markers = false ;
#endif /* HAS_PRESSURE_EQUALIZER */
2017-05-03 18:28:22 +02:00
// Write information on the generator.
2017-12-14 09:18:28 +01:00
_write_format ( file , "; %s \n\n " , Slic3r :: header_slic3r_generated (). c_str ());
2019-10-23 13:31:24 +02:00
2020-09-01 18:33:56 +02:00
DoExport :: export_thumbnails_to_file ( thumbnail_cb , print . full_print_config (). option < ConfigOptionPoints > ( "thumbnails" ) -> values ,
[ this , file ]( const char * sz ) { this -> _write ( file , sz ); },
2020-01-10 11:26:52 +01:00
[ & print ]() { print . throw_if_canceled (); });
2019-10-23 13:31:24 +02:00
2017-05-03 18:28:22 +02:00
// Write notes (content of the Print Settings tab -> Notes)
{
std :: list < std :: string > lines ;
2018-09-11 14:04:47 +02:00
boost :: split ( lines , print . config (). notes . value , boost :: is_any_of ( " \n " ), boost :: token_compress_off );
2017-05-03 18:28:22 +02:00
for ( auto line : lines ) {
// Remove the trailing '\r' from the '\r\n' sequence.
if ( ! line . empty () && line . back () == '\r' )
line . pop_back ();
2017-12-14 09:18:28 +01:00
_write_format ( file , "; %s \n " , line . c_str ());
2017-05-03 18:28:22 +02:00
}
if ( ! lines . empty ())
2017-12-14 09:18:28 +01:00
_write ( file , " \n " );
2017-05-03 18:28:22 +02:00
}
2018-03-28 17:05:31 +02:00
print . throw_if_canceled ();
2017-05-03 18:28:22 +02:00
// Write some terse information on the slicing parameters.
2018-11-08 14:23:17 +01:00
const PrintObject * first_object = print . objects (). front ();
2018-09-11 14:04:47 +02:00
const double layer_height = first_object -> config (). layer_height . value ;
2021-04-21 14:34:43 +02:00
assert ( ! print . config (). first_layer_height . percent );
const double first_layer_height = print . config (). first_layer_height . value ;
2021-05-05 16:21:55 +02:00
for ( size_t region_id = 0 ; region_id < print . num_print_regions (); ++ region_id ) {
2021-05-05 18:13:58 +02:00
const PrintRegion & region = print . get_print_region ( region_id );
2021-05-05 16:21:55 +02:00
_write_format ( file , "; external perimeters extrusion width = %.2fmm \n " , region . flow ( * first_object , frExternalPerimeter , layer_height ). width ());
_write_format ( file , "; perimeters extrusion width = %.2fmm \n " , region . flow ( * first_object , frPerimeter , layer_height ). width ());
_write_format ( file , "; infill extrusion width = %.2fmm \n " , region . flow ( * first_object , frInfill , layer_height ). width ());
_write_format ( file , "; solid infill extrusion width = %.2fmm \n " , region . flow ( * first_object , frSolidInfill , layer_height ). width ());
_write_format ( file , "; top infill extrusion width = %.2fmm \n " , region . flow ( * first_object , frTopSolidInfill , layer_height ). width ());
2017-12-13 15:35:00 +01:00
if ( print . has_support_material ())
2021-03-08 13:44:00 +01:00
_write_format ( file , "; support material extrusion width = %.2fmm \n " , support_material_flow ( first_object ). width ());
2018-09-11 14:04:47 +02:00
if ( print . config (). first_layer_extrusion_width . value > 0 )
2021-05-05 16:21:55 +02:00
_write_format ( file , "; first layer extrusion width = %.2fmm \n " , region . flow ( * first_object , frPerimeter , first_layer_height , true ). width ());
2018-01-02 10:57:30 +01:00
_write_format ( file , " \n " );
2017-05-03 18:28:22 +02:00
}
2018-03-28 17:05:31 +02:00
print . throw_if_canceled ();
2020-09-01 18:33:56 +02:00
2018-09-07 16:05:10 +02:00
// adds tags for time estimators
2020-08-10 14:22:05 +02:00
if ( print . config (). remaining_times . value )
2021-02-18 14:34:40 +01:00
#if ENABLE_VALIDATE_CUSTOM_GCODE
_write_format ( file , ";%s \n " , GCodeProcessor :: reserved_tag ( GCodeProcessor :: ETags :: First_Line_M73_Placeholder ). c_str ());
#else
2020-08-11 14:23:47 +02:00
_writeln ( file , GCodeProcessor :: First_Line_M73_Placeholder_Tag );
2021-02-18 14:34:40 +01:00
#endif // ENABLE_VALIDATE_CUSTOM_GCODE
2018-09-07 16:05:10 +02:00
2019-08-07 10:54:36 +02:00
// Prepare the helper object for replacing placeholders in custom G-code and output filename.
2018-09-11 14:04:47 +02:00
m_placeholder_parser = print . placeholder_parser ();
2017-05-03 18:28:22 +02:00
m_placeholder_parser . update_timestamp ();
2021-03-10 09:00:54 +01:00
m_placeholder_parser_context . rng = std :: mt19937 ( std :: chrono :: high_resolution_clock :: now (). time_since_epoch (). count ());
2019-05-17 16:27:00 +02:00
print . update_object_placeholders ( m_placeholder_parser . config_writable (), ".gcode" );
2017-05-16 15:30:03 +02:00
// Get optimal tool ordering to minimize tool switches of a multi-exruder print.
// For a print by objects, find the 1st printing object.
2017-05-23 15:00:01 +02:00
ToolOrdering tool_ordering ;
unsigned int initial_extruder_id = ( unsigned int ) - 1 ;
unsigned int final_extruder_id = ( unsigned int ) - 1 ;
2017-12-04 11:57:54 +01:00
bool has_wipe_tower = false ;
2020-01-23 09:53:06 +01:00
std :: vector < const PrintInstance *> print_object_instances_ordering ;
std :: vector < const PrintInstance *>:: const_iterator print_object_instance_sequential_active ;
2018-09-11 14:04:47 +02:00
if ( print . config (). complete_objects . value ) {
2020-01-23 09:53:06 +01:00
// Order object instances for sequential print.
print_object_instances_ordering = sort_object_instances_by_model_order ( print );
// print_object_instances_ordering = sort_object_instances_by_max_z(print);
2018-07-23 15:58:08 +02:00
// Find the 1st printing object, find its tool ordering and the initial extruder ID.
2020-01-23 09:53:06 +01:00
print_object_instance_sequential_active = print_object_instances_ordering . begin ();
for (; print_object_instance_sequential_active != print_object_instances_ordering . end (); ++ print_object_instance_sequential_active ) {
tool_ordering = ToolOrdering ( * ( * print_object_instance_sequential_active ) -> print_object , initial_extruder_id );
if (( initial_extruder_id = tool_ordering . first_extruder ()) != static_cast < unsigned int > ( - 1 ))
2018-07-23 15:58:08 +02:00
break ;
}
2020-01-14 10:31:18 +01:00
// We don't allow switching of extruders per layer by Model::custom_gcode_per_print_z in sequential mode.
// Use the extruder IDs collected from Regions.
2020-09-01 18:33:56 +02:00
this -> set_extruders ( print . extruders ());
2018-07-27 22:19:46 +02:00
} else {
2020-09-01 18:33:56 +02:00
// Find tool ordering for all the objects at once, and the initial extruder ID.
2017-05-25 22:27:53 +02:00
// If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it.
2020-09-01 18:33:56 +02:00
tool_ordering = print . tool_ordering ();
tool_ordering . assign_custom_gcodes ( print );
2017-12-04 11:57:54 +01:00
has_wipe_tower = print . has_wipe_tower () && tool_ordering . has_wipe_tower ();
2020-01-14 10:31:18 +01:00
initial_extruder_id = ( has_wipe_tower && ! print . config (). single_extruder_multi_material_priming ) ?
2018-07-27 22:19:46 +02:00
// The priming towers will be skipped.
tool_ordering . all_extruders (). back () :
2020-01-14 10:31:18 +01:00
// Don't skip the priming towers.
2018-07-27 22:19:46 +02:00
tool_ordering . first_extruder ();
2020-01-14 10:31:18 +01:00
// In non-sequential print, the printing extruders may have been modified by the extruder switches stored in Model::custom_gcode_per_print_z.
// Therefore initialize the printing extruders from there.
2020-09-01 18:33:56 +02:00
this -> set_extruders ( tool_ordering . all_extruders ());
2020-01-23 09:53:06 +01:00
// Order object instances using a nearest neighbor search.
print_object_instances_ordering = chain_print_object_instances ( print );
2017-05-16 15:30:03 +02:00
}
2017-05-17 19:25:36 +02:00
if ( initial_extruder_id == ( unsigned int ) - 1 ) {
2017-05-16 15:30:03 +02:00
// Nothing to print!
initial_extruder_id = 0 ;
2017-05-17 19:25:36 +02:00
final_extruder_id = 0 ;
} else {
2017-05-23 15:00:01 +02:00
final_extruder_id = tool_ordering . last_extruder ();
2017-05-17 19:25:36 +02:00
assert ( final_extruder_id != ( unsigned int ) - 1 );
}
2018-03-28 17:05:31 +02:00
print . throw_if_canceled ();
2017-05-16 15:30:03 +02:00
2017-06-23 10:13:09 +02:00
m_cooling_buffer -> set_current_extruder ( initial_extruder_id );
2018-07-17 19:37:24 +02:00
// Emit machine envelope limits for the Marlin firmware.
this -> print_machine_envelope ( file , print );
2017-06-21 17:45:55 +02:00
// Disable fan.
2018-09-11 14:04:47 +02:00
if ( ! print . config (). cooling . get_at ( initial_extruder_id ) || print . config (). disable_fan_first_layers . get_at ( initial_extruder_id ))
2018-01-02 10:57:30 +01:00
_write ( file , m_writer . set_fan ( 0 , true ));
2017-06-21 17:45:55 +02:00
2017-05-16 15:30:03 +02:00
// Let the start-up script prime the 1st printing tool.
m_placeholder_parser . set ( "initial_tool" , initial_extruder_id );
2017-07-31 15:42:55 +02:00
m_placeholder_parser . set ( "initial_extruder" , initial_extruder_id );
m_placeholder_parser . set ( "current_extruder" , initial_extruder_id );
2019-02-08 18:02:46 -05:00
//Set variable for total layer count so it can be used in custom gcode.
m_placeholder_parser . set ( "total_layer_count" , m_layer_count );
2017-11-28 15:30:05 +01:00
// Useful for sequential prints.
m_placeholder_parser . set ( "current_object_idx" , 0 );
2017-12-04 11:57:54 +01:00
// For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided.
m_placeholder_parser . set ( "has_wipe_tower" , has_wipe_tower );
2018-09-12 11:59:02 +02:00
m_placeholder_parser . set ( "has_single_extruder_multi_material_priming" , has_wipe_tower && print . config (). single_extruder_multi_material_priming );
2020-01-10 11:26:52 +01:00
m_placeholder_parser . set ( "total_toolchanges" , std :: max ( 0 , print . wipe_tower_data (). number_of_toolchanges )); // Check for negative toolchanges (single extruder mode) and set to 0 (no tool change).
2020-06-03 16:29:42 +02:00
{
BoundingBoxf bbox ( print . config (). bed_shape . values );
m_placeholder_parser . set ( "print_bed_min" , new ConfigOptionFloats ({ bbox . min . x (), bbox . min . y () }));
m_placeholder_parser . set ( "print_bed_max" , new ConfigOptionFloats ({ bbox . max . x (), bbox . max . y () }));
m_placeholder_parser . set ( "print_bed_size" , new ConfigOptionFloats ({ bbox . size (). x (), bbox . size (). y () }));
}
2020-06-03 14:49:40 +02:00
{
// Convex hull of the 1st layer extrusions, for bed leveling and placing the initial purge line.
// It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower.
// It does NOT encompass user extrusions generated by custom G-code,
// therefore it does NOT encompass the initial purge line.
// It does NOT encompass MMU/MMU2 starting (wipe) areas.
auto pts = std :: make_unique < ConfigOptionPoints > ();
pts -> values . reserve ( print . first_layer_convex_hull (). size ());
for ( const Point & pt : print . first_layer_convex_hull (). points )
pts -> values . emplace_back ( unscale ( pt ));
BoundingBoxf bbox ( pts -> values );
m_placeholder_parser . set ( "first_layer_print_convex_hull" , pts . release ());
2020-06-03 16:29:42 +02:00
m_placeholder_parser . set ( "first_layer_print_min" , new ConfigOptionFloats ({ bbox . min . x (), bbox . min . y () }));
m_placeholder_parser . set ( "first_layer_print_max" , new ConfigOptionFloats ({ bbox . max . x (), bbox . max . y () }));
m_placeholder_parser . set ( "first_layer_print_size" , new ConfigOptionFloats ({ bbox . size (). x (), bbox . size (). y () }));
2020-06-03 14:49:40 +02:00
}
2018-09-11 14:04:47 +02:00
std :: string start_gcode = this -> placeholder_parser_process ( "start_gcode" , print . config (). start_gcode . value , initial_extruder_id );
2017-11-28 15:19:57 +01:00
// Set bed temperature if the start G-code does not contain any bed temp control G-codes.
this -> _print_first_layer_bed_temperature ( file , print , start_gcode , initial_extruder_id , true );
// Set extruder(s) temperature before and after start G-code.
this -> _print_first_layer_extruder_temperatures ( file , print , start_gcode , initial_extruder_id , false );
2018-02-22 08:59:47 +01:00
2020-04-02 12:03:18 +02:00
// adds tag for processor
2021-02-18 14:34:40 +01:00
#if ENABLE_VALIDATE_CUSTOM_GCODE
_write_format ( file , ";%s%s \n " , GCodeProcessor :: reserved_tag ( GCodeProcessor :: ETags :: Role ). c_str (), ExtrusionEntity :: role_to_string ( erCustom ). c_str ());
#else
2020-07-28 09:48:55 +02:00
_write_format ( file , ";%s%s \n " , GCodeProcessor :: Extrusion_Role_Tag . c_str (), ExtrusionEntity :: role_to_string ( erCustom ). c_str ());
2021-02-18 14:34:40 +01:00
#endif // ENABLE_VALIDATE_CUSTOM_GCODE
2020-04-02 12:03:18 +02:00
2017-11-28 15:19:57 +01:00
// Write the custom start G-code
2017-12-14 09:18:28 +01:00
_writeln ( file , start_gcode );
2019-06-12 15:47:05 +02:00
// Process filament-specific gcode.
/* if (has_wipe_tower) {
2019-05-03 00:17:24 -04:00
// Wipe tower will control the extruder switching, it will call the start_filament_gcode.
2017-12-04 11:57:54 +01:00
} else {
2019-01-07 15:12:40 +01:00
DynamicConfig config;
config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(initial_extruder_id)));
2019-06-12 15:47:05 +02:00
_writeln(file, this->placeholder_parser_process("start_filament_gcode", print.config().start_filament_gcode.values[initial_extruder_id], initial_extruder_id, &config));
2017-11-30 16:01:47 +01:00
}
2019-06-12 15:47:05 +02:00
*/
2017-11-28 15:19:57 +01:00
this -> _print_first_layer_extruder_temperatures ( file , print , start_gcode , initial_extruder_id , true );
2018-03-28 17:05:31 +02:00
print . throw_if_canceled ();
2017-05-03 18:28:22 +02:00
// Set other general things.
2017-12-14 09:18:28 +01:00
_write ( file , this -> preamble ());
2017-05-03 18:28:22 +02:00
// Calculate wiping points if needed
2020-01-10 11:26:52 +01:00
DoExport :: init_ooze_prevention ( print , m_ooze_prevention );
print . throw_if_canceled ();
2020-09-01 18:33:56 +02:00
2020-09-02 00:26:13 +02:00
// Collect custom seam data from all objects.
2020-09-09 13:21:39 +02:00
m_seam_placer . init ( print );
2020-09-02 00:26:13 +02:00
2018-09-12 11:59:02 +02:00
if ( ! ( has_wipe_tower && print . config (). single_extruder_multi_material_priming )) {
2018-07-27 22:19:46 +02:00
// Set initial extruder only after custom start G-code.
// Ugly hack: Do not set the initial extruder if the extruder is primed using the MMU priming towers at the edge of the print bed.
2019-01-29 12:02:48 +01:00
_write ( file , this -> set_extruder ( initial_extruder_id , 0. ));
2018-07-27 22:19:46 +02:00
}
2017-05-16 15:30:03 +02:00
2017-05-03 18:28:22 +02:00
// Do all objects for each layer.
2018-09-11 14:04:47 +02:00
if ( print . config (). complete_objects . value ) {
2017-05-03 18:28:22 +02:00
size_t finished_objects = 0 ;
2020-01-23 09:53:06 +01:00
const PrintObject * prev_object = ( * print_object_instance_sequential_active ) -> print_object ;
for (; print_object_instance_sequential_active != print_object_instances_ordering . end (); ++ print_object_instance_sequential_active ) {
const PrintObject & object = * ( * print_object_instance_sequential_active ) -> print_object ;
if ( & object != prev_object || tool_ordering . first_extruder () != final_extruder_id ) {
tool_ordering = ToolOrdering ( object , final_extruder_id );
unsigned int new_extruder_id = tool_ordering . first_extruder ();
if ( new_extruder_id == ( unsigned int ) - 1 )
// Skip this object.
continue ;
initial_extruder_id = new_extruder_id ;
final_extruder_id = tool_ordering . last_extruder ();
assert ( final_extruder_id != ( unsigned int ) - 1 );
2017-05-03 18:28:22 +02:00
}
2020-01-23 09:53:06 +01:00
print . throw_if_canceled ();
this -> set_origin ( unscale (( * print_object_instance_sequential_active ) -> shift ));
if ( finished_objects > 0 ) {
// Move to the origin position for the copy we're going to print.
// This happens before Z goes down to layer 0 again, so that no collision happens hopefully.
m_enable_cooling_markers = false ; // we're not filtering these moves through CoolingBuffer
2020-11-17 10:42:27 +01:00
m_avoid_crossing_perimeters . use_external_mp_once ();
2020-01-23 09:53:06 +01:00
_write ( file , this -> retract ());
_write ( file , this -> travel_to ( Point ( 0 , 0 ), erNone , "move to origin position for next object" ));
m_enable_cooling_markers = true ;
// Disable motion planner when traveling to first object point.
2020-11-17 10:42:27 +01:00
m_avoid_crossing_perimeters . disable_once ();
2020-01-23 09:53:06 +01:00
// Ff we are printing the bottom layer of an object, and we have already finished
// another one, set first layer temperatures. This happens before the Z move
// is triggered, so machine has more time to reach such temperatures.
m_placeholder_parser . set ( "current_object_idx" , int ( finished_objects ));
std :: string between_objects_gcode = this -> placeholder_parser_process ( "between_objects_gcode" , print . config (). between_objects_gcode . value , initial_extruder_id );
// Set first layer bed and extruder temperatures, don't wait for it to reach the temperature.
this -> _print_first_layer_bed_temperature ( file , print , between_objects_gcode , initial_extruder_id , false );
this -> _print_first_layer_extruder_temperatures ( file , print , between_objects_gcode , initial_extruder_id , false );
_writeln ( file , between_objects_gcode );
}
// Reset the cooling buffer internal state (the current position, feed rate, accelerations).
m_cooling_buffer -> reset ();
m_cooling_buffer -> set_current_extruder ( initial_extruder_id );
// Pair the object layers with the support layers by z, extrude them.
std :: vector < LayerToPrint > layers_to_print = collect_layers_to_print ( object );
for ( const LayerToPrint & ltp : layers_to_print ) {
std :: vector < LayerToPrint > lrs ;
lrs . emplace_back ( std :: move ( ltp ));
2021-02-16 11:30:49 +01:00
this -> process_layer ( file , print , lrs , tool_ordering . tools_for_layer ( ltp . print_z ()), & ltp == & layers_to_print . back (),
nullptr , * print_object_instance_sequential_active - object . instances (). data ());
2020-01-23 09:53:06 +01:00
print . throw_if_canceled ();
}
#ifdef HAS_PRESSURE_EQUALIZER
if ( m_pressure_equalizer )
_write ( file , m_pressure_equalizer -> process ( "" , true ));
#endif /* HAS_PRESSURE_EQUALIZER */
++ finished_objects ;
// Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed.
// Reset it when starting another object from 1st layer.
m_second_layer_things_done = false ;
prev_object = & object ;
2017-05-03 18:28:22 +02:00
}
} else {
// Sort layers by Z.
2017-05-10 11:25:57 +02:00
// All extrusion moves with the same top layer height are extruded uninterrupted.
2017-05-23 17:09:43 +02:00
std :: vector < std :: pair < coordf_t , std :: vector < LayerToPrint >>> layers_to_print = collect_layers_to_print ( print );
2017-05-16 13:45:28 +02:00
// Prusa Multi-Material wipe tower.
2017-12-04 11:57:54 +01:00
if ( has_wipe_tower && ! layers_to_print . empty ()) {
2018-09-11 14:04:47 +02:00
m_wipe_tower . reset ( new WipeTowerIntegration ( print . config (), * print . wipe_tower_data (). priming . get (), print . wipe_tower_data (). tool_changes , * print . wipe_tower_data (). final_purge . get ()));
2018-01-02 10:57:30 +01:00
_write ( file , m_writer . travel_to_z ( first_layer_height + m_config . z_offset . value , "Move to the first layer height" ));
2018-09-12 11:59:02 +02:00
if ( print . config (). single_extruder_multi_material_priming ) {
2020-09-01 18:33:56 +02:00
_write ( file , m_wipe_tower -> prime ( * this ));
2018-07-27 22:19:46 +02:00
// Verify, whether the print overaps the priming extrusions.
BoundingBoxf bbox_print ( get_print_extrusions_extents ( print ));
coordf_t twolayers_printz = (( layers_to_print . size () == 1 ) ? layers_to_print . front () : layers_to_print [ 1 ]). first + EPSILON ;
2018-11-08 14:23:17 +01:00
for ( const PrintObject * print_object : print . objects ())
2018-07-27 22:19:46 +02:00
bbox_print . merge ( get_print_object_extrusions_extents ( * print_object , twolayers_printz ));
bbox_print . merge ( get_wipe_tower_extrusions_extents ( print , twolayers_printz ));
BoundingBoxf bbox_prime ( get_wipe_tower_priming_extrusions_extents ( print ));
bbox_prime . offset ( 0.5f );
2020-12-14 14:19:35 +01:00
bool overlap = bbox_prime . overlap ( bbox_print );
2021-03-30 13:30:18 +02:00
if ( print . config (). gcode_flavor == gcfMarlinLegacy || print . config (). gcode_flavor == gcfMarlinFirmware ) {
2020-12-14 14:19:35 +01:00
_write ( file , this -> retract ());
_write ( file , "M300 S800 P500 \n " ); // Beep for 500ms, tone 800Hz.
if ( overlap ) {
// Wait for the user to remove the priming extrusions.
_write ( file , "M1 Remove priming towers and click button. \n " );
} else {
// Just wait for a bit to let the user check, that the priming succeeded.
//TODO Add a message explaining what the printer is waiting for. This needs a firmware fix.
_write ( file , "M1 S10 \n " );
}
} else {
// This is not Marlin, M1 command is probably not supported.
// (See https://github.com/prusa3d/PrusaSlicer/issues/5441.)
if ( overlap ) {
print . active_step_add_warning ( PrintStateBase :: WarningLevel :: CRITICAL ,
2020-12-18 13:04:28 +01:00
_ ( L ( "Your print is very close to the priming regions. "
"Make sure there is no collision." )));
2020-12-14 14:19:35 +01:00
} else {
// Just continue printing, no action necessary.
}
2018-07-27 22:19:46 +02:00
}
2017-12-04 11:57:54 +01:00
}
2018-03-28 17:05:31 +02:00
print . throw_if_canceled ();
2017-09-01 17:30:18 +02:00
}
2017-05-10 11:25:57 +02:00
// Extrude the layers.
2017-05-23 17:09:43 +02:00
for ( auto & layer : layers_to_print ) {
2018-06-26 14:12:25 +02:00
const LayerTools & layer_tools = tool_ordering . tools_for_layer ( layer . first );
2017-05-25 22:27:53 +02:00
if ( m_wipe_tower && layer_tools . has_wipe_tower )
m_wipe_tower -> next_layer ();
2021-02-16 11:30:49 +01:00
this -> process_layer ( file , print , layer . second , layer_tools , & layer == & layers_to_print . back (), & print_object_instances_ordering , size_t ( - 1 ));
2018-03-28 17:05:31 +02:00
print . throw_if_canceled ();
2017-05-16 13:45:28 +02:00
}
2019-01-29 18:07:45 +01:00
#ifdef HAS_PRESSURE_EQUALIZER
2017-06-30 17:05:58 +02:00
if ( m_pressure_equalizer )
2018-01-02 10:57:30 +01:00
_write ( file , m_pressure_equalizer -> process ( "" , true ));
2019-01-29 18:07:45 +01:00
#endif /* HAS_PRESSURE_EQUALIZER */
2017-05-25 22:27:53 +02:00
if ( m_wipe_tower )
// Purge the extruder, pull out the active filament.
2018-01-02 10:57:30 +01:00
_write ( file , m_wipe_tower -> finalize ( * this ));
2017-05-03 18:28:22 +02:00
}
2017-06-05 11:30:57 +02:00
// Write end commands to file.
2017-12-14 09:18:28 +01:00
_write ( file , this -> retract ());
_write ( file , m_writer . set_fan ( false ));
2018-02-22 08:59:47 +01:00
2020-04-02 12:03:18 +02:00
// adds tag for processor
2021-02-18 14:34:40 +01:00
#if ENABLE_VALIDATE_CUSTOM_GCODE
_write_format ( file , ";%s%s \n " , GCodeProcessor :: reserved_tag ( GCodeProcessor :: ETags :: Role ). c_str (), ExtrusionEntity :: role_to_string ( erCustom ). c_str ());
#else
2020-07-28 09:48:55 +02:00
_write_format ( file , ";%s%s \n " , GCodeProcessor :: Extrusion_Role_Tag . c_str (), ExtrusionEntity :: role_to_string ( erCustom ). c_str ());
2021-02-18 14:34:40 +01:00
#endif // ENABLE_VALIDATE_CUSTOM_GCODE
2020-04-02 12:03:18 +02:00
2017-06-05 11:30:57 +02:00
// Process filament-specific gcode in extruder order.
2018-05-15 14:04:29 +02:00
{
DynamicConfig config ;
config . set_key_value ( "layer_num" , new ConfigOptionInt ( m_layer_index ));
2018-08-17 15:53:43 +02:00
config . set_key_value ( "layer_z" , new ConfigOptionFloat ( m_writer . get_position ()( 2 ) - m_config . z_offset . value ));
2020-12-01 08:23:46 +01:00
config . set_key_value ( "max_layer_z" , new ConfigOptionFloat ( m_max_layer_z ));
2018-09-12 11:59:02 +02:00
if ( print . config (). single_extruder_multi_material ) {
2018-05-15 14:04:29 +02:00
// Process the end_filament_gcode for the active filament only.
2019-01-07 15:12:40 +01:00
int extruder_id = m_writer . extruder () -> id ();
config . set_key_value ( "filament_extruder_id" , new ConfigOptionInt ( extruder_id ));
_writeln ( file , this -> placeholder_parser_process ( "end_filament_gcode" , print . config (). end_filament_gcode . get_at ( extruder_id ), extruder_id , & config ));
2018-05-15 14:04:29 +02:00
} else {
2019-01-07 15:12:40 +01:00
for ( const std :: string & end_gcode : print . config (). end_filament_gcode . values ) {
2020-09-01 18:33:56 +02:00
int extruder_id = ( unsigned int )( & end_gcode - & print . config (). end_filament_gcode . values . front ());
2019-01-07 15:12:40 +01:00
config . set_key_value ( "filament_extruder_id" , new ConfigOptionInt ( extruder_id ));
_writeln ( file , this -> placeholder_parser_process ( "end_filament_gcode" , end_gcode , extruder_id , & config ));
}
2018-05-15 14:04:29 +02:00
}
2018-09-12 11:59:02 +02:00
_writeln ( file , this -> placeholder_parser_process ( "end_gcode" , print . config (). end_gcode , m_writer . extruder () -> id (), & config ));
2017-11-30 16:01:47 +01:00
}
2017-12-14 09:18:28 +01:00
_write ( file , m_writer . update_progress ( m_layer_count , m_layer_count , true )); // 100%
_write ( file , m_writer . postamble ());
2019-04-08 11:54:58 +02:00
// adds tags for time estimators
if ( print . config (). remaining_times . value )
2021-02-18 14:34:40 +01:00
#if ENABLE_VALIDATE_CUSTOM_GCODE
_write_format ( file , ";%s \n " , GCodeProcessor :: reserved_tag ( GCodeProcessor :: ETags :: Last_Line_M73_Placeholder ). c_str ());
#else
2020-08-11 14:23:47 +02:00
_writeln ( file , GCodeProcessor :: Last_Line_M73_Placeholder_Tag );
2021-02-18 14:34:40 +01:00
#endif // ENABLE_VALIDATE_CUSTOM_GCODE
2019-04-08 11:54:58 +02:00
2018-03-28 17:05:31 +02:00
print . throw_if_canceled ();
2017-05-16 13:45:28 +02:00
2017-06-05 11:30:57 +02:00
// Get filament stats.
2020-01-10 11:26:52 +01:00
_write ( file , DoExport :: update_print_stats_and_format_filament_stats (
// Const inputs
2020-08-05 15:43:46 +02:00
has_wipe_tower , print . wipe_tower_data (),
2020-01-10 11:26:52 +01:00
m_writer . extruders (),
// Modifies
print . m_print_statistics ));
2020-02-14 12:50:26 +01:00
_write ( file , " \n " );
2020-10-28 10:46:59 +01:00
_write_format ( file , "; total filament used [g] = %.2lf \n " , print . m_print_statistics . total_weight );
_write_format ( file , "; total filament cost = %.2lf \n " , print . m_print_statistics . total_cost );
2019-11-05 11:37:40 +01:00
if ( print . m_print_statistics . total_toolchanges > 0 )
_write_format ( file , "; total toolchanges = %i \n " , print . m_print_statistics . total_toolchanges );
2021-02-18 14:34:40 +01:00
#if ENABLE_VALIDATE_CUSTOM_GCODE
_write_format ( file , ";%s \n " , GCodeProcessor :: reserved_tag ( GCodeProcessor :: ETags :: Estimated_Printing_Time_Placeholder ). c_str ());
#else
2020-08-11 14:23:47 +02:00
_writeln ( file , GCodeProcessor :: Estimated_Printing_Time_Placeholder_Tag );
2021-02-18 14:34:40 +01:00
#endif // ENABLE_VALIDATE_CUSTOM_GCODE
2017-05-03 18:28:22 +02:00
// Append full config.
2017-12-14 09:18:28 +01:00
_write ( file , " \n " );
2017-06-14 15:16:43 +02:00
{
2020-10-28 09:51:05 +01:00
std :: string full_config ;
2018-02-13 15:19:55 +01:00
append_full_config ( print , full_config );
if ( ! full_config . empty ())
_write ( file , full_config );
2017-06-14 15:16:43 +02:00
}
2018-03-28 17:05:31 +02:00
print . throw_if_canceled ();
2017-12-05 15:54:24 +01:00
}
2017-05-03 18:28:22 +02:00
2017-12-05 15:54:24 +01:00
std :: string GCode :: placeholder_parser_process ( const std :: string & name , const std :: string & templ , unsigned int current_extruder_id , const DynamicConfig * config_override )
{
try {
2020-12-09 09:19:46 +01:00
return m_placeholder_parser . process ( templ , current_extruder_id , config_override , & m_placeholder_parser_context );
2017-12-05 15:54:24 +01:00
} catch ( std :: runtime_error & err ) {
// Collect the names of failed template substitutions for error reporting.
2020-12-03 12:50:18 +01:00
auto it = m_placeholder_parser_failed_templates . find ( name );
if ( it == m_placeholder_parser_failed_templates . end ())
// Only if there was no error reported for this template, store the first error message into the map to be reported.
// We don't want to collect error message for each and every occurence of a single custom G-code section.
m_placeholder_parser_failed_templates . insert ( it , std :: make_pair ( name , std :: string ( err . what ())));
2017-12-05 15:54:24 +01:00
// Insert the macro error message into the G-code.
2017-12-05 17:38:29 +01:00
return
2020-09-01 18:33:56 +02:00
std :: string ( " \n !!!!! Failed to process the custom G-code template " ) + name + " \n " +
err . what () +
2017-12-05 17:38:29 +01:00
"!!!!! End of an error report for the custom G-code template " + name + " \n\n " ;
2017-12-05 15:54:24 +01:00
}
2015-07-01 23:00:52 +02:00
}
2020-07-20 20:57:37 +10:00
// Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait or optionally G10 with temperature inside the custom G-code.
2017-11-28 15:19:57 +01:00
// Returns true if one of the temp commands are found, and try to parse the target temperature value into temp_out.
2020-07-20 20:57:37 +10:00
static bool custom_gcode_sets_temperature ( const std :: string & gcode , const int mcode_set_temp_dont_wait , const int mcode_set_temp_and_wait , const bool include_g10 , int & temp_out )
2017-11-28 15:19:57 +01:00
{
temp_out = - 1 ;
if ( gcode . empty ())
return false ;
const char * ptr = gcode . data ();
bool temp_set_by_gcode = false ;
while ( * ptr != 0 ) {
// Skip whitespaces.
for (; * ptr == ' ' || * ptr == '\t' ; ++ ptr );
2020-10-24 17:32:30 +02:00
if ( * ptr == 'M' || // Line starts with 'M'. It is a machine command.
( * ptr == 'G' && include_g10 )) { // Only check for G10 if requested
bool is_gcode = * ptr == 'G' ;
2017-11-28 15:19:57 +01:00
++ ptr ;
2020-10-24 17:32:30 +02:00
// Parse the M or G code value.
2017-11-28 15:19:57 +01:00
char * endptr = nullptr ;
2020-10-24 17:32:30 +02:00
int mgcode = int ( strtol ( ptr , & endptr , 10 ));
if ( endptr != nullptr && endptr != ptr &&
is_gcode ?
// G10 found
mgcode == 10 :
// M104/M109 or M140/M190 found.
( mgcode == mcode_set_temp_dont_wait || mgcode == mcode_set_temp_and_wait )) {
2020-07-20 20:57:37 +10:00
ptr = endptr ;
2020-10-24 17:32:30 +02:00
if ( ! is_gcode )
// Let the caller know that the custom M-code sets the temperature.
temp_set_by_gcode = true ;
2020-07-20 20:57:37 +10:00
// Now try to parse the temperature value.
// While not at the end of the line:
while ( strchr ( "; \r\n\0 " , * ptr ) == nullptr ) {
// Skip whitespaces.
for (; * ptr == ' ' || * ptr == '\t' ; ++ ptr );
if ( * ptr == 'S' ) {
// Skip whitespaces.
for ( ++ ptr ; * ptr == ' ' || * ptr == '\t' ; ++ ptr );
// Parse an int.
endptr = nullptr ;
long temp_parsed = strtol ( ptr , & endptr , 10 );
if ( endptr > ptr ) {
ptr = endptr ;
temp_out = temp_parsed ;
// Let the caller know that the custom G-code sets the temperature
// Only do this after successfully parsing temperature since G10
// can be used for other reasons
temp_set_by_gcode = true ;
}
} else {
// Skip this word.
for (; strchr ( " \t ; \r\n\0 " , * ptr ) == nullptr ; ++ ptr );
}
}
}
2017-11-28 15:19:57 +01:00
}
// Skip the rest of the line.
for (; * ptr != 0 && * ptr != '\r' && * ptr != '\n' ; ++ ptr );
2020-09-01 18:33:56 +02:00
// Skip the end of line indicators.
2019-09-24 16:01:01 +02:00
for (; * ptr == '\r' || * ptr == '\n' ; ++ ptr );
2020-09-01 18:33:56 +02:00
}
2017-11-28 15:19:57 +01:00
return temp_set_by_gcode ;
}
2018-07-17 19:37:24 +02:00
// Print the machine envelope G-code for the Marlin firmware based on the "machine_max_xxx" parameters.
// Do not process this piece of G-code by the time estimator, it already knows the values through another sources.
void GCode :: print_machine_envelope ( FILE * file , Print & print )
{
2021-03-30 13:30:18 +02:00
if (( print . config (). gcode_flavor . value == gcfMarlinLegacy || print . config (). gcode_flavor . value == gcfMarlinFirmware )
2021-03-30 12:46:09 +02:00
&& print . config (). machine_limits_usage . value == MachineLimitsUsage :: EmitToGCode ) {
2018-07-17 19:37:24 +02:00
fprintf ( file , "M201 X%d Y%d Z%d E%d ; sets maximum accelerations, mm/sec^2 \n " ,
2018-09-12 11:59:02 +02:00
int ( print . config (). machine_max_acceleration_x . values . front () + 0.5 ),
int ( print . config (). machine_max_acceleration_y . values . front () + 0.5 ),
int ( print . config (). machine_max_acceleration_z . values . front () + 0.5 ),
int ( print . config (). machine_max_acceleration_e . values . front () + 0.5 ));
2018-07-17 19:37:24 +02:00
fprintf ( file , "M203 X%d Y%d Z%d E%d ; sets maximum feedrates, mm/sec \n " ,
2018-09-12 11:59:02 +02:00
int ( print . config (). machine_max_feedrate_x . values . front () + 0.5 ),
int ( print . config (). machine_max_feedrate_y . values . front () + 0.5 ),
int ( print . config (). machine_max_feedrate_z . values . front () + 0.5 ),
int ( print . config (). machine_max_feedrate_e . values . front () + 0.5 ));
2021-03-30 13:37:49 +02:00
// Now M204 - acceleration. This one is quite hairy thanks to how Marlin guys care about
// backwards compatibility: https://github.com/prusa3d/PrusaSlicer/issues/1089
// Legacy Marlin should export travel acceleration the same as printing acceleration.
// MarlinFirmware has the two separated.
int travel_acc = print . config (). gcode_flavor == gcfMarlinLegacy
? int ( print . config (). machine_max_acceleration_extruding . values . front () + 0.5 )
: int ( print . config (). machine_max_acceleration_travel . values . front () + 0.5 );
2018-08-03 16:26:28 +02:00
fprintf ( file , "M204 P%d R%d T%d ; sets acceleration (P, T) and retract acceleration (R), mm/sec^2 \n " ,
2018-09-12 11:59:02 +02:00
int ( print . config (). machine_max_acceleration_extruding . values . front () + 0.5 ),
int ( print . config (). machine_max_acceleration_retracting . values . front () + 0.5 ),
2021-03-30 13:37:49 +02:00
travel_acc );
2018-07-17 19:37:24 +02:00
fprintf ( file , "M205 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/sec \n " ,
2018-09-12 11:59:02 +02:00
print . config (). machine_max_jerk_x . values . front (),
print . config (). machine_max_jerk_y . values . front (),
print . config (). machine_max_jerk_z . values . front (),
print . config (). machine_max_jerk_e . values . front ());
2018-07-17 19:37:24 +02:00
fprintf ( file , "M205 S%d T%d ; sets the minimum extruding and travel feed rate, mm/sec \n " ,
2018-09-12 11:59:02 +02:00
int ( print . config (). machine_min_extruding_rate . values . front () + 0.5 ),
int ( print . config (). machine_min_travel_rate . values . front () + 0.5 ));
2018-07-17 19:37:24 +02:00
}
}
2017-11-28 15:19:57 +01:00
// Write 1st layer bed temperatures into the G-code.
// Only do that if the start G-code does not already contain any M-code controlling an extruder temperature.
// M140 - Set Extruder Temperature
// M190 - Set Extruder Temperature and Wait
void GCode :: _print_first_layer_bed_temperature ( FILE * file , Print & print , const std :: string & gcode , unsigned int first_printing_extruder_id , bool wait )
{
// Initial bed temperature based on the first extruder.
2018-09-11 14:04:47 +02:00
int temp = print . config (). first_layer_bed_temperature . get_at ( first_printing_extruder_id );
2017-11-28 15:19:57 +01:00
// Is the bed temperature set by the provided custom G-code?
int temp_by_gcode = - 1 ;
2020-07-20 20:57:37 +10:00
bool temp_set_by_gcode = custom_gcode_sets_temperature ( gcode , 140 , 190 , false , temp_by_gcode );
2017-12-09 16:39:49 +01:00
if ( temp_set_by_gcode && temp_by_gcode >= 0 && temp_by_gcode < 1000 )
2017-11-28 15:19:57 +01:00
temp = temp_by_gcode ;
// Always call m_writer.set_bed_temperature() so it will set the internal "current" state of the bed temp as if
// the custom start G-code emited these.
std :: string set_temp_gcode = m_writer . set_bed_temperature ( temp , wait );
2017-12-09 16:39:49 +01:00
if ( ! temp_set_by_gcode )
2018-01-02 10:57:30 +01:00
_write ( file , set_temp_gcode );
2017-11-28 15:19:57 +01:00
}
2017-05-03 18:28:22 +02:00
// Write 1st layer extruder temperatures into the G-code.
// Only do that if the start G-code does not already contain any M-code controlling an extruder temperature.
// M104 - Set Extruder Temperature
// M109 - Set Extruder Temperature and Wait
2020-10-24 17:32:30 +02:00
// RepRapFirmware: G10 Sxx
2017-11-28 15:19:57 +01:00
void GCode :: _print_first_layer_extruder_temperatures ( FILE * file , Print & print , const std :: string & gcode , unsigned int first_printing_extruder_id , bool wait )
2015-07-01 23:00:52 +02:00
{
2017-11-28 15:19:57 +01:00
// Is the bed temperature set by the provided custom G-code?
2020-10-24 17:32:30 +02:00
int temp_by_gcode = - 1 ;
bool include_g10 = print . config (). gcode_flavor == gcfRepRapFirmware ;
2020-08-21 14:07:50 +10:00
if ( custom_gcode_sets_temperature ( gcode , 104 , 109 , include_g10 , temp_by_gcode )) {
2017-11-28 15:19:57 +01:00
// Set the extruder temperature at m_writer, but throw away the generated G-code as it will be written with the custom G-code.
2018-09-11 14:04:47 +02:00
int temp = print . config (). first_layer_temperature . get_at ( first_printing_extruder_id );
2017-11-28 15:19:57 +01:00
if ( temp_by_gcode >= 0 && temp_by_gcode < 1000 )
temp = temp_by_gcode ;
2019-06-25 13:06:04 +02:00
m_writer . set_temperature ( temp , wait , first_printing_extruder_id );
2017-11-28 15:19:57 +01:00
} else {
// Custom G-code does not set the extruder temperature. Do it now.
2018-09-11 14:04:47 +02:00
if ( print . config (). single_extruder_multi_material . value ) {
2017-05-16 16:02:52 +02:00
// Set temperature of the first printing extruder only.
2018-09-11 14:04:47 +02:00
int temp = print . config (). first_layer_temperature . get_at ( first_printing_extruder_id );
2017-05-03 18:28:22 +02:00
if ( temp > 0 )
2018-01-02 10:57:30 +01:00
_write ( file , m_writer . set_temperature ( temp , wait , first_printing_extruder_id ));
2017-05-16 16:02:52 +02:00
} else {
// Set temperatures of all the printing extruders.
for ( unsigned int tool_id : print . extruders ()) {
2018-09-11 14:04:47 +02:00
int temp = print . config (). first_layer_temperature . get_at ( tool_id );
if ( print . config (). ooze_prevention . value )
temp += print . config (). standby_temperature_delta . value ;
2017-05-16 16:02:52 +02:00
if ( temp > 0 )
2018-01-02 10:57:30 +01:00
_write ( file , m_writer . set_temperature ( temp , wait , tool_id ));
2017-05-16 16:02:52 +02:00
}
2017-05-03 18:28:22 +02:00
}
}
2015-07-01 23:00:52 +02:00
}
2017-05-10 11:25:57 +02:00
inline GCode :: ObjectByExtruder & object_by_extruder (
2020-09-01 18:33:56 +02:00
std :: map < unsigned int , std :: vector < GCode :: ObjectByExtruder >> & by_extruder ,
unsigned int extruder_id ,
size_t object_idx ,
2017-05-10 11:25:57 +02:00
size_t num_objects )
2015-07-01 23:00:52 +02:00
{
2017-05-10 11:25:57 +02:00
std :: vector < GCode :: ObjectByExtruder > & objects_by_extruder = by_extruder [ extruder_id ];
if ( objects_by_extruder . empty ())
objects_by_extruder . assign ( num_objects , GCode :: ObjectByExtruder ());
return objects_by_extruder [ object_idx ];
}
inline std :: vector < GCode :: ObjectByExtruder :: Island >& object_islands_by_extruder (
2020-09-01 18:33:56 +02:00
std :: map < unsigned int , std :: vector < GCode :: ObjectByExtruder >> & by_extruder ,
unsigned int extruder_id ,
size_t object_idx ,
2017-05-10 11:25:57 +02:00
size_t num_objects ,
size_t num_islands )
{
std :: vector < GCode :: ObjectByExtruder :: Island > & islands = object_by_extruder ( by_extruder , extruder_id , object_idx , num_objects ). islands ;
if ( islands . empty ())
islands . assign ( num_islands , GCode :: ObjectByExtruder :: Island ());
return islands ;
}
2019-09-26 16:39:50 +02:00
std :: vector < GCode :: InstanceToPrint > GCode :: sort_print_object_instances (
2020-09-01 18:33:56 +02:00
std :: vector < GCode :: ObjectByExtruder > & objects_by_extruder ,
const std :: vector < LayerToPrint > & layers ,
// Ordering must be defined for normal (non-sequential print).
const std :: vector < const PrintInstance *> * ordering ,
// For sequential print, the instance of the object to be printing has to be defined.
const size_t single_object_instance_idx )
2019-09-26 16:39:50 +02:00
{
std :: vector < InstanceToPrint > out ;
if ( ordering == nullptr ) {
2020-09-01 18:33:56 +02:00
// Sequential print, single object is being printed.
for ( ObjectByExtruder & object_by_extruder : objects_by_extruder ) {
const size_t layer_id = & object_by_extruder - objects_by_extruder . data ();
const PrintObject * print_object = layers [ layer_id ]. object ();
if ( print_object )
out . emplace_back ( object_by_extruder , layer_id , * print_object , single_object_instance_idx );
}
2019-09-26 16:39:50 +02:00
} else {
2020-09-01 18:33:56 +02:00
// Create mapping from PrintObject* to ObjectByExtruder*.
std :: vector < std :: pair < const PrintObject * , ObjectByExtruder *>> sorted ;
sorted . reserve ( objects_by_extruder . size ());
for ( ObjectByExtruder & object_by_extruder : objects_by_extruder ) {
const size_t layer_id = & object_by_extruder - objects_by_extruder . data ();
const PrintObject * print_object = layers [ layer_id ]. object ();
if ( print_object )
sorted . emplace_back ( print_object , & object_by_extruder );
}
std :: sort ( sorted . begin (), sorted . end ());
2019-09-26 16:39:50 +02:00
2020-09-01 18:33:56 +02:00
if ( ! sorted . empty ()) {
out . reserve ( sorted . size ());
for ( const PrintInstance * instance : * ordering ) {
const PrintObject & print_object = * instance -> print_object ;
std :: pair < const PrintObject * , ObjectByExtruder *> key ( & print_object , nullptr );
auto it = std :: lower_bound ( sorted . begin (), sorted . end (), key );
if ( it != sorted . end () && it -> first == & print_object )
// ObjectByExtruder for this PrintObject was found.
out . emplace_back ( * it -> second , it -> second - objects_by_extruder . data (), print_object , instance - print_object . instances (). data ());
}
}
}
return out ;
2019-09-26 16:39:50 +02:00
}
2020-01-14 10:31:18 +01:00
namespace ProcessLayer
{
2020-01-14 16:38:34 +01:00
static std :: string emit_custom_gcode_per_print_z (
2021-03-01 12:52:40 +01:00
GCode & gcodegen ,
2020-09-01 18:33:56 +02:00
const CustomGCode :: Item * custom_gcode ,
2021-03-01 12:52:40 +01:00
unsigned int current_extruder_id ,
2020-01-14 10:31:18 +01:00
// ID of the first extruder printing this layer.
unsigned int first_extruder_id ,
2020-06-03 10:42:47 +02:00
const PrintConfig & config )
2020-09-01 18:33:56 +02:00
{
2020-01-14 11:54:09 +01:00
std :: string gcode ;
2020-06-03 10:42:47 +02:00
bool single_extruder_printer = config . nozzle_diameter . size () == 1 ;
2020-09-01 18:33:56 +02:00
2020-01-14 11:54:09 +01:00
if ( custom_gcode != nullptr ) {
2020-09-01 18:33:56 +02:00
// Extruder switches are processed by LayerTools, they should be filtered out.
assert ( custom_gcode -> type != CustomGCode :: ToolChange );
2020-01-14 10:31:18 +01:00
2020-06-03 10:42:47 +02:00
CustomGCode :: Type gcode_type = custom_gcode -> type ;
bool color_change = gcode_type == CustomGCode :: ColorChange ;
bool tool_change = gcode_type == CustomGCode :: ToolChange ;
2020-09-01 18:33:56 +02:00
// Tool Change is applied as Color Change for a single extruder printer only.
assert ( ! tool_change || single_extruder_printer );
2020-02-06 14:03:18 +01:00
2020-09-01 18:33:56 +02:00
std :: string pause_print_msg ;
int m600_extruder_before_layer = - 1 ;
if ( color_change && custom_gcode -> extruder > 0 )
m600_extruder_before_layer = custom_gcode -> extruder - 1 ;
else if ( gcode_type == CustomGCode :: PausePrint )
pause_print_msg = custom_gcode -> extra ;
2020-01-14 10:31:18 +01:00
2020-09-01 18:33:56 +02:00
// we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count
if ( color_change || tool_change )
{
2020-03-03 14:52:16 +01:00
assert ( m600_extruder_before_layer >= 0 );
2020-02-06 14:03:18 +01:00
// Color Change or Tool Change as Color Change.
2020-04-06 11:53:15 +02:00
// add tag for processor
2021-02-18 14:34:40 +01:00
#if ENABLE_VALIDATE_CUSTOM_GCODE
gcode += ";" + GCodeProcessor :: reserved_tag ( GCodeProcessor :: ETags :: Color_Change ) + ",T" + std :: to_string ( m600_extruder_before_layer ) + " \n " ;
#else
2020-11-04 13:33:27 +01:00
gcode += ";" + GCodeProcessor :: Color_Change_Tag + ",T" + std :: to_string ( m600_extruder_before_layer ) + " \n " ;
2021-02-18 14:34:40 +01:00
#endif // ENABLE_VALIDATE_CUSTOM_GCODE
2020-01-14 10:31:18 +01:00
2020-03-03 14:52:16 +01:00
if ( ! single_extruder_printer && m600_extruder_before_layer >= 0 && first_extruder_id != ( unsigned ) m600_extruder_before_layer
2020-09-01 18:33:56 +02:00
// && !MMU1
) {
//! FIXME_in_fw show message during print pause
2021-03-01 12:52:40 +01:00
DynamicConfig cfg ;
cfg . set_key_value ( "color_change_extruder" , new ConfigOptionInt ( m600_extruder_before_layer ));
gcode += gcodegen . placeholder_parser_process ( "pause_print_gcode" , config . pause_print_gcode , current_extruder_id , & cfg );
2020-06-03 10:42:47 +02:00
gcode += " \n " ;
2020-09-01 18:33:56 +02:00
gcode += "M117 Change filament for Extruder " + std :: to_string ( m600_extruder_before_layer ) + " \n " ;
}
2020-02-06 14:03:18 +01:00
else {
2021-03-01 12:52:40 +01:00
gcode += gcodegen . placeholder_parser_process ( "color_change_gcode" , config . color_change_gcode , current_extruder_id );
2020-02-06 14:03:18 +01:00
gcode += " \n " ;
2021-04-15 16:29:30 +02:00
//FIXME Tell G-code writer that M600 filled the extruder, thus the G-code writer shall reset the extruder to unretracted state after
// return from M600. Thus the G-code generated by the following line is ignored.
// see GH issue #6362
gcodegen . writer (). unretract ();
2020-02-06 14:03:18 +01:00
}
2020-01-14 10:31:18 +01:00
}
2021-02-18 14:34:40 +01:00
else {
2020-06-03 10:42:47 +02:00
if ( gcode_type == CustomGCode :: PausePrint ) // Pause print
2020-01-14 10:31:18 +01:00
{
2020-04-06 11:53:15 +02:00
// add tag for processor
2021-02-18 14:34:40 +01:00
#if ENABLE_VALIDATE_CUSTOM_GCODE
gcode += ";" + GCodeProcessor :: reserved_tag ( GCodeProcessor :: ETags :: Pause_Print ) + " \n " ;
#else
2020-11-04 13:33:27 +01:00
gcode += ";" + GCodeProcessor :: Pause_Print_Tag + " \n " ;
2021-02-18 14:34:40 +01:00
#endif // ENABLE_VALIDATE_CUSTOM_GCODE
2020-04-06 11:53:15 +02:00
//! FIXME_in_fw show message during print pause
2020-01-14 10:31:18 +01:00
if ( ! pause_print_msg . empty ())
gcode += "M117 " + pause_print_msg + " \n " ;
2021-03-01 12:52:40 +01:00
gcode += gcodegen . placeholder_parser_process ( "pause_print_gcode" , config . pause_print_gcode , current_extruder_id );
2020-08-05 15:43:46 +02:00
}
2021-02-18 14:34:40 +01:00
else {
2020-04-06 11:53:15 +02:00
// add tag for processor
2021-02-18 14:34:40 +01:00
#if ENABLE_VALIDATE_CUSTOM_GCODE
gcode += ";" + GCodeProcessor :: reserved_tag ( GCodeProcessor :: ETags :: Custom_Code ) + " \n " ;
#else
2020-11-04 13:33:27 +01:00
gcode += ";" + GCodeProcessor :: Custom_Code_Tag + " \n " ;
2021-02-18 14:34:40 +01:00
#endif // ENABLE_VALIDATE_CUSTOM_GCODE
2021-03-01 12:52:40 +01:00
if ( gcode_type == CustomGCode :: Template ) // Template Custom Gcode
gcode += gcodegen . placeholder_parser_process ( "template_custom_gcode" , config . template_custom_gcode , current_extruder_id );
2020-06-03 10:42:47 +02:00
else // custom Gcode
2020-09-01 18:33:56 +02:00
gcode += custom_gcode -> extra ;
2020-06-03 10:42:47 +02:00
2020-09-01 18:33:56 +02:00
}
gcode += " \n " ;
}
}
2020-01-14 10:31:18 +01:00
2020-09-01 18:33:56 +02:00
return gcode ;
}
2020-01-14 10:31:18 +01:00
} // namespace ProcessLayer
namespace Skirt {
2020-09-01 18:33:56 +02:00
static void skirt_loops_per_extruder_all_printing ( const Print & print , const LayerTools & layer_tools , std :: map < unsigned int , std :: pair < size_t , size_t >> & skirt_loops_per_extruder_out )
{
2020-01-14 16:38:34 +01:00
// Prime all extruders printing over the 1st layer over the skirt lines.
size_t n_loops = print . skirt (). entities . size ();
size_t n_tools = layer_tools . extruders . size ();
size_t lines_per_extruder = ( n_loops + n_tools - 1 ) / n_tools ;
for ( size_t i = 0 ; i < n_loops ; i += lines_per_extruder )
skirt_loops_per_extruder_out [ layer_tools . extruders [ i / lines_per_extruder ]] = std :: pair < size_t , size_t > ( i , std :: min ( i + lines_per_extruder , n_loops ));
2020-09-01 18:33:56 +02:00
}
2020-01-14 16:38:34 +01:00
static std :: map < unsigned int , std :: pair < size_t , size_t >> make_skirt_loops_per_extruder_1st_layer (
2020-01-14 10:31:18 +01:00
const Print & print ,
2020-09-01 18:33:56 +02:00
const LayerTools & layer_tools ,
2020-01-14 14:24:38 +01:00
// Heights (print_z) at which the skirt has already been extruded.
std :: vector < coordf_t > & skirt_done )
2020-01-14 10:31:18 +01:00
{
// Extrude skirt at the print_z of the raft layers and normal object layers
// not at the print_z of the interlaced support material layers.
std :: map < unsigned int , std :: pair < size_t , size_t >> skirt_loops_per_extruder_out ;
2021-03-09 13:54:42 +01:00
//For sequential print, the following test may fail when extruding the 2nd and other objects.
// assert(skirt_done.empty());
2021-02-25 12:18:00 +01:00
if ( skirt_done . empty () && print . has_skirt () && ! print . skirt (). entities . empty () && layer_tools . has_skirt ) {
2020-09-01 18:33:56 +02:00
skirt_loops_per_extruder_all_printing ( print , layer_tools , skirt_loops_per_extruder_out );
2020-01-14 14:24:38 +01:00
skirt_done . emplace_back ( layer_tools . print_z );
2020-01-14 10:31:18 +01:00
}
return skirt_loops_per_extruder_out ;
}
2020-01-14 16:38:34 +01:00
static std :: map < unsigned int , std :: pair < size_t , size_t >> make_skirt_loops_per_extruder_other_layers (
2020-01-14 10:31:18 +01:00
const Print & print ,
2020-09-01 18:33:56 +02:00
const LayerTools & layer_tools ,
// Heights (print_z) at which the skirt has already been extruded.
2020-01-14 14:24:38 +01:00
std :: vector < coordf_t > & skirt_done )
2020-01-14 10:31:18 +01:00
{
// Extrude skirt at the print_z of the raft layers and normal object layers
// not at the print_z of the interlaced support material layers.
std :: map < unsigned int , std :: pair < size_t , size_t >> skirt_loops_per_extruder_out ;
2021-02-25 12:18:00 +01:00
if ( print . has_skirt () && ! print . skirt (). entities . empty () && layer_tools . has_skirt &&
2020-01-14 10:31:18 +01:00
// Not enough skirt layers printed yet.
//FIXME infinite or high skirt does not make sense for sequential print!
2021-02-25 12:18:00 +01:00
( skirt_done . size () < ( size_t ) print . config (). skirt_height . value || print . has_infinite_skirt ())) {
bool valid = ! skirt_done . empty () && skirt_done . back () < layer_tools . print_z - EPSILON ;
assert ( valid );
2020-01-14 14:24:38 +01:00
// This print_z has not been extruded yet (sequential print)
2021-01-02 13:13:54 +01:00
// FIXME: The skirt_done should not be empty at this point. The check is a workaround
// of https://github.com/prusa3d/PrusaSlicer/issues/5652, but it deserves a real fix.
2021-02-25 12:18:00 +01:00
if ( valid ) {
2020-01-14 16:38:34 +01:00
#if 0
2021-02-25 12:18:00 +01:00
// Prime just the first printing extruder. This is original Slic3r's implementation.
skirt_loops_per_extruder_out[layer_tools.extruders.front()] = std::pair<size_t, size_t>(0, print.config().skirts.value);
2020-01-14 16:38:34 +01:00
#else
2021-02-25 12:18:00 +01:00
// Prime all extruders planned for this layer, see
// https://github.com/prusa3d/PrusaSlicer/issues/469#issuecomment-322450619
skirt_loops_per_extruder_all_printing ( print , layer_tools , skirt_loops_per_extruder_out );
2020-01-14 16:38:34 +01:00
#endif
2021-02-25 12:18:00 +01:00
assert ( ! skirt_done . empty ());
skirt_done . emplace_back ( layer_tools . print_z );
}
2020-01-14 10:31:18 +01:00
}
return skirt_loops_per_extruder_out ;
}
} // namespace Skirt
2020-09-01 18:33:56 +02:00
// In sequential mode, process_layer is called once per each object and its copy,
2019-09-26 16:39:50 +02:00
// therefore layers will contain a single entry and single_object_instance_idx will point to the copy of the object.
2017-05-10 11:25:57 +02:00
// In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated.
// For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths
// and performing the extruder specific extrusions together.
void GCode :: process_layer (
// Write into the output file.
2020-01-23 09:53:06 +01:00
FILE * file ,
const Print & print ,
2017-05-10 11:25:57 +02:00
// Set of object & print layers of the same PrintObject and with the same print_z.
2020-01-23 09:53:06 +01:00
const std :: vector < LayerToPrint > & layers ,
const LayerTools & layer_tools ,
2021-02-16 11:30:49 +01:00
const bool last_layer ,
2020-09-01 18:33:56 +02:00
// Pairs of PrintObject index and its instance index.
const std :: vector < const PrintInstance *> * ordering ,
2017-05-10 11:25:57 +02:00
// If set to size_t(-1), then print all copies of all objects.
// Otherwise print a single copy of a single object.
2020-01-23 09:53:06 +01:00
const size_t single_object_instance_idx )
2017-05-10 11:25:57 +02:00
{
assert ( ! layers . empty ());
// Either printing all copies of all objects, or just a single copy of a single object.
2019-09-26 16:39:50 +02:00
assert ( single_object_instance_idx == size_t ( - 1 ) || layers . size () == 1 );
2017-05-10 11:25:57 +02:00
2017-05-16 15:30:03 +02:00
if ( layer_tools . extruders . empty ())
// Nothing to extrude.
return ;
2017-05-10 11:25:57 +02:00
// Extract 1st object_layer and support_layer of this set of layers with an equal print_z.
const Layer * object_layer = nullptr ;
const SupportLayer * support_layer = nullptr ;
for ( const LayerToPrint & l : layers ) {
if ( l . object_layer != nullptr && object_layer == nullptr )
object_layer = l . object_layer ;
if ( l . support_layer != nullptr && support_layer == nullptr )
support_layer = l . support_layer ;
}
2020-01-14 10:31:18 +01:00
const Layer & layer = ( object_layer != nullptr ) ? * object_layer : * support_layer ;
2017-05-10 11:25:57 +02:00
coordf_t print_z = layer . print_z ;
2017-05-16 13:45:28 +02:00
bool first_layer = layer . id () == 0 ;
2017-06-21 17:45:55 +02:00
unsigned int first_extruder_id = layer_tools . extruders . front ();
2017-05-10 11:25:57 +02:00
// Initialize config with the 1st object to be printed at this layer.
2018-09-11 14:04:47 +02:00
m_config . apply ( layer . object () -> config (), true );
2017-05-03 18:28:22 +02:00
// Check whether it is possible to apply the spiral vase logic for this layer.
2017-05-05 09:59:56 +02:00
// Just a reminder: A spiral vase mode is allowed for a single object, single material print only.
2020-12-09 14:53:54 +01:00
m_enable_loop_clipping = true ;
2017-05-10 11:25:57 +02:00
if ( m_spiral_vase && layers . size () == 1 && support_layer == nullptr ) {
2021-02-03 15:12:53 +01:00
bool enable = ( layer . id () > 0 || ! print . has_brim ()) && ( layer . id () >= ( size_t ) print . config (). skirt_height . value && ! print . has_infinite_skirt ());
2017-05-03 18:28:22 +02:00
if ( enable ) {
2018-09-11 14:04:47 +02:00
for ( const LayerRegion * layer_region : layer . regions ())
2021-05-05 18:13:58 +02:00
if ( size_t ( layer_region -> region (). config (). bottom_solid_layers . value ) > layer . id () ||
2019-06-25 13:06:04 +02:00
layer_region -> perimeters . items_count () > 1u ||
2017-05-03 18:28:22 +02:00
layer_region -> fills . items_count () > 0 ) {
enable = false ;
break ;
}
}
2020-12-09 14:53:54 +01:00
m_spiral_vase -> enable ( enable );
// If we're going to apply spiralvase to this layer, disable loop clipping.
m_enable_loop_clipping = ! enable ;
2017-05-03 18:28:22 +02:00
}
2020-09-01 18:33:56 +02:00
2017-05-10 11:25:57 +02:00
std :: string gcode ;
2020-08-18 12:37:07 +02:00
// add tag for processor
2021-02-18 14:34:40 +01:00
#if ENABLE_VALIDATE_CUSTOM_GCODE
gcode += ";" + GCodeProcessor :: reserved_tag ( GCodeProcessor :: ETags :: Layer_Change ) + " \n " ;
#else
2020-11-16 22:48:13 +01:00
gcode += ";" + GCodeProcessor :: Layer_Change_Tag + " \n " ;
2021-02-18 14:34:40 +01:00
#endif // ENABLE_VALIDATE_CUSTOM_GCODE
2020-07-30 13:49:57 +02:00
// export layer z
char buf [ 64 ];
2020-08-10 14:22:05 +02:00
sprintf ( buf , ";Z:%g \n " , print_z );
2020-07-30 13:49:57 +02:00
gcode += buf ;
// export layer height
float height = first_layer ? static_cast < float > ( print_z ) : static_cast < float > ( print_z ) - m_last_layer_z ;
2021-02-18 14:34:40 +01:00
#if ENABLE_VALIDATE_CUSTOM_GCODE
sprintf ( buf , ";%s%g \n " , GCodeProcessor :: reserved_tag ( GCodeProcessor :: ETags :: Height ). c_str (), height );
#else
2020-07-30 13:49:57 +02:00
sprintf ( buf , ";%s%g \n " , GCodeProcessor :: Height_Tag . c_str (), height );
2021-02-18 14:34:40 +01:00
#endif // ENABLE_VALIDATE_CUSTOM_GCODE
2020-07-30 13:49:57 +02:00
gcode += buf ;
// update caches
m_last_layer_z = static_cast < float > ( print_z );
2020-12-01 08:23:46 +01:00
m_max_layer_z = std :: max ( m_max_layer_z , m_last_layer_z );
2020-07-30 13:49:57 +02:00
m_last_height = height ;
2017-05-03 18:28:22 +02:00
// Set new layer - this will change Z and force a retraction if retract_layer_change is enabled.
2018-09-11 14:04:47 +02:00
if ( ! print . config (). before_layer_gcode . value . empty ()) {
2017-11-17 11:15:46 +01:00
DynamicConfig config ;
2020-12-01 08:23:46 +01:00
config . set_key_value ( "layer_num" , new ConfigOptionInt ( m_layer_index + 1 ));
config . set_key_value ( "layer_z" , new ConfigOptionFloat ( print_z ));
config . set_key_value ( "max_layer_z" , new ConfigOptionFloat ( m_max_layer_z ));
2017-12-05 15:54:24 +01:00
gcode += this -> placeholder_parser_process ( "before_layer_gcode" ,
2018-09-11 14:04:47 +02:00
print . config (). before_layer_gcode . value , m_writer . extruder () -> id (), & config )
2017-11-17 11:15:46 +01:00
+ " \n " ;
2017-05-03 18:28:22 +02:00
}
2017-05-10 11:25:57 +02:00
gcode += this -> change_layer ( print_z ); // this will increase m_layer_index
2020-09-01 18:33:56 +02:00
m_layer = & layer ;
2018-09-11 14:04:47 +02:00
if ( ! print . config (). layer_gcode . value . empty ()) {
2017-11-17 11:15:46 +01:00
DynamicConfig config ;
config . set_key_value ( "layer_num" , new ConfigOptionInt ( m_layer_index ));
config . set_key_value ( "layer_z" , new ConfigOptionFloat ( print_z ));
2017-12-05 15:54:24 +01:00
gcode += this -> placeholder_parser_process ( "layer_gcode" ,
2018-09-11 14:04:47 +02:00
print . config (). layer_gcode . value , m_writer . extruder () -> id (), & config )
2017-11-17 11:15:46 +01:00
+ " \n " ;
2020-12-01 08:23:46 +01:00
config . set_key_value ( "max_layer_z" , new ConfigOptionFloat ( m_max_layer_z ));
2017-05-03 18:28:22 +02:00
}
2017-05-10 11:25:57 +02:00
2017-05-16 16:02:52 +02:00
if ( ! first_layer && ! m_second_layer_things_done ) {
// Transition from 1st to 2nd layer. Adjust nozzle temperatures as prescribed by the nozzle dependent
// first_layer_temperature vs. temperature settings.
2017-06-22 12:59:23 +02:00
for ( const Extruder & extruder : m_writer . extruders ()) {
2018-09-11 14:04:47 +02:00
if ( print . config (). single_extruder_multi_material . value && extruder . id () != m_writer . extruder () -> id ())
2017-05-18 16:53:19 +02:00
// In single extruder multi material mode, set the temperature for the current extruder only.
continue ;
2018-09-11 14:04:47 +02:00
int temperature = print . config (). temperature . get_at ( extruder . id ());
if ( temperature > 0 && temperature != print . config (). first_layer_temperature . get_at ( extruder . id ()))
2017-06-22 12:59:23 +02:00
gcode += m_writer . set_temperature ( temperature , false , extruder . id ());
2017-05-16 16:02:52 +02:00
}
2018-09-11 14:04:47 +02:00
gcode += m_writer . set_bed_temperature ( print . config (). bed_temperature . get_at ( first_extruder_id ));
2017-05-16 16:02:52 +02:00
// Mark the temperature transition from 1st to 2nd layer to be finished.
m_second_layer_things_done = true ;
}
2020-01-14 10:31:18 +01:00
// Map from extruder ID to <begin, end> index of skirt loops to be extruded with that extruder.
2017-05-10 11:25:57 +02:00
std :: map < unsigned int , std :: pair < size_t , size_t >> skirt_loops_per_extruder ;
2020-01-14 10:31:18 +01:00
2020-01-14 14:24:38 +01:00
if ( single_object_instance_idx == size_t ( - 1 )) {
// Normal (non-sequential) print.
2021-03-01 12:52:40 +01:00
gcode += ProcessLayer :: emit_custom_gcode_per_print_z ( * this , layer_tools . custom_gcode , m_writer . extruder () -> id (), first_extruder_id , print . config ());
2017-05-03 18:28:22 +02:00
}
2020-01-14 14:24:38 +01:00
// Extrude skirt at the print_z of the raft layers and normal object layers
// not at the print_z of the interlaced support material layers.
skirt_loops_per_extruder = first_layer ?
2021-02-25 12:18:00 +01:00
Skirt :: make_skirt_loops_per_extruder_1st_layer ( print , layer_tools , m_skirt_done ) :
Skirt :: make_skirt_loops_per_extruder_other_layers ( print , layer_tools , m_skirt_done );
2017-05-10 11:25:57 +02:00
// Group extrusions by an extruder, then by an object, an island and a region.
std :: map < unsigned int , std :: vector < ObjectByExtruder >> by_extruder ;
2020-01-08 15:16:31 +01:00
bool is_anything_overridden = const_cast < LayerTools &> ( layer_tools ). wiping_extrusions (). is_anything_overridden ();
2017-05-10 11:25:57 +02:00
for ( const LayerToPrint & layer_to_print : layers ) {
if ( layer_to_print . support_layer != nullptr ) {
const SupportLayer & support_layer = * layer_to_print . support_layer ;
const PrintObject & object = * support_layer . object ();
2017-07-07 13:22:00 +02:00
if ( ! support_layer . support_fills . entities . empty ()) {
2017-07-11 11:42:55 +02:00
ExtrusionRole role = support_layer . support_fills . role ();
2017-07-07 13:22:00 +02:00
bool has_support = role == erMixed || role == erSupportMaterial ;
bool has_interface = role == erMixed || role == erSupportMaterialInterface ;
// Extruder ID of the support base. -1 if "don't care".
2018-09-11 14:04:47 +02:00
unsigned int support_extruder = object . config (). support_material_extruder . value - 1 ;
2017-07-07 13:22:00 +02:00
// Shall the support be printed with the active extruder, preferably with non-soluble, to avoid tool changes?
2018-09-11 14:04:47 +02:00
bool support_dontcare = object . config (). support_material_extruder . value == 0 ;
2017-07-07 13:22:00 +02:00
// Extruder ID of the support interface. -1 if "don't care".
2018-09-11 14:04:47 +02:00
unsigned int interface_extruder = object . config (). support_material_interface_extruder . value - 1 ;
2017-07-07 13:22:00 +02:00
// Shall the support interface be printed with the active extruder, preferably with non-soluble, to avoid tool changes?
2018-09-11 14:04:47 +02:00
bool interface_dontcare = object . config (). support_material_interface_extruder . value == 0 ;
2017-07-07 13:22:00 +02:00
if ( support_dontcare || interface_dontcare ) {
// Some support will be printed with "don't care" material, preferably non-soluble.
// Is the current extruder assigned a soluble filament?
unsigned int dontcare_extruder = first_extruder_id ;
2018-09-11 14:04:47 +02:00
if ( print . config (). filament_soluble . get_at ( dontcare_extruder )) {
2017-07-07 13:22:00 +02:00
// The last extruder printed on the previous layer extrudes soluble filament.
// Try to find a non-soluble extruder on the same layer.
for ( unsigned int extruder_id : layer_tools . extruders )
2018-09-11 14:04:47 +02:00
if ( ! print . config (). filament_soluble . get_at ( extruder_id )) {
2017-07-07 13:22:00 +02:00
dontcare_extruder = extruder_id ;
break ;
}
}
if ( support_dontcare )
support_extruder = dontcare_extruder ;
if ( interface_dontcare )
interface_extruder = dontcare_extruder ;
}
2017-05-10 11:25:57 +02:00
// Both the support and the support interface are printed with the same extruder, therefore
// the interface may be interleaved with the support base.
2017-07-07 13:22:00 +02:00
bool single_extruder = ! has_support || support_extruder == interface_extruder ;
2017-05-10 11:25:57 +02:00
// Assign an extruder to the base.
2017-10-03 13:22:37 +02:00
ObjectByExtruder & obj = object_by_extruder ( by_extruder , has_support ? support_extruder : interface_extruder , & layer_to_print - layers . data (), layers . size ());
2017-05-10 11:25:57 +02:00
obj . support = & support_layer . support_fills ;
obj . support_extrusion_role = single_extruder ? erMixed : erSupportMaterial ;
2017-07-07 13:22:00 +02:00
if ( ! single_extruder && has_interface ) {
ObjectByExtruder & obj_interface = object_by_extruder ( by_extruder , interface_extruder , & layer_to_print - layers . data (), layers . size ());
2017-05-10 11:25:57 +02:00
obj_interface . support = & support_layer . support_fills ;
obj_interface . support_extrusion_role = erSupportMaterialInterface ;
2017-05-03 18:28:22 +02:00
}
}
}
2017-05-10 11:25:57 +02:00
if ( layer_to_print . object_layer != nullptr ) {
const Layer & layer = * layer_to_print . object_layer ;
2020-09-01 18:33:56 +02:00
// We now define a strategy for building perimeters and fills. The separation
// between regions doesn't matter in terms of printing order, as we follow
2017-05-10 11:25:57 +02:00
// another logic instead:
// - we group all extrusions by extruder so that we minimize toolchanges
// - we start from the last used extruder
// - for each extruder, we group extrusions by island
// - for each island, we extrude perimeters first, unless user set the infill_first
// option
// (Still, we have to keep track of regions because we need to apply their config)
2020-01-03 14:05:56 +01:00
size_t n_slices = layer . lslices . size ();
const std :: vector < BoundingBox > & layer_surface_bboxes = layer . lslices_bboxes ;
2019-09-27 09:51:07 +02:00
// Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first,
// so we can just test a point inside ExPolygon::contour and we may skip testing the holes.
std :: vector < size_t > slices_test_order ;
slices_test_order . reserve ( n_slices );
for ( size_t i = 0 ; i < n_slices ; ++ i )
2020-09-01 18:33:56 +02:00
slices_test_order . emplace_back ( i );
2020-01-10 11:26:52 +01:00
std :: sort ( slices_test_order . begin (), slices_test_order . end (), [ & layer_surface_bboxes ]( size_t i , size_t j ) {
2020-09-01 18:33:56 +02:00
const Vec2d s1 = layer_surface_bboxes [ i ]. size (). cast < double > ();
const Vec2d s2 = layer_surface_bboxes [ j ]. size (). cast < double > ();
return s1 . x () * s1 . y () < s2 . x () * s2 . y ();
2019-09-27 09:51:07 +02:00
});
2020-09-01 18:33:56 +02:00
auto point_inside_surface = [ & layer , & layer_surface_bboxes ]( const size_t i , const Point & point ) {
2017-05-10 11:25:57 +02:00
const BoundingBox & bbox = layer_surface_bboxes [ i ];
2018-08-17 15:53:43 +02:00
return point ( 0 ) >= bbox . min ( 0 ) && point ( 0 ) < bbox . max ( 0 ) &&
point ( 1 ) >= bbox . min ( 1 ) && point ( 1 ) < bbox . max ( 1 ) &&
2020-01-03 14:05:56 +01:00
layer . lslices [ i ]. contour . contains ( point );
2017-05-10 11:25:57 +02:00
};
2020-01-08 14:58:12 +01:00
for ( size_t region_id = 0 ; region_id < layer . regions (). size (); ++ region_id ) {
const LayerRegion * layerm = layer . regions ()[ region_id ];
2017-05-10 11:25:57 +02:00
if ( layerm == nullptr )
continue ;
2021-05-06 13:00:57 +02:00
// PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not
// identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion.
const PrintRegion & region = print . get_print_region ( layerm -> region (). print_region_id ());
2018-06-05 12:50:34 +02:00
2018-06-20 12:52:00 +02:00
// Now we must process perimeters and infills and create islands of extrusions in by_region std::map.
// It is also necessary to save which extrusions are part of MM wiping and which are not.
// The process is almost the same for perimeters and infills - we will do it in a cycle that repeats twice:
2020-01-08 14:58:12 +01:00
std :: vector < unsigned int > printing_extruders ;
for ( const ObjectByExtruder :: Island :: Region :: Type entity_type : { ObjectByExtruder :: Island :: Region :: INFILL , ObjectByExtruder :: Island :: Region :: PERIMETERS }) {
for ( const ExtrusionEntity * ee : ( entity_type == ObjectByExtruder :: Island :: Region :: INFILL ) ? layerm -> fills . entities : layerm -> perimeters . entities ) {
// extrusions represents infill or perimeter extrusions of a single island.
assert ( dynamic_cast < const ExtrusionEntityCollection *> ( ee ) != nullptr );
const auto * extrusions = static_cast < const ExtrusionEntityCollection *> ( ee );
if ( extrusions -> entities . empty ()) // This shouldn't happen but first_point() would fail.
2018-06-20 12:52:00 +02:00
continue ;
// This extrusion is part of certain Region, which tells us which extruder should be used for it:
2020-01-14 10:31:18 +01:00
int correct_extruder_id = layer_tools . extruder ( * extrusions , region );
2018-06-20 12:52:00 +02:00
// Let's recover vector of extruder overrides:
2020-01-08 15:16:31 +01:00
const WipingExtrusions :: ExtruderPerCopy * entity_overrides = nullptr ;
2020-02-10 14:24:21 +01:00
if ( ! layer_tools . has_extruder ( correct_extruder_id )) {
2020-09-01 18:33:56 +02:00
// this entity is not overridden, but its extruder is not in layer_tools - we'll print it
2020-02-10 14:24:21 +01:00
// by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools)
correct_extruder_id = layer_tools . extruders . back ();
}
printing_extruders . clear ();
2020-01-08 15:16:31 +01:00
if ( is_anything_overridden ) {
2020-09-01 18:33:56 +02:00
entity_overrides = const_cast < LayerTools &> ( layer_tools ). wiping_extrusions (). get_extruder_overrides ( extrusions , correct_extruder_id , layer_to_print . object () -> instances (). size ());
if ( entity_overrides == nullptr ) {
printing_extruders . emplace_back ( correct_extruder_id );
} else {
printing_extruders . reserve ( entity_overrides -> size ());
for ( int extruder : * entity_overrides )
printing_extruders . emplace_back ( extruder >= 0 ?
// at least one copy is overridden to use this extruder
extruder :
// at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation)
static_cast < unsigned int > ( - extruder - 1 ));
Slic3r :: sort_remove_duplicates ( printing_extruders );
}
} else
printing_extruders . emplace_back ( correct_extruder_id );
2018-06-20 12:52:00 +02:00
// Now we must add this extrusion into the by_extruder map, once for each extruder that will print it:
2020-01-08 14:58:12 +01:00
for ( unsigned int extruder : printing_extruders )
2018-06-20 12:52:00 +02:00
{
2020-01-08 14:58:12 +01:00
std :: vector < ObjectByExtruder :: Island > & islands = object_islands_by_extruder (
by_extruder ,
extruder ,
& layer_to_print - layers . data (),
layers . size (), n_slices + 1 );
for ( size_t i = 0 ; i <= n_slices ; ++ i ) {
2020-09-01 18:33:56 +02:00
bool last = i == n_slices ;
size_t island_idx = last ? n_slices : slices_test_order [ i ];
2020-01-08 14:58:12 +01:00
if ( // extrusions->first_point does not fit inside any slice
2020-09-01 18:33:56 +02:00
last ||
2020-01-08 14:58:12 +01:00
// extrusions->first_point fits inside ith slice
point_inside_surface ( island_idx , extrusions -> first_point ())) {
if ( islands [ island_idx ]. by_region . empty ())
2021-05-05 16:21:55 +02:00
islands [ island_idx ]. by_region . assign ( print . num_print_regions (), ObjectByExtruder :: Island :: Region ());
2021-05-06 13:00:57 +02:00
islands [ island_idx ]. by_region [ region . print_region_id ()]. append ( entity_type , extrusions , entity_overrides );
2020-01-08 14:58:12 +01:00
break ;
2019-09-27 09:51:07 +02:00
}
2018-06-05 12:50:34 +02:00
}
2017-05-10 11:25:57 +02:00
}
2018-06-20 12:52:00 +02:00
}
2017-05-10 11:25:57 +02:00
}
} // for regions
}
} // for objects
// Extrude the skirt, brim, support, perimeters, infill ordered by the extruders.
std :: vector < std :: unique_ptr < EdgeGrid :: Grid >> lower_layer_edge_grids ( layers . size ());
2017-05-16 13:45:28 +02:00
for ( unsigned int extruder_id : layer_tools . extruders )
2018-06-20 12:52:00 +02:00
{
2017-05-25 22:27:53 +02:00
gcode += ( layer_tools . has_wipe_tower && m_wipe_tower ) ?
2017-05-18 16:53:19 +02:00
m_wipe_tower -> tool_change ( * this , extruder_id , extruder_id == layer_tools . extruders . back ()) :
2019-01-29 12:02:48 +01:00
this -> set_extruder ( extruder_id , print_z );
2017-05-10 11:25:57 +02:00
2020-05-07 10:49:12 +02:00
// let analyzer tag generator aware of a role type change
if ( layer_tools . has_wipe_tower && m_wipe_tower )
m_last_processor_extrusion_role = erWipeTower ;
2018-05-07 14:23:07 +02:00
2020-01-14 10:31:18 +01:00
if ( auto loops_it = skirt_loops_per_extruder . find ( extruder_id ); loops_it != skirt_loops_per_extruder . end ()) {
const std :: pair < size_t , size_t > loops = loops_it -> second ;
this -> set_origin ( 0. , 0. );
2020-11-17 10:42:27 +01:00
m_avoid_crossing_perimeters . use_external_mp ();
2021-03-08 13:44:00 +01:00
Flow layer_skirt_flow = print . skirt_flow (). with_height ( float ( m_skirt_done . back () - ( m_skirt_done . size () == 1 ? 0. : m_skirt_done [ m_skirt_done . size () - 2 ])));
2020-01-14 10:31:18 +01:00
double mm3_per_mm = layer_skirt_flow . mm3_per_mm ();
for ( size_t i = loops . first ; i < loops . second ; ++ i ) {
// Adjust flow according to this layer's layer height.
ExtrusionLoop loop = * dynamic_cast < const ExtrusionLoop *> ( print . skirt (). entities [ i ]);
for ( ExtrusionPath & path : loop . paths ) {
2021-03-08 13:44:00 +01:00
path . height = layer_skirt_flow . height ();
2020-01-14 10:31:18 +01:00
path . mm3_per_mm = mm3_per_mm ;
2017-05-10 11:25:57 +02:00
}
2020-01-14 10:31:18 +01:00
//FIXME using the support_material_speed of the 1st object printed.
gcode += this -> extrude_loop ( loop , "skirt" , m_config . support_material_speed . value );
2017-05-10 11:25:57 +02:00
}
2020-11-17 10:42:27 +01:00
m_avoid_crossing_perimeters . use_external_mp ( false );
2020-01-14 10:31:18 +01:00
// Allow a straight travel move to the first object point if this is the first layer (but don't in next layers).
if ( first_layer && loops . first == 0 )
2020-11-17 10:42:27 +01:00
m_avoid_crossing_perimeters . disable_once ();
2017-05-10 11:25:57 +02:00
}
2018-06-20 12:52:00 +02:00
2017-05-10 11:25:57 +02:00
// Extrude brim with the extruder of the 1st region.
if ( ! m_brim_done ) {
2017-06-02 13:33:19 +02:00
this -> set_origin ( 0. , 0. );
2020-11-17 10:42:27 +01:00
m_avoid_crossing_perimeters . use_external_mp ();
2019-08-14 15:44:32 +02:00
for ( const ExtrusionEntity * ee : print . brim (). entities ) {
gcode += this -> extrude_entity ( * ee , "brim" , m_config . support_material_speed . value );
}
2017-05-10 11:25:57 +02:00
m_brim_done = true ;
2020-11-17 10:42:27 +01:00
m_avoid_crossing_perimeters . use_external_mp ( false );
2017-05-10 11:25:57 +02:00
// Allow a straight travel move to the first object point.
2020-11-17 10:42:27 +01:00
m_avoid_crossing_perimeters . disable_once ();
2017-05-10 11:25:57 +02:00
}
2018-05-24 14:05:51 +02:00
2017-05-10 11:25:57 +02:00
auto objects_by_extruder_it = by_extruder . find ( extruder_id );
if ( objects_by_extruder_it == by_extruder . end ())
continue ;
2019-01-21 14:05:04 +01:00
2020-09-01 18:33:56 +02:00
std :: vector < InstanceToPrint > instances_to_print = sort_print_object_instances ( objects_by_extruder_it -> second , layers , ordering , single_object_instance_idx );
2019-09-26 16:39:50 +02:00
2018-07-09 13:44:41 +02:00
// We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature):
2020-09-01 18:33:56 +02:00
std :: vector < ObjectByExtruder :: Island :: Region > by_region_per_copy_cache ;
2019-01-21 14:05:04 +01:00
for ( int print_wipe_extrusions = is_anything_overridden ; print_wipe_extrusions >= 0 ; -- print_wipe_extrusions ) {
if ( is_anything_overridden && print_wipe_extrusions == 0 )
2018-07-09 13:44:41 +02:00
gcode += "; PURGING FINISHED \n " ;
2018-01-17 10:39:05 +01:00
2019-09-26 16:39:50 +02:00
for ( InstanceToPrint & instance_to_print : instances_to_print ) {
m_config . apply ( instance_to_print . print_object . config (), true );
m_layer = layers [ instance_to_print . layer_id ]. layer ();
2018-06-20 12:52:00 +02:00
if ( m_config . avoid_crossing_perimeters )
2020-10-07 14:34:59 +02:00
m_avoid_crossing_perimeters . init_layer ( * m_layer );
2019-09-26 16:39:50 +02:00
if ( this -> config (). gcode_label_objects )
gcode += std :: string ( "; printing object " ) + instance_to_print . print_object . model_object () -> name + " id:" + std :: to_string ( instance_to_print . layer_id ) + " copy " + std :: to_string ( instance_to_print . instance_id ) + " \n " ;
// When starting a new object, use the external motion planner for the first travel move.
2020-01-23 09:53:06 +01:00
const Point & offset = instance_to_print . print_object . instances ()[ instance_to_print . instance_id ]. shift ;
2019-09-26 16:39:50 +02:00
std :: pair < const PrintObject * , Point > this_object_copy ( & instance_to_print . print_object , offset );
if ( m_last_obj_copy != this_object_copy )
2020-11-17 10:42:27 +01:00
m_avoid_crossing_perimeters . use_external_mp_once ();
2019-09-26 16:39:50 +02:00
m_last_obj_copy = this_object_copy ;
this -> set_origin ( unscale ( offset ));
if ( instance_to_print . object_by_extruder . support != nullptr && ! print_wipe_extrusions ) {
m_layer = layers [ instance_to_print . layer_id ]. support_layer ;
gcode += this -> extrude_support (
// support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths.
2019-09-27 18:17:21 +02:00
instance_to_print . object_by_extruder . support -> chained_path_from ( m_last_pos , instance_to_print . object_by_extruder . support_extrusion_role ));
2019-09-26 16:39:50 +02:00
m_layer = layers [ instance_to_print . layer_id ]. layer ();
2017-05-10 11:25:57 +02:00
}
2021-01-06 12:19:08 +01:00
//FIXME order islands?
// Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511)
2019-09-26 16:39:50 +02:00
for ( ObjectByExtruder :: Island & island : instance_to_print . object_by_extruder . islands ) {
2020-01-10 11:26:52 +01:00
const auto & by_region_specific = is_anything_overridden ? island . by_region_per_copy ( by_region_per_copy_cache , static_cast < unsigned int > ( instance_to_print . instance_id ), extruder_id , print_wipe_extrusions != 0 ) : island . by_region ;
2020-09-01 18:33:56 +02:00
//FIXME the following code prints regions in the order they are defined, the path is not optimized in any way.
2019-09-26 16:39:50 +02:00
if ( print . config (). infill_first ) {
2020-04-14 11:53:28 +02:00
gcode += this -> extrude_infill ( print , by_region_specific , false );
2019-09-26 16:39:50 +02:00
gcode += this -> extrude_perimeters ( print , by_region_specific , lower_layer_edge_grids [ instance_to_print . layer_id ]);
} else {
gcode += this -> extrude_perimeters ( print , by_region_specific , lower_layer_edge_grids [ instance_to_print . layer_id ]);
2020-04-14 11:53:28 +02:00
gcode += this -> extrude_infill ( print , by_region_specific , false );
2019-09-26 16:39:50 +02:00
}
2020-04-14 11:53:28 +02:00
// ironing
gcode += this -> extrude_infill ( print , by_region_specific , true );
2019-09-26 16:39:50 +02:00
}
if ( this -> config (). gcode_label_objects )
2020-09-01 18:33:56 +02:00
gcode += std :: string ( "; stop printing object " ) + instance_to_print . print_object . model_object () -> name + " id:" + std :: to_string ( instance_to_print . layer_id ) + " copy " + std :: to_string ( instance_to_print . instance_id ) + " \n " ;
2017-05-10 11:25:57 +02:00
}
}
}
2017-05-03 18:28:22 +02:00
// Apply spiral vase post-processing if this layer contains suitable geometry
2020-09-01 18:33:56 +02:00
// (we must feed all the G-code into the post-processor, including the first
2017-05-03 18:28:22 +02:00
// bottom non-spiral layers otherwise it will mess with positions)
// we apply spiral vase at this stage because it requires a full layer.
2017-06-30 17:05:58 +02:00
// Just a reminder: A spiral vase mode is allowed for a single object per layer, single material print only.
2017-05-03 18:28:22 +02:00
if ( m_spiral_vase )
2021-02-16 11:30:49 +01:00
gcode = m_spiral_vase -> process_layer ( std :: move ( gcode ));
2017-05-10 11:25:57 +02:00
2017-05-03 18:28:22 +02:00
// Apply cooling logic; this may alter speeds.
if ( m_cooling_buffer )
2021-02-16 11:30:49 +01:00
gcode = m_cooling_buffer -> process_layer ( std :: move ( gcode ), layer . id (),
// Flush the cooling buffer at each object layer or possibly at the last layer, even if it contains just supports (This should not happen).
object_layer || last_layer );
2015-07-01 23:00:52 +02:00
2019-01-29 18:07:45 +01:00
#ifdef HAS_PRESSURE_EQUALIZER
2017-06-30 17:05:58 +02:00
// Apply pressure equalization if enabled;
2017-05-03 18:28:22 +02:00
// printf("G-code before filter:\n%s\n", gcode.c_str());
2017-06-30 17:05:58 +02:00
if ( m_pressure_equalizer )
gcode = m_pressure_equalizer -> process ( gcode . c_str (), false );
2017-05-03 18:28:22 +02:00
// printf("G-code after filter:\n%s\n", out.c_str());
2019-01-29 18:07:45 +01:00
#endif /* HAS_PRESSURE_EQUALIZER */
2020-09-01 18:33:56 +02:00
2017-12-14 09:18:28 +01:00
_write ( file , gcode );
2020-09-11 15:19:23 +02:00
BOOST_LOG_TRIVIAL ( trace ) << "Exported layer " << layer . id () << " print_z " << print_z <<
2021-02-16 11:30:49 +01:00
log_memory_info ();
2015-07-01 23:00:52 +02:00
}
2017-05-03 18:28:22 +02:00
void GCode :: apply_print_config ( const PrintConfig & print_config )
2015-07-02 19:33:08 +02:00
{
2017-05-03 18:28:22 +02:00
m_writer . apply_print_config ( print_config );
m_config . apply ( print_config );
}
2019-07-25 14:39:19 +02:00
void GCode :: append_full_config ( const Print & print , std :: string & str )
2018-02-13 15:19:55 +01:00
{
2020-09-01 18:33:56 +02:00
const DynamicPrintConfig & cfg = print . full_print_config ();
2020-01-21 09:55:36 +01:00
// Sorted list of config keys, which shall not be stored into the G-code. Initializer list.
2020-09-01 18:33:56 +02:00
static constexpr auto banned_keys = {
"compatible_printers" sv ,
"compatible_prints" sv ,
2020-10-28 09:51:05 +01:00
//FIXME The print host keys should not be exported to full_print_config anymore. The following keys may likely be removed.
2020-09-01 18:33:56 +02:00
"print_host" sv ,
"printhost_apikey" sv ,
"printhost_cafile" sv
};
2020-01-21 09:55:36 +01:00
assert ( std :: is_sorted ( banned_keys . begin (), banned_keys . end ()));
2020-09-01 18:33:56 +02:00
auto is_banned = []( const std :: string & key ) {
return std :: binary_search ( banned_keys . begin (), banned_keys . end (), key );
};
2019-07-25 14:39:19 +02:00
for ( const std :: string & key : cfg . keys ())
2020-01-21 09:55:36 +01:00
if ( ! is_banned ( key ) && ! cfg . option ( key ) -> is_nil ())
2019-07-25 14:39:19 +02:00
str += "; " + key + " = " + cfg . opt_serialize ( key ) + " \n " ;
2018-02-13 15:19:55 +01:00
}
2017-05-03 18:28:22 +02:00
void GCode :: set_extruders ( const std :: vector < unsigned int > & extruder_ids )
{
m_writer . set_extruders ( extruder_ids );
2020-09-01 18:33:56 +02:00
2015-07-02 19:33:08 +02:00
// enable wipe path generation if any extruder has wipe enabled
2017-05-03 18:28:22 +02:00
m_wipe . enable = false ;
2017-03-29 17:45:38 +02:00
for ( auto id : extruder_ids )
2017-05-03 18:28:22 +02:00
if ( m_config . wipe . get_at ( id )) {
m_wipe . enable = true ;
2015-07-02 19:33:08 +02:00
break ;
}
}
2018-08-21 21:05:24 +02:00
void GCode :: set_origin ( const Vec2d & pointf )
2020-09-01 18:33:56 +02:00
{
2015-07-01 23:00:52 +02:00
// if origin increases (goes towards right), last_pos decreases because it goes towards left
2015-12-19 14:49:29 +01:00
const Point translate (
2018-08-17 15:53:43 +02:00
scale_ ( m_origin ( 0 ) - pointf ( 0 )),
scale_ ( m_origin ( 1 ) - pointf ( 1 ))
2015-07-01 23:00:52 +02:00
);
2018-08-17 14:14:24 +02:00
m_last_pos += translate ;
2017-05-03 18:28:22 +02:00
m_wipe . path . translate ( translate );
m_origin = pointf ;
2015-07-01 23:00:52 +02:00
}
2017-05-03 18:28:22 +02:00
std :: string GCode :: preamble ()
2015-07-01 23:00:52 +02:00
{
2017-05-03 18:28:22 +02:00
std :: string gcode = m_writer . preamble ();
2020-09-01 18:33:56 +02:00
2015-07-01 23:00:52 +02:00
/* Perform a *silent* move to z_offset: we need this to initialize the Z
position of our writer object so that any initial lift taking place
before the first layer change will raise the extruder from the correct
initial Z instead of 0. */
2017-05-03 18:28:22 +02:00
m_writer . travel_to_z ( m_config . z_offset . value );
2020-09-01 18:33:56 +02:00
2015-07-01 23:00:52 +02:00
return gcode ;
}
2017-05-10 11:25:57 +02:00
// called by GCode::process_layer()
std :: string GCode :: change_layer ( coordf_t print_z )
2015-07-02 19:33:08 +02:00
{
2016-09-26 12:52:40 +02:00
std :: string gcode ;
2017-05-03 18:28:22 +02:00
if ( m_layer_count > 0 )
2017-05-10 11:25:57 +02:00
// Increment a progress bar indicator.
gcode += m_writer . update_progress ( ++ m_layer_index , m_layer_count );
coordf_t z = print_z + m_config . z_offset . value ; // in unscaled coordinates
if ( EXTRUDER_CONFIG ( retract_layer_change ) && m_writer . will_move_z ( z ))
2015-07-02 19:33:08 +02:00
gcode += this -> retract ();
2017-05-10 11:25:57 +02:00
2015-07-02 19:33:08 +02:00
{
std :: ostringstream comment ;
2017-05-03 18:28:22 +02:00
comment << "move to next layer (" << m_layer_index << ")" ;
gcode += m_writer . travel_to_z ( z , comment . str ());
2015-07-02 19:33:08 +02:00
}
2020-09-01 18:33:56 +02:00
2015-07-02 19:33:08 +02:00
// forget last wiping path as wiping after raising Z is pointless
2017-05-03 18:28:22 +02:00
m_wipe . reset_path ();
2020-09-01 18:33:56 +02:00
2015-07-02 19:33:08 +02:00
return gcode ;
}
2016-09-12 16:25:15 +02:00
2017-05-10 11:25:57 +02:00
std :: string GCode :: extrude_loop ( ExtrusionLoop loop , std :: string description , double speed , std :: unique_ptr < EdgeGrid :: Grid > * lower_layer_edge_grid )
2015-07-02 18:57:40 +02:00
{
2015-07-02 20:24:16 +02:00
// get a copy; don't modify the orientation of the original loop object otherwise
// next copies (if any) would not detect the correct orientation
2016-09-12 16:25:15 +02:00
2017-05-10 11:25:57 +02:00
if ( m_layer -> lower_layer != nullptr && lower_layer_edge_grid != nullptr ) {
if ( ! * lower_layer_edge_grid ) {
2016-09-12 16:25:15 +02:00
// Create the distance field for a layer below.
2017-05-18 16:53:19 +02:00
const coord_t distance_field_resolution = coord_t ( scale_ ( 1. ) + 0.5 );
2017-05-10 11:25:57 +02:00
* lower_layer_edge_grid = make_unique < EdgeGrid :: Grid > ();
2020-01-03 14:05:56 +01:00
( * lower_layer_edge_grid ) -> create ( m_layer -> lower_layer -> lslices , distance_field_resolution );
2017-05-10 11:25:57 +02:00
( * lower_layer_edge_grid ) -> calculate_sdf ();
2016-09-12 16:25:15 +02:00
#if 0
{
static int iRun = 0;
2017-05-10 11:25:57 +02:00
BoundingBox bbox = (*lower_layer_edge_grid)->bbox();
2018-08-17 15:53:43 +02:00
bbox.min(0) -= scale_(5.f);
bbox.min(1) -= scale_(5.f);
bbox.max(0) += scale_(5.f);
bbox.max(1) += scale_(5.f);
2017-05-10 11:25:57 +02:00
EdgeGrid::save_png(*(*lower_layer_edge_grid), bbox, scale_(0.1f), debug_out_path("GCode_extrude_loop_edge_grid-%d.png", iRun++));
2016-09-12 16:25:15 +02:00
}
#endif
}
}
2020-09-01 18:33:56 +02:00
2015-07-02 20:24:16 +02:00
// extrude all loops ccw
bool was_clockwise = loop . make_counter_clockwise ();
2020-09-01 18:33:56 +02:00
2017-05-03 18:28:22 +02:00
SeamPosition seam_position = m_config . seam_position ;
2020-09-01 18:33:56 +02:00
if ( loop . loop_role () == elrSkirt )
2017-02-07 18:46:02 +01:00
seam_position = spNearest ;
2020-09-01 18:33:56 +02:00
2015-07-02 20:24:16 +02:00
// find the point of the loop that is closest to the current extruder position
// or randomize if requested
Point last_pos = this -> last_pos ();
2017-05-03 18:28:22 +02:00
if ( m_config . spiral_vase ) {
2017-02-02 18:49:33 +01:00
loop . split_at ( last_pos , false );
2020-09-09 13:21:39 +02:00
} else {
const EdgeGrid :: Grid * edge_grid_ptr = ( lower_layer_edge_grid && * lower_layer_edge_grid )
? lower_layer_edge_grid -> get ()
: nullptr ;
2020-11-25 13:13:37 +01:00
Point seam = m_seam_placer . get_seam ( * m_layer , seam_position , loop ,
2020-09-09 13:21:39 +02:00
last_pos , EXTRUDER_CONFIG ( nozzle_diameter ),
( m_layer == NULL ? nullptr : m_layer -> object ()),
was_clockwise , edge_grid_ptr );
2016-09-12 16:25:15 +02:00
// Split the loop at the point with a minium penalty.
2020-09-09 13:21:39 +02:00
if ( ! loop . split_at_vertex ( seam ))
2016-09-12 16:25:15 +02:00
// The point is not in the original loop. Insert it.
2020-09-09 13:21:39 +02:00
loop . split_at ( seam , true );
2015-07-02 20:24:16 +02:00
}
2020-09-01 18:33:56 +02:00
2015-07-02 20:24:16 +02:00
// clip the path to avoid the extruder to get exactly on the first point of the loop;
// if polyline was shorter than the clipping distance we'd get a null polyline, so
// we discard it in that case
2020-09-01 18:33:56 +02:00
double clip_length = m_enable_loop_clipping ?
scale_ ( EXTRUDER_CONFIG ( nozzle_diameter )) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER :
2017-05-03 18:28:22 +02:00
0 ;
2015-07-02 20:24:16 +02:00
// get paths
ExtrusionPaths paths ;
loop . clip_end ( clip_length , & paths );
if ( paths . empty ()) return "" ;
2020-09-01 18:33:56 +02:00
2015-07-02 20:24:16 +02:00
// apply the small perimeter speed
2017-05-03 18:28:22 +02:00
if ( is_perimeter ( paths . front (). role ()) && loop . length () <= SMALL_PERIMETER_LENGTH && speed == - 1 )
speed = m_config . small_perimeter_speed . get_abs_value ( m_config . perimeter_speed );
2020-09-01 18:33:56 +02:00
2015-07-02 20:24:16 +02:00
// extrude along the path
std :: string gcode ;
2017-02-15 17:51:46 +01:00
for ( ExtrusionPaths :: iterator path = paths . begin (); path != paths . end (); ++ path ) {
2019-09-06 14:58:04 +02:00
// description += ExtrusionLoop::role_to_string(loop.loop_role());
// description += ExtrusionEntity::role_to_string(path->role);
2017-02-15 17:51:46 +01:00
path -> simplify ( SCALED_RESOLUTION );
2015-07-02 20:24:16 +02:00
gcode += this -> _extrude ( * path , description , speed );
2017-02-15 17:51:46 +01:00
}
2020-09-01 18:33:56 +02:00
2015-07-02 20:24:16 +02:00
// reset acceleration
2017-09-01 17:30:18 +02:00
gcode += m_writer . set_acceleration (( unsigned int )( m_config . default_acceleration . value + 0.5 ));
2020-09-01 18:33:56 +02:00
2017-05-03 18:28:22 +02:00
if ( m_wipe . enable )
m_wipe . path = paths . front (). polyline ; // TODO: don't limit wipe to last path
2020-09-01 18:33:56 +02:00
2015-07-02 20:24:16 +02:00
// make a little move inwards before leaving loop
2020-09-01 18:33:56 +02:00
if ( paths . back (). role () == erExternalPerimeter && m_layer != NULL && m_config . perimeters . value > 1 && paths . front (). size () >= 2 && paths . back (). polyline . points . size () >= 3 ) {
2015-07-02 20:24:16 +02:00
// detect angle between last and first segment
// the side depends on the original winding order of the polygon (left for contours, right for holes)
2020-09-01 18:33:56 +02:00
//FIXME improve the algorithm in case the loop is tiny.
//FIXME improve the algorithm in case the loop is split into segments with a low number of points (see the Point b query).
2015-07-02 20:24:16 +02:00
Point a = paths . front (). polyline . points [ 1 ]; // second point
Point b = * ( paths . back (). polyline . points . end () - 3 ); // second to last point
if ( was_clockwise ) {
// swap points
Point c = a ; a = b ; b = c ;
}
2020-09-01 18:33:56 +02:00
2015-07-02 20:24:16 +02:00
double angle = paths . front (). first_point (). ccw_angle ( a , b ) / 3 ;
2020-09-01 18:33:56 +02:00
2015-07-02 20:24:16 +02:00
// turn left if contour, turn right if hole
if ( was_clockwise ) angle *= - 1 ;
2020-09-01 18:33:56 +02:00
2015-07-02 20:24:16 +02:00
// create the destination point along the first segment and rotate it
// we make sure we don't exceed the segment length because we don't know
// the rotation of the second segment so we might cross the object boundary
2018-08-17 14:14:24 +02:00
Vec2d p1 = paths . front (). polyline . points . front (). cast < double > ();
Vec2d p2 = paths . front (). polyline . points [ 1 ]. cast < double > ();
Vec2d v = p2 - p1 ;
double nd = scale_ ( EXTRUDER_CONFIG ( nozzle_diameter ));
double l2 = v . squaredNorm ();
// Shift by no more than a nozzle diameter.
//FIXME Hiding the seams will not work nicely for very densely discretized contours!
Point pt = (( nd * nd >= l2 ) ? p2 : ( p1 + v * ( nd / sqrt ( l2 )))). cast < coord_t > ();
pt . rotate ( angle , paths . front (). polyline . points . front ());
2015-07-02 20:24:16 +02:00
// generate the travel move
2018-08-17 14:14:24 +02:00
gcode += m_writer . travel_to_xy ( this -> point_to_gcode ( pt ), "move inwards before travel" );
2015-07-02 20:24:16 +02:00
}
2020-09-01 18:33:56 +02:00
2015-07-02 20:24:16 +02:00
return gcode ;
}
2017-05-10 11:25:57 +02:00
std :: string GCode :: extrude_multi_path ( ExtrusionMultiPath multipath , std :: string description , double speed )
2017-01-19 13:35:55 +01:00
{
// extrude along the path
std :: string gcode ;
2017-05-10 11:25:57 +02:00
for ( ExtrusionPath path : multipath . paths ) {
2019-09-06 14:58:04 +02:00
// description += ExtrusionLoop::role_to_string(loop.loop_role());
// description += ExtrusionEntity::role_to_string(path->role);
2017-05-10 11:25:57 +02:00
path . simplify ( SCALED_RESOLUTION );
gcode += this -> _extrude ( path , description , speed );
2017-02-15 17:51:46 +01:00
}
2017-05-03 18:28:22 +02:00
if ( m_wipe . enable ) {
m_wipe . path = std :: move ( multipath . paths . back (). polyline ); // TODO: don't limit wipe to last path
m_wipe . path . reverse ();
2017-02-15 17:51:46 +01:00
}
2017-01-19 13:35:55 +01:00
// reset acceleration
2017-09-01 17:30:18 +02:00
gcode += m_writer . set_acceleration (( unsigned int ) floor ( m_config . default_acceleration . value + 0.5 ));
2017-01-19 13:35:55 +01:00
return gcode ;
}
2017-05-15 11:32:59 +02:00
std :: string GCode :: extrude_entity ( const ExtrusionEntity & entity , std :: string description , double speed , std :: unique_ptr < EdgeGrid :: Grid > * lower_layer_edge_grid )
2015-07-02 20:24:16 +02:00
{
2017-05-10 11:25:57 +02:00
if ( const ExtrusionPath * path = dynamic_cast < const ExtrusionPath *> ( & entity ))
return this -> extrude_path ( * path , description , speed );
else if ( const ExtrusionMultiPath * multipath = dynamic_cast < const ExtrusionMultiPath *> ( & entity ))
return this -> extrude_multi_path ( * multipath , description , speed );
else if ( const ExtrusionLoop * loop = dynamic_cast < const ExtrusionLoop *> ( & entity ))
2017-05-15 11:32:59 +02:00
return this -> extrude_loop ( * loop , description , speed , lower_layer_edge_grid );
2019-08-14 15:44:32 +02:00
else
2020-09-14 16:27:55 +02:00
throw Slic3r :: InvalidArgument ( "Invalid argument supplied to extrude()" );
2019-08-14 15:44:32 +02:00
return "" ;
2015-07-02 20:24:16 +02:00
}
2017-05-10 11:25:57 +02:00
std :: string GCode :: extrude_path ( ExtrusionPath path , std :: string description , double speed )
2015-07-02 20:24:16 +02:00
{
2019-09-06 14:58:04 +02:00
// description += ExtrusionEntity::role_to_string(path.role());
2017-02-15 17:51:46 +01:00
path . simplify ( SCALED_RESOLUTION );
2015-07-02 20:24:16 +02:00
std :: string gcode = this -> _extrude ( path , description , speed );
2017-05-03 18:28:22 +02:00
if ( m_wipe . enable ) {
m_wipe . path = std :: move ( path . polyline );
m_wipe . path . reverse ();
2017-05-10 11:25:57 +02:00
}
2015-07-02 18:57:40 +02:00
// reset acceleration
2017-05-10 11:25:57 +02:00
gcode += m_writer . set_acceleration (( unsigned int ) floor ( m_config . default_acceleration . value + 0.5 ));
2015-07-02 18:57:40 +02:00
return gcode ;
}
2017-05-03 18:28:22 +02:00
// Extrude perimeters: Decide where to put seams (hide or align seams).
2017-05-10 11:25:57 +02:00
std :: string GCode :: extrude_perimeters ( const Print & print , const std :: vector < ObjectByExtruder :: Island :: Region > & by_region , std :: unique_ptr < EdgeGrid :: Grid > & lower_layer_edge_grid )
2017-04-07 17:37:30 +02:00
{
std :: string gcode ;
2020-03-17 09:41:37 +01:00
for ( const ObjectByExtruder :: Island :: Region & region : by_region )
if ( ! region . perimeters . empty ()) {
2021-05-05 18:13:58 +02:00
m_config . apply ( print . get_print_region ( & region - & by_region . front ()). config ());
2020-03-17 09:41:37 +01:00
for ( const ExtrusionEntity * ee : region . perimeters )
gcode += this -> extrude_entity ( * ee , "perimeter" , - 1. , & lower_layer_edge_grid );
}
2017-05-03 18:28:22 +02:00
return gcode ;
}
// Chain the paths hierarchically by a greedy algorithm to minimize a travel distance.
2020-04-14 11:53:28 +02:00
std :: string GCode :: extrude_infill ( const Print & print , const std :: vector < ObjectByExtruder :: Island :: Region > & by_region , bool ironing )
2017-05-03 18:28:22 +02:00
{
2020-04-14 11:53:28 +02:00
std :: string gcode ;
ExtrusionEntitiesPtr extrusions ;
const char * extrusion_name = ironing ? "ironing" : "infill" ;
2020-03-17 09:41:37 +01:00
for ( const ObjectByExtruder :: Island :: Region & region : by_region )
if ( ! region . infills . empty ()) {
2020-09-01 18:33:56 +02:00
extrusions . clear ();
extrusions . reserve ( region . infills . size ());
for ( ExtrusionEntity * ee : region . infills )
if (( ee -> role () == erIroning ) == ironing )
extrusions . emplace_back ( ee );
if ( ! extrusions . empty ()) {
2021-05-05 18:13:58 +02:00
m_config . apply ( print . get_print_region ( & region - & by_region . front ()). config ());
2020-09-01 18:33:56 +02:00
chain_and_reorder_extrusion_entities ( extrusions , & m_last_pos );
for ( const ExtrusionEntity * fill : extrusions ) {
auto * eec = dynamic_cast < const ExtrusionEntityCollection *> ( fill );
if ( eec ) {
for ( ExtrusionEntity * ee : eec -> chained_path_from ( m_last_pos ). entities )
gcode += this -> extrude_entity ( * ee , extrusion_name );
} else
gcode += this -> extrude_entity ( * fill , extrusion_name );
}
}
2017-05-03 18:28:22 +02:00
}
return gcode ;
}
2017-05-10 11:25:57 +02:00
std :: string GCode :: extrude_support ( const ExtrusionEntityCollection & support_fills )
2017-05-03 18:28:22 +02:00
{
2021-02-24 08:48:33 +01:00
static constexpr const char * support_label = "support material" ;
static constexpr const char * support_interface_label = "support material interface" ;
2017-05-03 18:28:22 +02:00
std :: string gcode ;
if ( ! support_fills . entities . empty ()) {
const double support_speed = m_config . support_material_speed . value ;
const double support_interface_speed = m_config . support_material_interface_speed . get_abs_value ( support_speed );
for ( const ExtrusionEntity * ee : support_fills . entities ) {
2017-04-07 17:37:30 +02:00
ExtrusionRole role = ee -> role ();
assert ( role == erSupportMaterial || role == erSupportMaterialInterface );
const char * label = ( role == erSupportMaterial ) ? support_label : support_interface_label ;
const double speed = ( role == erSupportMaterial ) ? support_speed : support_interface_speed ;
const ExtrusionPath * path = dynamic_cast < const ExtrusionPath *> ( ee );
if ( path )
2017-05-10 11:25:57 +02:00
gcode += this -> extrude_path ( * path , label , speed );
2017-04-07 17:37:30 +02:00
else {
const ExtrusionMultiPath * multipath = dynamic_cast < const ExtrusionMultiPath *> ( ee );
if ( multipath )
2017-05-10 11:25:57 +02:00
gcode += this -> extrude_multi_path ( * multipath , label , speed );
2021-02-24 08:48:33 +01:00
else {
const ExtrusionEntityCollection * eec = dynamic_cast < const ExtrusionEntityCollection *> ( ee );
assert ( eec );
if ( eec )
gcode += this -> extrude_support ( * eec );
}
2017-04-07 17:37:30 +02:00
}
}
}
return gcode ;
}
2018-01-08 13:44:10 +01:00
void GCode :: _write ( FILE * file , const char * what )
2017-12-14 09:18:28 +01:00
{
2018-01-08 13:44:10 +01:00
if ( what != nullptr ) {
2020-05-07 10:49:12 +02:00
const char * gcode = what ;
2018-01-08 13:44:10 +01:00
// writes string to file
fwrite ( gcode , 1 , :: strlen ( gcode ), file );
2017-12-14 09:18:28 +01:00
}
}
2018-01-03 17:29:49 +01:00
void GCode :: _writeln ( FILE * file , const std :: string & what )
2017-12-14 09:18:28 +01:00
{
2018-01-03 17:29:49 +01:00
if ( ! what . empty ())
_write ( file , ( what . back () == '\n' ) ? what : ( what + '\n' ));
2017-12-14 09:18:28 +01:00
}
void GCode :: _write_format ( FILE * file , const char * format , ...)
{
2018-01-02 10:57:30 +01:00
va_list args ;
va_start ( args , format );
2018-01-03 20:53:39 +01:00
int buflen ;
{
va_list args2 ;
va_copy ( args2 , args );
buflen =
#ifdef _MSC_VER
:: _vscprintf ( format , args2 )
#else
:: vsnprintf ( nullptr , 0 , format , args2 )
#endif
+ 1 ;
va_end ( args2 );
}
2018-01-03 17:29:49 +01:00
char buffer [ 1024 ];
bool buffer_dynamic = buflen > 1024 ;
char * bufptr = buffer_dynamic ? ( char * ) malloc ( buflen ) : buffer ;
int res = :: vsnprintf ( bufptr , buflen , format , args );
2018-01-03 20:53:39 +01:00
if ( res > 0 )
2018-01-08 13:44:10 +01:00
_write ( file , bufptr );
2018-01-17 10:39:05 +01:00
2018-01-03 17:29:49 +01:00
if ( buffer_dynamic )
free ( bufptr );
2018-01-03 20:53:39 +01:00
va_end ( args );
2017-12-14 09:18:28 +01:00
}
2017-05-10 11:25:57 +02:00
std :: string GCode :: _extrude ( const ExtrusionPath & path , std :: string description , double speed )
2015-07-02 18:57:40 +02:00
{
std :: string gcode ;
2020-09-01 18:33:56 +02:00
2019-03-26 10:38:50 +01:00
if ( is_bridge ( path . role ()))
description += " (bridge)" ;
2020-09-01 18:33:56 +02:00
2015-07-02 18:57:40 +02:00
// go to first point of extrusion path
2018-08-15 13:51:40 +02:00
if ( ! m_last_pos_defined || m_last_pos != path . first_point ()) {
2015-07-02 18:57:40 +02:00
gcode += this -> travel_to (
path . first_point (),
2017-04-07 17:37:30 +02:00
path . role (),
2015-07-02 18:57:40 +02:00
"move to first " + description + " point"
);
}
2020-09-01 18:33:56 +02:00
2015-07-02 18:57:40 +02:00
// compensate retraction
gcode += this -> unretract ();
2020-09-01 18:33:56 +02:00
2015-07-02 18:57:40 +02:00
// adjust acceleration
{
double acceleration ;
2017-05-10 11:25:57 +02:00
if ( this -> on_first_layer () && m_config . first_layer_acceleration . value > 0 ) {
2017-05-03 18:28:22 +02:00
acceleration = m_config . first_layer_acceleration . value ;
} else if ( m_config . perimeter_acceleration . value > 0 && is_perimeter ( path . role ())) {
acceleration = m_config . perimeter_acceleration . value ;
} else if ( m_config . bridge_acceleration . value > 0 && is_bridge ( path . role ())) {
acceleration = m_config . bridge_acceleration . value ;
} else if ( m_config . infill_acceleration . value > 0 && is_infill ( path . role ())) {
acceleration = m_config . infill_acceleration . value ;
2015-07-02 18:57:40 +02:00
} else {
2017-05-03 18:28:22 +02:00
acceleration = m_config . default_acceleration . value ;
2015-07-02 18:57:40 +02:00
}
2017-05-10 11:25:57 +02:00
gcode += m_writer . set_acceleration (( unsigned int ) floor ( acceleration + 0.5 ));
2015-07-02 18:57:40 +02:00
}
2020-09-01 18:33:56 +02:00
2015-07-02 18:57:40 +02:00
// calculate extrusion length per distance unit
2017-06-22 12:59:23 +02:00
double e_per_mm = m_writer . extruder () -> e_per_mm3 () * path . mm3_per_mm ;
2017-05-03 18:28:22 +02:00
if ( m_writer . extrusion_axis (). empty ()) e_per_mm = 0 ;
2020-09-01 18:33:56 +02:00
2015-07-02 18:57:40 +02:00
// set speed
if ( speed == - 1 ) {
2017-04-07 17:37:30 +02:00
if ( path . role () == erPerimeter ) {
2017-05-03 18:28:22 +02:00
speed = m_config . get_abs_value ( "perimeter_speed" );
2017-04-07 17:37:30 +02:00
} else if ( path . role () == erExternalPerimeter ) {
2017-05-03 18:28:22 +02:00
speed = m_config . get_abs_value ( "external_perimeter_speed" );
2017-04-07 17:37:30 +02:00
} else if ( path . role () == erOverhangPerimeter || path . role () == erBridgeInfill ) {
2017-05-03 18:28:22 +02:00
speed = m_config . get_abs_value ( "bridge_speed" );
2017-04-07 17:37:30 +02:00
} else if ( path . role () == erInternalInfill ) {
2017-05-03 18:28:22 +02:00
speed = m_config . get_abs_value ( "infill_speed" );
2017-04-07 17:37:30 +02:00
} else if ( path . role () == erSolidInfill ) {
2017-05-03 18:28:22 +02:00
speed = m_config . get_abs_value ( "solid_infill_speed" );
2017-04-07 17:37:30 +02:00
} else if ( path . role () == erTopSolidInfill ) {
2017-05-03 18:28:22 +02:00
speed = m_config . get_abs_value ( "top_solid_infill_speed" );
2020-04-14 11:53:28 +02:00
} else if ( path . role () == erIroning ) {
speed = m_config . get_abs_value ( "ironing_speed" );
2017-04-07 17:37:30 +02:00
} else if ( path . role () == erGapFill ) {
2017-05-03 18:28:22 +02:00
speed = m_config . get_abs_value ( "gap_fill_speed" );
2015-07-02 18:57:40 +02:00
} else {
2020-09-14 16:27:55 +02:00
throw Slic3r :: InvalidArgument ( "Invalid speed" );
2015-07-02 18:57:40 +02:00
}
}
2017-05-10 11:25:57 +02:00
if ( m_volumetric_speed != 0. && speed == 0 )
2017-05-03 18:28:22 +02:00
speed = m_volumetric_speed / path . mm3_per_mm ;
2021-01-19 16:50:24 +01:00
if ( this -> on_first_layer ())
speed = m_config . get_abs_value ( "first_layer_speed" , speed );
2017-05-03 18:28:22 +02:00
if ( m_config . max_volumetric_speed . value > 0 ) {
2015-07-02 18:57:40 +02:00
// cap speed with max_volumetric_speed anyway (even if user is not using autospeed)
speed = std :: min (
speed ,
2017-05-03 18:28:22 +02:00
m_config . max_volumetric_speed . value / path . mm3_per_mm
2015-07-02 18:57:40 +02:00
);
}
2016-11-01 13:41:24 +01:00
if ( EXTRUDER_CONFIG ( filament_max_volumetric_speed ) > 0 ) {
// cap speed with max_volumetric_speed anyway (even if user is not using autospeed)
speed = std :: min (
speed ,
EXTRUDER_CONFIG ( filament_max_volumetric_speed ) / path . mm3_per_mm
);
}
2019-08-07 10:54:36 +02:00
double F = speed * 60 ; // convert mm/sec to mm/min
2020-09-01 18:33:56 +02:00
2015-07-02 18:57:40 +02:00
// extrude arc or line
2018-05-07 14:23:07 +02:00
if ( m_enable_extrusion_role_markers )
2018-01-08 13:44:10 +01:00
{
if ( path . role () != m_last_extrusion_role )
{
m_last_extrusion_role = path . role ();
if ( m_enable_extrusion_role_markers )
{
char buf [ 32 ];
sprintf ( buf , ";_EXTRUSION_ROLE:%d \n " , int ( m_last_extrusion_role ));
gcode += buf ;
}
}
}
2020-07-30 13:49:57 +02:00
// adds processor tags and updates processor tracking data
2020-08-17 15:59:36 +02:00
// PrusaMultiMaterial::Writer may generate GCodeProcessor::Height_Tag lines without updating m_last_height
// so, if the last role was erWipeTower we force export of GCodeProcessor::Height_Tag lines
bool last_was_wipe_tower = ( m_last_processor_extrusion_role == erWipeTower );
2020-11-12 14:03:58 +01:00
char buf [ 64 ];
2019-02-28 11:26:27 +01:00
2020-11-12 14:03:58 +01:00
if ( path . role () != m_last_processor_extrusion_role ) {
m_last_processor_extrusion_role = path . role ();
2021-02-18 14:34:40 +01:00
#if ENABLE_VALIDATE_CUSTOM_GCODE
sprintf ( buf , ";%s%s \n " , GCodeProcessor :: reserved_tag ( GCodeProcessor :: ETags :: Role ). c_str (), ExtrusionEntity :: role_to_string ( m_last_processor_extrusion_role ). c_str ());
#else
2020-11-12 14:03:58 +01:00
sprintf ( buf , ";%s%s \n " , GCodeProcessor :: Extrusion_Role_Tag . c_str (), ExtrusionEntity :: role_to_string ( m_last_processor_extrusion_role ). c_str ());
2021-02-18 14:34:40 +01:00
#endif // ENABLE_VALIDATE_CUSTOM_GCODE
2020-11-12 14:03:58 +01:00
gcode += buf ;
}
2020-08-17 10:06:41 +02:00
2020-12-08 15:55:53 +01:00
if ( last_was_wipe_tower || m_last_width != path . width ) {
m_last_width = path . width ;
2021-02-18 14:34:40 +01:00
#if ENABLE_VALIDATE_CUSTOM_GCODE
sprintf ( buf , ";%s%g \n " , GCodeProcessor :: reserved_tag ( GCodeProcessor :: ETags :: Width ). c_str (), m_last_width );
#else
2020-12-08 15:55:53 +01:00
sprintf ( buf , ";%s%g \n " , GCodeProcessor :: Width_Tag . c_str (), m_last_width );
2021-02-18 14:34:40 +01:00
#endif // ENABLE_VALIDATE_CUSTOM_GCODE
2020-12-08 15:55:53 +01:00
gcode += buf ;
}
2020-08-17 10:06:41 +02:00
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
2020-11-12 14:03:58 +01:00
if ( last_was_wipe_tower || ( m_last_mm3_per_mm != path . mm3_per_mm )) {
m_last_mm3_per_mm = path . mm3_per_mm ;
sprintf ( buf , ";%s%f \n " , GCodeProcessor :: Mm3_Per_Mm_Tag . c_str (), m_last_mm3_per_mm );
gcode += buf ;
}
2020-08-17 10:06:41 +02:00
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
2020-08-17 15:59:36 +02:00
2020-11-12 14:03:58 +01:00
if ( last_was_wipe_tower || std :: abs ( m_last_height - path . height ) > EPSILON ) {
m_last_height = path . height ;
2021-02-18 14:34:40 +01:00
#if ENABLE_VALIDATE_CUSTOM_GCODE
sprintf ( buf , ";%s%g \n " , GCodeProcessor :: reserved_tag ( GCodeProcessor :: ETags :: Height ). c_str (), m_last_height );
#else
2020-11-12 14:03:58 +01:00
sprintf ( buf , ";%s%g \n " , GCodeProcessor :: Height_Tag . c_str (), m_last_height );
2021-02-18 14:34:40 +01:00
#endif // ENABLE_VALIDATE_CUSTOM_GCODE
2020-11-12 14:03:58 +01:00
gcode += buf ;
2018-01-08 13:44:10 +01:00
}
2017-06-30 17:05:58 +02:00
std :: string comment ;
if ( m_enable_cooling_markers ) {
if ( is_bridge ( path . role ()))
gcode += ";_BRIDGE_FAN_START \n " ;
else
comment = ";_EXTRUDE_SET_SPEED" ;
if ( path . role () == erExternalPerimeter )
comment += ";_EXTERNAL_PERIMETER" ;
}
2018-05-07 14:23:07 +02:00
2017-06-30 17:05:58 +02:00
// F is mm per minute.
gcode += m_writer . set_speed ( F , "" , comment );
2017-06-22 12:59:23 +02:00
double path_length = 0. ;
2015-07-02 18:57:40 +02:00
{
2017-05-03 18:28:22 +02:00
std :: string comment = m_config . gcode_comments ? description : "" ;
2017-05-10 11:25:57 +02:00
for ( const Line & line : path . polyline . lines ()) {
const double line_length = line . length () * SCALING_FACTOR ;
2015-07-02 19:14:55 +02:00
path_length += line_length ;
2017-05-03 18:28:22 +02:00
gcode += m_writer . extrude_to_xy (
2017-05-10 11:25:57 +02:00
this -> point_to_gcode ( line . b ),
2015-07-02 19:14:55 +02:00
e_per_mm * line_length ,
2017-05-10 11:25:57 +02:00
comment );
2015-07-02 18:57:40 +02:00
}
}
2017-06-30 17:05:58 +02:00
if ( m_enable_cooling_markers )
gcode += is_bridge ( path . role ()) ? ";_BRIDGE_FAN_END \n " : ";_EXTRUDE_END \n " ;
2020-09-01 18:33:56 +02:00
2015-07-02 18:57:40 +02:00
this -> set_last_pos ( path . last_point ());
return gcode ;
}
2015-07-02 15:12:04 +02:00
// This method accepts &point in print coordinates.
2017-05-18 16:53:19 +02:00
std :: string GCode :: travel_to ( const Point & point , ExtrusionRole role , std :: string comment )
2020-09-01 18:33:56 +02:00
{
2015-07-02 15:12:04 +02:00
/* Define the travel move as a line between current position and the taget point.
This is expressed in print coordinates, so it will need to be translated by
2015-07-02 18:57:40 +02:00
this->origin in order to get G-code coordinates. */
2020-11-17 10:42:27 +01:00
Polyline travel { this -> last_pos (), point };
2020-09-01 18:33:56 +02:00
2015-07-02 15:12:04 +02:00
// check whether a straight travel move would need retraction
2021-02-25 23:30:22 +01:00
bool needs_retraction = this -> needs_retraction ( travel , role );
2020-10-22 06:20:38 +02:00
// check whether wipe could be disabled without causing visible stringing
2021-02-25 23:30:22 +01:00
bool could_be_wipe_disabled = false ;
// Save state of use_external_mp_once for the case that will be needed to call twice m_avoid_crossing_perimeters.travel_to.
const bool used_external_mp_once = m_avoid_crossing_perimeters . used_external_mp_once ();
2020-09-01 18:33:56 +02:00
2015-07-02 15:12:04 +02:00
// if a retraction would be needed, try to use avoid_crossing_perimeters to plan a
// multi-hop travel path inside the configuration space
if ( needs_retraction
2017-05-03 18:28:22 +02:00
&& m_config . avoid_crossing_perimeters
2020-11-17 10:42:27 +01:00
&& ! m_avoid_crossing_perimeters . disabled_once ()) {
2020-10-22 06:20:38 +02:00
travel = m_avoid_crossing_perimeters . travel_to ( * this , point , & could_be_wipe_disabled );
2015-07-02 15:12:04 +02:00
// check again whether the new travel path still needs a retraction
needs_retraction = this -> needs_retraction ( travel , role );
2017-05-03 18:28:22 +02:00
//if (needs_retraction && m_layer_index > 1) exit(0);
2015-07-02 15:12:04 +02:00
}
2020-09-01 18:33:56 +02:00
2015-07-02 15:12:04 +02:00
// Re-allow avoid_crossing_perimeters for the next travel moves
2020-11-17 10:42:27 +01:00
m_avoid_crossing_perimeters . reset_once_modifiers ();
2020-09-01 18:33:56 +02:00
2015-07-02 15:12:04 +02:00
// generate G-code for the travel move
std :: string gcode ;
2020-10-13 22:29:36 +02:00
if ( needs_retraction ) {
2020-11-17 10:42:27 +01:00
if ( m_config . avoid_crossing_perimeters && could_be_wipe_disabled )
2020-10-22 06:20:38 +02:00
m_wipe . reset_path ();
2020-10-13 22:29:36 +02:00
Point last_post_before_retract = this -> last_pos ();
2017-03-29 17:45:38 +02:00
gcode += this -> retract ();
2020-10-13 22:29:36 +02:00
// When "Wipe while retracting" is enabled, then extruder moves to another position, and travel from this position can cross perimeters.
2021-02-18 14:41:56 +01:00
// Because of it, it is necessary to call avoid crossing perimeters again with new starting point after calling retraction()
// FIXME Lukas H.: Try to predict if this second calling of avoid crossing perimeters will be needed or not. It could save computations.
2020-11-17 10:42:27 +01:00
if ( last_post_before_retract != this -> last_pos () && m_config . avoid_crossing_perimeters ) {
2021-02-25 23:30:22 +01:00
// If in the previous call of m_avoid_crossing_perimeters.travel_to was use_external_mp_once set to true restore this value for next call.
if ( used_external_mp_once )
m_avoid_crossing_perimeters . use_external_mp_once ();
travel = m_avoid_crossing_perimeters . travel_to ( * this , point );
// If state of use_external_mp_once was changed reset it to right value.
if ( used_external_mp_once )
m_avoid_crossing_perimeters . reset_once_modifiers ();
2020-10-13 22:29:36 +02:00
}
} else
2017-03-29 17:45:38 +02:00
// Reset the wipe path when traveling, so one would not wipe along an old path.
2017-05-03 18:28:22 +02:00
m_wipe . reset_path ();
2020-09-01 18:33:56 +02:00
2015-07-02 15:12:04 +02:00
// use G1 because we rely on paths being straight (G0 may make round paths)
2020-11-17 10:42:27 +01:00
if ( travel . size () >= 2 ) {
for ( size_t i = 1 ; i < travel . size (); ++ i )
gcode += m_writer . travel_to_xy ( this -> point_to_gcode ( travel . points [ i ]), comment );
this -> set_last_pos ( travel . points . back ());
2019-01-14 19:57:41 +01:00
}
2015-07-02 15:12:04 +02:00
return gcode ;
}
2017-07-10 13:15:36 +02:00
bool GCode :: needs_retraction ( const Polyline & travel , ExtrusionRole role )
2015-07-01 23:14:40 +02:00
{
2015-07-02 14:31:21 +02:00
if ( travel . length () < scale_ ( EXTRUDER_CONFIG ( retract_before_travel ))) {
2015-07-01 23:14:40 +02:00
// skip retraction if the move is shorter than the configured threshold
return false ;
}
2020-09-01 18:33:56 +02:00
2015-07-01 23:14:40 +02:00
if ( role == erSupportMaterial ) {
2017-05-03 18:28:22 +02:00
const SupportLayer * support_layer = dynamic_cast < const SupportLayer *> ( m_layer );
2017-03-07 13:03:14 +01:00
//FIXME support_layer->support_islands.contains should use some search structure!
2017-05-10 11:25:57 +02:00
if ( support_layer != NULL && support_layer -> support_islands . contains ( travel ))
2015-07-01 23:14:40 +02:00
// skip retraction if this is a travel move inside a support material island
2017-05-10 11:25:57 +02:00
//FIXME not retracting over a long path may cause oozing, which in turn may result in missing material
// at the end of the extrusion path!
2015-07-01 23:14:40 +02:00
return false ;
}
2017-05-10 11:25:57 +02:00
2017-07-10 13:15:36 +02:00
if ( m_config . only_retract_when_crossing_perimeters && m_layer != nullptr &&
m_config . fill_density . value > 0 && m_layer -> any_internal_region_slice_contains ( travel ))
// Skip retraction if travel is contained in an internal slice *and*
// internal infill is enabled (so that stringing is entirely not visible).
//FIXME any_internal_region_slice_contains() is potentionally very slow, it shall test for the bounding boxes first.
return false ;
2020-09-01 18:33:56 +02:00
2015-07-01 23:14:40 +02:00
// retract if only_retract_when_crossing_perimeters is disabled or doesn't apply
return true ;
}
2018-01-02 10:57:30 +01:00
std :: string GCode :: retract ( bool toolchange )
2015-07-01 23:00:52 +02:00
{
std :: string gcode ;
2020-09-01 18:33:56 +02:00
2017-05-10 11:25:57 +02:00
if ( m_writer . extruder () == nullptr )
2015-07-01 23:00:52 +02:00
return gcode ;
2020-09-01 18:33:56 +02:00
2015-07-01 23:00:52 +02:00
// wipe (if it's enabled for this extruder and we have a stored wipe path)
2017-05-19 19:24:21 +02:00
if ( EXTRUDER_CONFIG ( wipe ) && m_wipe . has_path ()) {
gcode += toolchange ? m_writer . retract_for_toolchange ( true ) : m_writer . retract ( true );
2017-05-03 18:28:22 +02:00
gcode += m_wipe . wipe ( * this , toolchange );
2017-05-19 19:24:21 +02:00
}
2020-09-01 18:33:56 +02:00
2015-07-01 23:00:52 +02:00
/* The parent class will decide whether we need to perform an actual retraction
2020-09-01 18:33:56 +02:00
(the extruder might be already retracted fully or partially). We call these
2015-07-01 23:00:52 +02:00
methods even if we performed wipe, since this will ensure the entire retraction
length is honored in case wipe path was too short. */
2017-05-03 18:28:22 +02:00
gcode += toolchange ? m_writer . retract_for_toolchange () : m_writer . retract ();
2020-09-01 18:33:56 +02:00
2017-05-03 18:28:22 +02:00
gcode += m_writer . reset_e ();
if ( m_writer . extruder () -> retract_length () > 0 || m_config . use_firmware_retraction )
gcode += m_writer . lift ();
2020-09-01 18:33:56 +02:00
2015-07-01 23:00:52 +02:00
return gcode ;
}
2019-01-29 12:02:48 +01:00
std :: string GCode :: set_extruder ( unsigned int extruder_id , double print_z )
2015-07-02 15:02:20 +02:00
{
2017-05-03 18:28:22 +02:00
if ( ! m_writer . need_toolchange ( extruder_id ))
2015-07-02 15:02:20 +02:00
return "" ;
2020-09-01 18:33:56 +02:00
2015-07-02 15:02:20 +02:00
// if we are running a single-extruder setup, just set the extruder and return nothing
2017-11-30 16:01:47 +01:00
if ( ! m_writer . multiple_extruders ) {
m_placeholder_parser . set ( "current_extruder" , extruder_id );
2020-09-01 18:33:56 +02:00
2019-07-19 10:02:52 +02:00
std :: string gcode ;
// Append the filament start G-code.
const std :: string & start_filament_gcode = m_config . start_filament_gcode . get_at ( extruder_id );
if ( ! start_filament_gcode . empty ()) {
// Process the start_filament_gcode for the filament.
gcode += this -> placeholder_parser_process ( "start_filament_gcode" , start_filament_gcode , extruder_id );
check_add_eol ( gcode );
}
gcode += m_writer . toolchange ( extruder_id );
return gcode ;
2017-11-30 16:01:47 +01:00
}
2020-09-01 18:33:56 +02:00
2015-07-02 15:02:20 +02:00
// prepend retraction on the current extruder
std :: string gcode = this -> retract ( true );
2017-03-29 17:45:38 +02:00
// Always reset the extrusion path, even if the tool change retract is set to zero.
2017-05-03 18:28:22 +02:00
m_wipe . reset_path ();
2020-09-01 18:33:56 +02:00
2017-11-30 16:01:47 +01:00
if ( m_writer . extruder () != nullptr ) {
2019-06-12 15:47:05 +02:00
// Process the custom end_filament_gcode. set_extruder() is only called if there is no wipe tower
// so it should not be injected twice.
2017-11-30 16:01:47 +01:00
unsigned int old_extruder_id = m_writer . extruder () -> id ();
const std :: string & end_filament_gcode = m_config . end_filament_gcode . get_at ( old_extruder_id );
2019-06-12 15:47:05 +02:00
if ( ! end_filament_gcode . empty ()) {
2017-12-05 15:54:24 +01:00
gcode += placeholder_parser_process ( "end_filament_gcode" , end_filament_gcode , old_extruder_id );
2017-11-30 16:01:47 +01:00
check_add_eol ( gcode );
}
}
2020-09-01 18:33:56 +02:00
2017-11-30 16:01:47 +01:00
// If ooze prevention is enabled, park current extruder in the nearest
// standby point and set it to the standby temperature.
2017-05-25 22:52:28 +02:00
if ( m_ooze_prevention . enable && m_writer . extruder () != nullptr )
2017-05-18 16:53:19 +02:00
gcode += m_ooze_prevention . pre_toolchange ( * this );
2019-06-12 15:47:05 +02:00
const std :: string & toolchange_gcode = m_config . toolchange_gcode . value ;
2019-09-09 12:59:17 +02:00
std :: string toolchange_gcode_parsed ;
2019-07-16 13:06:58 +02:00
// Process the custom toolchange_gcode. If it is empty, insert just a Tn command.
if ( ! toolchange_gcode . empty ()) {
DynamicConfig config ;
config . set_key_value ( "previous_extruder" , new ConfigOptionInt (( int )( m_writer . extruder () != nullptr ? m_writer . extruder () -> id () : - 1 )));
config . set_key_value ( "next_extruder" , new ConfigOptionInt (( int ) extruder_id ));
config . set_key_value ( "layer_num" , new ConfigOptionInt ( m_layer_index ));
config . set_key_value ( "layer_z" , new ConfigOptionFloat ( print_z ));
2021-03-05 13:54:29 +01:00
config . set_key_value ( "toolchange_z" , new ConfigOptionFloat ( print_z ));
2020-12-01 08:23:46 +01:00
config . set_key_value ( "max_layer_z" , new ConfigOptionFloat ( m_max_layer_z ));
2019-09-09 12:59:17 +02:00
toolchange_gcode_parsed = placeholder_parser_process ( "toolchange_gcode" , toolchange_gcode , extruder_id , & config );
gcode += toolchange_gcode_parsed ;
2019-07-16 13:06:58 +02:00
check_add_eol ( gcode );
2019-06-12 15:47:05 +02:00
}
// We inform the writer about what is happening, but we may not use the resulting gcode.
std :: string toolchange_command = m_writer . toolchange ( extruder_id );
2019-09-09 12:59:17 +02:00
if ( ! custom_gcode_changes_tool ( toolchange_gcode_parsed , m_writer . toolchange_prefix (), extruder_id ))
2019-06-12 15:47:05 +02:00
gcode += toolchange_command ;
else {
// user provided his own toolchange gcode, no need to do anything
}
2019-06-26 14:50:12 +02:00
// Set the temperature if the wipe tower didn't (not needed for non-single extruder MM)
if ( m_config . single_extruder_multi_material && ! m_config . wipe_tower ) {
2019-08-20 14:22:31 +02:00
int temp = ( m_layer_index <= 0 ? m_config . first_layer_temperature . get_at ( extruder_id ) :
2019-06-26 14:50:12 +02:00
m_config . temperature . get_at ( extruder_id ));
gcode += m_writer . set_temperature ( temp , false );
}
2019-06-12 15:47:05 +02:00
m_placeholder_parser . set ( "current_extruder" , extruder_id );
// Append the filament start G-code.
2017-11-30 16:01:47 +01:00
const std :: string & start_filament_gcode = m_config . start_filament_gcode . get_at ( extruder_id );
2019-06-12 15:47:05 +02:00
if ( ! start_filament_gcode . empty ()) {
// Process the start_filament_gcode for the new filament.
2017-12-05 15:54:24 +01:00
gcode += this -> placeholder_parser_process ( "start_filament_gcode" , start_filament_gcode , extruder_id );
2017-11-30 16:01:47 +01:00
check_add_eol ( gcode );
}
// Set the new extruder to the operating temperature.
2017-05-18 16:53:19 +02:00
if ( m_ooze_prevention . enable )
gcode += m_ooze_prevention . post_toolchange ( * this );
2020-09-01 18:33:56 +02:00
2015-07-02 15:02:20 +02:00
return gcode ;
}
2015-07-01 23:00:52 +02:00
// convert a model-space scaled point into G-code coordinates
2018-08-21 21:05:24 +02:00
Vec2d GCode :: point_to_gcode ( const Point & point ) const
2015-07-01 23:00:52 +02:00
{
2018-08-21 21:05:24 +02:00
Vec2d extruder_offset = EXTRUDER_CONFIG ( extruder_offset );
2018-08-21 17:43:05 +02:00
return unscale ( point ) + m_origin - extruder_offset ;
2017-05-18 16:53:19 +02:00
}
// convert a model-space scaled point into G-code coordinates
2018-08-21 21:05:24 +02:00
Point GCode :: gcode_to_point ( const Vec2d & point ) const
2017-05-18 16:53:19 +02:00
{
2018-08-21 21:05:24 +02:00
Vec2d extruder_offset = EXTRUDER_CONFIG ( extruder_offset );
2017-05-18 16:53:19 +02:00
return Point (
2018-08-17 15:53:43 +02:00
scale_ ( point ( 0 ) - m_origin ( 0 ) + extruder_offset ( 0 )),
scale_ ( point ( 1 ) - m_origin ( 1 ) + extruder_offset ( 1 )));
2015-07-01 23:00:52 +02:00
}
2018-06-20 12:52:00 +02:00
// Goes through by_region std::vector and returns reference to a subvector of entities, that are to be printed
// during infill/perimeter wiping, or normally (depends on wiping_entities parameter)
2020-01-08 14:58:12 +01:00
// Fills in by_region_per_copy_cache and returns its reference.
2020-01-10 11:26:52 +01:00
const std :: vector < GCode :: ObjectByExtruder :: Island :: Region >& GCode :: ObjectByExtruder :: Island :: by_region_per_copy ( std :: vector < Region > & by_region_per_copy_cache , unsigned int copy , unsigned int extruder , bool wiping_entities ) const
2018-05-31 16:21:10 +02:00
{
2020-01-08 14:58:12 +01:00
bool has_overrides = false ;
for ( const auto & reg : by_region )
2020-09-01 18:33:56 +02:00
if ( ! reg . infills_overrides . empty () || ! reg . perimeters_overrides . empty ()) {
has_overrides = true ;
break ;
}
2020-03-17 09:41:37 +01:00
2020-09-01 18:33:56 +02:00
// Data is cleared, but the memory is not.
2020-03-17 09:41:37 +01:00
by_region_per_copy_cache . clear ();
2020-01-08 14:58:12 +01:00
if ( ! has_overrides )
2020-09-01 18:33:56 +02:00
// Simple case. No need to copy the regions.
return wiping_entities ? by_region_per_copy_cache : this -> by_region ;
2020-01-08 14:58:12 +01:00
// Complex case. Some of the extrusions of some object instances are to be printed first - those are the wiping extrusions.
// Some of the extrusions of some object instances are printed later - those are the clean print extrusions.
// Filter out the extrusions based on the infill_overrides / perimeter_overrides:
2018-06-06 18:24:42 +02:00
for ( const auto & reg : by_region ) {
2020-01-08 14:58:12 +01:00
by_region_per_copy_cache . emplace_back (); // creates a region in the newly created Island
2018-05-31 16:21:10 +02:00
2018-06-20 12:52:00 +02:00
// Now we are going to iterate through perimeters and infills and pick ones that are supposed to be printed
// References are used so that we don't have to repeat the same code
for ( int iter = 0 ; iter < 2 ; ++ iter ) {
2020-01-09 10:00:38 +01:00
const ExtrusionEntitiesPtr & entities = ( iter ? reg . infills : reg . perimeters );
ExtrusionEntitiesPtr & target_eec = ( iter ? by_region_per_copy_cache . back (). infills : by_region_per_copy_cache . back (). perimeters );
2020-01-08 14:58:12 +01:00
const std :: vector < const WipingExtrusions :: ExtruderPerCopy *>& overrides = ( iter ? reg . infills_overrides : reg . perimeters_overrides );
2018-06-07 16:19:57 +02:00
2018-06-20 12:52:00 +02:00
// Now the most important thing - which extrusion should we print.
// See function ToolOrdering::get_extruder_overrides for details about the negative numbers hack.
2020-01-08 14:58:12 +01:00
if ( wiping_entities ) {
2020-09-01 18:33:56 +02:00
// Apply overrides for this region.
for ( unsigned int i = 0 ; i < overrides . size (); ++ i ) {
const WipingExtrusions :: ExtruderPerCopy * this_override = overrides [ i ];
// This copy (aka object instance) should be printed with this extruder, which overrides the default one.
if ( this_override != nullptr && ( * this_override )[ copy ] == int ( extruder ))
target_eec . emplace_back ( entities [ i ]);
}
} else {
// Apply normal extrusions (non-overrides) for this region.
unsigned int i = 0 ;
for (; i < overrides . size (); ++ i ) {
const WipingExtrusions :: ExtruderPerCopy * this_override = overrides [ i ];
// This copy (aka object instance) should be printed with this extruder, which shall be equal to the default one.
if ( this_override == nullptr || ( * this_override )[ copy ] == - int ( extruder ) - 1 )
target_eec . emplace_back ( entities [ i ]);
}
for (; i < entities . size (); ++ i )
2020-01-09 10:00:38 +01:00
target_eec . emplace_back ( entities [ i ]);
2020-09-01 18:33:56 +02:00
}
2018-06-20 12:52:00 +02:00
}
2018-05-31 16:21:10 +02:00
}
2018-06-06 18:24:42 +02:00
return by_region_per_copy_cache ;
2015-07-01 20:14:05 +02:00
}
2018-05-31 16:21:10 +02:00
2018-06-20 12:52:00 +02:00
// This function takes the eec and appends its entities to either perimeters or infills of this Region (depending on the first parameter)
// It also saves pointer to ExtruderPerCopy struct (for each entity), that holds information about which extruders should be used for which copy.
2020-01-08 14:58:12 +01:00
void GCode :: ObjectByExtruder :: Island :: Region :: append ( const Type type , const ExtrusionEntityCollection * eec , const WipingExtrusions :: ExtruderPerCopy * copies_extruder )
2018-06-20 12:52:00 +02:00
{
// We are going to manipulate either perimeters or infills, exactly in the same way. Let's create pointers to the proper structure to not repeat ourselves:
2020-01-09 10:00:38 +01:00
ExtrusionEntitiesPtr * perimeters_or_infills ;
2020-01-08 14:58:12 +01:00
std :: vector < const WipingExtrusions :: ExtruderPerCopy *>* perimeters_or_infills_overrides ;
2018-06-20 12:52:00 +02:00
2020-01-08 14:58:12 +01:00
switch ( type ) {
case PERIMETERS :
perimeters_or_infills = & perimeters ;
perimeters_or_infills_overrides = & perimeters_overrides ;
break ;
case INFILL :
2020-09-01 18:33:56 +02:00
perimeters_or_infills = & infills ;
perimeters_or_infills_overrides = & infills_overrides ;
2020-01-08 14:58:12 +01:00
break ;
default :
2020-09-14 16:27:55 +02:00
throw Slic3r :: InvalidArgument ( "Unknown parameter!" );
2018-06-20 12:52:00 +02:00
}
// First we append the entities, there are eec->entities.size() of them:
2020-01-09 10:00:38 +01:00
size_t old_size = perimeters_or_infills -> size ();
2020-04-24 09:41:48 +02:00
size_t new_size = old_size + ( eec -> can_reverse () ? eec -> entities . size () : 1 );
2020-03-17 09:41:37 +01:00
perimeters_or_infills -> reserve ( new_size );
2020-04-24 09:41:48 +02:00
if ( eec -> can_reverse ()) {
2020-09-01 18:33:56 +02:00
for ( auto * ee : eec -> entities )
perimeters_or_infills -> emplace_back ( ee );
} else
perimeters_or_infills -> emplace_back ( const_cast < ExtrusionEntityCollection *> ( eec ));
2018-06-20 12:52:00 +02:00
2020-01-08 14:58:12 +01:00
if ( copies_extruder != nullptr ) {
2020-09-01 18:33:56 +02:00
// Don't reallocate overrides if not needed.
// Missing overrides are implicitely considered non-overridden.
2020-03-17 09:41:37 +01:00
perimeters_or_infills_overrides -> reserve ( new_size );
perimeters_or_infills_overrides -> resize ( old_size , nullptr );
perimeters_or_infills_overrides -> resize ( new_size , copies_extruder );
2020-09-01 18:33:56 +02:00
}
2018-06-20 12:52:00 +02:00
}
2018-05-31 16:21:10 +02:00
} // namespace Slic3r