2017-05-25 22:27:53 +02:00
#include "Print.hpp"
2017-05-16 13:45:28 +02:00
#include "ToolOrdering.hpp"
2017-12-11 17:19:55 +01:00
// #define SLIC3R_DEBUG
// Make assert active if SLIC3R_DEBUG
#ifdef SLIC3R_DEBUG
#define DEBUG
#define _DEBUG
#undef NDEBUG
#endif
#include <cassert>
2017-06-09 14:24:00 +02:00
#include <limits>
2017-05-23 15:00:01 +02:00
2020-01-10 11:26:52 +01:00
#include <libslic3r.h>
2020-01-14 11:54:09 +01:00
#include "../GCodeWriter.hpp"
2017-05-16 13:45:28 +02:00
namespace Slic3r {
2017-05-23 15:00:01 +02:00
2018-07-13 11:25:22 +02:00
// Returns true in case that extruder a comes before b (b does not have to be present). False otherwise.
bool LayerTools :: is_extruder_order ( unsigned int a , unsigned int b ) const
{
2020-01-08 14:58:12 +01:00
if ( a == b )
2018-07-13 11:25:22 +02:00
return false ;
for ( auto extruder : extruders ) {
if ( extruder == a )
return true ;
if ( extruder == b )
return false ;
}
return false ;
}
2020-01-14 10:31:18 +01:00
// Return a zero based extruder from the region, or extruder_override if overriden.
unsigned int LayerTools :: perimeter_extruder ( const PrintRegion & region ) const
{
assert ( region . config (). perimeter_extruder . value > 0 );
return (( this -> extruder_override == 0 ) ? region . config (). perimeter_extruder . value : this -> extruder_override ) - 1 ;
}
unsigned int LayerTools :: infill_extruder ( const PrintRegion & region ) const
{
assert ( region . config (). infill_extruder . value > 0 );
return (( this -> extruder_override == 0 ) ? region . config (). infill_extruder . value : this -> extruder_override ) - 1 ;
}
unsigned int LayerTools :: solid_infill_extruder ( const PrintRegion & region ) const
{
assert ( region . config (). solid_infill_extruder . value > 0 );
return (( this -> extruder_override == 0 ) ? region . config (). solid_infill_extruder . value : this -> extruder_override ) - 1 ;
}
// Returns a zero based extruder this eec should be printed with, according to PrintRegion config or extruder_override if overriden.
unsigned int LayerTools :: extruder ( const ExtrusionEntityCollection & extrusions , const PrintRegion & region ) const
{
assert ( region . config (). perimeter_extruder . value > 0 );
assert ( region . config (). infill_extruder . value > 0 );
assert ( region . config (). solid_infill_extruder . value > 0 );
// 1 based extruder ID.
unsigned int extruder = (( this -> extruder_override == 0 ) ?
( is_infill ( extrusions . role ()) ?
( is_solid_infill ( extrusions . entities . front () -> role ()) ? region . config (). solid_infill_extruder : region . config (). infill_extruder ) :
region . config (). perimeter_extruder . value ) :
this -> extruder_override );
return ( extruder == 0 ) ? 0 : extruder - 1 ;
}
2018-07-13 11:25:22 +02:00
2017-05-23 15:00:01 +02:00
// For the use case when each object is printed separately
2018-09-11 14:04:47 +02:00
// (print.config().complete_objects is true).
2017-09-01 17:30:18 +02:00
ToolOrdering :: ToolOrdering ( const PrintObject & object , unsigned int first_extruder , bool prime_multi_material )
2017-05-23 15:00:01 +02:00
{
2018-09-11 14:04:47 +02:00
if ( object . layers (). empty ())
2017-06-08 16:58:29 +02:00
return ;
2017-05-23 15:00:01 +02:00
// Initialize the print layers for just a single object.
{
std :: vector < coordf_t > zs ;
2018-09-11 14:04:47 +02:00
zs . reserve ( zs . size () + object . layers (). size () + object . support_layers (). size ());
for ( auto layer : object . layers ())
2017-05-23 15:00:01 +02:00
zs . emplace_back ( layer -> print_z );
2018-09-11 14:04:47 +02:00
for ( auto layer : object . support_layers ())
2017-05-23 15:00:01 +02:00
zs . emplace_back ( layer -> print_z );
this -> initialize_layers ( zs );
}
// Collect extruders reuqired to print the layers.
2020-01-14 12:10:01 +01:00
this -> collect_extruders ( object , std :: vector < std :: pair < double , unsigned int >> ());
2017-05-23 15:00:01 +02:00
// Reorder the extruders to minimize tool switches.
this -> reorder_extruders ( first_extruder );
2018-09-11 14:04:47 +02:00
this -> fill_wipe_tower_partitions ( object . print () -> config (), object . layers (). front () -> print_z - object . layers (). front () -> height );
2017-09-01 17:30:18 +02:00
this -> collect_extruder_statistics ( prime_multi_material );
2017-05-23 15:00:01 +02:00
}
// For the use case when all objects are printed at once.
2018-09-11 14:04:47 +02:00
// (print.config().complete_objects is false).
2020-01-14 11:54:09 +01:00
ToolOrdering :: ToolOrdering ( const Print & print , unsigned int first_extruder , bool prime_multi_material )
2017-05-23 15:00:01 +02:00
{
2018-09-12 11:59:02 +02:00
m_print_config_ptr = & print . config ();
2018-07-18 09:37:25 +02:00
2017-09-01 17:30:18 +02:00
// Initialize the print layers for all objects and all layers.
2017-06-08 16:58:29 +02:00
coordf_t object_bottom_z = 0. ;
2017-05-23 15:00:01 +02:00
{
std :: vector < coordf_t > zs ;
2018-11-08 14:23:17 +01:00
for ( auto object : print . objects ()) {
2018-09-11 14:04:47 +02:00
zs . reserve ( zs . size () + object -> layers (). size () + object -> support_layers (). size ());
for ( auto layer : object -> layers ())
2017-05-23 15:00:01 +02:00
zs . emplace_back ( layer -> print_z );
2018-09-11 14:04:47 +02:00
for ( auto layer : object -> support_layers ())
2017-05-23 15:00:01 +02:00
zs . emplace_back ( layer -> print_z );
2019-08-02 16:49:22 +02:00
// Find first object layer that is not empty and save its print_z
for ( const Layer * layer : object -> layers ())
if ( layer -> has_extrusions ()) {
object_bottom_z = layer -> print_z - layer -> height ;
break ;
}
2017-05-23 15:00:01 +02:00
}
this -> initialize_layers ( zs );
}
2020-01-14 11:54:09 +01:00
// Use the extruder switches from Model::custom_gcode_per_print_z to override the extruder to print the object.
// Do it only if all the objects were configured to be printed with a single extruder.
2020-01-14 12:10:01 +01:00
std :: vector < std :: pair < double , unsigned int >> per_layer_extruder_switches ;
2020-01-16 13:39:03 +01:00
if ( auto num_extruders = unsigned ( print . config (). nozzle_diameter . size ());
num_extruders > 1 && print . object_extruders (). size () == 1 ) {
// Printing a single extruder platter on a printer with more than 1 extruder (or single-extruder multi-material).
// There may be custom per-layer tool changes available at the model.
per_layer_extruder_switches = custom_tool_changes ( print . model (), num_extruders );
}
2020-01-14 11:54:09 +01:00
2017-05-23 15:00:01 +02:00
// Collect extruders reuqired to print the layers.
2018-11-08 14:23:17 +01:00
for ( auto object : print . objects ())
2020-01-14 11:54:09 +01:00
this -> collect_extruders ( * object , per_layer_extruder_switches );
2017-05-23 15:00:01 +02:00
// Reorder the extruders to minimize tool switches.
this -> reorder_extruders ( first_extruder );
2018-09-11 14:04:47 +02:00
this -> fill_wipe_tower_partitions ( print . config (), object_bottom_z );
2017-05-23 15:00:01 +02:00
2017-09-01 17:30:18 +02:00
this -> collect_extruder_statistics ( prime_multi_material );
2020-01-14 11:54:09 +01:00
// Assign custom G-code actions from Model::custom_gcode_per_print_z to their respecive layers,
// ignoring the extruder switches, which were processed above, and ignoring color changes for extruders,
// that do not print above their respective print_z.
this -> assign_custom_gcodes ( print );
2017-05-23 15:00:01 +02:00
}
void ToolOrdering :: initialize_layers ( std :: vector < coordf_t > & zs )
{
sort_remove_duplicates ( zs );
// Merge numerically very close Z values.
for ( size_t i = 0 ; i < zs . size ();) {
// Find the last layer with roughly the same print_z.
size_t j = i + 1 ;
coordf_t zmax = zs [ i ] + EPSILON ;
for (; j < zs . size () && zs [ j ] <= zmax ; ++ j ) ;
// Assign an average print_z to the set of layers with nearly equal print_z.
2020-01-09 10:32:52 +01:00
m_layer_tools . emplace_back ( LayerTools ( 0.5 * ( zs [ i ] + zs [ j - 1 ])));
2017-05-23 15:00:01 +02:00
i = j ;
}
}
2017-05-16 13:45:28 +02:00
// Collect extruders reuqired to print layers.
2020-01-14 12:10:01 +01:00
void ToolOrdering :: collect_extruders ( const PrintObject & object , const std :: vector < std :: pair < double , unsigned int >> & per_layer_extruder_switches )
2017-05-16 13:45:28 +02:00
{
// Collect the support extruders.
2018-09-11 14:04:47 +02:00
for ( auto support_layer : object . support_layers ()) {
2017-05-23 15:00:01 +02:00
LayerTools & layer_tools = this -> tools_for_layer ( support_layer -> print_z );
2017-05-16 13:45:28 +02:00
ExtrusionRole role = support_layer -> support_fills . role ();
bool has_support = role == erMixed || role == erSupportMaterial ;
bool has_interface = role == erMixed || role == erSupportMaterialInterface ;
2018-09-11 14:04:47 +02:00
unsigned int extruder_support = object . config (). support_material_extruder . value ;
unsigned int extruder_interface = object . config (). support_material_interface_extruder . value ;
2017-05-16 13:45:28 +02:00
if ( has_support )
2017-05-23 15:00:01 +02:00
layer_tools . extruders . push_back ( extruder_support );
2017-05-16 13:45:28 +02:00
if ( has_interface )
2017-05-23 15:00:01 +02:00
layer_tools . extruders . push_back ( extruder_interface );
2017-05-19 19:24:21 +02:00
if ( has_support || has_interface )
2017-05-23 15:00:01 +02:00
layer_tools . has_support = true ;
2017-05-16 13:45:28 +02:00
}
2020-01-14 10:31:18 +01:00
// Extruder overrides are ordered by print_z.
std :: vector < std :: pair < double , unsigned int >>:: const_iterator it_per_layer_extruder_override ;
2020-01-14 12:10:01 +01:00
it_per_layer_extruder_override = per_layer_extruder_switches . begin ();
2020-01-14 10:31:18 +01:00
unsigned int extruder_override = 0 ;
2017-05-16 13:45:28 +02:00
// Collect the object extruders.
2018-09-11 14:04:47 +02:00
for ( auto layer : object . layers ()) {
2017-05-23 15:00:01 +02:00
LayerTools & layer_tools = this -> tools_for_layer ( layer -> print_z );
2020-01-14 10:31:18 +01:00
// Override extruder with the next
2020-01-14 12:10:01 +01:00
for (; it_per_layer_extruder_override != per_layer_extruder_switches . end () && it_per_layer_extruder_override -> first < layer -> print_z + EPSILON ; ++ it_per_layer_extruder_override )
extruder_override = ( int ) it_per_layer_extruder_override -> second ;
2020-01-14 10:31:18 +01:00
// Store the current extruder override (set to zero if no overriden), so that layer_tools.wiping_extrusions().is_overridable_and_mark() will use it.
layer_tools . extruder_override = extruder_override ;
2017-05-16 13:45:28 +02:00
// What extruders are required to print this object layer?
2018-11-06 15:31:26 +01:00
for ( size_t region_id = 0 ; region_id < object . region_volumes . size (); ++ region_id ) {
2019-08-16 16:31:05 +02:00
const LayerRegion * layerm = ( region_id < layer -> regions (). size ()) ? layer -> regions ()[ region_id ] : nullptr ;
2017-05-16 13:45:28 +02:00
if ( layerm == nullptr )
continue ;
2018-09-11 14:04:47 +02:00
const PrintRegion & region = * object . print () -> regions ()[ region_id ];
2018-06-26 14:12:25 +02:00
2017-05-19 19:24:21 +02:00
if ( ! layerm -> perimeters . entities . empty ()) {
2018-06-27 15:49:02 +02:00
bool something_nonoverriddable = true ;
if ( m_print_config_ptr ) { // in this case complete_objects is false (see ToolOrdering constructors)
something_nonoverriddable = false ;
for ( const auto & eec : layerm -> perimeters . entities ) // let's check if there are nonoverriddable entities
2020-01-10 11:26:52 +01:00
if ( ! layer_tools . wiping_extrusions (). is_overriddable_and_mark ( dynamic_cast < const ExtrusionEntityCollection &> ( * eec ), * m_print_config_ptr , object , region )) {
2018-06-27 15:49:02 +02:00
something_nonoverriddable = true ;
break ;
}
}
2018-06-26 14:12:25 +02:00
if ( something_nonoverriddable )
2020-01-14 10:31:18 +01:00
layer_tools . extruders . emplace_back (( extruder_override == 0 ) ? region . config (). perimeter_extruder . value : extruder_override );
2018-06-26 14:12:25 +02:00
2017-05-23 15:00:01 +02:00
layer_tools . has_object = true ;
2017-05-19 19:24:21 +02:00
}
2018-06-26 14:12:25 +02:00
2017-05-19 19:24:21 +02:00
bool has_infill = false ;
2017-05-16 13:45:28 +02:00
bool has_solid_infill = false ;
2018-06-26 14:12:25 +02:00
bool something_nonoverriddable = false ;
2017-05-16 13:45:28 +02:00
for ( const ExtrusionEntity * ee : layerm -> fills . entities ) {
// fill represents infill extrusions of a single island.
const auto * fill = dynamic_cast < const ExtrusionEntityCollection *> ( ee );
ExtrusionRole role = fill -> entities . empty () ? erNone : fill -> entities . front () -> role ();
if ( is_solid_infill ( role ))
has_solid_infill = true ;
else if ( role != erNone )
has_infill = true ;
2018-06-26 14:12:25 +02:00
2018-06-27 15:49:02 +02:00
if ( m_print_config_ptr ) {
2020-01-10 11:26:52 +01:00
if ( ! something_nonoverriddable && ! layer_tools . wiping_extrusions (). is_overriddable_and_mark ( * fill , * m_print_config_ptr , object , region ))
2018-06-27 15:49:02 +02:00
something_nonoverriddable = true ;
}
2018-06-26 14:12:25 +02:00
}
2018-06-27 15:49:02 +02:00
2020-01-14 10:31:18 +01:00
if ( something_nonoverriddable || ! m_print_config_ptr ) {
if ( extruder_override == 0 ) {
if ( has_solid_infill )
layer_tools . extruders . emplace_back ( region . config (). solid_infill_extruder );
if ( has_infill )
layer_tools . extruders . emplace_back ( region . config (). infill_extruder );
} else if ( has_solid_infill || has_infill )
layer_tools . extruders . emplace_back ( extruder_override );
2017-05-16 13:45:28 +02:00
}
2017-05-19 19:24:21 +02:00
if ( has_solid_infill || has_infill )
2017-05-23 15:00:01 +02:00
layer_tools . has_object = true ;
2017-05-16 13:45:28 +02:00
}
}
2018-06-27 14:08:46 +02:00
for ( auto & layer : m_layer_tools ) {
// Sort and remove duplicates
sort_remove_duplicates ( layer . extruders );
2018-06-26 14:12:25 +02:00
2018-06-27 14:08:46 +02:00
// make sure that there are some tools for each object layer (e.g. tall wiping object will result in empty extruders vector)
if ( layer . extruders . empty () && layer . has_object )
2020-01-14 10:31:18 +01:00
layer . extruders . emplace_back ( 0 ); // 0="dontcare" extruder - it will be taken care of in reorder_extruders
2018-06-26 14:12:25 +02:00
}
2017-05-16 13:45:28 +02:00
}
// Reorder extruders to minimize layer changes.
2017-05-23 15:00:01 +02:00
void ToolOrdering :: reorder_extruders ( unsigned int last_extruder_id )
2017-05-16 13:45:28 +02:00
{
2017-05-23 15:00:01 +02:00
if ( m_layer_tools . empty ())
2017-05-16 13:45:28 +02:00
return ;
2017-05-16 15:30:03 +02:00
if ( last_extruder_id == ( unsigned int ) - 1 ) {
// The initial print extruder has not been decided yet.
// Initialize the last_extruder_id with the first non-zero extruder id used for the print.
last_extruder_id = 0 ;
2017-05-23 15:00:01 +02:00
for ( size_t i = 0 ; i < m_layer_tools . size () && last_extruder_id == 0 ; ++ i ) {
const LayerTools & lt = m_layer_tools [ i ];
2017-05-16 15:30:03 +02:00
for ( unsigned int extruder_id : lt . extruders )
if ( extruder_id > 0 ) {
last_extruder_id = extruder_id ;
break ;
}
}
if ( last_extruder_id == 0 )
// Nothing to extrude.
return ;
} else
// 1 based index
++ last_extruder_id ;
2017-05-16 13:45:28 +02:00
2017-05-23 15:00:01 +02:00
for ( LayerTools & lt : m_layer_tools ) {
2017-05-16 13:45:28 +02:00
if ( lt . extruders . empty ())
continue ;
if ( lt . extruders . size () == 1 && lt . extruders . front () == 0 )
lt . extruders . front () = last_extruder_id ;
else {
if ( lt . extruders . front () == 0 )
// Pop the "don't care" extruder, the "don't care" region will be merged with the next one.
lt . extruders . erase ( lt . extruders . begin ());
// Reorder the extruders to start with the last one.
for ( size_t i = 1 ; i < lt . extruders . size (); ++ i )
if ( lt . extruders [ i ] == last_extruder_id ) {
// Move the last extruder to the front.
memmove ( lt . extruders . data () + 1 , lt . extruders . data (), i * sizeof ( unsigned int ));
lt . extruders . front () = last_extruder_id ;
break ;
}
}
last_extruder_id = lt . extruders . back ();
}
// Reindex the extruders, so they are zero based, not 1 based.
2017-05-23 15:00:01 +02:00
for ( LayerTools & lt : m_layer_tools )
2017-05-16 13:45:28 +02:00
for ( unsigned int & extruder_id : lt . extruders ) {
assert ( extruder_id > 0 );
-- extruder_id ;
2020-01-14 10:31:18 +01:00
}
2017-05-16 13:45:28 +02:00
}
2017-06-08 16:58:29 +02:00
void ToolOrdering :: fill_wipe_tower_partitions ( const PrintConfig & config , coordf_t object_bottom_z )
2017-05-16 13:45:28 +02:00
{
2017-05-23 15:00:01 +02:00
if ( m_layer_tools . empty ())
2017-05-16 13:45:28 +02:00
return ;
// Count the minimum number of tool changes per layer.
2017-05-16 15:30:03 +02:00
size_t last_extruder = size_t ( - 1 );
2017-05-23 15:00:01 +02:00
for ( LayerTools & lt : m_layer_tools ) {
2017-05-17 16:45:37 +02:00
lt . wipe_tower_partitions = lt . extruders . size ();
2017-05-16 15:30:03 +02:00
if ( ! lt . extruders . empty ()) {
if ( last_extruder == size_t ( - 1 ) || last_extruder == lt . extruders . front ())
// The first extruder on this layer is equal to the current one, no need to do an initial tool change.
-- lt . wipe_tower_partitions ;
last_extruder = lt . extruders . back ();
}
}
2017-05-16 13:45:28 +02:00
// Propagate the wipe tower partitions down to support the upper partitions by the lower partitions.
2017-05-23 15:00:01 +02:00
for ( int i = int ( m_layer_tools . size ()) - 2 ; i >= 0 ; -- i )
m_layer_tools [ i ]. wipe_tower_partitions = std :: max ( m_layer_tools [ i + 1 ]. wipe_tower_partitions , m_layer_tools [ i ]. wipe_tower_partitions );
2017-05-19 19:24:21 +02:00
//FIXME this is a hack to get the ball rolling.
2017-05-23 15:00:01 +02:00
for ( LayerTools & lt : m_layer_tools )
2017-06-08 16:58:29 +02:00
lt . has_wipe_tower = ( lt . has_object && lt . wipe_tower_partitions > 0 ) || lt . print_z < object_bottom_z + EPSILON ;
// Test for a raft, insert additional wipe tower layer to fill in the raft separation gap.
2017-06-09 14:24:00 +02:00
double max_layer_height = std :: numeric_limits < double >:: max ();
2017-06-08 16:58:29 +02:00
for ( size_t i = 0 ; i < config . nozzle_diameter . values . size (); ++ i ) {
double mlh = config . max_layer_height . values [ i ];
if ( mlh == 0. )
mlh = 0.75 * config . nozzle_diameter . values [ i ];
max_layer_height = std :: min ( max_layer_height , mlh );
}
for ( size_t i = 0 ; i + 1 < m_layer_tools . size (); ++ i ) {
const LayerTools & lt = m_layer_tools [ i ];
const LayerTools & lt_next = m_layer_tools [ i + 1 ];
if ( lt . print_z < object_bottom_z + EPSILON && lt_next . print_z >= object_bottom_z + EPSILON ) {
// lt is the last raft layer. Find the 1st object layer.
size_t j = i + 1 ;
for (; j < m_layer_tools . size () && ! m_layer_tools [ j ]. has_wipe_tower ; ++ j );
if ( j < m_layer_tools . size ()) {
const LayerTools & lt_object = m_layer_tools [ j ];
coordf_t gap = lt_object . print_z - lt . print_z ;
assert ( gap > 0.f );
if ( gap > max_layer_height + EPSILON ) {
// Insert one additional wipe tower layer between lh.print_z and lt_object.print_z.
LayerTools lt_new ( 0.5f * ( lt . print_z + lt_object . print_z ));
// Find the 1st layer above lt_new.
2017-12-13 10:32:25 +01:00
for ( j = i + 1 ; j < m_layer_tools . size () && m_layer_tools [ j ]. print_z < lt_new . print_z - EPSILON ; ++ j );
2019-09-24 16:01:01 +02:00
if ( std :: abs ( m_layer_tools [ j ]. print_z - lt_new . print_z ) < EPSILON ) {
2017-12-11 17:19:55 +01:00
m_layer_tools [ j ]. has_wipe_tower = true ;
} else {
LayerTools & lt_extra = * m_layer_tools . insert ( m_layer_tools . begin () + j , lt_new );
2019-08-02 16:49:22 +02:00
//LayerTools <_prev = m_layer_tools[j];
2017-12-11 17:19:55 +01:00
LayerTools & lt_next = m_layer_tools [ j + 1 ];
2019-06-25 13:06:04 +02:00
assert ( ! m_layer_tools [ j - 1 ]. extruders . empty () && ! lt_next . extruders . empty ());
2019-05-22 16:43:14 +02:00
// FIXME: Following assert tripped when running combine_infill.t. I decided to comment it out for now.
// If it is a bug, it's likely not critical, because this code is unchanged for a long time. It might
// still be worth looking into it more and decide if it is a bug or an obsolete assert.
//assert(lt_prev.extruders.back() == lt_next.extruders.front());
2019-08-16 16:31:05 +02:00
lt_extra . has_wipe_tower = true ;
2017-12-11 17:19:55 +01:00
lt_extra . extruders . push_back ( lt_next . extruders . front ());
2019-08-16 16:31:05 +02:00
lt_extra . wipe_tower_partitions = lt_next . wipe_tower_partitions ;
}
2017-06-08 16:58:29 +02:00
}
}
break ;
}
}
2019-09-10 12:49:20 +02:00
// If the model contains empty layers (such as https://github.com/prusa3d/Slic3r/issues/1266), there might be layers
// that were not marked as has_wipe_tower, even when they should have been. This produces a crash with soluble supports
// and maybe other problems. We will therefore go through layer_tools and detect and fix this.
// So, if there is a non-object layer starting with different extruder than the last one ended with (or containing more than one extruder),
// we'll mark it with has_wipe tower.
for ( unsigned int i = 0 ; i + 1 < m_layer_tools . size (); ++ i ) {
LayerTools & lt = m_layer_tools [ i ];
LayerTools & lt_next = m_layer_tools [ i + 1 ];
if ( lt . extruders . empty () || lt_next . extruders . empty ())
break ;
if ( ! lt_next . has_wipe_tower && ( lt_next . extruders . front () != lt . extruders . back () || lt_next . extruders . size () > 1 ))
lt_next . has_wipe_tower = true ;
// We should also check that the next wipe tower layer is no further than max_layer_height:
unsigned int j = i + 1 ;
double last_wipe_tower_print_z = lt_next . print_z ;
while ( ++ j < m_layer_tools . size () - 1 && ! m_layer_tools [ j ]. has_wipe_tower )
if ( m_layer_tools [ j + 1 ]. print_z - last_wipe_tower_print_z > max_layer_height ) {
m_layer_tools [ j ]. has_wipe_tower = true ;
last_wipe_tower_print_z = m_layer_tools [ j ]. print_z ;
}
}
2017-06-08 16:58:29 +02:00
// Calculate the wipe_tower_layer_height values.
coordf_t wipe_tower_print_z_last = 0. ;
for ( LayerTools & lt : m_layer_tools )
if ( lt . has_wipe_tower ) {
lt . wipe_tower_layer_height = lt . print_z - wipe_tower_print_z_last ;
wipe_tower_print_z_last = lt . print_z ;
}
2017-05-16 13:45:28 +02:00
}
2017-09-01 17:30:18 +02:00
void ToolOrdering :: collect_extruder_statistics ( bool prime_multi_material )
{
m_first_printing_extruder = ( unsigned int ) - 1 ;
for ( const auto & lt : m_layer_tools )
if ( ! lt . extruders . empty ()) {
m_first_printing_extruder = lt . extruders . front ();
break ;
}
m_last_printing_extruder = ( unsigned int ) - 1 ;
for ( auto lt_it = m_layer_tools . rbegin (); lt_it != m_layer_tools . rend (); ++ lt_it )
if ( ! lt_it -> extruders . empty ()) {
m_last_printing_extruder = lt_it -> extruders . back ();
break ;
}
m_all_printing_extruders . clear ();
for ( const auto & lt : m_layer_tools ) {
append ( m_all_printing_extruders , lt . extruders );
sort_remove_duplicates ( m_all_printing_extruders );
}
if ( prime_multi_material && ! m_all_printing_extruders . empty ()) {
// Reorder m_all_printing_extruders in the sequence they will be primed, the last one will be m_first_printing_extruder.
// Then set m_first_printing_extruder to the 1st extruder primed.
m_all_printing_extruders . erase (
2019-08-16 16:31:05 +02:00
std :: remove_if ( m_all_printing_extruders . begin (), m_all_printing_extruders . end (),
2017-09-01 17:30:18 +02:00
[ this ]( const unsigned int eid ) { return eid == m_first_printing_extruder ; }),
m_all_printing_extruders . end ());
m_all_printing_extruders . emplace_back ( m_first_printing_extruder );
m_first_printing_extruder = m_all_printing_extruders . front ();
}
}
2020-01-14 11:54:09 +01:00
// Assign a pointer to a custom G-code to the respective ToolOrdering::LayerTools.
// Ignore color changes, which are performed on a layer and for such an extruder, that the extruder will not be printing above that layer.
// If multiple events are planned over a span of a single layer, use the last one.
void ToolOrdering :: assign_custom_gcodes ( const Print & print )
{
2020-01-15 16:20:16 +01:00
// Only valid for non-sequential print.
assert ( ! print . config (). complete_objects . value );
2020-01-14 11:54:09 +01:00
const std :: vector < Model :: CustomGCode > & custom_gcode_per_print_z = print . model (). custom_gcode_per_print_z ;
if ( custom_gcode_per_print_z . empty ())
return ;
unsigned int num_extruders = * std :: max_element ( m_all_printing_extruders . begin (), m_all_printing_extruders . end ()) + 1 ;
std :: vector < unsigned char > extruder_printing_above ( num_extruders , false );
auto custom_gcode_it = custom_gcode_per_print_z . rbegin ();
2020-01-16 13:39:03 +01:00
// If printing on a single extruder machine, make the tool changes trigger color change (M600) events.
bool tool_changes_as_color_changes = num_extruders == 1 ;
2020-01-14 11:54:09 +01:00
// From the last layer to the first one:
for ( auto it_lt = m_layer_tools . rbegin (); it_lt != m_layer_tools . rend (); ++ it_lt ) {
LayerTools & lt = * it_lt ;
// Add the extruders of the current layer to the set of extruders printing at and above this print_z.
for ( unsigned int i : lt . extruders )
extruder_printing_above [ i ] = true ;
// Skip all custom G-codes above this layer and skip all extruder switches.
for (; custom_gcode_it != custom_gcode_per_print_z . rend () && ( custom_gcode_it -> print_z > lt . print_z + EPSILON || custom_gcode_it -> gcode == ExtruderChangeCode ); ++ custom_gcode_it );
if ( custom_gcode_it == custom_gcode_per_print_z . rend ())
// Custom G-codes were processed.
break ;
// Some custom G-code is configured for this layer or a layer below.
const Model :: CustomGCode & custom_gcode = * custom_gcode_it ;
// print_z of the layer below the current layer.
coordf_t print_z_below = 0. ;
2020-01-14 16:10:53 +01:00
if ( auto it_lt_below = it_lt ; ++ it_lt_below != m_layer_tools . rend ())
2020-01-14 11:54:09 +01:00
print_z_below = it_lt_below -> print_z ;
2020-01-14 17:35:42 +01:00
if ( custom_gcode . print_z > print_z_below + 0.5 * EPSILON ) {
2020-01-14 11:54:09 +01:00
// The custom G-code applies to the current layer.
2020-01-16 14:02:19 +01:00
if ( tool_changes_as_color_changes || custom_gcode . gcode != ColorChangeCode ||
2020-01-15 15:35:56 +01:00
( custom_gcode . extruder <= num_extruders && extruder_printing_above [ unsigned ( custom_gcode . extruder - 1 )]))
2020-01-14 11:54:09 +01:00
// If it is color change, it will actually be useful as the exturder above will print.
lt . custom_gcode = & custom_gcode ;
// Consume that custom G-code event.
2020-01-14 16:10:53 +01:00
++ custom_gcode_it ;
2020-01-14 11:54:09 +01:00
}
}
}
2020-01-07 14:35:43 +01:00
const LayerTools & ToolOrdering :: tools_for_layer ( coordf_t print_z ) const
{
auto it_layer_tools = std :: lower_bound ( m_layer_tools . begin (), m_layer_tools . end (), LayerTools ( print_z - EPSILON ));
assert ( it_layer_tools != m_layer_tools . end ());
coordf_t dist_min = std :: abs ( it_layer_tools -> print_z - print_z );
for ( ++ it_layer_tools ; it_layer_tools != m_layer_tools . end (); ++ it_layer_tools ) {
coordf_t d = std :: abs ( it_layer_tools -> print_z - print_z );
if ( d >= dist_min )
break ;
dist_min = d ;
}
-- it_layer_tools ;
assert ( dist_min < EPSILON );
return * it_layer_tools ;
}
2018-06-20 12:52:00 +02:00
2018-06-21 10:16:52 +02:00
// This function is called from Print::mark_wiping_extrusions and sets extruder this entity should be printed with (-1 .. as usual)
2020-01-14 10:31:18 +01:00
void WipingExtrusions :: set_extruder_override ( const ExtrusionEntity * entity , size_t copy_id , int extruder , size_t num_of_copies )
2018-06-27 14:08:46 +02:00
{
2018-06-21 10:16:52 +02:00
something_overridden = true ;
2018-06-20 12:52:00 +02:00
2020-01-08 14:58:12 +01:00
auto entity_map_it = ( entity_map . emplace ( entity , ExtruderPerCopy ())). first ; // (add and) return iterator
ExtruderPerCopy & copies_vector = entity_map_it -> second ;
copies_vector . resize ( num_of_copies , - 1 );
2018-06-21 10:16:52 +02:00
if ( copies_vector [ copy_id ] != - 1 )
std :: cout << "ERROR: Entity extruder overriden multiple times!!! \n " ; // A debugging message - this must never happen.
copies_vector [ copy_id ] = extruder ;
}
2018-06-20 12:52:00 +02:00
2018-06-29 12:26:22 +02:00
// Finds first non-soluble extruder on the layer
2018-07-11 14:46:13 +02:00
int WipingExtrusions :: first_nonsoluble_extruder_on_layer ( const PrintConfig & print_config ) const
2018-06-29 12:26:22 +02:00
{
2018-07-11 14:46:13 +02:00
const LayerTools & lt = * m_layer_tools ;
2018-06-29 12:26:22 +02:00
for ( auto extruders_it = lt . extruders . begin (); extruders_it != lt . extruders . end (); ++ extruders_it )
if ( ! print_config . filament_soluble . get_at ( * extruders_it ))
return ( * extruders_it );
return ( - 1 );
}
2018-06-26 14:12:25 +02:00
// Finds last non-soluble extruder on the layer
2018-07-11 14:46:13 +02:00
int WipingExtrusions :: last_nonsoluble_extruder_on_layer ( const PrintConfig & print_config ) const
2018-06-27 14:08:46 +02:00
{
2018-07-11 14:46:13 +02:00
const LayerTools & lt = * m_layer_tools ;
2018-06-26 14:12:25 +02:00
for ( auto extruders_it = lt . extruders . rbegin (); extruders_it != lt . extruders . rend (); ++ extruders_it )
if ( ! print_config . filament_soluble . get_at ( * extruders_it ))
2018-06-29 12:26:22 +02:00
return ( * extruders_it );
return ( - 1 );
2018-06-26 14:12:25 +02:00
}
// Decides whether this entity could be overridden
2018-06-27 14:08:46 +02:00
bool WipingExtrusions :: is_overriddable ( const ExtrusionEntityCollection & eec , const PrintConfig & print_config , const PrintObject & object , const PrintRegion & region ) const
{
2020-01-14 10:31:18 +01:00
if ( print_config . filament_soluble . get_at ( m_layer_tools -> extruder ( eec , region )))
2018-06-27 14:08:46 +02:00
return false ;
2018-09-12 11:59:02 +02:00
if ( object . config (). wipe_into_objects )
2018-06-27 14:08:46 +02:00
return true ;
2018-09-12 11:59:02 +02:00
if ( ! region . config (). wipe_into_infill || eec . role () != erInternalInfill )
2018-06-27 14:08:46 +02:00
return false ;
2018-06-26 14:12:25 +02:00
return true ;
}
// Following function iterates through all extrusions on the layer, remembers those that could be used for wiping after toolchange
// and returns volume that is left to be wiped on the wipe tower.
2018-07-18 11:05:39 +02:00
float WipingExtrusions :: mark_wiping_extrusions ( const Print & print , unsigned int old_extruder , unsigned int new_extruder , float volume_to_wipe )
2018-06-26 14:12:25 +02:00
{
2018-07-13 11:25:22 +02:00
const LayerTools & lt = * m_layer_tools ;
2018-06-26 14:12:25 +02:00
const float min_infill_volume = 0.f ; // ignore infill with smaller volume than this
2020-01-14 15:12:45 +01:00
if ( ! this -> something_overridable || volume_to_wipe <= 0. || print . config (). filament_soluble . get_at ( old_extruder ) || print . config (). filament_soluble . get_at ( new_extruder ))
2020-01-14 15:43:43 +01:00
return std :: max ( 0.f , volume_to_wipe ); // Soluble filament cannot be wiped in a random infill, neither the filament after it
2018-06-26 14:12:25 +02:00
// we will sort objects so that dedicated for wiping are at the beginning:
2018-11-08 14:23:17 +01:00
PrintObjectPtrs object_list = print . objects ();
2018-09-12 11:59:02 +02:00
std :: sort ( object_list . begin (), object_list . end (), []( const PrintObject * a , const PrintObject * b ) { return a -> config (). wipe_into_objects ; });
2018-06-26 14:12:25 +02:00
// We will now iterate through
// - first the dedicated objects to mark perimeters or infills (depending on infill_first)
// - second through the dedicated ones again to mark infills or perimeters (depending on infill_first)
// - then all the others to mark infills (in case that !infill_first, we must also check that the perimeter is finished already
// this is controlled by the following variable:
bool perimeters_done = false ;
2018-06-29 12:26:22 +02:00
for ( int i = 0 ; i < ( int ) object_list . size () + ( perimeters_done ? 0 : 1 ); ++ i ) {
2018-09-12 11:59:02 +02:00
if ( ! perimeters_done && ( i == ( int ) object_list . size () || ! object_list [ i ] -> config (). wipe_into_objects )) { // we passed the last dedicated object in list
2018-06-26 14:12:25 +02:00
perimeters_done = true ;
i =- 1 ; // let's go from the start again
continue ;
}
2019-09-04 14:56:35 +02:00
const PrintObject * object = object_list [ i ];
2018-06-29 12:26:22 +02:00
2018-06-26 14:12:25 +02:00
// Finds this layer:
2020-01-10 11:26:52 +01:00
const Layer * this_layer = object -> get_layer_at_printz ( lt . print_z , EPSILON );
if ( this_layer == nullptr )
continue ;
2019-09-04 14:56:35 +02:00
size_t num_of_copies = object -> copies (). size ();
2018-06-26 14:12:25 +02:00
2020-01-10 11:26:52 +01:00
// iterate through copies (aka PrintObject instances) first, so that we mark neighbouring infills to minimize travel moves
for ( unsigned int copy = 0 ; copy < num_of_copies ; ++ copy ) {
2018-06-26 14:12:25 +02:00
2018-11-07 15:17:29 +01:00
for ( size_t region_id = 0 ; region_id < object -> region_volumes . size (); ++ region_id ) {
2018-09-12 11:59:02 +02:00
const auto & region = * object -> print () -> regions ()[ region_id ];
2018-06-26 14:12:25 +02:00
2018-09-12 11:59:02 +02:00
if ( ! region . config (). wipe_into_infill && ! object -> config (). wipe_into_objects )
2018-06-26 14:12:25 +02:00
continue ;
2020-01-10 11:26:52 +01:00
bool wipe_into_infill_only = ! object -> config (). wipe_into_objects && region . config (). wipe_into_infill ;
if ( print . config (). infill_first != perimeters_done || wipe_into_infill_only ) {
2018-09-12 11:59:02 +02:00
for ( const ExtrusionEntity * ee : this_layer -> regions ()[ region_id ] -> fills . entities ) { // iterate through all infill Collections
2018-06-26 14:12:25 +02:00
auto * fill = dynamic_cast < const ExtrusionEntityCollection *> ( ee );
2018-09-12 11:59:02 +02:00
if ( ! is_overriddable ( * fill , print . config (), * object , region ))
2018-06-26 14:12:25 +02:00
continue ;
2020-01-10 11:26:52 +01:00
if ( wipe_into_infill_only && ! print . config (). infill_first )
2018-06-26 14:12:25 +02:00
// In this case we must check that the original extruder is used on this layer before the one we are overridding
// (and the perimeters will be finished before the infill is printed):
2020-01-14 10:31:18 +01:00
if ( ! lt . is_extruder_order ( lt . perimeter_extruder ( region ), new_extruder ))
2018-07-13 11:25:22 +02:00
continue ;
2018-06-26 14:12:25 +02:00
2018-06-29 12:26:22 +02:00
if (( ! is_entity_overridden ( fill , copy ) && fill -> total_volume () > min_infill_volume )) { // this infill will be used to wipe this extruder
2018-06-26 14:12:25 +02:00
set_extruder_override ( fill , copy , new_extruder , num_of_copies );
2020-01-10 11:26:52 +01:00
if (( volume_to_wipe -= float ( fill -> total_volume ())) <= 0.f )
// More material was purged already than asked for.
return 0.f ;
2018-06-26 14:12:25 +02:00
}
}
}
2018-06-27 15:07:37 +02:00
// Now the same for perimeters - see comments above for explanation:
2020-01-10 11:26:52 +01:00
if ( object -> config (). wipe_into_objects && print . config (). infill_first == perimeters_done )
2018-06-26 14:12:25 +02:00
{
2018-09-12 11:59:02 +02:00
for ( const ExtrusionEntity * ee : this_layer -> regions ()[ region_id ] -> perimeters . entities ) {
2018-06-26 14:12:25 +02:00
auto * fill = dynamic_cast < const ExtrusionEntityCollection *> ( ee );
2020-01-10 11:26:52 +01:00
if ( is_overriddable ( * fill , print . config (), * object , region ) && ! is_entity_overridden ( fill , copy ) && fill -> total_volume () > min_infill_volume ) {
2018-06-26 14:12:25 +02:00
set_extruder_override ( fill , copy , new_extruder , num_of_copies );
2020-01-10 11:26:52 +01:00
if (( volume_to_wipe -= float ( fill -> total_volume ())) <= 0.f )
// More material was purged already than asked for.
return 0.f ;
2018-06-26 14:12:25 +02:00
}
}
}
}
}
}
2020-01-10 11:26:52 +01:00
// Some purge remains to be done on the Wipe Tower.
assert ( volume_to_wipe > 0. );
return volume_to_wipe ;
2018-06-26 14:12:25 +02:00
}
2018-06-29 12:26:22 +02:00
// Called after all toolchanges on a layer were mark_infill_overridden. There might still be overridable entities,
// that were not actually overridden. If they are part of a dedicated object, printing them with the extruder
// they were initially assigned to might mean violating the perimeter-infill order. We will therefore go through
// them again and make sure we override it.
2018-07-11 14:46:13 +02:00
void WipingExtrusions :: ensure_perimeters_infills_order ( const Print & print )
2018-06-29 12:26:22 +02:00
{
2020-01-10 11:26:52 +01:00
if ( ! this -> something_overridable )
return ;
2018-07-13 11:25:22 +02:00
const LayerTools & lt = * m_layer_tools ;
2018-09-12 11:59:02 +02:00
unsigned int first_nonsoluble_extruder = first_nonsoluble_extruder_on_layer ( print . config ());
unsigned int last_nonsoluble_extruder = last_nonsoluble_extruder_on_layer ( print . config ());
2018-06-29 12:26:22 +02:00
2018-11-08 14:23:17 +01:00
for ( const PrintObject * object : print . objects ()) {
2018-06-29 12:26:22 +02:00
// Finds this layer:
2020-01-10 11:26:52 +01:00
const Layer * this_layer = object -> get_layer_at_printz ( lt . print_z , EPSILON );
if ( this_layer == nullptr )
continue ;
2019-09-04 14:56:35 +02:00
size_t num_of_copies = object -> copies (). size ();
2018-06-29 12:26:22 +02:00
2019-09-04 14:56:35 +02:00
for ( size_t copy = 0 ; copy < num_of_copies ; ++ copy ) { // iterate through copies first, so that we mark neighbouring infills to minimize travel moves
2018-11-07 15:17:29 +01:00
for ( size_t region_id = 0 ; region_id < object -> region_volumes . size (); ++ region_id ) {
2018-09-12 11:59:02 +02:00
const auto & region = * object -> print () -> regions ()[ region_id ];
2018-06-29 12:26:22 +02:00
2018-09-12 11:59:02 +02:00
if ( ! region . config (). wipe_into_infill && ! object -> config (). wipe_into_objects )
2018-06-29 12:26:22 +02:00
continue ;
2018-09-12 11:59:02 +02:00
for ( const ExtrusionEntity * ee : this_layer -> regions ()[ region_id ] -> fills . entities ) { // iterate through all infill Collections
2018-06-29 12:26:22 +02:00
auto * fill = dynamic_cast < const ExtrusionEntityCollection *> ( ee );
2018-09-12 11:59:02 +02:00
if ( ! is_overriddable ( * fill , print . config (), * object , region )
2018-06-29 12:26:22 +02:00
|| is_entity_overridden ( fill , copy ) )
continue ;
2018-07-13 11:25:22 +02:00
// This infill could have been overridden but was not - unless we do something, it could be
2018-06-29 12:26:22 +02:00
// printed before its perimeter, or not be printed at all (in case its original extruder has
// not been added to LayerTools
// Either way, we will now force-override it with something suitable:
2018-09-12 11:59:02 +02:00
if ( print . config (). infill_first
|| object -> config (). wipe_into_objects // in this case the perimeter is overridden, so we can override by the last one safely
2020-01-14 10:31:18 +01:00
|| lt . is_extruder_order ( lt . perimeter_extruder ( region ), last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints
|| ! lt . has_extruder ( lt . infill_extruder ( region )))) // we have to force override - this could violate infill_first (FIXME)
2018-09-12 11:59:02 +02:00
set_extruder_override ( fill , copy , ( print . config (). infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder ), num_of_copies );
2018-07-13 11:25:22 +02:00
else {
// In this case we can (and should) leave it to be printed normally.
// Force overriding would mean it gets printed before its perimeter.
}
2018-06-29 12:26:22 +02:00
}
// Now the same for perimeters - see comments above for explanation:
2018-09-12 11:59:02 +02:00
for ( const ExtrusionEntity * ee : this_layer -> regions ()[ region_id ] -> perimeters . entities ) { // iterate through all perimeter Collections
2018-06-29 12:26:22 +02:00
auto * fill = dynamic_cast < const ExtrusionEntityCollection *> ( ee );
2020-01-10 11:26:52 +01:00
if ( is_overriddable ( * fill , print . config (), * object , region ) && ! is_entity_overridden ( fill , copy ))
set_extruder_override ( fill , copy , ( print . config (). infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder ), num_of_copies );
2018-06-29 12:26:22 +02:00
}
}
}
}
}
2020-01-08 14:58:12 +01:00
// Following function is called from GCode::process_layer and returns pointer to vector with information about which extruders should be used for given copy of this entity.
// If this extrusion does not have any override, nullptr is returned.
// Otherwise it modifies the vector in place and changes all -1 to correct_extruder_id (at the time the overrides were created, correct extruders were not known,
// so -1 was used as "print as usual").
// The resulting vector therefore keeps track of which extrusions are the ones that were overridden and which were not. If the extruder used is overridden,
// its number is saved as is (zero-based index). Regular extrusions are saved as -number-1 (unfortunately there is no negative zero).
const WipingExtrusions :: ExtruderPerCopy * WipingExtrusions :: get_extruder_overrides ( const ExtrusionEntity * entity , int correct_extruder_id , size_t num_of_copies )
2018-06-27 14:08:46 +02:00
{
2020-01-08 14:58:12 +01:00
ExtruderPerCopy * overrides = nullptr ;
2018-06-21 10:16:52 +02:00
auto entity_map_it = entity_map . find ( entity );
2020-01-08 14:58:12 +01:00
if ( entity_map_it != entity_map . end ()) {
overrides = & entity_map_it -> second ;
overrides -> resize ( num_of_copies , - 1 );
// Each -1 now means "print as usual" - we will replace it with actual extruder id (shifted it so we don't lose that information):
std :: replace ( overrides -> begin (), overrides -> end (), - 1 , - correct_extruder_id - 1 );
}
return overrides ;
2018-06-21 10:16:52 +02:00
}
2019-08-16 16:31:05 +02:00
2018-06-20 12:52:00 +02:00
2017-05-16 13:45:28 +02:00
} // namespace Slic3r