From 5a99e694cee58b35183ff1fa9300576883e0373b Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 26 Oct 2017 17:17:39 +0200 Subject: [PATCH] Another step towards the C++ presets. --- lib/Slic3r/GUI.pm | 32 +---- lib/Slic3r/GUI/MainFrame.pm | 114 +++++----------- lib/Slic3r/GUI/Plater.pm | 197 +++++++-------------------- lib/Slic3r/GUI/Preferences.pm | 3 +- lib/Slic3r/GUI/Tab.pm | 223 +++++++++++++++--------------- xs/src/libslic3r/Utils.hpp | 3 + xs/src/libslic3r/utils.cpp | 3 +- xs/src/slic3r/GUI/Preset.cpp | 247 ++++++++++++++++++++++------------ xs/src/slic3r/GUI/Preset.hpp | 42 +++--- xs/xsp/GUI_Preset.xsp | 43 ++++-- xs/xsp/XS.xsp | 7 +- 11 files changed, 431 insertions(+), 483 deletions(-) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index ca88f2e7d..a75b0d83b 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -81,8 +81,6 @@ our $medium_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); $medium_font->SetPointSize(12); our $grey = Wx::Colour->new(200,200,200); -#our $VERSION_CHECK_EVENT : shared = Wx::NewEventType; - sub OnInit { my ($self) = @_; @@ -123,6 +121,8 @@ sub OnInit { $Settings->{_}{version} = $Slic3r::VERSION; $self->save_settings; + # Suppress the '- default -' presets. + $self->{preset_bundle}->set_default_suppressed($Slic3r::GUI::Settings->{_}{no_defaults} ? 1 : 0); eval { $self->{preset_bundle}->load_presets(Slic3r::data_dir) }; # application frame @@ -169,43 +169,18 @@ sub OnInit { $self->{mainframe}->config_wizard; eval { $self->{preset_bundle}->load_presets(Slic3r::data_dir) }; } - -# $self->check_version -# if $self->have_version_check -# && ($Settings->{_}{version_check} // 1) -# && (!$Settings->{_}{last_version_check} || (time - $Settings->{_}{last_version_check}) >= 86400); - + EVT_IDLE($frame, sub { while (my $cb = shift @cb) { $cb->(); } }); -# EVT_COMMAND($self, -1, $VERSION_CHECK_EVENT, sub { -# my ($self, $event) = @_; -# my ($success, $response, $manual_check) = @{$event->GetData}; -# -# if ($success) { -# if ($response =~ /^obsolete ?= ?([a-z0-9.-]+,)*\Q$Slic3r::VERSION\E(?:,|$)/) { -# my $res = Wx::MessageDialog->new(undef, "A new version is available. Do you want to open the Slic3r website now?", -# 'Update', wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_INFORMATION | wxICON_ERROR)->ShowModal; -# Wx::LaunchDefaultBrowser('http://slic3r.org/') if $res == wxID_YES; -# } else { -# Slic3r::GUI::show_info(undef, "You're using the latest version. No updates are available.") if $manual_check; -# } -# $Settings->{_}{last_version_check} = time(); -# $self->save_settings; -# } else { -# Slic3r::GUI::show_error(undef, "Failed to check for updates. Try later.") if $manual_check; -# } -# }); - return 1; } sub about { my ($self) = @_; - my $about = Slic3r::GUI::AboutDialog->new(undef); $about->ShowModal; $about->Destroy; @@ -213,7 +188,6 @@ sub about { sub system_info { my ($self) = @_; - my $slic3r_info = Slic3r::slic3r_info(format => 'html'); my $copyright_info = Slic3r::copyright_info(format => 'html'); my $system_info = Slic3r::system_info(format => 'html'); diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 554c9fbf5..3847fe4c3 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -133,14 +133,16 @@ sub _init_tabpanel { } } }); - $tab->load_presets; + # Load the currently selected preset into the GUI, update the preset selection box. + $tab->load_current_preset; $panel->AddPage($tab, $tab->title); } if ($self->{plater}) { $self->{plater}->on_select_preset(sub { - my ($group, $i) = @_; - $self->{options_tabs}{$group}->select_preset($i); + my ($group, $name) = @_; + print "MainFrame::on_select_preset callback, group: $group, name: $name\n"; + $self->{options_tabs}{$group}->select_preset($name); }); # load initial config @@ -503,19 +505,19 @@ sub extra_variables { sub export_config { my $self = shift; - - my $config = $self->config; - eval { - # validate configuration - $config->validate; - }; + # Generate a cummulative configuration for the selected print, filaments and printer. + my $config = wxTheApp->{preset_bundle}->full_config(); + # Validate the cummulative configuration. + eval { $config->validate; }; Slic3r::GUI::catch_error($self) and return; - + # Ask user for the file name for the config file. my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; my $filename = $last_config ? basename($last_config) : "config.ini"; my $dlg = Wx::FileDialog->new($self, 'Save configuration as:', $dir, $filename, &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - if ($dlg->ShowModal == wxID_OK) { + my $file = ($dlg->ShowModal == wxID_OK) ? $dlg->GetPath : undef; + $dlg->Destroy; + if (defined $file) { my $file = $dlg->GetPath; $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); wxTheApp->save_settings; @@ -537,10 +539,10 @@ sub load_config_file { $file = $dlg->GetPaths; $dlg->Destroy; } - for my $tab (values %{$self->{options_tabs}}) { - # Dont proceed further if the config file cannot be loaded. - return undef if ! $tab->load_config_file($file); - } + eval { wxTheApp->{preset_bundle}->load_config_file($file); }; + # Dont proceed further if the config file cannot be loaded. + return if Slic3r::GUI::catch_error($self); + $_->load_current_preset for (values %{$self->{options_tabs}}); $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); wxTheApp->save_settings; $last_config = $file; @@ -548,38 +550,23 @@ sub load_config_file { sub export_configbundle { my $self = shift; - - eval { - # validate current configuration in case it's dirty - $self->config->validate; - }; + # validate current configuration in case it's dirty + eval { $self->config->validate; }; Slic3r::GUI::catch_error($self) and return; - + # Ask user for a file name. my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; my $filename = "Slic3r_config_bundle.ini"; my $dlg = Wx::FileDialog->new($self, 'Save presets bundle as:', $dir, $filename, &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - if ($dlg->ShowModal == wxID_OK) { - my $file = $dlg->GetPath; + my $file = ($dlg->ShowModal == wxID_OK) ? $dlg->GetPath : undef; + $dlg->Destroy; + if (defined $file) { + # Export the config bundle. $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); wxTheApp->save_settings; - - # leave default category empty to prevent the bundle from being parsed as a normal config file - my $ini = { _ => {} }; - $ini->{settings}{$_} = $Slic3r::GUI::Settings->{_}{$_} for qw(autocenter); - $ini->{presets} = $Slic3r::GUI::Settings->{presets}; - - foreach my $section (qw(print filament printer)) { - my $presets = wxTheApp->{preset_bundle}->$section->presets_hash; - foreach my $preset_name (keys %{$presets}) { - my $config = Slic3r::Config->load($presets->{$preset_name}); - $ini->{"$section:$preset_name"} = $config->as_ini->{_}; - } - } - - Slic3r::Config->write_ini($file, $ini); + eval { $self->{presets}->export_configbundle($file); }; + Slic3r::GUI::catch_error($self) and return; } - $dlg->Destroy; } sub load_configbundle { @@ -596,46 +583,17 @@ sub load_configbundle { $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); wxTheApp->save_settings; - - # load .ini file - my $ini = Slic3r::Config->read_ini($file); - - if ($ini->{settings}) { - $Slic3r::GUI::Settings->{_}{$_} = $ini->{settings}{$_} for keys %{$ini->{settings}}; - wxTheApp->save_settings; - } - if ($ini->{presets}) { - $Slic3r::GUI::Settings->{presets} = $ini->{presets}; - wxTheApp->save_settings; - } - my $imported = 0; - INI_BLOCK: foreach my $ini_category (sort keys %$ini) { - next unless $ini_category =~ /^(print|filament|printer):(.+)$/; - my ($section, $preset_name) = ($1, $2); - my $config = Slic3r::Config->load_ini_hash($ini->{$ini_category}); - next if $skip_no_id && !$config->get($section . "_settings_id"); - - { - my $current_presets = wxTheApp->{preset_bundle}->$section->presets_hash; - my %current_ids = map { $_ => 1 } - grep $_, - map Slic3r::Config->load($_)->get($section . "_settings_id"), - values %{$current_presets}; - next INI_BLOCK if exists $current_ids{$config->get($section . "_settings_id")}; - } - - $config->save(sprintf Slic3r::data_dir . "/%s/%s.ini", $section, $preset_name); - Slic3r::debugf "Imported %s preset %s\n", $section, $preset_name; - $imported++; - } + my $presets_imported = 0; + eval { $presets_imported = $self->{presets}->load_configbundle($file); }; + Slic3r::GUI::catch_error($self) and return; + + # Load the currently selected preset into the GUI, update the preset selection box. foreach my $tab (values %{$self->{options_tabs}}) { - $tab->load_presets; + $tab->load_current_preset; } - return if !$imported; - - my $message = sprintf "%d presets successfully imported.", $imported; + my $message = sprintf "%d presets successfully imported.", $presets_imported; Slic3r::GUI::show_info($self, $message); } @@ -669,12 +627,6 @@ sub config_wizard { } } -sub filament_preset_names { - my ($self) = @_; - return map $self->{options_tabs}{filament}->{presets}->preset($_)->name, - $self->{plater}->filament_presets; -} - # This is called when closing the application, when loading a config file or when starting the config wizard # to notify the user whether he is aware that some preset changes will be lost. sub check_unsaved_changes { diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index d8a9ec7ba..827170a8b 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -366,20 +366,17 @@ sub new { # once a printer preset with multiple extruders is activated. # $self->{preset_choosers}{$group}[$idx] $self->{preset_choosers} = {}; - # Boolean indicating whether the '- default -' preset is shown by the combo box. - $self->{preset_choosers_default_suppressed} = {}; for my $group (qw(print filament printer)) { my $text = Wx::StaticText->new($self, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); $text->SetFont($Slic3r::GUI::small_font); my $choice = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY); EVT_LEFT_DOWN($choice, sub { $self->filament_color_box_lmouse_down(0, @_); } ); $self->{preset_choosers}{$group} = [$choice]; - $self->{preset_choosers_default_suppressed}{$group} = 0; # setup the listener EVT_COMBOBOX($choice, $choice, sub { my ($choice) = @_; wxTheApp->CallAfter(sub { - $self->_on_select_preset($group, $choice); + $self->_on_select_preset($group, $choice, 0); }); }); $presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); @@ -504,28 +501,23 @@ sub on_select_preset { $self->{on_select_preset} = $cb; } +# Called from the platter combo boxes selecting the active print, filament or printer. sub _on_select_preset { - my $self = shift; - my ($group, $choice) = @_; - + my ($self, $group, $choice, $idx) = @_; # If user changed a filament preset and the selected machine is equipped with multiple extruders, # there are multiple filament selection combo boxes shown at the platter. In that case # don't propagate the filament selection changes to the tab. - my $default_suppressed = $self->{preset_choosers_default_suppressed}{$group}; + if ($group eq 'filament') { + wxTheApp->{preset_bundle}->set_filament_preset($idx, $choice->GetStringSelection); + } if ($group eq 'filament' && @{$self->{preset_choosers}{filament}} > 1) { - # Indices of the filaments selected. - my @filament_presets = $self->filament_presets; - $Slic3r::GUI::Settings->{presets}{filament} = $choice->GetString($filament_presets[0] - $default_suppressed) . ".ini"; - $Slic3r::GUI::Settings->{presets}{"filament_${_}"} = $choice->GetString($filament_presets[$_] - $default_suppressed) - for 1 .. $#filament_presets; wxTheApp->save_settings; - $self->update_filament_colors_preview($choice); + wxTheApp->{preset_bundle}->update_platter_filament_ui_colors($idx, $choice); } else { # call GetSelection() in scalar context as it's context-aware - $self->{on_select_preset}->($group, scalar($choice->GetSelection) + $default_suppressed) + $self->{on_select_preset}->($group, $choice->GetStringSelection) if $self->{on_select_preset}; } - # get new config and generate on_config_change() event for updating plater and other things $self->on_config_change(wxTheApp->{preset_bundle}->full_config); } @@ -572,127 +564,31 @@ sub update_ui_from_settings # For Print settings and Printer, synchronize the selection index with their tabs. # For Filament, synchronize the selection index for a single extruder printer only, otherwise keep the selection. sub update_presets { - # $presets: one of qw(print filament printer) - # $selected: index of the selected preset in the array. This may not correspond - # with the index of selection in the UI element, where not all items are displayed. - my ($self, $group, $presets, $default_suppressed, $selected, $is_dirty) = @_; - - print "Plater.pm update_presets presets: $presets\n"; -# my @choosers = @{ $self->{preset_choosers}{$group} }; -# my $choice_idx = 0; -# foreach my $choice (@choosers) { -# if ($group eq 'filament' && @choosers > 1) { - # if we have more than one filament chooser, keep our selection - # instead of importing the one from the tab -# $selected = $choice->GetSelection + $self->{preset_choosers_default_suppressed}{$group}; -# $is_dirty = 0; -# } -# $choice->Clear; -# foreach my $preset (@{$presets}) { -# print "Prset of $presets: $preset\n"; -# next if ($preset->default && $default_suppressed); -# my $bitmap; -# if ($group eq 'filament') { -# $bitmap = Wx::Bitmap->new(Slic3r::var("spool.png"), wxBITMAP_TYPE_PNG); -# } elsif ($group eq 'print') { -# $bitmap = Wx::Bitmap->new(Slic3r::var("cog.png"), wxBITMAP_TYPE_PNG); -# } elsif ($group eq 'printer') { -# $bitmap = Wx::Bitmap->new(Slic3r::var("printer_empty.png"), wxBITMAP_TYPE_PNG); -# } -# $choice->AppendString($preset->name, $bitmap); -# } - -# if ($selected <= $#$presets) { -# my $idx = $selected - $default_suppressed; -# if ($idx >= 0) { -# if ($is_dirty) { -# $choice->SetString($idx, $choice->GetString($idx) . " (modified)"); -# } -# # call SetSelection() only after SetString() otherwise the new string -# # won't be picked up as the visible string -# $choice->SetSelection($idx); -# } -# } -# $choice_idx += 1; -# } - -# $self->{preset_choosers_default_suppressed}{$group} = $default_suppressed; - - wxTheApp->CallAfter(sub { $self->update_filament_colors_preview }) if $group eq 'filament' || $group eq 'printer'; -} - -# Update the color icon in front of each filament selection on the platter. -# If the extruder has a preview color assigned, apply the extruder color to the active selection. -# Always apply the filament color to the non-active selections. -sub update_filament_colors_preview { - my ($self, $extruder_idx) = shift; - -# my @choosers = @{$self->{preset_choosers}{filament}}; - -# if (ref $extruder_idx) { - # $extruder_idx is the chooser. -# foreach my $chooser (@choosers) { -# if ($extruder_idx == $chooser) { -# $extruder_idx = $chooser; -# last; -# } -# } -# } - -# my @extruder_colors = @{$self->{config}->extruder_colour}; - -# my @extruder_list; -# if (defined $extruder_idx) { -# @extruder_list = ($extruder_idx); -# } else { - # Collect extruder indices. -# @extruder_list = (0..$#extruder_colors); -# } - -# my $filament_tab = $self->GetFrame->{options_tabs}{filament}; -# my $presets = $filament_tab->{presets}; -# my $default_suppressed = $filament_tab->{default_suppressed}; - -# foreach my $extruder_idx (@extruder_list) { -# my $chooser = $choosers[$extruder_idx]; -# my $extruder_color = $self->{config}->extruder_colour->[$extruder_idx]; -# my $preset_idx = 0; -# my $selection_idx = $chooser->GetSelection; -# foreach my $preset (@$presets) { -# my $bitmap; -# if ($preset->default) { -# next if $default_suppressed; -# } else { - # Assign an extruder color to the selected item if the extruder color is defined. -# my $filament_colour_cfg = $preset->config_ref->filament_colour; -# print $filament_colour_cfg . "\n"; -# my $filament_rgb = $filament_colour_cfg->[0]; -# my $extruder_rgb = ($preset_idx == $selection_idx && $extruder_color =~ m/^#[[:xdigit:]]{6}/) ? $extruder_color : $filament_rgb; -# $filament_rgb =~ s/^#//; -# $extruder_rgb =~ s/^#//; -# my $image = Wx::Image->new(24,16); -# if ($filament_rgb ne $extruder_rgb) { -# my @rgb = unpack 'C*', pack 'H*', $extruder_rgb; -# $image->SetRGB(Wx::Rect->new(0,0,16,16), @rgb); -# @rgb = unpack 'C*', pack 'H*', $filament_rgb; -# $image->SetRGB(Wx::Rect->new(16,0,8,16), @rgb); -# } else { -# my @rgb = unpack 'C*', pack 'H*', $filament_rgb; -# $image->SetRGB(Wx::Rect->new(0,0,24,16), @rgb); -# } -# $bitmap = Wx::Bitmap->new($image); -# } -# $chooser->SetItemBitmap($preset_idx, $bitmap) if $bitmap; -# $preset_idx += 1; -# } -# } -} - -# Return a vector of indices of filaments selected by the $self->{preset_choosers}{filament} combo boxes. -sub filament_presets { - my $self = shift; - # force scalar context for GetSelection() as it's context-aware - return map scalar($_->GetSelection) + $self->{preset_choosers_default_suppressed}{filament}, @{ $self->{preset_choosers}{filament} }; + # $group: one of qw(print filament printer) + # $presets: PresetCollection + my ($self, $group, $presets) = @_; + print "Platter::update_presets\n"; + my @choosers = @{$self->{preset_choosers}{$group}}; + if ($group eq 'filament') { + my $choice_idx = 0; + if (int(@choosers) == 1) { + # Single filament printer, synchronize the filament presets. + wxTheApp->{preset_bundle}->set_filament_preset(0, wxTheApp->{preset_bundle}->filament->get_selected_preset->name); + } + foreach my $choice (@choosers) { + wxTheApp->{preset_bundle}->update_platter_filament_ui($choice_idx, $choice); + $choice_idx += 1; + } + } elsif ($group eq 'print') { + wxTheApp->{preset_bundle}->print->update_platter_ui($choosers[0]); + } elsif ($group eq 'printer') { + wxTheApp->{preset_bundle}->printer->update_platter_ui($choosers[0]); + my $choice_idx = 0; + foreach my $choice (@{$self->{preset_choosers}{filament}}) { + wxTheApp->{preset_bundle}->update_platter_filament_ui_colors($choice_idx, $choice); + $choice_idx += 1; + } + } } sub add { @@ -1557,7 +1453,7 @@ sub do_print { my $printer_panel = $controller->add_printer($printer_preset->name, $printer_preset->config); my $filament_stats = $self->{print}->filament_stats; - my @filament_names = $self->GetFrame->filament_preset_names; + my @filament_names = wxTheApp->{preset_bundle}->filament_presets; $filament_stats = { map { $filament_names[$_] => $filament_stats->{$_} } keys %$filament_stats }; $printer_panel->load_print_job($self->{print_file}, $filament_stats); @@ -1713,11 +1609,18 @@ sub update { } # When a number of extruders changes, the UI needs to be updated to show a single filament selection combo box per extruder. +# Also the wxTheApp->{preset_bundle}->filament_presets needs to be resized accordingly +# and some reasonable default has to be selected for the additional extruders. sub on_extruders_change { my ($self, $num_extruders) = @_; my $choices = $self->{preset_choosers}{filament}; - while (@$choices < $num_extruders) { + print "on_extruders_change1 $num_extruders, current choices: " . int(@$choices) . "\n"; + wxTheApp->{preset_bundle}->update_multi_material_filament_presets; + print "on_extruders_change2 $num_extruders, current choices: " . int(@$choices) . "\n"; + + while (int(@$choices) < $num_extruders) { + print "Adding an extruder selection combo box\n"; # copy strings from first choice my @presets = $choices->[0]->GetStrings; @@ -1726,25 +1629,20 @@ sub on_extruders_change { my $extruder_idx = scalar @$choices; EVT_LEFT_DOWN($choice, sub { $self->filament_color_box_lmouse_down($extruder_idx, @_); } ); push @$choices, $choice; - # copy icons from first choice $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets; - # insert new choice into sizer $self->{presets_sizer}->Insert(4 + ($#$choices-1)*2, 0, 0); $self->{presets_sizer}->Insert(5 + ($#$choices-1)*2, $choice, 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING); - # setup the listener EVT_COMBOBOX($choice, $choice, sub { my ($choice) = @_; wxTheApp->CallAfter(sub { - $self->_on_select_preset('filament', $choice); + $self->_on_select_preset('filament', $choice, $extruder_idx); }); }); - # initialize selection - my $i = first { $choice->GetString($_) eq ($Slic3r::GUI::Settings->{presets}{"filament_" . $#$choices} || '') } 0 .. $#presets; - $choice->SetSelection($i || 0); + wxTheApp->{preset_bundle}->update_platter_filament_ui($extruder_idx, $choice); } # remove unused choices if any @@ -1758,8 +1656,7 @@ sub on_extruders_change { } sub on_config_change { - my $self = shift; - my ($config) = @_; + my ($self, $config) = @_; my $update_scheduled; foreach my $opt_key (@{$self->{config}->diff($config)}) { @@ -1864,7 +1761,7 @@ sub filament_color_box_lmouse_down $colors->[$extruder_idx] = $dialog->GetColourData->GetColour->GetAsString(wxC2S_HTML_SYNTAX); $cfg->set('extruder_colour', $colors); $self->GetFrame->{options_tabs}{printer}->load_config($cfg); - $self->update_filament_colors_preview($extruder_idx); + wxTheApp->{preset_bundle}->update_platter_filament_ui_colors($extruder_idx, $combobox); } $dialog->Destroy(); } @@ -1964,9 +1861,9 @@ sub object_list_changed { for grep $self->{"btn_$_"}, qw(reslice export_gcode print send_gcode); } +# Selection of an active 3D object changed. sub selection_changed { my $self = shift; - my ($obj_idx, $object) = $self->selected_object; my $have_sel = defined $obj_idx; diff --git a/lib/Slic3r/GUI/Preferences.pm b/lib/Slic3r/GUI/Preferences.pm index b19d623cd..4b6c665b7 100644 --- a/lib/Slic3r/GUI/Preferences.pm +++ b/lib/Slic3r/GUI/Preferences.pm @@ -81,7 +81,8 @@ sub new { sub _accept { my $self = shift; - if (defined($self->{values}{no_controller})) { + if (defined($self->{values}{no_controller}) || + defined($self->{values}{no_defaults})) { Slic3r::GUI::warning_catcher($self)->("You need to restart Slic3r to make the changes effective."); } diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 6de0985ac..34fa9f44b 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -68,8 +68,11 @@ sub new { $self->{treectrl} = Wx::TreeCtrl->new($self, -1, wxDefaultPosition, [185, -1], wxTR_NO_BUTTONS | wxTR_HIDE_ROOT | wxTR_SINGLE | wxTR_NO_LINES | wxBORDER_SUNKEN | wxWANTS_CHARS); $left_sizer->Add($self->{treectrl}, 1, wxEXPAND); $self->{icons} = Wx::ImageList->new(16, 16, 1); + # Map from an icon file name to its index in $self->{icons}. + $self->{icon_index} = {}; + # Index of the last icon inserted into $self->{icons}. + $self->{icon_count} = -1; $self->{treectrl}->AssignImageList($self->{icons}); - $self->{iconcount} = -1; $self->{treectrl}->AddRoot("root"); $self->{pages} = []; $self->{treectrl}->SetIndent(0); @@ -94,7 +97,6 @@ sub new { EVT_CHOICE($parent, $self->{presets_choice}, sub { $self->select_preset($self->{presets_choice}->GetStringSelection); - $self->_on_presets_changed; }); EVT_BUTTON($self, $self->{btn_save_preset}, sub { $self->save_preset }); @@ -103,17 +105,17 @@ sub new { # Initialize the DynamicPrintConfig by default keys/values. # Possible %params keys: no_controller $self->build(%params); - $self->update_tree; + $self->rebuild_page_tree; $self->_update; return $self; } -# Are the '- default -' selections suppressed by the Slic3r GUI preferences? -sub no_defaults { - return $Slic3r::GUI::Settings->{_}{no_defaults} ? 1 : 0; -} - +# Save the current preset into file. +# This removes the "dirty" flag of the preset, possibly creates a new preset under a new name, +# and activates the new preset. +# Wizard calls save_preset with a name "My Settings", otherwise no name is provided and this method +# opens a Slic3r::GUI::SavePresetWindow dialog. sub save_preset { my ($self, $name) = @_; @@ -127,18 +129,25 @@ sub save_preset { my $default_name = $preset->default ? 'Untitled' : $preset->name; $default_name =~ s/\.[iI][nN][iI]$//; + my @prsts = @{$self->{presets}}; + print "Num of presets: ". int(@prsts) . "\n"; + for my $pr (@prsts) { + print "Name: " . $pr->name . " default " . $pr->default . "\n"; + } my $dlg = Slic3r::GUI::SavePresetWindow->new($self, title => lc($self->title), default => $default_name, values => [ map $_->name, grep !$_->default && !$_->external, @{$self->{presets}} ], ); return unless $dlg->ShowModal == wxID_OK; - $name = Slic3r::normalize_utf8_nfc($dlg->get_name); + $name = $dlg->get_name; } - - $self->{config}->save(sprintf Slic3r::data_dir . "/%s/%s.ini", $self->name, $name); - $self->load_presets; - $self->select_preset($name); + # Save the preset into Slic3r::data_dir/section_name/preset_name.ini + eval { $self->{presets}->save_current_preset($name); }; + Slic3r::GUI::catch_error($self) and return; + # Add the new item into the UI component, remove dirty flags and activate the saved item. + $self->{presets}->update_tab_ui($self->{presets_choice}); + # Update the selection boxes at the platter. $self->_on_presets_changed; } @@ -147,29 +156,35 @@ sub delete_preset { my ($self) = @_; my $current_preset = $self->{presets}->get_selected_preset; # Don't let the user delete the '- default -' configuration. - return if $current_preset->{default} || - wxID_YES == Wx::MessageDialog->new($self, "Are you sure you want to delete the selected preset?", 'Delete Preset', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION)->ShowModal; + return if $current_preset->default || + wxID_YES != Wx::MessageDialog->new($self, "Are you sure you want to delete the selected preset?", 'Delete Preset', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION)->ShowModal; # Delete the file and select some other reasonable preset. - eval { $self->{presets}->delete_file($current_preset->name); }; + # The 'external' presets will only be removed from the preset list, their files will not be deleted. + eval { $self->{presets}->delete_current_preset; }; Slic3r::GUI::catch_error($self) and return; - # Delete the item from the UI component. - $self->{presets}->update_platter_ui($self->{presets_choice}); + # Delete the item from the UI component and activate another preset. + $self->{presets}->update_tab_ui($self->{presets_choice}); + # Update the selection boxes at the patter. $self->_on_presets_changed; } +# Register the on_value_change callback. sub on_value_change { my ($self, $cb) = @_; $self->{on_value_change} = $cb; } +# Register the on_presets_changed callback. sub on_presets_changed { my ($self, $cb) = @_; $self->{on_presets_changed} = $cb; } # This method is called whenever an option field is changed by the user. -# Propagate event to the parent through the 'on_value_changed' callback +# Propagate event to the parent through the 'on_value_change' callback # and call _update. +# The on_value_change callback triggers Platter::on_config_change() to configure the 3D preview +# (colors, wipe tower positon etc) and to restart the background slicing process. sub _on_value_change { my ($self, $key, $value) = @_; $self->{on_value_change}->($key, $value) if $self->{on_value_change}; @@ -178,27 +193,28 @@ sub _on_value_change { # Override this to capture changes of configuration caused either by loading or switching a preset, # or by a user changing an option field. +# This callback is useful for cross-validating configuration values of a single preset. sub _update {} -# Call a callback to update the selection of presets on the platter. +# Call a callback to update the selection of presets on the platter: +# To update the content of the selection boxes, +# to update the filament colors of the selection boxes, +# to update the "dirty" flags of the selection boxes, +# to uddate number of "filament" selection boxes when the number of extruders change. sub _on_presets_changed { my ($self) = @_; - $self->{on_presets_changed}->( - $self->{presets}, - $self->{default_suppressed}, - scalar($self->{presets_choice}->GetSelection) + $self->{default_suppressed}, - $self->{presets}->current_is_dirty, - ) if $self->{on_presets_changed}; + print "Tab::_on_presets_changed\n"; + $self->{on_presets_changed}->($self->{presets}) if $self->{on_presets_changed}; } +# For the printer profile, generate the extruder pages after a preset is loaded. sub on_preset_loaded {} -# Called by the UI combo box when the user switches profiles. -# Select a preset by a name. If ! defined(name), then the first visible preset is selected. -# If the current profile is modified, user is asked to save the changes. -sub select_preset { - my ($self, $name) = @_; - +# If the current preset is dirty, the user is asked whether the changes may be discarded. +# if the current preset was not dirty, or the user agreed to discard the changes, 1 is returned. +sub may_discard_current_preset_if_dirty +{ + my ($self) = @_; if ($self->{presets}->current_is_dirty) { # Display a dialog showing the dirty options in a human readable form. my $old_preset = $self->{presets}->get_current_preset; @@ -215,37 +231,52 @@ sub select_preset { my $changes = join "\n", map "- $_", @option_names; my $confirm = Wx::MessageDialog->new($self, "$name has unsaved changes:\n$changes\n\nDiscard changes and continue anyway?", 'Unsaved Changes', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); - if ($confirm->ShowModal == wxID_NO) { - $self->{presets}->update_platter_ui($self->{presets_choice}); - # Trigger the on_presets_changed event so that we also restore the previous value in the plater selector. - $self->_on_presets_changed; - return; - } + return 0 if $confirm->ShowModal == wxID_NO; } + return 1; +} - $self->{presets}->select_by_name_ui(defined $name ? $name : "", $self->{presets_choice}); +# Called by the UI combo box when the user switches profiles. +# Select a preset by a name. If ! defined(name), then the first visible preset is selected. +# If the current profile is modified, user is asked to save the changes. +sub select_preset { + my ($self, $name, $force) = @_; + print "select_preset 1\n"; + if (! $self->may_discard_current_preset_if_dirty) { + $self->{presets}->update_tab_ui($self->{presets_choice}); + # Trigger the on_presets_changed event so that we also restore the previous value in the plater selector. + $self->_on_presets_changed; + return; + } + print "select_preset 2\n"; + $self->{presets}->select_preset_by_name(defined $name ? $name : ""); + print "select_preset 3\n"; + # Initialize the UI from the current preset. + $self->load_current_preset; + print "select_preset 4\n"; + # Save the current application settings with the newly selected preset name. + wxTheApp->save_settings; + print "select_preset 5\n"; + +} + +# Initialize the UI from the current preset. +sub load_current_preset { + my ($self) = @_; + print "load_current_preset 1\n"; + $self->{presets}->update_tab_ui($self->{presets_choice}); + print "load_current_preset 2\n"; my $preset = $self->{presets}->get_current_preset; - my $preset_config = $preset->config; eval { local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self); - foreach my $opt_key (@{$self->{config}->get_keys}) { - if ($preset_config->has($opt_key) && - $self->{config}->serialize($opt_key) ne $preset_config->serialize($opt_key)) { - $self->{config}->set($opt_key, $preset_config->get($opt_key)); - } - } - ($preset->default || $preset->external) - ? $self->{btn_delete_preset}->Disable - : $self->{btn_delete_preset}->Enable; + my $method = ($preset->default || $preset->external) ? 'Disable' : 'Enable'; + $self->{btn_delete_preset}->$method; $self->_update; # For the printer profile, generate the extruder pages. $self->on_preset_loaded; # Reload preset pages with the new configuration values. - $self->reload_config; - # Use this preset the next time Slic3r starts. - $Slic3r::GUI::Settings->{presets}{$self->name} = $preset->file ? basename($preset->file) : ''; + $self->_reload_config; }; - # use CallAfter because some field triggers schedule on_change calls using CallAfter, # and we don't want them to be called after this update_dirty() as they would mark the # preset dirty again @@ -254,22 +285,25 @@ sub select_preset { $self->_on_presets_changed; $self->update_dirty; }); - - # Save the current application settings with the newly selected preset name. - wxTheApp->save_settings; } sub add_options_page { - my $self = shift; - my ($title, $icon, %params) = @_; - + my ($self, $title, $icon, %params) = @_; + # Index of $icon in an icon list $self->{icons}. + my $icon_idx = 0; if ($icon) { - my $bitmap = Wx::Bitmap->new(Slic3r::var($icon), wxBITMAP_TYPE_PNG); - $self->{icons}->Add($bitmap); - $self->{iconcount}++; + $icon_idx = $self->{icon_index}->{$icon}; + if (! defined $icon_idx) { + # Add a new icon to the icon list. + my $bitmap = Wx::Bitmap->new(Slic3r::var($icon), wxBITMAP_TYPE_PNG); + $self->{icons}->Add($bitmap); + $icon_idx = $self->{icon_count} + 1; + $self->{icon_count} = $icon_idx; + $self->{icon_index}->{$icon} = $icon_idx; + } } - - my $page = Slic3r::GUI::Tab::Page->new($self, $title, $self->{iconcount}); + # Initialize the page. + my $page = Slic3r::GUI::Tab::Page->new($self, $title, $icon_idx); $page->Hide; $self->{hsizer}->Add($page, 1, wxEXPAND | wxLEFT, 5); push @{$self->{pages}}, $page; @@ -277,14 +311,16 @@ sub add_options_page { } # Reload current $self->{config} (aka $self->{presets}->edited_preset->config) into the UI fields. -sub reload_config { +sub _reload_config { my ($self) = @_; $_->reload_config for @{$self->{pages}}; } -sub update_tree { +# Regerenerate content of the page tree. +sub rebuild_page_tree { my ($self) = @_; + print "Tab::rebuild_page_tree " . $self->title . "\n"; # get label of the currently selected item my $selected = $self->{treectrl}->GetItemText($self->{treectrl}->GetSelection); @@ -315,40 +351,6 @@ sub update_dirty { $self->_on_presets_changed; } -# Search all ini files in the presets directory, add them into the list of $self->{presets} in the form of Slic3r::GUI::Tab::Preset. -# Initialize the drop down list box. -sub load_presets { - my ($self) = @_; - - print "Load presets, ui: " . $self->{presets_choice} . "\n"; -# $self->current_preset(undef); -# $self->{presets}->set_default_suppressed(Slic3r::GUI::Tab->no_defaults); -# $self->{presets_choice}->Clear; -# foreach my $preset (@{$self->{presets}}) { -# if ($preset->visible) { -# # Set client data of the choice item to $preset. -# $self->{presets_choice}->Append($preset->name, $preset); -# } -# } -# { -# # load last used preset -# my $i = first { basename($self->{presets}[$_]->file) eq ($Slic3r::GUI::Settings->{presets}{$self->name} || '') } 1 .. $#{$self->{presets}}; -# $self->select_preset($i || $self->{default_suppressed}); -# } - - $self->{presets}->update_platter_ui($self->{presets_choice}); - $self->_on_presets_changed; -} - -# Load a config file containing a Print, Filament & Printer preset. -sub load_config_file { - my ($self, $file) = @_; - $self->{presets}->update_platter_ui($self->{presets_choice}); - $self->select_preset; - $self->_on_presets_changed; - return 1; -} - # Load a provied DynamicConfig into the tab, modifying the active preset. # This could be used for example by setting a Wipe Tower position by interactive manipulation in the 3D view. sub load_config { @@ -362,15 +364,16 @@ sub load_config { if ($modified) { $self->update_dirty; # Initialize UI components with the config values. - $self->reload_config; + $self->_reload_config; $self->_update; } } # Find a field with an index over all pages of this tab. +# This method is used often and everywhere, therefore it shall be quick. sub get_field { my ($self, $opt_key, $opt_index) = @_; - foreach my $page (@{ $self->{pages} }) { + foreach my $page (@{$self->{pages}}) { my $field = $page->get_field($opt_key, $opt_index); return $field if defined $field; } @@ -378,10 +381,12 @@ sub get_field { } # Set a key/value pair on this page. Return true if the value has been modified. +# Currently used for distributing extruders_count over preset pages of Slic3r::GUI::Tab::Printer +# after a preset is loaded. sub set_value { my ($self, $opt_key, $value) = @_; my $changed = 0; - foreach my $page (@{ $self->{pages} }) { + foreach my $page (@{$self->{pages}}) { $changed = 1 if $page->set_value($opt_key, $value); } return $changed; @@ -660,10 +665,10 @@ sub build { } # Reload current $self->{config} (aka $self->{presets}->edited_preset->config) into the UI fields. -sub reload_config { +sub _reload_config { my ($self) = @_; # $self->_reload_compatible_printers_widget; - $self->SUPER::reload_config; + $self->SUPER::_reload_config; } # Slic3r::GUI::Tab::Print::_update is called after a configuration preset is loaded or switched, or when a single option is modifed by the user. @@ -1194,7 +1199,7 @@ sub build { $self->{config}->set('octoprint_host', $value); $self->update_dirty; $self->_on_value_change('octoprint_host', $value); - $self->reload_config; + $self->_reload_config; } } else { Wx::MessageDialog->new($self, 'No Bonjour device found', 'Device Browser', wxOK | wxICON_INFORMATION)->ShowModal; @@ -1421,7 +1426,7 @@ sub _build_extruder_pages { @{$self->{extruder_pages}}[ 0 .. $self->{extruders_count}-1 ], $page_notes ); - $self->update_tree; + $self->rebuild_page_tree; } # Slic3r::GUI::Tab::Printer::_update is called after a configuration preset is loaded or switched, or when a single option is modifed by the user. @@ -1580,6 +1585,8 @@ sub set_value { return $changed; } +# Dialog to select a new file name for a modified preset to be saved. +# Called from Tab::save_preset(). package Slic3r::GUI::SavePresetWindow; use Wx qw(:combobox :dialog :id :misc :sizer); use Wx::Event qw(EVT_BUTTON EVT_TEXT_ENTER); @@ -1612,7 +1619,7 @@ sub new { sub accept { my ($self, $event) = @_; - if (($self->{chosen_name} = $self->{combo}->GetValue)) { + if (($self->{chosen_name} = Slic3r::normalize_utf8_nfc($self->{combo}->GetValue))) { if ($self->{chosen_name} !~ /^[^<>:\/\\|?*\"]+$/) { Slic3r::GUI::show_error($self, "The supplied name is not valid; the following characters are not allowed: <>:/\|?*\""); } elsif ($self->{chosen_name} eq '- default -') { diff --git a/xs/src/libslic3r/Utils.hpp b/xs/src/libslic3r/Utils.hpp index 23dc330a1..d4da25d87 100644 --- a/xs/src/libslic3r/Utils.hpp +++ b/xs/src/libslic3r/Utils.hpp @@ -1,6 +1,8 @@ #ifndef slic3r_Utils_hpp_ #define slic3r_Utils_hpp_ +#include + namespace Slic3r { extern void set_logging_level(unsigned int level); @@ -23,6 +25,7 @@ std::string config_path(const std::string &file_name); // The suffix ".ini" will be added if it is missing in the name. std::string config_path(const std::string §ion, const std::string &name); +extern std::locale locale_utf8; extern std::string encode_path(const char *src); extern std::string decode_path(const char *src); extern std::string normalize_utf8_nfc(const char *src); diff --git a/xs/src/libslic3r/utils.cpp b/xs/src/libslic3r/utils.cpp index 3910a0586..98f709f1e 100644 --- a/xs/src/libslic3r/utils.cpp +++ b/xs/src/libslic3r/utils.cpp @@ -226,9 +226,10 @@ std::string decode_path(const char *src) #endif /* WIN32 */ } +std::locale locale_utf8(boost::locale::generator().generate("")); + std::string normalize_utf8_nfc(const char *src) { - static std::locale locale_utf8(boost::locale::generator().generate("")); return boost::locale::normalize(src, boost::locale::norm_nfc, locale_utf8); } diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 8e1eb93eb..b2c980447 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -5,9 +5,11 @@ #include #include +#include #include #include #include +#include #include #include @@ -25,7 +27,16 @@ namespace Slic3r { +// Suffix to be added to a modified preset name in the combo box. static std::string g_suffix_modified = " (modified)"; +// Remove an optional "(modified)" suffix from a name. +// This converts a UI name to a unique preset identifier. +std::string remove_suffix_modified(const std::string &name) +{ + return boost::algorithm::ends_with(name, g_suffix_modified) ? + name.substr(0, name.size() - g_suffix_modified.size()) : + name; +} // Load keys from a config file or a G-code. // Throw exceptions with reasonable messages if something goes wrong. @@ -117,9 +128,11 @@ PresetCollection::~PresetCollection() // Throws an exception on error. void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir) { + boost::filesystem::path dir = boost::filesystem::canonical(boost::filesystem::path(dir_path) / subdir).make_preferred(); + m_dir_path = dir.string(); m_presets.erase(m_presets.begin()+1, m_presets.end()); t_config_option_keys keys = this->default_preset().config.keys(); - for (auto &file : boost::filesystem::directory_iterator(boost::filesystem::canonical(boost::filesystem::path(dir_path) / subdir).make_preferred())) + for (auto &file : boost::filesystem::directory_iterator(dir)) if (boost::filesystem::is_regular_file(file.status()) && boost::algorithm::iends_with(file.path().filename().string(), ".ini")) { std::string name = file.path().filename().string(); // Remove the .ini suffix. @@ -133,7 +146,9 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri } } - std::sort(m_presets.begin() + 1, m_presets.end(), [](const Preset &p1, const Preset &p2){ return p1.name < p2.name; }); + std::sort(m_presets.begin() + 1, m_presets.end()); + m_presets.front().is_visible = ! m_default_suppressed || m_presets.size() > 1; + this->select_preset(first_visible_idx()); } // Load a preset from an already parsed config file, insert it into the sorted sequence of presets @@ -158,6 +173,50 @@ Preset& PresetCollection::load_preset(const std::string &path, const std::string return preset; } +void PresetCollection::save_current_preset(const std::string &new_name) +{ + Preset key(m_type, new_name, false); + auto it = std::lower_bound(m_presets.begin(), m_presets.end(), key); + if (it != m_presets.end() && it->name == key.name) { + // Preset with the same name found. + Preset &preset = *it; + if (preset.is_default) + // Cannot overwrite the default preset. + return; + // Overwriting an existing preset. + preset.config = std::move(m_edited_preset.config); + m_idx_selected = it - m_presets.begin(); + } else { + // Creating a new preset. + m_idx_selected = m_presets.insert(it, m_edited_preset) - m_presets.begin(); + Preset &preset = m_presets[m_idx_selected]; + std::string file_name = new_name; + if (! boost::iends_with(file_name, ".ini")) + file_name += ".ini"; + preset.name = new_name; + preset.file = (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string(); + } + m_edited_preset = m_presets[m_idx_selected]; + m_presets[m_idx_selected].save(); +} + +void PresetCollection::delete_current_preset() +{ + const Preset &selected = this->get_selected_preset(); + if (selected.is_default || selected.is_external) + return; + // Erase the preset file. + boost::nowide::remove(selected.file.c_str()); + // Remove the preset from the list. + m_presets.erase(m_presets.begin() + m_idx_selected); + // Find the next visible preset. + m_presets.front().is_visible = ! m_default_suppressed || m_presets.size() > 1; + for (; m_idx_selected < m_presets.size() && ! m_presets[m_idx_selected].is_visible; ++ m_idx_selected) ; + if (m_idx_selected == m_presets.size()) + m_idx_selected = this->first_visible_idx(); + m_edited_preset = m_presets[m_idx_selected]; +} + bool PresetCollection::load_bitmap_default(const std::string &file_name) { return m_bitmap_main_frame->LoadFile(wxString::FromUTF8(Slic3r::var(file_name).c_str()), wxBITMAP_TYPE_PNG); @@ -168,8 +227,7 @@ bool PresetCollection::load_bitmap_default(const std::string &file_name) Preset* PresetCollection::find_preset(const std::string &name, bool first_visible_if_not_found) { Preset key(m_type, name, false); - auto it = std::lower_bound(m_presets.begin(), m_presets.end(), key, - [](const Preset &p1, const Preset &p2) { return p1.name < p2.name; } ); + auto it = std::lower_bound(m_presets.begin(), m_presets.end(), key); // Ensure that a temporary copy is returned if the preset found is currently selected. return (it != m_presets.end() && it->name == key.name) ? &this->preset(it - m_presets.begin()) : first_visible_if_not_found ? &this->first_visible() : nullptr; @@ -216,84 +274,31 @@ void PresetCollection::enable_disable_compatible_to_printer(const std::string &a // Update the wxChoice UI component from this list of presets. // Hide the -void PresetCollection::update_editor_ui(wxBitmapComboBox *ui) -{ - if (ui == nullptr) - return; - - size_t n_visible = this->num_visible(); - size_t n_choice = size_t(ui->GetCount()); - std::string name_selected = dynamic_cast(ui)->GetStringSelection().ToUTF8().data(); - if (boost::algorithm::iends_with(name_selected, g_suffix_modified)) - // Remove the g_suffix_modified. - name_selected.erase(name_selected.end() - g_suffix_modified.size(), name_selected.end()); -#if 0 - if (std::abs(int(n_visible) - int(n_choice)) <= 1) { - // The number of items differs by at most one, update the choice. - size_t i_preset = 0; - size_t i_ui = 0; - while (i_preset < presets.size()) { - std::string name_ui = ui->GetString(i_ui).ToUTF8(); - if (boost::algorithm::iends_with(name_ui, g_suffix_modified)) - // Remove the g_suffix_modified. - name_ui.erase(name_ui.end() - g_suffix_modified.size(), name_ui.end()); - while (this->presets[i_preset].name ) - const Preset &preset = this->presets[i_preset]; - if (preset) - } - } else -#endif - { - // Otherwise fill in the list from scratch. - ui->Clear(); - for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++ i) { - const Preset &preset = this->m_presets[i]; - const wxBitmap *bmp = (i == 0 || preset.is_visible) ? m_bitmap_compatible : m_bitmap_incompatible; - ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), (bmp == 0) ? wxNullBitmap : *bmp, (void*)i); - if (name_selected == preset.name) - ui->SetSelection(ui->GetCount() - 1); - } - } -} - void PresetCollection::update_platter_ui(wxBitmapComboBox *ui) { if (ui == nullptr) return; - - size_t n_visible = this->num_visible(); - size_t n_choice = size_t(ui->GetCount()); - if (std::abs(int(n_visible) - int(n_choice)) <= 1) { - // The number of items differs by at most one, update the choice. - } else { - // Otherwise fill in the list from scratch. + // Otherwise fill in the list from scratch. + ui->Clear(); + for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++ i) { + const Preset &preset = this->m_presets[i]; + const wxBitmap *bmp = (i == 0 || preset.is_visible) ? m_bitmap_compatible : m_bitmap_incompatible; + ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), (bmp == 0) ? wxNullBitmap : *bmp, (void*)i); + if (i == m_idx_selected) + ui->SetSelection(ui->GetCount() - 1); } } -void PresetCollection::update_platter_ui(wxChoice *ui) +void PresetCollection::update_tab_ui(wxChoice *ui) { if (ui == nullptr) return; - - size_t n_visible = this->num_visible(); - size_t n_choice = size_t(ui->GetCount()); - if (std::abs(int(n_visible) - int(n_choice)) <= 1) { - // The number of items differs by at most one, update the choice. - } else { - // Otherwise fill in the list from scratch. - } - - std::string name_selected = dynamic_cast(ui)->GetStringSelection().ToUTF8().data(); - if (boost::algorithm::iends_with(name_selected, g_suffix_modified)) - // Remove the g_suffix_modified. - name_selected.erase(name_selected.end() - g_suffix_modified.size(), name_selected.end()); - ui->Clear(); for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++ i) { const Preset &preset = this->m_presets[i]; const wxBitmap *bmp = (i == 0 || preset.is_visible) ? m_bitmap_compatible : m_bitmap_incompatible; ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), (void*)&preset); - if (name_selected == preset.name) + if (i == m_idx_selected) ui->SetSelection(ui->GetCount() - 1); } } @@ -309,9 +314,7 @@ bool PresetCollection::update_dirty_ui(wxItemContainer *ui) // 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(); - std::string preset_name = boost::algorithm::ends_with(old_label, g_suffix_modified) ? - old_label.substr(0, g_suffix_modified.size()) : - old_label; + std::string preset_name = remove_suffix_modified(old_label); const Preset *preset = this->find_preset(preset_name, false); assert(preset != nullptr); std::string new_label = preset->is_dirty ? preset->name + g_suffix_modified : preset->name; @@ -335,12 +338,12 @@ Preset& PresetCollection::select_preset(size_t idx) return m_presets[idx]; } -bool PresetCollection::select_preset_by_name(const std::string &name, bool force) -{ +bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, bool force) +{ + std::string name = remove_suffix_modified(name_w_suffix); // 1) Try to find the preset by its name. Preset key(m_type, name, false); - auto it = std::lower_bound(m_presets.begin(), m_presets.end(), key, - [](const Preset &p1, const Preset &p2) { return p1.name < p2.name; } ); + auto it = std::lower_bound(m_presets.begin(), m_presets.end(), key); size_t idx = 0; if (it != m_presets.end() && it->name == key.name) // Preset found by its name. @@ -419,6 +422,7 @@ void PresetBundle::load_presets(const std::string &dir_path) this->prints .load_presets(dir_path, "print"); this->filaments.load_presets(dir_path, "filament"); this->printers .load_presets(dir_path, "printer"); + this->update_multi_material_filament_presets(); } bool PresetBundle::load_compatible_bitmaps(const std::string &path_bitmap_compatible, const std::string &path_bitmap_incompatible) @@ -567,8 +571,7 @@ std::string PresetCollection::name() const // Load a config bundle file, into presets and store the loaded presets into separate files // of the local configuration directory. -// Load settings into the provided settings instance. -void PresetBundle::load_configbundle(const std::string &path, const DynamicPrintConfig &settings) +size_t PresetBundle::load_configbundle(const std::string &path) { // 1) Read the complete config file into the boost::property_tree. namespace pt = boost::property_tree; @@ -583,6 +586,7 @@ void PresetBundle::load_configbundle(const std::string &path, const DynamicPrint std::string active_print; std::vector active_filaments; std::string active_printer; + size_t presets_loaded = 0; for (const auto §ion : tree) { PresetCollection *presets = nullptr; std::vector *loaded = nullptr; @@ -631,6 +635,7 @@ void PresetBundle::load_configbundle(const std::string &path, const DynamicPrint config.set_deserialize(kvp.first, kvp.second.data()); // Load the preset into the list of presets, save it to disk. presets->load_preset(Slic3r::config_path(presets->name(), preset_name), preset_name, config, false).save(); + ++ presets_loaded; } } @@ -642,15 +647,24 @@ void PresetBundle::load_configbundle(const std::string &path, const DynamicPrint // Activate the first filament preset. if (! active_filaments.empty() && ! active_filaments.front().empty()) filaments.select_preset_by_name(active_filaments.front(), true); + + this->update_multi_material_filament_presets(); + for (size_t i = 0; i < std::min(this->filament_presets.size(), active_filaments.size()); ++ i) + this->filament_presets[i] = filaments.first_visible().name; + return presets_loaded; +} + +void PresetBundle::update_multi_material_filament_presets() +{ // Verify and select the filament presets. auto *nozzle_diameter = static_cast(printers.get_selected_preset().config.option("nozzle_diameter")); size_t num_extruders = nozzle_diameter->values.size(); + // Verify validity of the current filament presets. + for (size_t i = 0; i < std::min(this->filament_presets.size(), num_extruders); ++ i) + this->filament_presets[i] = this->filaments.find_preset(this->filament_presets[i], true)->name; + // Append the rest of filament presets. if (this->filament_presets.size() < num_extruders) - this->filament_presets.resize(num_extruders, filaments.get_selected_preset().name); - for (size_t i = 0; i < num_extruders; ++ i) - this->filament_presets[i] = (i < active_filaments.size()) ? - filaments.find_preset(active_filaments[i], true)->name : - filaments.first_visible().name; + this->filament_presets.resize(num_extruders, this->filaments.first_visible().name); } void PresetBundle::export_configbundle(const std::string &path, const DynamicPrintConfig &settings) @@ -703,6 +717,15 @@ void PresetBundle::export_configbundle(const std::string &path, const DynamicPri c.close(); } +// Set the filament preset name. As the name could come from the UI selection box, +// an optional "(modified)" suffix will be removed from the filament name. +void PresetBundle::set_filament_preset(size_t idx, const std::string &name) +{ + if (idx >= filament_presets.size()) + filament_presets.resize(idx + 1, filaments.default_preset().name); + filament_presets[idx] = remove_suffix_modified(name); +} + static inline int hex_digit_to_int(const char c) { return @@ -727,9 +750,67 @@ static inline bool parse_color(const std::string &scolor, unsigned char *rgb_out return true; } -// Update the colors preview at the platter extruder combo box. -void PresetBundle::update_platter_filament_ui_colors(wxBitmapComboBox *ui, unsigned int idx_extruder, unsigned int idx_filament) +void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitmapComboBox *ui) { + if (ui == nullptr) + return; + + unsigned char rgb[3]; + std::string extruder_color = this->printers.get_edited_preset().config.opt_string("extruder_colour", idx_extruder); + if (! parse_color(extruder_color, rgb)) + // Extruder color is not defined. + extruder_color.clear(); + + // Fill in the list from scratch. + ui->Clear(); + for (size_t i = this->filaments().front().is_visible ? 0 : 1; i < this->filaments().size(); ++ i) { + const Preset &preset = this->filaments.preset(i); + if (! preset.is_visible) + continue; + bool selected = this->filament_presets[idx_extruder] == preset.name; + // Assign an extruder color to the selected item if the extruder color is defined. + std::string filament_rgb = preset.config.opt_string("filament_colour", 0); + std::string extruder_rgb = (selected && !extruder_color.empty()) ? extruder_color : filament_rgb; + wxBitmap *bitmap = nullptr; + if (filament_rgb == extruder_rgb) { + auto it = m_mapColorToBitmap.find(filament_rgb); + if (it == m_mapColorToBitmap.end()) { + // Create the bitmap. + parse_color(filament_rgb, rgb); + wxImage image(24, 16); + image.SetRGB(wxRect(0, 0, 24, 16), rgb[0], rgb[1], rgb[2]); + m_mapColorToBitmap[filament_rgb] = bitmap = new wxBitmap(image); + } else { + bitmap = it->second; + } + } else { + std::string bitmap_key = filament_rgb + extruder_rgb; + auto it = m_mapColorToBitmap.find(bitmap_key); + if (it == m_mapColorToBitmap.end()) { + // Create the bitmap. + wxImage image(24, 16); + parse_color(extruder_rgb, rgb); + image.SetRGB(wxRect(0, 0, 16, 16), rgb[0], rgb[1], rgb[2]); + parse_color(filament_rgb, rgb); + image.SetRGB(wxRect(16, 0, 8, 16), rgb[0], rgb[1], rgb[2]); + m_mapColorToBitmap[filament_rgb] = bitmap = new wxBitmap(image); + } else { + bitmap = it->second; + } + } + + ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), (bitmap == 0) ? wxNullBitmap : *bitmap); + if (selected) + ui->SetSelection(ui->GetCount() - 1); + } +} + +// Update the colors preview at the platter extruder combo box. +void PresetBundle::update_platter_filament_ui_colors(unsigned int idx_extruder, wxBitmapComboBox *ui) +{ + this->update_platter_filament_ui(idx_extruder, ui); + return; + unsigned char rgb[3]; std::string extruder_color = this->printers.get_edited_preset().config.opt_string("extruder_colour", idx_extruder); if (! parse_color(extruder_color, rgb)) diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index ae84c2480..cce3e3cf0 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -93,6 +93,14 @@ public: // and select it, losing previous modifications. Preset& load_preset(const std::string &path, const std::string &name, const DynamicPrintConfig &config, bool select = true); + // 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 save_current_preset(const std::string &new_name); + + // Delete the current preset, activate the first visible preset. + void delete_current_preset(); + // Load default bitmap to be placed at the wxBitmapComboBox of a MainFrame. bool load_bitmap_default(const std::string &file_name); @@ -143,17 +151,8 @@ public: // Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ. std::vector current_dirty_options() { return this->get_selected_preset().config.diff(this->get_edited_preset().config); } - // 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 save_current_preset(const std::string &new_name); - - // Delete the current preset, activate the first visible preset. - void delete_current_preset(); - // Update the choice UI from the list of presets. - void update_editor_ui(wxBitmapComboBox *ui); - void update_platter_ui(wxChoice *ui); + void update_tab_ui(wxChoice *ui); void update_platter_ui(wxBitmapComboBox *ui); // Update a dirty floag of the current preset, update the labels of the UI component accordingly. @@ -194,6 +193,8 @@ private: // Marks placed at the wxBitmapComboBox of a MainFrame. // These bitmaps are owned by PresetCollection. wxBitmap *m_bitmap_main_frame; + // Path to the directory to store the config files into. + std::string m_dir_path; }; // Bundle of Print + Filament + Printer presets. @@ -224,24 +225,35 @@ public: // Load a config bundle file, into presets and store the loaded presets into separate files // of the local configuration directory. // Load settings into the provided settings instance. - // Activate the presets stored in the - void load_configbundle(const std::string &path, const DynamicPrintConfig &settings); + // Activate the presets stored in the config bundle. + // Returns the number of presets loaded successfully. + size_t load_configbundle(const std::string &path); // Export a config bundle file containing all the presets and the names of the active presets. void export_configbundle(const std::string &path, const DynamicPrintConfig &settings); + // Update a filament selection combo box on the platter for an idx_extruder. + void update_platter_filament_ui(unsigned int idx_extruder, wxBitmapComboBox *ui); // Update the colors preview at the platter extruder combo box. - void update_platter_filament_ui_colors(wxBitmapComboBox *ui, unsigned int idx_extruder, unsigned int idx_filament); + void update_platter_filament_ui_colors(unsigned int idx_extruder, wxBitmapComboBox *ui); static const std::vector& print_options(); static const std::vector& filament_options(); static const std::vector& printer_options(); // Enable / disable the "- default -" preset. - void set_default_suppressed(bool default_suppressed); + void set_default_suppressed(bool default_suppressed); + + // Set the filament preset name. As the name could come from the UI selection box, + // an optional "(modified)" suffix will be removed from the filament name. + void set_filament_preset(size_t idx, const std::string &name); + + // Read out the number of extruders from an active printer preset, + // update size and content of filament_presets. + void update_multi_material_filament_presets(); private: - bool load_compatible_bitmaps(const std::string &path_bitmap_compatible, const std::string &path_bitmap_incompatible); + bool load_compatible_bitmaps(const std::string &path_bitmap_compatible, const std::string &path_bitmap_incompatible); // Indicator, that the preset is compatible with the selected printer. wxBitmap *m_bitmapCompatible; diff --git a/xs/xsp/GUI_Preset.xsp b/xs/xsp/GUI_Preset.xsp index a665c0733..987574d1a 100644 --- a/xs/xsp/GUI_Preset.xsp +++ b/xs/xsp/GUI_Preset.xsp @@ -33,15 +33,18 @@ Ref get_current_preset() %code%{ RETVAL = &THIS->get_edited_preset(); %}; std::string get_current_preset_name() %code%{ RETVAL = THIS->get_selected_preset().name; %}; Ref get_edited_preset() %code%{ RETVAL = &THIS->get_edited_preset(); %}; - void set_default_suppressed(bool default_suppressed); Ref find_preset(char *name, bool first_visible_if_not_found = false) %code%{ RETVAL = THIS->find_preset(name, first_visible_if_not_found); %}; bool current_is_dirty(); std::vector current_dirty_options(); + void update_tab_ui(SV *ui) + %code%{ auto cb = (wxChoice*)wxPli_sv_2_object( aTHX_ ui, "Wx::Choice" ); + THIS->update_tab_ui(cb); %}; + void update_platter_ui(SV *ui) - %code%{ wxChoice* cb = (wxChoice*)wxPli_sv_2_object( aTHX_ ui, "Wx::Choice" ); + %code%{ auto cb = (wxBitmapComboBox*)wxPli_sv_2_object( aTHX_ ui, "Wx::BitmapComboBox" ); THIS->update_platter_ui(cb); %}; bool update_dirty_ui(SV *ui) @@ -51,6 +54,9 @@ bool select_by_name_ui(char *name, SV *ui) %code%{ RETVAL = THIS->select_by_name_ui(name, (wxChoice*)wxPli_sv_2_object(aTHX_ ui, "Wx::Choice")); %}; + void save_current_preset(char *new_name); + void delete_current_preset(); + %{ SV* @@ -58,10 +64,9 @@ PresetCollection::arrayref() CODE: AV* av = newAV(); av_fill(av, THIS->size()-1); - int i = 0; - for (size_t i = 0; i < THIS->size(); ++ i) { + for (int i = 0; i < int(THIS->size()); ++ i) { Preset &preset = THIS->preset(i); - av_store(av, i++, perl_to_SV_ref(preset)); + av_store(av, i, perl_to_SV_ref(preset)); } RETVAL = newRV_noinc((SV*)av); OUTPUT: @@ -88,12 +93,26 @@ PresetCollection::presets_hash() PresetBundle(); ~PresetBundle(); - void load_presets(std::string dir_path); - void set_default_suppressed(bool default_suppressed); + void load_presets(const char *dir_path); + size_t load_configbundle(const char *path); + void set_default_suppressed(bool default_suppressed); + + Ref print() %code%{ RETVAL = &THIS->prints; %}; + Ref filament() %code%{ RETVAL = &THIS->filaments; %}; + Ref printer() %code%{ RETVAL = &THIS->printers; %}; + + std::vector filament_presets() %code%{ RETVAL = THIS->filament_presets; %}; + void set_filament_preset(int idx, const char *name); + void update_multi_material_filament_presets(); + + Clone full_config() %code%{ RETVAL = THIS->full_config(); %}; + + void update_platter_filament_ui(int extruder_idx, SV *ui) + %code%{ auto cb = (wxBitmapComboBox*)wxPli_sv_2_object(aTHX_ ui, "Wx::BitmapComboBox"); + THIS->update_platter_filament_ui(extruder_idx, cb); %}; + + void update_platter_filament_ui_colors(int extruder_idx, SV *ui) + %code%{ auto cb = (wxBitmapComboBox*)wxPli_sv_2_object(aTHX_ ui, "Wx::BitmapComboBox"); + THIS->update_platter_filament_ui_colors(extruder_idx, cb); %}; - Ref print() %code%{ RETVAL = &THIS->prints; %}; - Ref filament() %code%{ RETVAL = &THIS->filaments; %}; - Ref printer() %code%{ RETVAL = &THIS->printers; %}; - - Clone full_config() %code%{ RETVAL = THIS->full_config(); %}; }; diff --git a/xs/xsp/XS.xsp b/xs/xsp/XS.xsp index 194c22b23..2be42d383 100644 --- a/xs/xsp/XS.xsp +++ b/xs/xsp/XS.xsp @@ -80,10 +80,11 @@ data_dir() OUTPUT: RETVAL std::string -config_path(file_name) - const char *file_name; +config_path(section, name) + const char *section; + const char *name; CODE: - RETVAL = Slic3r::config_path(file_name); + RETVAL = Slic3r::config_path(section, name); OUTPUT: RETVAL std::string