2019-09-13 14:16:37 +00:00
# include "clipper/clipper_z.hpp"
2013-12-20 00:36:42 +00:00
# include "Print.hpp"
2014-05-06 08:07:18 +00:00
# include "BoundingBox.hpp"
2014-11-09 14:27:34 +00:00
# include "ClipperUtils.hpp"
2017-02-15 10:05:52 +00:00
# include "Extruder.hpp"
2014-12-12 18:14:52 +00:00
# include "Flow.hpp"
2014-11-11 20:17:02 +00:00
# include "Geometry.hpp"
2018-06-20 11:57:37 +00:00
# include "I18N.hpp"
2019-09-26 14:39:50 +00:00
# include "ShortestPath.hpp"
2014-12-12 18:14:52 +00:00
# include "SupportMaterial.hpp"
2018-03-23 10:41:20 +00:00
# include "GCode.hpp"
2019-06-17 08:16:07 +00:00
# include "GCode/WipeTower.hpp"
2018-12-18 10:31:41 +00:00
# include "Utils.hpp"
2013-12-20 00:36:42 +00:00
2019-04-04 10:30:11 +00:00
//#include "PrintExport.hpp"
2013-12-20 00:36:42 +00:00
2019-06-20 19:07:26 +00:00
# include <float.h>
2018-12-18 10:31:41 +00:00
# include <algorithm>
2019-01-11 10:46:54 +00:00
# include <limits>
2018-12-18 10:31:41 +00:00
# include <unordered_set>
2018-12-12 14:09:20 +00:00
# include <boost/filesystem/path.hpp>
2019-08-06 13:11:46 +00:00
# include <boost/format.hpp>
2018-12-18 10:31:41 +00:00
# include <boost/log/trivial.hpp>
2018-12-12 14:09:20 +00:00
2019-06-25 11:06:04 +00:00
//! macro used to mark string used at localization,
2018-06-20 11:57:37 +00:00
//! return same string
2018-06-20 16:55:31 +00:00
# define L(s) Slic3r::I18N::translate(s)
2018-06-20 11:57:37 +00:00
2013-12-20 00:36:42 +00:00
namespace Slic3r {
2017-05-30 15:17:26 +00:00
template class PrintState < PrintStep , psCount > ;
template class PrintState < PrintObjectStep , posCount > ;
2014-05-06 08:07:18 +00:00
2018-11-08 13:23:17 +00:00
void Print : : clear ( )
2014-05-06 08:07:18 +00:00
{
2018-11-16 17:28:50 +00:00
tbb : : mutex : : scoped_lock lock ( this - > state_mutex ( ) ) ;
2018-11-08 13:23:17 +00:00
// The following call should stop background processing if it is running.
this - > invalidate_all_steps ( ) ;
2018-09-14 08:25:20 +00:00
for ( PrintObject * object : m_objects )
delete object ;
m_objects . clear ( ) ;
2018-09-11 12:04:47 +00:00
for ( PrintRegion * region : m_regions )
2017-05-31 15:02:23 +00:00
delete region ;
2018-09-11 12:04:47 +00:00
m_regions . clear ( ) ;
2018-12-03 15:25:21 +00:00
m_model . clear_objects ( ) ;
2014-05-06 08:07:18 +00:00
}
2017-05-30 18:09:34 +00:00
PrintRegion * Print : : add_region ( )
2014-05-06 08:07:18 +00:00
{
2018-09-11 12:04:47 +00:00
m_regions . emplace_back ( new PrintRegion ( this ) ) ;
return m_regions . back ( ) ;
}
PrintRegion * Print : : add_region ( const PrintRegionConfig & config )
{
m_regions . emplace_back ( new PrintRegion ( this , config ) ) ;
return m_regions . back ( ) ;
2014-05-06 08:07:18 +00:00
}
2019-05-22 14:43:14 +00:00
// Called by Print::apply().
2017-05-30 18:09:34 +00:00
// This method only accepts PrintConfig option keys.
bool Print : : invalidate_state_by_config_options ( const std : : vector < t_config_option_key > & opt_keys )
2014-05-06 08:07:18 +00:00
{
2017-05-30 18:09:34 +00:00
if ( opt_keys . empty ( ) )
return false ;
2014-05-06 08:07:18 +00:00
2017-05-31 15:02:23 +00:00
// Cache the plenty of parameters, which influence the G-code generator only,
// or they are only notes not influencing the generated G-code.
2018-03-28 15:05:31 +00:00
static std : : unordered_set < std : : string > steps_gcode = {
2017-11-28 14:19:57 +00:00
" avoid_crossing_perimeters " ,
" bed_shape " ,
" bed_temperature " ,
" before_layer_gcode " ,
" between_objects_gcode " ,
" bridge_acceleration " ,
" bridge_fan_speed " ,
2018-11-07 13:44:47 +00:00
" colorprint_heights " ,
2017-11-28 14:19:57 +00:00
" cooling " ,
" default_acceleration " ,
" deretract_speed " ,
" disable_fan_first_layers " ,
" duplicate_distance " ,
" end_gcode " ,
" end_filament_gcode " ,
" extrusion_axis " ,
" extruder_clearance_height " ,
" extruder_clearance_radius " ,
" extruder_colour " ,
" extruder_offset " ,
" extrusion_multiplier " ,
" fan_always_on " ,
" fan_below_layer_time " ,
" filament_colour " ,
" filament_diameter " ,
" filament_density " ,
" filament_notes " ,
" filament_cost " ,
" first_layer_acceleration " ,
" first_layer_bed_temperature " ,
" first_layer_speed " ,
" gcode_comments " ,
2019-01-31 14:09:16 +00:00
" gcode_label_objects " ,
2017-11-28 14:19:57 +00:00
" infill_acceleration " ,
" layer_gcode " ,
" min_fan_speed " ,
" max_fan_speed " ,
2018-03-09 09:40:42 +00:00
" max_print_height " ,
2017-11-28 14:19:57 +00:00
" min_print_speed " ,
" max_print_speed " ,
" max_volumetric_speed " ,
2019-01-29 17:07:45 +00:00
# ifdef HAS_PRESSURE_EQUALIZER
2017-11-28 14:19:57 +00:00
" max_volumetric_extrusion_rate_slope_positive " ,
" max_volumetric_extrusion_rate_slope_negative " ,
2019-01-29 17:07:45 +00:00
# endif /* HAS_PRESSURE_EQUALIZER */
2017-11-28 14:19:57 +00:00
" notes " ,
" only_retract_when_crossing_perimeters " ,
" output_filename_format " ,
" perimeter_acceleration " ,
" post_process " ,
" printer_notes " ,
" retract_before_travel " ,
" retract_before_wipe " ,
" retract_layer_change " ,
" retract_length " ,
" retract_length_toolchange " ,
" retract_lift " ,
" retract_lift_above " ,
" retract_lift_below " ,
" retract_restart_extra " ,
" retract_restart_extra_toolchange " ,
" retract_speed " ,
2018-07-27 20:19:46 +00:00
" single_extruder_multi_material_priming " ,
2017-11-28 14:19:57 +00:00
" slowdown_below_layer_time " ,
" standby_temperature_delta " ,
" start_gcode " ,
" start_filament_gcode " ,
" toolchange_gcode " ,
" threads " ,
" travel_speed " ,
" use_firmware_retraction " ,
" use_relative_e_distances " ,
" use_volumetric_e " ,
" variable_layer_height " ,
2019-05-07 03:33:09 +00:00
" wipe "
2017-11-28 14:19:57 +00:00
} ;
2017-05-31 15:02:23 +00:00
2018-03-28 17:47:26 +00:00
static std : : unordered_set < std : : string > steps_ignore ;
2018-03-28 15:05:31 +00:00
2017-05-30 15:17:26 +00:00
std : : vector < PrintStep > steps ;
std : : vector < PrintObjectStep > osteps ;
2017-05-31 15:02:23 +00:00
bool invalidated = false ;
2018-06-07 14:19:57 +00:00
2017-05-30 15:17:26 +00:00
for ( const t_config_option_key & opt_key : opt_keys ) {
2018-03-28 15:05:31 +00:00
if ( steps_gcode . find ( opt_key ) ! = steps_gcode . end ( ) ) {
2017-05-31 15:02:23 +00:00
// These options only affect G-code export or they are just notes without influence on the generated G-code,
// so there is nothing to invalidate.
2018-03-28 15:05:31 +00:00
steps . emplace_back ( psGCodeExport ) ;
} else if ( steps_ignore . find ( opt_key ) ! = steps_ignore . end ( ) ) {
// These steps have no influence on the G-code whatsoever. Just ignore them.
2017-05-31 15:02:23 +00:00
} else if (
opt_key = = " skirts "
2017-05-30 15:17:26 +00:00
| | opt_key = = " skirt_height "
| | opt_key = = " skirt_distance "
| | opt_key = = " min_skirt_length "
2019-05-07 03:33:09 +00:00
| | opt_key = = " ooze_prevention "
| | opt_key = = " wipe_tower_x "
| | opt_key = = " wipe_tower_y "
| | opt_key = = " wipe_tower_rotation_angle " ) {
2017-05-30 15:17:26 +00:00
steps . emplace_back ( psSkirt ) ;
} else if ( opt_key = = " brim_width " ) {
steps . emplace_back ( psBrim ) ;
steps . emplace_back ( psSkirt ) ;
2017-05-31 15:02:23 +00:00
} else if (
opt_key = = " nozzle_diameter "
2017-05-30 15:17:26 +00:00
| | opt_key = = " resolution " ) {
osteps . emplace_back ( posSlice ) ;
2017-05-25 20:27:53 +00:00
} else if (
2017-05-30 15:17:26 +00:00
opt_key = = " complete_objects "
| | opt_key = = " filament_type "
2017-05-31 15:02:23 +00:00
| | opt_key = = " filament_soluble "
2017-05-30 15:17:26 +00:00
| | opt_key = = " first_layer_temperature "
2018-03-05 09:45:35 +00:00
| | opt_key = = " filament_loading_speed "
2018-08-21 12:36:24 +00:00
| | opt_key = = " filament_loading_speed_start "
2018-03-05 09:45:35 +00:00
| | opt_key = = " filament_unloading_speed "
2018-08-14 14:23:23 +00:00
| | opt_key = = " filament_unloading_speed_start "
2018-03-05 09:45:35 +00:00
| | opt_key = = " filament_toolchange_delay "
2018-04-24 11:02:08 +00:00
| | opt_key = = " filament_cooling_moves "
2018-08-02 13:14:12 +00:00
| | opt_key = = " filament_minimal_purge_on_wipe_tower "
2018-04-24 11:02:08 +00:00
| | opt_key = = " filament_cooling_initial_speed "
| | opt_key = = " filament_cooling_final_speed "
2018-03-15 13:04:12 +00:00
| | opt_key = = " filament_ramming_parameters "
2019-06-10 10:26:47 +00:00
| | opt_key = = " filament_max_volumetric_speed "
2017-05-30 15:17:26 +00:00
| | opt_key = = " gcode_flavor "
2018-12-14 19:09:10 +00:00
| | opt_key = = " high_current_on_filament_swap "
2018-08-06 14:31:51 +00:00
| | opt_key = = " infill_first "
2017-05-30 15:17:26 +00:00
| | opt_key = = " single_extruder_multi_material "
| | opt_key = = " spiral_vase "
| | opt_key = = " temperature "
| | opt_key = = " wipe_tower "
| | opt_key = = " wipe_tower_width "
2018-03-12 14:41:25 +00:00
| | opt_key = = " wipe_tower_bridging "
2019-10-01 09:41:37 +00:00
| | opt_key = = " wipe_tower_no_sparse_layers "
2018-03-20 14:07:18 +00:00
| | opt_key = = " wiping_volumes_matrix "
2018-04-12 13:38:05 +00:00
| | opt_key = = " parking_pos_retraction "
| | opt_key = = " cooling_tube_retraction "
| | opt_key = = " cooling_tube_length "
2018-04-16 12:26:57 +00:00
| | opt_key = = " extra_loading_move "
2017-05-30 15:17:26 +00:00
| | opt_key = = " z_offset " ) {
steps . emplace_back ( psWipeTower ) ;
2019-05-07 03:33:09 +00:00
steps . emplace_back ( psSkirt ) ;
2017-05-31 15:02:23 +00:00
} else if (
opt_key = = " first_layer_extrusion_width "
| | opt_key = = " min_layer_height "
| | opt_key = = " max_layer_height " ) {
2017-05-30 15:17:26 +00:00
osteps . emplace_back ( posPerimeters ) ;
osteps . emplace_back ( posInfill ) ;
osteps . emplace_back ( posSupportMaterial ) ;
steps . emplace_back ( psSkirt ) ;
steps . emplace_back ( psBrim ) ;
2014-06-10 22:15:02 +00:00
} else {
2014-06-13 09:19:53 +00:00
// for legacy, if we can't handle this option let's invalidate all steps
2017-05-31 10:55:59 +00:00
//FIXME invalidate all steps of all objects as well?
2017-05-31 15:02:23 +00:00
invalidated | = this - > invalidate_all_steps ( ) ;
// Continue with the other opt_keys to possibly invalidate any object specific steps.
2014-06-10 22:15:02 +00:00
}
}
2017-05-31 15:02:23 +00:00
2017-05-30 15:17:26 +00:00
sort_remove_duplicates ( steps ) ;
for ( PrintStep step : steps )
invalidated | = this - > invalidate_step ( step ) ;
sort_remove_duplicates ( osteps ) ;
for ( PrintObjectStep ostep : osteps )
2018-09-11 12:04:47 +00:00
for ( PrintObject * object : m_objects )
2017-05-31 10:55:59 +00:00
invalidated | = object - > invalidate_step ( ostep ) ;
2014-06-13 09:19:53 +00:00
return invalidated ;
2014-06-10 22:15:02 +00:00
}
2017-05-30 15:17:26 +00:00
bool Print : : invalidate_step ( PrintStep step )
2014-06-10 22:15:02 +00:00
{
2018-11-08 13:23:17 +00:00
bool invalidated = Inherited : : invalidate_step ( step ) ;
2017-05-30 15:17:26 +00:00
// Propagate to dependent steps.
2017-05-31 15:02:23 +00:00
//FIXME Why should skirt invalidate brim? Shouldn't it be vice versa?
2017-05-25 20:27:53 +00:00
if ( step = = psSkirt )
2018-11-08 13:23:17 +00:00
invalidated | = Inherited : : invalidate_step ( psBrim ) ;
2018-11-12 15:28:27 +00:00
if ( step ! = psGCodeExport )
invalidated | = Inherited : : invalidate_step ( psGCodeExport ) ;
2014-06-13 09:19:53 +00:00
return invalidated ;
2014-06-10 22:15:02 +00:00
}
2014-11-30 19:18:09 +00:00
// returns true if an object step is done on all objects
// and there's at least one object
2018-03-28 15:05:31 +00:00
bool Print : : is_step_done ( PrintObjectStep step ) const
2014-11-30 19:18:09 +00:00
{
2018-09-11 12:04:47 +00:00
if ( m_objects . empty ( ) )
2017-05-30 15:17:26 +00:00
return false ;
2019-09-24 14:01:01 +00:00
tbb : : mutex : : scoped_lock lock ( this - > state_mutex ( ) ) ;
2018-09-11 12:04:47 +00:00
for ( const PrintObject * object : m_objects )
2019-02-21 07:44:07 +00:00
if ( ! object - > is_step_done_unguarded ( step ) )
2014-11-30 19:18:09 +00:00
return false ;
return true ;
}
2014-08-03 16:41:09 +00:00
// returns 0-based indices of used extruders
2017-05-03 16:28:22 +00:00
std : : vector < unsigned int > Print : : object_extruders ( ) const
2014-08-03 16:41:09 +00:00
{
2017-05-03 16:28:22 +00:00
std : : vector < unsigned int > extruders ;
2018-12-11 15:33:43 +00:00
extruders . reserve ( m_regions . size ( ) * 3 ) ;
2019-08-21 11:49:37 +00:00
std : : vector < unsigned char > region_used ( m_regions . size ( ) , false ) ;
for ( const PrintObject * object : m_objects )
for ( const std : : vector < std : : pair < t_layer_height_range , int > > & volumes_per_region : object - > region_volumes )
if ( ! volumes_per_region . empty ( ) )
region_used [ & volumes_per_region - & object - > region_volumes . front ( ) ] = true ;
for ( size_t idx_region = 0 ; idx_region < m_regions . size ( ) ; + + idx_region )
if ( region_used [ idx_region ] )
m_regions [ idx_region ] - > collect_object_printing_extruders ( extruders ) ;
2017-05-15 09:32:59 +00:00
sort_remove_duplicates ( extruders ) ;
2015-03-09 18:27:57 +00:00
return extruders ;
}
// returns 0-based indices of used extruders
2017-05-03 16:28:22 +00:00
std : : vector < unsigned int > Print : : support_material_extruders ( ) const
2015-03-09 18:27:57 +00:00
{
2017-05-03 16:28:22 +00:00
std : : vector < unsigned int > extruders ;
2017-01-30 18:56:46 +00:00
bool support_uses_current_extruder = false ;
2019-08-21 11:49:37 +00:00
auto num_extruders = ( unsigned int ) m_config . nozzle_diameter . size ( ) ;
2017-01-30 18:56:46 +00:00
2018-09-11 12:04:47 +00:00
for ( PrintObject * object : m_objects ) {
2017-05-03 16:28:22 +00:00
if ( object - > has_support_material ( ) ) {
2019-08-21 11:49:37 +00:00
assert ( object - > config ( ) . support_material_extruder > = 0 ) ;
2018-09-11 12:04:47 +00:00
if ( object - > config ( ) . support_material_extruder = = 0 )
2017-01-30 18:56:46 +00:00
support_uses_current_extruder = true ;
2019-08-21 11:49:37 +00:00
else {
unsigned int i = ( unsigned int ) object - > config ( ) . support_material_extruder - 1 ;
extruders . emplace_back ( ( i > = num_extruders ) ? 0 : i ) ;
}
assert ( object - > config ( ) . support_material_interface_extruder > = 0 ) ;
2018-09-11 12:04:47 +00:00
if ( object - > config ( ) . support_material_interface_extruder = = 0 )
2017-01-30 18:56:46 +00:00
support_uses_current_extruder = true ;
2019-08-21 11:49:37 +00:00
else {
unsigned int i = ( unsigned int ) object - > config ( ) . support_material_interface_extruder - 1 ;
extruders . emplace_back ( ( i > = num_extruders ) ? 0 : i ) ;
}
2015-03-06 08:56:58 +00:00
}
2014-08-03 16:41:09 +00:00
}
2017-01-30 18:56:46 +00:00
2017-05-03 16:28:22 +00:00
if ( support_uses_current_extruder )
2017-01-30 18:56:46 +00:00
// Add all object extruders to the support extruders as it is not know which one will be used to print supports.
2017-05-03 16:28:22 +00:00
append ( extruders , this - > object_extruders ( ) ) ;
2014-08-03 16:41:09 +00:00
2017-05-15 09:32:59 +00:00
sort_remove_duplicates ( extruders ) ;
2014-08-03 16:41:09 +00:00
return extruders ;
}
2015-03-09 18:36:23 +00:00
// returns 0-based indices of used extruders
2017-05-03 16:28:22 +00:00
std : : vector < unsigned int > Print : : extruders ( ) const
2015-03-09 18:36:23 +00:00
{
2017-05-03 16:28:22 +00:00
std : : vector < unsigned int > extruders = this - > object_extruders ( ) ;
append ( extruders , this - > support_material_extruders ( ) ) ;
2017-05-15 09:32:59 +00:00
sort_remove_duplicates ( extruders ) ;
2015-03-09 18:36:23 +00:00
return extruders ;
}
2018-10-23 20:53:43 +00:00
unsigned int Print : : num_object_instances ( ) const
{
unsigned int instances = 0 ;
for ( const PrintObject * print_object : m_objects )
2019-06-20 14:15:09 +00:00
instances + = ( unsigned int ) print_object - > copies ( ) . size ( ) ;
2018-10-23 20:53:43 +00:00
return instances ;
}
2017-05-03 16:28:22 +00:00
double Print : : max_allowed_layer_height ( ) const
2014-08-03 16:41:09 +00:00
{
2017-05-03 16:28:22 +00:00
double nozzle_diameter_max = 0. ;
for ( unsigned int extruder_id : this - > extruders ( ) )
2018-09-11 12:04:47 +00:00
nozzle_diameter_max = std : : max ( nozzle_diameter_max , m_config . nozzle_diameter . get_at ( extruder_id ) ) ;
2017-05-03 16:28:22 +00:00
return nozzle_diameter_max ;
2014-08-03 16:41:09 +00:00
}
2018-11-12 15:28:27 +00:00
// Add or remove support modifier ModelVolumes from model_object_dst to match the ModelVolumes of model_object_new
// in the exact order and with the same IDs.
// It is expected, that the model_object_dst already contains the non-support volumes of model_object_new in the correct order.
void Print : : model_volume_list_update_supports ( ModelObject & model_object_dst , const ModelObject & model_object_new )
2018-10-17 09:12:38 +00:00
{
2018-11-12 15:28:27 +00:00
typedef std : : pair < const ModelVolume * , bool > ModelVolumeWithStatus ;
std : : vector < ModelVolumeWithStatus > old_volumes ;
old_volumes . reserve ( model_object_dst . volumes . size ( ) ) ;
for ( const ModelVolume * model_volume : model_object_dst . volumes )
old_volumes . emplace_back ( ModelVolumeWithStatus ( model_volume , false ) ) ;
auto model_volume_lower = [ ] ( const ModelVolumeWithStatus & mv1 , const ModelVolumeWithStatus & mv2 ) { return mv1 . first - > id ( ) < mv2 . first - > id ( ) ; } ;
auto model_volume_equal = [ ] ( const ModelVolumeWithStatus & mv1 , const ModelVolumeWithStatus & mv2 ) { return mv1 . first - > id ( ) = = mv2 . first - > id ( ) ; } ;
std : : sort ( old_volumes . begin ( ) , old_volumes . end ( ) , model_volume_lower ) ;
model_object_dst . volumes . clear ( ) ;
model_object_dst . volumes . reserve ( model_object_new . volumes . size ( ) ) ;
for ( const ModelVolume * model_volume_src : model_object_new . volumes ) {
ModelVolumeWithStatus key ( model_volume_src , false ) ;
auto it = std : : lower_bound ( old_volumes . begin ( ) , old_volumes . end ( ) , key , model_volume_lower ) ;
if ( it ! = old_volumes . end ( ) & & model_volume_equal ( * it , key ) ) {
// The volume was found in the old list. Just copy it.
assert ( ! it - > second ) ; // not consumed yet
it - > second = true ;
ModelVolume * model_volume_dst = const_cast < ModelVolume * > ( it - > first ) ;
2019-02-21 07:44:07 +00:00
// For support modifiers, the type may have been switched from blocker to enforcer and vice versa.
assert ( ( model_volume_dst - > is_support_modifier ( ) & & model_volume_src - > is_support_modifier ( ) ) | | model_volume_dst - > type ( ) = = model_volume_src - > type ( ) ) ;
2018-11-12 15:28:27 +00:00
model_object_dst . volumes . emplace_back ( model_volume_dst ) ;
2019-02-21 07:44:07 +00:00
if ( model_volume_dst - > is_support_modifier ( ) ) {
// For support modifiers, the type may have been switched from blocker to enforcer and vice versa.
model_volume_dst - > set_type ( model_volume_src - > type ( ) ) ;
model_volume_dst - > set_transformation ( model_volume_src - > get_transformation ( ) ) ;
}
2018-11-12 15:28:27 +00:00
assert ( model_volume_dst - > get_matrix ( ) . isApprox ( model_volume_src - > get_matrix ( ) ) ) ;
} else {
// The volume was not found in the old list. Create a new copy.
assert ( model_volume_src - > is_support_modifier ( ) ) ;
model_object_dst . volumes . emplace_back ( new ModelVolume ( * model_volume_src ) ) ;
model_object_dst . volumes . back ( ) - > set_model_object ( & model_object_dst ) ;
2018-10-17 09:12:38 +00:00
}
}
2018-11-12 15:28:27 +00:00
// Release the non-consumed old volumes (those were deleted from the new list).
for ( ModelVolumeWithStatus & mv_with_status : old_volumes )
if ( ! mv_with_status . second )
delete mv_with_status . first ;
2018-10-17 09:12:38 +00:00
}
2019-02-22 11:12:10 +00:00
static inline void model_volume_list_copy_configs ( ModelObject & model_object_dst , const ModelObject & model_object_src , const ModelVolumeType type )
2018-11-09 13:25:18 +00:00
{
size_t i_src , i_dst ;
for ( i_src = 0 , i_dst = 0 ; i_src < model_object_src . volumes . size ( ) & & i_dst < model_object_dst . volumes . size ( ) ; ) {
const ModelVolume & mv_src = * model_object_src . volumes [ i_src ] ;
ModelVolume & mv_dst = * model_object_dst . volumes [ i_dst ] ;
if ( mv_src . type ( ) ! = type ) {
+ + i_src ;
continue ;
}
if ( mv_dst . type ( ) ! = type ) {
+ + i_dst ;
continue ;
}
assert ( mv_src . id ( ) = = mv_dst . id ( ) ) ;
// Copy the ModelVolume data.
mv_dst . name = mv_src . name ;
2019-07-03 11:43:54 +00:00
static_cast < DynamicPrintConfig & > ( mv_dst . config ) = static_cast < const DynamicPrintConfig & > ( mv_src . config ) ;
2018-11-09 13:25:18 +00:00
//FIXME what to do with the materials?
// mv_dst.m_material_id = mv_src.m_material_id;
+ + i_src ;
+ + i_dst ;
}
}
2019-06-20 14:15:09 +00:00
static inline void layer_height_ranges_copy_configs ( t_layer_config_ranges & lr_dst , const t_layer_config_ranges & lr_src )
{
assert ( lr_dst . size ( ) = = lr_src . size ( ) ) ;
auto it_src = lr_src . cbegin ( ) ;
for ( auto & kvp_dst : lr_dst ) {
const auto & kvp_src = * it_src + + ;
assert ( std : : abs ( kvp_dst . first . first - kvp_src . first . first ) < = EPSILON ) ;
assert ( std : : abs ( kvp_dst . first . second - kvp_src . first . second ) < = EPSILON ) ;
// Layer heights are allowed do differ in case the layer height table is being overriden by the smooth profile.
// assert(std::abs(kvp_dst.second.option("layer_height")->getFloat() - kvp_src.second.option("layer_height")->getFloat()) <= EPSILON);
kvp_dst . second = kvp_src . second ;
}
}
2018-10-17 09:12:38 +00:00
static inline bool transform3d_lower ( const Transform3d & lhs , const Transform3d & rhs )
{
typedef Transform3d : : Scalar T ;
const T * lv = lhs . data ( ) ;
const T * rv = rhs . data ( ) ;
for ( size_t i = 0 ; i < 16 ; + + i , + + lv , + + rv ) {
if ( * lv < * rv )
return true ;
else if ( * lv > * rv )
return false ;
}
return false ;
}
static inline bool transform3d_equal ( const Transform3d & lhs , const Transform3d & rhs )
{
typedef Transform3d : : Scalar T ;
const T * lv = lhs . data ( ) ;
const T * rv = rhs . data ( ) ;
for ( size_t i = 0 ; i < 16 ; + + i , + + lv , + + rv )
if ( * lv ! = * rv )
return false ;
return true ;
}
struct PrintInstances
{
Transform3d trafo ;
2018-10-18 12:36:46 +00:00
Points copies ;
2018-10-17 09:12:38 +00:00
bool operator < ( const PrintInstances & rhs ) const { return transform3d_lower ( this - > trafo , rhs . trafo ) ; }
} ;
// Generate a list of trafos and XY offsets for instances of a ModelObject
static std : : vector < PrintInstances > print_objects_from_model_object ( const ModelObject & model_object )
{
std : : set < PrintInstances > trafos ;
PrintInstances trafo ;
2018-10-18 12:36:46 +00:00
trafo . copies . assign ( 1 , Point ( ) ) ;
2018-10-17 09:12:38 +00:00
for ( ModelInstance * model_instance : model_object . instances )
if ( model_instance - > is_printable ( ) ) {
2018-11-01 13:25:10 +00:00
trafo . trafo = model_instance - > get_matrix ( ) ;
2018-10-31 19:02:07 +00:00
// Set the Z axis of the transformation.
2018-11-01 13:25:37 +00:00
trafo . copies . front ( ) = Point : : new_scale ( trafo . trafo . data ( ) [ 12 ] , trafo . trafo . data ( ) [ 13 ] ) ;
trafo . trafo . data ( ) [ 12 ] = 0 ;
trafo . trafo . data ( ) [ 13 ] = 0 ;
2018-10-17 09:12:38 +00:00
auto it = trafos . find ( trafo ) ;
if ( it = = trafos . end ( ) )
trafos . emplace ( trafo ) ;
else
2018-10-18 12:36:46 +00:00
const_cast < PrintInstances & > ( * it ) . copies . emplace_back ( trafo . copies . front ( ) ) ;
2018-10-17 09:12:38 +00:00
}
return std : : vector < PrintInstances > ( trafos . begin ( ) , trafos . end ( ) ) ;
}
2019-06-20 14:15:09 +00:00
// Compare just the layer ranges and their layer heights, not the associated configs.
// Ignore the layer heights if check_layer_heights is false.
bool layer_height_ranges_equal ( const t_layer_config_ranges & lr1 , const t_layer_config_ranges & lr2 , bool check_layer_height )
{
if ( lr1 . size ( ) ! = lr2 . size ( ) )
return false ;
auto it2 = lr2 . begin ( ) ;
for ( const auto & kvp1 : lr1 ) {
const auto & kvp2 = * it2 + + ;
if ( std : : abs ( kvp1 . first . first - kvp2 . first . first ) > EPSILON | |
std : : abs ( kvp1 . first . second - kvp2 . first . second ) > EPSILON | |
( check_layer_height & & std : : abs ( kvp1 . second . option ( " layer_height " ) - > getFloat ( ) - kvp2 . second . option ( " layer_height " ) - > getFloat ( ) ) > EPSILON ) )
return false ;
}
return true ;
}
2019-07-25 12:39:19 +00:00
// Collect diffs of configuration values at various containers,
// resolve the filament rectract overrides of extruder retract values.
void Print : : config_diffs (
const DynamicPrintConfig & new_full_config ,
t_config_option_keys & print_diff , t_config_option_keys & object_diff , t_config_option_keys & region_diff ,
t_config_option_keys & full_config_diff ,
DynamicPrintConfig & placeholder_parser_overrides ,
DynamicPrintConfig & filament_overrides ) const
{
// Collect changes to print config, account for overrides of extruder retract values by filament presets.
{
const std : : vector < std : : string > & extruder_retract_keys = print_config_def . extruder_retract_keys ( ) ;
const std : : string filament_prefix = " filament_ " ;
for ( const t_config_option_key & opt_key : m_config . keys ( ) ) {
const ConfigOption * opt_old = m_config . option ( opt_key ) ;
assert ( opt_old ! = nullptr ) ;
const ConfigOption * opt_new = new_full_config . option ( opt_key ) ;
2019-07-25 15:08:31 +00:00
// assert(opt_new != nullptr);
if ( opt_new = = nullptr )
//FIXME This may happen when executing some test cases.
continue ;
2019-07-25 12:39:19 +00:00
const ConfigOption * opt_new_filament = std : : binary_search ( extruder_retract_keys . begin ( ) , extruder_retract_keys . end ( ) , opt_key ) ? new_full_config . option ( filament_prefix + opt_key ) : nullptr ;
if ( opt_new_filament ! = nullptr & & ! opt_new_filament - > is_nil ( ) ) {
// An extruder retract override is available at some of the filament presets.
if ( * opt_old ! = * opt_new | | opt_new - > overriden_by ( opt_new_filament ) ) {
auto opt_copy = opt_new - > clone ( ) ;
opt_copy - > apply_override ( opt_new_filament ) ;
if ( * opt_old = = * opt_copy )
delete opt_copy ;
else {
filament_overrides . set_key_value ( opt_key , opt_copy ) ;
print_diff . emplace_back ( opt_key ) ;
}
}
} else if ( * opt_new ! = * opt_old )
print_diff . emplace_back ( opt_key ) ;
}
}
// Collect changes to object and region configs.
object_diff = m_default_object_config . diff ( new_full_config ) ;
region_diff = m_default_region_config . diff ( new_full_config ) ;
// Prepare for storing of the full print config into new_full_config to be exported into the G-code and to be used by the PlaceholderParser.
// As the PlaceholderParser does not interpret the FloatOrPercent values itself, these values are stored into the PlaceholderParser converted to floats.
for ( const t_config_option_key & opt_key : new_full_config . keys ( ) ) {
const ConfigOption * opt_old = m_full_print_config . option ( opt_key ) ;
const ConfigOption * opt_new = new_full_config . option ( opt_key ) ;
if ( opt_old = = nullptr | | * opt_new ! = * opt_old )
full_config_diff . emplace_back ( opt_key ) ;
if ( opt_new - > type ( ) = = coFloatOrPercent ) {
// The m_placeholder_parser is never modified by the background processing, GCode.cpp/hpp makes a copy.
const ConfigOption * opt_old_pp = this - > placeholder_parser ( ) . config ( ) . option ( opt_key ) ;
double new_value = new_full_config . get_abs_value ( opt_key ) ;
if ( opt_old_pp = = nullptr | | static_cast < const ConfigOptionFloat * > ( opt_old_pp ) - > value ! = new_value )
placeholder_parser_overrides . set_key_value ( opt_key , new ConfigOptionFloat ( new_value ) ) ;
}
}
}
Print : : ApplyStatus Print : : apply ( const Model & model , DynamicPrintConfig new_full_config )
2018-10-17 09:12:38 +00:00
{
2018-11-02 18:49:40 +00:00
# ifdef _DEBUG
check_model_ids_validity ( model ) ;
# endif /* _DEBUG */
2019-07-25 12:39:19 +00:00
// Normalize the config.
new_full_config . option ( " print_settings_id " , true ) ;
new_full_config . option ( " filament_settings_id " , true ) ;
new_full_config . option ( " printer_settings_id " , true ) ;
new_full_config . normalize ( ) ;
// Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles.
t_config_option_keys print_diff , object_diff , region_diff , full_config_diff ;
DynamicPrintConfig placeholder_parser_overrides , filament_overrides ;
this - > config_diffs ( new_full_config , print_diff , object_diff , region_diff , full_config_diff , placeholder_parser_overrides , filament_overrides ) ;
// Do not use the ApplyStatus as we will use the max function when updating apply_status.
2018-10-23 20:53:43 +00:00
unsigned int apply_status = APPLY_STATUS_UNCHANGED ;
auto update_apply_status = [ & apply_status ] ( bool invalidated )
{ apply_status = std : : max < unsigned int > ( apply_status , invalidated ? APPLY_STATUS_INVALIDATED : APPLY_STATUS_CHANGED ) ; } ;
if ( ! ( print_diff . empty ( ) & & object_diff . empty ( ) & & region_diff . empty ( ) ) )
update_apply_status ( false ) ;
2018-10-17 09:12:38 +00:00
// Grab the lock for the Print / PrintObject milestones.
2018-11-16 17:28:50 +00:00
tbb : : mutex : : scoped_lock lock ( this - > state_mutex ( ) ) ;
2018-10-17 09:12:38 +00:00
2018-10-18 12:36:46 +00:00
// The following call may stop the background processing.
2018-11-23 16:05:44 +00:00
if ( ! print_diff . empty ( ) )
update_apply_status ( this - > invalidate_state_by_config_options ( print_diff ) ) ;
2019-07-25 12:39:19 +00:00
2018-10-18 12:36:46 +00:00
// Apply variables to placeholder parser. The placeholder parser is used by G-code export,
// which should be stopped if print_diff is not empty.
2019-08-21 12:05:32 +00:00
size_t num_extruders = m_config . nozzle_diameter . size ( ) ;
bool num_extruders_changed = false ;
2019-07-25 12:39:19 +00:00
if ( ! full_config_diff . empty ( ) | | ! placeholder_parser_overrides . empty ( ) ) {
2018-10-23 20:53:43 +00:00
update_apply_status ( this - > invalidate_step ( psGCodeExport ) ) ;
2019-07-25 15:08:31 +00:00
m_placeholder_parser . apply_config ( std : : move ( placeholder_parser_overrides ) ) ;
2018-12-03 12:14:28 +00:00
// Set the profile aliases for the PrintBase::output_filename()
2019-07-25 15:08:31 +00:00
m_placeholder_parser . set ( " print_preset " , new_full_config . option ( " print_settings_id " ) - > clone ( ) ) ;
m_placeholder_parser . set ( " filament_preset " , new_full_config . option ( " filament_settings_id " ) - > clone ( ) ) ;
m_placeholder_parser . set ( " printer_preset " , new_full_config . option ( " printer_settings_id " ) - > clone ( ) ) ;
2019-07-25 12:39:19 +00:00
// It is also safe to change m_config now after this->invalidate_state_by_config_options() call.
m_config . apply_only ( new_full_config , print_diff , true ) ;
m_config . apply ( filament_overrides ) ;
// Handle changes to object config defaults
m_default_object_config . apply_only ( new_full_config , object_diff , true ) ;
// Handle changes to regions config defaults
m_default_region_config . apply_only ( new_full_config , region_diff , true ) ;
m_full_print_config = std : : move ( new_full_config ) ;
2019-08-21 12:05:32 +00:00
if ( num_extruders ! = m_config . nozzle_diameter . size ( ) ) {
num_extruders = m_config . nozzle_diameter . size ( ) ;
num_extruders_changed = true ;
}
2018-12-03 12:14:28 +00:00
}
2018-10-18 12:36:46 +00:00
2019-06-20 14:15:09 +00:00
class LayerRanges
{
public :
LayerRanges ( ) { }
// Convert input config ranges into continuous non-overlapping sorted vector of intervals and their configs.
void assign ( const t_layer_config_ranges & in ) {
m_ranges . clear ( ) ;
m_ranges . reserve ( in . size ( ) ) ;
// Input ranges are sorted lexicographically. First range trims the other ranges.
coordf_t last_z = 0 ;
2019-07-24 10:39:01 +00:00
for ( const std : : pair < const t_layer_height_range , DynamicPrintConfig > & range : in )
if ( range . first . second > last_z ) {
2019-06-20 14:15:09 +00:00
coordf_t min_z = std : : max ( range . first . first , 0. ) ;
if ( min_z > last_z + EPSILON ) {
m_ranges . emplace_back ( t_layer_height_range ( last_z , min_z ) , nullptr ) ;
last_z = min_z ;
}
if ( range . first . second > last_z + EPSILON ) {
const DynamicPrintConfig * cfg = & range . second ;
m_ranges . emplace_back ( t_layer_height_range ( last_z , range . first . second ) , cfg ) ;
last_z = range . first . second ;
}
}
if ( m_ranges . empty ( ) )
m_ranges . emplace_back ( t_layer_height_range ( 0 , DBL_MAX ) , nullptr ) ;
else if ( m_ranges . back ( ) . second = = nullptr )
m_ranges . back ( ) . first . second = DBL_MAX ;
else
m_ranges . emplace_back ( t_layer_height_range ( m_ranges . back ( ) . first . second , DBL_MAX ) , nullptr ) ;
}
const DynamicPrintConfig * config ( const t_layer_height_range & range ) const {
auto it = std : : lower_bound ( m_ranges . begin ( ) , m_ranges . end ( ) , std : : make_pair < t_layer_height_range , const DynamicPrintConfig * > ( t_layer_height_range ( range . first - EPSILON , range . second - EPSILON ) , nullptr ) ) ;
assert ( it ! = m_ranges . end ( ) ) ;
assert ( it = = m_ranges . end ( ) | | std : : abs ( it - > first . first - range . first ) < EPSILON ) ;
assert ( it = = m_ranges . end ( ) | | std : : abs ( it - > first . second - range . second ) < EPSILON ) ;
return ( it = = m_ranges . end ( ) ) ? nullptr : it - > second ;
}
2019-06-20 18:40:17 +00:00
std : : vector < std : : pair < t_layer_height_range , const DynamicPrintConfig * > > : : const_iterator begin ( ) const { return m_ranges . cbegin ( ) ; }
std : : vector < std : : pair < t_layer_height_range , const DynamicPrintConfig * > > : : const_iterator end ( ) const { return m_ranges . cend ( ) ; }
2019-06-20 14:15:09 +00:00
private :
std : : vector < std : : pair < t_layer_height_range , const DynamicPrintConfig * > > m_ranges ;
} ;
2018-10-17 09:12:38 +00:00
struct ModelObjectStatus {
enum Status {
Unknown ,
Old ,
New ,
Moved ,
Deleted ,
} ;
2019-07-04 18:49:46 +00:00
ModelObjectStatus ( ObjectID id , Status status = Unknown ) : id ( id ) , status ( status ) { }
ObjectID id ;
Status status ;
LayerRanges layer_ranges ;
2018-10-17 09:12:38 +00:00
// Search by id.
bool operator < ( const ModelObjectStatus & rhs ) const { return id < rhs . id ; }
} ;
std : : set < ModelObjectStatus > model_object_status ;
// 1) Synchronize model objects.
if ( model . id ( ) ! = m_model . id ( ) ) {
// Kill everything, initialize from scratch.
2018-10-18 12:36:46 +00:00
// Stop background processing.
2019-02-21 11:39:38 +00:00
this - > call_cancel_callback ( ) ;
2018-10-23 20:53:43 +00:00
update_apply_status ( this - > invalidate_all_steps ( ) ) ;
2018-10-17 09:12:38 +00:00
for ( PrintObject * object : m_objects ) {
model_object_status . emplace ( object - > model_object ( ) - > id ( ) , ModelObjectStatus : : Deleted ) ;
2018-12-22 09:02:42 +00:00
update_apply_status ( object - > invalidate_all_steps ( ) ) ;
delete object ;
2018-10-17 09:12:38 +00:00
}
m_objects . clear ( ) ;
for ( PrintRegion * region : m_regions )
delete region ;
m_regions . clear ( ) ;
2018-11-02 14:08:08 +00:00
m_model . assign_copy ( model ) ;
2018-10-18 16:03:17 +00:00
for ( const ModelObject * model_object : m_model . objects )
model_object_status . emplace ( model_object - > id ( ) , ModelObjectStatus : : New ) ;
2018-10-17 09:12:38 +00:00
} else {
if ( model_object_list_equal ( m_model , model ) ) {
// The object list did not change.
2018-10-18 16:03:17 +00:00
for ( const ModelObject * model_object : m_model . objects )
model_object_status . emplace ( model_object - > id ( ) , ModelObjectStatus : : Old ) ;
2018-10-17 09:12:38 +00:00
} else if ( model_object_list_extended ( m_model , model ) ) {
// Add new objects. Their volumes and configs will be synchronized later.
2018-10-23 20:53:43 +00:00
update_apply_status ( this - > invalidate_step ( psGCodeExport ) ) ;
2018-10-18 12:36:46 +00:00
for ( const ModelObject * model_object : m_model . objects )
model_object_status . emplace ( model_object - > id ( ) , ModelObjectStatus : : Old ) ;
2018-10-17 09:12:38 +00:00
for ( size_t i = m_model . objects . size ( ) ; i < model . objects . size ( ) ; + + i ) {
model_object_status . emplace ( model . objects [ i ] - > id ( ) , ModelObjectStatus : : New ) ;
2018-11-02 14:08:08 +00:00
m_model . objects . emplace_back ( ModelObject : : new_copy ( * model . objects [ i ] ) ) ;
m_model . objects . back ( ) - > set_model ( & m_model ) ;
2018-10-17 09:12:38 +00:00
}
} else {
// Reorder the objects, add new objects.
// First stop background processing before shuffling or deleting the PrintObjects in the object list.
2019-02-21 11:39:38 +00:00
this - > call_cancel_callback ( ) ;
2018-11-12 15:28:27 +00:00
update_apply_status ( this - > invalidate_step ( psGCodeExport ) ) ;
2018-10-17 09:12:38 +00:00
// Second create a new list of objects.
2018-10-23 20:53:43 +00:00
std : : vector < ModelObject * > model_objects_old ( std : : move ( m_model . objects ) ) ;
2018-10-17 09:12:38 +00:00
m_model . objects . clear ( ) ;
m_model . objects . reserve ( model . objects . size ( ) ) ;
auto by_id_lower = [ ] ( const ModelObject * lhs , const ModelObject * rhs ) { return lhs - > id ( ) < rhs - > id ( ) ; } ;
2018-10-23 20:53:43 +00:00
std : : sort ( model_objects_old . begin ( ) , model_objects_old . end ( ) , by_id_lower ) ;
2018-10-17 09:12:38 +00:00
for ( const ModelObject * mobj : model . objects ) {
2018-10-23 20:53:43 +00:00
auto it = std : : lower_bound ( model_objects_old . begin ( ) , model_objects_old . end ( ) , mobj , by_id_lower ) ;
if ( it = = model_objects_old . end ( ) | | ( * it ) - > id ( ) ! = mobj - > id ( ) ) {
2018-10-17 09:12:38 +00:00
// New ModelObject added.
2018-11-02 18:49:40 +00:00
m_model . objects . emplace_back ( ModelObject : : new_copy ( * mobj ) ) ;
2018-11-02 14:08:08 +00:00
m_model . objects . back ( ) - > set_model ( & m_model ) ;
2018-10-17 09:12:38 +00:00
model_object_status . emplace ( mobj - > id ( ) , ModelObjectStatus : : New ) ;
} else {
// Existing ModelObject re-added (possibly moved in the list).
m_model . objects . emplace_back ( * it ) ;
2018-10-18 12:36:46 +00:00
model_object_status . emplace ( mobj - > id ( ) , ModelObjectStatus : : Moved ) ;
2018-10-17 09:12:38 +00:00
}
}
bool deleted_any = false ;
2018-10-23 20:53:43 +00:00
for ( ModelObject * & model_object : model_objects_old ) {
if ( model_object_status . find ( ModelObjectStatus ( model_object - > id ( ) ) ) = = model_object_status . end ( ) ) {
model_object_status . emplace ( model_object - > id ( ) , ModelObjectStatus : : Deleted ) ;
2018-10-17 09:12:38 +00:00
deleted_any = true ;
2018-10-23 20:53:43 +00:00
} else
// Do not delete this ModelObject instance.
model_object = nullptr ;
}
2018-10-17 09:12:38 +00:00
if ( deleted_any ) {
// Delete PrintObjects of the deleted ModelObjects.
2018-10-23 20:53:43 +00:00
std : : vector < PrintObject * > print_objects_old = std : : move ( m_objects ) ;
2018-10-17 09:12:38 +00:00
m_objects . clear ( ) ;
2018-10-23 20:53:43 +00:00
m_objects . reserve ( print_objects_old . size ( ) ) ;
for ( PrintObject * print_object : print_objects_old ) {
2018-10-17 09:12:38 +00:00
auto it_status = model_object_status . find ( ModelObjectStatus ( print_object - > model_object ( ) - > id ( ) ) ) ;
assert ( it_status ! = model_object_status . end ( ) ) ;
if ( it_status - > status = = ModelObjectStatus : : Deleted ) {
2018-10-23 20:53:43 +00:00
update_apply_status ( print_object - > invalidate_all_steps ( ) ) ;
2018-10-17 09:12:38 +00:00
delete print_object ;
} else
m_objects . emplace_back ( print_object ) ;
}
2018-10-23 20:53:43 +00:00
for ( ModelObject * model_object : model_objects_old )
delete model_object ;
2018-10-17 09:12:38 +00:00
}
}
}
// 2) Map print objects including their transformation matrices.
struct PrintObjectStatus {
enum Status {
Unknown ,
Deleted ,
2018-10-30 08:27:31 +00:00
Reused ,
2018-10-17 09:12:38 +00:00
New
} ;
PrintObjectStatus ( PrintObject * print_object , Status status = Unknown ) :
id ( print_object - > model_object ( ) - > id ( ) ) ,
print_object ( print_object ) ,
trafo ( print_object - > trafo ( ) ) ,
status ( status ) { }
2019-06-27 09:02:45 +00:00
PrintObjectStatus ( ObjectID id ) : id ( id ) , print_object ( nullptr ) , trafo ( Transform3d : : Identity ( ) ) , status ( Unknown ) { }
2018-10-17 09:12:38 +00:00
// ID of the ModelObject & PrintObject
2019-06-27 09:02:45 +00:00
ObjectID id ;
2018-10-17 09:12:38 +00:00
// Pointer to the old PrintObject
PrintObject * print_object ;
// Trafo generated with model_object->world_matrix(true)
Transform3d trafo ;
Status status ;
// Search by id.
bool operator < ( const PrintObjectStatus & rhs ) const { return id < rhs . id ; }
} ;
std : : multiset < PrintObjectStatus > print_object_status ;
for ( PrintObject * print_object : m_objects )
print_object_status . emplace ( PrintObjectStatus ( print_object ) ) ;
// 3) Synchronize ModelObjects & PrintObjects.
for ( size_t idx_model_object = 0 ; idx_model_object < model . objects . size ( ) ; + + idx_model_object ) {
ModelObject & model_object = * m_model . objects [ idx_model_object ] ;
auto it_status = model_object_status . find ( ModelObjectStatus ( model_object . id ( ) ) ) ;
assert ( it_status ! = model_object_status . end ( ) ) ;
assert ( it_status - > status ! = ModelObjectStatus : : Deleted ) ;
2019-06-20 14:15:09 +00:00
const ModelObject & model_object_new = * model . objects [ idx_model_object ] ;
const_cast < ModelObjectStatus & > ( * it_status ) . layer_ranges . assign ( model_object_new . layer_config_ranges ) ;
2018-10-17 09:12:38 +00:00
if ( it_status - > status = = ModelObjectStatus : : New )
// PrintObject instances will be added in the next loop.
continue ;
// Update the ModelObject instance, possibly invalidate the linked PrintObjects.
2018-10-18 12:36:46 +00:00
assert ( it_status - > status = = ModelObjectStatus : : Old | | it_status - > status = = ModelObjectStatus : : Moved ) ;
2018-10-17 09:12:38 +00:00
// Check whether a model part volume was added or removed, their transformations or order changed.
2019-06-20 14:15:09 +00:00
// Only volume IDs, volume types and their order are checked, configuration and other parameters are NOT checked.
2019-02-22 11:12:10 +00:00
bool model_parts_differ = model_volume_list_changed ( model_object , model_object_new , ModelVolumeType : : MODEL_PART ) ;
bool modifiers_differ = model_volume_list_changed ( model_object , model_object_new , ModelVolumeType : : PARAMETER_MODIFIER ) ;
bool support_blockers_differ = model_volume_list_changed ( model_object , model_object_new , ModelVolumeType : : SUPPORT_BLOCKER ) ;
bool support_enforcers_differ = model_volume_list_changed ( model_object , model_object_new , ModelVolumeType : : SUPPORT_ENFORCER ) ;
2018-10-23 13:27:31 +00:00
if ( model_parts_differ | | modifiers_differ | |
model_object . origin_translation ! = model_object_new . origin_translation | |
2019-06-20 14:15:09 +00:00
model_object . layer_height_profile ! = model_object_new . layer_height_profile | |
! layer_height_ranges_equal ( model_object . layer_config_ranges , model_object_new . layer_config_ranges , model_object_new . layer_height_profile . empty ( ) ) ) {
2018-10-17 09:12:38 +00:00
// The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects.
auto range = print_object_status . equal_range ( PrintObjectStatus ( model_object . id ( ) ) ) ;
for ( auto it = range . first ; it ! = range . second ; + + it ) {
2018-10-23 20:53:43 +00:00
update_apply_status ( it - > print_object - > invalidate_all_steps ( ) ) ;
2018-10-17 09:12:38 +00:00
const_cast < PrintObjectStatus & > ( * it ) . status = PrintObjectStatus : : Deleted ;
}
2018-11-02 14:08:08 +00:00
// Copy content of the ModelObject including its ID, do not change the parent.
model_object . assign_copy ( model_object_new ) ;
2018-10-17 09:12:38 +00:00
} else if ( support_blockers_differ | | support_enforcers_differ ) {
// First stop background processing before shuffling or deleting the ModelVolumes in the ModelObject's list.
2019-02-21 11:39:38 +00:00
this - > call_cancel_callback ( ) ;
2018-11-12 15:28:27 +00:00
update_apply_status ( false ) ;
2018-10-17 09:12:38 +00:00
// Invalidate just the supports step.
auto range = print_object_status . equal_range ( PrintObjectStatus ( model_object . id ( ) ) ) ;
for ( auto it = range . first ; it ! = range . second ; + + it )
2018-10-23 20:53:43 +00:00
update_apply_status ( it - > print_object - > invalidate_step ( posSupportMaterial ) ) ;
2018-10-17 09:12:38 +00:00
// Copy just the support volumes.
model_volume_list_update_supports ( model_object , model_object_new ) ;
}
if ( ! model_parts_differ & & ! modifiers_differ ) {
2018-10-18 12:36:46 +00:00
// Synchronize Object's config.
2018-11-09 13:25:18 +00:00
bool object_config_changed = model_object . config ! = model_object_new . config ;
if ( object_config_changed )
2019-07-03 11:43:54 +00:00
static_cast < DynamicPrintConfig & > ( model_object . config ) = static_cast < const DynamicPrintConfig & > ( model_object_new . config ) ;
2019-08-21 12:05:32 +00:00
if ( ! object_diff . empty ( ) | | object_config_changed | | num_extruders_changed ) {
2019-01-21 09:06:51 +00:00
PrintObjectConfig new_config = PrintObject : : object_config_from_model_object ( m_default_object_config , model_object , num_extruders ) ;
2018-10-18 12:36:46 +00:00
auto range = print_object_status . equal_range ( PrintObjectStatus ( model_object . id ( ) ) ) ;
for ( auto it = range . first ; it ! = range . second ; + + it ) {
t_config_option_keys diff = it - > print_object - > config ( ) . diff ( new_config ) ;
2018-10-23 20:53:43 +00:00
if ( ! diff . empty ( ) ) {
update_apply_status ( it - > print_object - > invalidate_state_by_config_options ( diff ) ) ;
it - > print_object - > config_apply_only ( new_config , diff , true ) ;
}
2018-10-18 12:36:46 +00:00
}
}
2018-11-09 13:25:18 +00:00
// Synchronize (just copy) the remaining data of ModelVolumes (name, config).
//FIXME What to do with m_material_id?
2019-02-22 11:12:10 +00:00
model_volume_list_copy_configs ( model_object /* dst */ , model_object_new /* src */ , ModelVolumeType : : MODEL_PART ) ;
model_volume_list_copy_configs ( model_object /* dst */ , model_object_new /* src */ , ModelVolumeType : : PARAMETER_MODIFIER ) ;
2019-06-20 14:15:09 +00:00
layer_height_ranges_copy_configs ( model_object . layer_config_ranges /* dst */ , model_object_new . layer_config_ranges /* src */ ) ;
// Copy the ModelObject name, input_file and instances. The instances will be compared against PrintObject instances in the next step.
2018-10-23 13:27:31 +00:00
model_object . name = model_object_new . name ;
model_object . input_file = model_object_new . input_file ;
2018-10-23 20:53:43 +00:00
model_object . clear_instances ( ) ;
2018-11-02 18:49:40 +00:00
model_object . instances . reserve ( model_object_new . instances . size ( ) ) ;
for ( const ModelInstance * model_instance : model_object_new . instances ) {
model_object . instances . emplace_back ( new ModelInstance ( * model_instance ) ) ;
model_object . instances . back ( ) - > set_model_object ( & model_object ) ;
}
2018-10-17 09:12:38 +00:00
}
}
// 4) Generate PrintObjects from ModelObjects and their instances.
2018-10-18 12:36:46 +00:00
{
std : : vector < PrintObject * > print_objects_new ;
print_objects_new . reserve ( std : : max ( m_objects . size ( ) , m_model . objects . size ( ) ) ) ;
2018-10-30 08:27:31 +00:00
bool new_objects = false ;
2018-10-18 12:36:46 +00:00
// Walk over all new model objects and check, whether there are matching PrintObjects.
for ( ModelObject * model_object : m_model . objects ) {
auto range = print_object_status . equal_range ( PrintObjectStatus ( model_object - > id ( ) ) ) ;
std : : vector < const PrintObjectStatus * > old ;
if ( range . first ! = range . second ) {
old . reserve ( print_object_status . count ( PrintObjectStatus ( model_object - > id ( ) ) ) ) ;
for ( auto it = range . first ; it ! = range . second ; + + it )
if ( it - > status ! = PrintObjectStatus : : Deleted )
old . emplace_back ( & ( * it ) ) ;
}
// Generate a list of trafos and XY offsets for instances of a ModelObject
2019-01-21 09:06:51 +00:00
PrintObjectConfig config = PrintObject : : object_config_from_model_object ( m_default_object_config , * model_object , num_extruders ) ;
2018-10-18 12:36:46 +00:00
std : : vector < PrintInstances > new_print_instances = print_objects_from_model_object ( * model_object ) ;
if ( old . empty ( ) ) {
// Simple case, just generate new instances.
for ( const PrintInstances & print_instances : new_print_instances ) {
2018-12-07 10:21:05 +00:00
PrintObject * print_object = new PrintObject ( this , model_object , false ) ;
2018-10-24 09:48:39 +00:00
print_object - > set_trafo ( print_instances . trafo ) ;
2018-10-18 12:36:46 +00:00
print_object - > set_copies ( print_instances . copies ) ;
print_object - > config_apply ( config ) ;
print_objects_new . emplace_back ( print_object ) ;
2018-10-30 08:27:31 +00:00
// print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New));
new_objects = true ;
2018-10-18 12:36:46 +00:00
}
continue ;
}
// Complex case, try to merge the two lists.
// Sort the old lexicographically by their trafos.
std : : sort ( old . begin ( ) , old . end ( ) , [ ] ( const PrintObjectStatus * lhs , const PrintObjectStatus * rhs ) { return transform3d_lower ( lhs - > trafo , rhs - > trafo ) ; } ) ;
// Merge the old / new lists.
auto it_old = old . begin ( ) ;
for ( const PrintInstances & new_instances : new_print_instances ) {
2018-10-24 09:48:39 +00:00
for ( ; it_old ! = old . end ( ) & & transform3d_lower ( ( * it_old ) - > trafo , new_instances . trafo ) ; + + it_old ) ;
if ( it_old = = old . end ( ) | | ! transform3d_equal ( ( * it_old ) - > trafo , new_instances . trafo ) ) {
2018-10-18 12:36:46 +00:00
// This is a new instance (or a set of instances with the same trafo). Just add it.
2018-12-07 10:21:05 +00:00
PrintObject * print_object = new PrintObject ( this , model_object , false ) ;
2018-10-24 09:48:39 +00:00
print_object - > set_trafo ( new_instances . trafo ) ;
2018-10-18 12:36:46 +00:00
print_object - > set_copies ( new_instances . copies ) ;
print_object - > config_apply ( config ) ;
print_objects_new . emplace_back ( print_object ) ;
2018-10-30 08:27:31 +00:00
// print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New));
new_objects = true ;
if ( it_old ! = old . end ( ) )
const_cast < PrintObjectStatus * > ( * it_old ) - > status = PrintObjectStatus : : Deleted ;
2018-10-31 18:21:00 +00:00
} else {
2018-10-23 13:27:31 +00:00
// The PrintObject already exists and the copies differ.
2018-12-22 09:02:42 +00:00
PrintBase : : ApplyStatus status = ( * it_old ) - > print_object - > set_copies ( new_instances . copies ) ;
if ( status ! = PrintBase : : APPLY_STATUS_UNCHANGED )
update_apply_status ( status = = PrintBase : : APPLY_STATUS_INVALIDATED ) ;
2018-10-23 13:27:31 +00:00
print_objects_new . emplace_back ( ( * it_old ) - > print_object ) ;
2018-10-30 08:27:31 +00:00
const_cast < PrintObjectStatus * > ( * it_old ) - > status = PrintObjectStatus : : Reused ;
2018-10-23 13:27:31 +00:00
}
2018-10-17 09:12:38 +00:00
}
}
2018-10-18 12:36:46 +00:00
if ( m_objects ! = print_objects_new ) {
2019-02-21 11:39:38 +00:00
this - > call_cancel_callback ( ) ;
2018-11-12 15:28:27 +00:00
update_apply_status ( this - > invalidate_all_steps ( ) ) ;
2018-10-18 12:36:46 +00:00
m_objects = print_objects_new ;
2018-10-30 08:27:31 +00:00
// Delete the PrintObjects marked as Unknown or Deleted.
bool deleted_objects = false ;
for ( auto & pos : print_object_status )
if ( pos . status = = PrintObjectStatus : : Unknown | | pos . status = = PrintObjectStatus : : Deleted ) {
2018-12-22 09:02:42 +00:00
update_apply_status ( pos . print_object - > invalidate_all_steps ( ) ) ;
2018-10-30 08:27:31 +00:00
delete pos . print_object ;
deleted_objects = true ;
}
2018-10-31 18:21:00 +00:00
if ( new_objects | | deleted_objects )
update_apply_status ( this - > invalidate_steps ( { psSkirt , psBrim , psWipeTower , psGCodeExport } ) ) ;
2018-12-22 09:02:42 +00:00
if ( new_objects )
update_apply_status ( false ) ;
2018-10-18 12:36:46 +00:00
}
2018-10-30 08:27:31 +00:00
print_object_status . clear ( ) ;
2018-10-17 09:12:38 +00:00
}
2018-10-18 12:36:46 +00:00
// 5) Synchronize configs of ModelVolumes, synchronize AMF / 3MF materials (and their configs), refresh PrintRegions.
// Update reference counts of regions from the remaining PrintObjects and their volumes.
// Regions with zero references could and should be reused.
for ( PrintRegion * region : m_regions )
region - > m_refcnt = 0 ;
for ( PrintObject * print_object : m_objects ) {
int idx_region = 0 ;
for ( const auto & volumes : print_object - > region_volumes ) {
if ( ! volumes . empty ( ) )
2018-10-23 13:27:31 +00:00
+ + m_regions [ idx_region ] - > m_refcnt ;
2018-10-18 12:36:46 +00:00
+ + idx_region ;
}
2018-10-17 09:12:38 +00:00
}
2018-10-18 12:36:46 +00:00
// All regions now have distinct settings.
// Check whether applying the new region config defaults we'd get different regions.
for ( size_t region_id = 0 ; region_id < m_regions . size ( ) ; + + region_id ) {
PrintRegion & region = * m_regions [ region_id ] ;
PrintRegionConfig this_region_config ;
bool this_region_config_set = false ;
for ( PrintObject * print_object : m_objects ) {
2019-06-20 14:15:09 +00:00
const LayerRanges * layer_ranges ;
{
auto it_status = model_object_status . find ( ModelObjectStatus ( print_object - > model_object ( ) - > id ( ) ) ) ;
assert ( it_status ! = model_object_status . end ( ) ) ;
assert ( it_status - > status ! = ModelObjectStatus : : Deleted ) ;
layer_ranges = & it_status - > layer_ranges ;
}
2018-10-18 12:36:46 +00:00
if ( region_id < print_object - > region_volumes . size ( ) ) {
2019-06-20 14:15:09 +00:00
for ( const std : : pair < t_layer_height_range , int > & volume_and_range : print_object - > region_volumes [ region_id ] ) {
const ModelVolume & volume = * print_object - > model_object ( ) - > volumes [ volume_and_range . second ] ;
const DynamicPrintConfig * layer_range_config = layer_ranges - > config ( volume_and_range . first ) ;
2018-10-18 12:36:46 +00:00
if ( this_region_config_set ) {
// If the new config for this volume differs from the other
// volume configs currently associated to this region, it means
// the region subdivision does not make sense anymore.
2019-06-20 14:15:09 +00:00
if ( ! this_region_config . equals ( PrintObject : : region_config_from_model_volume ( m_default_region_config , layer_range_config , volume , num_extruders ) ) )
2018-10-18 12:36:46 +00:00
// Regions were split. Reset this print_object.
goto print_object_end ;
} else {
2019-06-20 14:15:09 +00:00
this_region_config = PrintObject : : region_config_from_model_volume ( m_default_region_config , layer_range_config , volume , num_extruders ) ;
for ( size_t i = 0 ; i < region_id ; + + i ) {
2018-12-17 17:04:23 +00:00
const PrintRegion & region_other = * m_regions [ i ] ;
if ( region_other . m_refcnt ! = 0 & & region_other . config ( ) . equals ( this_region_config ) )
// Regions were merged. Reset this print_object.
goto print_object_end ;
}
2018-10-18 12:36:46 +00:00
this_region_config_set = true ;
}
}
}
continue ;
print_object_end :
2018-10-23 20:53:43 +00:00
update_apply_status ( print_object - > invalidate_all_steps ( ) ) ;
2018-10-18 12:36:46 +00:00
// Decrease the references to regions from this volume.
int ireg = 0 ;
2019-06-20 14:15:09 +00:00
for ( const std : : vector < std : : pair < t_layer_height_range , int > > & volumes : print_object - > region_volumes ) {
2018-10-18 12:36:46 +00:00
if ( ! volumes . empty ( ) )
2018-11-12 15:28:27 +00:00
- - m_regions [ ireg ] - > m_refcnt ;
2018-10-18 12:36:46 +00:00
+ + ireg ;
}
print_object - > region_volumes . clear ( ) ;
}
if ( this_region_config_set ) {
t_config_option_keys diff = region . config ( ) . diff ( this_region_config ) ;
if ( ! diff . empty ( ) ) {
region . config_apply_only ( this_region_config , diff , false ) ;
for ( PrintObject * print_object : m_objects )
if ( region_id < print_object - > region_volumes . size ( ) & & ! print_object - > region_volumes [ region_id ] . empty ( ) )
2018-10-23 20:53:43 +00:00
update_apply_status ( print_object - > invalidate_state_by_config_options ( diff ) ) ;
2018-10-18 12:36:46 +00:00
}
}
}
2018-10-17 09:12:38 +00:00
2018-10-18 12:36:46 +00:00
// Possibly add new regions for the newly added or resetted PrintObjects.
for ( size_t idx_print_object = 0 ; idx_print_object < m_objects . size ( ) ; + + idx_print_object ) {
PrintObject & print_object0 = * m_objects [ idx_print_object ] ;
const ModelObject & model_object = * print_object0 . model_object ( ) ;
2019-06-20 14:15:09 +00:00
const LayerRanges * layer_ranges ;
{
auto it_status = model_object_status . find ( ModelObjectStatus ( model_object . id ( ) ) ) ;
assert ( it_status ! = model_object_status . end ( ) ) ;
assert ( it_status - > status ! = ModelObjectStatus : : Deleted ) ;
layer_ranges = & it_status - > layer_ranges ;
}
std : : vector < int > regions_in_object ;
regions_in_object . reserve ( 64 ) ;
2018-10-18 12:36:46 +00:00
for ( size_t i = idx_print_object ; i < m_objects . size ( ) & & m_objects [ i ] - > model_object ( ) = = & model_object ; + + i ) {
PrintObject & print_object = * m_objects [ i ] ;
2018-10-18 16:03:17 +00:00
bool fresh = print_object . region_volumes . empty ( ) ;
2018-10-18 12:36:46 +00:00
unsigned int volume_id = 0 ;
2019-06-20 14:15:09 +00:00
unsigned int idx_region_in_object = 0 ;
2018-10-18 12:36:46 +00:00
for ( const ModelVolume * volume : model_object . volumes ) {
2018-12-17 18:46:36 +00:00
if ( ! volume - > is_model_part ( ) & & ! volume - > is_modifier ( ) ) {
+ + volume_id ;
continue ;
}
2019-06-20 14:15:09 +00:00
// Filter the layer ranges, so they do not overlap and they contain at least a single layer.
// Now insert a volume with a layer range to its own region.
for ( auto it_range = layer_ranges - > begin ( ) ; it_range ! = layer_ranges - > end ( ) ; + + it_range ) {
int region_id = - 1 ;
if ( & print_object = = & print_object0 ) {
// Get the config applied to this volume.
PrintRegionConfig config = PrintObject : : region_config_from_model_volume ( m_default_region_config , it_range - > second , * volume , num_extruders ) ;
// Find an existing print region with the same config.
int idx_empty_slot = - 1 ;
for ( int i = 0 ; i < ( int ) m_regions . size ( ) ; + + i ) {
if ( m_regions [ i ] - > m_refcnt = = 0 ) {
if ( idx_empty_slot = = - 1 )
idx_empty_slot = i ;
} else if ( config . equals ( m_regions [ i ] - > config ( ) ) ) {
region_id = i ;
break ;
}
}
// If no region exists with the same config, create a new one.
if ( region_id = = - 1 ) {
if ( idx_empty_slot = = - 1 ) {
region_id = ( int ) m_regions . size ( ) ;
this - > add_region ( config ) ;
} else {
region_id = idx_empty_slot ;
m_regions [ region_id ] - > set_config ( std : : move ( config ) ) ;
}
2018-10-18 12:36:46 +00:00
}
2019-06-20 14:15:09 +00:00
regions_in_object . emplace_back ( region_id ) ;
} else
region_id = regions_in_object [ idx_region_in_object + + ] ;
// Assign volume to a region.
if ( fresh ) {
2019-07-15 11:26:55 +00:00
if ( ( size_t ) region_id > = print_object . region_volumes . size ( ) | | print_object . region_volumes [ region_id ] . empty ( ) )
2019-06-20 14:15:09 +00:00
+ + m_regions [ region_id ] - > m_refcnt ;
print_object . add_region_volume ( region_id , volume_id , it_range - > first ) ;
}
}
+ + volume_id ;
}
2018-10-17 09:12:38 +00:00
}
}
2019-03-05 13:05:58 +00:00
// Update SlicingParameters for each object where the SlicingParameters is not valid.
// If it is not valid, then it is ensured that PrintObject.m_slicing_params is not in use
// (posSlicing and posSupportMaterial was invalidated).
for ( PrintObject * object : m_objects )
object - > update_slicing_parameters ( ) ;
2018-11-02 18:49:40 +00:00
# ifdef _DEBUG
check_model_ids_equal ( m_model , model ) ;
# endif /* _DEBUG */
2018-10-23 20:53:43 +00:00
return static_cast < ApplyStatus > ( apply_status ) ;
2018-10-17 09:12:38 +00:00
}
2015-03-06 08:56:58 +00:00
bool Print : : has_infinite_skirt ( ) const
2014-08-03 16:41:09 +00:00
{
2018-09-11 12:04:47 +00:00
return ( m_config . skirt_height = = - 1 & & m_config . skirts > 0 )
| | ( m_config . ooze_prevention & & this - > extruders ( ) . size ( ) > 1 ) ;
2015-03-06 08:56:58 +00:00
}
bool Print : : has_skirt ( ) const
{
2018-09-11 12:04:47 +00:00
return ( m_config . skirt_height > 0 & & m_config . skirts > 0 )
2015-03-06 08:56:58 +00:00
| | this - > has_infinite_skirt ( ) ;
2014-08-03 16:41:09 +00:00
}
2018-12-11 15:33:43 +00:00
// Precondition: Print::validate() requires the Print::apply() to be called its invocation.
2017-05-30 18:09:34 +00:00
std : : string Print : : validate ( ) const
2014-11-09 14:27:34 +00:00
{
2018-10-23 13:27:31 +00:00
if ( m_objects . empty ( ) )
2018-07-18 07:37:25 +00:00
return L ( " All objects are outside of the print volume. " ) ;
2019-10-01 10:48:58 +00:00
if ( extruders ( ) . empty ( ) )
return L ( " The supplied settings will cause an empty print. " ) ;
2018-09-11 12:04:47 +00:00
if ( m_config . complete_objects ) {
2017-05-31 10:55:59 +00:00
// Check horizontal clearance.
2014-11-09 14:27:34 +00:00
{
2017-05-31 10:55:59 +00:00
Polygons convex_hulls_other ;
2019-04-18 10:45:43 +00:00
for ( const PrintObject * print_object : m_objects ) {
assert ( ! print_object - > model_object ( ) - > instances . empty ( ) ) ;
assert ( ! print_object - > copies ( ) . empty ( ) ) ;
2017-06-13 09:35:24 +00:00
// Get convex hull of all meshes assigned to this print object.
2019-04-18 10:45:43 +00:00
ModelInstance * model_instance0 = print_object - > model_object ( ) - > instances . front ( ) ;
Vec3d rotation = model_instance0 - > get_rotation ( ) ;
rotation . z ( ) = 0. ;
// Calculate the convex hull of a printable object centered around X=0,Y=0.
2017-05-31 10:55:59 +00:00
// Grow convex hull with the clearance margin.
2019-02-08 11:30:10 +00:00
// FIXME: Arrangement has different parameters for offsetting (jtMiter, limit 2)
// which causes that the warning will be showed after arrangement with the
// appropriate object distance. Even if I set this to jtMiter the warning still shows up.
2019-04-18 10:45:43 +00:00
Polygon convex_hull0 = offset (
print_object - > model_object ( ) - > convex_hull_2d (
Geometry : : assemble_transform ( Vec3d : : Zero ( ) , rotation , model_instance0 - > get_scaling_factor ( ) , model_instance0 - > get_mirror ( ) ) ) ,
2019-06-20 14:15:09 +00:00
float ( scale_ ( 0.5 * m_config . extruder_clearance_radius . value ) ) , jtRound , float ( scale_ ( 0.1 ) ) ) . front ( ) ;
2017-05-31 10:55:59 +00:00
// Now we check that no instance of convex_hull intersects any of the previously checked object instances.
2019-06-20 18:23:05 +00:00
for ( const Point & copy : print_object - > copies ( ) ) {
2019-04-18 10:45:43 +00:00
Polygon convex_hull = convex_hull0 ;
convex_hull . translate ( copy ) ;
if ( ! intersection ( convex_hulls_other , convex_hull ) . empty ( ) )
2018-06-20 11:57:37 +00:00
return L ( " Some objects are too close; your extruder will collide with them. " ) ;
2019-04-18 10:45:43 +00:00
polygons_append ( convex_hulls_other , convex_hull ) ;
2014-11-09 14:27:34 +00:00
}
}
}
2017-05-31 10:55:59 +00:00
// Check vertical clearance.
2014-11-09 14:27:34 +00:00
{
std : : vector < coord_t > object_height ;
2018-09-11 12:04:47 +00:00
for ( const PrintObject * object : m_objects )
2018-08-17 13:53:43 +00:00
object_height . insert ( object_height . end ( ) , object - > copies ( ) . size ( ) , object - > size ( 2 ) ) ;
2014-11-09 14:27:34 +00:00
std : : sort ( object_height . begin ( ) , object_height . end ( ) ) ;
2017-05-31 10:55:59 +00:00
// Ignore the tallest *copy* (this is why we repeat height for all of them):
// it will be printed as last one so its height doesn't matter.
2014-11-09 14:27:34 +00:00
object_height . pop_back ( ) ;
2018-09-11 12:04:47 +00:00
if ( ! object_height . empty ( ) & & object_height . back ( ) > scale_ ( m_config . extruder_clearance_height . value ) )
2018-06-20 11:57:37 +00:00
return L ( " Some objects are too tall and cannot be printed without extruder collisions. " ) ;
2014-11-09 14:27:34 +00:00
}
2018-09-11 12:04:47 +00:00
} // end if (m_config.complete_objects)
2017-05-31 10:55:59 +00:00
2018-09-11 12:04:47 +00:00
if ( m_config . spiral_vase ) {
2014-11-09 14:27:34 +00:00
size_t total_copies_count = 0 ;
2018-09-11 12:04:47 +00:00
for ( const PrintObject * object : m_objects )
2017-05-31 10:55:59 +00:00
total_copies_count + = object - > copies ( ) . size ( ) ;
2017-06-23 08:13:09 +00:00
// #4043
2018-09-11 12:04:47 +00:00
if ( total_copies_count > 1 & & ! m_config . complete_objects . value )
2018-06-20 11:57:37 +00:00
return L ( " The Spiral Vase option can only be used when printing a single object. " ) ;
2019-08-21 11:08:26 +00:00
assert ( m_objects . size ( ) = = 1 ) ;
size_t num_regions = 0 ;
for ( const std : : vector < std : : pair < t_layer_height_range , int > > & volumes_per_region : m_objects . front ( ) - > region_volumes )
if ( ! volumes_per_region . empty ( ) )
+ + num_regions ;
if ( num_regions > 1 )
2018-06-20 11:57:37 +00:00
return L ( " The Spiral Vase option can only be used when printing single material objects. " ) ;
2014-11-09 14:27:34 +00:00
}
2017-06-06 08:36:14 +00:00
2018-09-11 12:04:47 +00:00
if ( this - > has_wipe_tower ( ) & & ! m_objects . empty ( ) ) {
2019-08-15 08:35:50 +00:00
// Make sure all extruders use same diameter filament and have the same nozzle diameter
// EPSILON comparison is used for nozzles and 10 % tolerance is used for filaments
double first_nozzle_diam = m_config . nozzle_diameter . get_at ( extruders ( ) . front ( ) ) ;
double first_filament_diam = m_config . filament_diameter . get_at ( extruders ( ) . front ( ) ) ;
2019-07-23 11:58:07 +00:00
for ( const auto & extruder_idx : extruders ( ) ) {
2019-08-15 08:35:50 +00:00
double nozzle_diam = m_config . nozzle_diameter . get_at ( extruder_idx ) ;
double filament_diam = m_config . filament_diameter . get_at ( extruder_idx ) ;
if ( nozzle_diam - EPSILON > first_nozzle_diam | | nozzle_diam + EPSILON < first_nozzle_diam
| | std : : abs ( ( filament_diam - first_filament_diam ) / first_filament_diam ) > 0.1 )
return L ( " The wipe tower is only supported if all extruders have the same nozzle diameter "
" and use filaments of the same diameter. " ) ;
2019-07-23 11:58:07 +00:00
}
2018-12-12 18:09:25 +00:00
if ( m_config . gcode_flavor ! = gcfRepRap & & m_config . gcode_flavor ! = gcfRepetier & & m_config . gcode_flavor ! = gcfMarlin )
return L ( " The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter and Repetier G-code flavors. " ) ;
2018-09-11 12:04:47 +00:00
if ( ! m_config . use_relative_e_distances )
2018-06-20 11:57:37 +00:00
return L ( " The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1) . " ) ;
2019-08-28 14:25:17 +00:00
if ( m_config . ooze_prevention )
return L ( " Ooze prevention is currently not supported with the wipe tower enabled. " ) ;
2019-05-07 03:33:09 +00:00
if ( m_config . use_volumetric_e )
return L ( " The Wipe Tower currently does not support volumetric E (use_volumetric_e=0) . " ) ;
2019-05-03 04:17:24 +00:00
2019-01-23 13:00:03 +00:00
if ( m_objects . size ( ) > 1 ) {
bool has_custom_layering = false ;
std : : vector < std : : vector < coordf_t > > layer_height_profiles ;
for ( const PrintObject * object : m_objects ) {
2019-08-06 13:11:46 +00:00
has_custom_layering = ! object - > model_object ( ) - > layer_config_ranges . empty ( ) | | ! object - > model_object ( ) - > layer_height_profile . empty ( ) ;
2019-01-23 13:00:03 +00:00
if ( has_custom_layering ) {
layer_height_profiles . assign ( m_objects . size ( ) , std : : vector < coordf_t > ( ) ) ;
break ;
}
}
2019-03-05 13:05:58 +00:00
const SlicingParameters & slicing_params0 = m_objects . front ( ) - > slicing_parameters ( ) ;
2019-01-23 13:00:03 +00:00
size_t tallest_object_idx = 0 ;
if ( has_custom_layering )
PrintObject : : update_layer_height_profile ( * m_objects . front ( ) - > model_object ( ) , slicing_params0 , layer_height_profiles . front ( ) ) ;
for ( size_t i = 1 ; i < m_objects . size ( ) ; + + i ) {
2019-03-05 13:05:58 +00:00
const PrintObject * object = m_objects [ i ] ;
const SlicingParameters & slicing_params = object - > slicing_parameters ( ) ;
2019-01-23 13:00:03 +00:00
if ( std : : abs ( slicing_params . first_print_layer_height - slicing_params0 . first_print_layer_height ) > EPSILON | |
std : : abs ( slicing_params . layer_height - slicing_params0 . layer_height ) > EPSILON )
2019-05-09 13:44:53 +00:00
return L ( " The Wipe Tower is only supported for multiple objects if they have equal layer heights " ) ;
2019-01-23 13:00:03 +00:00
if ( slicing_params . raft_layers ( ) ! = slicing_params0 . raft_layers ( ) )
return L ( " The Wipe Tower is only supported for multiple objects if they are printed over an equal number of raft layers " ) ;
if ( object - > config ( ) . support_material_contact_distance ! = m_objects . front ( ) - > config ( ) . support_material_contact_distance )
return L ( " The Wipe Tower is only supported for multiple objects if they are printed with the same support_material_contact_distance " ) ;
if ( ! equal_layering ( slicing_params , slicing_params0 ) )
return L ( " The Wipe Tower is only supported for multiple objects if they are sliced equally. " ) ;
if ( has_custom_layering ) {
PrintObject : : update_layer_height_profile ( * object - > model_object ( ) , slicing_params , layer_height_profiles [ i ] ) ;
if ( * ( layer_height_profiles [ i ] . end ( ) - 2 ) > * ( layer_height_profiles [ tallest_object_idx ] . end ( ) - 2 ) )
tallest_object_idx = i ;
}
}
2018-04-16 09:47:35 +00:00
2019-01-23 13:00:03 +00:00
if ( has_custom_layering ) {
const std : : vector < coordf_t > & layer_height_profile_tallest = layer_height_profiles [ tallest_object_idx ] ;
for ( size_t idx_object = 0 ; idx_object < m_objects . size ( ) ; + + idx_object ) {
2019-08-29 14:13:04 +00:00
if ( idx_object = = tallest_object_idx )
continue ;
2019-01-23 13:00:03 +00:00
const std : : vector < coordf_t > & layer_height_profile = layer_height_profiles [ idx_object ] ;
2019-08-29 14:13:04 +00:00
// The comparison of the profiles is not just about element-wise equality, some layers may not be
// explicitely included. Always remember z and height of last reference layer that in the vector
2019-09-20 08:53:50 +00:00
// and compare to that. In case some layers are in the vectors multiple times, only the last entry is
// taken into account and compared.
2019-08-29 14:13:04 +00:00
size_t i = 0 ; // index into tested profile
size_t j = 0 ; // index into reference profile
coordf_t ref_z = - 1. ;
coordf_t next_ref_z = layer_height_profile_tallest [ 0 ] ;
coordf_t ref_height = - 1. ;
while ( i < layer_height_profile . size ( ) ) {
coordf_t this_z = layer_height_profile [ i ] ;
2019-09-20 08:53:50 +00:00
// find the last entry with this z
while ( i + 2 < layer_height_profile . size ( ) & & layer_height_profile [ i + 2 ] = = this_z )
i + = 2 ;
2019-08-29 14:13:04 +00:00
coordf_t this_height = layer_height_profile [ i + 1 ] ;
2019-09-20 08:53:50 +00:00
if ( ref_height < - 1. | | next_ref_z < this_z + EPSILON ) {
2019-08-29 14:13:04 +00:00
ref_z = next_ref_z ;
do { // one layer can be in the vector several times
ref_height = layer_height_profile_tallest [ j + 1 ] ;
if ( j + 2 > = layer_height_profile_tallest . size ( ) )
break ;
j + = 2 ;
next_ref_z = layer_height_profile_tallest [ j ] ;
} while ( ref_z = = next_ref_z ) ;
2018-04-26 09:19:51 +00:00
}
2019-08-29 14:13:04 +00:00
if ( std : : abs ( this_height - ref_height ) > EPSILON )
return L ( " The Wipe tower is only supported if all objects have the same layer height profile " ) ;
i + = 2 ;
}
2018-04-04 07:59:41 +00:00
}
2018-04-03 11:51:12 +00:00
}
2017-06-06 09:40:35 +00:00
}
2017-06-06 08:36:14 +00:00
}
2014-11-09 14:27:34 +00:00
2019-01-11 10:15:32 +00:00
{
std : : vector < unsigned int > extruders = this - > extruders ( ) ;
// Find the smallest used nozzle diameter and the number of unique nozzle diameters.
2019-01-11 10:46:54 +00:00
double min_nozzle_diameter = std : : numeric_limits < double > : : max ( ) ;
2019-01-11 10:15:32 +00:00
double max_nozzle_diameter = 0 ;
for ( unsigned int extruder_id : extruders ) {
double dmr = m_config . nozzle_diameter . get_at ( extruder_id ) ;
min_nozzle_diameter = std : : min ( min_nozzle_diameter , dmr ) ;
max_nozzle_diameter = std : : max ( max_nozzle_diameter , dmr ) ;
}
2018-12-06 15:53:43 +00:00
#if 0
// We currently allow one to assign extruders with a higher index than the number
// of physical extruders the machine is equipped with, as the Printer::apply() clamps them.
2018-09-12 09:59:02 +00:00
unsigned int total_extruders_count = m_config . nozzle_diameter . size ( ) ;
2018-03-21 15:01:31 +00:00
for ( const auto & extruder_idx : extruders )
if ( extruder_idx > = total_extruders_count )
2018-06-20 11:57:37 +00:00
return L ( " One or more object were assigned an extruder that the printer does not have. " ) ;
2018-12-06 15:53:43 +00:00
# endif
2018-03-21 15:01:31 +00:00
2019-08-06 13:11:46 +00:00
auto validate_extrusion_width = [ min_nozzle_diameter , max_nozzle_diameter ] ( const ConfigBase & config , const char * opt_key , double layer_height , std : : string & err_msg ) - > bool {
double extrusion_width_min = config . get_abs_value ( opt_key , min_nozzle_diameter ) ;
double extrusion_width_max = config . get_abs_value ( opt_key , max_nozzle_diameter ) ;
2019-08-06 13:36:16 +00:00
if ( extrusion_width_min = = 0 ) {
// Default "auto-generated" extrusion width is always valid.
} else if ( extrusion_width_min < = layer_height ) {
2019-08-06 13:11:46 +00:00
err_msg = ( boost : : format ( L ( " %1%=%2% mm is too low to be printable at a layer height %3% mm " ) ) % opt_key % extrusion_width_min % layer_height ) . str ( ) ;
return false ;
2019-08-21 07:28:32 +00:00
} else if ( extrusion_width_max > = max_nozzle_diameter * 3. ) {
2019-08-06 13:11:46 +00:00
err_msg = ( boost : : format ( L ( " Excessive %1%=%2% mm to be printable with a nozzle diameter %3% mm " ) ) % opt_key % extrusion_width_max % max_nozzle_diameter ) . str ( ) ;
return false ;
}
return true ;
} ;
2018-09-11 12:04:47 +00:00
for ( PrintObject * object : m_objects ) {
2019-01-10 15:06:24 +00:00
if ( object - > config ( ) . raft_layers > 0 | | object - > config ( ) . support_material . value ) {
2019-01-11 10:15:32 +00:00
if ( ( object - > config ( ) . support_material_extruder = = 0 | | object - > config ( ) . support_material_interface_extruder = = 0 ) & & max_nozzle_diameter - min_nozzle_diameter > EPSILON ) {
2019-01-10 15:06:24 +00:00
// The object has some form of support and either support_material_extruder or support_material_interface_extruder
// will be printed with the current tool without a forced tool change. Play safe, assert that all object nozzles
// are of the same diameter.
2018-06-20 11:57:37 +00:00
return L ( " Printing with multiple extruders of differing nozzle diameters. "
2017-02-11 23:51:53 +00:00
" If support is to be printed with the current extruder (support_material_extruder == 0 or support_material_interface_extruder == 0), "
2018-06-20 11:57:37 +00:00
" all nozzles have to be of the same diameter. " ) ;
2019-01-10 15:06:24 +00:00
}
2019-01-10 17:17:22 +00:00
if ( this - > has_wipe_tower ( ) ) {
if ( object - > config ( ) . support_material_contact_distance = = 0 ) {
// Soluble interface
if ( object - > config ( ) . support_material_contact_distance = = 0 & & ! object - > config ( ) . support_material_synchronize_layers )
return L ( " For the Wipe Tower to work with the soluble supports, the support layers need to be synchronized with the object layers. " ) ;
} else {
// Non-soluble interface
if ( object - > config ( ) . support_material_extruder ! = 0 | | object - > config ( ) . support_material_interface_extruder ! = 0 )
return L ( " The Wipe Tower currently supports the non-soluble supports only if they are printed with the current extruder without triggering a tool change. "
" (both support_material_extruder and support_material_interface_extruder need to be set to 0). " ) ;
}
}
2017-01-30 18:56:46 +00:00
}
2015-03-06 20:35:00 +00:00
// validate first_layer_height
2019-05-15 07:43:57 +00:00
double first_layer_height = object - > config ( ) . get_abs_value ( " first_layer_height " ) ;
2015-03-06 20:35:00 +00:00
double first_layer_min_nozzle_diameter ;
2018-09-11 12:04:47 +00:00
if ( object - > config ( ) . raft_layers > 0 ) {
2015-03-06 20:35:00 +00:00
// if we have raft layers, only support material extruder is used on first layer
2018-09-11 12:04:47 +00:00
size_t first_layer_extruder = object - > config ( ) . raft_layers = = 1
? object - > config ( ) . support_material_interface_extruder - 1
: object - > config ( ) . support_material_extruder - 1 ;
2017-01-30 18:56:46 +00:00
first_layer_min_nozzle_diameter = ( first_layer_extruder = = size_t ( - 1 ) ) ?
min_nozzle_diameter :
2018-09-11 12:04:47 +00:00
m_config . nozzle_diameter . get_at ( first_layer_extruder ) ;
2015-03-06 20:35:00 +00:00
} else {
// if we don't have raft layers, any nozzle diameter is potentially used in first layer
first_layer_min_nozzle_diameter = min_nozzle_diameter ;
}
if ( first_layer_height > first_layer_min_nozzle_diameter )
2018-06-20 11:57:37 +00:00
return L ( " First layer height can't be greater than nozzle diameter " ) ;
2015-03-06 20:35:00 +00:00
// validate layer_height
2019-08-06 13:11:46 +00:00
double layer_height = object - > config ( ) . layer_height . value ;
if ( layer_height > min_nozzle_diameter )
2018-06-20 11:57:37 +00:00
return L ( " Layer height can't be greater than nozzle diameter " ) ;
2019-08-06 13:11:46 +00:00
// Validate extrusion widths.
2019-08-09 07:58:05 +00:00
std : : string err_msg ;
if ( ! validate_extrusion_width ( object - > config ( ) , " extrusion_width " , layer_height , err_msg ) )
return err_msg ;
if ( ( object - > config ( ) . support_material | | object - > config ( ) . raft_layers > 0 ) & & ! validate_extrusion_width ( object - > config ( ) , " support_material_extrusion_width " , layer_height , err_msg ) )
return err_msg ;
for ( const char * opt_key : { " perimeter_extrusion_width " , " external_perimeter_extrusion_width " , " infill_extrusion_width " , " solid_infill_extrusion_width " , " top_infill_extrusion_width " } )
2019-08-06 13:11:46 +00:00
for ( size_t i = 0 ; i < object - > region_volumes . size ( ) ; + + i )
2019-08-09 07:58:05 +00:00
if ( ! object - > region_volumes [ i ] . empty ( ) & & ! validate_extrusion_width ( this - > get_region ( i ) - > config ( ) , opt_key , layer_height , err_msg ) )
return err_msg ;
2014-11-09 14:27:34 +00:00
}
}
2016-11-05 01:23:46 +00:00
return std : : string ( ) ;
2014-11-09 14:27:34 +00:00
}
2014-12-12 18:14:52 +00:00
// the bounding box of objects placed in copies position
// (without taking skirt/brim/support material into account)
2017-05-30 18:09:34 +00:00
BoundingBox Print : : bounding_box ( ) const
2014-12-12 18:14:52 +00:00
{
BoundingBox bb ;
2018-09-11 12:04:47 +00:00
for ( const PrintObject * object : m_objects )
2018-09-12 09:59:02 +00:00
for ( Point copy : object - > m_copies ) {
2017-05-31 10:55:59 +00:00
bb . merge ( copy ) ;
2018-08-21 15:43:05 +00:00
copy + = to_2d ( object - > size ) ;
2017-05-31 10:55:59 +00:00
bb . merge ( copy ) ;
2014-12-12 18:14:52 +00:00
}
return bb ;
}
// the total bounding box of extrusions, including skirt/brim/support material
// this methods needs to be called even when no steps were processed, so it should
// only use configuration values
2017-05-30 18:09:34 +00:00
BoundingBox Print : : total_bounding_box ( ) const
2014-12-12 18:14:52 +00:00
{
// get objects bounding box
BoundingBox bb = this - > bounding_box ( ) ;
2014-12-12 18:25:50 +00:00
// we need to offset the objects bounding box by at least half the perimeters extrusion width
2018-09-11 12:04:47 +00:00
Flow perimeter_flow = m_objects . front ( ) - > get_layer ( 0 ) - > get_region ( 0 ) - > flow ( frPerimeter ) ;
2014-12-12 18:25:50 +00:00
double extra = perimeter_flow . width / 2 ;
// consider support material
if ( this - > has_support_material ( ) ) {
extra = std : : max ( extra , SUPPORT_MATERIAL_MARGIN ) ;
}
// consider brim and skirt
2018-09-11 12:04:47 +00:00
if ( m_config . brim_width . value > 0 ) {
2014-12-16 23:45:05 +00:00
Flow brim_flow = this - > brim_flow ( ) ;
2018-09-11 12:04:47 +00:00
extra = std : : max ( extra , m_config . brim_width . value + brim_flow . width / 2 ) ;
2014-12-12 18:25:50 +00:00
}
2015-03-06 08:56:58 +00:00
if ( this - > has_skirt ( ) ) {
2018-09-11 12:04:47 +00:00
int skirts = m_config . skirts . value ;
2015-03-06 08:56:58 +00:00
if ( skirts = = 0 & & this - > has_infinite_skirt ( ) ) skirts = 1 ;
2014-12-16 23:45:05 +00:00
Flow skirt_flow = this - > skirt_flow ( ) ;
2014-12-12 18:14:52 +00:00
extra = std : : max (
extra ,
2018-09-11 12:04:47 +00:00
m_config . brim_width . value
+ m_config . skirt_distance . value
2015-03-06 08:56:58 +00:00
+ skirts * skirt_flow . spacing ( )
2014-12-12 18:25:50 +00:00
+ skirt_flow . width / 2
2014-12-12 18:14:52 +00:00
) ;
}
if ( extra > 0 )
bb . offset ( scale_ ( extra ) ) ;
return bb ;
}
2017-05-30 18:09:34 +00:00
double Print : : skirt_first_layer_height ( ) const
2014-12-12 18:14:52 +00:00
{
2018-09-18 08:09:58 +00:00
if ( m_objects . empty ( ) )
throw std : : invalid_argument ( " skirt_first_layer_height() can't be called without PrintObjects " ) ;
2018-09-11 12:04:47 +00:00
return m_objects . front ( ) - > config ( ) . get_abs_value ( " first_layer_height " ) ;
2014-12-12 18:14:52 +00:00
}
2017-05-30 18:09:34 +00:00
Flow Print : : brim_flow ( ) const
2014-12-16 23:45:05 +00:00
{
2018-09-11 12:04:47 +00:00
ConfigOptionFloatOrPercent width = m_config . first_layer_extrusion_width ;
2017-11-09 09:48:06 +00:00
if ( width . value = = 0 )
2018-09-11 12:04:47 +00:00
width = m_regions . front ( ) - > config ( ) . perimeter_extrusion_width ;
2017-11-09 09:48:06 +00:00
if ( width . value = = 0 )
2018-09-11 12:04:47 +00:00
width = m_objects . front ( ) - > config ( ) . extrusion_width ;
2014-12-16 23:45:05 +00:00
/* We currently use a random region's perimeter extruder.
While this works for most cases , we should probably consider all of the perimeter
extruders and take the one with , say , the smallest index .
The same logic should be applied to the code that selects the extruder during G - code
generation as well . */
return Flow : : new_from_config_width (
frPerimeter ,
2019-06-20 14:15:09 +00:00
width ,
( float ) m_config . nozzle_diameter . get_at ( m_regions . front ( ) - > config ( ) . perimeter_extruder - 1 ) ,
( float ) this - > skirt_first_layer_height ( ) ,
2014-12-16 23:45:05 +00:00
0
) ;
}
2017-05-30 18:09:34 +00:00
Flow Print : : skirt_flow ( ) const
2014-12-12 18:14:52 +00:00
{
2018-09-11 12:04:47 +00:00
ConfigOptionFloatOrPercent width = m_config . first_layer_extrusion_width ;
2017-11-09 09:48:06 +00:00
if ( width . value = = 0 )
2018-09-11 12:04:47 +00:00
width = m_regions . front ( ) - > config ( ) . perimeter_extrusion_width ;
2017-11-09 09:48:06 +00:00
if ( width . value = = 0 )
2018-09-11 12:04:47 +00:00
width = m_objects . front ( ) - > config ( ) . extrusion_width ;
2014-12-12 18:14:52 +00:00
2014-12-16 23:45:05 +00:00
/* We currently use a random object's support material extruder.
While this works for most cases , we should probably consider all of the support material
extruders and take the one with , say , the smallest index ;
The same logic should be applied to the code that selects the extruder during G - code
generation as well . */
2014-12-12 18:14:52 +00:00
return Flow : : new_from_config_width (
frPerimeter ,
2019-06-20 14:15:09 +00:00
width ,
( float ) m_config . nozzle_diameter . get_at ( m_objects . front ( ) - > config ( ) . support_material_extruder - 1 ) ,
( float ) this - > skirt_first_layer_height ( ) ,
2014-12-12 18:14:52 +00:00
0
) ;
}
2017-05-30 18:09:34 +00:00
bool Print : : has_support_material ( ) const
2014-08-03 16:41:09 +00:00
{
2018-09-11 12:04:47 +00:00
for ( const PrintObject * object : m_objects )
2017-05-30 18:09:34 +00:00
if ( object - > has_support_material ( ) )
return true ;
2014-08-03 16:41:09 +00:00
return false ;
}
2015-12-02 17:29:33 +00:00
/* This method assigns extruders to the volumes having a material
but not having extruders set in the volume config . */
2017-05-30 18:09:34 +00:00
void Print : : auto_assign_extruders ( ModelObject * model_object ) const
2015-12-02 17:29:33 +00:00
{
// only assign extruders if object has more than one volume
2017-05-30 18:09:34 +00:00
if ( model_object - > volumes . size ( ) < 2 )
return ;
2015-12-02 17:29:33 +00:00
2018-09-11 12:04:47 +00:00
// size_t extruders = m_config.nozzle_diameter.values.size();
2017-05-30 18:09:34 +00:00
for ( size_t volume_id = 0 ; volume_id < model_object - > volumes . size ( ) ; + + volume_id ) {
ModelVolume * volume = model_object - > volumes [ volume_id ] ;
//FIXME Vojtech: This assigns an extruder ID even to a modifier volume, if it has a material assigned.
2018-09-17 13:12:13 +00:00
if ( ( volume - > is_model_part ( ) | | volume - > is_modifier ( ) ) & & ! volume - > material_id ( ) . empty ( ) & & ! volume - > config . has ( " extruder " ) )
2017-05-30 18:09:34 +00:00
volume - > config . opt < ConfigOptionInt > ( " extruder " , true ) - > value = int ( volume_id + 1 ) ;
2015-12-02 17:29:33 +00:00
}
}
2018-03-23 10:41:20 +00:00
// Slicing process, running at a background thread.
void Print : : process ( )
{
2018-12-18 10:31:41 +00:00
BOOST_LOG_TRIVIAL ( info ) < < " Staring the slicing process. " < < log_memory_info ( ) ;
2018-09-11 12:04:47 +00:00
for ( PrintObject * obj : m_objects )
2018-03-23 10:41:20 +00:00
obj - > make_perimeters ( ) ;
2019-05-04 00:07:07 +00:00
this - > set_status ( 70 , L ( " Infilling layers " ) ) ;
2018-09-11 12:04:47 +00:00
for ( PrintObject * obj : m_objects )
2018-03-23 10:41:20 +00:00
obj - > infill ( ) ;
2018-09-11 12:04:47 +00:00
for ( PrintObject * obj : m_objects )
2018-03-23 10:41:20 +00:00
obj - > generate_support_material ( ) ;
2019-05-07 03:33:09 +00:00
if ( this - > set_started ( psWipeTower ) ) {
m_wipe_tower_data . clear ( ) ;
if ( this - > has_wipe_tower ( ) ) {
//this->set_status(95, L("Generating wipe tower"));
this - > _make_wipe_tower ( ) ;
}
this - > set_done ( psWipeTower ) ;
}
2018-11-16 17:28:50 +00:00
if ( this - > set_started ( psSkirt ) ) {
2018-09-11 12:04:47 +00:00
m_skirt . clear ( ) ;
2018-03-23 10:41:20 +00:00
if ( this - > has_skirt ( ) ) {
2019-05-04 00:07:07 +00:00
this - > set_status ( 88 , L ( " Generating skirt " ) ) ;
2018-03-23 10:41:20 +00:00
this - > _make_skirt ( ) ;
}
2018-09-11 12:04:47 +00:00
this - > set_done ( psSkirt ) ;
2018-03-23 10:41:20 +00:00
}
2018-11-16 17:28:50 +00:00
if ( this - > set_started ( psBrim ) ) {
2018-09-11 12:04:47 +00:00
m_brim . clear ( ) ;
if ( m_config . brim_width > 0 ) {
2019-05-04 00:07:07 +00:00
this - > set_status ( 88 , L ( " Generating brim " ) ) ;
2018-03-23 10:41:20 +00:00
this - > _make_brim ( ) ;
}
2018-09-11 12:04:47 +00:00
this - > set_done ( psBrim ) ;
2018-03-23 10:41:20 +00:00
}
2018-12-18 10:31:41 +00:00
BOOST_LOG_TRIVIAL ( info ) < < " Slicing process finished. " < < log_memory_info ( ) ;
2018-03-23 10:41:20 +00:00
}
// G-code export process, running at a background thread.
// The export_gcode may die for various reasons (fails to process output_filename_format,
// write error into the G-code, cannot execute post-processing scripts).
// It is up to the caller to show an error message.
2019-10-23 11:31:24 +00:00
# if ENABLE_THUMBNAIL_GENERATOR
2019-11-22 11:39:03 +00:00
std : : string Print : : export_gcode ( const std : : string & path_template , GCodePreviewData * preview_data , ThumbnailsGeneratorCallback thumbnail_cb )
2019-10-23 11:31:24 +00:00
# else
2019-03-13 14:44:50 +00:00
std : : string Print : : export_gcode ( const std : : string & path_template , GCodePreviewData * preview_data )
2019-10-23 11:31:24 +00:00
# endif // ENABLE_THUMBNAIL_GENERATOR
2018-03-23 10:41:20 +00:00
{
// output everything to a G-code file
// The following call may die if the output_filename_format template substitution fails.
std : : string path = this - > output_filepath ( path_template ) ;
2019-05-08 15:43:43 +00:00
std : : string message ;
2019-02-22 09:11:57 +00:00
if ( ! path . empty ( ) & & preview_data = = nullptr ) {
// Only show the path if preview_data is not set -> running from command line.
2019-05-08 15:43:43 +00:00
message = L ( " Exporting G-code " ) ;
2018-03-23 10:41:20 +00:00
message + = " to " ;
message + = path ;
2019-05-08 15:43:43 +00:00
} else
message = L ( " Generating G-code " ) ;
2018-03-23 10:41:20 +00:00
this - > set_status ( 90 , message ) ;
// The following line may die for multiple reasons.
GCode gcode ;
2019-10-23 11:31:24 +00:00
# if ENABLE_THUMBNAIL_GENERATOR
2019-11-22 11:39:03 +00:00
gcode . do_export ( this , path . c_str ( ) , preview_data , thumbnail_cb ) ;
2019-10-23 11:31:24 +00:00
# else
2018-03-23 10:41:20 +00:00
gcode . do_export ( this , path . c_str ( ) , preview_data ) ;
2019-10-23 11:31:24 +00:00
# endif // ENABLE_THUMBNAIL_GENERATOR
2019-03-13 14:44:50 +00:00
return path . c_str ( ) ;
2018-03-23 10:41:20 +00:00
}
2017-02-15 10:05:52 +00:00
void Print : : _make_skirt ( )
{
// First off we need to decide how tall the skirt must be.
// The skirt_height option from config is expressed in layers, but our
// object might have different layer heights, so we need to find the print_z
// of the highest layer involved.
// Note that unless has_infinite_skirt() == true
// the actual skirt might not reach this $skirt_height_z value since the print
// order of objects on each layer is not guaranteed and will not generally
// include the thickest object first. It is just guaranteed that a skirt is
// prepended to the first 'n' layers (with 'n' = skirt_height).
// $skirt_height_z in this case is the highest possible skirt height for safety.
coordf_t skirt_height_z = 0. ;
2018-11-08 13:23:17 +00:00
for ( const PrintObject * object : m_objects ) {
2018-07-18 07:37:25 +00:00
size_t skirt_layers = this - > has_infinite_skirt ( ) ?
2017-02-15 10:05:52 +00:00
object - > layer_count ( ) :
2018-09-11 12:04:47 +00:00
std : : min ( size_t ( m_config . skirt_height . value ) , object - > layer_count ( ) ) ;
skirt_height_z = std : : max ( skirt_height_z , object - > m_layers [ skirt_layers - 1 ] - > print_z ) ;
2017-02-15 10:05:52 +00:00
}
// Collect points from all layers contained in skirt height.
Points points ;
2018-11-08 13:23:17 +00:00
for ( const PrintObject * object : m_objects ) {
2017-02-15 10:05:52 +00:00
Points object_points ;
// Get object layers up to skirt_height_z.
2018-09-11 12:04:47 +00:00
for ( const Layer * layer : object - > m_layers ) {
2017-02-15 10:05:52 +00:00
if ( layer - > print_z > skirt_height_z )
break ;
2019-10-01 15:17:08 +00:00
for ( const ExPolygon & expoly : layer - > slices )
2017-02-15 10:05:52 +00:00
// Collect the outer contour points only, ignore holes for the calculation of the convex hull.
append ( object_points , expoly . contour . points ) ;
}
// Get support layers up to skirt_height_z.
2018-09-11 12:04:47 +00:00
for ( const SupportLayer * layer : object - > support_layers ( ) ) {
2017-02-15 10:05:52 +00:00
if ( layer - > print_z > skirt_height_z )
break ;
for ( const ExtrusionEntity * extrusion_entity : layer - > support_fills . entities )
append ( object_points , extrusion_entity - > as_polyline ( ) . points ) ;
}
// Repeat points for each object copy.
2018-09-12 09:59:02 +00:00
for ( const Point & shift : object - > m_copies ) {
2017-02-15 10:05:52 +00:00
Points copy_points = object_points ;
for ( Point & pt : copy_points )
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
pt + = shift ;
2017-02-15 10:05:52 +00:00
append ( points , copy_points ) ;
}
}
2019-05-07 03:33:09 +00:00
// Include the wipe tower.
2019-10-08 12:04:50 +00:00
if ( has_wipe_tower ( ) & & ! m_wipe_tower_data . tool_changes . empty ( ) ) {
double width = m_config . wipe_tower_width + 2 * m_wipe_tower_data . brim_width ;
double depth = m_wipe_tower_data . depth + 2 * m_wipe_tower_data . brim_width ;
Vec2d pt = Vec2d ( m_config . wipe_tower_x - m_wipe_tower_data . brim_width , m_config . wipe_tower_y - m_wipe_tower_data . brim_width ) ;
points . push_back ( Point ( scale_ ( pt . x ( ) ) , scale_ ( pt . y ( ) ) ) ) ;
points . push_back ( Point ( scale_ ( pt . x ( ) + width ) , scale_ ( pt . y ( ) ) ) ) ;
points . push_back ( Point ( scale_ ( pt . x ( ) + width ) , scale_ ( pt . y ( ) + depth ) ) ) ;
points . push_back ( Point ( scale_ ( pt . x ( ) ) , scale_ ( pt . y ( ) + depth ) ) ) ;
}
2019-05-07 03:33:09 +00:00
2017-02-15 10:05:52 +00:00
if ( points . size ( ) < 3 )
// At least three points required for a convex hull.
return ;
2018-03-28 15:05:31 +00:00
this - > throw_if_canceled ( ) ;
2017-02-15 10:05:52 +00:00
Polygon convex_hull = Slic3r : : Geometry : : convex_hull ( points ) ;
// Skirt may be printed on several layers, having distinct layer heights,
// but loops must be aligned so can't vary width/spacing
// TODO: use each extruder's own flow
double first_layer_height = this - > skirt_first_layer_height ( ) ;
Flow flow = this - > skirt_flow ( ) ;
float spacing = flow . spacing ( ) ;
double mm3_per_mm = flow . mm3_per_mm ( ) ;
std : : vector < size_t > extruders ;
std : : vector < double > extruders_e_per_mm ;
{
auto set_extruders = this - > extruders ( ) ;
extruders . reserve ( set_extruders . size ( ) ) ;
extruders_e_per_mm . reserve ( set_extruders . size ( ) ) ;
for ( auto & extruder_id : set_extruders ) {
extruders . push_back ( extruder_id ) ;
2018-09-11 12:04:47 +00:00
extruders_e_per_mm . push_back ( Extruder ( ( unsigned int ) extruder_id , & m_config ) . e_per_mm ( mm3_per_mm ) ) ;
2017-02-15 10:05:52 +00:00
}
}
// Number of skirt loops per skirt layer.
2019-06-25 11:06:04 +00:00
size_t n_skirts = m_config . skirts . value ;
2017-02-15 10:05:52 +00:00
if ( this - > has_infinite_skirt ( ) & & n_skirts = = 0 )
n_skirts = 1 ;
// Initial offset of the brim inner edge from the object (possible with a support & raft).
// The skirt will touch the brim if the brim is extruded.
2019-08-14 13:44:32 +00:00
auto distance = float ( scale_ ( m_config . skirt_distance . value ) - spacing / 2. ) ;
2017-02-15 10:05:52 +00:00
// Draw outlines from outside to inside.
// Loop while we have less skirts than required or any extruder hasn't reached the min length if any.
std : : vector < coordf_t > extruded_length ( extruders . size ( ) , 0. ) ;
2019-06-25 11:06:04 +00:00
for ( size_t i = n_skirts , extruder_idx = 0 ; i > 0 ; - - i ) {
2018-03-28 15:05:31 +00:00
this - > throw_if_canceled ( ) ;
2017-02-15 10:05:52 +00:00
// Offset the skirt outside.
2019-06-20 14:15:09 +00:00
distance + = float ( scale_ ( spacing ) ) ;
2017-02-15 10:05:52 +00:00
// Generate the skirt centerline.
2017-03-23 11:35:00 +00:00
Polygon loop ;
{
2019-06-20 14:15:09 +00:00
Polygons loops = offset ( convex_hull , distance , ClipperLib : : jtRound , float ( scale_ ( 0.1 ) ) ) ;
2017-03-23 11:35:00 +00:00
Geometry : : simplify_polygons ( loops , scale_ ( 0.05 ) , & loops ) ;
2018-12-22 09:02:42 +00:00
if ( loops . empty ( ) )
break ;
loop = loops . front ( ) ;
2017-03-23 11:35:00 +00:00
}
2017-02-15 10:05:52 +00:00
// Extrude the skirt loop.
ExtrusionLoop eloop ( elrSkirt ) ;
eloop . paths . emplace_back ( ExtrusionPath (
ExtrusionPath (
erSkirt ,
2019-06-20 14:15:09 +00:00
( float ) mm3_per_mm , // this will be overridden at G-code export time
2017-02-15 10:05:52 +00:00
flow . width ,
2019-06-20 14:15:09 +00:00
( float ) first_layer_height // this will be overridden at G-code export time
2017-02-15 10:05:52 +00:00
) ) ) ;
eloop . paths . back ( ) . polyline = loop . split_at_first_point ( ) ;
2018-09-11 12:04:47 +00:00
m_skirt . append ( eloop ) ;
if ( m_config . min_skirt_length . value > 0 ) {
2017-02-15 10:05:52 +00:00
// The skirt length is limited. Sum the total amount of filament length extruded, in mm.
2018-08-21 15:43:05 +00:00
extruded_length [ extruder_idx ] + = unscale < double > ( loop . length ( ) ) * extruders_e_per_mm [ extruder_idx ] ;
2018-09-11 12:04:47 +00:00
if ( extruded_length [ extruder_idx ] < m_config . min_skirt_length . value ) {
2017-02-15 10:05:52 +00:00
// Not extruded enough yet with the current extruder. Add another loop.
if ( i = = 1 )
+ + i ;
} else {
2018-09-11 12:04:47 +00:00
assert ( extruded_length [ extruder_idx ] > = m_config . min_skirt_length . value ) ;
2017-02-15 10:05:52 +00:00
// Enough extruded with the current extruder. Extrude with the next one,
// until the prescribed number of skirt loops is extruded.
if ( extruder_idx + 1 < extruders . size ( ) )
+ + extruder_idx ;
}
} else {
// The skirt lenght is not limited, extrude the skirt with the 1st extruder only.
}
}
// Brims were generated inside out, reverse to print the outmost contour first.
2018-09-11 12:04:47 +00:00
m_skirt . reverse ( ) ;
2017-02-15 10:05:52 +00:00
}
2017-07-07 14:40:23 +00:00
void Print : : _make_brim ( )
{
// Brim is only printed on first layer and uses perimeter extruder.
Flow flow = this - > brim_flow ( ) ;
Polygons islands ;
2018-11-08 13:23:17 +00:00
for ( PrintObject * object : m_objects ) {
2017-07-07 14:40:23 +00:00
Polygons object_islands ;
2019-10-01 15:17:08 +00:00
for ( ExPolygon & expoly : object - > m_layers . front ( ) - > slices )
2017-07-07 14:40:23 +00:00
object_islands . push_back ( expoly . contour ) ;
2018-09-11 12:04:47 +00:00
if ( ! object - > support_layers ( ) . empty ( ) )
object - > support_layers ( ) . front ( ) - > support_fills . polygons_covered_by_spacing ( object_islands , float ( SCALED_EPSILON ) ) ;
2018-09-12 09:59:02 +00:00
islands . reserve ( islands . size ( ) + object_islands . size ( ) * object - > m_copies . size ( ) ) ;
for ( const Point & pt : object - > m_copies )
2017-07-07 14:40:23 +00:00
for ( Polygon & poly : object_islands ) {
islands . push_back ( poly ) ;
islands . back ( ) . translate ( pt ) ;
}
}
Polygons loops ;
2018-09-12 09:59:02 +00:00
size_t num_loops = size_t ( floor ( m_config . brim_width . value / flow . spacing ( ) ) ) ;
2017-07-07 14:40:23 +00:00
for ( size_t i = 0 ; i < num_loops ; + + i ) {
2018-03-28 15:05:31 +00:00
this - > throw_if_canceled ( ) ;
2017-07-07 14:40:23 +00:00
islands = offset ( islands , float ( flow . scaled_spacing ( ) ) , jtSquare ) ;
for ( Polygon & poly : islands ) {
// poly.simplify(SCALED_RESOLUTION);
poly . points . push_back ( poly . points . front ( ) ) ;
Points p = MultiPoint : : _douglas_peucker ( poly . points , SCALED_RESOLUTION ) ;
p . pop_back ( ) ;
poly . points = std : : move ( p ) ;
}
polygons_append ( loops , offset ( islands , - 0.5f * float ( flow . scaled_spacing ( ) ) ) ) ;
}
loops = union_pt_chained ( loops , false ) ;
2019-03-13 14:44:50 +00:00
// The function above produces ordering well suited for concentric infill (from outside to inside).
// For Brim, the ordering should be reversed (from inside to outside).
2017-07-07 14:40:23 +00:00
std : : reverse ( loops . begin ( ) , loops . end ( ) ) ;
2019-08-14 13:44:32 +00:00
// If there is a possibility that brim intersects skirt, go through loops and split those extrusions
// The result is either the original Polygon or a list of Polylines
if ( ! m_skirt . empty ( ) & & m_config . skirt_distance . value < m_config . brim_width )
{
// Find the bounding polygons of the skirt
const Polygons skirt_inners = offset ( dynamic_cast < ExtrusionLoop * > ( m_skirt . entities . back ( ) ) - > polygon ( ) ,
- float ( scale_ ( this - > skirt_flow ( ) . spacing ( ) ) ) / 2.f ,
ClipperLib : : jtRound ,
float ( scale_ ( 0.1 ) ) ) ;
const Polygons skirt_outers = offset ( dynamic_cast < ExtrusionLoop * > ( m_skirt . entities . front ( ) ) - > polygon ( ) ,
float ( scale_ ( this - > skirt_flow ( ) . spacing ( ) ) ) / 2.f ,
ClipperLib : : jtRound ,
float ( scale_ ( 0.1 ) ) ) ;
2019-09-13 14:16:37 +00:00
// First calculate the trimming region.
ClipperLib_Z : : Paths trimming ;
{
ClipperLib_Z : : Paths input_subject ;
ClipperLib_Z : : Paths input_clip ;
for ( const Polygon & poly : skirt_outers ) {
input_subject . emplace_back ( ) ;
ClipperLib_Z : : Path & out = input_subject . back ( ) ;
out . reserve ( poly . points . size ( ) ) ;
for ( const Point & pt : poly . points )
out . emplace_back ( pt . x ( ) , pt . y ( ) , 0 ) ;
}
for ( const Polygon & poly : skirt_inners ) {
input_clip . emplace_back ( ) ;
ClipperLib_Z : : Path & out = input_clip . back ( ) ;
out . reserve ( poly . points . size ( ) ) ;
for ( const Point & pt : poly . points )
out . emplace_back ( pt . x ( ) , pt . y ( ) , 0 ) ;
}
// init Clipper
ClipperLib_Z : : Clipper clipper ;
// add polygons
clipper . AddPaths ( input_subject , ClipperLib_Z : : ptSubject , true ) ;
clipper . AddPaths ( input_clip , ClipperLib_Z : : ptClip , true ) ;
// perform operation
clipper . Execute ( ClipperLib_Z : : ctDifference , trimming , ClipperLib_Z : : pftEvenOdd , ClipperLib_Z : : pftEvenOdd ) ;
}
2019-08-14 13:44:32 +00:00
2019-09-13 14:16:37 +00:00
// Second, trim the extrusion loops with the trimming regions.
ClipperLib_Z : : Paths loops_trimmed ;
{
// Produce a closed polyline (repeat the first point at the end).
ClipperLib_Z : : Paths input_clip ;
for ( const Polygon & loop : loops ) {
input_clip . emplace_back ( ) ;
ClipperLib_Z : : Path & out = input_clip . back ( ) ;
out . reserve ( loop . points . size ( ) ) ;
int64_t loop_idx = & loop - & loops . front ( ) ;
for ( const Point & pt : loop . points )
// The Z coordinate carries index of the source loop.
out . emplace_back ( pt . x ( ) , pt . y ( ) , loop_idx + 1 ) ;
out . emplace_back ( out . front ( ) ) ;
}
// init Clipper
ClipperLib_Z : : Clipper clipper ;
clipper . ZFillFunction ( [ ] ( const ClipperLib_Z : : IntPoint & e1bot , const ClipperLib_Z : : IntPoint & e1top , const ClipperLib_Z : : IntPoint & e2bot , const ClipperLib_Z : : IntPoint & e2top , ClipperLib_Z : : IntPoint & pt ) {
// Assign a valid input loop identifier. Such an identifier is strictly positive, the next line is safe even in case one side of a segment
// hat the Z coordinate not set to the contour coordinate.
pt . Z = std : : max ( std : : max ( e1bot . Z , e1top . Z ) , std : : max ( e2bot . Z , e2top . Z ) ) ;
} ) ;
// add polygons
clipper . AddPaths ( input_clip , ClipperLib_Z : : ptSubject , false ) ;
clipper . AddPaths ( trimming , ClipperLib_Z : : ptClip , true ) ;
// perform operation
ClipperLib_Z : : PolyTree loops_trimmed_tree ;
clipper . Execute ( ClipperLib_Z : : ctDifference , loops_trimmed_tree , ClipperLib_Z : : pftEvenOdd , ClipperLib_Z : : pftEvenOdd ) ;
ClipperLib_Z : : PolyTreeToPaths ( loops_trimmed_tree , loops_trimmed ) ;
}
// Third, produce the extrusions, sorted by the source loop indices.
{
std : : vector < std : : pair < const ClipperLib_Z : : Path * , size_t > > loops_trimmed_order ;
loops_trimmed_order . reserve ( loops_trimmed . size ( ) ) ;
for ( const ClipperLib_Z : : Path & path : loops_trimmed ) {
size_t input_idx = 0 ;
for ( const ClipperLib_Z : : IntPoint & pt : path )
if ( pt . Z > 0 ) {
input_idx = ( size_t ) pt . Z ;
break ;
}
assert ( input_idx ! = 0 ) ;
loops_trimmed_order . emplace_back ( & path , input_idx ) ;
}
std : : stable_sort ( loops_trimmed_order . begin ( ) , loops_trimmed_order . end ( ) ,
[ ] ( const std : : pair < const ClipperLib_Z : : Path * , size_t > & l , const std : : pair < const ClipperLib_Z : : Path * , size_t > & r ) {
return l . second < r . second ;
} ) ;
2019-09-26 14:39:50 +00:00
Point last_pt ( 0 , 0 ) ;
2019-09-13 14:16:37 +00:00
for ( size_t i = 0 ; i < loops_trimmed_order . size ( ) ; ) {
// Find all pieces that the initial loop was split into.
size_t j = i + 1 ;
for ( ; j < loops_trimmed_order . size ( ) & & loops_trimmed_order [ i ] . first = = loops_trimmed_order [ j ] . first ; + + j ) ;
const ClipperLib_Z : : Path & first_path = * loops_trimmed_order [ i ] . first ;
if ( i + 1 = = j & & first_path . size ( ) > 3 & & first_path . front ( ) . X = = first_path . back ( ) . X & & first_path . front ( ) . Y = = first_path . back ( ) . Y ) {
auto * loop = new ExtrusionLoop ( ) ;
m_brim . entities . emplace_back ( loop ) ;
loop - > paths . emplace_back ( erSkirt , float ( flow . mm3_per_mm ( ) ) , float ( flow . width ) , float ( this - > skirt_first_layer_height ( ) ) ) ;
Points & points = loop - > paths . front ( ) . polyline . points ;
points . reserve ( first_path . size ( ) ) ;
for ( const ClipperLib_Z : : IntPoint & pt : first_path )
points . emplace_back ( coord_t ( pt . X ) , coord_t ( pt . Y ) ) ;
i = j ;
} else {
2019-09-26 14:39:50 +00:00
//FIXME The path chaining here may not be optimal.
ExtrusionEntityCollection this_loop_trimmed ;
this_loop_trimmed . entities . reserve ( j - i ) ;
2019-09-13 14:16:37 +00:00
for ( ; i < j ; + + i ) {
2019-09-26 14:39:50 +00:00
this_loop_trimmed . entities . emplace_back ( new ExtrusionPath ( erSkirt , float ( flow . mm3_per_mm ( ) ) , float ( flow . width ) , float ( this - > skirt_first_layer_height ( ) ) ) ) ;
2019-09-13 14:16:37 +00:00
const ClipperLib_Z : : Path & path = * loops_trimmed_order [ i ] . first ;
2019-09-26 14:39:50 +00:00
Points & points = static_cast < ExtrusionPath * > ( this_loop_trimmed . entities . back ( ) ) - > polyline . points ;
2019-09-13 14:16:37 +00:00
points . reserve ( path . size ( ) ) ;
for ( const ClipperLib_Z : : IntPoint & pt : path )
points . emplace_back ( coord_t ( pt . X ) , coord_t ( pt . Y ) ) ;
}
2019-09-26 14:39:50 +00:00
chain_and_reorder_extrusion_entities ( this_loop_trimmed . entities , & last_pt ) ;
m_brim . entities . reserve ( m_brim . entities . size ( ) + this_loop_trimmed . entities . size ( ) ) ;
append ( m_brim . entities , std : : move ( this_loop_trimmed . entities ) ) ;
this_loop_trimmed . entities . clear ( ) ;
2019-09-13 14:16:37 +00:00
}
2019-09-26 14:39:50 +00:00
last_pt = m_brim . last_point ( ) ;
2019-09-13 14:16:37 +00:00
}
}
} else {
extrusion_entities_append_loops ( m_brim . entities , std : : move ( loops ) , erSkirt , float ( flow . mm3_per_mm ( ) ) , float ( flow . width ) , float ( this - > skirt_first_layer_height ( ) ) ) ;
}
2017-07-07 14:40:23 +00:00
}
2017-05-25 20:27:53 +00:00
// Wipe tower support.
2017-07-17 07:07:18 +00:00
bool Print : : has_wipe_tower ( ) const
2017-05-25 20:27:53 +00:00
{
return
2018-09-11 12:04:47 +00:00
! m_config . spiral_vase . value & &
m_config . wipe_tower . value & &
m_config . nozzle_diameter . values . size ( ) > 1 ;
2017-05-25 20:27:53 +00:00
}
2019-10-08 11:50:51 +00:00
const WipeTowerData & Print : : wipe_tower_data ( size_t extruders_cnt , double first_layer_height , double nozzle_diameter ) const
{
// If the wipe tower wasn't created yet, make sure the depth and brim_width members are set to default.
if ( ! is_step_done ( psWipeTower ) & & extruders_cnt ! = 0 ) {
float width = m_config . wipe_tower_width ;
float brim_spacing = nozzle_diameter * 1.25f - first_layer_height * ( 1. - M_PI_4 ) ;
const_cast < Print * > ( this ) - > m_wipe_tower_data . depth = ( 900.f / width ) * float ( extruders_cnt - 1 ) ;
const_cast < Print * > ( this ) - > m_wipe_tower_data . brim_width = 4.5f * brim_spacing ;
}
return m_wipe_tower_data ;
}
2017-05-25 20:27:53 +00:00
void Print : : _make_wipe_tower ( )
{
2018-09-12 09:59:02 +00:00
m_wipe_tower_data . clear ( ) ;
2017-05-25 20:27:53 +00:00
if ( ! this - > has_wipe_tower ( ) )
return ;
2018-05-24 15:24:37 +00:00
// Get wiping matrix to get number of extruders and convert vector<double> to vector<float>:
2018-09-12 09:59:02 +00:00
std : : vector < float > wiping_matrix ( cast < float > ( m_config . wiping_volumes_matrix . values ) ) ;
2018-05-24 15:24:37 +00:00
// Extract purging volumes for each extruder pair:
std : : vector < std : : vector < float > > wipe_volumes ;
const unsigned int number_of_extruders = ( unsigned int ) ( sqrt ( wiping_matrix . size ( ) ) + EPSILON ) ;
for ( unsigned int i = 0 ; i < number_of_extruders ; + + i )
wipe_volumes . push_back ( std : : vector < float > ( wiping_matrix . begin ( ) + i * number_of_extruders , wiping_matrix . begin ( ) + ( i + 1 ) * number_of_extruders ) ) ;
2018-05-24 12:05:51 +00:00
2017-09-01 15:30:18 +00:00
// Let the ToolOrdering class know there will be initial priming extrusions at the start of the print.
2018-09-11 12:04:47 +00:00
m_wipe_tower_data . tool_ordering = ToolOrdering ( * this , ( unsigned int ) - 1 , true ) ;
if ( ! m_wipe_tower_data . tool_ordering . has_wipe_tower ( ) )
2017-05-30 07:25:34 +00:00
// Don't generate any wipe tower.
2017-05-25 20:27:53 +00:00
return ;
2017-12-11 16:19:55 +00:00
// Check whether there are any layers in m_tool_ordering, which are marked with has_wipe_tower,
// they print neither object, nor support. These layers are above the raft and below the object, and they
// shall be added to the support layers to be printed.
2019-05-14 17:46:01 +00:00
// see https://github.com/prusa3d/PrusaSlicer/issues/607
2017-12-11 16:19:55 +00:00
{
size_t idx_begin = size_t ( - 1 ) ;
2018-09-11 12:04:47 +00:00
size_t idx_end = m_wipe_tower_data . tool_ordering . layer_tools ( ) . size ( ) ;
2017-12-11 16:19:55 +00:00
// Find the first wipe tower layer, which does not have a counterpart in an object or a support layer.
for ( size_t i = 0 ; i < idx_end ; + + i ) {
2018-09-12 09:59:02 +00:00
const LayerTools & lt = m_wipe_tower_data . tool_ordering . layer_tools ( ) [ i ] ;
2017-12-11 16:19:55 +00:00
if ( lt . has_wipe_tower & & ! lt . has_object & & ! lt . has_support ) {
idx_begin = i ;
break ;
}
}
if ( idx_begin ! = size_t ( - 1 ) ) {
2018-09-11 12:04:47 +00:00
// Find the position in m_objects.first()->support_layers to insert these new support layers.
double wipe_tower_new_layer_print_z_first = m_wipe_tower_data . tool_ordering . layer_tools ( ) [ idx_begin ] . print_z ;
SupportLayerPtrs : : const_iterator it_layer = m_objects . front ( ) - > support_layers ( ) . begin ( ) ;
SupportLayerPtrs : : const_iterator it_end = m_objects . front ( ) - > support_layers ( ) . end ( ) ;
2018-02-13 10:18:58 +00:00
for ( ; it_layer ! = it_end & & ( * it_layer ) - > print_z - EPSILON < wipe_tower_new_layer_print_z_first ; + + it_layer ) ;
2017-12-11 16:19:55 +00:00
// Find the stopper of the sequence of wipe tower layers, which do not have a counterpart in an object or a support layer.
for ( size_t i = idx_begin ; i < idx_end ; + + i ) {
2018-09-12 09:59:02 +00:00
LayerTools & lt = const_cast < LayerTools & > ( m_wipe_tower_data . tool_ordering . layer_tools ( ) [ i ] ) ;
2017-12-11 16:19:55 +00:00
if ( ! ( lt . has_wipe_tower & & ! lt . has_object & & ! lt . has_support ) )
break ;
lt . has_support = true ;
// Insert the new support layer.
2019-08-02 17:45:13 +00:00
double height = lt . print_z - ( i = = 0 ? 0. : m_wipe_tower_data . tool_ordering . layer_tools ( ) [ i - 1 ] . print_z ) ;
2018-02-13 10:18:58 +00:00
//FIXME the support layer ID is set to -1, as Vojtech hopes it is not being used anyway.
2019-06-20 14:15:09 +00:00
it_layer = m_objects . front ( ) - > insert_support_layer ( it_layer , - 1 , height , lt . print_z , lt . print_z - 0.5 * height ) ;
2017-12-11 16:19:55 +00:00
+ + it_layer ;
}
}
}
2018-03-28 15:05:31 +00:00
this - > throw_if_canceled ( ) ;
2017-12-11 16:19:55 +00:00
2017-05-25 20:27:53 +00:00
// Initialize the wipe tower.
2019-08-14 23:20:38 +00:00
WipeTower wipe_tower ( m_config , wipe_volumes , m_wipe_tower_data . tool_ordering . first_extruder ( ) ) ;
2018-03-20 14:45:11 +00:00
2017-05-25 20:27:53 +00:00
//wipe_tower.set_retract();
//wipe_tower.set_zhop();
// Set the extruder & material properties at the wipe tower object.
2018-05-24 15:24:37 +00:00
for ( size_t i = 0 ; i < number_of_extruders ; + + i )
2019-06-12 08:54:52 +00:00
2017-05-25 20:27:53 +00:00
wipe_tower . set_extruder (
2019-08-14 23:20:38 +00:00
i , m_config ) ;
2017-09-12 13:55:38 +00:00
2019-06-14 10:28:24 +00:00
m_wipe_tower_data . priming = Slic3r : : make_unique < std : : vector < WipeTower : : ToolChangeResult > > (
2019-06-20 14:15:09 +00:00
wipe_tower . prime ( ( float ) this - > skirt_first_layer_height ( ) , m_wipe_tower_data . tool_ordering . all_extruders ( ) , false ) ) ;
2017-09-01 15:30:18 +00:00
2017-12-21 12:28:26 +00:00
// Lets go through the wipe tower layers and determine pairs of extruder changes for each
// to pass to wipe_tower (so that it can use it for planning the layout of the tower)
{
2018-09-12 09:59:02 +00:00
unsigned int current_extruder_id = m_wipe_tower_data . tool_ordering . all_extruders ( ) . back ( ) ;
for ( auto & layer_tools : m_wipe_tower_data . tool_ordering . layer_tools ( ) ) { // for all layers
2017-12-21 12:28:26 +00:00
if ( ! layer_tools . has_wipe_tower ) continue ;
2018-09-12 09:59:02 +00:00
bool first_layer = & layer_tools = = & m_wipe_tower_data . tool_ordering . front ( ) ;
2019-06-20 14:15:09 +00:00
wipe_tower . plan_toolchange ( ( float ) layer_tools . print_z , ( float ) layer_tools . wipe_tower_layer_height , current_extruder_id , current_extruder_id , false ) ;
2017-12-21 12:28:26 +00:00
for ( const auto extruder_id : layer_tools . extruders ) {
2018-09-12 09:59:02 +00:00
if ( ( first_layer & & extruder_id = = m_wipe_tower_data . tool_ordering . all_extruders ( ) . back ( ) ) | | extruder_id ! = current_extruder_id ) {
2018-08-02 13:14:12 +00:00
float volume_to_wipe = wipe_volumes [ current_extruder_id ] [ extruder_id ] ; // total volume to wipe after this toolchange
// Not all of that can be used for infill purging:
2019-06-20 14:15:09 +00:00
volume_to_wipe - = ( float ) m_config . filament_minimal_purge_on_wipe_tower . get_at ( extruder_id ) ;
2018-05-24 15:24:37 +00:00
2018-07-13 11:16:38 +00:00
// try to assign some infills/objects for the wiping:
2018-08-02 13:14:12 +00:00
volume_to_wipe = layer_tools . wiping_extrusions ( ) . mark_wiping_extrusions ( * this , current_extruder_id , extruder_id , volume_to_wipe ) ;
2018-05-24 15:24:37 +00:00
2018-08-02 13:14:12 +00:00
// add back the minimal amount toforce on the wipe tower:
2019-06-20 14:15:09 +00:00
volume_to_wipe + = ( float ) m_config . filament_minimal_purge_on_wipe_tower . get_at ( extruder_id ) ;
2018-05-24 15:24:37 +00:00
2018-08-02 13:14:12 +00:00
// request a toolchange at the wipe tower with at least volume_to_wipe purging amount
2019-06-20 14:15:09 +00:00
wipe_tower . plan_toolchange ( ( float ) layer_tools . print_z , ( float ) layer_tools . wipe_tower_layer_height , current_extruder_id , extruder_id ,
2018-09-12 09:59:02 +00:00
first_layer & & extruder_id = = m_wipe_tower_data . tool_ordering . all_extruders ( ) . back ( ) , volume_to_wipe ) ;
2017-12-21 12:28:26 +00:00
current_extruder_id = extruder_id ;
2017-05-25 20:27:53 +00:00
}
}
2018-07-11 12:46:13 +00:00
layer_tools . wiping_extrusions ( ) . ensure_perimeters_infills_order ( * this ) ;
2018-09-12 09:59:02 +00:00
if ( & layer_tools = = & m_wipe_tower_data . tool_ordering . back ( ) | | ( & layer_tools + 1 ) - > wipe_tower_partitions = = 0 )
2017-12-21 12:28:26 +00:00
break ;
2017-05-25 20:27:53 +00:00
}
}
2017-12-21 12:28:26 +00:00
2017-05-25 20:27:53 +00:00
// Generate the wipe tower layers.
2018-09-12 09:59:02 +00:00
m_wipe_tower_data . tool_changes . reserve ( m_wipe_tower_data . tool_ordering . layer_tools ( ) . size ( ) ) ;
wipe_tower . generate ( m_wipe_tower_data . tool_changes ) ;
m_wipe_tower_data . depth = wipe_tower . get_depth ( ) ;
2019-10-08 11:50:51 +00:00
m_wipe_tower_data . brim_width = wipe_tower . get_brim_width ( ) ;
2018-07-27 13:56:27 +00:00
2017-05-25 20:27:53 +00:00
// Unload the current filament over the purge tower.
2018-09-11 12:04:47 +00:00
coordf_t layer_height = m_objects . front ( ) - > config ( ) . layer_height . value ;
if ( m_wipe_tower_data . tool_ordering . back ( ) . wipe_tower_partitions > 0 ) {
2017-05-30 07:25:34 +00:00
// The wipe tower goes up to the last layer of the print.
if ( wipe_tower . layer_finished ( ) ) {
// The wipe tower is printed to the top of the print and it has no space left for the final extruder purge.
// Lift Z to the next layer.
2018-09-11 12:04:47 +00:00
wipe_tower . set_layer ( float ( m_wipe_tower_data . tool_ordering . back ( ) . print_z + layer_height ) , float ( layer_height ) , 0 , false , true ) ;
2017-05-30 07:25:34 +00:00
} else {
// There is yet enough space at this layer of the wipe tower for the final purge.
}
} else {
// The wipe tower does not reach the last print layer, perform the pruge at the last print layer.
2018-09-11 12:04:47 +00:00
assert ( m_wipe_tower_data . tool_ordering . back ( ) . wipe_tower_partitions = = 0 ) ;
wipe_tower . set_layer ( float ( m_wipe_tower_data . tool_ordering . back ( ) . print_z ) , float ( layer_height ) , 0 , false , true ) ;
2017-05-25 20:27:53 +00:00
}
2018-09-11 12:04:47 +00:00
m_wipe_tower_data . final_purge = Slic3r : : make_unique < WipeTower : : ToolChangeResult > (
2018-03-08 15:44:52 +00:00
wipe_tower . tool_change ( ( unsigned int ) - 1 , false ) ) ;
2018-09-17 13:12:13 +00:00
m_wipe_tower_data . used_filament = wipe_tower . get_used_filament ( ) ;
m_wipe_tower_data . number_of_toolchanges = wipe_tower . get_number_of_toolchanges ( ) ;
2017-05-25 20:27:53 +00:00
}
2018-07-11 12:46:13 +00:00
// Returns extruder this eec should be printed with, according to PrintRegion config
int Print : : get_extruder ( const ExtrusionEntityCollection & fill , const PrintRegion & region )
{
2018-09-12 09:59:02 +00:00
return is_infill ( fill . role ( ) ) ? 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 ) ;
2013-12-20 00:36:42 +00:00
}
2018-07-11 12:46:13 +00:00
2019-02-03 09:41:14 +00:00
// Generate a recommended G-code output file name based on the format template, default extension, and template parameters
// (timestamps, object placeholders derived from the model, current placeholder prameters and print statistics.
// Use the final print statistics if available, or just keep the print statistics placeholders if not available yet (before G-code is finalized).
2019-05-17 14:27:00 +00:00
std : : string Print : : output_filename ( const std : : string & filename_base ) const
2018-12-12 11:00:45 +00:00
{
// Set the placeholders for the data know first after the G-code export is finished.
// These values will be just propagated into the output file name.
2018-12-12 14:09:20 +00:00
DynamicConfig config = this - > finished ( ) ? this - > print_statistics ( ) . config ( ) : this - > print_statistics ( ) . placeholders ( ) ;
2019-05-17 14:27:00 +00:00
return this - > PrintBase : : output_filename ( m_config . output_filename_format . value , " .gcode " , filename_base , & config ) ;
2018-12-12 14:09:20 +00:00
}
WIP: Nullable configuration value concept, implemented for
ConfigOptionFloatsNullable, ConfigOptionIntsNullable,
ConfigOptionPercentsNullable, ConfigOptionBoolsNullable.
retract override values were added to the Filament profile:
vector of floats: "retract_length", "retract_lift", "retract_lift_above",
"retract_lift_below", "retract_speed", "deretract_speed",
"retract_restart_extra", "retract_before_travel",
vector of bools: "retract_layer_change", "wipe"
vector of percents: "retract_before_wipe"
2019-07-23 12:15:42 +00:00
2018-12-12 14:09:20 +00:00
DynamicConfig PrintStatistics : : config ( ) const
{
DynamicConfig config ;
2018-12-12 14:48:39 +00:00
std : : string normal_print_time = short_time ( this - > estimated_normal_print_time ) ;
std : : string silent_print_time = short_time ( this - > estimated_silent_print_time ) ;
2018-12-12 14:09:20 +00:00
config . set_key_value ( " print_time " , new ConfigOptionString ( normal_print_time ) ) ;
config . set_key_value ( " normal_print_time " , new ConfigOptionString ( normal_print_time ) ) ;
config . set_key_value ( " silent_print_time " , new ConfigOptionString ( silent_print_time ) ) ;
2019-01-29 10:14:29 +00:00
config . set_key_value ( " used_filament " , new ConfigOptionFloat ( this - > total_used_filament / 1000. ) ) ;
2018-12-12 14:09:20 +00:00
config . set_key_value ( " extruded_volume " , new ConfigOptionFloat ( this - > total_extruded_volume ) ) ;
config . set_key_value ( " total_cost " , new ConfigOptionFloat ( this - > total_cost ) ) ;
2019-02-04 23:55:06 +00:00
config . set_key_value ( " total_toolchanges " , new ConfigOptionInt ( this - > total_toolchanges ) ) ;
2018-12-12 14:09:20 +00:00
config . set_key_value ( " total_weight " , new ConfigOptionFloat ( this - > total_weight ) ) ;
config . set_key_value ( " total_wipe_tower_cost " , new ConfigOptionFloat ( this - > total_wipe_tower_cost ) ) ;
config . set_key_value ( " total_wipe_tower_filament " , new ConfigOptionFloat ( this - > total_wipe_tower_filament ) ) ;
return config ;
}
DynamicConfig PrintStatistics : : placeholders ( )
{
2018-12-12 11:00:45 +00:00
DynamicConfig config ;
for ( const std : : string & key : {
" print_time " , " normal_print_time " , " silent_print_time " ,
" used_filament " , " extruded_volume " , " total_cost " , " total_weight " ,
2019-02-04 23:55:06 +00:00
" total_toolchanges " , " total_wipe_tower_cost " , " total_wipe_tower_filament " } )
2019-01-29 10:14:29 +00:00
config . set_key_value ( key , new ConfigOptionString ( std : : string ( " { " ) + key + " } " ) ) ;
2018-12-12 14:09:20 +00:00
return config ;
2018-12-12 11:00:45 +00:00
}
2018-12-12 14:09:20 +00:00
std : : string PrintStatistics : : finalize_output_path ( const std : : string & path_in ) const
{
std : : string final_path ;
try {
boost : : filesystem : : path path ( path_in ) ;
DynamicConfig cfg = this - > config ( ) ;
PlaceholderParser pp ;
std : : string new_stem = pp . process ( path . stem ( ) . string ( ) , 0 , & cfg ) ;
final_path = ( path . parent_path ( ) / ( new_stem + path . extension ( ) . string ( ) ) ) . string ( ) ;
} catch ( const std : : exception & ex ) {
BOOST_LOG_TRIVIAL ( error ) < < " Failed to apply the print statistics to the export file name: " < < ex . what ( ) ;
final_path = path_in ;
}
return final_path ;
}
2018-09-12 09:59:02 +00:00
2018-12-12 14:09:20 +00:00
} // namespace Slic3r