Integrated the "compatible printers" idea by @alexrj with Vojtech's twist:

The incompatible presets are hidden in the tabs if show_incompatible_presets
is false. If show_incompatible_presets is true, there is a button to
show / hide the incompatible presets from the tab selector.
This commit is contained in:
bubnikv 2017-11-10 17:27:05 +01:00
parent b23b9ea1d2
commit bfce6dba9b
12 changed files with 482 additions and 226 deletions

View file

@ -20,8 +20,8 @@ use utf8;
use File::Basename qw(basename);
use List::Util qw(first);
use Wx qw(:bookctrl :dialog :keycode :icon :id :misc :panel :sizer :treectrl :window
:button wxTheApp);
use Wx::Event qw(EVT_BUTTON EVT_CHOICE EVT_KEY_DOWN EVT_CHECKBOX EVT_TREE_SEL_CHANGED);
:button wxTheApp wxCB_READONLY);
use Wx::Event qw(EVT_BUTTON EVT_COMBOBOX EVT_KEY_DOWN EVT_CHECKBOX EVT_TREE_SEL_CHANGED);
use base qw(Wx::Panel Class::Accessor);
sub new {
@ -37,7 +37,7 @@ sub new {
{
# choice menu
$self->{presets_choice} = Wx::Choice->new($self, -1, wxDefaultPosition, [270, -1], []);
$self->{presets_choice} = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, [270, -1], [], wxCB_READONLY);
$self->{presets_choice}->SetFont($Slic3r::GUI::small_font);
# buttons
@ -45,15 +45,25 @@ sub new {
wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
$self->{btn_delete_preset} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG),
wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
$self->{show_incompatible_presets} = 0;
$self->{bmp_show_incompatible_presets} = Wx::Bitmap->new(Slic3r::var("flag-red-icon.png"), wxBITMAP_TYPE_PNG);
$self->{bmp_hide_incompatible_presets} = Wx::Bitmap->new(Slic3r::var("flag-green-icon.png"), wxBITMAP_TYPE_PNG);
$self->{btn_hide_incompatible_presets} = Wx::BitmapButton->new($self, -1,
$self->{bmp_hide_incompatible_presets},
wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
$self->{btn_save_preset}->SetToolTipString("Save current " . lc($self->title));
$self->{btn_delete_preset}->SetToolTipString("Delete this preset");
$self->{btn_delete_preset}->Disable;
my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
$self->{sizer}->Add($hsizer, 0, wxBOTTOM, 3);
$hsizer->Add($self->{presets_choice}, 1, wxLEFT | wxRIGHT | wxTOP | wxALIGN_CENTER_VERTICAL, 3);
$hsizer->AddSpacer(4);
$hsizer->Add($self->{btn_save_preset}, 0, wxALIGN_CENTER_VERTICAL);
$hsizer->AddSpacer(4);
$hsizer->Add($self->{btn_delete_preset}, 0, wxALIGN_CENTER_VERTICAL);
$hsizer->AddSpacer(16);
$hsizer->Add($self->{btn_hide_incompatible_presets}, 0, wxALIGN_CENTER_VERTICAL);
}
# Horizontal sizer to hold the tree and the selected page.
@ -95,12 +105,13 @@ sub new {
}
});
EVT_CHOICE($parent, $self->{presets_choice}, sub {
EVT_COMBOBOX($parent, $self->{presets_choice}, sub {
$self->select_preset($self->{presets_choice}->GetStringSelection);
});
EVT_BUTTON($self, $self->{btn_save_preset}, sub { $self->save_preset });
EVT_BUTTON($self, $self->{btn_delete_preset}, sub { $self->delete_preset });
EVT_BUTTON($self, $self->{btn_hide_incompatible_presets}, sub { $self->_toggle_show_hide_incompatible });
# Initialize the DynamicPrintConfig by default keys/values.
# Possible %params keys: no_controller
@ -140,7 +151,7 @@ sub save_preset {
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});
$self->{presets}->update_tab_ui($self->{presets_choice}, $self->{show_incompatible_presets});
# Update the selection boxes at the platter.
$self->_on_presets_changed;
}
@ -162,6 +173,22 @@ sub delete_preset {
$self->load_current_preset;
}
sub _toggle_show_hide_incompatible {
my ($self) = @_;
$self->{show_incompatible_presets} = ! $self->{show_incompatible_presets};
$self->_update_show_hide_incompatible_button;
$self->{presets}->update_tab_ui($self->{presets_choice}, $self->{show_incompatible_presets});
}
sub _update_show_hide_incompatible_button {
my ($self) = @_;
$self->{btn_hide_incompatible_presets}->SetBitmap($self->{show_incompatible_presets} ?
$self->{bmp_show_incompatible_presets} : $self->{bmp_hide_incompatible_presets});
$self->{btn_hide_incompatible_presets}->SetToolTipString($self->{show_incompatible_presets} ?
"Both compatible an incompatible presets are shown. Click to hide presets not compatible with the current printer." :
"Only compatible presets are shown. Click to show both the presets compatible and not compatible with the current printer.");
}
# Register the on_value_change callback.
sub on_value_change {
my ($self, $cb) = @_;
@ -205,28 +232,33 @@ sub on_preset_loaded {}
# 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
sub may_discard_current_dirty_preset
{
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;
my $name = $old_preset->default ? 'Default preset' : "Preset \"" . $old_preset->name . "\"";
# Collect descriptions of the dirty options.
my @option_names = ();
foreach my $opt_key (@{$self->{presets}->current_dirty_options}) {
my $opt = $Slic3r::Config::Options->{$opt_key};
my $name = $opt->{full_label} // $opt->{label};
$name = $opt->{category} . " > $name" if $opt->{category};
push @option_names, $name;
}
# Show a confirmation dialog with the list of dirty options.
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);
return 0 if $confirm->ShowModal == wxID_NO;
my ($self, $presets, $new_printer_name) = @_;
$presets //= $self->{presets};
# Display a dialog showing the dirty options in a human readable form.
my $old_preset = $presets->get_current_preset;
my $type_name = $presets->name;
my $name = $old_preset->default ?
('Default ' . $type_name . ' preset') :
($type_name . " preset \"" . $old_preset->name . "\"");
# Collect descriptions of the dirty options.
my @option_names = ();
foreach my $opt_key (@{$presets->current_dirty_options}) {
my $opt = $Slic3r::Config::Options->{$opt_key};
my $name = $opt->{full_label} // $opt->{label};
$name = $opt->{category} . " > $name" if $opt->{category};
push @option_names, $name;
}
return 1;
# Show a confirmation dialog with the list of dirty options.
my $changes = join "\n", map "- $_", @option_names;
my $message = (defined $new_printer_name) ?
"$name is not compatible with printer \"$new_printer_name\"\n and it has unsaved changes:" :
"$name has unsaved changes:";
my $confirm = Wx::MessageDialog->new($self,
$message . "\n$changes\n\nDiscard changes and continue anyway?",
'Unsaved Changes', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
return $confirm->ShowModal == wxID_YES;
}
# Called by the UI combo box when the user switches profiles.
@ -235,25 +267,60 @@ sub may_discard_current_preset_if_dirty
sub select_preset {
my ($self, $name, $force) = @_;
$force //= 0;
if (! $force && ! $self->may_discard_current_preset_if_dirty) {
$self->{presets}->update_tab_ui($self->{presets_choice});
my $current_dirty = $self->{presets}->current_is_dirty;
my $canceled = 0;
my $printer_tab = $self->{presets}->name eq 'printer';
if (! $force && $current_dirty && ! $self->may_discard_current_dirty_preset) {
$canceled = 1;
} elsif ($printer_tab) {
# Before switching the printer to a new one, verify, whether the currently active print and filament
# are compatible with the new printer.
# If they are not compatible and the the current print or filament are dirty, let user decide
# whether to discard the changes or keep the current printer selection.
my $new_printer_name = $name // '';
my $new_printer_preset = $self->{presets}->find_preset($new_printer_name, 1);
# my $new_nozzle_dmrs = $new_printer_preset->config->get('nozzle_diameter');
my $print_presets = wxTheApp->{preset_bundle}->print;
if ($print_presets->current_is_dirty &&
! $print_presets->get_edited_preset->is_compatible_with_printer($new_printer_name)) {
if ($self->may_discard_current_dirty_preset($print_presets, $new_printer_name)) {
$canceled = 1;
} else {
$print_presets->discard_current_changes;
}
}
my $filament_presets = wxTheApp->{preset_bundle}->filament;
# if ((@$new_nozzle_dmrs <= 1) &&
if (! $canceled && $filament_presets->current_is_dirty &&
! $filament_presets->get_edited_preset->is_compatible_with_printer($new_printer_name)) {
if ($self->may_discard_current_dirty_preset($filament_presets, $new_printer_name)) {
$canceled = 1;
} else {
$filament_presets->discard_current_changes;
}
}
}
if ($canceled) {
$self->{presets}->update_tab_ui($self->{presets_choice}, $self->{show_incompatible_presets});
# Trigger the on_presets_changed event so that we also restore the previous value in the plater selector.
$self->_on_presets_changed;
return;
}
if (defined $name) {
$self->{presets}->select_preset_by_name($name);
} else {
$self->{presets}->select_preset(0);
if (defined $name) {
$self->{presets}->select_preset_by_name($name);
} else {
$self->{presets}->select_preset(0);
}
# Mark the print & filament enabled if they are compatible with the currently selected preset.
wxTheApp->{preset_bundle}->update_compatible_with_printer(1)
if $current_dirty || $printer_tab;
# Initialize the UI from the current preset.
$self->load_current_preset;
}
# Initialize the UI from the current preset.
$self->load_current_preset;
}
# Initialize the UI from the current preset.
sub load_current_preset {
my ($self) = @_;
$self->{presets}->update_tab_ui($self->{presets_choice});
my $preset = $self->{presets}->get_current_preset;
eval {
local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self);
@ -270,9 +337,8 @@ sub load_current_preset {
# preset dirty again
# (not sure this is true anymore now that update_dirty is idempotent)
wxTheApp->CallAfter(sub {
$self->update_dirty;
#the following is called by update_dirty
#$self->_on_presets_changed;
$self->{presets}->update_tab_ui($self->{presets_choice}, $self->{show_incompatible_presets});
$self->_on_presets_changed;
});
}
@ -344,7 +410,6 @@ sub update_dirty {
# This could be used for example by setting a Wipe Tower position by interactive manipulation in the 3D view.
sub load_config {
my ($self, $config) = @_;
my $modified = 0;
foreach my $opt_key (@{$self->{config}->diff($config)}) {
$self->{config}->set($opt_key, $config->get($opt_key));
@ -358,6 +423,22 @@ sub load_config {
}
}
# To be called by custom widgets, load a value into a config,
# update the preset selection boxes (the dirty flags)
sub _load_key_value {
my ($self, $opt_key, $value) = @_;
$self->{config}->set($opt_key, $value);
# Mark the print & filament enabled if they are compatible with the currently selected preset.
if ($opt_key eq 'compatible_printers') {
wxTheApp->{preset_bundle}->update_compatible_with_printer(0);
$self->{presets}->update_tab_ui($self->{presets_choice}, $self->{show_incompatible_presets});
} else {
$self->{presets}->update_dirty_ui($self->{presets_choice});
}
$self->_on_presets_changed;
$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 {
@ -381,6 +462,87 @@ sub set_value {
return $changed;
}
# Return a callback to create a Tab widget to mark the preferences as compatible / incompatible to the current printer.
sub _compatible_printers_widget {
my ($self) = @_;
return sub {
my ($parent) = @_;
my $checkbox = $self->{compatible_printers_checkbox} = Wx::CheckBox->new($parent, -1, "All");
my $btn = $self->{compatible_printers_btn} = Wx::Button->new($parent, -1, "Set…", wxDefaultPosition, wxDefaultSize,
wxBU_LEFT | wxBU_EXACTFIT);
$btn->SetFont($Slic3r::GUI::small_font);
$btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("printer_empty.png"), wxBITMAP_TYPE_PNG));
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$sizer->Add($checkbox, 0, wxALIGN_CENTER_VERTICAL);
$sizer->Add($btn, 0, wxALIGN_CENTER_VERTICAL);
EVT_CHECKBOX($self, $checkbox, sub {
my $method = $checkbox->GetValue ? 'Disable' : 'Enable';
$btn->$method;
# All printers have been made compatible with this preset.
$self->_load_key_value('compatible_printers', []) if $checkbox->GetValue;
});
EVT_BUTTON($self, $btn, sub {
# Collect names of non-default non-external printer profiles.
my @presets = map $_->name, grep !$_->default && !$_->external,
@{wxTheApp->{preset_bundle}->printer};
my $dlg = Wx::MultiChoiceDialog->new($self,
"Select the printers this profile is compatible with.",
"Compatible printers", \@presets);
# Collect and set indices of printers marked as compatible.
my @selections = ();
foreach my $preset_name (@{ $self->{config}->get('compatible_printers') }) {
my $idx = first { $presets[$_] eq $preset_name } 0..$#presets;
push @selections, $idx if defined $idx;
}
$dlg->SetSelections(@selections);
# Show the dialog.
if ($dlg->ShowModal == wxID_OK) {
my $value = [ @presets[$dlg->GetSelections] ];
if (!@$value) {
$checkbox->SetValue(1);
$btn->Disable;
}
# All printers have been made compatible with this preset.
$self->_load_key_value('compatible_printers', $value);
}
});
return $sizer;
};
}
sub _reload_compatible_printers_widget {
my ($self) = @_;
my $has_any = int(@{$self->{config}->get('compatible_printers')}) > 0;
my $method = $has_any ? 'Enable' : 'Disable';
$self->{compatible_printers_checkbox}->SetValue(! $has_any);
$self->{compatible_printers_btn}->$method;
}
sub update_ui_from_settings {
my ($self) = @_;
# Show the 'show / hide presets' button only for the print and filament tabs, and only if enabled
# in application preferences.
my $show = wxTheApp->{app_config}->get("show_incompatible_presets") && $self->{presets}->name ne 'printer';
my $method = $show ? 'Show' : 'Hide';
$self->{btn_hide_incompatible_presets}->$method;
# If the 'show / hide presets' button is hidden, hide the incompatible presets.
if ($show) {
$self->_update_show_hide_incompatible_button;
} else {
if ($self->{show_incompatible_presets}) {
$self->{show_incompatible_presets} = 0;
$self->{presets}->update_tab_ui($self->{presets_choice}, 0);
}
}
}
package Slic3r::GUI::Tab::Print;
use base 'Slic3r::GUI::Tab';
@ -651,12 +813,26 @@ sub build {
$optgroup->append_single_option_line($option);
}
}
{
my $page = $self->add_options_page('Dependencies', 'wrench.png');
{
my $optgroup = $page->new_optgroup('Profile dependencies');
{
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Compatible printers',
widget => $self->_compatible_printers_widget,
);
$optgroup->append_line($line);
}
}
}
}
# Reload current $self->{config} (aka $self->{presets}->edited_preset->config) into the UI fields.
sub _reload_config {
my ($self) = @_;
# $self->_reload_compatible_printers_widget;
$self->_reload_compatible_printers_widget;
$self->SUPER::_reload_config;
}
@ -920,7 +1096,7 @@ sub build {
full_width => 1,
widget => sub {
my ($parent) = @_;
return $self->{description_line} = Slic3r::GUI::OptionsGroup::StaticText->new($parent);
return $self->{cooling_description_line} = Slic3r::GUI::OptionsGroup::StaticText->new($parent);
},
);
$optgroup->append_line($line);
@ -959,6 +1135,16 @@ sub build {
$optgroup = $page->new_optgroup('Print speed override');
$optgroup->append_single_option_line('filament_max_volumetric_speed', 0);
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => '',
full_width => 1,
widget => sub {
my ($parent) = @_;
return $self->{volumetric_speed_description_line} = Slic3r::GUI::OptionsGroup::StaticText->new($parent);
},
);
$optgroup->append_line($line);
}
}
@ -996,13 +1182,37 @@ sub build {
$optgroup->append_single_option_line($option);
}
}
{
my $page = $self->add_options_page('Dependencies', 'wrench.png');
{
my $optgroup = $page->new_optgroup('Profile dependencies');
{
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Compatible printers',
widget => $self->_compatible_printers_widget,
);
$optgroup->append_line($line);
}
}
}
}
# Reload current $self->{config} (aka $self->{presets}->edited_preset->config) into the UI fields.
sub _reload_config {
my ($self) = @_;
$self->_reload_compatible_printers_widget;
$self->SUPER::_reload_config;
}
# Slic3r::GUI::Tab::Filament::_update is called after a configuration preset is loaded or switched, or when a single option is modifed by the user.
sub _update {
my ($self) = @_;
$self->_update_description;
$self->{cooling_description_line}->SetText(
Slic3r::GUI::PresetHints::cooling_description($self->{presets}->get_edited_preset));
$self->{volumetric_speed_description_line}->SetText(
Slic3r::GUI::PresetHints::maximum_volumetric_flow_description(wxTheApp->{preset_bundle}));
my $cooling = $self->{config}->cooling->[0];
my $fan_always_on = $cooling || $self->{config}->fan_always_on->[0];
@ -1012,33 +1222,6 @@ sub _update {
for qw(min_fan_speed disable_fan_first_layers);
}
sub _update_description {
my ($self) = @_;
my $config = $self->{config};
my $msg = "";
my $fan_other_layers = $config->fan_always_on->[0]
? sprintf "will always run at %d%%%s.", $config->min_fan_speed->[0],
($config->disable_fan_first_layers->[0] > 1
? " except for the first " . $config->disable_fan_first_layers->[0] . " layers"
: $config->disable_fan_first_layers->[0] == 1
? " except for the first layer"
: "")
: "will be turned off.";
if ($config->cooling->[0]) {
$msg = sprintf "If estimated layer time is below ~%ds, fan will run at %d%% and print speed will be reduced so that no less than %ds are spent on that layer (however, speed will never be reduced below %dmm/s).",
$config->slowdown_below_layer_time->[0], $config->max_fan_speed->[0], $config->slowdown_below_layer_time->[0], $config->min_print_speed->[0];
if ($config->fan_below_layer_time->[0] > $config->slowdown_below_layer_time->[0]) {
$msg .= sprintf "\nIf estimated layer time is greater, but still below ~%ds, fan will run at a proportionally decreasing speed between %d%% and %d%%.",
$config->fan_below_layer_time->[0], $config->max_fan_speed->[0], $config->min_fan_speed->[0];
}
$msg .= "\nDuring the other layers, fan $fan_other_layers"
} else {
$msg = "Fan $fan_other_layers";
}
$self->{description_line}->SetText($msg);
}
package Slic3r::GUI::Tab::Printer;
use base 'Slic3r::GUI::Tab';
use Wx qw(wxTheApp :sizer :button :bitmap :misc :id :icon :dialog);
@ -1060,19 +1243,14 @@ sub build {
my $btn = Wx::Button->new($parent, -1, "Set…", wxDefaultPosition, wxDefaultSize,
wxBU_LEFT | wxBU_EXACTFIT);
$btn->SetFont($Slic3r::GUI::small_font);
$btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("cog.png"), wxBITMAP_TYPE_PNG));
$btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("printer_empty.png"), wxBITMAP_TYPE_PNG));
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$sizer->Add($btn);
EVT_BUTTON($self, $btn, sub {
my $dlg = Slic3r::GUI::BedShapeDialog->new($self, $self->{config}->bed_shape);
if ($dlg->ShowModal == wxID_OK) {
my $value = $dlg->GetValue;
$self->{config}->set('bed_shape', $value);
$self->update_dirty;
$self->_on_value_change('bed_shape', $value);
}
$self->_load_key_value('bed_shape', $dlg->GetValue) if $dlg->ShowModal == wxID_OK;
});
return $sizer;
@ -1188,13 +1366,8 @@ sub build {
}
if (@{$entries}) {
my $dlg = Slic3r::GUI::BonjourBrowser->new($self, $entries);
if ($dlg->ShowModal == wxID_OK) {
my $value = $dlg->GetValue . ":" . $dlg->GetPort;
$self->{config}->set('octoprint_host', $value);
$self->update_dirty;
$self->_on_value_change('octoprint_host', $value);
$self->_reload_config;
}
$self->_load_key_value('octoprint_host', $dlg->GetValue . ":" . $dlg->GetPort)
if $dlg->ShowModal == wxID_OK;
} else {
Wx::MessageDialog->new($self, 'No Bonjour device found', 'Device Browser', wxOK | wxICON_INFORMATION)->ShowModal;
}