2017-11-02 16:21:34 +01:00
#include <cassert>
2017-09-19 13:55:48 +02:00
#include "Preset.hpp"
2018-03-29 17:54:43 +02:00
#include "AppConfig.hpp"
2018-04-19 12:08:59 +02:00
#include "BitmapCache.hpp"
2018-11-26 14:41:58 +01:00
#include "I18N.hpp"
2019-04-08 09:37:23 +02:00
#include "wxExtensions.hpp"
2017-09-19 13:55:48 +02:00
2018-12-12 19:02:18 +01:00
#ifdef _MSC_VER
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#endif /* _MSC_VER */
2019-01-30 15:22:17 +01:00
#include <algorithm>
2017-09-19 13:55:48 +02:00
#include <fstream>
2018-04-12 20:04:48 +02:00
#include <stdexcept>
2019-02-08 10:33:07 +01:00
#include <unordered_map>
2018-04-12 20:04:48 +02:00
#include <boost/format.hpp>
2017-10-02 17:35:00 +02:00
#include <boost/filesystem.hpp>
2018-04-11 13:12:08 +02:00
#include <boost/filesystem/fstream.hpp>
2017-09-19 13:55:48 +02:00
#include <boost/algorithm/string/predicate.hpp>
2017-10-25 12:53:31 +02:00
#include <boost/nowide/cenv.hpp>
2018-12-12 19:02:18 +01:00
#include <boost/nowide/convert.hpp>
2017-10-26 17:17:39 +02:00
#include <boost/nowide/cstdio.hpp>
2017-10-25 12:53:31 +02:00
#include <boost/nowide/fstream.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree.hpp>
2017-10-26 17:17:39 +02:00
#include <boost/locale.hpp>
2018-05-01 10:35:07 +02:00
#include <boost/log/trivial.hpp>
2017-10-25 12:53:31 +02:00
2017-09-19 13:55:48 +02:00
#include <wx/image.h>
2017-10-25 12:53:31 +02:00
#include <wx/choice.h>
2017-09-19 13:55:48 +02:00
#include <wx/bmpcbox.h>
2017-10-28 00:18:02 +02:00
#include <wx/wupdlock.h>
2017-09-19 13:55:48 +02:00
2018-11-26 14:41:58 +01:00
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
#include "libslic3r/PlaceholderParser.hpp"
2018-10-09 12:41:05 +02:00
#include "Plater.hpp"
2017-10-25 12:53:31 +02:00
2018-04-11 13:12:08 +02:00
using boost :: property_tree :: ptree ;
2017-09-19 13:55:48 +02:00
namespace Slic3r {
2018-04-11 13:12:08 +02:00
ConfigFileType guess_config_file_type ( const ptree & tree )
2017-11-01 19:30:05 +01:00
{
size_t app_config = 0 ;
size_t bundle = 0 ;
size_t config = 0 ;
2018-04-11 13:12:08 +02:00
for ( const ptree :: value_type & v : tree ) {
2017-11-01 19:30:05 +01:00
if ( v . second . empty ()) {
if ( v . first == "background_processing" ||
v . first == "last_output_path" ||
v . first == "no_controller" ||
v . first == "no_defaults" )
++ app_config ;
else if ( v . first == "nozzle_diameter" ||
v . first == "filament_diameter" )
++ config ;
} else if ( boost :: algorithm :: starts_with ( v . first , "print:" ) ||
boost :: algorithm :: starts_with ( v . first , "filament:" ) ||
boost :: algorithm :: starts_with ( v . first , "printer:" ) ||
v . first == "settings" )
++ bundle ;
else if ( v . first == "presets" ) {
++ app_config ;
++ bundle ;
} else if ( v . first == "recent" ) {
for ( auto & kvp : v . second )
if ( kvp . first == "config_directory" || kvp . first == "skein_directory" )
++ app_config ;
}
}
return ( app_config > bundle && app_config > config ) ? CONFIG_FILE_TYPE_APP_CONFIG :
( bundle > config ) ? CONFIG_FILE_TYPE_CONFIG_BUNDLE : CONFIG_FILE_TYPE_CONFIG ;
}
2018-04-11 13:12:08 +02:00
VendorProfile VendorProfile :: from_ini ( const boost :: filesystem :: path & path , bool load_all )
{
ptree tree ;
boost :: filesystem :: ifstream ifs ( path );
boost :: property_tree :: read_ini ( ifs , tree );
return VendorProfile :: from_ini ( tree , path , load_all );
}
2019-02-08 10:33:07 +01:00
static const std :: unordered_map < std :: string , std :: string > pre_family_model_map {{
{ "MK3" , "MK3" },
{ "MK3MMU2" , "MK3" },
{ "MK2.5" , "MK2.5" },
{ "MK2.5MMU2" , "MK2.5" },
{ "MK2S" , "MK2" },
{ "MK2SMM" , "MK2" },
{ "SL1" , "SL1" },
}};
2018-04-11 13:12:08 +02:00
VendorProfile VendorProfile :: from_ini ( const ptree & tree , const boost :: filesystem :: path & path , bool load_all )
{
static const std :: string printer_model_key = "printer_model:" ;
2019-04-12 16:18:46 +02:00
static const std :: string filaments_section = "default_filaments" ;
static const std :: string materials_section = "default_sla_materials" ;
2018-04-11 13:12:08 +02:00
const std :: string id = path . stem (). string ();
2018-04-12 20:04:48 +02:00
if ( ! boost :: filesystem :: exists ( path )) {
throw std :: runtime_error (( boost :: format ( "Cannot load Vendor Config Bundle `%1%`: File not found: `%2%`." ) % id % path ). str ());
}
2018-04-11 13:12:08 +02:00
VendorProfile res ( id );
2019-04-12 16:18:46 +02:00
// Helper to get compulsory fields
2018-04-11 13:12:08 +02:00
auto get_or_throw = [ & ]( const ptree & tree , const std :: string & key ) -> ptree :: const_assoc_iterator
{
auto res = tree . find ( key );
if ( res == tree . not_found ()) {
throw std :: runtime_error (( boost :: format ( "Vendor Config Bundle `%1%` is not valid: Missing secion or key: `%2%`." ) % id % key ). str ());
}
return res ;
};
2019-04-12 16:18:46 +02:00
// Load the header
2018-04-11 13:12:08 +02:00
const auto & vendor_section = get_or_throw ( tree , "vendor" ) -> second ;
res . name = get_or_throw ( vendor_section , "name" ) -> second . data ();
auto config_version_str = get_or_throw ( vendor_section , "config_version" ) -> second . data ();
auto config_version = Semver :: parse ( config_version_str );
if ( ! config_version ) {
throw std :: runtime_error (( boost :: format ( "Vendor Config Bundle `%1%` is not valid: Cannot parse config_version: `%2%`." ) % id % config_version_str ). str ());
} else {
res . config_version = std :: move ( * config_version );
}
2019-04-12 16:18:46 +02:00
// Load URLs
2019-05-13 18:38:48 +02:00
const auto config_update_url = vendor_section . find ( "config_update_url" );
2018-04-11 13:12:08 +02:00
if ( config_update_url != vendor_section . not_found ()) {
res . config_update_url = config_update_url -> second . data ();
}
2019-05-13 18:38:48 +02:00
const auto changelog_url = vendor_section . find ( "changelog_url" );
if ( changelog_url != vendor_section . not_found ()) {
res . changelog_url = changelog_url -> second . data ();
}
2018-04-11 13:12:08 +02:00
if ( ! load_all ) {
return res ;
}
2019-04-12 16:18:46 +02:00
// Load printer models
2018-04-11 13:12:08 +02:00
for ( auto & section : tree ) {
if ( boost :: starts_with ( section . first , printer_model_key )) {
VendorProfile :: PrinterModel model ;
model . id = section . first . substr ( printer_model_key . size ());
model . name = section . second . get < std :: string > ( "name" , model . id );
2019-01-30 15:22:17 +01:00
2019-02-07 15:55:47 +01:00
const char * technology_fallback = boost :: algorithm :: starts_with ( model . id , "SL" ) ? "SLA" : "FFF" ;
auto technology_field = section . second . get < std :: string > ( "technology" , technology_fallback );
2018-07-31 15:09:57 +02:00
if ( ! ConfigOptionEnum < PrinterTechnology >:: from_string ( technology_field , model . technology )) {
BOOST_LOG_TRIVIAL ( error ) << boost :: format ( "Vendor bundle: `%1%`: Invalid printer technology field: `%2%`" ) % id % technology_field ;
model . technology = ptFFF ;
}
2019-01-30 15:22:17 +01:00
2019-02-07 15:55:47 +01:00
model . family = section . second . get < std :: string > ( "family" , std :: string ());
2019-02-08 10:33:07 +01:00
if ( model . family . empty () && res . name == "Prusa Research" ) {
// If no family is specified, it can be inferred for known printers
const auto from_pre_map = pre_family_model_map . find ( model . id );
if ( from_pre_map != pre_family_model_map . end ()) { model . family = from_pre_map -> second ; }
}
2018-12-21 20:09:25 +01:00
#if 0
2019-08-06 16:51:32 +02:00
// Remove SLA printers from the initial alpha.
if (model.technology == ptSLA)
continue;
2018-12-21 20:09:25 +01:00
#endif
2019-08-06 16:51:32 +02:00
section . second . get < std :: string > ( "variants" , "" );
2018-05-01 10:35:07 +02:00
const auto variants_field = section . second . get < std :: string > ( "variants" , "" );
2018-04-11 13:12:08 +02:00
std :: vector < std :: string > variants ;
2018-05-01 10:35:07 +02:00
if ( Slic3r :: unescape_strings_cstyle ( variants_field , variants )) {
2018-04-11 13:12:08 +02:00
for ( const std :: string & variant_name : variants ) {
if ( model . variant ( variant_name ) == nullptr )
model . variants . emplace_back ( VendorProfile :: PrinterVariant ( variant_name ));
}
} else {
2018-05-01 10:35:07 +02:00
BOOST_LOG_TRIVIAL ( error ) << boost :: format ( "Vendor bundle: `%1%`: Malformed variants field: `%2%`" ) % id % variants_field ;
2018-04-11 13:12:08 +02:00
}
2020-01-07 14:08:21 +01:00
model . bed_model = section . second . get < std :: string > ( "bed_model" , "" );
model . bed_texture = section . second . get < std :: string > ( "bed_texture" , "" );
2018-04-11 13:12:08 +02:00
if ( ! model . id . empty () && ! model . variants . empty ())
res . models . push_back ( std :: move ( model ));
}
}
2019-04-12 16:18:46 +02:00
// Load filaments and sla materials to be installed by default
const auto filaments = tree . find ( filaments_section );
if ( filaments != tree . not_found ()) {
for ( auto & pair : filaments -> second ) {
if ( pair . second . data () == "1" ) {
res . default_filaments . insert ( pair . first );
}
}
}
const auto materials = tree . find ( materials_section );
if ( materials != tree . not_found ()) {
for ( auto & pair : materials -> second ) {
if ( pair . second . data () == "1" ) {
res . default_sla_materials . insert ( pair . first );
}
}
}
2018-04-11 13:12:08 +02:00
return res ;
}
2019-01-30 15:22:17 +01:00
std :: vector < std :: string > VendorProfile :: families () const
{
std :: vector < std :: string > res ;
unsigned num_familiies = 0 ;
for ( auto & model : models ) {
2019-02-07 15:55:47 +01:00
if ( std :: find ( res . begin (), res . end (), model . family ) == res . end ()) {
2019-01-30 15:22:17 +01:00
res . push_back ( model . family );
num_familiies ++ ;
}
}
return res ;
}
2018-04-11 13:12:08 +02:00
2017-10-26 17:17:39 +02:00
// Suffix to be added to a modified preset name in the combo box.
2017-10-25 12:53:31 +02:00
static std :: string g_suffix_modified = " (modified)" ;
2017-10-30 18:41:50 +01:00
const std :: string & Preset :: suffix_modified ()
{
return g_suffix_modified ;
}
2018-07-23 15:44:01 +02:00
void Preset :: update_suffix_modified ()
{
2019-08-06 16:51:32 +02:00
g_suffix_modified = ( " (" + _ ( L ( "modified" )) + ")" ). ToUTF8 (). data ();
2018-07-23 15:44:01 +02:00
}
2017-10-26 17:17:39 +02:00
// Remove an optional "(modified)" suffix from a name.
// This converts a UI name to a unique preset identifier.
2017-10-30 18:41:50 +01:00
std :: string Preset :: remove_suffix_modified ( const std :: string & name )
2017-10-26 17:17:39 +02:00
{
return boost :: algorithm :: ends_with ( name , g_suffix_modified ) ?
name . substr ( 0 , name . size () - g_suffix_modified . size ()) :
name ;
}
2017-10-25 12:53:31 +02:00
2017-11-01 19:30:05 +01:00
// Update new extruder fields at the printer profile.
void Preset :: normalize ( DynamicPrintConfig & config )
{
auto * nozzle_diameter = dynamic_cast < const ConfigOptionFloats *> ( config . option ( "nozzle_diameter" ));
if ( nozzle_diameter != nullptr )
2018-07-31 15:09:57 +02:00
// Loaded the FFF Printer settings. Verify, that all extruder dependent values have enough values.
2019-10-15 16:31:20 +02:00
config . set_num_extruders (( unsigned int ) nozzle_diameter -> values . size ());
2017-11-02 16:21:34 +01:00
if ( config . option ( "filament_diameter" ) != nullptr ) {
// This config contains single or multiple filament presets.
// Ensure that the filament preset vector options contain the correct number of values.
size_t n = ( nozzle_diameter == nullptr ) ? 1 : nozzle_diameter -> values . size ();
const auto & defaults = FullPrintConfig :: defaults ();
for ( const std :: string & key : Preset :: filament_options ()) {
2019-08-06 16:51:32 +02:00
if ( key == "compatible_prints" || key == "compatible_printers" )
continue ;
2017-11-02 16:21:34 +01:00
auto * opt = config . option ( key , false );
2018-05-23 14:15:10 +02:00
/*assert(opt != nullptr);
assert(opt->is_vector());*/
2017-11-02 16:21:34 +01:00
if ( opt != nullptr && opt -> is_vector ())
static_cast < ConfigOptionVectorBase *> ( opt ) -> resize ( n , defaults . option ( key ));
}
2018-05-23 13:19:25 +02:00
// The following keys are mandatory for the UI, but they are not part of FullPrintConfig, therefore they are handled separately.
for ( const std :: string & key : { "filament_settings_id" }) {
auto * opt = config . option ( key , false );
2018-11-06 19:09:54 +01:00
assert ( opt == nullptr || opt -> type () == coStrings );
2018-05-23 13:19:25 +02:00
if ( opt != nullptr && opt -> type () == coStrings )
static_cast < ConfigOptionStrings *> ( opt ) -> values . resize ( n , std :: string ());
}
2017-11-02 16:21:34 +01:00
}
2017-11-01 19:30:05 +01:00
}
2018-10-31 16:22:36 +01:00
std :: string Preset :: remove_invalid_keys ( DynamicPrintConfig & config , const DynamicPrintConfig & default_config )
2017-09-19 13:55:48 +02:00
{
2018-10-31 16:22:36 +01:00
std :: string incorrect_keys ;
for ( const std :: string & key : config . keys ())
if ( ! default_config . has ( key )) {
if ( incorrect_keys . empty ())
incorrect_keys = key ;
else {
incorrect_keys += ", " ;
incorrect_keys += key ;
}
config . erase ( key );
2017-11-01 19:30:05 +01:00
}
2018-10-31 16:22:36 +01:00
return incorrect_keys ;
2017-09-19 13:55:48 +02:00
}
2017-10-25 12:53:31 +02:00
void Preset :: save ()
{
this -> config . save ( this -> file );
}
// Return a label of this preset, consisting of a name and a "(modified)" suffix, if this preset is dirty.
std :: string Preset :: label () const
{
return this -> name + ( this -> is_dirty ? g_suffix_modified : "" );
}
2019-12-05 14:48:11 +01:00
bool is_compatible_with_print ( const PresetWithVendorProfile & preset , const PresetWithVendorProfile & active_print )
2018-12-04 17:56:49 +01:00
{
2019-12-05 14:48:11 +01:00
if ( preset . vendor != nullptr && preset . vendor != active_print . vendor )
// The current profile has a vendor assigned and it is different from the active print's vendor.
return false ;
auto & condition = preset . preset . compatible_prints_condition ();
auto * compatible_prints = dynamic_cast < const ConfigOptionStrings *> ( preset . preset . config . option ( "compatible_prints" ));
2018-12-04 17:56:49 +01:00
bool has_compatible_prints = compatible_prints != nullptr && ! compatible_prints -> values . empty ();
if ( ! has_compatible_prints && ! condition . empty ()) {
try {
2019-12-05 14:48:11 +01:00
return PlaceholderParser :: evaluate_boolean_expression ( condition , active_print . preset . config );
2018-12-04 17:56:49 +01:00
} catch ( const std :: runtime_error & err ) {
//FIXME in case of an error, return "compatible with everything".
2019-12-05 14:48:11 +01:00
printf ( "Preset::is_compatible_with_print - parsing error of compatible_prints_condition %s: \n %s \n " , active_print . preset . name . c_str (), err . what ());
2018-12-04 17:56:49 +01:00
return true ;
}
}
2019-12-05 14:48:11 +01:00
return preset . preset . is_default || active_print . preset . name . empty () || ! has_compatible_prints ||
std :: find ( compatible_prints -> values . begin (), compatible_prints -> values . end (), active_print . preset . name ) !=
2018-12-04 17:56:49 +01:00
compatible_prints -> values . end ();
}
2019-12-05 14:48:11 +01:00
bool is_compatible_with_printer ( const PresetWithVendorProfile & preset , const PresetWithVendorProfile & active_printer , const DynamicPrintConfig * extra_config )
2017-09-19 13:55:48 +02:00
{
2019-12-05 14:48:11 +01:00
if ( preset . vendor != nullptr && preset . vendor != active_printer . vendor )
// The current profile has a vendor assigned and it is different from the active print's vendor.
return false ;
auto & condition = preset . preset . compatible_printers_condition ();
auto * compatible_printers = dynamic_cast < const ConfigOptionStrings *> ( preset . preset . config . option ( "compatible_printers" ));
2017-12-20 13:32:02 +01:00
bool has_compatible_printers = compatible_printers != nullptr && ! compatible_printers -> values . empty ();
2018-06-27 16:57:42 +02:00
if ( ! has_compatible_printers && ! condition . empty ()) {
2017-12-18 15:07:38 +01:00
try {
2019-12-05 14:48:11 +01:00
return PlaceholderParser :: evaluate_boolean_expression ( condition , active_printer . preset . config , extra_config );
2017-12-18 15:07:38 +01:00
} catch ( const std :: runtime_error & err ) {
//FIXME in case of an error, return "compatible with everything".
2019-12-05 14:48:11 +01:00
printf ( "Preset::is_compatible_with_printer - parsing error of compatible_printers_condition %s: \n %s \n " , active_printer . preset . name . c_str (), err . what ());
2017-12-18 15:07:38 +01:00
return true ;
}
}
2019-12-05 14:48:11 +01:00
return preset . preset . is_default || active_printer . preset . name . empty () || ! has_compatible_printers ||
std :: find ( compatible_printers -> values . begin (), compatible_printers -> values . end (), active_printer . preset . name ) !=
2017-09-19 13:55:48 +02:00
compatible_printers -> values . end ();
2017-11-10 17:27:05 +01:00
}
2019-12-05 14:48:11 +01:00
bool is_compatible_with_printer ( const PresetWithVendorProfile & preset , const PresetWithVendorProfile & active_printer )
2017-11-10 17:27:05 +01:00
{
2017-12-19 16:48:14 +01:00
DynamicPrintConfig config ;
2019-12-05 14:48:11 +01:00
config . set_key_value ( "printer_preset" , new ConfigOptionString ( active_printer . preset . name ));
const ConfigOption * opt = active_printer . preset . config . option ( "nozzle_diameter" );
2019-08-06 16:51:32 +02:00
if ( opt )
config . set_key_value ( "num_extruders" , new ConfigOptionInt (( int ) static_cast < const ConfigOptionFloats *> ( opt ) -> values . size ()));
2019-12-05 14:48:11 +01:00
return is_compatible_with_printer ( preset , active_printer , & config );
2017-09-19 13:55:48 +02:00
}
2018-03-29 17:54:43 +02:00
void Preset :: set_visible_from_appconfig ( const AppConfig & app_config )
{
if ( vendor == nullptr ) { return ; }
2019-04-12 16:18:46 +02:00
if ( type == TYPE_PRINTER ) {
const std :: string & model = config . opt_string ( "printer_model" );
const std :: string & variant = config . opt_string ( "printer_variant" );
if ( model . empty () || variant . empty ()) { return ; }
is_visible = app_config . get_variant ( vendor -> id , model , variant );
} else if ( type == TYPE_FILAMENT ) {
is_visible = app_config . has ( "filaments" , name );
} else if ( type == TYPE_SLA_MATERIAL ) {
is_visible = app_config . has ( "sla_materials" , name );
}
2018-03-29 17:54:43 +02:00
}
2017-11-01 19:30:05 +01:00
const std :: vector < std :: string >& Preset :: print_options ()
2019-08-06 16:51:32 +02:00
{
2017-11-01 19:30:05 +01:00
static std :: vector < std :: string > s_opts {
2019-08-06 16:51:32 +02:00
"layer_height" , "first_layer_height" , "perimeters" , "spiral_vase" , "slice_closing_radius" , "top_solid_layers" , "bottom_solid_layers" ,
"extra_perimeters" , "ensure_vertical_shell_thickness" , "avoid_crossing_perimeters" , "thin_walls" , "overhangs" ,
2019-02-22 15:25:35 +01:00
"seam_position" , "external_perimeters_first" , "fill_density" , "fill_pattern" , "top_fill_pattern" , "bottom_fill_pattern" ,
2019-08-06 16:51:32 +02:00
"infill_every_layers" , "infill_only_where_needed" , "solid_infill_every_layers" , "fill_angle" , "bridge_angle" ,
"solid_infill_below_area" , "only_retract_when_crossing_perimeters" , "infill_first" , "max_print_speed" ,
"max_volumetric_speed" ,
2019-01-29 18:07:45 +01:00
#ifdef HAS_PRESSURE_EQUALIZER
2019-08-06 16:51:32 +02:00
"max_volumetric_extrusion_rate_slope_positive" , "max_volumetric_extrusion_rate_slope_negative" ,
2019-01-29 18:07:45 +01:00
#endif /* HAS_PRESSURE_EQUALIZER */
2019-08-06 16:51:32 +02:00
"perimeter_speed" , "small_perimeter_speed" , "external_perimeter_speed" , "infill_speed" , "solid_infill_speed" ,
2017-11-01 19:30:05 +01:00
"top_solid_infill_speed" , "support_material_speed" , "support_material_xy_spacing" , "support_material_interface_speed" ,
2019-08-06 16:51:32 +02:00
"bridge_speed" , "gap_fill_speed" , "travel_speed" , "first_layer_speed" , "perimeter_acceleration" , "infill_acceleration" ,
2017-11-01 19:30:05 +01:00
"bridge_acceleration" , "first_layer_acceleration" , "default_acceleration" , "skirts" , "skirt_distance" , "skirt_height" ,
2019-08-06 16:51:32 +02:00
"min_skirt_length" , "brim_width" , "support_material" , "support_material_auto" , "support_material_threshold" , "support_material_enforce_layers" ,
"raft_layers" , "support_material_pattern" , "support_material_with_sheath" , "support_material_spacing" ,
"support_material_synchronize_layers" , "support_material_angle" , "support_material_interface_layers" ,
"support_material_interface_spacing" , "support_material_interface_contact_loops" , "support_material_contact_distance" ,
"support_material_buildplate_only" , "dont_support_bridges" , "notes" , "complete_objects" , "extruder_clearance_radius" ,
"extruder_clearance_height" , "gcode_comments" , "gcode_label_objects" , "output_filename_format" , "post_process" , "perimeter_extruder" ,
"infill_extruder" , "solid_infill_extruder" , "support_material_extruder" , "support_material_interface_extruder" ,
"ooze_prevention" , "standby_temperature_delta" , "interface_shells" , "extrusion_width" , "first_layer_extrusion_width" ,
"perimeter_extrusion_width" , "external_perimeter_extrusion_width" , "infill_extrusion_width" , "solid_infill_extrusion_width" ,
"top_infill_extrusion_width" , "support_material_extrusion_width" , "infill_overlap" , "bridge_flow_ratio" , "clip_multipart_objects" ,
2017-11-01 19:30:05 +01:00
"elefant_foot_compensation" , "xy_size_compensation" , "threads" , "resolution" , "wipe_tower" , "wipe_tower_x" , "wipe_tower_y" ,
2019-08-06 16:51:32 +02:00
"wipe_tower_width" , "wipe_tower_rotation_angle" , "wipe_tower_bridging" , "single_extruder_multi_material_priming" ,
2019-09-19 16:30:01 +02:00
"wipe_tower_no_sparse_layers" , "compatible_printers" , "compatible_printers_condition" , "inherits"
2017-11-01 19:30:05 +01:00
};
return s_opts ;
}
const std :: vector < std :: string >& Preset :: filament_options ()
2019-08-06 16:51:32 +02:00
{
2017-11-01 19:30:05 +01:00
static std :: vector < std :: string > s_opts {
2018-04-06 12:33:12 +02:00
"filament_colour" , "filament_diameter" , "filament_type" , "filament_soluble" , "filament_notes" , "filament_max_volumetric_speed" ,
2018-08-21 14:36:24 +02:00
"extrusion_multiplier" , "filament_density" , "filament_cost" , "filament_loading_speed" , "filament_loading_speed_start" , "filament_load_time" ,
"filament_unloading_speed" , "filament_unloading_speed_start" , "filament_unload_time" , "filament_toolchange_delay" , "filament_cooling_moves" ,
"filament_cooling_initial_speed" , "filament_cooling_final_speed" , "filament_ramming_parameters" , "filament_minimal_purge_on_wipe_tower" ,
"temperature" , "first_layer_temperature" , "bed_temperature" , "first_layer_bed_temperature" , "fan_always_on" , "cooling" , "min_fan_speed" ,
"max_fan_speed" , "bridge_fan_speed" , "disable_fan_first_layers" , "fan_below_layer_time" , "slowdown_below_layer_time" , "min_print_speed" ,
2018-12-04 17:56:49 +01:00
"start_filament_gcode" , "end_filament_gcode" ,
2019-07-23 14:15:42 +02:00
// Retract overrides
2019-08-06 16:51:32 +02:00
"filament_retract_length" , "filament_retract_lift" , "filament_retract_lift_above" , "filament_retract_lift_below" , "filament_retract_speed" , "filament_deretract_speed" , "filament_retract_restart_extra" , "filament_retract_before_travel" ,
"filament_retract_layer_change" , "filament_wipe" , "filament_retract_before_wipe" ,
// Profile compatibility
2019-04-12 16:18:46 +02:00
"filament_vendor" , "compatible_prints" , "compatible_prints_condition" , "compatible_printers" , "compatible_printers_condition" , "inherits"
2017-11-01 19:30:05 +01:00
};
return s_opts ;
}
const std :: vector < std :: string >& Preset :: printer_options ()
2019-08-06 16:51:32 +02:00
{
2017-11-01 19:30:05 +01:00
static std :: vector < std :: string > s_opts ;
if ( s_opts . empty ()) {
s_opts = {
2018-07-31 15:09:57 +02:00
"printer_technology" ,
2019-07-18 12:56:52 +02:00
"bed_shape" , "bed_custom_texture" , "bed_custom_model" , "z_offset" , "gcode_flavor" , "use_relative_e_distances" , "serial_port" , "serial_speed" ,
2018-08-24 12:20:24 +02:00
"use_firmware_retraction" , "use_volumetric_e" , "variable_layer_height" ,
"host_type" , "print_host" , "printhost_apikey" , "printhost_cafile" ,
2017-11-28 15:19:57 +01:00
"single_extruder_multi_material" , "start_gcode" , "end_gcode" , "before_layer_gcode" , "layer_gcode" , "toolchange_gcode" ,
2018-04-06 12:33:12 +02:00
"between_objects_gcode" , "printer_vendor" , "printer_model" , "printer_variant" , "printer_notes" , "cooling_tube_retraction" ,
2019-08-06 16:51:32 +02:00
"cooling_tube_length" , "high_current_on_filament_swap" , "parking_pos_retraction" , "extra_loading_move" , "max_print_height" ,
2018-12-14 20:09:10 +01:00
"default_print_profile" , "inherits" ,
2018-08-04 17:38:25 +02:00
"remaining_times" , "silent_mode" , "machine_max_acceleration_extruding" , "machine_max_acceleration_retracting" ,
2019-08-06 16:51:32 +02:00
"machine_max_acceleration_x" , "machine_max_acceleration_y" , "machine_max_acceleration_z" , "machine_max_acceleration_e" ,
"machine_max_feedrate_x" , "machine_max_feedrate_y" , "machine_max_feedrate_z" , "machine_max_feedrate_e" ,
"machine_min_extruding_rate" , "machine_min_travel_rate" ,
2019-11-15 17:33:31 +01:00
"machine_max_jerk_x" , "machine_max_jerk_y" , "machine_max_jerk_z" , "machine_max_jerk_e" ,
"thumbnails"
2017-11-01 19:30:05 +01:00
};
s_opts . insert ( s_opts . end (), Preset :: nozzle_options (). begin (), Preset :: nozzle_options (). end ());
}
return s_opts ;
}
2019-08-06 16:51:32 +02:00
// The following nozzle options of a printer profile will be adjusted to match the size
2018-03-13 09:45:04 +01:00
// of the nozzle_diameter vector.
2017-11-01 19:30:05 +01:00
const std :: vector < std :: string >& Preset :: nozzle_options ()
{
2019-10-15 16:31:20 +02:00
return print_config_def . extruder_option_keys ();
2017-11-01 19:30:05 +01:00
}
2018-11-16 17:36:23 +01:00
const std :: vector < std :: string >& Preset :: sla_print_options ()
2019-08-06 16:51:32 +02:00
{
2018-11-16 17:36:23 +01:00
static std :: vector < std :: string > s_opts ;
if ( s_opts . empty ()) {
s_opts = {
"layer_height" ,
2019-02-18 16:04:55 +01:00
"faded_layers" ,
2018-11-22 18:02:05 +01:00
"supports_enable" ,
2018-11-23 13:03:07 +01:00
"support_head_front_diameter" ,
2018-11-16 17:36:23 +01:00
"support_head_penetration" ,
"support_head_width" ,
2018-11-23 13:03:07 +01:00
"support_pillar_diameter" ,
2019-01-09 12:21:43 +01:00
"support_pillar_connection_mode" ,
2019-02-05 11:16:03 +01:00
"support_buildplate_only" ,
2018-11-23 11:51:45 +01:00
"support_pillar_widening_factor" ,
2018-11-23 13:03:07 +01:00
"support_base_diameter" ,
2018-11-16 17:36:23 +01:00
"support_base_height" ,
2019-06-11 17:57:39 +02:00
"support_base_safety_distance" ,
2018-11-16 17:36:23 +01:00
"support_critical_angle" ,
"support_max_bridge_length" ,
2019-03-08 11:39:34 +01:00
"support_max_pillar_link_distance" ,
2018-11-19 17:58:08 +01:00
"support_object_elevation" ,
2019-02-19 16:34:52 +01:00
"support_points_density_relative" ,
"support_points_minimal_distance" ,
2019-03-01 17:53:02 +01:00
"slice_closing_radius" ,
2018-11-20 16:12:04 +01:00
"pad_enable" ,
2018-11-16 17:36:23 +01:00
"pad_wall_thickness" ,
"pad_wall_height" ,
2019-09-24 15:15:49 +02:00
"pad_brim_size" ,
2018-11-16 17:36:23 +01:00
"pad_max_merge_distance" ,
2019-08-06 16:51:32 +02:00
// "pad_edge_radius",
2019-02-25 16:04:46 +01:00
"pad_wall_slope" ,
2019-06-12 13:15:42 +02:00
"pad_object_gap" ,
2019-08-28 11:32:49 +02:00
"pad_around_object" ,
2019-09-24 15:15:49 +02:00
"pad_around_object_everywhere" ,
2019-06-11 17:57:39 +02:00
"pad_object_connector_stride" ,
"pad_object_connector_width" ,
"pad_object_connector_penetration" ,
2019-08-06 16:51:32 +02:00
"output_filename_format" ,
2018-11-16 17:36:23 +01:00
"default_sla_print_profile" ,
"compatible_printers" ,
2019-08-06 16:51:32 +02:00
"compatible_printers_condition" ,
2018-11-16 17:36:23 +01:00
"inherits"
};
}
return s_opts ;
}
2018-12-03 13:14:28 +01:00
const std :: vector < std :: string >& Preset :: sla_material_options ()
2019-08-06 16:51:32 +02:00
{
2018-12-03 13:14:28 +01:00
static std :: vector < std :: string > s_opts ;
if ( s_opts . empty ()) {
s_opts = {
2019-08-02 16:15:27 +02:00
"material_type" ,
2018-12-03 13:14:28 +01:00
"initial_layer_height" ,
2019-09-26 16:33:55 +02:00
"bottle_cost" ,
"bottle_volume" ,
"bottle_weight" ,
"material_density" ,
2019-08-20 17:24:48 +02:00
"exposure_time" ,
"initial_exposure_time" ,
2019-04-03 17:34:46 +02:00
"material_correction" ,
2018-12-03 13:14:28 +01:00
"material_notes" ,
2019-04-12 16:18:46 +02:00
"material_vendor" ,
2018-12-03 13:14:28 +01:00
"default_sla_material_profile" ,
2019-08-06 16:51:32 +02:00
"compatible_prints" , "compatible_prints_condition" ,
2018-12-04 17:56:49 +01:00
"compatible_printers" , "compatible_printers_condition" , "inherits"
2018-12-03 13:14:28 +01:00
};
}
return s_opts ;
}
const std :: vector < std :: string >& Preset :: sla_printer_options ()
2019-08-06 16:51:32 +02:00
{
2018-12-03 13:14:28 +01:00
static std :: vector < std :: string > s_opts ;
if ( s_opts . empty ()) {
s_opts = {
"printer_technology" ,
2019-07-18 14:39:19 +02:00
"bed_shape" , "bed_custom_texture" , "bed_custom_model" , "max_print_height" ,
2018-12-03 13:14:28 +01:00
"bed_shape" , "max_print_height" ,
"display_width" , "display_height" , "display_pixels_x" , "display_pixels_y" ,
2019-05-18 23:21:59 +02:00
"display_mirror_x" , "display_mirror_y" ,
2018-12-13 12:42:45 +01:00
"display_orientation" ,
2019-02-18 16:04:55 +01:00
"fast_tilt_time" , "slow_tilt_time" , "area_fill" ,
2019-04-09 13:42:32 +02:00
"relative_correction" ,
"absolute_correction" ,
"gamma_correction" ,
2019-08-20 17:24:48 +02:00
"min_exposure_time" , "max_exposure_time" ,
"min_initial_exposure_time" , "max_initial_exposure_time" ,
2018-12-18 14:34:16 +01:00
"print_host" , "printhost_apikey" , "printhost_cafile" ,
2018-12-03 13:14:28 +01:00
"printer_notes" ,
"inherits"
};
}
return s_opts ;
}
2018-08-03 14:14:25 +02:00
PresetCollection :: PresetCollection ( Preset :: Type type , const std :: vector < std :: string > & keys , const Slic3r :: StaticPrintConfig & defaults , const std :: string & default_name ) :
2017-09-19 13:55:48 +02:00
m_type ( type ),
2017-10-25 12:53:31 +02:00
m_edited_preset ( type , "" , false ),
m_idx_selected ( 0 ),
2018-04-19 12:08:59 +02:00
m_bitmap_main_frame ( new wxBitmap ),
2019-03-22 15:45:51 +01:00
m_bitmap_add ( new wxBitmap ),
2019-08-06 16:51:32 +02:00
m_bitmap_cache ( new GUI :: BitmapCache )
2017-09-19 13:55:48 +02:00
{
// Insert just the default preset.
2018-08-03 14:14:25 +02:00
this -> add_default_preset ( keys , defaults , default_name );
2017-10-25 12:53:31 +02:00
m_edited_preset . config . apply ( m_presets . front (). config );
}
PresetCollection ::~ PresetCollection ()
{
delete m_bitmap_main_frame ;
m_bitmap_main_frame = nullptr ;
2019-03-22 15:45:51 +01:00
delete m_bitmap_add ;
m_bitmap_add = nullptr ;
2019-08-06 16:51:32 +02:00
delete m_bitmap_cache ;
m_bitmap_cache = nullptr ;
2017-09-19 13:55:48 +02:00
}
2017-12-19 19:51:22 +01:00
void PresetCollection :: reset ( bool delete_files )
{
2018-08-01 11:09:51 +02:00
if ( m_presets . size () > m_num_default_presets ) {
2017-12-19 19:51:22 +01:00
if ( delete_files ) {
// Erase the preset files.
for ( Preset & preset : m_presets )
2018-03-09 16:37:33 +01:00
if ( ! preset . is_default && ! preset . is_external && ! preset . is_system )
2017-12-19 19:51:22 +01:00
boost :: nowide :: remove ( preset . file . c_str ());
}
// Don't use m_presets.resize() here as it requires a default constructor for Preset.
2018-08-01 11:09:51 +02:00
m_presets . erase ( m_presets . begin () + m_num_default_presets , m_presets . end ());
2017-12-19 19:51:22 +01:00
this -> select_preset ( 0 );
}
2020-01-02 16:40:32 +01:00
m_map_system_profile_renamed . clear ();
2017-12-19 19:51:22 +01:00
}
2018-08-03 14:14:25 +02:00
void PresetCollection :: add_default_preset ( const std :: vector < std :: string > & keys , const Slic3r :: StaticPrintConfig & defaults , const std :: string & preset_name )
2018-08-01 11:09:51 +02:00
{
// Insert just the default preset.
m_presets . emplace_back ( Preset ( this -> type (), preset_name , true ));
2019-08-06 16:51:32 +02:00
m_presets . back (). config . apply_only ( defaults , keys . empty () ? defaults . keys () : keys );
m_presets . back (). loaded = true ;
2018-08-01 11:09:51 +02:00
++ m_num_default_presets ;
}
2017-10-02 17:35:00 +02:00
// Load all presets found in dir_path.
// Throws an exception on error.
void PresetCollection :: load_presets ( const std :: string & dir_path , const std :: string & subdir )
{
2019-08-06 16:51:32 +02:00
boost :: filesystem :: path dir = boost :: filesystem :: canonical ( boost :: filesystem :: path ( dir_path ) / subdir ). make_preferred ();
m_dir_path = dir . string ();
2017-11-28 10:08:01 +01:00
std :: string errors_cummulative ;
2019-08-06 16:51:32 +02:00
// Store the loaded presets into a new vector, otherwise the binary search for already existing presets would be broken.
// (see the "Preset already present, not loading" message).
std :: deque < Preset > presets_loaded ;
for ( auto & dir_entry : boost :: filesystem :: directory_iterator ( dir ))
2019-02-03 15:30:37 +01:00
if ( Slic3r :: is_ini_file ( dir_entry )) {
2017-11-02 16:40:25 +01:00
std :: string name = dir_entry . path (). filename (). string ();
2017-10-02 17:35:00 +02:00
// Remove the .ini suffix.
name . erase ( name . size () - 4 );
2018-03-09 16:37:33 +01:00
if ( this -> find_preset ( name , false )) {
2018-05-01 10:35:07 +02:00
// This happens when there's is a preset (most likely legacy one) with the same name as a system preset
// that's already been loaded from a bundle.
BOOST_LOG_TRIVIAL ( warning ) << "Preset already present, not loading: " << name ;
2018-03-09 16:37:33 +01:00
continue ;
}
2017-10-02 17:35:00 +02:00
try {
Preset preset ( m_type , name , false );
2017-11-02 16:40:25 +01:00
preset . file = dir_entry . path (). string ();
2018-10-31 16:22:36 +01:00
// Load the preset file, apply preset values on top of defaults.
try {
DynamicPrintConfig config ;
config . load_from_ini ( preset . file );
// Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field.
2019-08-06 16:51:32 +02:00
const Preset & default_preset = this -> default_preset_for ( config );
2018-10-31 16:22:36 +01:00
preset . config = default_preset . config ;
preset . config . apply ( std :: move ( config ));
Preset :: normalize ( preset . config );
// Report configuration fields, which are misplaced into a wrong group.
2019-08-06 16:51:32 +02:00
std :: string incorrect_keys = Preset :: remove_invalid_keys ( config , default_preset . config );
2018-10-31 16:22:36 +01:00
if ( ! incorrect_keys . empty ())
BOOST_LOG_TRIVIAL ( error ) << "Error in a preset file: The preset \" " <<
preset . file << " \" contains the following incorrect keys: " << incorrect_keys << ", which were removed" ;
preset . loaded = true ;
} catch ( const std :: ifstream :: failure & err ) {
2019-08-06 16:51:32 +02:00
throw std :: runtime_error ( std :: string ( "The selected preset cannot be loaded: " ) + preset . file + " \n\t Reason: " + err . what ());
2018-10-31 16:22:36 +01:00
} catch ( const std :: runtime_error & err ) {
2019-08-06 16:51:32 +02:00
throw std :: runtime_error ( std :: string ( "Failed loading the preset file: " ) + preset . file + " \n\t Reason: " + err . what ());
2018-10-31 16:22:36 +01:00
}
2019-08-06 16:51:32 +02:00
presets_loaded . emplace_back ( preset );
2017-11-28 10:08:01 +01:00
} catch ( const std :: runtime_error & err ) {
errors_cummulative += err . what ();
errors_cummulative += " \n " ;
2019-08-06 16:51:32 +02:00
}
2017-10-02 17:35:00 +02:00
}
2019-08-06 16:51:32 +02:00
m_presets . insert ( m_presets . end (), std :: make_move_iterator ( presets_loaded . begin ()), std :: make_move_iterator ( presets_loaded . end ()));
2018-08-01 11:09:51 +02:00
std :: sort ( m_presets . begin () + m_num_default_presets , m_presets . end ());
2017-10-26 17:17:39 +02:00
this -> select_preset ( first_visible_idx ());
2017-11-28 10:08:01 +01:00
if ( ! errors_cummulative . empty ())
throw std :: runtime_error ( errors_cummulative );
2017-10-25 12:53:31 +02:00
}
// Load a preset from an already parsed config file, insert it into the sorted sequence of presets
// and select it, losing previous modifications.
Preset & PresetCollection :: load_preset ( const std :: string & path , const std :: string & name , const DynamicPrintConfig & config , bool select )
2017-11-02 16:21:34 +01:00
{
DynamicPrintConfig cfg ( this -> default_preset (). config );
2017-11-03 19:14:33 +01:00
cfg . apply_only ( config , cfg . keys (), true );
2018-06-22 14:17:03 +02:00
return this -> load_preset ( path , name , std :: move ( cfg ), select );
}
2018-06-26 11:31:01 +02:00
static bool profile_print_params_same ( const DynamicPrintConfig & cfg1 , const DynamicPrintConfig & cfg2 )
{
t_config_option_keys diff = cfg1 . diff ( cfg2 );
// Following keys are used by the UI, not by the slicing core, therefore they are not important
// when comparing profiles for equality. Ignore them.
2019-08-06 16:51:32 +02:00
for ( const char * key : { "compatible_prints" , "compatible_prints_condition" ,
"compatible_printers" , "compatible_printers_condition" , "inherits" ,
2018-12-03 13:14:28 +01:00
"print_settings_id" , "filament_settings_id" , "sla_print_settings_id" , "sla_material_settings_id" , "printer_settings_id" ,
2018-12-06 14:42:15 +01:00
"printer_model" , "printer_variant" , "default_print_profile" , "default_filament_profile" , "default_sla_print_profile" , "default_sla_material_profile" ,
"printhost_apikey" , "printhost_cafile" })
2018-06-26 11:31:01 +02:00
diff . erase ( std :: remove ( diff . begin (), diff . end (), key ), diff . end ());
// Preset with the same name as stored inside the config exists.
return diff . empty ();
}
2018-06-22 14:17:03 +02:00
// Load a preset from an already parsed config file, insert it into the sorted sequence of presets
// and select it, losing previous modifications.
2019-08-06 16:51:32 +02:00
// In case
2018-06-22 14:17:03 +02:00
Preset & PresetCollection :: load_external_preset (
// Path to the profile source file (a G-code, an AMF or 3MF file, a config file)
const std :: string & path ,
// Name of the profile, derived from the source file name.
const std :: string & name ,
// Original name of the profile, extracted from the loaded config. Empty, if the name has not been stored.
const std :: string & original_name ,
// Config to initialize the preset from.
const DynamicPrintConfig & config ,
// Select the preset after loading?
bool select )
{
// Load the preset over a default preset, so that the missing fields are filled in from the default preset.
2018-12-03 13:14:28 +01:00
DynamicPrintConfig cfg ( this -> default_preset_for ( config ). config );
2018-06-22 14:17:03 +02:00
cfg . apply_only ( config , cfg . keys (), true );
// Is there a preset already loaded with the name stored inside the config?
2018-06-28 20:13:01 +02:00
std :: deque < Preset >:: iterator it = this -> find_preset_internal ( original_name );
2019-08-06 16:51:32 +02:00
bool found = it != m_presets . end () && it -> name == original_name ;
2020-01-02 16:40:32 +01:00
if ( ! found ) {
// Try to match the original_name against the "renamed_from" profile names of loaded system profiles.
it = this -> find_preset_renamed ( original_name );
found = it != m_presets . end ();
}
2018-11-06 19:09:54 +01:00
if ( found && profile_print_params_same ( it -> config , cfg )) {
2018-06-26 11:31:01 +02:00
// The preset exists and it matches the values stored inside config.
if ( select )
this -> select_preset ( it - m_presets . begin ());
return * it ;
}
2018-06-27 16:57:42 +02:00
// Update the "inherits" field.
std :: string & inherits = Preset :: inherits ( cfg );
2018-11-06 19:09:54 +01:00
if ( found && inherits . empty ()) {
2018-06-27 16:57:42 +02:00
// There is a profile with the same name already loaded. Should we update the "inherits" field?
if ( it -> vendor == nullptr )
inherits = it -> inherits ();
else
inherits = it -> name ;
}
2018-06-26 11:31:01 +02:00
// The external preset does not match an internal preset, load the external preset.
std :: string new_name ;
for ( size_t idx = 0 ;; ++ idx ) {
std :: string suffix ;
if ( original_name . empty ()) {
if ( idx > 0 )
suffix = " (" + std :: to_string ( idx ) + ")" ;
} else {
if ( idx == 0 )
2019-08-06 16:51:32 +02:00
suffix = " (" + original_name + ")" ;
2018-06-26 11:31:01 +02:00
else
suffix = " (" + original_name + "-" + std :: to_string ( idx ) + ")" ;
}
new_name = name + suffix ;
it = this -> find_preset_internal ( new_name );
2018-06-28 20:13:01 +02:00
if ( it == m_presets . end () || it -> name != new_name )
2018-06-26 11:31:01 +02:00
// Unique profile name. Insert a new profile.
break ;
if ( profile_print_params_same ( it -> config , cfg )) {
2018-06-22 14:17:03 +02:00
// The preset exists and it matches the values stored inside config.
if ( select )
this -> select_preset ( it - m_presets . begin ());
return * it ;
}
2018-06-26 11:31:01 +02:00
// Form another profile name.
2018-06-22 14:17:03 +02:00
}
2018-06-26 11:31:01 +02:00
// Insert a new profile.
Preset & preset = this -> load_preset ( path , new_name , std :: move ( cfg ), select );
2018-06-22 14:17:03 +02:00
preset . is_external = true ;
2018-07-17 10:41:17 +02:00
if ( & this -> get_selected_preset () == & preset )
this -> get_edited_preset (). is_external = true ;
2018-06-22 14:17:03 +02:00
return preset ;
2017-11-02 16:21:34 +01:00
}
Preset & PresetCollection :: load_preset ( const std :: string & path , const std :: string & name , DynamicPrintConfig && config , bool select )
2017-10-25 12:53:31 +02:00
{
2017-12-06 16:47:53 +01:00
auto it = this -> find_preset_internal ( name );
if ( it == m_presets . end () || it -> name != name ) {
// The preset was not found. Create a new preset.
2017-10-25 12:53:31 +02:00
it = m_presets . emplace ( it , Preset ( m_type , name , false ));
2017-12-06 16:47:53 +01:00
}
2017-10-25 12:53:31 +02:00
Preset & preset = * it ;
preset . file = path ;
2017-11-02 16:21:34 +01:00
preset . config = std :: move ( config );
2017-10-25 12:53:31 +02:00
preset . loaded = true ;
2017-11-02 16:21:34 +01:00
preset . is_dirty = false ;
2017-10-25 12:53:31 +02:00
if ( select )
this -> select_preset_by_name ( name , true );
return preset ;
}
2017-10-26 17:17:39 +02:00
void PresetCollection :: save_current_preset ( const std :: string & new_name )
{
2019-08-06 16:51:32 +02:00
// 1) Find the preset with a new_name or create a new one,
// initialize it with the edited config.
2017-12-06 16:47:53 +01:00
auto it = this -> find_preset_internal ( new_name );
if ( it != m_presets . end () && it -> name == new_name ) {
2017-10-26 17:17:39 +02:00
// Preset with the same name found.
Preset & preset = * it ;
2018-03-12 16:04:32 +01:00
if ( preset . is_default || preset . is_external || preset . is_system )
2017-10-26 17:17:39 +02:00
// Cannot overwrite the default preset.
return ;
// Overwriting an existing preset.
preset . config = std :: move ( m_edited_preset . config );
2019-08-06 16:51:32 +02:00
// The newly saved preset will be activated -> make it visible.
preset . is_visible = true ;
2017-10-26 17:17:39 +02:00
} else {
// Creating a new preset.
2019-08-06 16:51:32 +02:00
Preset & preset = * m_presets . insert ( it , m_edited_preset );
2018-06-27 16:57:42 +02:00
std :: string & inherits = preset . inherits ();
2018-03-12 16:04:32 +01:00
std :: string old_name = preset . name ;
2017-10-26 17:17:39 +02:00
preset . name = new_name ;
2019-08-06 16:51:32 +02:00
preset . file = this -> path_from_name ( new_name );
2018-03-12 16:04:32 +01:00
preset . vendor = nullptr ;
if ( preset . is_system ) {
// Inheriting from a system preset.
inherits = /* preset.vendor->name + "/" + */ old_name ;
} else if ( inherits . empty ()) {
// Inheriting from a user preset. Link the new preset to the old preset.
2018-03-13 09:45:04 +01:00
// inherits = old_name;
2018-03-12 16:04:32 +01:00
} else {
2019-08-06 16:51:32 +02:00
// Inherited from a user preset. Just maintain the "inherited" flag,
2018-03-12 16:04:32 +01:00
// meaning it will inherit from either the system preset, or the inherited user preset.
}
preset . is_default = false ;
preset . is_system = false ;
preset . is_external = false ;
2019-08-06 16:51:32 +02:00
// The newly saved preset will be activated -> make it visible.
preset . is_visible = true ;
2019-11-29 11:02:30 +01:00
// Just system presets have aliases
preset . alias . clear ();
2019-08-06 16:51:32 +02:00
}
// 2) Activate the saved preset.
this -> select_preset_by_name ( new_name , true );
// 2) Store the active preset to disk.
this -> get_selected_preset (). save ();
2017-10-26 17:17:39 +02:00
}
2019-03-22 12:11:23 +01:00
bool PresetCollection :: delete_current_preset ()
2017-10-26 17:17:39 +02:00
{
const Preset & selected = this -> get_selected_preset ();
2019-08-06 16:51:32 +02:00
if ( selected . is_default )
return false ;
if ( ! selected . is_external && ! selected . is_system ) {
// Erase the preset file.
boost :: nowide :: remove ( selected . file . c_str ());
}
2017-10-26 17:17:39 +02:00
// Remove the preset from the list.
m_presets . erase ( m_presets . begin () + m_idx_selected );
// Find the next visible preset.
2017-11-02 16:21:34 +01:00
size_t new_selected_idx = m_idx_selected ;
if ( new_selected_idx < m_presets . size ())
for (; new_selected_idx < m_presets . size () && ! m_presets [ new_selected_idx ]. is_visible ; ++ new_selected_idx ) ;
if ( new_selected_idx == m_presets . size ())
2019-08-06 16:51:32 +02:00
for ( -- new_selected_idx ; new_selected_idx > 0 && ! m_presets [ new_selected_idx ]. is_visible ; -- new_selected_idx );
2017-11-02 16:21:34 +01:00
this -> select_preset ( new_selected_idx );
2019-08-06 16:51:32 +02:00
return true ;
2017-10-26 17:17:39 +02:00
}
2019-10-02 22:50:55 +02:00
bool PresetCollection :: delete_preset ( const std :: string & name )
{
auto it = this -> find_preset_internal ( name );
const Preset & preset = * it ;
if ( preset . is_default )
return false ;
if ( ! preset . is_external && ! preset . is_system ) {
// Erase the preset file.
boost :: nowide :: remove ( preset . file . c_str ());
}
m_presets . erase ( it );
return true ;
}
2019-04-09 17:40:14 +02:00
void PresetCollection :: load_bitmap_default ( wxWindow * window , const std :: string & file_name )
2017-10-25 12:53:31 +02:00
{
2019-04-12 10:57:30 +02:00
// XXX: See note in PresetBundle::load_compatible_bitmaps()
( void ) window ;
* m_bitmap_main_frame = create_scaled_bitmap ( nullptr , file_name );
2017-10-25 12:53:31 +02:00
}
2019-04-09 17:40:14 +02:00
void PresetCollection :: load_bitmap_add ( wxWindow * window , const std :: string & file_name )
2019-03-22 15:45:51 +01:00
{
2019-04-12 10:57:30 +02:00
// XXX: See note in PresetBundle::load_compatible_bitmaps()
( void ) window ;
* m_bitmap_add = create_scaled_bitmap ( nullptr , file_name );
2019-03-22 15:45:51 +01:00
}
2018-03-12 16:04:32 +01:00
const Preset * PresetCollection :: get_selected_preset_parent () const
{
2019-05-15 17:15:52 +02:00
if ( this -> get_selected_idx () == - 1 )
// This preset collection has no preset activated yet. Only the get_edited_preset() is valid.
return nullptr ;
2019-07-01 14:56:28 +02:00
2020-01-02 16:40:32 +01:00
const Preset & selected_preset = this -> get_selected_preset ();
if ( selected_preset . is_system || selected_preset . is_default )
return & selected_preset ;
const Preset & edited_preset = this -> get_edited_preset ();
const std :: string & inherits = edited_preset . inherits ();
const Preset * preset = nullptr ;
if ( inherits . empty ()) {
if ( selected_preset . is_external )
2019-07-01 14:56:28 +02:00
return nullptr ;
2020-01-02 16:40:32 +01:00
preset = & this -> default_preset ( m_type == Preset :: Type :: TYPE_PRINTER && edited_preset . printer_technology () == ptSLA ? 1 : 0 );
} else
preset = this -> find_preset ( inherits , false );
if ( preset == nullptr ) {
// Resolve the "renamed_from" field.
assert ( ! inherits . empty ());
auto it = this -> find_preset_renamed ( inherits );
if ( it != m_presets . end ())
preset = & ( * it );
2019-07-01 14:56:28 +02:00
}
return ( preset == nullptr /* || preset->is_default*/ || preset -> is_external ) ? nullptr : preset ;
2018-03-12 16:04:32 +01:00
}
2018-04-09 16:50:17 +02:00
const Preset * PresetCollection :: get_preset_parent ( const Preset & child ) const
{
2018-06-27 16:57:42 +02:00
const std :: string & inherits = child . inherits ();
if ( inherits . empty ())
2019-08-06 16:51:32 +02:00
// return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr;
return nullptr ;
2018-06-27 16:57:42 +02:00
const Preset * preset = this -> find_preset ( inherits , false );
2020-01-02 16:40:32 +01:00
if ( preset == nullptr ) {
auto it = this -> find_preset_renamed ( inherits );
if ( it != m_presets . end ())
preset = & ( * it );
}
2018-04-09 16:50:17 +02:00
return ( preset == nullptr /* || preset->is_default */ || preset -> is_external ) ? nullptr : preset ;
}
2019-12-05 14:48:11 +01:00
// Return vendor of the first parent profile, for which the vendor is defined, or null if such profile does not exist.
PresetWithVendorProfile PresetCollection :: get_preset_with_vendor_profile ( const Preset & preset ) const
{
const Preset * p = & preset ;
const VendorProfile * v = nullptr ;
do {
if ( p -> vendor != nullptr ) {
v = p -> vendor ;
break ;
}
p = this -> get_preset_parent ( * p );
} while ( p != nullptr );
return PresetWithVendorProfile ( preset , v );
}
2019-11-29 11:02:30 +01:00
const std :: string & PresetCollection :: get_preset_name_by_alias ( const std :: string & alias ) const
2019-11-25 15:57:36 +01:00
{
for ( size_t i = this -> m_presets . front (). is_visible ? 0 : m_num_default_presets ; i < this -> m_presets . size (); ++ i ) {
const Preset & preset = this -> m_presets [ i ];
if ( ! preset . is_visible || ( ! preset . is_compatible && i != m_idx_selected ))
continue ;
if ( preset . alias == alias )
return preset . name ;
}
return alias ;
}
2018-04-06 15:42:52 +02:00
const std :: string & PresetCollection :: get_suffix_modified () {
2019-08-06 16:51:32 +02:00
return g_suffix_modified ;
2018-04-06 15:42:52 +02:00
}
2017-10-25 12:53:31 +02:00
// Return a preset by its name. If the preset is active, a temporary copy is returned.
// If a preset is not found by its name, null is returned.
Preset * PresetCollection :: find_preset ( const std :: string & name , bool first_visible_if_not_found )
{
Preset key ( m_type , name , false );
2017-12-06 16:47:53 +01:00
auto it = this -> find_preset_internal ( name );
2017-10-25 12:53:31 +02:00
// Ensure that a temporary copy is returned if the preset found is currently selected.
2019-08-06 16:51:32 +02:00
return ( it != m_presets . end () && it -> name == key . name ) ? & this -> preset ( it - m_presets . begin ()) :
2017-10-25 12:53:31 +02:00
first_visible_if_not_found ? & this -> first_visible () : nullptr ;
}
// Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible.
size_t PresetCollection :: first_visible_idx () const
{
2018-08-01 11:09:51 +02:00
size_t idx = m_default_suppressed ? m_num_default_presets : 0 ;
2017-10-25 12:53:31 +02:00
for (; idx < this -> m_presets . size (); ++ idx )
if ( m_presets [ idx ]. is_visible )
break ;
2018-09-10 09:11:49 +02:00
if ( idx == m_presets . size ())
2017-10-25 12:53:31 +02:00
idx = 0 ;
return idx ;
2017-10-02 17:35:00 +02:00
}
2017-09-19 13:55:48 +02:00
void PresetCollection :: set_default_suppressed ( bool default_suppressed )
{
if ( m_default_suppressed != default_suppressed ) {
m_default_suppressed = default_suppressed ;
2019-08-06 16:51:32 +02:00
bool default_visible = ! default_suppressed || m_idx_selected < m_num_default_presets ;
for ( size_t i = 0 ; i < m_num_default_presets ; ++ i )
m_presets [ i ]. is_visible = default_visible ;
2017-09-19 13:55:48 +02:00
}
}
2019-12-05 14:48:11 +01:00
size_t PresetCollection :: update_compatible_internal ( const PresetWithVendorProfile & active_printer , const PresetWithVendorProfile * active_print , bool unselect_if_incompatible )
2017-09-19 13:55:48 +02:00
{
2017-12-19 16:48:14 +01:00
DynamicPrintConfig config ;
2019-12-05 14:48:11 +01:00
config . set_key_value ( "printer_preset" , new ConfigOptionString ( active_printer . preset . name ));
const ConfigOption * opt = active_printer . preset . config . option ( "nozzle_diameter" );
2018-08-01 11:09:51 +02:00
if ( opt )
config . set_key_value ( "num_extruders" , new ConfigOptionInt (( int ) static_cast < const ConfigOptionFloats *> ( opt ) -> values . size ()));
for ( size_t idx_preset = m_num_default_presets ; idx_preset < m_presets . size (); ++ idx_preset ) {
2017-11-10 17:27:05 +01:00
bool selected = idx_preset == m_idx_selected ;
Preset & preset_selected = m_presets [ idx_preset ];
Preset & preset_edited = selected ? m_edited_preset : preset_selected ;
2019-12-05 14:48:11 +01:00
const PresetWithVendorProfile this_preset_with_vendor_profile = this -> get_preset_with_vendor_profile ( preset_edited );
preset_edited . is_compatible = is_compatible_with_printer ( this_preset_with_vendor_profile , active_printer , & config );
if ( active_print != nullptr )
preset_edited . is_compatible &= is_compatible_with_print ( this_preset_with_vendor_profile , * active_print );
if ( ! preset_edited . is_compatible && selected && unselect_if_incompatible )
2018-10-31 16:22:36 +01:00
m_idx_selected = - 1 ;
2017-11-10 17:27:05 +01:00
if ( selected )
preset_selected . is_compatible = preset_edited . is_compatible ;
}
2018-04-13 14:49:33 +02:00
return m_idx_selected ;
2017-09-19 13:55:48 +02:00
}
2017-10-25 12:53:31 +02:00
// Save the preset under a new name. If the name is different from the old one,
// a new preset is stored into the list of presets.
// All presets are marked as not modified and the new preset is activated.
//void PresetCollection::save_current_preset(const std::string &new_name);
// Delete the current preset, activate the first visible preset.
//void PresetCollection::delete_current_preset();
2017-09-19 13:55:48 +02:00
// Update the wxChoice UI component from this list of presets.
2019-08-06 16:51:32 +02:00
// Hide the
2018-10-09 12:41:05 +02:00
void PresetCollection :: update_platter_ui ( GUI :: PresetComboBox * ui )
2017-09-19 13:55:48 +02:00
{
2019-03-16 22:23:51 +01:00
if ( ui == nullptr )
2017-09-19 13:55:48 +02:00
return ;
2019-03-13 13:13:18 +01:00
2017-10-26 17:17:39 +02:00
// Otherwise fill in the list from scratch.
2017-10-28 00:18:02 +02:00
ui -> Freeze ();
2017-10-26 17:17:39 +02:00
ui -> Clear ();
2019-11-21 20:33:15 +01:00
size_t selected_preset_item = INT_MAX ; // some value meaning that no one item is selected
2018-04-19 12:08:59 +02:00
2019-08-06 16:51:32 +02:00
const Preset & selected_preset = this -> get_selected_preset ();
// Show wide icons if the currently selected preset is not compatible with the current printer,
// and draw a red flag in front of the selected preset.
bool wide_icons = ! selected_preset . is_compatible && m_bitmap_incompatible != nullptr ;
2018-04-19 12:08:59 +02:00
2019-04-23 16:33:06 +02:00
/* It's supposed that standard size of an icon is 16px*16px for 100% scaled display.
* So set sizes for solid_colored icons used for filament preset
* and scale them in respect to em_unit value
*/
const float scale_f = ui -> em_unit () * 0.1f ;
const int icon_height = 16 * scale_f + 0.5f ;
const int icon_width = 16 * scale_f + 0.5f ;
const int thin_space_icon_width = 4 * scale_f + 0.5f ;
const int wide_space_icon_width = 6 * scale_f + 0.5f ;
2019-08-06 16:51:32 +02:00
std :: map < wxString , wxBitmap *> nonsys_presets ;
wxString selected = "" ;
2019-11-25 15:57:36 +01:00
wxString tooltip = "" ;
2019-08-06 16:51:32 +02:00
if ( ! this -> m_presets . front (). is_visible )
2019-03-22 17:07:12 +01:00
ui -> set_label_marker ( ui -> Append ( PresetCollection :: separator ( L ( "System presets" )), wxNullBitmap ));
2019-08-06 16:51:32 +02:00
for ( size_t i = this -> m_presets . front (). is_visible ? 0 : m_num_default_presets ; i < this -> m_presets . size (); ++ i ) {
2017-10-26 17:17:39 +02:00
const Preset & preset = this -> m_presets [ i ];
2017-11-10 17:27:05 +01:00
if ( ! preset . is_visible || ( ! preset . is_compatible && i != m_idx_selected ))
continue ;
2019-08-06 16:51:32 +02:00
std :: string bitmap_key = "" ;
// If the filament preset is not compatible and there is a "red flag" icon loaded, show it left
// to the filament color image.
if ( wide_icons )
bitmap_key += preset . is_compatible ? ",cmpt" : ",ncmpt" ;
bitmap_key += ( preset . is_system || preset . is_default ) ? ",syst" : ",nsyst" ;
wxBitmap * bmp = m_bitmap_cache -> find ( bitmap_key );
if ( bmp == nullptr ) {
// Create the bitmap with color bars.
std :: vector < wxBitmap > bmps ;
if ( wide_icons )
// Paint a red flag for incompatible presets.
bmps . emplace_back ( preset . is_compatible ? m_bitmap_cache -> mkclear ( icon_width , icon_height ) : * m_bitmap_incompatible );
// Paint the color bars.
bmps . emplace_back ( m_bitmap_cache -> mkclear ( thin_space_icon_width , icon_height ));
bmps . emplace_back ( * m_bitmap_main_frame );
// Paint a lock at the system presets.
bmps . emplace_back ( m_bitmap_cache -> mkclear ( wide_space_icon_width , icon_height ));
bmps . emplace_back (( preset . is_system || preset . is_default ) ? * m_bitmap_lock : m_bitmap_cache -> mkclear ( icon_width , icon_height ));
bmp = m_bitmap_cache -> insert ( bitmap_key , bmps );
}
2018-04-11 13:56:37 +02:00
2019-11-25 15:57:36 +01:00
const std :: string name = preset . alias . empty () ? preset . name : preset . alias ;
2019-08-06 16:51:32 +02:00
if ( preset . is_default || preset . is_system ) {
2019-11-25 15:57:36 +01:00
ui -> Append ( wxString :: FromUTF8 (( /*preset.*/ name + ( preset . is_dirty ? g_suffix_modified : "" )). c_str ()),
2019-08-06 16:51:32 +02:00
( bmp == 0 ) ? ( m_bitmap_main_frame ? * m_bitmap_main_frame : wxNullBitmap ) : * bmp );
2019-11-21 20:33:15 +01:00
if ( i == m_idx_selected ||
// just in case: mark selected_preset_item as a first added element
2019-11-25 15:57:36 +01:00
selected_preset_item == INT_MAX ) {
2019-08-06 16:51:32 +02:00
selected_preset_item = ui -> GetCount () - 1 ;
2019-11-25 15:57:36 +01:00
tooltip = wxString :: FromUTF8 ( preset . name . c_str ());
}
2019-08-06 16:51:32 +02:00
}
else
{
2019-11-25 15:57:36 +01:00
nonsys_presets . emplace ( wxString :: FromUTF8 (( /*preset.*/ name + ( preset . is_dirty ? g_suffix_modified : "" )). c_str ()), bmp /*preset.is_compatible*/ );
if ( i == m_idx_selected ) {
selected = wxString :: FromUTF8 (( /*preset.*/ name + ( preset . is_dirty ? g_suffix_modified : "" )). c_str ());
tooltip = wxString :: FromUTF8 ( preset . name . c_str ());
}
2019-08-06 16:51:32 +02:00
}
if ( i + 1 == m_num_default_presets )
2019-03-22 17:07:12 +01:00
ui -> set_label_marker ( ui -> Append ( PresetCollection :: separator ( L ( "System presets" )), wxNullBitmap ));
2019-08-06 16:51:32 +02:00
}
if ( ! nonsys_presets . empty ())
{
2019-03-22 17:07:12 +01:00
ui -> set_label_marker ( ui -> Append ( PresetCollection :: separator ( L ( "User presets" )), wxNullBitmap ));
2019-08-06 16:51:32 +02:00
for ( std :: map < wxString , wxBitmap *>:: iterator it = nonsys_presets . begin (); it != nonsys_presets . end (); ++ it ) {
ui -> Append ( it -> first , * it -> second );
2019-11-21 20:33:15 +01:00
if ( it -> first == selected ||
// just in case: mark selected_preset_item as a first added element
selected_preset_item == INT_MAX )
2019-08-06 16:51:32 +02:00
selected_preset_item = ui -> GetCount () - 1 ;
}
}
if ( m_type == Preset :: TYPE_PRINTER ) {
std :: string bitmap_key = "" ;
// If the filament preset is not compatible and there is a "red flag" icon loaded, show it left
// to the filament color image.
if ( wide_icons )
bitmap_key += "wide," ;
bitmap_key += "add_printer" ;
wxBitmap * bmp = m_bitmap_cache -> find ( bitmap_key );
if ( bmp == nullptr ) {
// Create the bitmap with color bars.
std :: vector < wxBitmap > bmps ;
if ( wide_icons )
// Paint a red flag for incompatible presets.
bmps . emplace_back ( m_bitmap_cache -> mkclear ( icon_width , icon_height ));
// Paint the color bars.
bmps . emplace_back ( m_bitmap_cache -> mkclear ( thin_space_icon_width , icon_height ));
bmps . emplace_back ( * m_bitmap_main_frame );
// Paint a lock at the system presets.
bmps . emplace_back ( m_bitmap_cache -> mkclear ( wide_space_icon_width , icon_height ));
bmps . emplace_back ( m_bitmap_add ? * m_bitmap_add : wxNullBitmap );
bmp = m_bitmap_cache -> insert ( bitmap_key , bmps );
}
2019-06-04 18:01:41 +02:00
ui -> set_label_marker ( ui -> Append ( PresetCollection :: separator ( L ( "Add a new printer" )), * bmp ), GUI :: PresetComboBox :: LABEL_ITEM_WIZARD_PRINTERS );
} else if ( m_type == Preset :: TYPE_SLA_MATERIAL ) {
ui -> set_label_marker ( ui -> Append ( PresetCollection :: separator ( L ( "Add/Remove materials" )), wxNullBitmap ), GUI :: PresetComboBox :: LABEL_ITEM_WIZARD_MATERIALS );
2019-08-06 16:51:32 +02:00
}
2018-06-29 11:29:23 +02:00
2019-11-21 20:33:15 +01:00
/* But, if selected_preset_item is still equal to INT_MAX, it means that
* there is no presets added to the list.
* So, select last combobox item ("Add/Remove preset")
*/
if ( selected_preset_item == INT_MAX )
selected_preset_item = ui -> GetCount () - 1 ;
2019-08-06 16:51:32 +02:00
ui -> SetSelection ( selected_preset_item );
2019-11-25 15:57:36 +01:00
ui -> SetToolTip ( tooltip . IsEmpty () ? ui -> GetString ( selected_preset_item ) : tooltip );
2019-03-23 10:04:44 +01:00
ui -> check_selection ();
2019-04-23 16:33:06 +02:00
ui -> Thaw ();
// Update control min size after rescale (changed Display DPI under MSW)
if ( ui -> GetMinWidth () != 20 * ui -> em_unit ())
ui -> SetMinSize ( wxSize ( 20 * ui -> em_unit (), ui -> GetSize (). GetHeight ()));
2017-09-19 13:55:48 +02:00
}
2019-04-23 16:33:06 +02:00
size_t PresetCollection :: update_tab_ui ( wxBitmapComboBox * ui , bool show_incompatible , const int em /* = 10*/ )
2017-10-25 12:53:31 +02:00
{
if ( ui == nullptr )
2018-04-19 12:08:59 +02:00
return 0 ;
2017-10-28 00:18:02 +02:00
ui -> Freeze ();
2017-10-25 12:53:31 +02:00
ui -> Clear ();
2019-11-21 20:33:15 +01:00
size_t selected_preset_item = INT_MAX ; // some value meaning that no one item is selected
2018-04-19 12:08:59 +02:00
2019-04-23 16:33:06 +02:00
/* It's supposed that standard size of an icon is 16px*16px for 100% scaled display.
* So set sizes for solid_colored(empty) icons used for preset
* and scale them in respect to em_unit value
*/
const float scale_f = em * 0.1f ;
const int icon_height = 16 * scale_f + 0.5f ;
const int icon_width = 16 * scale_f + 0.5f ;
2019-08-06 16:51:32 +02:00
std :: map < wxString , wxBitmap *> nonsys_presets ;
wxString selected = "" ;
if ( ! this -> m_presets . front (). is_visible )
ui -> Append ( PresetCollection :: separator ( L ( "System presets" )), wxNullBitmap );
for ( size_t i = this -> m_presets . front (). is_visible ? 0 : m_num_default_presets ; i < this -> m_presets . size (); ++ i ) {
2017-10-25 12:53:31 +02:00
const Preset & preset = this -> m_presets [ i ];
2017-12-13 14:00:14 +01:00
if ( ! preset . is_visible || ( ! show_incompatible && ! preset . is_compatible && i != m_idx_selected ))
2017-11-10 17:27:05 +01:00
continue ;
2019-08-06 16:51:32 +02:00
std :: string bitmap_key = "tab" ;
bitmap_key += preset . is_compatible ? ",cmpt" : ",ncmpt" ;
bitmap_key += ( preset . is_system || preset . is_default ) ? ",syst" : ",nsyst" ;
wxBitmap * bmp = m_bitmap_cache -> find ( bitmap_key );
if ( bmp == nullptr ) {
// Create the bitmap with color bars.
std :: vector < wxBitmap > bmps ;
const wxBitmap * tmp_bmp = preset . is_compatible ? m_bitmap_compatible : m_bitmap_incompatible ;
bmps . emplace_back (( tmp_bmp == 0 ) ? ( m_bitmap_main_frame ? * m_bitmap_main_frame : wxNullBitmap ) : * tmp_bmp );
// Paint a lock at the system presets.
bmps . emplace_back (( preset . is_system || preset . is_default ) ? * m_bitmap_lock : m_bitmap_cache -> mkclear ( icon_width , icon_height ));
bmp = m_bitmap_cache -> insert ( bitmap_key , bmps );
}
2018-04-10 10:31:58 +02:00
2019-08-06 16:51:32 +02:00
if ( preset . is_default || preset . is_system ) {
ui -> Append ( wxString :: FromUTF8 (( preset . name + ( preset . is_dirty ? g_suffix_modified : "" )). c_str ()),
( bmp == 0 ) ? ( m_bitmap_main_frame ? * m_bitmap_main_frame : wxNullBitmap ) : * bmp );
2019-11-21 20:33:15 +01:00
if ( i == m_idx_selected ||
// just in case: mark selected_preset_item as a first added element
selected_preset_item == INT_MAX )
2019-08-06 16:51:32 +02:00
selected_preset_item = ui -> GetCount () - 1 ;
}
else
{
nonsys_presets . emplace ( wxString :: FromUTF8 (( preset . name + ( preset . is_dirty ? g_suffix_modified : "" )). c_str ()), bmp /*preset.is_compatible*/ );
if ( i == m_idx_selected )
selected = wxString :: FromUTF8 (( preset . name + ( preset . is_dirty ? g_suffix_modified : "" )). c_str ());
}
if ( i + 1 == m_num_default_presets )
ui -> Append ( PresetCollection :: separator ( L ( "System presets" )), wxNullBitmap );
}
if ( ! nonsys_presets . empty ())
{
ui -> Append ( PresetCollection :: separator ( L ( "User presets" )), wxNullBitmap );
for ( std :: map < wxString , wxBitmap *>:: iterator it = nonsys_presets . begin (); it != nonsys_presets . end (); ++ it ) {
ui -> Append ( it -> first , * it -> second );
2019-11-21 20:33:15 +01:00
if ( it -> first == selected ||
// just in case: mark selected_preset_item as a first added element
selected_preset_item == INT_MAX )
2019-08-06 16:51:32 +02:00
selected_preset_item = ui -> GetCount () - 1 ;
}
2017-10-25 12:53:31 +02:00
}
2019-03-22 15:45:51 +01:00
if ( m_type == Preset :: TYPE_PRINTER ) {
wxBitmap * bmp = m_bitmap_cache -> find ( "add_printer_tab" );
if ( bmp == nullptr ) {
// Create the bitmap with color bars.
std :: vector < wxBitmap > bmps ;
bmps . emplace_back ( * m_bitmap_main_frame );
bmps . emplace_back ( m_bitmap_add ? * m_bitmap_add : wxNullBitmap );
bmp = m_bitmap_cache -> insert ( "add_printer_tab" , bmps );
}
2019-03-22 17:07:12 +01:00
ui -> Append ( PresetCollection :: separator ( "Add a new printer" ), * bmp );
2019-03-22 15:45:51 +01:00
}
2019-11-21 20:33:15 +01:00
/* But, if selected_preset_item is still equal to INT_MAX, it means that
* there is no presets added to the list.
* So, select last combobox item ("Add/Remove preset")
*/
if ( selected_preset_item == INT_MAX )
selected_preset_item = ui -> GetCount () - 1 ;
2019-08-06 16:51:32 +02:00
ui -> SetSelection ( selected_preset_item );
ui -> SetToolTip ( ui -> GetString ( selected_preset_item ));
2017-10-28 00:18:02 +02:00
ui -> Thaw ();
2019-08-06 16:51:32 +02:00
return selected_preset_item ;
2017-10-25 12:53:31 +02:00
}
// Update a dirty floag of the current preset, update the labels of the UI component accordingly.
// Return true if the dirty flag changed.
2017-11-10 17:27:05 +01:00
bool PresetCollection :: update_dirty_ui ( wxBitmapComboBox * ui )
2017-10-25 12:53:31 +02:00
{
2017-11-10 17:27:05 +01:00
wxWindowUpdateLocker noUpdates ( ui );
2017-10-25 12:53:31 +02:00
// 1) Update the dirty flag of the current preset.
bool was_dirty = this -> get_selected_preset (). is_dirty ;
bool is_dirty = current_is_dirty ();
this -> get_selected_preset (). is_dirty = is_dirty ;
2017-11-01 19:30:05 +01:00
this -> get_edited_preset (). is_dirty = is_dirty ;
2017-10-25 12:53:31 +02:00
// 2) Update the labels.
for ( unsigned int ui_id = 0 ; ui_id < ui -> GetCount (); ++ ui_id ) {
std :: string old_label = ui -> GetString ( ui_id ). utf8_str (). data ();
2017-10-30 18:41:50 +01:00
std :: string preset_name = Preset :: remove_suffix_modified ( old_label );
2017-10-25 12:53:31 +02:00
const Preset * preset = this -> find_preset ( preset_name , false );
2018-10-23 15:27:31 +02:00
// The old_label could be the "----- system presets ------" or the "------- user presets --------" separator.
// assert(preset != nullptr);
2019-08-06 16:51:32 +02:00
if ( preset != nullptr ) {
std :: string new_label = preset -> is_dirty ? preset -> name + g_suffix_modified : preset -> name ;
if ( old_label != new_label )
ui -> SetString ( ui_id , wxString :: FromUTF8 ( new_label . c_str ()));
}
2017-10-25 12:53:31 +02:00
}
2017-12-20 13:46:43 +01:00
#ifdef __APPLE__
// wxWidgets on OSX do not upload the text of the combo box line automatically.
// Force it to update by re-selecting.
ui -> SetSelection ( ui -> GetSelection ());
#endif /* __APPLE __ */
2017-10-25 12:53:31 +02:00
return was_dirty != is_dirty ;
}
2018-12-04 17:56:49 +01:00
template < class T >
void add_correct_opts_to_diff ( const std :: string & opt_key , t_config_option_keys & vec , const ConfigBase & other , const ConfigBase & this_c )
{
2019-08-06 16:51:32 +02:00
const T * opt_init = static_cast < const T *> ( other . option ( opt_key ));
const T * opt_cur = static_cast < const T *> ( this_c . option ( opt_key ));
int opt_init_max_id = opt_init -> values . size () - 1 ;
for ( int i = 0 ; i < opt_cur -> values . size (); i ++ )
{
int init_id = i <= opt_init_max_id ? i : 0 ;
if ( opt_cur -> values [ i ] != opt_init -> values [ init_id ])
vec . emplace_back ( opt_key + "#" + std :: to_string ( i ));
}
2018-12-04 17:56:49 +01:00
}
// Use deep_diff to correct return of changed options, considering individual options for each extruder.
inline t_config_option_keys deep_diff ( const ConfigBase & config_this , const ConfigBase & config_other )
{
t_config_option_keys diff ;
for ( const t_config_option_key & opt_key : config_this . keys ()) {
const ConfigOption * this_opt = config_this . option ( opt_key );
const ConfigOption * other_opt = config_other . option ( opt_key );
if ( this_opt != nullptr && other_opt != nullptr && * this_opt != * other_opt )
{
if ( opt_key == "bed_shape" || opt_key == "compatible_prints" || opt_key == "compatible_printers" ) {
diff . emplace_back ( opt_key );
continue ;
}
switch ( other_opt -> type ())
{
case coInts : add_correct_opts_to_diff < ConfigOptionInts > ( opt_key , diff , config_other , config_this ); break ;
case coBools : add_correct_opts_to_diff < ConfigOptionBools > ( opt_key , diff , config_other , config_this ); break ;
case coFloats : add_correct_opts_to_diff < ConfigOptionFloats > ( opt_key , diff , config_other , config_this ); break ;
case coStrings : add_correct_opts_to_diff < ConfigOptionStrings > ( opt_key , diff , config_other , config_this ); break ;
case coPercents : add_correct_opts_to_diff < ConfigOptionPercents > ( opt_key , diff , config_other , config_this ); break ;
case coPoints : add_correct_opts_to_diff < ConfigOptionPoints > ( opt_key , diff , config_other , config_this ); break ;
default : diff . emplace_back ( opt_key ); break ;
}
}
}
return diff ;
}
2018-08-08 17:47:59 +02:00
std :: vector < std :: string > PresetCollection :: dirty_options ( const Preset * edited , const Preset * reference , const bool deep_compare /*= false*/ )
2018-03-12 16:04:32 +01:00
{
std :: vector < std :: string > changed ;
2019-08-06 16:51:32 +02:00
if ( edited != nullptr && reference != nullptr ) {
2018-08-08 17:47:59 +02:00
changed = deep_compare ?
2019-08-06 16:51:32 +02:00
deep_diff ( edited -> config , reference -> config ) :
reference -> config . diff ( edited -> config );
2018-03-12 16:04:32 +01:00
// The "compatible_printers" option key is handled differently from the others:
// It is not mandatory. If the key is missing, it means it is compatible with any printer.
// If the key exists and it is empty, it means it is compatible with no printer.
2018-12-04 17:56:49 +01:00
std :: initializer_list < const char *> optional_keys { "compatible_prints" , "compatible_printers" };
2018-03-12 16:04:32 +01:00
for ( auto & opt_key : optional_keys ) {
if ( reference -> config . has ( opt_key ) != edited -> config . has ( opt_key ))
changed . emplace_back ( opt_key );
}
2017-12-18 15:07:38 +01:00
}
2017-12-13 14:44:27 +01:00
return changed ;
}
2017-12-13 14:00:14 +01:00
// Select a new preset. This resets all the edits done to the currently selected preset.
// If the preset with index idx does not exist, a first visible preset is selected.
2017-10-25 12:53:31 +02:00
Preset & PresetCollection :: select_preset ( size_t idx )
{
2017-11-02 16:21:34 +01:00
for ( Preset & preset : m_presets )
preset . is_dirty = false ;
2017-10-25 12:53:31 +02:00
if ( idx >= m_presets . size ())
idx = first_visible_idx ();
m_idx_selected = idx ;
m_edited_preset = m_presets [ idx ];
2019-08-06 16:51:32 +02:00
bool default_visible = ! m_default_suppressed || m_idx_selected < m_num_default_presets ;
for ( size_t i = 0 ; i < m_num_default_presets ; ++ i )
m_presets [ i ]. is_visible = default_visible ;
2017-10-25 12:53:31 +02:00
return m_presets [ idx ];
}
2017-10-26 17:17:39 +02:00
bool PresetCollection :: select_preset_by_name ( const std :: string & name_w_suffix , bool force )
2019-08-06 16:51:32 +02:00
{
2017-10-30 18:41:50 +01:00
std :: string name = Preset :: remove_suffix_modified ( name_w_suffix );
2017-10-25 12:53:31 +02:00
// 1) Try to find the preset by its name.
2017-12-06 16:47:53 +01:00
auto it = this -> find_preset_internal ( name );
2017-10-25 12:53:31 +02:00
size_t idx = 0 ;
2019-08-06 16:51:32 +02:00
if ( it != m_presets . end () && it -> name == name && it -> is_visible )
2018-04-13 14:49:33 +02:00
// Preset found by its name and it is visible.
2017-10-25 12:53:31 +02:00
idx = it - m_presets . begin ();
else {
// Find the first visible preset.
2018-08-01 11:09:51 +02:00
for ( size_t i = m_default_suppressed ? m_num_default_presets : 0 ; i < m_presets . size (); ++ i )
2017-10-25 12:53:31 +02:00
if ( m_presets [ i ]. is_visible ) {
idx = i ;
break ;
}
// If the first visible preset was not found, return the 0th element, which is the default preset.
}
// 2) Select the new preset.
if ( m_idx_selected != idx || force ) {
this -> select_preset ( idx );
return true ;
}
return false ;
}
2018-04-13 14:49:33 +02:00
bool PresetCollection :: select_preset_by_name_strict ( const std :: string & name )
2019-08-06 16:51:32 +02:00
{
2018-04-13 14:49:33 +02:00
// 1) Try to find the preset by its name.
auto it = this -> find_preset_internal ( name );
size_t idx = ( size_t ) - 1 ;
2019-08-06 16:51:32 +02:00
if ( it != m_presets . end () && it -> name == name && it -> is_visible )
2018-04-13 14:49:33 +02:00
// Preset found by its name.
idx = it - m_presets . begin ();
// 2) Select the new preset.
if ( idx != ( size_t ) - 1 ) {
this -> select_preset ( idx );
return true ;
}
m_idx_selected = idx ;
return false ;
}
2018-04-18 13:35:51 +02:00
// Merge one vendor's presets with the other vendor's presets, report duplicates.
2019-06-17 16:39:22 +02:00
std :: vector < std :: string > PresetCollection :: merge_presets ( PresetCollection && other , const VendorMap & new_vendors )
2018-04-18 13:35:51 +02:00
{
std :: vector < std :: string > duplicates ;
for ( Preset & preset : other . m_presets ) {
if ( preset . is_default || preset . is_external )
continue ;
Preset key ( m_type , preset . name );
2018-08-01 11:09:51 +02:00
auto it = std :: lower_bound ( m_presets . begin () + m_num_default_presets , m_presets . end (), key );
2018-04-18 13:35:51 +02:00
if ( it == m_presets . end () || it -> name != preset . name ) {
if ( preset . vendor != nullptr ) {
// Re-assign a pointer to the vendor structure in the new PresetBundle.
2019-06-17 16:39:22 +02:00
auto it = new_vendors . find ( preset . vendor -> id );
2018-04-18 13:35:51 +02:00
assert ( it != new_vendors . end ());
2019-06-17 16:39:22 +02:00
preset . vendor = & it -> second ;
2018-04-18 13:35:51 +02:00
}
this -> m_presets . emplace ( it , std :: move ( preset ));
} else
duplicates . emplace_back ( std :: move ( preset . name ));
}
return duplicates ;
}
2020-01-02 16:40:32 +01:00
void PresetCollection :: update_map_system_profile_renamed ()
{
m_map_system_profile_renamed . clear ();
for ( Preset & preset : m_presets )
for ( const std :: string & renamed_from : preset . renamed_from ) {
const auto [ it , success ] = m_map_system_profile_renamed . insert ( std :: pair < std :: string , std :: string > ( renamed_from , preset . name ));
if ( ! success )
BOOST_LOG_TRIVIAL ( error ) << boost :: format ( "Preset name \" %1% \" was marked as renamed from \" %2% \" , though preset name \" %3% \" was marked as renamed from \" %2% \" as well." ) % preset . name % renamed_from % it -> second ;
}
}
2017-10-25 12:53:31 +02:00
std :: string PresetCollection :: name () const
{
switch ( this -> type ()) {
2019-01-21 12:34:28 +01:00
case Preset :: TYPE_PRINT : return L ( "print" );
case Preset :: TYPE_FILAMENT : return L ( "filament" );
case Preset :: TYPE_SLA_PRINT : return L ( "SLA print" );
2019-01-31 15:09:16 +01:00
case Preset :: TYPE_SLA_MATERIAL : return L ( "SLA material" );
2019-01-21 12:34:28 +01:00
case Preset :: TYPE_PRINTER : return L ( "printer" );
2018-12-04 17:56:49 +01:00
default : return "invalid" ;
2017-10-25 12:53:31 +02:00
}
}
2019-02-03 20:17:55 +01:00
std :: string PresetCollection :: section_name () const
{
switch ( this -> type ()) {
case Preset :: TYPE_PRINT : return "print" ;
case Preset :: TYPE_FILAMENT : return "filament" ;
case Preset :: TYPE_SLA_PRINT : return "sla_print" ;
case Preset :: TYPE_SLA_MATERIAL : return "sla_material" ;
case Preset :: TYPE_PRINTER : return "printer" ;
default : return "invalid" ;
}
}
2019-01-08 10:52:11 +01:00
std :: vector < std :: string > PresetCollection :: system_preset_names () const
{
size_t num = 0 ;
for ( const Preset & preset : m_presets )
if ( preset . is_system )
++ num ;
std :: vector < std :: string > out ;
out . reserve ( num );
2019-08-06 16:51:32 +02:00
for ( const Preset & preset : m_presets )
if ( preset . is_system )
out . emplace_back ( preset . name );
2019-01-08 10:52:11 +01:00
std :: sort ( out . begin (), out . end ());
return out ;
}
2017-12-20 11:28:16 +01:00
// Generate a file path from a profile name. Add the ".ini" suffix if it is missing.
std :: string PresetCollection :: path_from_name ( const std :: string & new_name ) const
{
2019-08-06 16:51:32 +02:00
std :: string file_name = boost :: iends_with ( new_name , ".ini" ) ? new_name : ( new_name + ".ini" );
2017-12-20 11:28:16 +01:00
return ( boost :: filesystem :: path ( m_dir_path ) / file_name ). make_preferred (). string ();
}
2019-04-10 09:56:32 +02:00
void PresetCollection :: clear_bitmap_cache ()
{
m_bitmap_cache -> clear ();
}
2019-03-22 17:07:12 +01:00
wxString PresetCollection :: separator ( const std :: string & label )
{
2019-08-06 16:51:32 +02:00
return wxString :: FromUTF8 ( PresetCollection :: separator_head ()) + _ ( label ) + wxString :: FromUTF8 ( PresetCollection :: separator_tail ());
2019-03-22 17:07:12 +01:00
}
2019-08-06 16:51:32 +02:00
const Preset & PrinterPresetCollection :: default_preset_for ( const DynamicPrintConfig & config ) const
{
2018-10-31 16:22:36 +01:00
const ConfigOptionEnumGeneric * opt_printer_technology = config . opt < ConfigOptionEnumGeneric > ( "printer_technology" );
2019-08-06 16:51:32 +02:00
return this -> default_preset (( opt_printer_technology == nullptr || opt_printer_technology -> value == ptFFF ) ? 0 : 1 );
2018-10-31 16:22:36 +01:00
}
2019-04-26 16:53:04 +02:00
const Preset * PrinterPresetCollection :: find_by_model_id ( const std :: string & model_id ) const
{
if ( model_id . empty ()) { return nullptr ; }
const auto it = std :: find_if ( cbegin (), cend (), [ & ]( const Preset & preset ) {
return preset . config . opt_string ( "printer_model" ) == model_id ;
});
return it != cend () ? &* it : nullptr ;
}
2017-09-19 13:55:48 +02:00
} // namespace Slic3r