2015-07-01 18:14:05 +00:00
# include "GCode.hpp"
2015-07-01 21:14:40 +00:00
# include "ExtrusionEntity.hpp"
2016-09-12 14:25:15 +00:00
# include "EdgeGrid.hpp"
2017-05-03 16:28:22 +00:00
# include "Geometry.hpp"
2017-09-12 07:01:48 +00:00
# include "GCode/PrintExtents.hpp"
2017-05-16 11:45:28 +00:00
# include "GCode/WipeTowerPrusaMM.hpp"
2017-10-30 17:15:41 +00:00
# include "Utils.hpp"
2017-05-03 16:28:22 +00:00
2015-07-02 16:57:40 +00:00
# include <algorithm>
2015-07-02 18:24:16 +00:00
# include <cstdlib>
2015-09-30 13:22:49 +00:00
# include <math.h>
2015-07-01 18:14:05 +00:00
2017-05-03 16:28:22 +00:00
# include <boost/algorithm/string.hpp>
# include <boost/algorithm/string/find.hpp>
# include <boost/foreach.hpp>
2018-09-17 10:01:02 +00:00
# include <boost/filesystem.hpp>
2018-02-14 21:25:09 +00:00
# include <boost/log/trivial.hpp>
2017-05-03 16:28:22 +00:00
2017-08-03 15:31:31 +00:00
# include <boost/nowide/iostream.hpp>
# include <boost/nowide/cstdio.hpp>
# include <boost/nowide/cstdlib.hpp>
2016-09-12 14:25:15 +00:00
# include "SVG.hpp"
2018-01-03 16:29:49 +00:00
# include <Shiny/Shiny.h>
2016-09-12 14:25:15 +00: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 18:14:05 +00:00
namespace Slic3r {
2017-11-30 15:01:47 +00:00
// Only add a newline in case the current G-code does not end with a newline.
static inline void check_add_eol ( std : : string & gcode )
{
if ( ! gcode . empty ( ) & & gcode . back ( ) ! = ' \n ' )
gcode + = ' \n ' ;
}
2015-07-01 18:14:05 +00:00
2017-06-02 11:33:19 +00: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 )
2015-07-01 18:14:05 +00:00
{
2017-06-02 11:33:19 +00:00
// 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.
2017-05-05 07:59:56 +00:00
bool use_external = this - > use_external_mp | | this - > use_external_mp_once ;
2018-08-17 13:53:43 +00:00
Point scaled_origin = use_external ? Point : : new_scale ( gcodegen . origin ( ) ( 0 ) , gcodegen . origin ( ) ( 1 ) ) : Point ( 0 , 0 ) ;
2017-05-05 07:59:56 +00:00
Polyline result = ( use_external ? m_external_mp . get ( ) : m_layer_mp . get ( ) ) - >
shortest_path ( gcodegen . last_pos ( ) + scaled_origin , point + scaled_origin ) ;
2017-06-02 11:33:19 +00:00
if ( use_external )
Removed Point::scale(),translate(),coincides_with(),distance_to(),
distance_to_squared(),perp_distance_to(),negative(),vector_to(),
translate(), distance_to() etc,
replaced with the Eigen equivalents.
2018-08-17 12:14:24 +00:00
result . translate ( - scaled_origin ) ;
2017-05-05 07:59:56 +00:00
return result ;
2015-07-01 18:14:05 +00:00
}
2017-05-03 16:28:22 +00:00
std : : string OozePrevention : : pre_toolchange ( GCode & gcodegen )
2015-07-02 13:02:20 +00:00
{
std : : string gcode ;
// move to the nearest standby point
if ( ! this - > standby_points . empty ( ) ) {
// get current position in print coordinates
2018-08-21 15:43:05 +00:00
Vec3d writer_pos = gcodegen . writer ( ) . get_position ( ) ;
2018-08-17 13:53:43 +00:00
Point pos = Point : : new_scale ( writer_pos ( 0 ) , writer_pos ( 1 ) ) ;
2015-07-02 13:02:20 +00:00
// 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 . */
2018-08-21 15:43:05 +00:00
gcode + = gcodegen . writer ( ) . travel_to_xy ( unscale ( standby_point ) ,
2015-07-02 13:02:20 +00:00
" move to standby position " ) ;
}
2017-05-03 16:28:22 +00:00
if ( gcodegen . config ( ) . standby_temperature_delta . value ! = 0 ) {
2015-07-02 13:02:20 +00:00
// we assume that heating is always slower than cooling, so no need to block
2017-05-03 16:28:22 +00:00
gcode + = gcodegen . writer ( ) . set_temperature
( this - > _get_temp ( gcodegen ) + gcodegen . config ( ) . standby_temperature_delta . value , false ) ;
2015-07-02 13:02:20 +00:00
}
return gcode ;
}
2017-04-26 12:24:31 +00:00
std : : string OozePrevention : : post_toolchange ( GCode & gcodegen )
2015-07-02 13:02:20 +00:00
{
2017-05-03 16:28:22 +00:00
return ( gcodegen . config ( ) . standby_temperature_delta . value ! = 0 ) ?
gcodegen . writer ( ) . set_temperature ( this - > _get_temp ( gcodegen ) , true ) :
2017-04-26 12:24:31 +00:00
std : : string ( ) ;
2015-07-02 13:02:20 +00:00
}
int
OozePrevention : : _get_temp ( GCode & gcodegen )
{
2017-05-03 16:28:22 +00:00
return ( gcodegen . layer ( ) ! = NULL & & gcodegen . layer ( ) - > id ( ) = = 0 )
2017-06-22 10:59:23 +00:00
? gcodegen . config ( ) . first_layer_temperature . get_at ( gcodegen . writer ( ) . extruder ( ) - > id ( ) )
: gcodegen . config ( ) . temperature . get_at ( gcodegen . writer ( ) . extruder ( ) - > id ( ) ) ;
2015-07-02 13:02:20 +00:00
}
2019-01-14 18:57:41 +00:00
std : : string Wipe : : wipe ( GCode & gcodegen , bool toolchange )
2015-07-01 21:00:52 +00:00
{
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 . */
2017-05-03 16:28:22 +00:00
double wipe_speed = gcodegen . writer ( ) . config . travel_speed . value * 0.8 ;
2015-07-01 21:00:52 +00:00
// get the retraction length
double length = toolchange
2017-05-03 16:28:22 +00:00
? gcodegen . writer ( ) . extruder ( ) - > retract_length_toolchange ( )
: gcodegen . writer ( ) . extruder ( ) - > retract_length ( ) ;
2017-05-22 16:16:35 +00:00
// Shorten the retraction length by the amount already retracted before wipe.
length * = ( 1. - gcodegen . writer ( ) . extruder ( ) - > retract_before_wipe ( ) ) ;
2015-07-01 21:00:52 +00:00
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 ? */
2017-05-03 16:28:22 +00:00
double wipe_dist = scale_ ( length / gcodegen . writer ( ) . extruder ( ) - > retract_speed ( ) * wipe_speed ) ;
2015-07-01 21:00:52 +00:00
/* 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
2019-01-14 18:57:41 +00:00
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 ( ) ) ;
2015-07-01 21:00:52 +00:00
}
// prevent wiping again on same path
this - > reset_path ( ) ;
}
return gcode ;
}
2017-05-19 17:24:21 +00:00
static inline Point wipe_tower_point_to_object_point ( GCode & gcodegen , const WipeTower : : xy & wipe_tower_pt )
{
2018-08-17 13:53:43 +00:00
return Point ( scale_ ( wipe_tower_pt . x - gcodegen . origin ( ) ( 0 ) ) , scale_ ( wipe_tower_pt . y - gcodegen . origin ( ) ( 1 ) ) ) ;
2017-05-19 17:24:21 +00:00
}
2017-05-25 20:27:53 +00:00
std : : string WipeTowerIntegration : : append_tcr ( GCode & gcodegen , const WipeTower : : ToolChangeResult & tcr , int new_extruder_id ) const
2017-05-18 14:53:19 +00:00
{
std : : string gcode ;
2018-07-27 13:56:27 +00:00
// Toolchangeresult.gcode assumes the wipe tower corner is at the origin
// We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position
float alpha = m_wipe_tower_rotation / 180.f * M_PI ;
WipeTower : : xy start_pos = tcr . start_pos ;
WipeTower : : xy end_pos = tcr . end_pos ;
start_pos . rotate ( alpha ) ;
start_pos . translate ( m_wipe_tower_pos ) ;
end_pos . rotate ( alpha ) ;
end_pos . translate ( m_wipe_tower_pos ) ;
std : : string tcr_rotated_gcode = rotate_wipe_tower_moves ( tcr . gcode , tcr . start_pos , m_wipe_tower_pos , alpha ) ;
2017-11-30 15:01:47 +00:00
// Disable linear advance for the wipe tower operations.
gcode + = " M900 K0 \n " ;
2017-05-25 20:27:53 +00: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 (
2018-07-27 13:56:27 +00:00
wipe_tower_point_to_object_point ( gcodegen , start_pos ) ,
2017-05-25 20:27:53 +00:00
erMixed ,
" Travel to a Wipe Tower " ) ;
gcode + = gcodegen . unretract ( ) ;
// Let the tool change be executed by the wipe tower class.
// Inform the G-code writer about the changes done behind its back.
2018-07-27 13:56:27 +00:00
gcode + = tcr_rotated_gcode ;
2017-05-25 20:27:53 +00:00
// Let the m_writer know the current extruder_id, but ignore the generated G-code.
2017-12-20 11:03:54 +00:00
if ( new_extruder_id > = 0 & & gcodegen . writer ( ) . need_toolchange ( new_extruder_id ) )
2017-05-25 20:27:53 +00:00
gcodegen . writer ( ) . toolchange ( new_extruder_id ) ;
2017-12-20 11:03:54 +00:00
// Always append the filament start G-code even if the extruder did not switch,
// because the wipe tower resets the linear advance and we want it to be re-enabled.
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.
gcodegen . placeholder_parser ( ) . set ( " current_extruder " , new_extruder_id ) ;
2019-01-07 14:12:40 +00:00
DynamicConfig config ;
config . set_key_value ( " filament_extruder_id " , new ConfigOptionInt ( new_extruder_id ) ) ;
gcode + = gcodegen . placeholder_parser_process ( " start_filament_gcode " , start_filament_gcode , new_extruder_id , & config ) ;
2017-12-20 11:03:54 +00:00
check_add_eol ( gcode ) ;
2017-11-30 15:01:47 +00:00
}
2017-05-25 20:27:53 +00:00
// A phony move to the end position at the wipe tower.
2018-08-21 19:05:24 +00:00
gcodegen . writer ( ) . travel_to_xy ( Vec2d ( end_pos . x , end_pos . y ) ) ;
2018-07-27 13:56:27 +00:00
gcodegen . set_last_pos ( wipe_tower_point_to_object_point ( gcodegen , end_pos ) ) ;
2017-05-25 20:27:53 +00:00
// Prepare a future wipe.
gcodegen . m_wipe . path . points . clear ( ) ;
if ( new_extruder_id > = 0 ) {
// Start the wipe at the current position.
2018-07-27 13:56:27 +00:00
gcodegen . m_wipe . path . points . emplace_back ( wipe_tower_point_to_object_point ( gcodegen , end_pos ) ) ;
2017-05-25 20:27:53 +00:00
// Wipe end point: Wipe direction away from the closer tower edge to the further tower edge.
gcodegen . m_wipe . path . points . emplace_back ( wipe_tower_point_to_object_point ( gcodegen ,
2018-07-27 13:56:27 +00:00
WipeTower : : xy ( ( std : : abs ( m_left - end_pos . x ) < std : : abs ( m_right - end_pos . x ) ) ? m_right : m_left ,
end_pos . y ) ) ) ;
2017-05-18 14:53:19 +00:00
}
2017-05-25 20:27:53 +00:00
// Let the planner know we are traveling between objects.
gcodegen . m_avoid_crossing_perimeters . use_external_mp_once = true ;
2017-05-18 14:53:19 +00:00
return gcode ;
}
2018-07-27 13:56:27 +00: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 : : rotate_wipe_tower_moves ( const std : : string & gcode_original , const WipeTower : : xy & start_pos , const WipeTower : : xy & translation , float angle ) const
{
std : : istringstream gcode_str ( gcode_original ) ;
std : : string gcode_out ;
std : : string line ;
WipeTower : : xy pos = start_pos ;
WipeTower : : xy transformed_pos ;
WipeTower : : xy old_pos ( - 1000.1f , - 1000.1f ) ;
while ( gcode_str ) {
std : : getline ( gcode_str , line ) ; // we read the gcode line by line
if ( line . find ( " G1 " ) = = 0 ) {
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 ' )
line_str > > pos . x ;
else
if ( ch = = ' Y ' )
line_str > > pos . y ;
else
line_out < < ch ;
}
transformed_pos = pos ;
transformed_pos . rotate ( angle ) ;
transformed_pos . translate ( translation ) ;
if ( transformed_pos ! = old_pos ) {
line = line_out . str ( ) ;
char buf [ 2048 ] = " G1 " ;
if ( transformed_pos . x ! = old_pos . x )
sprintf ( buf + strlen ( buf ) , " X%.3f " , transformed_pos . x ) ;
if ( transformed_pos . y ! = old_pos . y )
sprintf ( buf + strlen ( buf ) , " Y%.3f " , transformed_pos . y ) ;
line . replace ( line . find ( " G1 " ) , 3 , buf ) ;
old_pos = transformed_pos ;
}
}
gcode_out + = line + " \n " ;
}
return gcode_out ;
}
2017-09-01 15:30:18 +00:00
std : : string WipeTowerIntegration : : prime ( GCode & gcodegen )
{
assert ( m_layer_idx = = 0 ) ;
std : : string gcode ;
if ( & m_priming ! = nullptr & & ! m_priming . extrusions . empty ( ) ) {
2017-11-30 15:01:47 +00:00
// Disable linear advance for the wipe tower operations.
gcode + = " M900 K0 \n " ;
2017-09-01 15:30:18 +00:00
// Let the tool change be executed by the wipe tower class.
// Inform the G-code writer about the changes done behind its back.
gcode + = m_priming . gcode ;
// Let the m_writer know the current extruder_id, but ignore the generated G-code.
2017-11-30 15:01:47 +00:00
unsigned int current_extruder_id = m_priming . extrusions . back ( ) . tool ;
gcodegen . writer ( ) . toolchange ( current_extruder_id ) ;
gcodegen . placeholder_parser ( ) . set ( " current_extruder " , current_extruder_id ) ;
2017-09-01 15:30:18 +00:00
// A phony move to the end position at the wipe tower.
2018-08-21 19:05:24 +00:00
gcodegen . writer ( ) . travel_to_xy ( Vec2d ( m_priming . end_pos . x , m_priming . end_pos . y ) ) ;
2017-09-01 15:30:18 +00:00
gcodegen . set_last_pos ( wipe_tower_point_to_object_point ( gcodegen , m_priming . end_pos ) ) ;
// Prepare a future wipe.
gcodegen . m_wipe . path . points . clear ( ) ;
// Start the wipe at the current position.
gcodegen . m_wipe . path . points . emplace_back ( wipe_tower_point_to_object_point ( gcodegen , m_priming . end_pos ) ) ;
// Wipe end point: Wipe direction away from the closer tower edge to the further tower edge.
gcodegen . m_wipe . path . points . emplace_back ( wipe_tower_point_to_object_point ( gcodegen ,
WipeTower : : xy ( ( std : : abs ( m_left - m_priming . end_pos . x ) < std : : abs ( m_right - m_priming . end_pos . x ) ) ? m_right : m_left ,
m_priming . end_pos . y ) ) ) ;
}
return gcode ;
}
2017-05-25 20:27:53 +00:00
std : : string WipeTowerIntegration : : tool_change ( GCode & gcodegen , int extruder_id , bool finish_layer )
2017-05-18 14:53:19 +00:00
{
std : : string gcode ;
2017-05-25 20:27:53 +00:00
assert ( m_layer_idx > = 0 & & m_layer_idx < = m_tool_changes . size ( ) ) ;
if ( ! m_brim_done | | gcodegen . writer ( ) . need_toolchange ( extruder_id ) | | finish_layer ) {
if ( m_layer_idx < m_tool_changes . size ( ) ) {
assert ( m_tool_change_idx < m_tool_changes [ m_layer_idx ] . size ( ) ) ;
gcode + = append_tcr ( gcodegen , m_tool_changes [ m_layer_idx ] [ m_tool_change_idx + + ] , extruder_id ) ;
}
m_brim_done = true ;
2017-05-18 14:53:19 +00:00
}
return gcode ;
}
2017-05-25 20:27:53 +00:00
// Print is finished. Now it remains to unload the filament safely with ramming over the wipe tower.
std : : string WipeTowerIntegration : : finalize ( GCode & gcodegen )
2017-05-18 14:53:19 +00:00
{
2017-05-25 20:27:53 +00:00
std : : string gcode ;
2018-08-17 13:53:43 +00:00
if ( std : : abs ( gcodegen . writer ( ) . get_position ( ) ( 2 ) - m_final_purge . print_z ) > EPSILON )
2017-05-25 20:27:53 +00:00
gcode + = gcodegen . change_layer ( m_final_purge . print_z ) ;
gcode + = append_tcr ( gcodegen , m_final_purge , - 1 ) ;
2017-05-18 14:53:19 +00:00
return gcode ;
}
2017-06-22 10:59:23 +00:00
# define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.extruder()->id())
2015-07-02 12:31:21 +00:00
2017-05-23 15:09:43 +00: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.
std : : vector < GCode : : LayerToPrint > GCode : : collect_layers_to_print ( const PrintObject & object )
{
std : : vector < GCode : : LayerToPrint > layers_to_print ;
2018-09-11 12:04:47 +00:00
layers_to_print . reserve ( object . layers ( ) . size ( ) + object . support_layers ( ) . size ( ) ) ;
2017-05-23 15:09:43 +00:00
// Pair the object layers with the support layers by z.
size_t idx_object_layer = 0 ;
size_t idx_support_layer = 0 ;
2018-09-11 12:04:47 +00:00
while ( idx_object_layer < object . layers ( ) . size ( ) | | idx_support_layer < object . support_layers ( ) . size ( ) ) {
2017-05-23 15:09:43 +00:00
LayerToPrint layer_to_print ;
2018-09-11 12:04:47 +00: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 15:09:43 +00: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 ;
- - idx_support_layer ;
} else if ( layer_to_print . support_layer - > print_z < layer_to_print . object_layer - > print_z - EPSILON ) {
layer_to_print . object_layer = nullptr ;
- - idx_object_layer ;
}
}
layers_to_print . emplace_back ( layer_to_print ) ;
}
return layers_to_print ;
}
2017-05-24 13:20:20 +00:00
// Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z
// will be printed for all objects at once.
// Return a list of <print_z, per object LayerToPrint> items.
2017-05-23 15:09:43 +00:00
std : : vector < std : : pair < coordf_t , std : : vector < GCode : : LayerToPrint > > > GCode : : collect_layers_to_print ( const Print & print )
{
struct OrderingItem {
coordf_t print_z ;
size_t object_idx ;
size_t layer_idx ;
} ;
2018-07-23 13:58:08 +00:00
2018-11-08 13:23:17 +00:00
std : : vector < std : : vector < LayerToPrint > > per_object ( print . objects ( ) . size ( ) , std : : vector < LayerToPrint > ( ) ) ;
2017-05-23 15:09:43 +00:00
std : : vector < OrderingItem > ordering ;
2018-11-08 13:23:17 +00:00
for ( size_t i = 0 ; i < print . objects ( ) . size ( ) ; + + i ) {
per_object [ i ] = collect_layers_to_print ( * print . objects ( ) [ i ] ) ;
2017-05-23 15:09:43 +00:00
OrderingItem ordering_item ;
ordering_item . object_idx = i ;
2017-05-24 13:20:20 +00:00
ordering . reserve ( ordering . size ( ) + per_object [ i ] . size ( ) ) ;
const LayerToPrint & front = per_object [ i ] . front ( ) ;
2017-05-23 15:09:43 +00:00
for ( const LayerToPrint & ltp : per_object [ i ] ) {
2017-05-24 13:20:20 +00:00
ordering_item . print_z = ltp . print_z ( ) ;
2017-05-23 15:09:43 +00:00
ordering_item . layer_idx = & ltp - & front ;
ordering . emplace_back ( ordering_item ) ;
}
}
std : : sort ( ordering . begin ( ) , ordering . end ( ) , [ ] ( const OrderingItem & oi1 , const OrderingItem & oi2 ) { return oi1 . print_z < oi2 . print_z ; } ) ;
std : : vector < std : : pair < coordf_t , std : : vector < LayerToPrint > > > layers_to_print ;
// 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 ;
for ( ; j < ordering . size ( ) & & ordering [ j ] . print_z < = zmax ; + + j ) ;
// 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.
merged . first = 0.5 * ( ordering [ i ] . print_z + ordering [ j - 1 ] . print_z ) ;
2018-11-08 13:23:17 +00:00
merged . second . assign ( print . objects ( ) . size ( ) , LayerToPrint ( ) ) ;
2018-07-23 13:58:08 +00:00
for ( ; i < j ; + + i ) {
2017-05-23 15:09:43 +00:00
const OrderingItem & oi = ordering [ i ] ;
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 ;
}
2018-02-14 19:35:59 +00:00
void GCode : : do_export ( Print * print , const char * path , GCodePreviewData * preview_data )
2017-08-03 15:31:31 +00:00
{
2018-01-03 16:29:49 +00:00
PROFILE_CLEAR ( ) ;
2018-09-17 10:01:02 +00: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 07:28:00 +00:00
print - > set_started ( psGCodeExport ) ;
2018-12-18 10:31:41 +00:00
BOOST_LOG_TRIVIAL ( info ) < < " Exporting G-code... " < < log_memory_info ( ) ;
2018-02-14 21:25:09 +00:00
2017-08-03 15:31:31 +00: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 )
2017-12-05 14:54:24 +00:00
throw std : : runtime_error ( std : : string ( " G-code export to " ) + path + " failed. \n Cannot open the file for writing. \n " ) ;
2017-08-03 15:31:31 +00:00
2018-12-18 16:34:21 +00:00
m_enable_analyzer = preview_data ! = nullptr ;
2018-03-28 15:05:31 +00:00
try {
2018-09-11 12:04:47 +00:00
m_placeholder_parser_failed_templates . clear ( ) ;
2018-12-18 16:34:21 +00:00
this - > _do_export ( * print , file ) ;
2018-03-28 15:05:31 +00:00
fflush ( file ) ;
if ( ferror ( file ) ) {
fclose ( file ) ;
boost : : nowide : : remove ( path_tmp . c_str ( ) ) ;
throw std : : runtime_error ( std : : string ( " G-code export to " ) + path + " failed \n Is the disk full? \n " ) ;
}
2018-11-06 14:31:26 +00:00
} catch ( std : : exception & /* ex */ ) {
2018-03-28 15:05:31 +00:00
// Rethrow on any exception. std::runtime_exception and CanceledException are expected to be thrown.
// Close and remove the file.
2017-12-05 17:40:46 +00:00
fclose ( file ) ;
2017-08-03 15:31:31 +00:00
boost : : nowide : : remove ( path_tmp . c_str ( ) ) ;
2018-03-28 15:05:31 +00:00
throw ;
2017-12-05 14:54:24 +00:00
}
2017-12-05 17:40:46 +00:00
fclose ( file ) ;
2018-05-30 10:08:03 +00:00
2018-09-11 12:04:47 +00:00
if ( ! m_placeholder_parser_failed_templates . empty ( ) ) {
2017-12-05 14:54:24 +00: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 12:04:47 +00:00
for ( const std : : string & name : m_placeholder_parser_failed_templates )
2017-12-05 14:54:24 +00: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 " ;
throw std : : runtime_error ( msg ) ;
}
2017-08-03 15:31:31 +00:00
2018-12-18 16:34:21 +00:00
if ( print - > config ( ) . remaining_times . value ) {
BOOST_LOG_TRIVIAL ( debug ) < < " Processing remaining times for normal mode " ;
m_normal_time_estimator . post_process_remaining_times ( path_tmp , 60.0f ) ;
m_normal_time_estimator . reset ( ) ;
if ( m_silent_time_estimator_enabled ) {
BOOST_LOG_TRIVIAL ( debug ) < < " Processing remaining times for silent mode " ;
m_silent_time_estimator . post_process_remaining_times ( path_tmp , 60.0f ) ;
m_silent_time_estimator . reset ( ) ;
}
}
// starts analyzer calculations
if ( m_enable_analyzer ) {
BOOST_LOG_TRIVIAL ( debug ) < < " Preparing G-code preview data " ;
m_analyzer . calc_gcode_preview_data ( * preview_data ) ;
m_analyzer . reset ( ) ;
}
2018-09-14 07:28:00 +00:00
if ( rename_file ( path_tmp , path ) ! = 0 )
2017-12-05 14:54:24 +00:00
throw std : : runtime_error (
std : : string ( " Failed to rename the output G-code file from " ) + path_tmp + " to " + path + ' \n ' +
" Is " + path_tmp + " locked? " + ' \n ' ) ;
2018-01-03 16:29:49 +00:00
2018-12-18 10:31:41 +00:00
BOOST_LOG_TRIVIAL ( info ) < < " Exporting G-code finished " < < log_memory_info ( ) ;
2018-09-14 07:28:00 +00:00
print - > set_done ( psGCodeExport ) ;
2018-02-14 21:25:09 +00:00
2018-01-03 16:29:49 +00:00
// Write the profiler measurements to file
PROFILE_UPDATE ( ) ;
PROFILE_OUTPUT ( debug_out_path ( " gcode-export-profile.txt " ) . c_str ( ) ) ;
2017-08-03 15:31:31 +00:00
}
2018-12-18 16:34:21 +00:00
void GCode : : _do_export ( Print & print , FILE * file )
2015-07-01 21:00:52 +00:00
{
2018-01-03 16:29:49 +00:00
PROFILE_FUNC ( ) ;
2018-05-30 10:08:03 +00:00
// resets time estimators
2018-06-27 13:35:47 +00:00
m_normal_time_estimator . reset ( ) ;
2018-09-12 09:59:02 +00:00
m_normal_time_estimator . set_dialect ( print . config ( ) . gcode_flavor ) ;
m_silent_time_estimator_enabled = ( print . config ( ) . gcode_flavor = = gcfMarlin ) & & print . config ( ) . silent_mode ;
2018-07-18 12:00:42 +00:00
// 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.
2018-09-12 09:59:02 +00:00
if ( print . config ( ) . gcode_flavor . value = = gcfMarlin ) {
m_normal_time_estimator . set_max_acceleration ( print . config ( ) . machine_max_acceleration_extruding . values [ 0 ] ) ;
m_normal_time_estimator . set_retract_acceleration ( print . config ( ) . machine_max_acceleration_retracting . values [ 0 ] ) ;
m_normal_time_estimator . set_minimum_feedrate ( print . config ( ) . machine_min_extruding_rate . values [ 0 ] ) ;
m_normal_time_estimator . set_minimum_travel_feedrate ( print . config ( ) . machine_min_travel_rate . values [ 0 ] ) ;
m_normal_time_estimator . set_axis_max_acceleration ( GCodeTimeEstimator : : X , print . config ( ) . machine_max_acceleration_x . values [ 0 ] ) ;
m_normal_time_estimator . set_axis_max_acceleration ( GCodeTimeEstimator : : Y , print . config ( ) . machine_max_acceleration_y . values [ 0 ] ) ;
m_normal_time_estimator . set_axis_max_acceleration ( GCodeTimeEstimator : : Z , print . config ( ) . machine_max_acceleration_z . values [ 0 ] ) ;
m_normal_time_estimator . set_axis_max_acceleration ( GCodeTimeEstimator : : E , print . config ( ) . machine_max_acceleration_e . values [ 0 ] ) ;
m_normal_time_estimator . set_axis_max_feedrate ( GCodeTimeEstimator : : X , print . config ( ) . machine_max_feedrate_x . values [ 0 ] ) ;
m_normal_time_estimator . set_axis_max_feedrate ( GCodeTimeEstimator : : Y , print . config ( ) . machine_max_feedrate_y . values [ 0 ] ) ;
m_normal_time_estimator . set_axis_max_feedrate ( GCodeTimeEstimator : : Z , print . config ( ) . machine_max_feedrate_z . values [ 0 ] ) ;
m_normal_time_estimator . set_axis_max_feedrate ( GCodeTimeEstimator : : E , print . config ( ) . machine_max_feedrate_e . values [ 0 ] ) ;
m_normal_time_estimator . set_axis_max_jerk ( GCodeTimeEstimator : : X , print . config ( ) . machine_max_jerk_x . values [ 0 ] ) ;
m_normal_time_estimator . set_axis_max_jerk ( GCodeTimeEstimator : : Y , print . config ( ) . machine_max_jerk_y . values [ 0 ] ) ;
m_normal_time_estimator . set_axis_max_jerk ( GCodeTimeEstimator : : Z , print . config ( ) . machine_max_jerk_z . values [ 0 ] ) ;
m_normal_time_estimator . set_axis_max_jerk ( GCodeTimeEstimator : : E , print . config ( ) . machine_max_jerk_e . values [ 0 ] ) ;
2018-07-18 12:00:42 +00:00
if ( m_silent_time_estimator_enabled )
{
m_silent_time_estimator . reset ( ) ;
2018-09-12 09:59:02 +00:00
m_silent_time_estimator . set_dialect ( print . config ( ) . gcode_flavor ) ;
m_silent_time_estimator . set_max_acceleration ( print . config ( ) . machine_max_acceleration_extruding . values [ 1 ] ) ;
m_silent_time_estimator . set_retract_acceleration ( print . config ( ) . machine_max_acceleration_retracting . values [ 1 ] ) ;
m_silent_time_estimator . set_minimum_feedrate ( print . config ( ) . machine_min_extruding_rate . values [ 1 ] ) ;
m_silent_time_estimator . set_minimum_travel_feedrate ( print . config ( ) . machine_min_travel_rate . values [ 1 ] ) ;
m_silent_time_estimator . set_axis_max_acceleration ( GCodeTimeEstimator : : X , print . config ( ) . machine_max_acceleration_x . values [ 1 ] ) ;
m_silent_time_estimator . set_axis_max_acceleration ( GCodeTimeEstimator : : Y , print . config ( ) . machine_max_acceleration_y . values [ 1 ] ) ;
m_silent_time_estimator . set_axis_max_acceleration ( GCodeTimeEstimator : : Z , print . config ( ) . machine_max_acceleration_z . values [ 1 ] ) ;
m_silent_time_estimator . set_axis_max_acceleration ( GCodeTimeEstimator : : E , print . config ( ) . machine_max_acceleration_e . values [ 1 ] ) ;
m_silent_time_estimator . set_axis_max_feedrate ( GCodeTimeEstimator : : X , print . config ( ) . machine_max_feedrate_x . values [ 1 ] ) ;
m_silent_time_estimator . set_axis_max_feedrate ( GCodeTimeEstimator : : Y , print . config ( ) . machine_max_feedrate_y . values [ 1 ] ) ;
m_silent_time_estimator . set_axis_max_feedrate ( GCodeTimeEstimator : : Z , print . config ( ) . machine_max_feedrate_z . values [ 1 ] ) ;
m_silent_time_estimator . set_axis_max_feedrate ( GCodeTimeEstimator : : E , print . config ( ) . machine_max_feedrate_e . values [ 1 ] ) ;
m_silent_time_estimator . set_axis_max_jerk ( GCodeTimeEstimator : : X , print . config ( ) . machine_max_jerk_x . values [ 1 ] ) ;
m_silent_time_estimator . set_axis_max_jerk ( GCodeTimeEstimator : : Y , print . config ( ) . machine_max_jerk_y . values [ 1 ] ) ;
m_silent_time_estimator . set_axis_max_jerk ( GCodeTimeEstimator : : Z , print . config ( ) . machine_max_jerk_z . values [ 1 ] ) ;
m_silent_time_estimator . set_axis_max_jerk ( GCodeTimeEstimator : : E , print . config ( ) . machine_max_jerk_e . values [ 1 ] ) ;
if ( print . config ( ) . single_extruder_multi_material ) {
2018-08-06 14:31:51 +00:00
// 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.
2018-09-12 09:59:02 +00:00
m_silent_time_estimator . set_filament_load_times ( print . config ( ) . filament_load_time . values ) ;
m_silent_time_estimator . set_filament_unload_times ( print . config ( ) . filament_unload_time . values ) ;
2018-08-06 14:31:51 +00:00
}
2018-07-18 12:00:42 +00:00
}
2018-06-06 10:21:24 +00:00
}
2018-08-04 15:38:25 +00:00
// Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful.
2018-09-12 09:59:02 +00:00
if ( print . config ( ) . single_extruder_multi_material ) {
2018-08-06 14:31:51 +00:00
// 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.
2018-09-12 09:59:02 +00:00
m_normal_time_estimator . set_filament_load_times ( print . config ( ) . filament_load_time . values ) ;
m_normal_time_estimator . set_filament_unload_times ( print . config ( ) . filament_unload_time . values ) ;
2018-08-06 14:31:51 +00:00
}
2017-12-11 08:06:29 +00:00
2018-01-08 12:44:10 +00:00
// resets analyzer
m_analyzer . reset ( ) ;
// resets analyzer's tracking data
m_last_mm3_per_mm = GCodeAnalyzer : : Default_mm3_per_mm ;
m_last_width = GCodeAnalyzer : : Default_Width ;
m_last_height = GCodeAnalyzer : : Default_Height ;
2017-05-03 16:28:22 +00: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 12:04:47 +00:00
if ( print . config ( ) . complete_objects . value ) {
2017-05-10 09:25:57 +00:00
// Add each of the object's layers separately.
2018-11-08 13:23:17 +00:00
for ( auto object : print . objects ( ) ) {
2017-05-10 09:25:57 +00:00
std : : vector < coordf_t > zs ;
2018-09-11 12:04:47 +00:00
zs . reserve ( object - > layers ( ) . size ( ) + object - > support_layers ( ) . size ( ) ) ;
for ( auto layer : object - > layers ( ) )
2017-05-10 09:25:57 +00:00
zs . push_back ( layer - > print_z ) ;
2018-09-11 12:04:47 +00:00
for ( auto layer : object - > support_layers ( ) )
2017-05-10 09:25:57 +00:00
zs . push_back ( layer - > print_z ) ;
std : : sort ( zs . begin ( ) , zs . end ( ) ) ;
m_layer_count + = ( unsigned int ) ( object - > copies ( ) . size ( ) * ( std : : unique ( zs . begin ( ) , zs . end ( ) ) - zs . begin ( ) ) ) ;
}
} else {
// Print all objects with the same print_z together.
std : : vector < coordf_t > zs ;
2018-11-08 13:23:17 +00:00
for ( auto object : print . objects ( ) ) {
2018-09-11 12:04:47 +00:00
zs . reserve ( zs . size ( ) + object - > layers ( ) . size ( ) + object - > support_layers ( ) . size ( ) ) ;
for ( auto layer : object - > layers ( ) )
2017-05-10 09:25:57 +00:00
zs . push_back ( layer - > print_z ) ;
2018-09-11 12:04:47 +00:00
for ( auto layer : object - > support_layers ( ) )
2017-05-10 09:25:57 +00: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 15:05:31 +00:00
print . throw_if_canceled ( ) ;
2017-05-03 16:28:22 +00:00
m_enable_cooling_markers = true ;
2018-09-11 12:04:47 +00:00
this - > apply_print_config ( print . config ( ) ) ;
2017-05-03 16:28:22 +00:00
this - > set_extruders ( print . extruders ( ) ) ;
2018-11-07 13:44:47 +00:00
// Initialize colorprint.
m_colorprint_heights = cast < float > ( print . config ( ) . colorprint_heights . values ) ;
2017-05-03 16:28:22 +00:00
// Initialize autospeed.
{
// get the minimum cross-section used in the print
std : : vector < double > mm3_per_mm ;
2018-11-08 13:23:17 +00:00
for ( auto object : print . objects ( ) ) {
2018-11-06 14:31:26 +00:00
for ( size_t region_id = 0 ; region_id < object - > region_volumes . size ( ) ; + + region_id ) {
2018-11-07 14:17:29 +00:00
const PrintRegion * region = print . regions ( ) [ region_id ] ;
2018-09-11 12:04:47 +00:00
for ( auto layer : object - > layers ( ) ) {
2018-11-07 14:17:29 +00:00
const LayerRegion * layerm = layer - > regions ( ) [ region_id ] ;
2018-09-11 12:04:47 +00:00
if ( region - > config ( ) . get_abs_value ( " perimeter_speed " ) = = 0 | |
region - > config ( ) . get_abs_value ( " small_perimeter_speed " ) = = 0 | |
region - > config ( ) . get_abs_value ( " external_perimeter_speed " ) = = 0 | |
region - > config ( ) . get_abs_value ( " bridge_speed " ) = = 0 )
2017-05-03 16:28:22 +00:00
mm3_per_mm . push_back ( layerm - > perimeters . min_mm3_per_mm ( ) ) ;
2018-09-11 12:04:47 +00:00
if ( region - > config ( ) . get_abs_value ( " infill_speed " ) = = 0 | |
region - > config ( ) . get_abs_value ( " solid_infill_speed " ) = = 0 | |
region - > config ( ) . get_abs_value ( " top_solid_infill_speed " ) = = 0 | |
region - > config ( ) . get_abs_value ( " bridge_speed " ) = = 0 )
2017-05-03 16:28:22 +00:00
mm3_per_mm . push_back ( layerm - > fills . min_mm3_per_mm ( ) ) ;
}
}
2018-09-11 12:04:47 +00:00
if ( object - > config ( ) . get_abs_value ( " support_material_speed " ) = = 0 | |
object - > config ( ) . get_abs_value ( " support_material_interface_speed " ) = = 0 )
for ( auto layer : object - > support_layers ( ) )
2017-05-03 16:28:22 +00:00
mm3_per_mm . push_back ( layer - > support_fills . min_mm3_per_mm ( ) ) ;
}
2018-03-28 15:05:31 +00:00
print . throw_if_canceled ( ) ;
2017-05-03 16:28:22 +00:00
// 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 ( ) ) ;
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.
2018-09-11 12:04:47 +00:00
m_volumetric_speed = * std : : min_element ( mm3_per_mm . begin ( ) , mm3_per_mm . end ( ) ) * print . config ( ) . max_print_speed . value ;
2017-05-03 16:28:22 +00:00
// limit such volumetric speed with max_volumetric_speed if set
2018-09-11 12:04:47 +00:00
if ( print . config ( ) . max_volumetric_speed . value > 0 )
m_volumetric_speed = std : : min ( m_volumetric_speed , print . config ( ) . max_volumetric_speed . value ) ;
2017-05-03 16:28:22 +00:00
}
}
2018-03-28 15:05:31 +00:00
print . throw_if_canceled ( ) ;
2017-05-03 16:28:22 +00:00
2017-05-03 16:57:33 +00:00
m_cooling_buffer = make_unique < CoolingBuffer > ( * this ) ;
2018-09-11 12:04:47 +00:00
if ( print . config ( ) . spiral_vase . value )
m_spiral_vase = make_unique < SpiralVase > ( print . config ( ) ) ;
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 16:28:22 +00:00
m_enable_extrusion_role_markers = ( bool ) m_pressure_equalizer ;
// Write information on the generator.
2017-12-14 08:18:28 +00:00
_write_format ( file , " ; %s \n \n " , Slic3r : : header_slic3r_generated ( ) . c_str ( ) ) ;
2017-05-03 16:28:22 +00:00
// Write notes (content of the Print Settings tab -> Notes)
{
std : : list < std : : string > lines ;
2018-09-11 12:04:47 +00:00
boost : : split ( lines , print . config ( ) . notes . value , boost : : is_any_of ( " \n " ) , boost : : token_compress_off ) ;
2017-05-03 16:28:22 +00: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 08:18:28 +00:00
_write_format ( file , " ; %s \n " , line . c_str ( ) ) ;
2017-05-03 16:28:22 +00:00
}
if ( ! lines . empty ( ) )
2017-12-14 08:18:28 +00:00
_write ( file , " \n " ) ;
2017-05-03 16:28:22 +00:00
}
2018-03-28 15:05:31 +00:00
print . throw_if_canceled ( ) ;
2017-05-03 16:28:22 +00:00
// Write some terse information on the slicing parameters.
2018-11-08 13:23:17 +00:00
const PrintObject * first_object = print . objects ( ) . front ( ) ;
2018-09-11 12:04:47 +00: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 14:17:29 +00:00
for ( const PrintRegion * region : print . regions ( ) ) {
2018-01-02 09:57:30 +00: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 14:35:00 +00:00
if ( print . has_support_material ( ) )
2018-01-02 09:57:30 +00:00
_write_format ( file , " ; support material extrusion width = %.2fmm \n " , support_material_flow ( first_object ) . width ) ;
2018-09-11 12:04:47 +00:00
if ( print . config ( ) . first_layer_extrusion_width . value > 0 )
2018-01-02 09:57:30 +00: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 16:28:22 +00:00
}
2018-03-28 15:05:31 +00:00
print . throw_if_canceled ( ) ;
2017-05-03 16:28:22 +00:00
2018-09-07 14:05:10 +00:00
// adds tags for time estimators
2018-09-12 09:59:02 +00:00
if ( print . config ( ) . remaining_times . value )
2018-09-07 14:05:10 +00: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 ) ;
}
2017-05-03 16:28:22 +00:00
// Prepare the helper object for replacing placeholders in custom G-code and output filename.
2018-09-11 12:04:47 +00:00
m_placeholder_parser = print . placeholder_parser ( ) ;
2017-05-03 16:28:22 +00:00
m_placeholder_parser . update_timestamp ( ) ;
2017-05-16 13:30:03 +00: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 13:00:01 +00:00
ToolOrdering tool_ordering ;
unsigned int initial_extruder_id = ( unsigned int ) - 1 ;
unsigned int final_extruder_id = ( unsigned int ) - 1 ;
size_t initial_print_object_id = 0 ;
2017-12-04 10:57:54 +00:00
bool has_wipe_tower = false ;
2018-09-11 12:04:47 +00:00
if ( print . config ( ) . complete_objects . value ) {
2018-07-23 13:58:08 +00:00
// Find the 1st printing object, find its tool ordering and the initial extruder ID.
2018-11-08 13:23:17 +00:00
for ( ; initial_print_object_id < print . objects ( ) . size ( ) ; + + initial_print_object_id ) {
tool_ordering = ToolOrdering ( * print . objects ( ) [ initial_print_object_id ] , initial_extruder_id ) ;
2018-07-23 13:58:08 +00:00
if ( ( initial_extruder_id = tool_ordering . first_extruder ( ) ) ! = ( unsigned int ) - 1 )
break ;
}
2018-07-27 20:19:46 +00:00
} else {
2017-05-17 15:21:55 +00:00
// Find tool ordering for all the objects at once, and the initial extruder ID.
2017-05-25 20:27:53 +00:00
// If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it.
2018-09-11 12:04:47 +00:00
tool_ordering = print . wipe_tower_data ( ) . tool_ordering . empty ( ) ?
2017-05-25 20:27:53 +00:00
ToolOrdering ( print , initial_extruder_id ) :
2018-09-11 12:04:47 +00:00
print . wipe_tower_data ( ) . tool_ordering ;
2017-12-04 10:57:54 +00:00
has_wipe_tower = print . has_wipe_tower ( ) & & tool_ordering . has_wipe_tower ( ) ;
2018-09-12 09:59:02 +00:00
initial_extruder_id = ( has_wipe_tower & & ! print . config ( ) . single_extruder_multi_material_priming ) ?
2018-07-27 20:19:46 +00:00
// The priming towers will be skipped.
tool_ordering . all_extruders ( ) . back ( ) :
// Don't skip the priming towers.
tool_ordering . first_extruder ( ) ;
2017-05-16 13:30:03 +00:00
}
2017-05-17 17:25:36 +00:00
if ( initial_extruder_id = = ( unsigned int ) - 1 ) {
2017-05-16 13:30:03 +00:00
// Nothing to print!
initial_extruder_id = 0 ;
2017-05-17 17:25:36 +00:00
final_extruder_id = 0 ;
} else {
2017-05-23 13:00:01 +00:00
final_extruder_id = tool_ordering . last_extruder ( ) ;
2017-05-17 17:25:36 +00:00
assert ( final_extruder_id ! = ( unsigned int ) - 1 ) ;
}
2018-03-28 15:05:31 +00:00
print . throw_if_canceled ( ) ;
2017-05-16 13:30:03 +00:00
2017-06-23 08:13:09 +00:00
m_cooling_buffer - > set_current_extruder ( initial_extruder_id ) ;
2018-07-17 17:37:24 +00:00
// Emit machine envelope limits for the Marlin firmware.
this - > print_machine_envelope ( file , print ) ;
2017-06-21 15:45:55 +00:00
// Disable fan.
2018-09-11 12:04:47 +00:00
if ( ! print . config ( ) . cooling . get_at ( initial_extruder_id ) | | print . config ( ) . disable_fan_first_layers . get_at ( initial_extruder_id ) )
2018-01-02 09:57:30 +00:00
_write ( file , m_writer . set_fan ( 0 , true ) ) ;
2017-06-21 15:45:55 +00:00
2017-05-16 13:30:03 +00:00
// Let the start-up script prime the 1st printing tool.
m_placeholder_parser . set ( " initial_tool " , initial_extruder_id ) ;
2017-07-31 13:42:55 +00:00
m_placeholder_parser . set ( " initial_extruder " , initial_extruder_id ) ;
m_placeholder_parser . set ( " current_extruder " , initial_extruder_id ) ;
2017-11-28 14:30:05 +00:00
// Useful for sequential prints.
m_placeholder_parser . set ( " current_object_idx " , 0 ) ;
2017-12-04 10:57:54 +00: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 09:59:02 +00:00
m_placeholder_parser . set ( " has_single_extruder_multi_material_priming " , has_wipe_tower & & print . config ( ) . single_extruder_multi_material_priming ) ;
2018-09-11 12:04:47 +00:00
std : : string start_gcode = this - > placeholder_parser_process ( " start_gcode " , print . config ( ) . start_gcode . value , initial_extruder_id ) ;
2017-11-28 14:19:57 +00: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 07:59:47 +00:00
if ( m_enable_analyzer )
{
// adds tag for analyzer
char buf [ 32 ] ;
sprintf ( buf , " ;%s%d \n " , GCodeAnalyzer : : Extrusion_Role_Tag . c_str ( ) , erCustom ) ;
_writeln ( file , buf ) ;
}
2017-11-28 14:19:57 +00:00
// Write the custom start G-code
2017-12-14 08:18:28 +00:00
_writeln ( file , start_gcode ) ;
2017-06-05 09:30:57 +00:00
// Process filament-specific gcode in extruder order.
2018-09-11 12:04:47 +00:00
if ( print . config ( ) . single_extruder_multi_material ) {
2017-12-04 10:57:54 +00:00
if ( has_wipe_tower ) {
// Wipe tower will control the extruder switching, it will call the start_filament_gcode.
} else {
// Only initialize the initial extruder.
2019-01-07 14:12:40 +00:00
DynamicConfig config ;
config . set_key_value ( " filament_extruder_id " , new ConfigOptionInt ( int ( initial_extruder_id ) ) ) ;
_writeln ( file , this - > placeholder_parser_process ( " start_filament_gcode " , print . config ( ) . start_filament_gcode . values [ initial_extruder_id ] , initial_extruder_id , & config ) ) ;
2017-12-04 10:57:54 +00:00
}
} else {
2019-01-07 14:12:40 +00:00
DynamicConfig config ;
for ( const std : : string & start_gcode : print . config ( ) . start_filament_gcode . values ) {
int extruder_id = ( unsigned int ) ( & start_gcode - & print . config ( ) . start_filament_gcode . values . front ( ) ) ;
config . set_key_value ( " filament_extruder_id " , new ConfigOptionInt ( extruder_id ) ) ;
_writeln ( file , this - > placeholder_parser_process ( " start_filament_gcode " , start_gcode , extruder_id , & config ) ) ;
}
2017-11-30 15:01:47 +00:00
}
2017-11-28 14:19:57 +00:00
this - > _print_first_layer_extruder_temperatures ( file , print , start_gcode , initial_extruder_id , true ) ;
2018-03-28 15:05:31 +00:00
print . throw_if_canceled ( ) ;
2017-05-03 16:28:22 +00:00
// Set other general things.
2017-12-14 08:18:28 +00:00
_write ( file , this - > preamble ( ) ) ;
2017-05-03 16:28:22 +00:00
// Initialize a motion planner for object-to-object travel moves.
2018-09-11 12:04:47 +00:00
if ( print . config ( ) . avoid_crossing_perimeters . value ) {
2017-06-02 11:33:19 +00:00
// Collect outer contours of all objects over all layers.
// Discard objects only containing thin walls (offset would fail on an empty polygon).
Polygons islands ;
2018-11-08 13:23:17 +00:00
for ( const PrintObject * object : print . objects ( ) )
2018-09-11 12:04:47 +00:00
for ( const Layer * layer : object - > layers ( ) )
2017-05-03 16:28:22 +00:00
for ( const ExPolygon & expoly : layer - > slices . expolygons )
2018-09-12 09:59:02 +00:00
for ( const Point & copy : object - > copies ( ) ) {
2017-06-02 11:33:19 +00:00
islands . emplace_back ( expoly . contour ) ;
2019-01-07 11:20:48 +00:00
islands . back ( ) . translate ( copy ) ;
2017-05-03 16:28:22 +00:00
}
2017-06-02 11:33:19 +00:00
//FIXME Mege the islands in parallel.
m_avoid_crossing_perimeters . init_external_mp ( union_ex ( islands ) ) ;
2018-03-28 15:05:31 +00:00
print . throw_if_canceled ( ) ;
2017-05-03 16:28:22 +00:00
}
// Calculate wiping points if needed
2018-09-11 12:04:47 +00:00
if ( print . config ( ) . ooze_prevention . value & & ! print . config ( ) . single_extruder_multi_material ) {
2017-05-03 16:28:22 +00:00
Points skirt_points ;
2018-09-11 12:04:47 +00:00
for ( const ExtrusionEntity * ee : print . skirt ( ) . entities )
2017-05-03 16:28:22 +00:00
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 ( ) ) {
2018-09-12 09:59:02 +00:00
const Vec2d & extruder_offset = print . config ( ) . extruder_offset . get_at ( extruder_id ) ;
2017-05-03 16:28:22 +00:00
Polygon s ( outer_skirt ) ;
2018-08-17 13:53:43 +00:00
s . translate ( Point : : new_scale ( - extruder_offset ( 0 ) , - extruder_offset ( 1 ) ) ) ;
2017-05-03 16:28:22 +00:00
skirts . emplace_back ( std : : move ( s ) ) ;
}
m_ooze_prevention . enable = true ;
m_ooze_prevention . standby_points =
offset ( Slic3r : : Geometry : : convex_hull ( skirts ) , scale_ ( 3.f ) ) . front ( ) . equally_spaced_points ( scale_ ( 10. ) ) ;
#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
}
2018-03-28 15:05:31 +00:00
print . throw_if_canceled ( ) ;
2017-05-03 16:28:22 +00:00
}
2018-09-12 09:59:02 +00:00
if ( ! ( has_wipe_tower & & print . config ( ) . single_extruder_multi_material_priming ) ) {
2018-07-27 20:19:46 +00: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.
_write ( file , this - > set_extruder ( initial_extruder_id ) ) ;
}
2017-05-16 13:30:03 +00:00
2017-05-03 16:28:22 +00:00
// Do all objects for each layer.
2018-09-11 12:04:47 +00:00
if ( print . config ( ) . complete_objects . value ) {
2017-05-03 16:28:22 +00:00
// Print objects from the smallest to the tallest to avoid collisions
// when moving onto next object starting point.
2018-11-08 13:23:17 +00:00
std : : vector < PrintObject * > objects ( print . objects ( ) ) ;
2018-08-17 16:07:45 +00:00
std : : sort ( objects . begin ( ) , objects . end ( ) , [ ] ( const PrintObject * po1 , const PrintObject * po2 ) { return po1 - > size ( 2 ) < po2 - > size ( 2 ) ; } ) ;
2017-05-03 16:28:22 +00:00
size_t finished_objects = 0 ;
2017-05-16 13:30:03 +00:00
for ( size_t object_id = initial_print_object_id ; object_id < objects . size ( ) ; + + object_id ) {
2017-07-05 09:58:00 +00:00
const PrintObject & object = * objects [ object_id ] ;
2018-09-12 09:59:02 +00:00
for ( const Point & copy : object . copies ( ) ) {
2017-05-16 13:30:03 +00:00
// Get optimal tool ordering to minimize tool switches of a multi-exruder print.
2018-09-12 09:59:02 +00:00
if ( object_id ! = initial_print_object_id | | & copy ! = object . copies ( ) . data ( ) ) {
2017-05-16 13:30:03 +00:00
// Don't initialize for the first object and first copy.
2017-05-23 13:00:01 +00:00
tool_ordering = ToolOrdering ( object , final_extruder_id ) ;
unsigned int new_extruder_id = tool_ordering . first_extruder ( ) ;
2017-05-16 13:30:03 +00:00
if ( new_extruder_id = = ( unsigned int ) - 1 )
// Skip this object.
continue ;
initial_extruder_id = new_extruder_id ;
2017-05-23 13:00:01 +00:00
final_extruder_id = tool_ordering . last_extruder ( ) ;
2017-05-17 17:25:36 +00:00
assert ( final_extruder_id ! = ( unsigned int ) - 1 ) ;
2017-05-16 13:30:03 +00:00
}
2018-03-28 15:05:31 +00:00
print . throw_if_canceled ( ) ;
2018-08-21 15:43:05 +00:00
this - > set_origin ( unscale ( copy ) ) ;
2017-05-03 16:28:22 +00:00
if ( finished_objects > 0 ) {
2017-05-10 09:25:57 +00:00
// 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.
2017-05-03 16:28:22 +00:00
m_enable_cooling_markers = false ; // we're not filtering these moves through CoolingBuffer
m_avoid_crossing_perimeters . use_external_mp_once = true ;
2017-12-14 08:18:28 +00:00
_write ( file , this - > retract ( ) ) ;
_write ( file , this - > travel_to ( Point ( 0 , 0 ) , erNone , " move to origin position for next object " ) ) ;
2017-05-03 16:28:22 +00:00
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.
2017-11-28 14:30:05 +00:00
m_placeholder_parser . set ( " current_object_idx " , int ( finished_objects ) ) ;
2018-09-11 12:04:47 +00:00
std : : string between_objects_gcode = this - > placeholder_parser_process ( " between_objects_gcode " , print . config ( ) . between_objects_gcode . value , initial_extruder_id ) ;
2017-11-28 14:19:57 +00:00
// 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 ) ;
2017-12-14 08:18:28 +00:00
_writeln ( file , between_objects_gcode ) ;
2017-05-10 09:25:57 +00:00
}
2017-06-30 15:05:58 +00:00
// 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 ) ;
2017-05-10 09:25:57 +00:00
// Pair the object layers with the support layers by z, extrude them.
2017-05-23 15:09:43 +00:00
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 ) ) ;
2018-09-12 09:59:02 +00:00
this - > process_layer ( file , print , lrs , tool_ordering . tools_for_layer ( ltp . print_z ( ) ) , & copy - object . copies ( ) . data ( ) ) ;
2018-03-28 15:05:31 +00:00
print . throw_if_canceled ( ) ;
2017-05-03 16:28:22 +00:00
}
2017-06-30 15:05:58 +00:00
if ( m_pressure_equalizer )
2018-01-02 09:57:30 +00:00
_write ( file , m_pressure_equalizer - > process ( " " , true ) ) ;
2017-05-03 16:28:22 +00:00
+ + 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 ;
}
}
} else {
// Order objects using a nearest neighbor search.
std : : vector < size_t > object_indices ;
Points object_reference_points ;
2018-11-08 13:23:17 +00:00
for ( PrintObject * object : print . objects ( ) )
2018-09-12 09:59:02 +00:00
object_reference_points . push_back ( object - > copies ( ) . front ( ) ) ;
2017-05-10 09:25:57 +00:00
Slic3r : : Geometry : : chained_path ( object_reference_points , object_indices ) ;
2017-05-03 16:28:22 +00:00
// Sort layers by Z.
2017-05-10 09:25:57 +00:00
// All extrusion moves with the same top layer height are extruded uninterrupted.
2017-05-23 15:09:43 +00:00
std : : vector < std : : pair < coordf_t , std : : vector < LayerToPrint > > > layers_to_print = collect_layers_to_print ( print ) ;
2017-05-16 11:45:28 +00:00
// Prusa Multi-Material wipe tower.
2017-12-04 10:57:54 +00:00
if ( has_wipe_tower & & ! layers_to_print . empty ( ) ) {
2018-09-11 12:04:47 +00: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 09:57:30 +00: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 09:59:02 +00:00
if ( print . config ( ) . single_extruder_multi_material_priming ) {
2018-07-27 20:19:46 +00:00
_write ( file , m_wipe_tower - > prime ( * this ) ) ;
// 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 13:23:17 +00:00
for ( const PrintObject * print_object : print . objects ( ) )
2018-07-27 20:19:46 +00: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 10:57:54 +00:00
}
2018-03-28 15:05:31 +00:00
print . throw_if_canceled ( ) ;
2017-09-01 15:30:18 +00:00
}
2017-05-10 09:25:57 +00:00
// Extrude the layers.
2017-05-23 15:09:43 +00:00
for ( auto & layer : layers_to_print ) {
2018-06-26 12:12:25 +00:00
const LayerTools & layer_tools = tool_ordering . tools_for_layer ( layer . first ) ;
2017-05-25 20:27:53 +00:00
if ( m_wipe_tower & & layer_tools . has_wipe_tower )
m_wipe_tower - > next_layer ( ) ;
2017-05-23 13:00:01 +00:00
this - > process_layer ( file , print , layer . second , layer_tools , size_t ( - 1 ) ) ;
2018-03-28 15:05:31 +00:00
print . throw_if_canceled ( ) ;
2017-05-16 11:45:28 +00:00
}
2017-06-30 15:05:58 +00:00
if ( m_pressure_equalizer )
2018-01-02 09:57:30 +00:00
_write ( file , m_pressure_equalizer - > process ( " " , true ) ) ;
2017-05-25 20:27:53 +00:00
if ( m_wipe_tower )
// Purge the extruder, pull out the active filament.
2018-01-02 09:57:30 +00:00
_write ( file , m_wipe_tower - > finalize ( * this ) ) ;
2017-05-03 16:28:22 +00:00
}
2017-06-05 09:30:57 +00:00
// Write end commands to file.
2017-12-14 08:18:28 +00:00
_write ( file , this - > retract ( ) ) ;
_write ( file , m_writer . set_fan ( false ) ) ;
2018-02-22 07:59:47 +00:00
if ( m_enable_analyzer )
{
// adds tag for analyzer
char buf [ 32 ] ;
sprintf ( buf , " ;%s%d \n " , GCodeAnalyzer : : Extrusion_Role_Tag . c_str ( ) , erCustom ) ;
_writeln ( file , buf ) ;
}
2017-06-05 09:30:57 +00:00
// Process filament-specific gcode in extruder order.
2018-05-15 12:04:29 +00:00
{
DynamicConfig config ;
config . set_key_value ( " layer_num " , new ConfigOptionInt ( m_layer_index ) ) ;
2018-08-17 13:53:43 +00:00
config . set_key_value ( " layer_z " , new ConfigOptionFloat ( m_writer . get_position ( ) ( 2 ) - m_config . z_offset . value ) ) ;
2018-09-12 09:59:02 +00:00
if ( print . config ( ) . single_extruder_multi_material ) {
2018-05-15 12:04:29 +00:00
// Process the end_filament_gcode for the active filament only.
2019-01-07 14:12:40 +00: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 12:04:29 +00:00
} else {
2019-01-07 14:12:40 +00:00
for ( const std : : string & end_gcode : print . config ( ) . end_filament_gcode . values ) {
int extruder_id = ( unsigned int ) ( & end_gcode - & print . config ( ) . end_filament_gcode . values . front ( ) ) ;
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 12:04:29 +00:00
}
2018-09-12 09:59:02 +00:00
_writeln ( file , this - > placeholder_parser_process ( " end_gcode " , print . config ( ) . end_gcode , m_writer . extruder ( ) - > id ( ) , & config ) ) ;
2017-11-30 15:01:47 +00:00
}
2017-12-14 08:18:28 +00:00
_write ( file , m_writer . update_progress ( m_layer_count , m_layer_count , true ) ) ; // 100%
_write ( file , m_writer . postamble ( ) ) ;
2018-03-28 15:05:31 +00:00
print . throw_if_canceled ( ) ;
2017-05-16 11:45:28 +00:00
2017-12-11 10:11:54 +00:00
// calculates estimated printing time
2018-07-20 10:05:08 +00:00
m_normal_time_estimator . calculate_time ( false ) ;
2018-06-22 12:01:27 +00:00
if ( m_silent_time_estimator_enabled )
2018-07-20 10:05:08 +00:00
m_silent_time_estimator . calculate_time ( false ) ;
2017-05-16 11:45:28 +00:00
2017-06-05 09:30:57 +00:00
// Get filament stats.
2018-09-11 12:04:47 +00:00
print . m_print_statistics . clear ( ) ;
2018-09-12 09:59:02 +00:00
print . m_print_statistics . estimated_normal_print_time = m_normal_time_estimator . get_time_dhms ( ) ;
print . m_print_statistics . estimated_silent_print_time = m_silent_time_estimator_enabled ? m_silent_time_estimator . get_time_dhms ( ) : " N/A " ;
2017-06-22 10:59:23 +00:00
for ( const Extruder & extruder : m_writer . extruders ( ) ) {
2018-09-17 13:12:13 +00:00
double used_filament = extruder . used_filament ( ) + ( has_wipe_tower ? print . wipe_tower_data ( ) . used_filament [ extruder . id ( ) ] : 0.f ) ;
double extruded_volume = extruder . extruded_volume ( ) + ( has_wipe_tower ? print . wipe_tower_data ( ) . used_filament [ extruder . id ( ) ] * 2.4052f : 0.f ) ; // assumes 1.75mm filament diameter
2017-05-03 16:28:22 +00:00
double filament_weight = extruded_volume * extruder . filament_density ( ) * 0.001 ;
double filament_cost = filament_weight * extruder . filament_cost ( ) * 0.001 ;
2018-09-12 09:59:02 +00:00
print . m_print_statistics . filament_stats . insert ( std : : pair < size_t , float > ( extruder . id ( ) , ( float ) used_filament ) ) ;
2017-12-14 08:18:28 +00:00
_write_format ( file , " ; filament used = %.1lfmm (%.1lfcm3) \n " , used_filament , extruded_volume * 0.001 ) ;
2017-05-03 16:28:22 +00:00
if ( filament_weight > 0. ) {
2018-09-11 12:04:47 +00:00
print . m_print_statistics . total_weight = print . m_print_statistics . total_weight + filament_weight ;
2017-12-14 08:18:28 +00:00
_write_format ( file , " ; filament used = %.1lf \n " , filament_weight ) ;
2017-05-03 16:28:22 +00:00
if ( filament_cost > 0. ) {
2018-09-11 12:04:47 +00:00
print . m_print_statistics . total_cost = print . m_print_statistics . total_cost + filament_cost ;
2017-12-14 08:18:28 +00:00
_write_format ( file , " ; filament cost = %.1lf \n " , filament_cost ) ;
2017-05-03 16:28:22 +00:00
}
}
2018-09-17 13:12:13 +00:00
print . m_print_statistics . total_used_filament + = used_filament ;
print . m_print_statistics . total_extruded_volume + = extruded_volume ;
print . m_print_statistics . total_wipe_tower_filament + = has_wipe_tower ? used_filament - extruder . used_filament ( ) : 0. ;
print . m_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. ;
2017-05-03 16:28:22 +00:00
}
2018-09-11 12:04:47 +00:00
_write_format ( file , " ; total filament cost = %.1lf \n " , print . m_print_statistics . total_cost ) ;
2018-06-27 13:35:47 +00:00
_write_format ( file , " ; estimated printing time (normal mode) = %s \n " , m_normal_time_estimator . get_time_dhms ( ) . c_str ( ) ) ;
2018-06-22 12:01:27 +00:00
if ( m_silent_time_estimator_enabled )
2018-06-06 10:21:24 +00:00
_write_format ( file , " ; estimated printing time (silent mode) = %s \n " , m_silent_time_estimator . get_time_dhms ( ) . c_str ( ) ) ;
2017-05-03 16:28:22 +00:00
// Append full config.
2017-12-14 08:18:28 +00:00
_write ( file , " \n " ) ;
2017-06-14 13:16:43 +00:00
{
2018-02-13 14:19:55 +00:00
std : : string full_config = " " ;
append_full_config ( print , full_config ) ;
if ( ! full_config . empty ( ) )
_write ( file , full_config ) ;
2017-06-14 13:16:43 +00:00
}
2018-03-28 15:05:31 +00:00
print . throw_if_canceled ( ) ;
2017-12-05 14:54:24 +00:00
}
2017-05-03 16:28:22 +00:00
2017-12-05 14:54:24 +00: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 12:04:47 +00:00
m_placeholder_parser_failed_templates . insert ( name ) ;
2017-12-05 14:54:24 +00:00
// Insert the macro error message into the G-code.
2017-12-05 16:38:29 +00:00
return
std : : string ( " \n !!!!! Failed to process the custom G-code template " ) + name + " \n " +
2017-12-05 14:54:24 +00:00
err . what ( ) +
2017-12-05 16:38:29 +00:00
" !!!!! End of an error report for the custom G-code template " + name + " \n \n " ;
2017-12-05 14:54:24 +00:00
}
2015-07-01 21:00:52 +00:00
}
2017-11-28 14:19:57 +00:00
// Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait inside the custom G-code.
// Returns true if one of the temp commands are found, and try to parse the target temperature value into temp_out.
static bool custom_gcode_sets_temperature ( const std : : string & gcode , const int mcode_set_temp_dont_wait , const int mcode_set_temp_and_wait , int & temp_out )
{
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 ) ;
if ( * ptr = = ' M ' ) {
// Line starts with 'M'. It is a machine command.
+ + ptr ;
// Parse the M code value.
char * endptr = nullptr ;
int mcode = int ( strtol ( ptr , & endptr , 10 ) ) ;
if ( endptr ! = nullptr & & endptr ! = ptr & & ( mcode = = mcode_set_temp_dont_wait | | mcode = = mcode_set_temp_and_wait ) ) {
// M104/M109 or M140/M190 found.
ptr = endptr ;
// Let the caller know that the custom G-code sets the temperature.
temp_set_by_gcode = true ;
// 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 ;
}
} else {
// Skip this word.
for ( ; strchr ( " \t ; \r \n \0 " , * ptr ) = = nullptr ; + + ptr ) ;
}
}
}
}
// Skip the rest of the line.
for ( ; * ptr ! = 0 & & * ptr ! = ' \r ' & & * ptr ! = ' \n ' ; + + ptr ) ;
// Skip the end of line indicators.
for ( ; * ptr = = ' \r ' | | * ptr = = ' \n ' ; + + ptr ) ;
}
return temp_set_by_gcode ;
}
2018-07-17 17:37:24 +00: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 )
{
2018-09-12 09:59:02 +00:00
if ( print . config ( ) . gcode_flavor . value = = gcfMarlin ) {
2018-07-17 17:37:24 +00:00
fprintf ( file , " M201 X%d Y%d Z%d E%d ; sets maximum accelerations, mm/sec^2 \n " ,
2018-09-12 09:59:02 +00: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 17:37:24 +00:00
fprintf ( file , " M203 X%d Y%d Z%d E%d ; sets maximum feedrates, mm/sec \n " ,
2018-09-12 09:59:02 +00: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 14:26:28 +00: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 09:59:02 +00: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 17:37:24 +00:00
fprintf ( file , " M205 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/sec \n " ,
2018-09-12 09:59:02 +00: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 17:37:24 +00:00
fprintf ( file , " M205 S%d T%d ; sets the minimum extruding and travel feed rate, mm/sec \n " ,
2018-09-12 09:59:02 +00: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 17:37:24 +00:00
}
}
2017-11-28 14:19:57 +00: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 12:04:47 +00:00
int temp = print . config ( ) . first_layer_bed_temperature . get_at ( first_printing_extruder_id ) ;
2017-11-28 14:19:57 +00:00
// Is the bed temperature set by the provided custom G-code?
int temp_by_gcode = - 1 ;
bool temp_set_by_gcode = custom_gcode_sets_temperature ( gcode , 140 , 190 , temp_by_gcode ) ;
2017-12-09 15:39:49 +00:00
if ( temp_set_by_gcode & & temp_by_gcode > = 0 & & temp_by_gcode < 1000 )
2017-11-28 14:19:57 +00: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 15:39:49 +00:00
if ( ! temp_set_by_gcode )
2018-01-02 09:57:30 +00:00
_write ( file , set_temp_gcode ) ;
2017-11-28 14:19:57 +00:00
}
2017-05-03 16:28:22 +00: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
2017-11-28 14:19:57 +00: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 21:00:52 +00:00
{
2017-11-28 14:19:57 +00:00
// Is the bed temperature set by the provided custom G-code?
int temp_by_gcode = - 1 ;
if ( custom_gcode_sets_temperature ( gcode , 104 , 109 , temp_by_gcode ) ) {
// 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 12:04:47 +00:00
int temp = print . config ( ) . first_layer_temperature . get_at ( first_printing_extruder_id ) ;
2017-11-28 14:19:57 +00:00
if ( temp_by_gcode > = 0 & & temp_by_gcode < 1000 )
temp = temp_by_gcode ;
m_writer . set_temperature ( temp_by_gcode , wait , first_printing_extruder_id ) ;
} else {
// Custom G-code does not set the extruder temperature. Do it now.
2018-09-11 12:04:47 +00:00
if ( print . config ( ) . single_extruder_multi_material . value ) {
2017-05-16 14:02:52 +00:00
// Set temperature of the first printing extruder only.
2018-09-11 12:04:47 +00:00
int temp = print . config ( ) . first_layer_temperature . get_at ( first_printing_extruder_id ) ;
2017-05-03 16:28:22 +00:00
if ( temp > 0 )
2018-01-02 09:57:30 +00:00
_write ( file , m_writer . set_temperature ( temp , wait , first_printing_extruder_id ) ) ;
2017-05-16 14:02:52 +00:00
} else {
// Set temperatures of all the printing extruders.
for ( unsigned int tool_id : print . extruders ( ) ) {
2018-09-11 12:04:47 +00: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 14:02:52 +00:00
if ( temp > 0 )
2018-01-02 09:57:30 +00:00
_write ( file , m_writer . set_temperature ( temp , wait , tool_id ) ) ;
2017-05-16 14:02:52 +00:00
}
2017-05-03 16:28:22 +00:00
}
}
2015-07-01 21:00:52 +00:00
}
2017-05-10 09:25:57 +00:00
inline GCode : : ObjectByExtruder & object_by_extruder (
std : : map < unsigned int , std : : vector < GCode : : ObjectByExtruder > > & by_extruder ,
unsigned int extruder_id ,
size_t object_idx ,
size_t num_objects )
2015-07-01 21:00:52 +00:00
{
2017-05-10 09:25:57 +00: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 (
std : : map < unsigned int , std : : vector < GCode : : ObjectByExtruder > > & by_extruder ,
unsigned int extruder_id ,
size_t object_idx ,
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 ;
}
// In sequential mode, process_layer is called once per each object and its copy,
// therefore layers will contain a single entry and single_object_idx will point to the copy of the object.
// 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.
FILE * file ,
const Print & print ,
// Set of object & print layers of the same PrintObject and with the same print_z.
const std : : vector < LayerToPrint > & layers ,
2018-12-18 13:10:31 +00:00
const LayerTools & layer_tools ,
2017-05-10 09:25:57 +00:00
// If set to size_t(-1), then print all copies of all objects.
// Otherwise print a single copy of a single object.
const size_t single_object_idx )
{
assert ( ! layers . empty ( ) ) ;
2018-10-18 16:06:40 +00:00
// assert(! layer_tools.extruders.empty());
2017-05-10 09:25:57 +00:00
// Either printing all copies of all objects, or just a single copy of a single object.
assert ( single_object_idx = = size_t ( - 1 ) | | layers . size ( ) = = 1 ) ;
2017-05-16 13:30:03 +00:00
if ( layer_tools . extruders . empty ( ) )
// Nothing to extrude.
return ;
2017-05-10 09:25:57 +00: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 ;
}
const Layer & layer = ( object_layer ! = nullptr ) ? * object_layer : * support_layer ;
coordf_t print_z = layer . print_z ;
2017-05-16 11:45:28 +00:00
bool first_layer = layer . id ( ) = = 0 ;
2017-06-21 15:45:55 +00:00
unsigned int first_extruder_id = layer_tools . extruders . front ( ) ;
2017-05-10 09:25:57 +00:00
// Initialize config with the 1st object to be printed at this layer.
2018-09-11 12:04:47 +00:00
m_config . apply ( layer . object ( ) - > config ( ) , true ) ;
2017-05-03 16:28:22 +00:00
// Check whether it is possible to apply the spiral vase logic for this layer.
2017-05-05 07:59:56 +00:00
// Just a reminder: A spiral vase mode is allowed for a single object, single material print only.
2017-05-10 09:25:57 +00:00
if ( m_spiral_vase & & layers . size ( ) = = 1 & & support_layer = = nullptr ) {
2018-09-11 12:04:47 +00:00
bool enable = ( layer . id ( ) > 0 | | print . config ( ) . brim_width . value = = 0. ) & & ( layer . id ( ) > = print . config ( ) . skirt_height . value & & ! print . has_infinite_skirt ( ) ) ;
2017-05-03 16:28:22 +00:00
if ( enable ) {
2018-09-11 12:04:47 +00:00
for ( const LayerRegion * layer_region : layer . regions ( ) )
if ( layer_region - > region ( ) - > config ( ) . bottom_solid_layers . value > layer . id ( ) | |
2017-05-03 16:28:22 +00:00
layer_region - > perimeters . items_count ( ) > 1 | |
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 09:25:57 +00:00
m_enable_loop_clipping = ! m_spiral_vase | | ! m_spiral_vase - > enable ;
2017-05-03 16:28:22 +00:00
2017-05-10 09:25:57 +00:00
std : : string gcode ;
2017-05-03 16:28:22 +00:00
// Set new layer - this will change Z and force a retraction if retract_layer_change is enabled.
2018-09-11 12:04:47 +00:00
if ( ! print . config ( ) . before_layer_gcode . value . empty ( ) ) {
2017-11-17 10:15:46 +00: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 14:54:24 +00:00
gcode + = this - > placeholder_parser_process ( " before_layer_gcode " ,
2018-09-11 12:04:47 +00:00
print . config ( ) . before_layer_gcode . value , m_writer . extruder ( ) - > id ( ) , & config )
2017-11-17 10:15:46 +00:00
+ " \n " ;
2017-05-03 16:28:22 +00:00
}
2017-05-10 09:25:57 +00:00
gcode + = this - > change_layer ( print_z ) ; // this will increase m_layer_index
m_layer = & layer ;
2018-09-11 12:04:47 +00:00
if ( ! print . config ( ) . layer_gcode . value . empty ( ) ) {
2017-11-17 10:15:46 +00: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 14:54:24 +00:00
gcode + = this - > placeholder_parser_process ( " layer_gcode " ,
2018-09-11 12:04:47 +00:00
print . config ( ) . layer_gcode . value , m_writer . extruder ( ) - > id ( ) , & config )
2017-11-17 10:15:46 +00:00
+ " \n " ;
2017-05-03 16:28:22 +00:00
}
2017-05-10 09:25:57 +00:00
2017-05-16 14:02:52 +00: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 10:59:23 +00:00
for ( const Extruder & extruder : m_writer . extruders ( ) ) {
2018-09-11 12:04:47 +00:00
if ( print . config ( ) . single_extruder_multi_material . value & & extruder . id ( ) ! = m_writer . extruder ( ) - > id ( ) )
2017-05-18 14:53:19 +00:00
// In single extruder multi material mode, set the temperature for the current extruder only.
continue ;
2018-09-11 12:04:47 +00: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 10:59:23 +00:00
gcode + = m_writer . set_temperature ( temperature , false , extruder . id ( ) ) ;
2017-05-16 14:02:52 +00:00
}
2018-09-11 12:04:47 +00:00
gcode + = m_writer . set_bed_temperature ( print . config ( ) . bed_temperature . get_at ( first_extruder_id ) ) ;
2017-05-16 14:02:52 +00:00
// Mark the temperature transition from 1st to 2nd layer to be finished.
m_second_layer_things_done = true ;
}
2018-11-07 13:44:47 +00:00
// Let's issue a filament change command if requested at this layer.
// In case there are more toolchange requests that weren't done yet and should happen simultaneously, erase them all.
// (Layers can be close to each other, model could have been resliced with bigger layer height, ...).
bool colorprint_change = false ;
2018-11-28 11:32:43 +00:00
while ( ! m_colorprint_heights . empty ( ) & & m_colorprint_heights . front ( ) - EPSILON < layer . print_z ) {
2018-11-07 13:44:47 +00:00
m_colorprint_heights . erase ( m_colorprint_heights . begin ( ) ) ;
colorprint_change = true ;
}
2018-11-28 15:02:29 +00:00
if ( colorprint_change & & print . extruders ( ) . size ( ) = = 1 )
2018-11-07 13:44:47 +00:00
gcode + = " M600 \n " ;
2017-05-03 16:28:22 +00: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.
2017-05-10 09:25:57 +00:00
bool extrude_skirt =
2018-09-11 12:04:47 +00:00
! print . skirt ( ) . entities . empty ( ) & &
2017-05-10 09:25:57 +00:00
// Not enough skirt layers printed yet.
2018-09-11 12:04:47 +00:00
( m_skirt_done . size ( ) < print . config ( ) . skirt_height . value | | print . has_infinite_skirt ( ) ) & &
2017-05-03 16:28:22 +00:00
// This print_z has not been extruded yet
2017-05-10 09:25:57 +00:00
( m_skirt_done . empty ( ) ? 0. : m_skirt_done . back ( ) ) < print_z - EPSILON & &
2017-05-03 16:28:22 +00:00
// and this layer is the 1st layer, or it is an object layer, or it is a raft layer.
2017-05-10 09:25:57 +00:00
( first_layer | | object_layer ! = nullptr | | support_layer - > id ( ) < m_config . raft_layers . value ) ;
std : : map < unsigned int , std : : pair < size_t , size_t > > skirt_loops_per_extruder ;
coordf_t skirt_height = 0. ;
if ( extrude_skirt ) {
// Fill in skirt_loops_per_extruder.
skirt_height = print_z - ( m_skirt_done . empty ( ) ? 0. : m_skirt_done . back ( ) ) ;
m_skirt_done . push_back ( print_z ) ;
if ( first_layer ) {
// Prime the extruders over the skirt lines.
std : : vector < unsigned int > extruder_ids = m_writer . extruder_ids ( ) ;
// Reorder the extruders, so that the last used extruder is at the front.
for ( size_t i = 1 ; i < extruder_ids . size ( ) ; + + i )
2017-05-16 11:45:28 +00:00
if ( extruder_ids [ i ] = = first_extruder_id ) {
2017-05-10 09:25:57 +00:00
// Move the last extruder to the front.
2017-05-15 14:42:29 +00:00
memmove ( extruder_ids . data ( ) + 1 , extruder_ids . data ( ) , i * sizeof ( unsigned int ) ) ;
2017-05-16 11:45:28 +00:00
extruder_ids . front ( ) = first_extruder_id ;
2017-05-03 16:28:22 +00:00
break ;
2017-05-10 09:25:57 +00:00
}
2018-09-11 12:04:47 +00:00
size_t n_loops = print . skirt ( ) . entities . size ( ) ;
2017-05-10 09:25:57 +00:00
if ( n_loops < = extruder_ids . size ( ) ) {
for ( size_t i = 0 ; i < n_loops ; + + i )
skirt_loops_per_extruder [ extruder_ids [ i ] ] = std : : pair < size_t , size_t > ( i , i + 1 ) ;
} else {
// Assign skirt loops to the extruders.
std : : vector < unsigned int > extruder_loops ( extruder_ids . size ( ) , 1 ) ;
n_loops - = extruder_loops . size ( ) ;
while ( n_loops > 0 ) {
for ( size_t i = 0 ; i < extruder_ids . size ( ) & & n_loops > 0 ; + + i , - - n_loops )
+ + extruder_loops [ i ] ;
}
for ( size_t i = 0 ; i < extruder_ids . size ( ) ; + + i )
skirt_loops_per_extruder [ extruder_ids [ i ] ] = std : : make_pair < size_t , size_t > (
( i = = 0 ) ? 0 : extruder_loops [ i - 1 ] ,
( ( i = = 0 ) ? 0 : extruder_loops [ i - 1 ] ) + extruder_loops [ i ] ) ;
2017-05-03 16:28:22 +00:00
}
2017-05-10 09:25:57 +00:00
} else
// Extrude all skirts with the current extruder.
2018-09-11 12:04:47 +00:00
skirt_loops_per_extruder [ first_extruder_id ] = std : : pair < size_t , size_t > ( 0 , print . config ( ) . skirts . value ) ;
2017-05-03 16:28:22 +00:00
}
2017-05-10 09:25:57 +00:00
// Group extrusions by an extruder, then by an object, an island and a region.
std : : map < unsigned int , std : : vector < ObjectByExtruder > > by_extruder ;
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 11:22:00 +00:00
if ( ! support_layer . support_fills . entities . empty ( ) ) {
2017-07-11 09:42:55 +00:00
ExtrusionRole role = support_layer . support_fills . role ( ) ;
2017-07-07 11:22:00 +00: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 12:04:47 +00:00
unsigned int support_extruder = object . config ( ) . support_material_extruder . value - 1 ;
2017-07-07 11:22:00 +00:00
// Shall the support be printed with the active extruder, preferably with non-soluble, to avoid tool changes?
2018-09-11 12:04:47 +00:00
bool support_dontcare = object . config ( ) . support_material_extruder . value = = 0 ;
2017-07-07 11:22:00 +00:00
// Extruder ID of the support interface. -1 if "don't care".
2018-09-11 12:04:47 +00:00
unsigned int interface_extruder = object . config ( ) . support_material_interface_extruder . value - 1 ;
2017-07-07 11:22:00 +00:00
// Shall the support interface be printed with the active extruder, preferably with non-soluble, to avoid tool changes?
2018-09-11 12:04:47 +00:00
bool interface_dontcare = object . config ( ) . support_material_interface_extruder . value = = 0 ;
2017-07-07 11:22:00 +00: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 12:04:47 +00:00
if ( print . config ( ) . filament_soluble . get_at ( dontcare_extruder ) ) {
2017-07-07 11:22:00 +00: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 12:04:47 +00:00
if ( ! print . config ( ) . filament_soluble . get_at ( extruder_id ) ) {
2017-07-07 11:22:00 +00:00
dontcare_extruder = extruder_id ;
break ;
}
}
if ( support_dontcare )
support_extruder = dontcare_extruder ;
if ( interface_dontcare )
interface_extruder = dontcare_extruder ;
}
2017-05-10 09:25:57 +00: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 11:22:00 +00:00
bool single_extruder = ! has_support | | support_extruder = = interface_extruder ;
2017-05-10 09:25:57 +00:00
// Assign an extruder to the base.
2017-10-03 11:22:37 +00:00
ObjectByExtruder & obj = object_by_extruder ( by_extruder , has_support ? support_extruder : interface_extruder , & layer_to_print - layers . data ( ) , layers . size ( ) ) ;
2017-05-10 09:25:57 +00:00
obj . support = & support_layer . support_fills ;
obj . support_extrusion_role = single_extruder ? erMixed : erSupportMaterial ;
2017-07-07 11:22:00 +00: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 09:25:57 +00:00
obj_interface . support = & support_layer . support_fills ;
obj_interface . support_extrusion_role = erSupportMaterialInterface ;
}
2017-05-03 16:28:22 +00:00
}
2017-05-10 09:25:57 +00:00
}
if ( layer_to_print . object_layer ! = nullptr ) {
const Layer & layer = * layer_to_print . object_layer ;
// 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
// 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)
size_t n_slices = layer . slices . expolygons . size ( ) ;
std : : vector < BoundingBox > layer_surface_bboxes ;
layer_surface_bboxes . reserve ( n_slices ) ;
for ( const ExPolygon & expoly : layer . slices . expolygons )
layer_surface_bboxes . push_back ( get_extents ( expoly . contour ) ) ;
auto point_inside_surface = [ & layer , & layer_surface_bboxes ] ( const size_t i , const Point & point ) {
const BoundingBox & bbox = layer_surface_bboxes [ i ] ;
2018-08-17 13:53:43 +00:00
return point ( 0 ) > = bbox . min ( 0 ) & & point ( 0 ) < bbox . max ( 0 ) & &
point ( 1 ) > = bbox . min ( 1 ) & & point ( 1 ) < bbox . max ( 1 ) & &
2017-05-10 09:25:57 +00:00
layer . slices . expolygons [ i ] . contour . contains ( point ) ;
} ;
2018-09-11 12:04:47 +00:00
for ( size_t region_id = 0 ; region_id < print . regions ( ) . size ( ) ; + + region_id ) {
2018-11-06 14:31:26 +00:00
const LayerRegion * layerm = ( region_id < layer . regions ( ) . size ( ) ) ? layer . regions ( ) [ region_id ] : nullptr ;
2017-05-10 09:25:57 +00:00
if ( layerm = = nullptr )
2017-05-03 16:28:22 +00:00
continue ;
2018-09-11 12:04:47 +00:00
const PrintRegion & region = * print . regions ( ) [ region_id ] ;
2018-06-05 10:50:34 +00:00
2018-05-24 12:05:51 +00:00
2018-06-20 10:52:00 +00: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:
for ( std : : string entity_type ( " infills " ) ; entity_type ! = " done " ; entity_type = entity_type = = " infills " ? " perimeters " : " done " ) {
const ExtrusionEntitiesPtr & source_entities = entity_type = = " infills " ? layerm - > fills . entities : layerm - > perimeters . entities ;
for ( const ExtrusionEntity * ee : source_entities ) {
// fill represents infill extrusions of a single island.
const auto * fill = dynamic_cast < const ExtrusionEntityCollection * > ( ee ) ;
if ( fill - > entities . empty ( ) ) // This shouldn't happen but first_point() would fail.
continue ;
// This extrusion is part of certain Region, which tells us which extruder should be used for it:
2018-09-12 09:59:02 +00:00
int correct_extruder_id = Print : : get_extruder ( * fill , region ) ;
//FIXME what is this?
entity_type = = " infills " ?
std : : max < int > ( 0 , ( is_solid_infill ( fill - > entities . front ( ) - > role ( ) ) ? region . config ( ) . solid_infill_extruder : region . config ( ) . infill_extruder ) - 1 ) :
std : : max < int > ( region . config ( ) . perimeter_extruder . value - 1 , 0 ) ;
2018-06-20 10:52:00 +00:00
// Let's recover vector of extruder overrides:
2018-09-12 09:59:02 +00:00
const ExtruderPerCopy * entity_overrides = const_cast < LayerTools & > ( layer_tools ) . wiping_extrusions ( ) . get_extruder_overrides ( fill , correct_extruder_id , layer_to_print . object ( ) - > copies ( ) . size ( ) ) ;
2018-06-20 10:52:00 +00:00
// Now we must add this extrusion into the by_extruder map, once for each extruder that will print it:
for ( unsigned int extruder : layer_tools . extruders )
{
// Init by_extruder item only if we actually use the extruder:
2018-06-26 12:12:25 +00:00
if ( std : : find ( entity_overrides - > begin ( ) , entity_overrides - > end ( ) , extruder ) ! = entity_overrides - > end ( ) | | // at least one copy is overridden to use this extruder
std : : find ( entity_overrides - > begin ( ) , entity_overrides - > end ( ) , - extruder - 1 ) ! = entity_overrides - > end ( ) | | // at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation)
( std : : find ( layer_tools . extruders . begin ( ) , layer_tools . extruders . end ( ) , correct_extruder_id ) = = layer_tools . extruders . end ( ) & & extruder = = layer_tools . extruders . back ( ) ) ) // this entity is not overridden, but its extruder is not in layer_tools - we'll print it
//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)
2018-06-20 10:52:00 +00: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 )
if ( // fill->first_point does not fit inside any slice
i = = n_slices | |
// fill->first_point fits inside ith slice
point_inside_surface ( i , fill - > first_point ( ) ) ) {
if ( islands [ i ] . by_region . empty ( ) )
2018-09-12 09:59:02 +00:00
islands [ i ] . by_region . assign ( print . regions ( ) . size ( ) , ObjectByExtruder : : Island : : Region ( ) ) ;
islands [ i ] . by_region [ region_id ] . append ( entity_type , fill , entity_overrides , layer_to_print . object ( ) - > copies ( ) . size ( ) ) ;
2018-06-20 10:52:00 +00:00
break ;
}
2018-06-01 13:38:49 +00:00
}
2017-05-10 09:25:57 +00:00
}
2018-06-20 10:52:00 +00:00
}
2017-05-10 09:25:57 +00:00
}
} // for regions
}
} // for objects
2018-06-20 10:52:00 +00:00
2017-05-10 09:25:57 +00:00
// 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 11:45:28 +00:00
for ( unsigned int extruder_id : layer_tools . extruders )
2018-06-20 10:52:00 +00:00
{
2017-05-25 20:27:53 +00:00
gcode + = ( layer_tools . has_wipe_tower & & m_wipe_tower ) ?
2017-05-18 14:53:19 +00:00
m_wipe_tower - > tool_change ( * this , extruder_id , extruder_id = = layer_tools . extruders . back ( ) ) :
this - > set_extruder ( extruder_id ) ;
2017-05-10 09:25:57 +00:00
2018-05-07 12:23:07 +00: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 ;
2017-05-10 09:25:57 +00:00
if ( extrude_skirt ) {
auto loops_it = skirt_loops_per_extruder . find ( extruder_id ) ;
if ( 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 skirt_flow = print . skirt_flow ( ) ;
for ( size_t i = loops . first ; i < loops . second ; + + i ) {
// Adjust flow according to this layer's layer height.
2018-09-11 12:04:47 +00:00
ExtrusionLoop loop = * dynamic_cast < const ExtrusionLoop * > ( print . skirt ( ) . entities [ i ] ) ;
2017-05-10 09:25:57 +00:00
Flow layer_skirt_flow ( skirt_flow ) ;
layer_skirt_flow . height = ( float ) skirt_height ;
double mm3_per_mm = layer_skirt_flow . mm3_per_mm ( ) ;
for ( ExtrusionPath & path : loop . paths ) {
path . height = ( float ) layer . height ;
path . mm3_per_mm = mm3_per_mm ;
2018-06-20 10:52:00 +00:00
}
2017-05-10 09:25:57 +00:00
gcode + = this - > extrude_loop ( loop , " skirt " , m_config . support_material_speed . value ) ;
}
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-03 16:28:22 +00:00
}
2017-05-10 09:25:57 +00:00
}
2018-06-20 10:52:00 +00:00
2017-05-10 09:25:57 +00:00
// Extrude brim with the extruder of the 1st region.
if ( ! m_brim_done ) {
2017-06-02 11:33:19 +00:00
this - > set_origin ( 0. , 0. ) ;
2017-05-10 09:25:57 +00:00
m_avoid_crossing_perimeters . use_external_mp = true ;
2018-09-11 12:04:47 +00:00
for ( const ExtrusionEntity * ee : print . brim ( ) . entities )
2017-05-10 09:25:57 +00:00
gcode + = this - > extrude_loop ( * dynamic_cast < const ExtrusionLoop * > ( ee ) , " brim " , m_config . support_material_speed . value ) ;
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 ;
}
2017-05-03 16:28:22 +00:00
2018-05-31 14:21:10 +00:00
2017-05-10 09:25:57 +00:00
auto objects_by_extruder_it = by_extruder . find ( extruder_id ) ;
if ( objects_by_extruder_it = = by_extruder . end ( ) )
continue ;
2019-01-21 13:05:04 +00:00
2018-07-09 11:44:41 +00: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):
2019-01-21 13:05:04 +00:00
bool is_anything_overridden = const_cast < LayerTools & > ( layer_tools ) . wiping_extrusions ( ) . is_anything_overridden ( ) ;
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 11:44:41 +00:00
gcode + = " ; PURGING FINISHED \n " ;
2018-06-20 10:52:00 +00:00
for ( ObjectByExtruder & object_by_extruder : objects_by_extruder_it - > second ) {
const size_t layer_id = & object_by_extruder - objects_by_extruder_it - > second . data ( ) ;
const PrintObject * print_object = layers [ layer_id ] . object ( ) ;
if ( print_object = = nullptr )
// This layer is empty for this particular object, it has neither object extrusions nor support extrusions at this print_z.
continue ;
2018-06-05 10:50:34 +00:00
2018-09-12 09:59:02 +00:00
m_config . apply ( print_object - > config ( ) , true ) ;
2018-06-20 10:52:00 +00:00
m_layer = layers [ layer_id ] . layer ( ) ;
if ( m_config . avoid_crossing_perimeters )
m_avoid_crossing_perimeters . init_layer_mp ( union_ex ( m_layer - > slices , true ) ) ;
Points copies ;
if ( single_object_idx = = size_t ( - 1 ) )
2018-09-12 09:59:02 +00:00
copies = print_object - > copies ( ) ;
2018-06-20 10:52:00 +00:00
else
2018-09-12 09:59:02 +00:00
copies . push_back ( print_object - > copies ( ) [ single_object_idx ] ) ;
2018-06-20 10:52:00 +00:00
// Sort the copies by the closest point starting with the current print position.
unsigned int copy_id = 0 ;
for ( const Point & copy : copies ) {
// When starting a new object, use the external motion planner for the first travel move.
std : : pair < const PrintObject * , Point > this_object_copy ( print_object , copy ) ;
if ( m_last_obj_copy ! = this_object_copy )
m_avoid_crossing_perimeters . use_external_mp_once = true ;
m_last_obj_copy = this_object_copy ;
2018-08-21 15:43:05 +00:00
this - > set_origin ( unscale ( copy ) ) ;
2018-07-10 11:02:43 +00:00
if ( object_by_extruder . support ! = nullptr & & ! print_wipe_extrusions ) {
2018-06-20 10:52:00 +00:00
m_layer = layers [ layer_id ] . support_layer ;
gcode + = this - > extrude_support (
// support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths.
object_by_extruder . support - > chained_path_from ( m_last_pos , false , object_by_extruder . support_extrusion_role ) ) ;
m_layer = layers [ layer_id ] . layer ( ) ;
2017-05-10 09:25:57 +00:00
}
2018-06-20 10:52:00 +00:00
for ( ObjectByExtruder : : Island & island : object_by_extruder . islands ) {
2019-01-21 13:05:04 +00:00
const auto & by_region_specific = is_anything_overridden ? island . by_region_per_copy ( copy_id , extruder_id , print_wipe_extrusions ) : island . by_region ;
2018-06-05 10:50:34 +00:00
2018-09-12 09:59:02 +00:00
if ( print . config ( ) . infill_first ) {
2018-06-20 10:52:00 +00:00
gcode + = this - > extrude_infill ( print , by_region_specific ) ;
gcode + = this - > extrude_perimeters ( print , by_region_specific , lower_layer_edge_grids [ layer_id ] ) ;
} else {
gcode + = this - > extrude_perimeters ( print , by_region_specific , lower_layer_edge_grids [ layer_id ] ) ;
gcode + = this - > extrude_infill ( print , by_region_specific ) ;
2018-06-05 10:50:34 +00:00
}
2017-05-10 09:25:57 +00:00
}
2018-06-20 10:52:00 +00:00
+ + copy_id ;
2017-05-03 16:28:22 +00:00
}
}
}
2017-05-10 09:25:57 +00:00
}
2017-05-03 16:28:22 +00:00
// Apply spiral vase post-processing if this layer contains suitable geometry
// (we must feed all the G-code into the post-processor, including the first
// 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 15:05:58 +00:00
// Just a reminder: A spiral vase mode is allowed for a single object per layer, single material print only.
2017-05-03 16:28:22 +00:00
if ( m_spiral_vase )
gcode = m_spiral_vase - > process_layer ( gcode ) ;
2017-05-10 09:25:57 +00:00
2017-05-03 16:28:22 +00:00
// Apply cooling logic; this may alter speeds.
if ( m_cooling_buffer )
2017-06-23 08:13:09 +00:00
gcode = m_cooling_buffer - > process_layer ( gcode , layer . id ( ) ) ;
2015-07-01 21:00:52 +00:00
2017-06-30 15:05:58 +00:00
// Apply pressure equalization if enabled;
2017-05-03 16:28:22 +00:00
// printf("G-code before filter:\n%s\n", gcode.c_str());
2017-06-30 15:05:58 +00:00
if ( m_pressure_equalizer )
gcode = m_pressure_equalizer - > process ( gcode . c_str ( ) , false ) ;
2017-05-03 16:28:22 +00:00
// printf("G-code after filter:\n%s\n", out.c_str());
2018-05-30 10:08:03 +00:00
2017-12-14 08:18:28 +00:00
_write ( file , gcode ) ;
2018-12-18 14:55:45 +00:00
BOOST_LOG_TRIVIAL ( trace ) < < " Exported layer " < < layer . id ( ) < < " print_z " < < print_z < <
" , time estimator memory: " < <
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 ( ) ) ;
2015-07-01 21:00:52 +00:00
}
2017-05-03 16:28:22 +00:00
void GCode : : apply_print_config ( const PrintConfig & print_config )
2015-07-02 17:33:08 +00:00
{
2017-05-03 16:28:22 +00:00
m_writer . apply_print_config ( print_config ) ;
m_config . apply ( print_config ) ;
}
2018-02-13 14:19:55 +00:00
void GCode : : append_full_config ( const Print & print , std : : string & str )
{
2018-09-12 09:59:02 +00:00
const StaticPrintConfig * configs [ ] = { static_cast < const GCodeConfig * > ( & print . config ( ) ) , & print . default_object_config ( ) , & print . default_region_config ( ) } ;
2018-02-13 14:19:55 +00:00
for ( size_t i = 0 ; i < sizeof ( configs ) / sizeof ( configs [ 0 ] ) ; + + i ) {
const StaticPrintConfig * cfg = configs [ i ] ;
for ( const std : : string & key : cfg - > keys ( ) )
if ( key ! = " compatible_printers " )
2018-03-05 09:53:18 +00:00
str + = " ; " + key + " = " + cfg - > serialize ( key ) + " \n " ;
2018-02-13 14:19:55 +00:00
}
2018-09-12 09:59:02 +00:00
const DynamicConfig & full_config = print . placeholder_parser ( ) . config ( ) ;
2018-06-26 11:22:24 +00:00
for ( const char * key : {
2018-11-16 16:36:23 +00:00
" print_settings_id " , " filament_settings_id " , " sla_print_settings_id " , " sla_material_settings_id " , " printer_settings_id " ,
" printer_model " , " printer_variant " ,
" default_print_profile " , " default_filament_profile " , " default_sla_print_profile " , " default_sla_material_profile " ,
2018-12-04 16:56:49 +00:00
" compatible_prints_condition_cummulative " , " compatible_printers_condition_cummulative " , " inherits_cummulative " } ) {
2018-06-26 11:22:24 +00:00
const ConfigOption * opt = full_config . option ( key ) ;
if ( opt ! = nullptr )
str + = std : : string ( " ; " ) + key + " = " + opt - > serialize ( ) + " \n " ;
}
2018-02-13 14:19:55 +00:00
}
2017-05-03 16:28:22 +00:00
void GCode : : set_extruders ( const std : : vector < unsigned int > & extruder_ids )
{
m_writer . set_extruders ( extruder_ids ) ;
2015-07-02 17:33:08 +00:00
// enable wipe path generation if any extruder has wipe enabled
2017-05-03 16:28:22 +00:00
m_wipe . enable = false ;
2017-03-29 15:45:38 +00:00
for ( auto id : extruder_ids )
2017-05-03 16:28:22 +00:00
if ( m_config . wipe . get_at ( id ) ) {
m_wipe . enable = true ;
2015-07-02 17:33:08 +00:00
break ;
}
}
2018-08-21 19:05:24 +00:00
void GCode : : set_origin ( const Vec2d & pointf )
2015-07-01 21:00:52 +00:00
{
// if origin increases (goes towards right), last_pos decreases because it goes towards left
2015-12-19 13:49:29 +00:00
const Point translate (
2018-08-17 13:53:43 +00:00
scale_ ( m_origin ( 0 ) - pointf ( 0 ) ) ,
scale_ ( m_origin ( 1 ) - pointf ( 1 ) )
2015-07-01 21:00:52 +00:00
) ;
Removed Point::scale(),translate(),coincides_with(),distance_to(),
distance_to_squared(),perp_distance_to(),negative(),vector_to(),
translate(), distance_to() etc,
replaced with the Eigen equivalents.
2018-08-17 12:14:24 +00:00
m_last_pos + = translate ;
2017-05-03 16:28:22 +00:00
m_wipe . path . translate ( translate ) ;
m_origin = pointf ;
2015-07-01 21:00:52 +00:00
}
2017-05-03 16:28:22 +00:00
std : : string GCode : : preamble ( )
2015-07-01 21:00:52 +00:00
{
2017-05-03 16:28:22 +00:00
std : : string gcode = m_writer . preamble ( ) ;
2015-07-01 21:00:52 +00: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 16:28:22 +00:00
m_writer . travel_to_z ( m_config . z_offset . value ) ;
2015-07-01 21:00:52 +00:00
return gcode ;
}
2017-05-10 09:25:57 +00:00
// called by GCode::process_layer()
std : : string GCode : : change_layer ( coordf_t print_z )
2015-07-02 17:33:08 +00:00
{
2016-09-26 10:52:40 +00:00
std : : string gcode ;
2017-05-03 16:28:22 +00:00
if ( m_layer_count > 0 )
2017-05-10 09:25:57 +00: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 17:33:08 +00:00
gcode + = this - > retract ( ) ;
2017-05-10 09:25:57 +00:00
2015-07-02 17:33:08 +00:00
{
std : : ostringstream comment ;
2017-05-03 16:28:22 +00:00
comment < < " move to next layer ( " < < m_layer_index < < " ) " ;
gcode + = m_writer . travel_to_z ( z , comment . str ( ) ) ;
2015-07-02 17:33:08 +00:00
}
// forget last wiping path as wiping after raising Z is pointless
2017-05-03 16:28:22 +00:00
m_wipe . reset_path ( ) ;
2015-07-02 17:33:08 +00:00
return gcode ;
}
2016-09-12 14:25:15 +00:00
static inline const char * ExtrusionRole2String ( const ExtrusionRole role )
{
switch ( role ) {
case erNone : return " erNone " ;
case erPerimeter : return " erPerimeter " ;
case erExternalPerimeter : return " erExternalPerimeter " ;
case erOverhangPerimeter : return " erOverhangPerimeter " ;
case erInternalInfill : return " erInternalInfill " ;
case erSolidInfill : return " erSolidInfill " ;
case erTopSolidInfill : return " erTopSolidInfill " ;
case erBridgeInfill : return " erBridgeInfill " ;
case erGapFill : return " erGapFill " ;
case erSkirt : return " erSkirt " ;
case erSupportMaterial : return " erSupportMaterial " ;
case erSupportMaterialInterface : return " erSupportMaterialInterface " ;
2018-02-05 12:16:08 +00:00
case erWipeTower : return " erWipeTower " ;
2017-04-07 15:37:30 +00:00
case erMixed : return " erMixed " ;
2018-02-05 12:16:08 +00:00
2016-09-12 14:25:15 +00:00
default : return " erInvalid " ;
} ;
}
static inline const char * ExtrusionLoopRole2String ( const ExtrusionLoopRole role )
{
switch ( role ) {
case elrDefault : return " elrDefault " ;
case elrContourInternalPerimeter : return " elrContourInternalPerimeter " ;
case elrSkirt : return " elrSkirt " ;
default : return " elrInvalid " ;
}
} ;
// Return a value in <0, 1> of a cubic B-spline kernel centered around zero.
// The B-spline is re-scaled so it has value 1 at zero.
static inline float bspline_kernel ( float x )
{
x = std : : abs ( x ) ;
if ( x < 1.f ) {
2017-05-03 16:28:22 +00:00
return 1.f - ( 3.f / 2.f ) * x * x + ( 3.f / 4.f ) * x * x * x ;
2016-09-12 14:25:15 +00:00
}
else if ( x < 2.f ) {
x - = 1.f ;
float x2 = x * x ;
float x3 = x2 * x ;
return ( 1.f / 4.f ) - ( 3.f / 4.f ) * x + ( 3.f / 4.f ) * x2 - ( 1.f / 4.f ) * x3 ;
}
else
return 0 ;
}
static float extrudate_overlap_penalty ( float nozzle_r , float weight_zero , float overlap_distance )
{
// The extrudate is not fully supported by the lower layer. Fit a polynomial penalty curve.
// Solved by sympy package:
/*
from sympy import *
( x , a , b , c , d , r , z ) = symbols ( ' x a b c d r z ' )
p = a + b * x + c * x * x + d * x * x * x
p2 = p . subs ( solve ( [ p . subs ( x , - r ) , p . diff ( x ) . subs ( x , - r ) , p . diff ( x , x ) . subs ( x , - r ) , p . subs ( x , 0 ) - z ] , [ a , b , c , d ] ) )
from sympy . plotting import plot
plot ( p2 . subs ( r , 0.2 ) . subs ( z , 1. ) , ( x , - 1 , 3 ) , adaptive = False , nb_of_points = 400 )
*/
if ( overlap_distance < - nozzle_r ) {
// The extrudate is fully supported by the lower layer. This is the ideal case, therefore zero penalty.
return 0.f ;
} else {
float x = overlap_distance / nozzle_r ;
float x2 = x * x ;
float x3 = x2 * x ;
return weight_zero * ( 1.f + 3.f * x + 3.f * x2 + x3 ) ;
}
}
static Points : : iterator project_point_to_polygon_and_insert ( Polygon & polygon , const Point & pt , double eps )
{
assert ( polygon . points . size ( ) > = 2 ) ;
if ( polygon . points . size ( ) < = 1 )
if ( polygon . points . size ( ) = = 1 )
return polygon . points . begin ( ) ;
Point pt_min ;
double d_min = std : : numeric_limits < double > : : max ( ) ;
size_t i_min = size_t ( - 1 ) ;
for ( size_t i = 0 ; i < polygon . points . size ( ) ; + + i ) {
size_t j = i + 1 ;
if ( j = = polygon . points . size ( ) )
j = 0 ;
const Point & p1 = polygon . points [ i ] ;
const Point & p2 = polygon . points [ j ] ;
Removed Point::scale(),translate(),coincides_with(),distance_to(),
distance_to_squared(),perp_distance_to(),negative(),vector_to(),
translate(), distance_to() etc,
replaced with the Eigen equivalents.
2018-08-17 12:14:24 +00:00
const Slic3r : : Point v_seg = p2 - p1 ;
const Slic3r : : Point v_pt = pt - p1 ;
2018-08-17 13:53:43 +00:00
const int64_t l2_seg = int64_t ( v_seg ( 0 ) ) * int64_t ( v_seg ( 0 ) ) + int64_t ( v_seg ( 1 ) ) * int64_t ( v_seg ( 1 ) ) ;
int64_t t_pt = int64_t ( v_seg ( 0 ) ) * int64_t ( v_pt ( 0 ) ) + int64_t ( v_seg ( 1 ) ) * int64_t ( v_pt ( 1 ) ) ;
2016-09-12 14:25:15 +00:00
if ( t_pt < 0 ) {
// Closest to p1.
2018-08-17 13:53:43 +00:00
double dabs = sqrt ( int64_t ( v_pt ( 0 ) ) * int64_t ( v_pt ( 0 ) ) + int64_t ( v_pt ( 1 ) ) * int64_t ( v_pt ( 1 ) ) ) ;
2016-09-12 14:25:15 +00:00
if ( dabs < d_min ) {
d_min = dabs ;
i_min = i ;
pt_min = p1 ;
}
}
else if ( t_pt > l2_seg ) {
// Closest to p2. Then p2 is the starting point of another segment, which shall be discovered in the next step.
continue ;
} else {
// Closest to the segment.
assert ( t_pt > = 0 & & t_pt < = l2_seg ) ;
2018-08-17 13:53:43 +00:00
int64_t d_seg = int64_t ( v_seg ( 1 ) ) * int64_t ( v_pt ( 0 ) ) - int64_t ( v_seg ( 0 ) ) * int64_t ( v_pt ( 1 ) ) ;
2016-09-12 14:25:15 +00:00
double d = double ( d_seg ) / sqrt ( double ( l2_seg ) ) ;
double dabs = std : : abs ( d ) ;
if ( dabs < d_min ) {
d_min = dabs ;
i_min = i ;
// Evaluate the foot point.
pt_min = p1 ;
double linv = double ( d_seg ) / double ( l2_seg ) ;
2018-08-17 13:53:43 +00:00
pt_min ( 0 ) = pt ( 0 ) - coord_t ( floor ( double ( v_seg ( 1 ) ) * linv + 0.5 ) ) ;
pt_min ( 1 ) = pt ( 1 ) + coord_t ( floor ( double ( v_seg ( 0 ) ) * linv + 0.5 ) ) ;
2016-09-12 14:25:15 +00:00
assert ( Line ( p1 , p2 ) . distance_to ( pt_min ) < scale_ ( 1e-5 ) ) ;
}
}
}
assert ( i_min ! = size_t ( - 1 ) ) ;
Removed Point::scale(),translate(),coincides_with(),distance_to(),
distance_to_squared(),perp_distance_to(),negative(),vector_to(),
translate(), distance_to() etc,
replaced with the Eigen equivalents.
2018-08-17 12:14:24 +00:00
if ( ( pt_min - polygon . points [ i_min ] ) . cast < double > ( ) . norm ( ) > eps ) {
2016-09-12 14:25:15 +00:00
// Insert a new point on the segment i_min, i_min+1.
return polygon . points . insert ( polygon . points . begin ( ) + ( i_min + 1 ) , pt_min ) ;
}
return polygon . points . begin ( ) + i_min ;
}
std : : vector < float > polygon_parameter_by_length ( const Polygon & polygon )
{
// Parametrize the polygon by its length.
std : : vector < float > lengths ( polygon . points . size ( ) + 1 , 0. ) ;
for ( size_t i = 1 ; i < polygon . points . size ( ) ; + + i )
Removed Point::scale(),translate(),coincides_with(),distance_to(),
distance_to_squared(),perp_distance_to(),negative(),vector_to(),
translate(), distance_to() etc,
replaced with the Eigen equivalents.
2018-08-17 12:14:24 +00:00
lengths [ i ] = lengths [ i - 1 ] + ( polygon . points [ i ] - polygon . points [ i - 1 ] ) . cast < float > ( ) . norm ( ) ;
lengths . back ( ) = lengths [ lengths . size ( ) - 2 ] + ( polygon . points . front ( ) - polygon . points . back ( ) ) . cast < float > ( ) . norm ( ) ;
2016-09-12 14:25:15 +00:00
return lengths ;
}
std : : vector < float > polygon_angles_at_vertices ( const Polygon & polygon , const std : : vector < float > & lengths , float min_arm_length )
{
assert ( polygon . points . size ( ) + 1 = = lengths . size ( ) ) ;
if ( min_arm_length > 0.25f * lengths . back ( ) )
min_arm_length = 0.25f * lengths . back ( ) ;
// Find the initial prev / next point span.
size_t idx_prev = polygon . points . size ( ) ;
size_t idx_curr = 0 ;
size_t idx_next = 1 ;
while ( idx_prev > idx_curr & & lengths . back ( ) - lengths [ idx_prev ] < min_arm_length )
- - idx_prev ;
while ( idx_next < idx_prev & & lengths [ idx_next ] < min_arm_length )
+ + idx_next ;
std : : vector < float > angles ( polygon . points . size ( ) , 0.f ) ;
for ( ; idx_curr < polygon . points . size ( ) ; + + idx_curr ) {
// Move idx_prev up until the distance between idx_prev and idx_curr is lower than min_arm_length.
if ( idx_prev > = idx_curr ) {
while ( idx_prev < polygon . points . size ( ) & & lengths . back ( ) - lengths [ idx_prev ] + lengths [ idx_curr ] > min_arm_length )
+ + idx_prev ;
if ( idx_prev = = polygon . points . size ( ) )
idx_prev = 0 ;
}
while ( idx_prev < idx_curr & & lengths [ idx_curr ] - lengths [ idx_prev ] > min_arm_length )
+ + idx_prev ;
// Move idx_prev one step back.
if ( idx_prev = = 0 )
idx_prev = polygon . points . size ( ) - 1 ;
else
- - idx_prev ;
// Move idx_next up until the distance between idx_curr and idx_next is greater than min_arm_length.
if ( idx_curr < = idx_next ) {
while ( idx_next < polygon . points . size ( ) & & lengths [ idx_next ] - lengths [ idx_curr ] < min_arm_length )
+ + idx_next ;
if ( idx_next = = polygon . points . size ( ) )
idx_next = 0 ;
}
while ( idx_next < idx_curr & & lengths . back ( ) - lengths [ idx_curr ] + lengths [ idx_next ] < min_arm_length )
+ + idx_next ;
// Calculate angle between idx_prev, idx_curr, idx_next.
const Point & p0 = polygon . points [ idx_prev ] ;
const Point & p1 = polygon . points [ idx_curr ] ;
const Point & p2 = polygon . points [ idx_next ] ;
Removed Point::scale(),translate(),coincides_with(),distance_to(),
distance_to_squared(),perp_distance_to(),negative(),vector_to(),
translate(), distance_to() etc,
replaced with the Eigen equivalents.
2018-08-17 12:14:24 +00:00
const Point v1 = p1 - p0 ;
const Point v2 = p2 - p1 ;
2018-08-17 13:53:43 +00:00
int64_t dot = int64_t ( v1 ( 0 ) ) * int64_t ( v2 ( 0 ) ) + int64_t ( v1 ( 1 ) ) * int64_t ( v2 ( 1 ) ) ;
int64_t cross = int64_t ( v1 ( 0 ) ) * int64_t ( v2 ( 1 ) ) - int64_t ( v1 ( 1 ) ) * int64_t ( v2 ( 0 ) ) ;
2016-09-12 14:25:15 +00:00
float angle = float ( atan2 ( double ( cross ) , double ( dot ) ) ) ;
angles [ idx_curr ] = angle ;
}
return angles ;
}
2017-05-10 09:25:57 +00: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 16:57:40 +00:00
{
2015-07-02 18:24:16 +00: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 14:25:15 +00:00
2017-05-10 09:25:57 +00:00
if ( m_layer - > lower_layer ! = nullptr & & lower_layer_edge_grid ! = nullptr ) {
if ( ! * lower_layer_edge_grid ) {
2016-09-12 14:25:15 +00:00
// Create the distance field for a layer below.
2017-05-18 14:53:19 +00:00
const coord_t distance_field_resolution = coord_t ( scale_ ( 1. ) + 0.5 ) ;
2017-05-10 09:25:57 +00:00
* lower_layer_edge_grid = make_unique < EdgeGrid : : Grid > ( ) ;
( * lower_layer_edge_grid ) - > create ( m_layer - > lower_layer - > slices , distance_field_resolution ) ;
( * lower_layer_edge_grid ) - > calculate_sdf ( ) ;
2016-09-12 14:25:15 +00:00
#if 0
{
static int iRun = 0 ;
2017-05-10 09:25:57 +00:00
BoundingBox bbox = ( * lower_layer_edge_grid ) - > bbox ( ) ;
2018-08-17 13:53:43 +00: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 09:25:57 +00: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 14:25:15 +00:00
}
# endif
}
}
2015-07-02 18:24:16 +00:00
// extrude all loops ccw
bool was_clockwise = loop . make_counter_clockwise ( ) ;
2017-05-03 16:28:22 +00:00
SeamPosition seam_position = m_config . seam_position ;
2017-04-07 15:37:30 +00:00
if ( loop . loop_role ( ) = = elrSkirt )
2017-02-07 17:46:02 +00:00
seam_position = spNearest ;
2015-12-21 14:02:39 +00:00
2015-07-02 18:24:16 +00: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 16:28:22 +00:00
if ( m_config . spiral_vase ) {
2017-02-02 17:49:33 +00:00
loop . split_at ( last_pos , false ) ;
2017-02-07 17:46:02 +00:00
} else if ( seam_position = = spNearest | | seam_position = = spAligned | | seam_position = = spRear ) {
2016-09-12 14:25:15 +00:00
Polygon polygon = loop . polygon ( ) ;
const coordf_t nozzle_dmr = EXTRUDER_CONFIG ( nozzle_diameter ) ;
2017-09-01 15:30:18 +00:00
const coord_t nozzle_r = coord_t ( scale_ ( 0.5 * nozzle_dmr ) + 0.5 ) ;
2016-09-12 14:25:15 +00:00
// Retrieve the last start position for this object.
float last_pos_weight = 1.f ;
2017-02-07 17:46:02 +00:00
switch ( seam_position ) {
case spAligned :
// Seam is aligned to the seam at the preceding layer.
2017-05-03 16:28:22 +00:00
if ( m_layer ! = NULL & & m_seam_position . count ( m_layer - > object ( ) ) > 0 ) {
last_pos = m_seam_position [ m_layer - > object ( ) ] ;
2017-02-22 15:35:07 +00:00
last_pos_weight = 1.f ;
2017-02-07 17:46:02 +00:00
}
break ;
case spRear :
2017-05-03 16:28:22 +00:00
last_pos = m_layer - > object ( ) - > bounding_box ( ) . center ( ) ;
2018-08-17 13:53:43 +00:00
last_pos ( 1 ) + = coord_t ( 3. * m_layer - > object ( ) - > bounding_box ( ) . radius ( ) ) ;
2016-09-12 14:25:15 +00:00
last_pos_weight = 5.f ;
2017-02-07 17:46:02 +00:00
break ;
2015-07-02 18:24:16 +00:00
}
2016-09-12 14:25:15 +00:00
// Insert a projection of last_pos into the polygon.
2017-02-07 17:46:02 +00:00
size_t last_pos_proj_idx ;
{
Points : : iterator it = project_point_to_polygon_and_insert ( polygon , last_pos , 0.1 * nozzle_r ) ;
last_pos_proj_idx = it - polygon . points . begin ( ) ;
}
2016-09-12 14:25:15 +00:00
Point last_pos_proj = polygon . points [ last_pos_proj_idx ] ;
// Parametrize the polygon by its length.
std : : vector < float > lengths = polygon_parameter_by_length ( polygon ) ;
// For each polygon point, store a penalty.
// First calculate the angles, store them as penalties. The angles are caluculated over a minimum arm length of nozzle_r.
2017-05-18 14:53:19 +00:00
std : : vector < float > penalties = polygon_angles_at_vertices ( polygon , lengths , float ( nozzle_r ) ) ;
2016-09-12 14:25:15 +00:00
// No penalty for reflex points, slight penalty for convex points, high penalty for flat surfaces.
const float penaltyConvexVertex = 1.f ;
const float penaltyFlatSurface = 5.f ;
const float penaltySeam = 1.3f ;
const float penaltyOverhangHalf = 10.f ;
// Penalty for visible seams.
for ( size_t i = 0 ; i < polygon . points . size ( ) ; + + i ) {
float ccwAngle = penalties [ i ] ;
if ( was_clockwise )
ccwAngle = - ccwAngle ;
float penalty = 0 ;
// if (ccwAngle <- float(PI/3.))
if ( ccwAngle < - float ( 0.6 * PI ) )
// Sharp reflex vertex. We love that, it hides the seam perfectly.
penalty = 0.f ;
// else if (ccwAngle > float(PI/3.))
else if ( ccwAngle > float ( 0.6 * PI ) )
// Seams on sharp convex vertices are more visible than on reflex vertices.
penalty = penaltyConvexVertex ;
else if ( ccwAngle < 0.f ) {
// Interpolate penalty between maximum and zero.
2017-09-01 15:30:18 +00:00
penalty = penaltyFlatSurface * bspline_kernel ( ccwAngle * float ( PI * 2. / 3. ) ) ;
2016-09-12 14:25:15 +00:00
} else {
assert ( ccwAngle > = 0.f ) ;
// Interpolate penalty between maximum and the penalty for a convex vertex.
2017-09-01 15:30:18 +00:00
penalty = penaltyConvexVertex + ( penaltyFlatSurface - penaltyConvexVertex ) * bspline_kernel ( ccwAngle * float ( PI * 2. / 3. ) ) ;
2015-07-02 18:24:16 +00:00
}
2016-09-12 14:25:15 +00:00
// Give a negative penalty for points close to the last point or the prefered seam location.
//float dist_to_last_pos_proj = last_pos_proj.distance_to(polygon.points[i]);
float dist_to_last_pos_proj = ( i < last_pos_proj_idx ) ?
std : : min ( lengths [ last_pos_proj_idx ] - lengths [ i ] , lengths . back ( ) - lengths [ last_pos_proj_idx ] + lengths [ i ] ) :
std : : min ( lengths [ i ] - lengths [ last_pos_proj_idx ] , lengths . back ( ) - lengths [ i ] + lengths [ last_pos_proj_idx ] ) ;
float dist_max = 0.1f * lengths . back ( ) ; // 5.f * nozzle_dmr
penalty - = last_pos_weight * bspline_kernel ( dist_to_last_pos_proj / dist_max ) ;
penalties [ i ] = std : : max ( 0.f , penalty ) ;
2015-07-02 18:24:16 +00:00
}
2016-09-12 14:25:15 +00:00
// Penalty for overhangs.
2017-05-10 09:25:57 +00:00
if ( lower_layer_edge_grid & & ( * lower_layer_edge_grid ) ) {
2016-09-12 14:25:15 +00:00
// Use the edge grid distance field structure over the lower layer to calculate overhangs.
2017-09-01 15:30:18 +00:00
coord_t nozzle_r = coord_t ( floor ( scale_ ( 0.5 * nozzle_dmr ) + 0.5 ) ) ;
coord_t search_r = coord_t ( floor ( scale_ ( 0.8 * nozzle_dmr ) + 0.5 ) ) ;
2016-09-12 14:25:15 +00:00
for ( size_t i = 0 ; i < polygon . points . size ( ) ; + + i ) {
const Point & p = polygon . points [ i ] ;
coordf_t dist ;
// Signed distance is positive outside the object, negative inside the object.
// The point is considered at an overhang, if it is more than nozzle radius
// outside of the lower layer contour.
2017-05-10 09:25:57 +00:00
bool found = ( * lower_layer_edge_grid ) - > signed_distance ( p , search_r , dist ) ;
// If the approximate Signed Distance Field was initialized over lower_layer_edge_grid,
2016-09-12 14:25:15 +00:00
// then the signed distnace shall always be known.
assert ( found ) ;
2017-09-01 15:30:18 +00:00
penalties [ i ] + = extrudate_overlap_penalty ( float ( nozzle_r ) , penaltyOverhangHalf , float ( dist ) ) ;
2015-07-02 18:24:16 +00:00
}
}
2016-09-12 14:25:15 +00:00
// Find a point with a minimum penalty.
size_t idx_min = std : : min_element ( penalties . begin ( ) , penalties . end ( ) ) - penalties . begin ( ) ;
2017-02-22 15:35:07 +00:00
// if (seam_position == spAligned)
// For all (aligned, nearest, rear) seams:
{
// Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx.
// In that case use last_pos_proj_idx instead.
float penalty_aligned = penalties [ last_pos_proj_idx ] ;
float penalty_min = penalties [ idx_min ] ;
float penalty_diff_abs = std : : abs ( penalty_min - penalty_aligned ) ;
float penalty_max = std : : max ( penalty_min , penalty_aligned ) ;
float penalty_diff_rel = ( penalty_max = = 0.f ) ? 0.f : penalty_diff_abs / penalty_max ;
// printf("Align seams, penalty aligned: %f, min: %f, diff abs: %f, diff rel: %f\n", penalty_aligned, penalty_min, penalty_diff_abs, penalty_diff_rel);
if ( penalty_diff_rel < 0.05 ) {
// Penalty of the aligned point is very close to the minimum penalty.
// Align the seams as accurately as possible.
idx_min = last_pos_proj_idx ;
}
2017-05-03 16:28:22 +00:00
m_seam_position [ m_layer - > object ( ) ] = polygon . points [ idx_min ] ;
2017-02-22 15:35:07 +00:00
}
2016-09-12 14:25:15 +00:00
// Export the contour into a SVG file.
#if 0
{
static int iRun = 0 ;
2016-10-21 08:18:01 +00:00
SVG svg ( debug_out_path ( " GCode_extrude_loop-%d.svg " , iRun + + ) ) ;
2017-05-03 16:28:22 +00:00
if ( m_layer - > lower_layer ! = NULL )
svg . draw ( m_layer - > lower_layer - > slices . expolygons ) ;
2016-09-12 14:25:15 +00:00
for ( size_t i = 0 ; i < loop . paths . size ( ) ; + + i )
svg . draw ( loop . paths [ i ] . as_polyline ( ) , " red " ) ;
Polylines polylines ;
for ( size_t i = 0 ; i < loop . paths . size ( ) ; + + i )
polylines . push_back ( loop . paths [ i ] . as_polyline ( ) ) ;
Slic3r : : Polygons polygons ;
coordf_t nozzle_dmr = EXTRUDER_CONFIG ( nozzle_diameter ) ;
coord_t delta = scale_ ( 0.5 * nozzle_dmr ) ;
Slic3r : : offset ( polylines , & polygons , delta ) ;
// for (size_t i = 0; i < polygons.size(); ++ i) svg.draw((Polyline)polygons[i], "blue");
2017-02-07 17:46:02 +00:00
svg . draw ( last_pos , " green " , 3 ) ;
svg . draw ( polygon . points [ idx_min ] , " yellow " , 3 ) ;
svg . Close ( ) ;
2016-09-12 14:25:15 +00:00
}
# endif
// Split the loop at the point with a minium penalty.
if ( ! loop . split_at_vertex ( polygon . points [ idx_min ] ) )
// The point is not in the original loop. Insert it.
2017-02-02 17:49:33 +00:00
loop . split_at ( polygon . points [ idx_min ] , true ) ;
2016-09-12 14:25:15 +00:00
2015-12-21 14:02:39 +00:00
} else if ( seam_position = = spRandom ) {
2017-04-07 15:37:30 +00:00
if ( loop . loop_role ( ) = = elrContourInternalPerimeter ) {
2016-09-12 14:25:15 +00:00
// This loop does not contain any other loop. Set a random position.
// The other loops will get a seam close to the random point chosen
// on the inner most contour.
//FIXME This works correctly for inner contours first only.
//FIXME Better parametrize the loop by its length.
2015-07-02 18:24:16 +00:00
Polygon polygon = loop . polygon ( ) ;
Point centroid = polygon . centroid ( ) ;
2018-08-17 13:53:43 +00:00
last_pos = Point ( polygon . bounding_box ( ) . max ( 0 ) , centroid ( 1 ) ) ;
2015-09-30 13:22:49 +00:00
last_pos . rotate ( fmod ( ( float ) rand ( ) / 16.0 , 2.0 * PI ) , centroid ) ;
2015-07-02 18:24:16 +00:00
}
2017-02-07 17:46:02 +00:00
// Find the closest point, avoid overhangs.
2017-02-02 17:49:33 +00:00
loop . split_at ( last_pos , true ) ;
2015-07-02 18:24:16 +00: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
2017-05-03 16:28:22 +00:00
double clip_length = m_enable_loop_clipping ?
scale_ ( EXTRUDER_CONFIG ( nozzle_diameter ) ) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER :
0 ;
2015-07-02 18:24:16 +00:00
// get paths
ExtrusionPaths paths ;
loop . clip_end ( clip_length , & paths ) ;
if ( paths . empty ( ) ) return " " ;
// apply the small perimeter speed
2017-05-03 16:28:22 +00: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 ) ;
2015-07-02 18:24:16 +00:00
// extrude along the path
std : : string gcode ;
2017-02-15 16:51:46 +00:00
for ( ExtrusionPaths : : iterator path = paths . begin ( ) ; path ! = paths . end ( ) ; + + path ) {
2017-04-07 15:37:30 +00:00
// description += ExtrusionLoopRole2String(loop.loop_role());
2016-09-12 14:25:15 +00:00
// description += ExtrusionRole2String(path->role);
2017-02-15 16:51:46 +00:00
path - > simplify ( SCALED_RESOLUTION ) ;
2015-07-02 18:24:16 +00:00
gcode + = this - > _extrude ( * path , description , speed ) ;
2017-02-15 16:51:46 +00:00
}
2015-07-02 18:24:16 +00:00
// reset acceleration
2017-09-01 15:30:18 +00:00
gcode + = m_writer . set_acceleration ( ( unsigned int ) ( m_config . default_acceleration . value + 0.5 ) ) ;
2015-07-02 18:24:16 +00:00
2017-05-03 16:28:22 +00:00
if ( m_wipe . enable )
m_wipe . path = paths . front ( ) . polyline ; // TODO: don't limit wipe to last path
2015-07-02 18:24:16 +00:00
// make a little move inwards before leaving loop
2018-12-07 10:21:05 +00: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 18:24:16 +00: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)
2018-12-07 10:21:05 +00: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 18:24:16 +00: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 ;
}
double angle = paths . front ( ) . first_point ( ) . ccw_angle ( a , b ) / 3 ;
// turn left if contour, turn right if hole
if ( was_clockwise ) angle * = - 1 ;
// 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
Removed Point::scale(),translate(),coincides_with(),distance_to(),
distance_to_squared(),perp_distance_to(),negative(),vector_to(),
translate(), distance_to() etc,
replaced with the Eigen equivalents.
2018-08-17 12:14:24 +00: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 18:24:16 +00:00
// generate the travel move
Removed Point::scale(),translate(),coincides_with(),distance_to(),
distance_to_squared(),perp_distance_to(),negative(),vector_to(),
translate(), distance_to() etc,
replaced with the Eigen equivalents.
2018-08-17 12:14:24 +00:00
gcode + = m_writer . travel_to_xy ( this - > point_to_gcode ( pt ) , " move inwards before travel " ) ;
2015-07-02 18:24:16 +00:00
}
return gcode ;
}
2017-05-10 09:25:57 +00:00
std : : string GCode : : extrude_multi_path ( ExtrusionMultiPath multipath , std : : string description , double speed )
2017-01-19 12:35:55 +00:00
{
// extrude along the path
std : : string gcode ;
2017-05-10 09:25:57 +00:00
for ( ExtrusionPath path : multipath . paths ) {
2017-04-07 15:37:30 +00:00
// description += ExtrusionLoopRole2String(loop.loop_role());
2017-01-19 12:35:55 +00:00
// description += ExtrusionRole2String(path->role);
2017-05-10 09:25:57 +00:00
path . simplify ( SCALED_RESOLUTION ) ;
gcode + = this - > _extrude ( path , description , speed ) ;
2017-02-15 16:51:46 +00:00
}
2017-05-03 16:28:22 +00: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 16:51:46 +00:00
}
2017-01-19 12:35:55 +00:00
// reset acceleration
2017-09-01 15:30:18 +00:00
gcode + = m_writer . set_acceleration ( ( unsigned int ) floor ( m_config . default_acceleration . value + 0.5 ) ) ;
2017-01-19 12:35:55 +00:00
return gcode ;
}
2017-05-15 09:32:59 +00: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 18:24:16 +00:00
{
2017-05-10 09:25:57 +00: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 09:32:59 +00:00
return this - > extrude_loop ( * loop , description , speed , lower_layer_edge_grid ) ;
2017-05-10 09:25:57 +00:00
else {
2018-09-18 08:09:58 +00:00
throw std : : invalid_argument ( " Invalid argument supplied to extrude() " ) ;
2015-07-02 18:24:16 +00:00
return " " ;
}
}
2017-05-10 09:25:57 +00:00
std : : string GCode : : extrude_path ( ExtrusionPath path , std : : string description , double speed )
2015-07-02 18:24:16 +00:00
{
2017-04-07 15:37:30 +00:00
// description += ExtrusionRole2String(path.role());
2017-02-15 16:51:46 +00:00
path . simplify ( SCALED_RESOLUTION ) ;
2015-07-02 18:24:16 +00:00
std : : string gcode = this - > _extrude ( path , description , speed ) ;
2017-05-03 16:28:22 +00:00
if ( m_wipe . enable ) {
m_wipe . path = std : : move ( path . polyline ) ;
m_wipe . path . reverse ( ) ;
2017-05-10 09:25:57 +00:00
}
2015-07-02 16:57:40 +00:00
// reset acceleration
2017-05-10 09:25:57 +00:00
gcode + = m_writer . set_acceleration ( ( unsigned int ) floor ( m_config . default_acceleration . value + 0.5 ) ) ;
2015-07-02 16:57:40 +00:00
return gcode ;
}
2017-05-03 16:28:22 +00:00
// Extrude perimeters: Decide where to put seams (hide or align seams).
2017-05-10 09:25:57 +00: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 15:37:30 +00:00
{
std : : string gcode ;
2017-05-10 09:25:57 +00:00
for ( const ObjectByExtruder : : Island : : Region & region : by_region ) {
2018-09-11 12:04:47 +00:00
m_config . apply ( print . regions ( ) [ & region - & by_region . front ( ) ] - > config ( ) ) ;
2017-05-03 16:28:22 +00:00
for ( ExtrusionEntity * ee : region . perimeters . entities )
2017-05-15 09:32:59 +00:00
gcode + = this - > extrude_entity ( * ee , " perimeter " , - 1. , & lower_layer_edge_grid ) ;
2017-05-03 16:28:22 +00:00
}
return gcode ;
}
// Chain the paths hierarchically by a greedy algorithm to minimize a travel distance.
2017-05-10 09:25:57 +00:00
std : : string GCode : : extrude_infill ( const Print & print , const std : : vector < ObjectByExtruder : : Island : : Region > & by_region )
2017-05-03 16:28:22 +00:00
{
std : : string gcode ;
2017-05-10 09:25:57 +00:00
for ( const ObjectByExtruder : : Island : : Region & region : by_region ) {
2018-09-11 12:04:47 +00:00
m_config . apply ( print . regions ( ) [ & region - & by_region . front ( ) ] - > config ( ) ) ;
2017-05-03 16:28:22 +00:00
ExtrusionEntityCollection chained = region . infills . chained_path_from ( m_last_pos , false ) ;
for ( ExtrusionEntity * fill : chained . entities ) {
auto * eec = dynamic_cast < ExtrusionEntityCollection * > ( fill ) ;
if ( eec ) {
ExtrusionEntityCollection chained2 = eec - > chained_path_from ( m_last_pos , false ) ;
for ( ExtrusionEntity * ee : chained2 . entities )
2017-05-10 09:25:57 +00:00
gcode + = this - > extrude_entity ( * ee , " infill " ) ;
2017-05-03 16:28:22 +00:00
} else
2017-05-10 09:25:57 +00:00
gcode + = this - > extrude_entity ( * fill , " infill " ) ;
2017-05-03 16:28:22 +00:00
}
}
return gcode ;
}
2017-05-10 09:25:57 +00:00
std : : string GCode : : extrude_support ( const ExtrusionEntityCollection & support_fills )
2017-05-03 16:28:22 +00:00
{
std : : string gcode ;
if ( ! support_fills . entities . empty ( ) ) {
2017-04-07 15:37:30 +00:00
const char * support_label = " support material " ;
const char * support_interface_label = " support material interface " ;
2017-05-03 16:28:22 +00: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 15:37:30 +00: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 09:25:57 +00:00
gcode + = this - > extrude_path ( * path , label , speed ) ;
2017-04-07 15:37:30 +00:00
else {
const ExtrusionMultiPath * multipath = dynamic_cast < const ExtrusionMultiPath * > ( ee ) ;
assert ( multipath ! = nullptr ) ;
if ( multipath )
2017-05-10 09:25:57 +00:00
gcode + = this - > extrude_multi_path ( * multipath , label , speed ) ;
2017-04-07 15:37:30 +00:00
}
}
}
return gcode ;
}
2018-01-08 12:44:10 +00:00
void GCode : : _write ( FILE * file , const char * what )
2017-12-14 08:18:28 +00:00
{
2018-01-08 12:44:10 +00:00
if ( what ! = nullptr ) {
// apply analyzer, if enabled
const char * gcode = m_enable_analyzer ? m_analyzer . process_gcode ( what ) . c_str ( ) : what ;
// writes string to file
fwrite ( gcode , 1 , : : strlen ( gcode ) , file ) ;
// updates time estimator and gcode lines vector
2018-06-27 13:35:47 +00:00
m_normal_time_estimator . add_gcode_block ( gcode ) ;
2018-06-22 12:01:27 +00:00
if ( m_silent_time_estimator_enabled )
2018-06-06 10:21:24 +00:00
m_silent_time_estimator . add_gcode_block ( gcode ) ;
2017-12-14 08:18:28 +00:00
}
}
2018-01-03 16:29:49 +00:00
void GCode : : _writeln ( FILE * file , const std : : string & what )
2017-12-14 08:18:28 +00:00
{
2018-01-03 16:29:49 +00:00
if ( ! what . empty ( ) )
_write ( file , ( what . back ( ) = = ' \n ' ) ? what : ( what + ' \n ' ) ) ;
2017-12-14 08:18:28 +00:00
}
void GCode : : _write_format ( FILE * file , const char * format , . . . )
{
2018-01-02 09:57:30 +00:00
va_list args ;
va_start ( args , format ) ;
2018-01-03 19:53:39 +00: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 16:29:49 +00: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 19:53:39 +00:00
if ( res > 0 )
2018-01-08 12:44:10 +00:00
_write ( file , bufptr ) ;
2018-01-17 09:39:05 +00:00
2018-01-03 16:29:49 +00:00
if ( buffer_dynamic )
free ( bufptr ) ;
2018-01-03 19:53:39 +00:00
va_end ( args ) ;
2017-12-14 08:18:28 +00:00
}
2017-05-10 09:25:57 +00:00
std : : string GCode : : _extrude ( const ExtrusionPath & path , std : : string description , double speed )
2015-07-02 16:57:40 +00:00
{
std : : string gcode ;
// go to first point of extrusion path
2018-08-15 11:51:40 +00:00
if ( ! m_last_pos_defined | | m_last_pos ! = path . first_point ( ) ) {
2015-07-02 16:57:40 +00:00
gcode + = this - > travel_to (
path . first_point ( ) ,
2017-04-07 15:37:30 +00:00
path . role ( ) ,
2015-07-02 16:57:40 +00:00
" move to first " + description + " point "
) ;
}
// compensate retraction
gcode + = this - > unretract ( ) ;
// adjust acceleration
{
double acceleration ;
2017-05-10 09:25:57 +00:00
if ( this - > on_first_layer ( ) & & m_config . first_layer_acceleration . value > 0 ) {
2017-05-03 16:28:22 +00: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 16:57:40 +00:00
} else {
2017-05-03 16:28:22 +00:00
acceleration = m_config . default_acceleration . value ;
2015-07-02 16:57:40 +00:00
}
2017-05-10 09:25:57 +00:00
gcode + = m_writer . set_acceleration ( ( unsigned int ) floor ( acceleration + 0.5 ) ) ;
2015-07-02 16:57:40 +00:00
}
// calculate extrusion length per distance unit
2017-06-22 10:59:23 +00:00
double e_per_mm = m_writer . extruder ( ) - > e_per_mm3 ( ) * path . mm3_per_mm ;
2017-05-03 16:28:22 +00:00
if ( m_writer . extrusion_axis ( ) . empty ( ) ) e_per_mm = 0 ;
2015-07-02 16:57:40 +00:00
// set speed
if ( speed = = - 1 ) {
2017-04-07 15:37:30 +00:00
if ( path . role ( ) = = erPerimeter ) {
2017-05-03 16:28:22 +00:00
speed = m_config . get_abs_value ( " perimeter_speed " ) ;
2017-04-07 15:37:30 +00:00
} else if ( path . role ( ) = = erExternalPerimeter ) {
2017-05-03 16:28:22 +00:00
speed = m_config . get_abs_value ( " external_perimeter_speed " ) ;
2017-04-07 15:37:30 +00:00
} else if ( path . role ( ) = = erOverhangPerimeter | | path . role ( ) = = erBridgeInfill ) {
2017-05-03 16:28:22 +00:00
speed = m_config . get_abs_value ( " bridge_speed " ) ;
2017-04-07 15:37:30 +00:00
} else if ( path . role ( ) = = erInternalInfill ) {
2017-05-03 16:28:22 +00:00
speed = m_config . get_abs_value ( " infill_speed " ) ;
2017-04-07 15:37:30 +00:00
} else if ( path . role ( ) = = erSolidInfill ) {
2017-05-03 16:28:22 +00:00
speed = m_config . get_abs_value ( " solid_infill_speed " ) ;
2017-04-07 15:37:30 +00:00
} else if ( path . role ( ) = = erTopSolidInfill ) {
2017-05-03 16:28:22 +00:00
speed = m_config . get_abs_value ( " top_solid_infill_speed " ) ;
2017-04-07 15:37:30 +00:00
} else if ( path . role ( ) = = erGapFill ) {
2017-05-03 16:28:22 +00:00
speed = m_config . get_abs_value ( " gap_fill_speed " ) ;
2015-07-02 16:57:40 +00:00
} else {
2018-09-18 08:09:58 +00:00
throw std : : invalid_argument ( " Invalid speed " ) ;
2015-07-02 16:57:40 +00:00
}
}
2017-05-10 09:25:57 +00:00
if ( this - > on_first_layer ( ) )
2017-05-03 16:28:22 +00:00
speed = m_config . get_abs_value ( " first_layer_speed " , speed ) ;
2017-05-10 09:25:57 +00:00
if ( m_volumetric_speed ! = 0. & & speed = = 0 )
2017-05-03 16:28:22 +00:00
speed = m_volumetric_speed / path . mm3_per_mm ;
if ( m_config . max_volumetric_speed . value > 0 ) {
2015-07-02 16:57:40 +00:00
// cap speed with max_volumetric_speed anyway (even if user is not using autospeed)
speed = std : : min (
speed ,
2017-05-03 16:28:22 +00:00
m_config . max_volumetric_speed . value / path . mm3_per_mm
2015-07-02 16:57:40 +00:00
) ;
}
2016-11-01 12:41:24 +00: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
) ;
}
2015-07-02 16:57:40 +00:00
double F = speed * 60 ; // convert mm/sec to mm/min
// extrude arc or line
2018-05-07 12:23:07 +00:00
if ( m_enable_extrusion_role_markers )
2018-01-08 12:44:10 +00: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 ;
}
}
}
// adds analyzer tags and updates analyzer's tracking data
if ( m_enable_analyzer )
{
2018-05-07 12:23:07 +00:00
if ( path . role ( ) ! = m_last_analyzer_extrusion_role )
{
m_last_analyzer_extrusion_role = path . role ( ) ;
char buf [ 32 ] ;
sprintf ( buf , " ;%s%d \n " , GCodeAnalyzer : : Extrusion_Role_Tag . c_str ( ) , int ( m_last_analyzer_extrusion_role ) ) ;
gcode + = buf ;
}
2018-01-08 12:44:10 +00:00
if ( m_last_mm3_per_mm ! = path . mm3_per_mm )
{
m_last_mm3_per_mm = path . mm3_per_mm ;
char buf [ 32 ] ;
sprintf ( buf , " ;%s%f \n " , GCodeAnalyzer : : Mm3_Per_Mm_Tag . c_str ( ) , m_last_mm3_per_mm ) ;
gcode + = buf ;
}
if ( m_last_width ! = path . width )
{
m_last_width = path . width ;
char buf [ 32 ] ;
sprintf ( buf , " ;%s%f \n " , GCodeAnalyzer : : Width_Tag . c_str ( ) , m_last_width ) ;
gcode + = buf ;
}
if ( m_last_height ! = path . height )
{
m_last_height = path . height ;
char buf [ 32 ] ;
sprintf ( buf , " ;%s%f \n " , GCodeAnalyzer : : Height_Tag . c_str ( ) , m_last_height ) ;
gcode + = buf ;
}
}
2017-06-30 15:05:58 +00: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 12:23:07 +00:00
2017-06-30 15:05:58 +00:00
// F is mm per minute.
gcode + = m_writer . set_speed ( F , " " , comment ) ;
2017-06-22 10:59:23 +00:00
double path_length = 0. ;
2015-07-02 16:57:40 +00:00
{
2017-05-03 16:28:22 +00:00
std : : string comment = m_config . gcode_comments ? description : " " ;
2017-05-10 09:25:57 +00:00
for ( const Line & line : path . polyline . lines ( ) ) {
const double line_length = line . length ( ) * SCALING_FACTOR ;
2015-07-02 17:14:55 +00:00
path_length + = line_length ;
2017-05-03 16:28:22 +00:00
gcode + = m_writer . extrude_to_xy (
2017-05-10 09:25:57 +00:00
this - > point_to_gcode ( line . b ) ,
2015-07-02 17:14:55 +00:00
e_per_mm * line_length ,
2017-05-10 09:25:57 +00:00
comment ) ;
2015-07-02 16:57:40 +00:00
}
}
2017-06-30 15:05:58 +00:00
if ( m_enable_cooling_markers )
gcode + = is_bridge ( path . role ( ) ) ? " ;_BRIDGE_FAN_END \n " : " ;_EXTRUDE_END \n " ;
2015-07-02 17:14:55 +00:00
2015-07-02 16:57:40 +00:00
this - > set_last_pos ( path . last_point ( ) ) ;
return gcode ;
}
2015-07-02 13:12:04 +00:00
// This method accepts &point in print coordinates.
2017-05-18 14:53:19 +00:00
std : : string GCode : : travel_to ( const Point & point , ExtrusionRole role , std : : string comment )
2015-07-02 13:12:04 +00: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 16:57:40 +00:00
this - > origin in order to get G - code coordinates . */
2015-07-02 13:12:04 +00:00
Polyline travel ;
travel . append ( this - > last_pos ( ) ) ;
travel . append ( point ) ;
// check whether a straight travel move would need retraction
bool needs_retraction = this - > needs_retraction ( travel , role ) ;
// 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 16:28:22 +00:00
& & m_config . avoid_crossing_perimeters
& & ! m_avoid_crossing_perimeters . disable_once ) {
travel = m_avoid_crossing_perimeters . travel_to ( * this , point ) ;
2015-07-02 13:12:04 +00:00
// check again whether the new travel path still needs a retraction
needs_retraction = this - > needs_retraction ( travel , role ) ;
2017-05-03 16:28:22 +00:00
//if (needs_retraction && m_layer_index > 1) exit(0);
2015-07-02 13:12:04 +00:00
}
// Re-allow avoid_crossing_perimeters for the next travel moves
2017-05-03 16:28:22 +00:00
m_avoid_crossing_perimeters . disable_once = false ;
m_avoid_crossing_perimeters . use_external_mp_once = false ;
2015-07-02 13:12:04 +00:00
// generate G-code for the travel move
std : : string gcode ;
2017-03-29 15:45:38 +00: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 16:28:22 +00:00
m_wipe . reset_path ( ) ;
2015-07-02 13:12:04 +00:00
// use G1 because we rely on paths being straight (G0 may make round paths)
Lines lines = travel . lines ( ) ;
2019-01-14 18:57:41 +00:00
if ( ! lines . empty ( ) ) {
for ( const Line & line : lines )
gcode + = m_writer . travel_to_xy ( this - > point_to_gcode ( line . b ) , comment ) ;
this - > set_last_pos ( lines . back ( ) . b ) ;
}
2015-07-02 13:12:04 +00:00
return gcode ;
}
2017-07-10 11:15:36 +00:00
bool GCode : : needs_retraction ( const Polyline & travel , ExtrusionRole role )
2015-07-01 21:14:40 +00:00
{
2015-07-02 12:31:21 +00:00
if ( travel . length ( ) < scale_ ( EXTRUDER_CONFIG ( retract_before_travel ) ) ) {
2015-07-01 21:14:40 +00:00
// skip retraction if the move is shorter than the configured threshold
return false ;
}
if ( role = = erSupportMaterial ) {
2017-05-03 16:28:22 +00:00
const SupportLayer * support_layer = dynamic_cast < const SupportLayer * > ( m_layer ) ;
2017-03-07 12:03:14 +00:00
//FIXME support_layer->support_islands.contains should use some search structure!
2017-05-10 09:25:57 +00:00
if ( support_layer ! = NULL & & support_layer - > support_islands . contains ( travel ) )
2015-07-01 21:14:40 +00:00
// skip retraction if this is a travel move inside a support material island
2017-05-10 09:25:57 +00: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 21:14:40 +00:00
return false ;
}
2017-05-10 09:25:57 +00:00
2017-07-10 11:15:36 +00: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 ;
2015-07-01 21:14:40 +00:00
// retract if only_retract_when_crossing_perimeters is disabled or doesn't apply
return true ;
}
2018-01-02 09:57:30 +00:00
std : : string GCode : : retract ( bool toolchange )
2015-07-01 21:00:52 +00:00
{
std : : string gcode ;
2017-05-10 09:25:57 +00:00
if ( m_writer . extruder ( ) = = nullptr )
2015-07-01 21:00:52 +00:00
return gcode ;
// wipe (if it's enabled for this extruder and we have a stored wipe path)
2017-05-19 17:24:21 +00:00
if ( EXTRUDER_CONFIG ( wipe ) & & m_wipe . has_path ( ) ) {
gcode + = toolchange ? m_writer . retract_for_toolchange ( true ) : m_writer . retract ( true ) ;
2017-05-03 16:28:22 +00:00
gcode + = m_wipe . wipe ( * this , toolchange ) ;
2017-05-19 17:24:21 +00:00
}
2015-07-01 21:00:52 +00:00
/* The parent class will decide whether we need to perform an actual retraction
( the extruder might be already retracted fully or partially ) . We call these
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 16:28:22 +00:00
gcode + = toolchange ? m_writer . retract_for_toolchange ( ) : m_writer . retract ( ) ;
2015-07-01 21:00:52 +00:00
2017-05-03 16:28:22 +00:00
gcode + = m_writer . reset_e ( ) ;
if ( m_writer . extruder ( ) - > retract_length ( ) > 0 | | m_config . use_firmware_retraction )
gcode + = m_writer . lift ( ) ;
2015-07-01 21:00:52 +00:00
return gcode ;
}
2017-05-10 09:25:57 +00:00
std : : string GCode : : set_extruder ( unsigned int extruder_id )
2015-07-02 13:02:20 +00:00
{
2017-05-03 16:28:22 +00:00
if ( ! m_writer . need_toolchange ( extruder_id ) )
2015-07-02 13:02:20 +00:00
return " " ;
// if we are running a single-extruder setup, just set the extruder and return nothing
2017-11-30 15:01:47 +00:00
if ( ! m_writer . multiple_extruders ) {
m_placeholder_parser . set ( " current_extruder " , extruder_id ) ;
2017-05-03 16:28:22 +00:00
return m_writer . toolchange ( extruder_id ) ;
2017-11-30 15:01:47 +00:00
}
2015-07-02 13:02:20 +00:00
// prepend retraction on the current extruder
std : : string gcode = this - > retract ( true ) ;
2017-03-29 15:45:38 +00:00
// Always reset the extrusion path, even if the tool change retract is set to zero.
2017-05-03 16:28:22 +00:00
m_wipe . reset_path ( ) ;
2015-07-02 13:02:20 +00:00
2017-11-30 15:01:47 +00:00
if ( m_writer . extruder ( ) ! = nullptr ) {
// Process the custom end_filament_gcode in case of single_extruder_multi_material.
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 ) ;
if ( m_config . single_extruder_multi_material & & ! end_filament_gcode . empty ( ) ) {
2017-12-05 14:54:24 +00:00
gcode + = placeholder_parser_process ( " end_filament_gcode " , end_filament_gcode , old_extruder_id ) ;
2017-11-30 15:01:47 +00:00
check_add_eol ( gcode ) ;
}
}
m_placeholder_parser . set ( " current_extruder " , extruder_id ) ;
if ( m_writer . extruder ( ) ! = nullptr & & ! m_config . toolchange_gcode . value . empty ( ) ) {
// Process the custom toolchange_gcode.
2017-11-17 10:15:46 +00:00
DynamicConfig config ;
config . set_key_value ( " previous_extruder " , new ConfigOptionInt ( ( int ) m_writer . extruder ( ) - > id ( ) ) ) ;
config . set_key_value ( " next_extruder " , new ConfigOptionInt ( ( int ) extruder_id ) ) ;
2017-12-05 14:54:24 +00:00
gcode + = placeholder_parser_process ( " toolchange_gcode " , m_config . toolchange_gcode . value , extruder_id , & config ) ;
2017-11-30 15:01:47 +00:00
check_add_eol ( gcode ) ;
2015-07-02 13:02:20 +00:00
}
2017-11-30 15:01:47 +00:00
// If ooze prevention is enabled, park current extruder in the nearest
// standby point and set it to the standby temperature.
2017-05-25 20:52:28 +00:00
if ( m_ooze_prevention . enable & & m_writer . extruder ( ) ! = nullptr )
2017-05-18 14:53:19 +00:00
gcode + = m_ooze_prevention . pre_toolchange ( * this ) ;
2017-11-30 15:01:47 +00:00
// Append the toolchange command.
2017-05-18 14:53:19 +00:00
gcode + = m_writer . toolchange ( extruder_id ) ;
2017-11-30 15:01:47 +00:00
// Append the filament start G-code for single_extruder_multi_material.
const std : : string & start_filament_gcode = m_config . start_filament_gcode . get_at ( extruder_id ) ;
if ( m_config . single_extruder_multi_material & & ! start_filament_gcode . empty ( ) ) {
// Process the start_filament_gcode for the active filament only.
2017-12-05 14:54:24 +00:00
gcode + = this - > placeholder_parser_process ( " start_filament_gcode " , start_filament_gcode , extruder_id ) ;
2017-11-30 15:01:47 +00:00
check_add_eol ( gcode ) ;
}
// Set the new extruder to the operating temperature.
2017-05-18 14:53:19 +00:00
if ( m_ooze_prevention . enable )
gcode + = m_ooze_prevention . post_toolchange ( * this ) ;
2015-07-02 13:02:20 +00:00
return gcode ;
}
2015-07-01 21:00:52 +00:00
// convert a model-space scaled point into G-code coordinates
2018-08-21 19:05:24 +00:00
Vec2d GCode : : point_to_gcode ( const Point & point ) const
2015-07-01 19:47:17 +00:00
{
2018-08-21 19:05:24 +00:00
Vec2d extruder_offset = EXTRUDER_CONFIG ( extruder_offset ) ;
2018-08-21 15:43:05 +00:00
return unscale ( point ) + m_origin - extruder_offset ;
2017-05-18 14:53:19 +00:00
}
// convert a model-space scaled point into G-code coordinates
2018-08-21 19:05:24 +00:00
Point GCode : : gcode_to_point ( const Vec2d & point ) const
2017-05-18 14:53:19 +00:00
{
2018-08-21 19:05:24 +00:00
Vec2d extruder_offset = EXTRUDER_CONFIG ( extruder_offset ) ;
2017-05-18 14:53:19 +00:00
return Point (
2018-08-17 13:53:43 +00:00
scale_ ( point ( 0 ) - m_origin ( 0 ) + extruder_offset ( 0 ) ) ,
scale_ ( point ( 1 ) - m_origin ( 1 ) + extruder_offset ( 1 ) ) ) ;
2015-07-01 19:47:17 +00:00
}
2018-06-20 10:52:00 +00: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)
// Returns a reference to member to avoid copying.
const std : : vector < GCode : : ObjectByExtruder : : Island : : Region > & GCode : : ObjectByExtruder : : Island : : by_region_per_copy ( unsigned int copy , int extruder , bool wiping_entities )
2018-05-31 14:21:10 +00:00
{
2018-06-20 10:52:00 +00:00
by_region_per_copy_cache . clear ( ) ;
2018-06-06 16:24:42 +00:00
for ( const auto & reg : by_region ) {
2018-06-20 10:52:00 +00:00
by_region_per_copy_cache . push_back ( ObjectByExtruder : : Island : : Region ( ) ) ; // creates a region in the newly created Island
// 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 ) {
const ExtrusionEntitiesPtr & entities = ( iter ? reg . infills . entities : reg . perimeters . entities ) ;
ExtrusionEntityCollection & target_eec = ( iter ? by_region_per_copy_cache . back ( ) . infills : by_region_per_copy_cache . back ( ) . perimeters ) ;
const std : : vector < const ExtruderPerCopy * > & overrides = ( iter ? reg . infills_overrides : reg . perimeters_overrides ) ;
// Now the most important thing - which extrusion should we print.
// See function ToolOrdering::get_extruder_overrides for details about the negative numbers hack.
int this_extruder_mark = wiping_entities ? extruder : - extruder - 1 ;
for ( unsigned int i = 0 ; i < entities . size ( ) ; + + i )
if ( overrides [ i ] - > at ( copy ) = = this_extruder_mark ) // this copy should be printed with this extruder
target_eec . append ( ( * entities [ i ] ) ) ;
}
}
return by_region_per_copy_cache ;
2015-07-01 18:14:05 +00:00
}
2018-05-31 14:21:10 +00:00
2018-06-07 14:19:57 +00:00
2018-06-20 10:52:00 +00: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.
void GCode : : ObjectByExtruder : : Island : : Region : : append ( const std : : string & type , const ExtrusionEntityCollection * eec , const ExtruderPerCopy * copies_extruder , unsigned int object_copies_num )
{
// 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:
ExtrusionEntityCollection * perimeters_or_infills = & infills ;
std : : vector < const ExtruderPerCopy * > * perimeters_or_infills_overrides = & infills_overrides ;
if ( type = = " perimeters " ) {
perimeters_or_infills = & perimeters ;
perimeters_or_infills_overrides = & perimeters_overrides ;
2018-05-31 14:21:10 +00:00
}
2018-06-20 10:52:00 +00:00
else
if ( type ! = " infills " ) {
2018-09-18 08:09:58 +00:00
throw std : : invalid_argument ( " Unknown parameter! " ) ;
2018-06-20 10:52:00 +00:00
return ;
}
// First we append the entities, there are eec->entities.size() of them:
perimeters_or_infills - > append ( eec - > entities ) ;
for ( unsigned int i = 0 ; i < eec - > entities . size ( ) ; + + i )
perimeters_or_infills_overrides - > push_back ( copies_extruder ) ;
2015-07-01 18:14:05 +00:00
}
2018-05-31 14:21:10 +00:00
} // namespace Slic3r