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-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>
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
void AvoidCrossingPerimeters :: init_external_mp ( const Print & print )
{
m_external_mp = Slic3r :: make_unique < MotionPlanner > ( union_ex ( this -> collect_contours_all_layers ( print . objects ())));
2019-06-14 12:28:24 +02:00
}
2017-05-25 22:27:53 +02:00
2020-07-16 11:09:21 +02:00
// Plan a travel move while minimizing the number of perimeter crossings.
// point is in unscaled coordinates, in the coordinate system of the current active object
// (set by gcodegen.set_origin()).
Polyline AvoidCrossingPerimeters :: travel_to ( const GCode & gcodegen , const Point & point )
{
// If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset).
// Otherwise perform the path planning in the coordinate system of the active object.
bool use_external = this -> use_external_mp || this -> use_external_mp_once ;
Point scaled_origin = use_external ? Point :: new_scale ( gcodegen . origin ()( 0 ), gcodegen . origin ()( 1 )) : Point ( 0 , 0 );
Polyline result = ( use_external ? m_external_mp . get () : m_layer_mp . get ()) ->
shortest_path ( gcodegen . last_pos () + scaled_origin , point + scaled_origin );
if ( use_external )
result . translate ( - scaled_origin );
return result ;
2019-09-19 16:30:01 +02:00
}
2019-09-19 14:58:04 +02:00
2020-07-16 11:09:21 +02:00
// Collect outer contours of all objects over all layers.
// Discard objects only containing thin walls (offset would fail on an empty polygon).
// Used by avoid crossing perimeters feature.
Polygons AvoidCrossingPerimeters :: collect_contours_all_layers ( const PrintObjectPtrs & objects )
{
Polygons islands ;
for ( const PrintObject * object : objects ) {
// Reducing all the object slices into the Z projection in a logarithimc fashion.
// First reduce to half the number of layers.
std :: vector < Polygons > polygons_per_layer (( object -> layers (). size () + 1 ) / 2 );
tbb :: parallel_for ( tbb :: blocked_range < size_t > ( 0 , object -> layers (). size () / 2 ),
[ & object , & polygons_per_layer ]( const tbb :: blocked_range < size_t >& range ) {
for ( size_t i = range . begin (); i < range . end (); ++ i ) {
const Layer * layer1 = object -> layers ()[ i * 2 ];
const Layer * layer2 = object -> layers ()[ i * 2 + 1 ];
Polygons polys ;
polys . reserve ( layer1 -> lslices . size () + layer2 -> lslices . size ());
for ( const ExPolygon & expoly : layer1 -> lslices )
//FIXME no holes?
polys . emplace_back ( expoly . contour );
for ( const ExPolygon & expoly : layer2 -> lslices )
//FIXME no holes?
polys . emplace_back ( expoly . contour );
polygons_per_layer [ i ] = union_ ( polys );
}
});
if ( object -> layers (). size () & 1 ) {
const Layer * layer = object -> layers (). back ();
Polygons polys ;
polys . reserve ( layer -> lslices . size ());
for ( const ExPolygon & expoly : layer -> lslices )
//FIXME no holes?
polys . emplace_back ( expoly . contour );
polygons_per_layer . back () = union_ ( polys );
}
// Now reduce down to a single layer.
size_t cnt = polygons_per_layer . size ();
while ( cnt > 1 ) {
tbb :: parallel_for ( tbb :: blocked_range < size_t > ( 0 , cnt / 2 ),
[ & polygons_per_layer ]( const tbb :: blocked_range < size_t >& range ) {
for ( size_t i = range . begin (); i < range . end (); ++ i ) {
Polygons polys ;
polys . reserve ( polygons_per_layer [ i * 2 ]. size () + polygons_per_layer [ i * 2 + 1 ]. size ());
polygons_append ( polys , polygons_per_layer [ i * 2 ]);
polygons_append ( polys , polygons_per_layer [ i * 2 + 1 ]);
polygons_per_layer [ i * 2 ] = union_ ( polys );
}
});
2020-10-06 13:19:39 +02:00
for ( size_t i = 1 ; i < cnt / 2 ; ++ i )
2020-07-16 11:09:21 +02:00
polygons_per_layer [ i ] = std :: move ( polygons_per_layer [ i * 2 ]);
if ( cnt & 1 )
polygons_per_layer [ cnt / 2 ] = std :: move ( polygons_per_layer [ cnt - 1 ]);
cnt = ( cnt + 1 ) / 2 ;
}
// And collect copies of the objects.
for ( const PrintInstance & instance : object -> instances ()) {
// All the layers were reduced to the 1st item of polygons_per_layer.
size_t i = islands . size ();
polygons_append ( islands , polygons_per_layer . front ());
for (; i < islands . size (); ++ i )
islands [ i ]. translate ( instance . shift );
}
2019-05-03 00:17:24 -04:00
}
2020-07-16 11:09:21 +02:00
return islands ;
2019-05-03 00:17:24 -04:00
}
2019-06-12 15:47:05 +02:00
2020-09-02 00:26:13 +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 ()) {
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"
);
}
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 );
gcodegen . m_avoid_crossing_perimeters . use_external_mp_once = true ;
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 ;
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 ));
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.
gcodegen . m_avoid_crossing_perimeters . use_external_mp_once = true ;
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 )
{
assert ( m_layer_idx == 0 );
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 );
if ( ! m_brim_done || gcodegen . writer (). need_toolchange ( extruder_id ) || finish_layer ) {
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 ;
ignore_sparse = ( m_brim_done && 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 );
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 ;
}
}
m_brim_done = true ;
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-05-07 10:49:12 +02:00
#if ENABLE_GCODE_VIEWER
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
#endif // ENABLE_GCODE_VIEWER
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.
assert ( ! object . config (). support_material || 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-03-02 15:13:23 +01:00
#if ENABLE_GCODE_VIEWER
2020-08-07 15:30:08 +02:00
// free functions called by GCode::do_export()
namespace DoExport {
static void update_print_estimated_times_stats ( const GCodeProcessor & processor , PrintStatistics & print_statistics )
{
const GCodeProcessor :: Result & result = processor . get_result ();
2020-09-17 11:42:58 +02:00
print_statistics . estimated_normal_print_time = get_time_dhms ( result . time_statistics . modes [ static_cast < size_t > ( PrintEstimatedTimeStatistics :: ETimeMode :: Normal )]. time );
2020-08-07 15:30:08 +02:00
print_statistics . estimated_silent_print_time = processor . is_stealth_time_estimator_enabled () ?
2020-09-17 11:42:58 +02:00
get_time_dhms ( result . time_statistics . modes [ static_cast < size_t > ( PrintEstimatedTimeStatistics :: ETimeMode :: Stealth )]. time ) : "N/A" ;
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 )
2019-10-23 13:31:24 +02:00
#else
2019-11-22 12:39:03 +01:00
void GCode :: do_export ( Print * print , const char * path , GCodePreviewData * preview_data , ThumbnailsGeneratorCallback thumbnail_cb )
2020-03-02 15:13:23 +01:00
#endif // ENABLE_GCODE_VIEWER
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
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
2020-05-07 10:49:12 +02:00
#if !ENABLE_GCODE_VIEWER
2018-12-18 17:34:21 +01:00
m_enable_analyzer = preview_data != nullptr ;
2020-05-07 10:49:12 +02:00
#endif // !ENABLE_GCODE_VIEWER
2018-12-18 17:34:21 +01: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.
std :: string msg = std :: string ( "G-code export to " ) + path + " failed due to invalid custom G-code sections: \n\n " ;
2018-09-11 14:04:47 +02:00
for ( const std :: string & name : m_placeholder_parser_failed_templates )
2017-12-05 15:54:24 +01:00
msg += std :: string ( " \t " ) + name + " \n " ;
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-09-14 16:27:55 +02:00
throw Slic3r :: RuntimeError ( msg );
2017-12-05 15:54:24 +01:00
}
2017-08-03 17:31:31 +02:00
2020-02-14 08:31:31 +01:00
#if ENABLE_GCODE_VIEWER
2020-09-11 15:19:23 +02:00
BOOST_LOG_TRIVIAL ( debug ) << "Start processing gcode, " << log_memory_info ();
2020-09-03 08:32:06 +02:00
m_processor . process_file ( path_tmp , [ print ]() { print -> throw_if_canceled (); });
2020-08-07 15:30:08 +02:00
DoExport :: update_print_estimated_times_stats ( m_processor , print -> m_print_statistics );
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 ());
2020-09-11 15:19:23 +02:00
BOOST_LOG_TRIVIAL ( debug ) << "Finished processing gcode, " << log_memory_info ();
2020-08-05 15:43:46 +02:00
#else
2019-09-05 09:54:44 +02:00
GCodeTimeEstimator :: PostProcessData normal_data = m_normal_time_estimator . get_post_process_data ();
GCodeTimeEstimator :: PostProcessData silent_data = m_silent_time_estimator . get_post_process_data ();
bool remaining_times_enabled = print -> config (). remaining_times . value ;
BOOST_LOG_TRIVIAL ( debug ) << "Time estimator post processing" << log_memory_info ();
GCodeTimeEstimator :: post_process ( path_tmp , 60.0f , remaining_times_enabled ? & normal_data : nullptr , ( remaining_times_enabled && m_silent_time_estimator_enabled ) ? & silent_data : nullptr );
2020-08-05 15:43:46 +02:00
if ( remaining_times_enabled ) {
2018-12-18 17:34:21 +01:00
m_normal_time_estimator . reset ();
2019-09-05 09:54:44 +02:00
if ( m_silent_time_estimator_enabled )
2018-12-18 17:34:21 +01:00
m_silent_time_estimator . reset ();
}
// starts analyzer calculations
if ( m_enable_analyzer ) {
2019-08-05 14:30:32 +02:00
BOOST_LOG_TRIVIAL ( debug ) << "Preparing G-code preview data" << log_memory_info ();
2019-03-12 11:57:39 +01:00
m_analyzer . calc_gcode_preview_data ( * preview_data , [ print ]() { print -> throw_if_canceled (); });
2018-12-18 17:34:21 +01:00
m_analyzer . reset ();
}
2020-08-05 15:43:46 +02:00
#endif // ENABLE_GCODE_VIEWER
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
#if !ENABLE_GCODE_VIEWER
static void init_time_estimators ( const PrintConfig & config , GCodeTimeEstimator & normal_time_estimator , GCodeTimeEstimator & silent_time_estimator , bool & silent_time_estimator_enabled )
2020-01-10 11:26:52 +01:00
{
// resets time estimators
normal_time_estimator . reset ();
normal_time_estimator . set_dialect ( config . gcode_flavor );
normal_time_estimator . set_extrusion_axis ( config . get_extrusion_axis ()[ 0 ]);
silent_time_estimator_enabled = ( config . gcode_flavor == gcfMarlin ) && config . silent_mode ;
// Until we have a UI support for the other firmwares than the Marlin, use the hardcoded default values
// and let the user to enter the G-code limits into the start G-code.
// If the following block is enabled for other firmwares than the Marlin, then the function
// this->print_machine_envelope(file, print);
// shall be adjusted as well to produce a G-code block compatible with the particular firmware flavor.
if ( config . gcode_flavor . value == gcfMarlin ) {
2020-10-06 13:18:25 +02:00
if ( config . machine_limits_usage . value != MachineLimitsUsage :: Ignore ) {
2020-10-02 17:31:55 +02:00
normal_time_estimator . set_max_acceleration (( float ) config . machine_max_acceleration_extruding . values [ 0 ]);
normal_time_estimator . set_retract_acceleration (( float ) config . machine_max_acceleration_retracting . values [ 0 ]);
normal_time_estimator . set_minimum_feedrate (( float ) config . machine_min_extruding_rate . values [ 0 ]);
normal_time_estimator . set_minimum_travel_feedrate (( float ) config . machine_min_travel_rate . values [ 0 ]);
normal_time_estimator . set_axis_max_acceleration ( GCodeTimeEstimator :: X , ( float ) config . machine_max_acceleration_x . values [ 0 ]);
normal_time_estimator . set_axis_max_acceleration ( GCodeTimeEstimator :: Y , ( float ) config . machine_max_acceleration_y . values [ 0 ]);
normal_time_estimator . set_axis_max_acceleration ( GCodeTimeEstimator :: Z , ( float ) config . machine_max_acceleration_z . values [ 0 ]);
normal_time_estimator . set_axis_max_acceleration ( GCodeTimeEstimator :: E , ( float ) config . machine_max_acceleration_e . values [ 0 ]);
normal_time_estimator . set_axis_max_feedrate ( GCodeTimeEstimator :: X , ( float ) config . machine_max_feedrate_x . values [ 0 ]);
normal_time_estimator . set_axis_max_feedrate ( GCodeTimeEstimator :: Y , ( float ) config . machine_max_feedrate_y . values [ 0 ]);
normal_time_estimator . set_axis_max_feedrate ( GCodeTimeEstimator :: Z , ( float ) config . machine_max_feedrate_z . values [ 0 ]);
normal_time_estimator . set_axis_max_feedrate ( GCodeTimeEstimator :: E , ( float ) config . machine_max_feedrate_e . values [ 0 ]);
normal_time_estimator . set_axis_max_jerk ( GCodeTimeEstimator :: X , ( float ) config . machine_max_jerk_x . values [ 0 ]);
normal_time_estimator . set_axis_max_jerk ( GCodeTimeEstimator :: Y , ( float ) config . machine_max_jerk_y . values [ 0 ]);
normal_time_estimator . set_axis_max_jerk ( GCodeTimeEstimator :: Z , ( float ) config . machine_max_jerk_z . values [ 0 ]);
normal_time_estimator . set_axis_max_jerk ( GCodeTimeEstimator :: E , ( float ) config . machine_max_jerk_e . values [ 0 ]);
}
2020-01-10 11:26:52 +01:00
if ( silent_time_estimator_enabled )
{
silent_time_estimator . reset ();
silent_time_estimator . set_dialect ( config . gcode_flavor );
silent_time_estimator . set_extrusion_axis ( config . get_extrusion_axis ()[ 0 ]);
2020-10-02 17:31:55 +02:00
2020-10-06 13:18:25 +02:00
if ( config . machine_limits_usage . value != MachineLimitsUsage :: Ignore ) {
2020-10-02 17:31:55 +02:00
/* "Stealth mode" values can be just a copy of "normal mode" values
* (when they aren't input for a printer preset).
* Thus, use back value from values, instead of second one, which could be absent
*/
silent_time_estimator . set_max_acceleration (( float ) config . machine_max_acceleration_extruding . values . back ());
silent_time_estimator . set_retract_acceleration (( float ) config . machine_max_acceleration_retracting . values . back ());
silent_time_estimator . set_minimum_feedrate (( float ) config . machine_min_extruding_rate . values . back ());
silent_time_estimator . set_minimum_travel_feedrate (( float ) config . machine_min_travel_rate . values . back ());
silent_time_estimator . set_axis_max_acceleration ( GCodeTimeEstimator :: X , ( float ) config . machine_max_acceleration_x . values . back ());
silent_time_estimator . set_axis_max_acceleration ( GCodeTimeEstimator :: Y , ( float ) config . machine_max_acceleration_y . values . back ());
silent_time_estimator . set_axis_max_acceleration ( GCodeTimeEstimator :: Z , ( float ) config . machine_max_acceleration_z . values . back ());
silent_time_estimator . set_axis_max_acceleration ( GCodeTimeEstimator :: E , ( float ) config . machine_max_acceleration_e . values . back ());
silent_time_estimator . set_axis_max_feedrate ( GCodeTimeEstimator :: X , ( float ) config . machine_max_feedrate_x . values . back ());
silent_time_estimator . set_axis_max_feedrate ( GCodeTimeEstimator :: Y , ( float ) config . machine_max_feedrate_y . values . back ());
silent_time_estimator . set_axis_max_feedrate ( GCodeTimeEstimator :: Z , ( float ) config . machine_max_feedrate_z . values . back ());
silent_time_estimator . set_axis_max_feedrate ( GCodeTimeEstimator :: E , ( float ) config . machine_max_feedrate_e . values . back ());
silent_time_estimator . set_axis_max_jerk ( GCodeTimeEstimator :: X , ( float ) config . machine_max_jerk_x . values . back ());
silent_time_estimator . set_axis_max_jerk ( GCodeTimeEstimator :: Y , ( float ) config . machine_max_jerk_y . values . back ());
silent_time_estimator . set_axis_max_jerk ( GCodeTimeEstimator :: Z , ( float ) config . machine_max_jerk_z . values . back ());
silent_time_estimator . set_axis_max_jerk ( GCodeTimeEstimator :: E , ( float ) config . machine_max_jerk_e . values . back ());
}
2020-01-10 11:26:52 +01:00
if ( config . single_extruder_multi_material ) {
// As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
// are considered to be active for the single extruder multi-material printers only.
silent_time_estimator . set_filament_load_times ( config . filament_load_time . values );
silent_time_estimator . set_filament_unload_times ( config . filament_unload_time . values );
}
}
}
// Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful.
if ( config . single_extruder_multi_material ) {
// As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
// are considered to be active for the single extruder multi-material printers only.
normal_time_estimator . set_filament_load_times ( config . filament_load_time . values );
normal_time_estimator . set_filament_unload_times ( config . filament_unload_time . values );
}
}
2020-08-05 15:43:46 +02:00
#endif // !ENABLE_GCODE_VIEWER
2020-01-10 11:26:52 +01:00
2020-05-07 10:49:12 +02:00
#if ENABLE_GCODE_VIEWER
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
{
2020-08-05 15:43:46 +02:00
silent_time_estimator_enabled = ( config . gcode_flavor == gcfMarlin ) && 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
}
#else
static void init_gcode_analyzer ( const PrintConfig & config , GCodeAnalyzer & analyzer )
2020-01-10 11:26:52 +01:00
{
// resets analyzer
analyzer . reset ();
// send extruder offset data to analyzer
GCodeAnalyzer :: ExtruderOffsetsMap extruder_offsets ;
unsigned int num_extruders = static_cast < unsigned int > ( config . nozzle_diameter . values . size ());
for ( unsigned int extruder_id = 0 ; extruder_id < num_extruders ; ++ extruder_id )
{
Vec2d offset = config . extruder_offset . get_at ( extruder_id );
if ( ! offset . isApprox ( Vec2d :: Zero ()))
extruder_offsets [ extruder_id ] = offset ;
}
analyzer . set_extruder_offsets ( extruder_offsets );
// tell analyzer about the extrusion axis
analyzer . set_extrusion_axis ( config . get_extrusion_axis ()[ 0 ]);
// send extruders count to analyzer to allow it to detect invalid extruder idxs
analyzer . set_extruders_count ( num_extruders );
// tell analyzer about the gcode flavor
analyzer . set_gcode_flavor ( config . gcode_flavor );
2020-03-02 15:13:23 +01:00
}
#endif // ENABLE_GCODE_VIEWER
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 ()) {
for ( size_t region_id = 0 ; region_id < object -> region_volumes . size (); ++ region_id ) {
const PrintRegion * region = print . regions ()[ region_id ];
for ( auto layer : object -> layers ()) {
const LayerRegion * layerm = layer -> regions ()[ region_id ];
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 )
mm3_per_mm . push_back ( layerm -> perimeters . min_mm3_per_mm ());
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 )
mm3_per_mm . push_back ( layerm -> fills . min_mm3_per_mm ());
}
}
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 ;
ThumbnailsList thumbnails ;
thumbnail_cb ( thumbnails , sizes , true , true , true , true );
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
#if !ENABLE_GCODE_VIEWER
const GCodeTimeEstimator & normal_time_estimator ,
2020-01-10 11:26:52 +01:00
const GCodeTimeEstimator & silent_time_estimator ,
const bool silent_time_estimator_enabled ,
2020-08-05 15:43:46 +02:00
#endif // !ENABLE_GCODE_VIEWER
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-08-05 15:43:46 +02:00
#if !ENABLE_GCODE_VIEWER
2020-07-16 11:09:21 +02:00
print_statistics . estimated_normal_print_time = normal_time_estimator . get_time_dhm /*s*/ ();
print_statistics . estimated_silent_print_time = silent_time_estimator_enabled ? silent_time_estimator . get_time_dhm /*s*/ () : "N/A" ;
2020-07-08 13:33:50 +02:00
print_statistics . estimated_normal_custom_gcode_print_times = normal_time_estimator . get_custom_gcode_times_dhm ( true );
if ( silent_time_estimator_enabled )
print_statistics . estimated_silent_custom_gcode_print_times = silent_time_estimator . get_custom_gcode_times_dhm ( true );
2020-08-05 15:43:46 +02:00
#endif // !ENABLE_GCODE_VIEWER
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 ;
auto append = [ & extruder , & extruders ]( std :: pair < std :: string , unsigned int > & dst , const char * tmpl , double value ) {
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 ;
};
print_statistics . filament_stats . insert ( std :: pair < size_t , float > { extruder . id (), ( float ) used_filament });
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
}
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
}
// 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-03-02 15:13:23 +01:00
#if ENABLE_GCODE_VIEWER
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 );
2020-05-07 10:49:12 +02:00
#else
2020-08-05 15:43:46 +02:00
DoExport :: init_time_estimators ( print . config (),
// modifies the following:
m_normal_time_estimator , m_silent_time_estimator , m_silent_time_estimator_enabled );
2020-05-07 10:49:12 +02:00
DoExport :: init_gcode_analyzer ( print . config (), m_analyzer );
2020-03-02 15:13:23 +01:00
#endif // ENABLE_GCODE_VIEWER
2019-07-12 12:38:00 +02:00
2018-01-08 13:44:10 +01:00
// resets analyzer's tracking data
2020-05-07 10:49:12 +02:00
#if ENABLE_GCODE_VIEWER
m_last_height = 0.0f ;
2020-07-30 13:49:57 +02:00
m_last_layer_z = 0.0f ;
2020-08-17 10:06:41 +02:00
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
m_last_mm3_per_mm = 0.0 ;
2020-08-17 14:37:26 +02:00
m_last_width = 0.0f ;
2020-08-17 10:06:41 +02:00
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
2020-05-07 10:49:12 +02:00
#else
2018-01-08 13:44:10 +01:00
m_last_mm3_per_mm = GCodeAnalyzer :: Default_mm3_per_mm ;
m_last_width = GCodeAnalyzer :: Default_Width ;
m_last_height = GCodeAnalyzer :: Default_Height ;
2020-05-07 10:49:12 +02:00
#endif // ENABLE_GCODE_VIEWER
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 ;
const double first_layer_height = first_object -> config (). first_layer_height . get_abs_value ( layer_height );
2018-11-07 15:17:29 +01:00
for ( const PrintRegion * region : print . regions ()) {
2018-01-02 10:57:30 +01:00
_write_format ( file , "; external perimeters extrusion width = %.2fmm \n " , region -> flow ( frExternalPerimeter , layer_height , false , false , - 1. , * first_object ). width );
_write_format ( file , "; perimeters extrusion width = %.2fmm \n " , region -> flow ( frPerimeter , layer_height , false , false , - 1. , * first_object ). width );
_write_format ( file , "; infill extrusion width = %.2fmm \n " , region -> flow ( frInfill , layer_height , false , false , - 1. , * first_object ). width );
_write_format ( file , "; solid infill extrusion width = %.2fmm \n " , region -> flow ( frSolidInfill , layer_height , false , false , - 1. , * first_object ). width );
_write_format ( file , "; top infill extrusion width = %.2fmm \n " , region -> flow ( frTopSolidInfill , layer_height , false , false , - 1. , * first_object ). width );
2017-12-13 15:35:00 +01:00
if ( print . has_support_material ())
2018-01-02 10:57:30 +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 )
2018-01-02 10:57:30 +01:00
_write_format ( file , "; first layer extrusion width = %.2fmm \n " , region -> flow ( frPerimeter , first_layer_height , false , true , - 1. , * first_object ). width );
_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 ENABLE_GCODE_VIEWER
if ( print . config (). remaining_times . value )
2020-08-11 14:23:47 +02:00
_writeln ( file , GCodeProcessor :: First_Line_M73_Placeholder_Tag );
2020-08-10 14:22:05 +02:00
#else
2020-08-05 15:43:46 +02:00
if ( print . config (). remaining_times . value ) {
2018-09-07 16:05:10 +02:00
_writeln ( file , GCodeTimeEstimator :: Normal_First_M73_Output_Placeholder_Tag );
if ( m_silent_time_estimator_enabled )
_writeln ( file , GCodeTimeEstimator :: Silent_First_M73_Output_Placeholder_Tag );
}
2020-08-10 14:22:05 +02:00
#endif // ENABLE_GCODE_VIEWER
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 ();
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
#if ENABLE_GCODE_VIEWER
// adds tag for processor
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 ());
2020-05-07 10:49:12 +02:00
#else
if ( m_enable_analyzer )
// adds tag for analyzer
_write_format ( file , ";%s%d \n " , GCodeAnalyzer :: Extrusion_Role_Tag . c_str (), erCustom );
2020-04-02 12:03:18 +02:00
#endif // ENABLE_GCODE_VIEWER
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
// Initialize a motion planner for object-to-object travel moves.
2019-08-26 09:55:00 +02:00
m_avoid_crossing_perimeters . reset ();
2018-09-11 14:04:47 +02:00
if ( print . config (). avoid_crossing_perimeters . value ) {
2019-08-25 09:01:01 +02:00
m_avoid_crossing_perimeters . init_external_mp ( print );
2018-03-28 17:05:31 +02:00
print . throw_if_canceled ();
2017-05-03 18:28:22 +02:00
}
2019-08-23 13:06:29 +02:00
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
m_avoid_crossing_perimeters . use_external_mp_once = true ;
_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.
m_avoid_crossing_perimeters . disable_once = true ;
// 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 ));
this -> process_layer ( file , print , lrs , tool_ordering . tools_for_layer ( ltp . print_z ()), nullptr , * print_object_instance_sequential_active - object . instances (). data ());
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 );
// Beep for 500ms, tone 800Hz. Yet better, play some Morse.
_write ( file , this -> retract ());
_write ( file , "M300 S800 P500 \n " );
if ( bbox_prime . overlap ( bbox_print )) {
// Wait for the user to remove the priming extrusions, otherwise they would
// get covered by the print.
_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 " );
}
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 ();
2019-09-26 16:39:50 +02:00
this -> process_layer ( file , print , layer . second , layer_tools , & 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
#if ENABLE_GCODE_VIEWER
// adds tag for processor
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 ());
2020-05-07 10:49:12 +02:00
#else
if ( m_enable_analyzer )
// adds tag for analyzer
_write_format ( file , ";%s%d \n " , GCodeAnalyzer :: Extrusion_Role_Tag . c_str (), erCustom );
2020-04-02 12:03:18 +02:00
#endif // ENABLE_GCODE_VIEWER
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 ));
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
2020-08-10 14:22:05 +02:00
#if ENABLE_GCODE_VIEWER
2019-04-08 11:54:58 +02:00
if ( print . config (). remaining_times . value )
2020-08-11 14:23:47 +02:00
_writeln ( file , GCodeProcessor :: Last_Line_M73_Placeholder_Tag );
2020-08-10 14:22:05 +02:00
#else
if ( print . config (). remaining_times . value ) {
2019-04-08 11:54:58 +02:00
_writeln ( file , GCodeTimeEstimator :: Normal_Last_M73_Output_Placeholder_Tag );
if ( m_silent_time_estimator_enabled )
_writeln ( file , GCodeTimeEstimator :: Silent_Last_M73_Output_Placeholder_Tag );
}
2020-08-10 14:22:05 +02:00
#endif // ENABLE_GCODE_VIEWER
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-12-11 11:11:54 +01:00
// calculates estimated printing time
2020-08-05 15:43:46 +02:00
#if !ENABLE_GCODE_VIEWER
2018-07-20 12:05:08 +02:00
m_normal_time_estimator . calculate_time ( false );
2018-06-22 14:01:27 +02:00
if ( m_silent_time_estimator_enabled )
2018-07-20 12:05:08 +02:00
m_silent_time_estimator . calculate_time ( false );
2020-08-05 15:43:46 +02:00
#endif // !ENABLE_GCODE_VIEWER
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
#if !ENABLE_GCODE_VIEWER
m_normal_time_estimator , m_silent_time_estimator , m_silent_time_estimator_enabled ,
#endif // !ENABLE_GCODE_VIEWER
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 );
2020-08-11 14:23:47 +02:00
#if ENABLE_GCODE_VIEWER
_writeln ( file , GCodeProcessor :: Estimated_Printing_Time_Placeholder_Tag );
#else
2018-06-27 15:35:47 +02:00
_write_format ( file , "; estimated printing time (normal mode) = %s \n " , m_normal_time_estimator . get_time_dhms (). c_str ());
2018-06-22 14:01:27 +02:00
if ( m_silent_time_estimator_enabled )
2018-06-06 12:21:24 +02:00
_write_format ( file , "; estimated printing time (silent mode) = %s \n " , m_silent_time_estimator . get_time_dhms (). c_str ());
2020-08-11 15:44:32 +02:00
#endif // ENABLE_GCODE_VIEWER
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 {
return m_placeholder_parser . process ( templ , current_extruder_id , config_override );
} catch ( std :: runtime_error & err ) {
// Collect the names of failed template substitutions for error reporting.
2018-09-11 14:04:47 +02:00
m_placeholder_parser_failed_templates . insert ( name );
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 )
{
2020-10-06 13:18:25 +02:00
if ( print . config (). gcode_flavor . value == gcfMarlin && 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 ));
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 ),
int ( print . config (). machine_max_acceleration_extruding . values . front () + 0.5 ));
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 (
2020-09-01 18:33:56 +02:00
const CustomGCode :: Item * custom_gcode ,
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
#if ENABLE_GCODE_VIEWER
// add tag for processor
gcode += "; " + GCodeProcessor :: Color_Change_Tag + ",T" + std :: to_string ( m600_extruder_before_layer ) + " \n " ;
2020-05-07 10:49:12 +02:00
#else
// add tag for analyzer
gcode += "; " + GCodeAnalyzer :: Color_Change_Tag + ",T" + std :: to_string ( m600_extruder_before_layer ) + " \n " ;
2020-04-06 11:53:15 +02:00
// add tag for time estimator
2020-08-05 15:43:46 +02:00
gcode += "; " + GCodeTimeEstimator :: Color_Change_Tag + " \n " ;
#endif // ENABLE_GCODE_VIEWER
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
gcode += config . pause_print_gcode ; // pause print
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 {
2020-06-03 10:42:47 +02:00
gcode += config . color_change_gcode ; //ColorChangeCode;
2020-02-06 14:03:18 +01:00
gcode += " \n " ;
}
2020-01-14 10:31:18 +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
#if ENABLE_GCODE_VIEWER
// add tag for processor
gcode += "; " + GCodeProcessor :: Pause_Print_Tag + " \n " ;
2020-05-07 10:49:12 +02:00
#else
// add tag for analyzer
gcode += "; " + GCodeAnalyzer :: Pause_Print_Tag + " \n " ;
2020-04-06 11:53:15 +02:00
#endif // ENABLE_GCODE_VIEWER
//! 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 " ;
2020-08-05 15:43:46 +02:00
#if !ENABLE_GCODE_VIEWER
// add tag for time estimator
2020-02-20 17:32:46 +01:00
gcode += "; " + GCodeTimeEstimator :: Pause_Print_Tag + " \n " ;
2020-08-05 15:43:46 +02:00
#endif // !ENABLE_GCODE_VIEWER
2020-09-09 09:06:50 +02:00
gcode += config . pause_print_gcode ;
2020-08-05 15:43:46 +02:00
}
2020-06-03 10:42:47 +02:00
else
2020-01-14 10:31:18 +01:00
{
2020-04-06 11:53:15 +02:00
#if ENABLE_GCODE_VIEWER
// add tag for processor
gcode += "; " + GCodeProcessor :: Custom_Code_Tag + " \n " ;
2020-05-07 10:49:12 +02:00
#else
// add tag for analyzer
gcode += "; " + GCodeAnalyzer :: Custom_Code_Tag + " \n " ;
2020-04-06 11:53:15 +02:00
#endif // ENABLE_GCODE_VIEWER
// add tag for time estimator
2020-01-14 10:31:18 +01:00
//gcode += "; " + GCodeTimeEstimator::Custom_Code_Tag + "\n";
2020-06-03 10:42:47 +02:00
if ( gcode_type == CustomGCode :: Template ) // Template Cistom Gcode
gcode += config . template_custom_gcode ;
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 std :: vector < GCode :: LayerToPrint > & /*layers */ ,
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 ;
2020-01-14 14:24:38 +01:00
if ( skirt_done . empty () && print . has_skirt () && ! print . skirt (). entities . empty ()) {
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 std :: vector < GCode :: LayerToPrint > & layers ,
const LayerTools & layer_tools ,
// First non-empty support layer.
const SupportLayer * support_layer ,
// 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 ;
if ( print . has_skirt () && ! print . skirt (). entities . empty () &&
// Not enough skirt layers printed yet.
//FIXME infinite or high skirt does not make sense for sequential print!
( skirt_done . size () < ( size_t ) print . config (). skirt_height . value || print . has_infinite_skirt ()) &&
2020-01-14 14:24:38 +01:00
// This print_z has not been extruded yet (sequential print)
2020-01-14 10:31:18 +01:00
skirt_done . back () < layer_tools . print_z - EPSILON &&
// and this layer is an object layer, or it is a raft layer.
2020-02-10 16:37:38 +01:00
( layer_tools . has_object || support_layer -> id () < ( size_t ) support_layer -> object () -> config (). raft_layers . value )) {
2020-01-14 16:38:34 +01:00
#if 0
// 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);
#else
2020-09-01 18:33:56 +02:00
// Prime all extruders planned for this layer, see
2020-01-14 16:38:34 +01:00
// https://github.com/prusa3d/PrusaSlicer/issues/469#issuecomment-322450619
skirt_loops_per_extruder_all_printing ( print , layer_tools , skirt_loops_per_extruder_out );
#endif
2020-01-14 10:31:18 +01:00
assert ( ! skirt_done . empty ());
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 ;
}
} // 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 ,
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.
2017-05-10 11:25:57 +02:00
if ( m_spiral_vase && layers . size () == 1 && support_layer == nullptr ) {
2019-06-25 13:06:04 +02:00
bool enable = ( layer . id () > 0 || print . config (). brim_width . value == 0. ) && ( 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 ())
2019-06-25 13:06:04 +02:00
if ( size_t ( layer_region -> region () -> config (). bottom_solid_layers . value ) > layer . id () ||
layer_region -> perimeters . items_count () > 1u ||
2017-05-03 18:28:22 +02:00
layer_region -> fills . items_count () > 0 ) {
enable = false ;
break ;
}
}
m_spiral_vase -> enable = enable ;
}
// If we're going to apply spiralvase to this layer, disable loop clipping
2017-05-10 11:25:57 +02:00
m_enable_loop_clipping = ! m_spiral_vase || ! m_spiral_vase -> enable ;
2020-09-01 18:33:56 +02:00
2017-05-10 11:25:57 +02:00
std :: string gcode ;
2020-07-30 13:49:57 +02:00
#if ENABLE_GCODE_VIEWER
2020-08-18 12:37:07 +02:00
// add tag for processor
gcode += "; " + GCodeProcessor :: Layer_Change_Tag + " \n " ;
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 ;
sprintf ( buf , ";%s%g \n " , GCodeProcessor :: Height_Tag . c_str (), height );
gcode += buf ;
// update caches
m_last_layer_z = static_cast < float > ( print_z );
m_last_height = height ;
#endif // ENABLE_GCODE_VIEWER
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 ;
config . set_key_value ( "layer_num" , new ConfigOptionInt ( m_layer_index + 1 ));
config . set_key_value ( "layer_z" , new ConfigOptionFloat ( print_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 " ;
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.
2020-06-03 10:42:47 +02:00
gcode += ProcessLayer :: emit_custom_gcode_per_print_z ( layer_tools . custom_gcode , 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 ?
2020-01-14 16:38:34 +01:00
Skirt :: make_skirt_loops_per_extruder_1st_layer ( print , layers , layer_tools , m_skirt_done ) :
2020-02-10 16:37:38 +01:00
Skirt :: make_skirt_loops_per_extruder_other_layers ( print , layers , layer_tools , support_layer , 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 ;
2018-09-11 14:04:47 +02:00
const PrintRegion & region = * print . regions ()[ 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 ())
islands [ island_idx ]. by_region . assign ( print . regions (). size (), ObjectByExtruder :: Island :: Region ());
islands [ island_idx ]. by_region [ region_id ]. append ( entity_type , extrusions , entity_overrides );
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
#if ENABLE_GCODE_VIEWER
// 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 ;
#else
2018-05-07 14:23:07 +02:00
// let analyzer tag generator aware of a role type change
if ( m_enable_analyzer && layer_tools . has_wipe_tower && m_wipe_tower )
m_last_analyzer_extrusion_role = erWipeTower ;
2020-05-07 10:49:12 +02:00
#endif // ENABLE_GCODE_VIEWER
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. );
m_avoid_crossing_perimeters . use_external_mp = true ;
Flow layer_skirt_flow ( print . skirt_flow ());
2020-01-14 14:24:38 +01:00
layer_skirt_flow . 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 ) {
path . height = layer_skirt_flow . height ;
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-01-14 10:31:18 +01:00
m_avoid_crossing_perimeters . use_external_mp = false ;
// 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 )
m_avoid_crossing_perimeters . disable_once = true ;
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. );
2017-05-10 11:25:57 +02:00
m_avoid_crossing_perimeters . use_external_mp = true ;
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 ;
m_avoid_crossing_perimeters . use_external_mp = false ;
// Allow a straight travel move to the first object point.
m_avoid_crossing_perimeters . disable_once = true ;
}
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-01-03 14:05:56 +01:00
m_avoid_crossing_perimeters . init_layer_mp ( union_ex ( m_layer -> lslices , true ));
2018-06-20 12:52:00 +02:00
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 )
m_avoid_crossing_perimeters . use_external_mp_once = true ;
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
}
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 )
gcode = m_spiral_vase -> process_layer ( 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 )
2017-06-23 10:13:09 +02:00
gcode = m_cooling_buffer -> process_layer ( gcode , layer . id ());
2015-07-01 23:00:52 +02:00
2020-05-07 10:49:12 +02:00
#if !ENABLE_GCODE_VIEWER
2019-11-06 16:06:21 +01:00
// add tag for analyzer
if ( gcode . find ( GCodeAnalyzer :: Pause_Print_Tag ) != gcode . npos )
gcode += " \n ; " + GCodeAnalyzer :: End_Pause_Print_Or_Custom_Code_Tag + " \n " ;
else if ( gcode . find ( GCodeAnalyzer :: Custom_Code_Tag ) != gcode . npos )
gcode += " \n ; " + GCodeAnalyzer::End_Pause_Print_Or_Custom_Code_Tag + " \ n ";
2020-05-07 10:49:12 +02:00
#endif // !ENABLE_GCODE_VIEWER
2019-11-06 16:06:21 +01: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
#if ENABLE_GCODE_VIEWER
BOOST_LOG_TRIVIAL ( trace ) << "Exported layer " << layer . id () << " print_z " << print_z <<
log_memory_info ();
#else
2020-08-05 15:43:46 +02:00
BOOST_LOG_TRIVIAL ( trace ) << "Exported layer " << layer . id () << " print_z " << print_z <<
2018-12-18 15:55:45 +01:00
", time estimator memory: " <<
2020-09-11 15:19:23 +02:00
format_memsize_MB ( m_normal_time_estimator . memory_used () + ( m_silent_time_estimator_enabled ? m_silent_time_estimator . memory_used () : 0 )) <<
", analyzer memory: " <<
format_memsize_MB ( m_analyzer . memory_used ()) <<
log_memory_info ();
#endif // ENABLE_GCODE_VIEWER
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 ;
Point seam = m_seam_placer . get_seam ( m_layer -> id (), seam_position , loop ,
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 ()) {
m_config . apply ( print . regions ()[ & region - & by_region . front ()] -> config ());
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 ()) {
m_config . apply ( print . regions ()[ & region - & by_region . front ()] -> config ());
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
{
std :: string gcode ;
if ( ! support_fills . entities . empty ()) {
2017-04-07 17:37:30 +02:00
const char * support_label = "support material" ;
const char * support_interface_label = "support material interface" ;
2017-05-03 18:28:22 +02:00
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 );
assert ( multipath != nullptr );
if ( multipath )
2017-05-10 11:25:57 +02:00
gcode += this -> extrude_multi_path ( * multipath , label , speed );
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
#if ENABLE_GCODE_VIEWER
const char * gcode = what ;
#else
2018-01-08 13:44:10 +01:00
// apply analyzer, if enabled
const char * gcode = m_enable_analyzer ? m_analyzer . process_gcode ( what ). c_str () : what ;
2020-05-07 10:49:12 +02:00
#endif // !ENABLE_GCODE_VIEWER
2018-01-08 13:44:10 +01:00
// writes string to file
fwrite ( gcode , 1 , :: strlen ( gcode ), file );
2020-08-05 15:43:46 +02:00
#if !ENABLE_GCODE_VIEWER
2018-01-08 13:44:10 +01:00
// updates time estimator and gcode lines vector
2018-06-27 15:35:47 +02:00
m_normal_time_estimator . add_gcode_block ( gcode );
2018-06-22 14:01:27 +02:00
if ( m_silent_time_estimator_enabled )
2018-06-06 12:21:24 +02:00
m_silent_time_estimator . add_gcode_block ( gcode );
2020-08-05 15:43:46 +02:00
#endif // !ENABLE_GCODE_VIEWER
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 ( this -> on_first_layer ())
2017-05-03 18:28:22 +02:00
speed = m_config . get_abs_value ( "first_layer_speed" , speed );
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 ;
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-05-07 10:49:12 +02:00
#if ENABLE_GCODE_VIEWER
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-05-07 10:49:12 +02:00
#else
2020-08-17 15:59:36 +02:00
if ( m_enable_analyzer ) {
2020-07-22 10:37:25 +02:00
// PrusaMultiMaterial::Writer may generate GCodeAnalyzer::Height_Tag and GCodeAnalyzer::Width_Tag lines without updating m_last_height and m_last_width
// so, if the last role was erWipeTower we force export of GCodeAnalyzer::Height_Tag and GCodeAnalyzer::Width_Tag lines
2019-02-28 11:26:27 +01:00
bool last_was_wipe_tower = ( m_last_analyzer_extrusion_role == erWipeTower );
2020-05-07 10:49:12 +02:00
#endif // ENABLE_GCODE_VIEWER
2019-09-09 10:18:17 +02:00
char buf [ 64 ];
2019-02-28 11:26:27 +01:00
2020-05-07 10:49:12 +02:00
#if ENABLE_GCODE_VIEWER
2020-07-22 10:37:25 +02:00
if ( path . role () != m_last_processor_extrusion_role ) {
2020-05-07 10:49:12 +02:00
m_last_processor_extrusion_role = path . role ();
2020-07-28 09:48:55 +02:00
sprintf ( buf , ";%s%s \n " , GCodeProcessor :: Extrusion_Role_Tag . c_str (), ExtrusionEntity :: role_to_string ( m_last_processor_extrusion_role ). c_str ());
2020-05-07 10:49:12 +02:00
gcode += buf ;
}
2020-08-17 10:06:41 +02:00
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
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 14:37:26 +02:00
if ( last_was_wipe_tower || m_last_width != path . width ) {
m_last_width = path . width ;
sprintf ( buf , ";%s%g \n " , GCodeProcessor :: Width_Tag . c_str (), m_last_width );
gcode += buf ;
}
2020-08-17 10:06:41 +02:00
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
2020-08-17 15:59:36 +02:00
if ( last_was_wipe_tower || std :: abs ( m_last_height - path . height ) > EPSILON ) {
m_last_height = path . height ;
sprintf ( buf , ";%s%g \n " , GCodeProcessor :: Height_Tag . c_str (), m_last_height );
gcode += buf ;
}
2020-05-07 10:49:12 +02:00
#else
2020-07-22 10:37:25 +02:00
if ( path . role () != m_last_analyzer_extrusion_role ) {
2018-05-07 14:23:07 +02:00
m_last_analyzer_extrusion_role = path . role ();
sprintf ( buf , ";%s%d \n " , GCodeAnalyzer :: Extrusion_Role_Tag . c_str (), int ( m_last_analyzer_extrusion_role ));
gcode += buf ;
}
2020-07-22 10:37:25 +02:00
if ( last_was_wipe_tower || ( m_last_mm3_per_mm != path . mm3_per_mm )) {
2018-01-08 13:44:10 +01:00
m_last_mm3_per_mm = path . mm3_per_mm ;
2020-05-07 10:49:12 +02:00
sprintf ( buf , ";%s%f \n " , GCodeAnalyzer :: Mm3_Per_Mm_Tag . c_str (), m_last_mm3_per_mm );
gcode += buf ;
2018-01-08 13:44:10 +01:00
}
2020-07-30 13:49:57 +02:00
if ( last_was_wipe_tower || m_last_width != path . width ) {
2018-01-08 13:44:10 +01:00
m_last_width = path . width ;
2020-05-07 10:49:12 +02:00
sprintf ( buf , ";%s%f \n " , GCodeAnalyzer :: Width_Tag . c_str (), m_last_width );
2020-07-30 13:49:57 +02:00
gcode += buf ;
2018-01-08 13:44:10 +01:00
}
2020-08-17 14:37:26 +02:00
2020-07-30 13:49:57 +02:00
if ( last_was_wipe_tower || m_last_height != path . height ) {
m_last_height = path . height ;
2020-05-07 10:49:12 +02:00
sprintf ( buf , ";%s%f \n " , GCodeAnalyzer :: Height_Tag . c_str (), m_last_height );
gcode += buf ;
2018-01-08 13:44:10 +01:00
}
}
2020-07-30 13:49:57 +02:00
#endif // ENABLE_GCODE_VIEWER
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. */
2015-07-02 15:12:04 +02:00
Polyline travel ;
travel . append ( this -> last_pos ());
travel . append ( 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
bool needs_retraction = this -> needs_retraction ( travel , role );
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
&& ! m_avoid_crossing_perimeters . disable_once ) {
travel = m_avoid_crossing_perimeters . travel_to ( * this , point );
2020-09-01 18:33:56 +02:00
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
2017-05-03 18:28:22 +02:00
m_avoid_crossing_perimeters . disable_once = false ;
m_avoid_crossing_perimeters . use_external_mp_once = false ;
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 ;
2017-03-29 17:45:38 +02:00
if ( needs_retraction )
gcode += this -> retract ();
else
// 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)
Lines lines = travel . lines ();
2019-01-14 19:57:41 +01:00
if ( ! lines . empty ()) {
for ( const Line & line : lines )
2020-09-01 18:33:56 +02:00
gcode += m_writer . travel_to_xy ( this -> point_to_gcode ( line . b ), comment );
2019-01-14 19:57:41 +01:00
this -> set_last_pos ( lines . back (). b );
}
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 ));
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