Changes to the GUI. New preset editor dialog. Option overrides in plater and between configs. More.

This commit is contained in:
Alessandro Ranellucci 2017-03-17 01:22:54 +01:00
parent c20d44c388
commit 5d77c160c6
21 changed files with 996 additions and 1093 deletions

View File

@ -117,7 +117,6 @@ The author of the Silk icon set is Mark James.
--gui Forces the GUI launch instead of command line slicing (if you
supply a model file, it will be loaded into the plater)
--no-plater Disable the plater tab
--gui-mode Overrides the configured mode (simple/expert)
--autosave <file> Automatically export current configuration to the specified file
Output options:

View File

@ -36,12 +36,11 @@ sub new_from_defaults {
my (@opt_keys) = @_;
my $self = $class->new;
my $defaults = Slic3r::Config::Full->new;
if (@opt_keys) {
$self->set($_, $defaults->get($_))
for grep $defaults->has($_), @opt_keys;
$self->set($_, $Options->{$_}{default})
for grep exists $Options->{$_}{default}, @opt_keys;
} else {
$self->apply_static($defaults);
$self->apply_static(Slic3r::Config::Full->new);
}
return $self;
}

View File

@ -31,9 +31,10 @@ use Slic3r::GUI::ProgressStatusBar;
use Slic3r::GUI::Projector;
use Slic3r::GUI::OptionsGroup;
use Slic3r::GUI::OptionsGroup::Field;
use Slic3r::GUI::SimpleTab;
use Slic3r::GUI::Preset;
use Slic3r::GUI::PresetEditor;
use Slic3r::GUI::PresetEditorDialog;
use Slic3r::GUI::SLAPrintOptions;
use Slic3r::GUI::Tab;
our $have_OpenGL = eval "use Slic3r::GUI::3DScene; 1";
our $have_LWP = eval "use LWP::UserAgent; 1";
@ -57,21 +58,17 @@ use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(known stl obj amf
our $datadir;
# If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
our $no_controller;
our $no_plater;
our $mode;
our $autosave;
our @cb;
our $Settings = {
_ => {
mode => 'simple',
version_check => 1,
autocenter => 1,
invert_zoom => 0,
background_processing => 0,
# If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
# By default, Prusa has the controller hidden.
no_controller => 1,
no_controller => 0,
},
};
@ -96,6 +93,7 @@ sub OnInit {
Slic3r::debugf "wxWidgets version %s, Wx version %s\n", &Wx::wxVERSION_STRING, $Wx::VERSION;
$self->{notifier} = Slic3r::GUI::Notifier->new;
$self->{external_presets} = [];
# locate or create data directory
# Unix: ~/.Slic3r
@ -123,7 +121,7 @@ sub OnInit {
my $ini = eval { Slic3r::Config->read_ini("$datadir/slic3r.ini") };
$Settings = $ini if $ini;
$last_version = $Settings->{_}{version};
$Settings->{_}{mode} ||= 'expert';
delete $Settings->{_}{mode}; # handle legacy
$Settings->{_}{autocenter} //= 1;
$Settings->{_}{invert_zoom} //= 0;
$Settings->{_}{background_processing} //= 1;
@ -133,13 +131,17 @@ sub OnInit {
$Settings->{_}{version} = $Slic3r::VERSION;
$self->save_settings;
if (-f "$enc_datadir/simple.ini") {
# The Simple Mode settings were already automatically duplicated to presets
# named "Simple Mode" in each group, so we already support retrocompatibility.
unlink "$enc_datadir/simple.ini";
}
# application frame
Wx::Image::AddHandler(Wx::PNGHandler->new);
$self->{mainframe} = my $frame = Slic3r::GUI::MainFrame->new(
mode => $mode // $Settings->{_}{mode},
# If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
no_controller => $no_controller // $Settings->{_}{no_controller},
no_plater => $no_plater,
);
$self->SetTopWindow($frame);
@ -277,20 +279,46 @@ sub save_settings {
}
sub presets {
my ($self, $section) = @_;
my ($self, $section, $force_default) = @_;
my @presets = ();
my %presets = ();
opendir my $dh, Slic3r::encode_path("$Slic3r::GUI::datadir/$section")
or die "Failed to read directory $Slic3r::GUI::datadir/$section (errno: $!)\n";
foreach my $file (grep /\.ini$/i, readdir $dh) {
$file = Slic3r::decode_path($file);
my $name = basename($file);
$name =~ s/\.ini$//;
$presets{$name} = "$Slic3r::GUI::datadir/$section/$file";
$name =~ s/\.ini$//i;
push @presets, Slic3r::GUI::Preset->new(
name => $name,
file => "$Slic3r::GUI::datadir/$section/$file",
);
}
closedir $dh;
return %presets;
@presets = sort { $a->name cmp $b->name }
@presets,
(grep -e $_->file, @{$self->{external_presets}});
if ($force_default || !@presets) {
unshift @presets, Slic3r::GUI::Preset->new(
default => 1,
name => '- default -',
);
}
return @presets;
}
sub add_external_preset {
my ($self, $file) = @_;
push @{$self->{external_presets}}, my $preset = Slic3r::GUI::Preset->new(
name => basename($file), # keep .ini suffix
file => $file,
external => 1,
);
return $preset;
}
sub have_version_check {

View File

@ -7,6 +7,7 @@ use strict;
use warnings;
use utf8;
use List::Util qw(any);
use Wx qw(wxTheApp :frame :id :misc :sizer :bitmap :button :icon :dialog);
use Wx::Event qw(EVT_CLOSE EVT_LEFT_DOWN EVT_MENU);
use base qw(Wx::ScrolledWindow Class::Accessor);
@ -39,14 +40,14 @@ sub new {
EVT_LEFT_DOWN($btn, sub {
my $menu = Wx::Menu->new;
my %presets = wxTheApp->presets('printer');
my %presets = map { $_->name => $_ } wxTheApp->presets('printer');
# remove printers that already exist
my @panels = $self->print_panels;
delete $presets{$_} for map $_->printer_name, @panels;
foreach my $preset_name (sort keys %presets) {
my $config = Slic3r::Config->load($presets{$preset_name});
my $config = $presets{$preset_name}->load_config;
next if !$config->serial_port;
my $id = &Wx::NewId();
@ -100,8 +101,8 @@ sub OnActivate {
# get all available presets
my %presets = ();
{
my %all = wxTheApp->presets('printer');
my %configs = map { my $name = $_; $name => Slic3r::Config->load($all{$name}) } keys %all;
my %all = map { $_->name => $_ } wxTheApp->presets('printer');
my %configs = map { my $name = $_; $name => $all{$name}->load_config } keys %all;
%presets = map { $_ => $configs{$_} } grep $configs{$_}->serial_port, keys %all;
}
@ -177,6 +178,12 @@ sub print_panels {
map $_->GetWindow, $self->{sizer}->GetChildren;
}
sub printing {
my ($self) = @_;
return any { $_->printing } $self->print_panels;
}
sub update_presets {
my $self = shift;
my ($group, $presets, $selected, $is_dirty) = @_;

View File

@ -28,11 +28,8 @@ sub new {
}
# store input params
$self->{mode} = $params{mode};
$self->{mode} = 'expert' if $self->{mode} !~ /^(?:simple|expert)$/;
# If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
$self->{no_controller} = $params{no_controller};
$self->{no_plater} = $params{no_plater};
$self->{loaded} = 0;
# initialize tabpanel and menubar
@ -80,9 +77,17 @@ sub new {
EVT_CLOSE($self, sub {
my (undef, $event) = @_;
if ($event->CanVeto && !$self->check_unsaved_changes) {
$event->Veto;
return;
if ($event->CanVeto) {
my $veto = 0;
if ($self->{controller} && $self->{controller}->printing) {
my $confirm = Wx::MessageDialog->new($self, "You are currently printing. Do you want to stop printing and continue anyway?",
'Unfinished Print', wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT);
$veto = 1 if $confirm->ShowModal == wxID_YES;
}
if ($veto) {
$event->Veto;
return;
}
}
# save window size
@ -107,71 +112,10 @@ sub _init_tabpanel {
$panel->OnActivate if $panel->can('OnActivate');
});
if (!$self->{no_plater}) {
$panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel), "Plater");
}
$panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel), "Plater");
if (!$self->{no_controller}) {
$panel->AddPage($self->{controller} = Slic3r::GUI::Controller->new($panel), "Controller");
}
$self->{options_tabs} = {};
my $simple_config;
if ($self->{mode} eq 'simple') {
$simple_config = Slic3r::Config->load("$Slic3r::GUI::datadir/simple.ini")
if -e Slic3r::encode_path("$Slic3r::GUI::datadir/simple.ini");
}
my $class_prefix = $self->{mode} eq 'simple' ? "Slic3r::GUI::SimpleTab::" : "Slic3r::GUI::Tab::";
for my $tab_name (qw(print filament printer)) {
my $tab;
$tab = $self->{options_tabs}{$tab_name} = ($class_prefix . ucfirst $tab_name)->new(
$panel,
no_controller => $self->{no_controller});
$tab->on_value_change(sub {
my ($opt_key, $value) = @_;
my $config = $tab->config;
if ($self->{plater}) {
$self->{plater}->on_config_change($config); # propagate config change events to the plater
$self->{plater}->on_extruders_change($value) if $opt_key eq 'extruders_count';
}
if ($self->{loaded}) { # don't save while loading for the first time
if ($self->{mode} eq 'simple') {
# save config
$self->config->save("$Slic3r::GUI::datadir/simple.ini");
# save a copy into each preset section
# so that user gets the config when switching to expert mode
$config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $tab->name, 'Simple Mode');
$Slic3r::GUI::Settings->{presets}{$tab->name} = 'Simple Mode.ini';
wxTheApp->save_settings;
}
$self->config->save($Slic3r::GUI::autosave) if $Slic3r::GUI::autosave;
}
});
$tab->on_presets_changed(sub {
if ($self->{plater}) {
$self->{plater}->update_presets($tab_name, @_);
$self->{plater}->on_config_change($tab->config);
if ($self->{controller}) {
$self->{controller}->update_presets($tab_name, @_);
}
}
});
$tab->load_presets;
$panel->AddPage($tab, $tab->title);
$tab->load_config($simple_config) if $simple_config;
}
if ($self->{plater}) {
$self->{plater}->on_select_preset(sub {
my ($group, $i) = @_;
$self->{options_tabs}{$group}->select_preset($i);
});
# load initial config
$self->{plater}->on_config_change($self->config);
}
}
sub _init_menubar {
@ -239,7 +183,7 @@ sub _init_menubar {
}
# Plater menu
unless ($self->{no_plater}) {
{
my $plater = $self->{plater};
$self->{plater_menu} = Wx::Menu->new;
@ -252,49 +196,27 @@ sub _init_menubar {
$self->_append_menu_item($self->{plater_menu}, "Export plate as AMF...", 'Export current plate as AMF', sub {
$plater->export_amf;
}, undef, 'brick_go.png');
$self->_append_menu_item($self->{plater_menu}, "Open DLP Projector…\tCtrl+L", 'Open projector window for DLP printing', sub {
$plater->pause_background_process;
Slic3r::GUI::SLAPrintOptions->new($self)->ShowModal;
$plater->resume_background_process;
}, undef, 'film.png');
$self->{object_menu} = $self->{plater}->object_menu;
$self->on_plater_selection_changed(0);
}
# Window menu
my $windowMenu = Wx::Menu->new;
# Settings menu
my $settingsMenu = Wx::Menu->new;
{
my $tab_offset = 0;
if (!$self->{no_plater}) {
$self->_append_menu_item($windowMenu, "Select &Plater Tab\tCtrl+1", 'Show the plater', sub {
$self->select_tab(0);
}, undef, 'application_view_tile.png');
$tab_offset += 1;
}
if (!$self->{no_controller}) {
$self->_append_menu_item($windowMenu, "Select &Controller Tab\tCtrl+T", 'Show the printer controller', sub {
$self->select_tab(1);
}, undef, 'printer_empty.png');
$tab_offset += 1;
}
if ($tab_offset > 0) {
$windowMenu->AppendSeparator();
}
$self->_append_menu_item($windowMenu, "Select P&rint Settings Tab\tCtrl+2", 'Show the print settings', sub {
$self->select_tab($tab_offset+0);
$self->_append_menu_item($settingsMenu, "P&rint Settings…\tCtrl+1", 'Show the print settings editor', sub {
$self->{plater}->show_preset_editor('print');
}, undef, 'cog.png');
$self->_append_menu_item($windowMenu, "Select &Filament Settings Tab\tCtrl+3", 'Show the filament settings', sub {
$self->select_tab($tab_offset+1);
$self->_append_menu_item($settingsMenu, "&Filament Settings…\tCtrl+2", 'Show the filament settings editor', sub {
$self->{plater}->show_preset_editor('filament');
}, undef, 'spool.png');
$self->_append_menu_item($windowMenu, "Select Print&er Settings Tab\tCtrl+4", 'Show the printer settings', sub {
$self->select_tab($tab_offset+2);
$self->_append_menu_item($settingsMenu, "Print&er Settings…\tCtrl+3", 'Show the printer settings editor', sub {
$self->{plater}->show_preset_editor('printer');
}, undef, 'printer_empty.png');
}
# View menu
if (!$self->{no_plater}) {
{
$self->{viewMenu} = Wx::Menu->new;
$self->_append_menu_item($self->{viewMenu}, "Iso" , 'Iso View' , sub { $self->select_view('iso' ); });
$self->_append_menu_item($self->{viewMenu}, "Top" , 'Top View' , sub { $self->select_view('top' ); });
@ -305,6 +227,22 @@ sub _init_menubar {
$self->_append_menu_item($self->{viewMenu}, "Right" , 'Right View' , sub { $self->select_view('right' ); });
}
# Window menu
my $windowMenu = Wx::Menu->new;
{
$self->_append_menu_item($windowMenu, "&Plater\tCtrl+T", 'Show the plater', sub {
$self->select_tab(0);
}, undef, 'application_view_tile.png');
$self->_append_menu_item($windowMenu, "&Controller\tCtrl+Y", 'Show the printer controller', sub {
$self->select_tab(1);
}, undef, 'printer_empty.png') if !$self->{no_controller};
$self->_append_menu_item($windowMenu, "DLP Projector…\tCtrl+P", 'Open projector window for DLP printing', sub {
$self->{plater}->pause_background_process;
Slic3r::GUI::SLAPrintOptions->new($self)->ShowModal;
$self->{plater}->resume_background_process;
}, undef, 'film.png');
}
# Help menu
my $helpMenu = Wx::Menu->new;
{
@ -336,8 +274,9 @@ sub _init_menubar {
$menubar->Append($fileMenu, "&File");
$menubar->Append($self->{plater_menu}, "&Plater") if $self->{plater_menu};
$menubar->Append($self->{object_menu}, "&Object") if $self->{object_menu};
$menubar->Append($windowMenu, "&Window");
$menubar->Append($settingsMenu, "&Settings");
$menubar->Append($self->{viewMenu}, "&View") if $self->{viewMenu};
$menubar->Append($windowMenu, "&Window");
$menubar->Append($helpMenu, "&Help");
$self->SetMenuBar($menubar);
}
@ -363,7 +302,7 @@ sub quick_slice {
my $progress_dialog;
eval {
# validate configuration
my $config = $self->config;
my $config = $self->{plater}->config;
$config->validate;
# select input file
@ -509,7 +448,7 @@ sub repair_stl {
sub export_config {
my $self = shift;
my $config = $self->config;
my $config = $self->{plater}->config;
eval {
# validate configuration
$config->validate;
@ -535,7 +474,6 @@ sub load_config_file {
my ($file) = @_;
if (!$file) {
return unless $self->check_unsaved_changes;
my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || '';
my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', $dir, "config.ini",
&Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
@ -546,9 +484,10 @@ sub load_config_file {
$Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file);
wxTheApp->save_settings;
$last_config = $file;
for my $tab (values %{$self->{options_tabs}}) {
$tab->load_config_file($file);
}
my $preset = wxTheApp->add_external_preset($file);
$self->{plater}->load_presets;
$self->{plater}->select_preset_by_name($preset->name, $_) for qw(print filament printer);
}
sub export_configbundle {
@ -556,7 +495,7 @@ sub export_configbundle {
eval {
# validate current configuration in case it's dirty
$self->config->validate;
$self->{plater}->config->validate;
};
Slic3r::GUI::catch_error($self) and return;
@ -571,18 +510,13 @@ sub export_configbundle {
# 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 mode);
$ini->{settings}{$_} = $Slic3r::GUI::Settings->{_}{$_} for qw(autocenter);
$ini->{presets} = $Slic3r::GUI::Settings->{presets};
if (-e "$Slic3r::GUI::datadir/simple.ini") {
my $config = Slic3r::Config->load("$Slic3r::GUI::datadir/simple.ini");
$ini->{simple} = $config->as_ini->{_};
}
foreach my $section (qw(print filament printer)) {
my %presets = wxTheApp->presets($section);
foreach my $preset_name (keys %presets) {
my $config = Slic3r::Config->load($presets{$preset_name});
$ini->{"$section:$preset_name"} = $config->as_ini->{_};
my @presets = wxTheApp->presets($section);
foreach my $preset (@presets) {
$ini->{"$section:" . $preset->name} = $preset->load_config->as_ini->{_};
}
}
@ -617,15 +551,6 @@ sub load_configbundle {
$Slic3r::GUI::Settings->{presets} = $ini->{presets};
wxTheApp->save_settings;
}
if ($ini->{simple}) {
my $config = Slic3r::Config->load_ini_hash($ini->{simple});
$config->save("$Slic3r::GUI::datadir/simple.ini");
if ($self->{mode} eq 'simple') {
foreach my $tab (values %{$self->{options_tabs}}) {
$tab->load_config($config) for values %{$self->{options_tabs}};
}
}
}
my $imported = 0;
INI_BLOCK: foreach my $ini_category (sort keys %$ini) {
next unless $ini_category =~ /^(print|filament|printer):(.+)$/;
@ -634,11 +559,11 @@ sub load_configbundle {
next if $skip_no_id && !$config->get($section . "_settings_id");
{
my %current_presets = Slic3r::GUI->presets($section);
my @current_presets = Slic3r::GUI->presets($section);
my %current_ids = map { $_ => 1 }
grep $_,
map Slic3r::Config->load($_)->get($section . "_settings_id"),
values %current_presets;
map $_->load_config->get($section . "_settings_id"),
@current_presets;
next INI_BLOCK if exists $current_ids{$config->get($section . "_settings_id")};
}
@ -646,150 +571,34 @@ sub load_configbundle {
Slic3r::debugf "Imported %s preset %s\n", $section, $preset_name;
$imported++;
}
if ($self->{mode} eq 'expert') {
foreach my $tab (values %{$self->{options_tabs}}) {
$tab->load_presets;
}
}
$self->{plater}->load_presets;
return if !$imported;
my $message = sprintf "%d presets successfully imported.", $imported;
if ($self->{mode} eq 'simple' && $Slic3r::GUI::Settings->{_}{mode} eq 'expert') {
Slic3r::GUI::show_info($self, "$message You need to restart Slic3r to make the changes effective.");
} else {
Slic3r::GUI::show_info($self, $message);
}
Slic3r::GUI::show_info($self, $message);
}
sub load_config {
my $self = shift;
my ($config) = @_;
my ($self, $config) = @_;
foreach my $tab (values %{$self->{options_tabs}}) {
$tab->load_config($config);
}
if ($self->{plater}) {
$self->{plater}->on_config_change($config);
}
$self->{plater}->load_config($config);
}
sub config_wizard {
my $self = shift;
return unless $self->check_unsaved_changes;
if (my $config = Slic3r::GUI::ConfigWizard->new($self)->run) {
if ($self->{mode} eq 'expert') {
for my $tab (values %{$self->{options_tabs}}) {
$tab->select_default_preset;
}
} else {
# TODO: select default settings in simple mode
}
$self->load_config($config);
if ($self->{mode} eq 'expert') {
for my $tab (values %{$self->{options_tabs}}) {
$tab->save_preset('My Settings');
}
foreach my $group (qw(print filament printer)) {
my $name = 'My Settings';
$config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $group, $name);
$Slic3r::GUI::Settings->{presets}{$group} = "$name.ini";
$self->{plater}->load_presets;
$self->{plater}->select_preset_by_name($name, $group);
}
}
}
=head2 config
This method collects all config values from the tabs and merges them into a single config object.
=cut
sub config {
my $self = shift;
return Slic3r::Config->new_from_defaults
if !exists $self->{options_tabs}{print}
|| !exists $self->{options_tabs}{filament}
|| !exists $self->{options_tabs}{printer};
# retrieve filament presets and build a single config object for them
my $filament_config;
if (!$self->{plater} || $self->{plater}->filament_presets == 1 || $self->{mode} eq 'simple') {
$filament_config = $self->{options_tabs}{filament}->config;
} else {
my $i = -1;
foreach my $preset_idx ($self->{plater}->filament_presets) {
$i++;
my $config;
if ($preset_idx == $self->{options_tabs}{filament}->current_preset) {
# the selected preset for this extruder is the one in the tab
# use the tab's config instead of the preset in case it is dirty
# perhaps plater shouldn't expose dirty presets at all in multi-extruder environments.
$config = $self->{options_tabs}{filament}->config;
} else {
my $preset = $self->{options_tabs}{filament}->get_preset($preset_idx);
$config = $self->{options_tabs}{filament}->get_preset_config($preset);
}
if (!$filament_config) {
$filament_config = $config->clone;
next;
}
foreach my $opt_key (@{$config->get_keys}) {
my $value = $filament_config->get($opt_key);
next unless ref $value eq 'ARRAY';
$value->[$i] = $config->get($opt_key)->[0];
$filament_config->set($opt_key, $value);
}
}
}
my $config = Slic3r::Config->merge(
Slic3r::Config->new_from_defaults,
$self->{options_tabs}{print}->config,
$self->{options_tabs}{printer}->config,
$filament_config,
);
if ($self->{mode} eq 'simple') {
# set some sensible defaults
$config->set('first_layer_height', $config->nozzle_diameter->[0]);
$config->set('avoid_crossing_perimeters', 1);
$config->set('infill_every_layers', 10);
} else {
my $extruders_count = $self->{options_tabs}{printer}{extruders_count};
$config->set("${_}_extruder", min($config->get("${_}_extruder"), $extruders_count))
for qw(perimeter infill solid_infill support_material support_material_interface);
}
return $config;
}
sub filament_preset_names {
my ($self) = @_;
if ($self->{mode} eq 'simple') {
return '';
}
return map $self->{options_tabs}{filament}->get_preset($_)->name,
$self->{plater}->filament_presets;
}
sub check_unsaved_changes {
my $self = shift;
my @dirty = ();
foreach my $tab (values %{$self->{options_tabs}}) {
push @dirty, $tab->title if $tab->is_dirty;
}
if (@dirty) {
my $titles = join ', ', @dirty;
my $confirm = Wx::MessageDialog->new($self, "You have unsaved changes ($titles). Discard changes and continue anyway?",
'Unsaved Presets', wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT);
return ($confirm->ShowModal == wxID_YES);
}
return 1;
}
sub select_tab {
my ($self, $tab) = @_;
$self->{tabpanel}->SetSelection($tab);
@ -798,9 +607,8 @@ sub select_tab {
# Set a camera direction, zoom to all objects.
sub select_view {
my ($self, $direction) = @_;
if (! $self->{no_plater}) {
$self->{plater}->select_view($direction);
}
$self->{plater}->select_view($direction);
}
sub _append_menu_item {

View File

@ -1,4 +1,4 @@
# A dialog group object. Used by the Tab, SimpleTab, Preferences dialog, ManualControlDialog etc.
# A dialog group object. Used by the PresetEditor, Preferences dialog, ManualControlDialog etc.
package Slic3r::GUI::OptionsGroup;
use Moo;

View File

@ -6,16 +6,17 @@ use warnings;
use utf8;
use File::Basename qw(basename dirname);
use List::Util qw(sum first max);
use List::Util qw(sum first max none any);
use Slic3r::Geometry qw(X Y Z MIN MAX scale unscale deg2rad rad2deg);
use LWP::UserAgent;
use threads::shared qw(shared_clone);
use Wx qw(:button :cursor :dialog :filedialog :keycode :icon :font :id :listctrl :misc
use Wx qw(:button :cursor :dialog :filedialog :keycode :icon :font :id :misc
:panel :sizer :toolbar :window wxTheApp :notebook :combobox);
use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED
EVT_LIST_ITEM_DESELECTED EVT_LIST_ITEM_SELECTED EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL
use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_KEY_DOWN EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL
EVT_CHOICE EVT_COMBOBOX EVT_TIMER EVT_NOTEBOOK_PAGE_CHANGED);
use base 'Wx::Panel';
use base qw(Wx::Panel Class::Accessor);
__PACKAGE__->mk_accessors(qw(presets));
use constant TB_ADD => &Wx::NewId;
use constant TB_REMOVE => &Wx::NewId;
@ -42,15 +43,13 @@ our $PROCESS_COMPLETED_EVENT : shared = Wx::NewEventType;
use constant FILAMENT_CHOOSERS_SPACING => 0;
use constant PROCESS_DELAY => 0.5 * 1000; # milliseconds
my $PreventListEvents = 0;
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
$self->{config} = Slic3r::Config->new_from_defaults(qw(
bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width
serial_port serial_speed octoprint_host octoprint_apikey
serial_port serial_speed octoprint_host octoprint_apikey overridable
));
$self->{model} = Slic3r::Model->new;
$self->{print} = Slic3r::Print->new;
@ -194,23 +193,6 @@ sub new {
}
}
$self->{list} = Wx::ListView->new($self, -1, wxDefaultPosition, wxDefaultSize,
wxLC_SINGLE_SEL | wxLC_REPORT | wxBORDER_SUNKEN | wxTAB_TRAVERSAL | wxWANTS_CHARS );
$self->{list}->InsertColumn(0, "Name", wxLIST_FORMAT_LEFT, 145);
$self->{list}->InsertColumn(1, "Copies", wxLIST_FORMAT_CENTER, 45);
$self->{list}->InsertColumn(2, "Scale", wxLIST_FORMAT_CENTER, wxLIST_AUTOSIZE_USEHEADER);
EVT_LIST_ITEM_SELECTED($self, $self->{list}, \&list_item_selected);
EVT_LIST_ITEM_DESELECTED($self, $self->{list}, \&list_item_deselected);
EVT_LIST_ITEM_ACTIVATED($self, $self->{list}, \&list_item_activated);
EVT_KEY_DOWN($self->{list}, sub {
my ($list, $event) = @_;
if ($event->GetKeyCode == WXK_TAB) {
$list->Navigate($event->ShiftDown ? &Wx::wxNavigateBackward : &Wx::wxNavigateForward);
} else {
$event->Skip;
}
});
# right pane buttons
$self->{btn_export_gcode} = Wx::Button->new($self, -1, "Export G-code…", wxDefaultPosition, [-1, 30], wxBU_LEFT);
$self->{btn_print} = Wx::Button->new($self, -1, "Print…", wxDefaultPosition, [-1, 30], wxBU_LEFT);
@ -314,7 +296,7 @@ sub new {
$_->SetDropTarget(Slic3r::GUI::Plater::DropTarget->new($self))
for grep defined($_),
$self, $self->{canvas}, $self->{canvas3D}, $self->{preview3D}, $self->{list};
$self, $self->{canvas}, $self->{canvas3D}, $self->{preview3D};
EVT_COMMAND($self, -1, $THUMBNAIL_DONE_EVENT, sub {
my ($self, $event) = @_;
@ -365,32 +347,52 @@ sub new {
$self->update;
{
my $presets;
if ($self->GetFrame->{mode} eq 'expert') {
$presets = $self->{presets_sizer} = Wx::FlexGridSizer->new(3, 2, 1, 2);
$presets->AddGrowableCol(1, 1);
$presets->SetFlexibleDirection(wxHORIZONTAL);
my %group_labels = (
print => 'Print settings',
filament => 'Filament',
printer => 'Printer',
);
$self->{preset_choosers} = {};
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);
$self->{preset_choosers}{$group} = [$choice];
# setup the listener
EVT_COMBOBOX($choice, $choice, sub {
my ($choice) = @_;
wxTheApp->CallAfter(sub {
$self->_on_select_preset($group, $choice);
});
my $presets = $self->{presets_sizer} = Wx::FlexGridSizer->new(3, 3, 1, 2);
$presets->AddGrowableCol(1, 1);
$presets->SetFlexibleDirection(wxHORIZONTAL);
my %group_labels = (
print => 'Print settings',
filament => 'Filament',
printer => 'Printer',
);
$self->{preset_choosers} = {};
for my $group (qw(print filament printer)) {
# label
my $text = Wx::StaticText->new($self, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT);
$text->SetFont($Slic3r::GUI::small_font);
# dropdown control
my $choice = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY);
$self->{preset_choosers}{$group} = [$choice];
# setup the listener
EVT_COMBOBOX($choice, $choice, sub {
my ($choice) = @_;
wxTheApp->CallAfter(sub {
$self->_on_select_preset($group);
});
$presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4);
$presets->Add($choice, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM, 0);
}
});
# settings button
my $settings_btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG),
wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
EVT_BUTTON($self, $settings_btn, sub {
$self->show_preset_editor($group, 0);
});
$presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4);
$presets->Add($choice, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM, 0);
$presets->Add($settings_btn, 0, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxLEFT, 3);
}
{
$self->{settings_override_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self,
on_change => sub {
$self->config_changed;
});
$self->{settings_override_panel}->set_editable(0);
$self->{settings_override_config} = Slic3r::Config->new;
$self->{settings_override_panel}->set_default_config($self->{settings_override_config});
$self->{settings_override_panel}->set_config($self->{settings_override_config});
}
my $object_info_sizer;
@ -405,6 +407,8 @@ sub new {
$object_info_sizer->Add($grid_sizer, 0, wxEXPAND);
my @info = (
name => "Name",
copies => "Copies",
size => "Size",
volume => "Volume",
facets => "Facets",
@ -473,7 +477,7 @@ sub new {
my $right_sizer = Wx::BoxSizer->new(wxVERTICAL);
$right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets;
$right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM, 5);
$right_sizer->Add($self->{list}, 1, wxEXPAND, 5);
$right_sizer->Add($self->{settings_override_panel}, 1, wxEXPAND, 5);
$right_sizer->Add($object_info_sizer, 0, wxEXPAND, 0);
$right_sizer->Add($print_info_sizer, 0, wxEXPAND, 0);
$right_sizer->Hide($print_info_sizer);
@ -492,35 +496,83 @@ sub new {
$self->SetSizer($sizer);
}
$self->load_presets;
$self->_on_select_preset($_) for qw(printer filament print);
return $self;
}
# sets the callback
sub on_select_preset {
my ($self, $cb) = @_;
$self->{on_select_preset} = $cb;
sub _on_select_preset {
my ($self, $group) = @_;
my @presets = map $self->presets->{$group}[scalar $_->GetSelection],
@{$self->{preset_choosers}{$group}};
$Slic3r::GUI::Settings->{presets}{$group} = $presets[0]->name;
$Slic3r::GUI::Settings->{presets}{"${group}_${_}"} = $presets[$_]->name
for 1..$#presets;
wxTheApp->save_settings;
my $config = $self->config;
$self->on_extruders_change(scalar @{$config->get('nozzle_diameter')});
foreach my $opt_key (@{$self->{config}->diff($config)}) {
# Ignore overrides. No need to set them in our config; we'll use them directly below.
next if $opt_key eq 'overrides';
$self->{config}->set($opt_key, $config->get($opt_key));
if ($opt_key eq 'bed_shape') {
$self->{canvas}->update_bed_size;
$self->{canvas3D}->update_bed_size if $self->{canvas3D};
$self->{preview3D}->set_bed_shape($self->{config}->bed_shape)
if $self->{preview3D};
$self->update;
} elsif ($opt_key eq 'serial_port') {
if ($config->get('serial_port')) {
$self->{btn_print}->Show;
} else {
$self->{btn_print}->Hide;
}
$self->Layout;
} elsif ($opt_key eq 'octoprint_host') {
if ($config->get('octoprint_host')) {
$self->{btn_send_gcode}->Show;
} else {
$self->{btn_send_gcode}->Hide;
}
}
}
if ($group eq 'print') {
$self->{settings_override_config}->clear;
my $overridable = $config->get('overridable');
if ($overridable) {
$self->{settings_override_config}->set($_, $config->get($_))
for @$overridable;
}
$self->{settings_override_panel}->update_optgroup;
} elsif ($group eq 'printer') {
# reload print and filament settings to honor their compatible_printer options
$self->load_presets;
}
return if !$self->GetFrame->is_loaded;
$self->config_changed;
}
sub _on_select_preset {
my $self = shift;
my ($group, $choice) = @_;
# if user changed filament preset, don't propagate this to the tabs
if ($group eq 'filament' && @{$self->{preset_choosers}{filament}} > 1) {
my @filament_presets = $self->filament_presets;
$Slic3r::GUI::Settings->{presets}{filament} = $choice->GetString($filament_presets[0]) . ".ini";
$Slic3r::GUI::Settings->{presets}{"filament_${_}"} = $choice->GetString($filament_presets[$_])
for 1 .. $#filament_presets;
wxTheApp->save_settings;
return;
}
# call GetSelection() in scalar context as it's context-aware
$self->{on_select_preset}->($group, scalar $choice->GetSelection)
if $self->{on_select_preset};
# get new config and generate on_config_change() event for updating plater and other things
$self->on_config_change($self->GetFrame->config);
sub load_config {
my ($self, $config) = @_;
# This method is called with the CLI options.
# We add them to the visible overrides.
$self->{settings_override_config}->apply($config);
$self->{settings_override_panel}->update_optgroup;
$self->config_changed;
}
sub GetFrame {
@ -528,60 +580,188 @@ sub GetFrame {
return &Wx::GetTopLevelParent($self);
}
sub update_presets {
my $self = shift;
my ($group, $presets, $selected, $is_dirty) = @_;
sub load_presets {
my ($self) = @_;
my @choosers = @{ $self->{preset_choosers}{$group} };
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;
$is_dirty = 0;
}
$choice->Clear;
foreach my $preset (@$presets) {
my $bitmap;
if ($group eq 'filament') {
my $config = $preset->config(['filament_colour']);
my $rgb_hex = $config->filament_colour->[0];
if ($preset->default) {
$bitmap = Wx::Bitmap->new($Slic3r::var->("spool.png"), wxBITMAP_TYPE_PNG);
} else {
$rgb_hex =~ s/^#//;
my @rgb = unpack 'C*', pack 'H*', $rgb_hex;
my $image = Wx::Image->new(16,16);
$image->SetRGB(Wx::Rect->new(0,0,16,16), @rgb);
$bitmap = Wx::Bitmap->new($image);
$self->presets({});
my $selected_printer_name;
foreach my $group (qw(printer filament print)) {
my @presets = wxTheApp->presets($group);
if ($group eq 'filament' || $group eq 'print') {
my %printer_names = map { $_->name => 1 } @{ $self->presets->{printer} };
# Skip presets not compatible with the selected printer, if they
# have other compatible printers configured (and at least one of them exists).
for (my $i = 0; $i <= $#presets; ++$i) {
my $config = $presets[$i]->config;
next if !$config->has('compatible_printers');
my @compat = @{$config->compatible_printers};
if (@compat
&& (none { $_ eq $selected_printer_name } @compat)
&& (any { $printer_names{$_} } @compat)) {
splice @presets, $i, 1;
--$i;
}
} 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 (!@presets) {
unshift @presets, Slic3r::GUI::Preset->new(
default => 1,
name => '- default -',
);
}
if ($selected <= $#$presets) {
my $preset_name = $choice->GetString($selected);
if ($is_dirty) {
$choice->SetString($selected, "$preset_name (modified)");
$self->presets->{$group} = [@presets];
# get the wxChoice objects for this group
my @choosers = @{ $self->{preset_choosers}{$group} };
# find the currently selected one(s) according to the saved file
my @sel = ();
if (my $current = $Slic3r::GUI::Settings->{presets}{$group}) {
push @sel, grep defined, first { $presets[$_]->name eq $current } 0..$#presets;
}
for my $i (1..(@choosers-1)) {
if (my $current = $Slic3r::GUI::Settings->{presets}{"${group}_$i"}) {
push @sel, grep defined, first { $presets[$_]->name eq $current } 0..$#presets;
}
}
@sel = (0) if !@sel;
# populate the wxChoice objects
foreach my $choice (@choosers) {
$choice->Clear;
foreach my $preset (@presets) {
# load/generate the proper icon
my $bitmap;
if ($group eq 'filament') {
my $config = $preset->config;
if ($preset->default || !$config->has('filament_colour')) {
$bitmap = Wx::Bitmap->new($Slic3r::var->("spool.png"), wxBITMAP_TYPE_PNG);
} else {
my $rgb_hex = $config->filament_colour->[0];
$rgb_hex =~ s/^#//;
my @rgb = unpack 'C*', pack 'H*', $rgb_hex;
my $image = Wx::Image->new(16,16);
$image->SetRGB(Wx::Rect->new(0,0,16,16), @rgb);
$bitmap = Wx::Bitmap->new($image);
}
} 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);
}
my $selected = shift @sel;
if ($selected <= $#presets) {
# call SetSelection() only after SetString() otherwise the new string
# won't be picked up as the visible string
$choice->SetSelection($selected);
my $preset_name = $choice->GetString($selected);
$self->{print}->placeholder_parser->set("${group}_preset", $preset_name);
$selected_printer_name = $preset_name if $group eq 'printer';
}
# call SetSelection() only after SetString() otherwise the new string
# won't be picked up as the visible string
$choice->SetSelection($selected);
$self->{print}->placeholder_parser->set("${group}_preset", $preset_name);
}
}
#$self->_on_select_preset($_) for qw(printer filament print);
}
sub filament_presets {
my $self = shift;
sub select_preset_by_name {
my ($self, $name, $group, $n) = @_;
# force scalar context for GetSelection() as it's context-aware
return map scalar($_->GetSelection), @{ $self->{preset_choosers}{filament} };
# $n is optional
my $presets = $self->presets->{$group};
my $i = first { $presets->[$_]->name eq $name } 0..$#$presets;
return if !defined $i;
my $choosers = $self->{preset_choosers}{$group};
if (defined $n && $n <= $#$choosers) {
$choosers->[$n]->SetSelection($i);
} else {
$_->SetSelection($i) for @$choosers;
}
$self->_on_select_preset($group);
}
sub selected_presets {
my ($self, $group) = @_;
my %presets;
foreach my $group (qw(printer filament print)) {
my @i = map scalar($_->GetSelection), @{ $self->{preset_choosers}{$group} };
$presets{$group} = [ @{$self->presets->{$group}}[@i] ];
}
return $group ? @{$presets{$group}} : %presets;
}
sub show_preset_editor {
my ($self, $group, $i) = @_;
my $class = "Slic3r::GUI::PresetEditorDialog::" . ucfirst($group);
my $dlg = $class->new($self);
my @presets = $self->selected_presets($group);
$dlg->preset_editor->select_preset_by_name($presets[$i // 0]->name);
$dlg->ShowModal;
# Re-load the presets as they might have changed.
$self->load_presets;
# Select the preset that was last selected in the editor.
$self->select_preset_by_name
($dlg->preset_editor->get_current_preset->name, $group, $i, 1);
}
# Returns the current config by merging the selected presets and the overrides.
sub config {
my ($self) = @_;
# use a DynamicConfig because FullPrintConfig is not enough
my $config = Slic3r::Config->new_from_defaults;
# get defaults also for the values tracked by the Plater's config
# (for example 'overridable')
$config->apply(Slic3r::Config->new_from_defaults(@{$self->{config}->get_keys}));
my %classes = map { $_ => "Slic3r::GUI::PresetEditor::".ucfirst($_) }
qw(print filament printer);
my %presets = $self->selected_presets;
$config->apply($_->config([ $classes{printer}->options ]))
for @{ $presets{printer} };
if (@{ $presets{filament} }) {
my @opt_keys = ($classes{filament}->options, $classes{filament}->overriding_options);
my $filament_config = shift(@{ $presets{filament} })
->config(\@opt_keys);
my $i = 1;
for my $preset (@{ $presets{filament} }) {
my $config = $preset->config(\@opt_keys);
foreach my $opt_key (@{$config->get_keys}) {
if ($filament_config->has($opt_key)) {
my $value = $filament_config->get($opt_key);
next unless ref $value eq 'ARRAY';
$value->[$i] = $config->get($opt_key)->[0];
$filament_config->set($opt_key, $value);
}
}
++$i;
}
$config->apply($filament_config);
}
$config->apply($_->config([ $classes{print}->options ]))
for @{ $presets{print} };
$config->apply($self->{settings_override_config});
return $config;
}
sub add {
@ -712,18 +892,7 @@ sub load_model_objects {
);
}
foreach my $obj_idx (@obj_idx) {
my $object = $self->{objects}[$obj_idx];
my $model_object = $self->{model}->objects->[$obj_idx];
$self->{list}->InsertStringItem($obj_idx, $object->name);
$self->{list}->SetItemFont($obj_idx, Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL))
if $self->{list}->can('SetItemFont'); # legacy code for wxPerl < 0.9918 not supporting SetItemFont()
$self->{list}->SetItem($obj_idx, 1, $model_object->instances_count);
$self->{list}->SetItem($obj_idx, 2, ($model_object->instances->[0]->scaling_factor * 100) . "%");
$self->make_thumbnail($obj_idx);
}
$self->make_thumbnail($_) for @obj_idx;
$self->arrange if $need_arrange;
$self->update;
@ -731,8 +900,6 @@ sub load_model_objects {
$self->{canvas3D}->zoom_to_volumes
if $self->{canvas3D};
$self->{list}->Update;
$self->{list}->Select($obj_idx[-1], 1);
$self->object_list_changed;
$self->schedule_background_process;
@ -766,7 +933,6 @@ sub remove {
splice @{$self->{objects}}, $obj_idx, 1;
$self->{model}->delete_object($obj_idx);
$self->{print}->delete_object($obj_idx);
$self->{list}->DeleteItem($obj_idx);
$self->object_list_changed;
$self->select_object(undef);
@ -786,7 +952,6 @@ sub reset {
@{$self->{objects}} = ();
$self->{model}->clear_objects;
$self->{print}->clear_objects;
$self->{list}->DeleteAllItems;
$self->object_list_changed;
$self->select_object(undef);
@ -808,7 +973,6 @@ sub increase {
);
$self->{print}->objects->[$obj_idx]->add_copy($instance->offset);
}
$self->{list}->SetItem($obj_idx, 1, $model_object->instances_count);
# only autoarrange if user has autocentering enabled
$self->stop_background_process;
@ -833,15 +997,10 @@ sub decrease {
$model_object->delete_last_instance;
$self->{print}->objects->[$obj_idx]->delete_last_copy;
}
$self->{list}->SetItem($obj_idx, 1, $model_object->instances_count);
} else {
$self->remove;
}
if ($self->{objects}[$obj_idx]) {
$self->{list}->Select($obj_idx, 0);
$self->{list}->Select($obj_idx, 1);
}
$self->update;
$self->schedule_background_process;
}
@ -1006,8 +1165,8 @@ sub changescale {
$model_instance->scaling_factor*100, $self);
return if !$scale || $scale !~ /^\d*(?:\.\d*)?$/ || $scale < 0;
}
$self->{list}->SetItem($obj_idx, 2, "$scale%");
return if !$scale || $scale < 0;
$scale /= 100; # turn percent into factor
my $variation = $scale / $model_instance->scaling_factor;
@ -1035,7 +1194,7 @@ sub arrange {
$self->pause_background_process;
my $bb = Slic3r::Geometry::BoundingBoxf->new_from_points($self->{config}->bed_shape);
my $success = $self->{model}->arrange_objects($self->GetFrame->config->min_object_distance, $bb);
my $success = $self->{model}->arrange_objects($self->config->min_object_distance, $bb);
# ignore arrange failures on purpose: user has visual feedback and we don't need to warn him
# when parts don't fit in print bed
@ -1086,6 +1245,17 @@ sub split_object {
$self->load_model_objects(@model_objects);
}
sub config_changed {
my ($self) = @_;
if ($Slic3r::GUI::autosave) {
$self->config->save($Slic3r::GUI::autosave);
}
# (re) start timer
$self->schedule_background_process;
}
sub schedule_background_process {
my ($self) = @_;
@ -1117,7 +1287,7 @@ sub async_apply_config {
$self->pause_background_process;
# apply new config
my $invalidated = $self->{print}->apply_config($self->GetFrame->config);
my $invalidated = $self->{print}->apply_config($self->config);
return if !$Slic3r::GUI::Settings->{_}{background_processing};
@ -1151,7 +1321,7 @@ sub start_background_process {
# don't start process thread if config is not valid
eval {
# this will throw errors if config is not valid
$self->GetFrame->config->validate;
$self->config->validate;
$self->{print}->validate;
};
if ($@) {
@ -1239,14 +1409,14 @@ sub export_gcode {
# (we assume that if it is running, config is valid)
eval {
# this will throw errors if config is not valid
$self->GetFrame->config->validate;
$self->config->validate;
$self->{print}->validate;
};
Slic3r::GUI::catch_error($self) and return;
# apply config and validate print
my $config = $self->GetFrame->config;
my $config = $self->config;
eval {
# this will throw errors if config is not valid
$config->validate;
@ -1410,15 +1580,15 @@ sub on_export_completed {
sub do_print {
my ($self) = @_;
my $printer_tab = $self->GetFrame->{options_tabs}{printer};
my $printer_name = $printer_tab->get_current_preset->name;
my $controller = $self->GetFrame->{controller} or return;
my $controller = $self->GetFrame->{controller};
my $printer_panel = $controller->add_printer($printer_name, $printer_tab->config);
my %current_presets = $self->selected_presets;
my $printer_name = $current_presets{printer}->[0]->name;
my $printer_panel = $controller->add_printer($printer_name, $self->config);
my $filament_stats = $self->{print}->filament_stats;
my @filament_names = $self->GetFrame->filament_preset_names;
$filament_stats = { map { $filament_names[$_] => $filament_stats->{$_} } keys %$filament_stats };
$filament_stats = { map { $current_presets{filament}[$_]->name => $filament_stats->{$_} } keys %$filament_stats };
$printer_panel->load_print_job($self->{print_file}, $filament_stats);
$self->GetFrame->select_tab(1);
@ -1620,18 +1790,27 @@ sub on_extruders_change {
# 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);
# settings button
my $settings_btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG),
wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
# setup the listener
# insert new row into sizer
$self->{presets_sizer}->Insert(6 + ($#$choices-1)*3, 0, 0);
$self->{presets_sizer}->Insert(7 + ($#$choices-1)*3, $choice, 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING);
$self->{presets_sizer}->Insert(8 + ($#$choices-1)*3, $settings_btn, 0, wxEXPAND | wxLEFT, 4);
# setup the listeners
EVT_COMBOBOX($choice, $choice, sub {
my ($choice) = @_;
wxTheApp->CallAfter(sub {
$self->_on_select_preset('filament', $choice);
$self->_on_select_preset('filament');
});
});
EVT_BUTTON($self, $settings_btn, sub {
$self->show_preset_editor('filament', $#$choices);
});
# initialize selection
my $i = first { $choice->GetString($_) eq ($Slic3r::GUI::Settings->{presets}{"filament_" . $#$choices} || '') } 0 .. $#presets;
$choice->SetSelection($i || 0);
@ -1639,79 +1818,21 @@ sub on_extruders_change {
# remove unused choices if any
while (@$choices > $num_extruders) {
$self->{presets_sizer}->Remove(4 + ($#$choices-1)*2); # label
$self->{presets_sizer}->Remove(4 + ($#$choices-1)*2); # wxChoice
my $i = 6 + ($#$choices-1)*3;
$self->{presets_sizer}->Remove($i); # label
$self->{presets_sizer}->Remove($i); # wxChoice
my $settings_btn = $self->{presets_sizer}->GetItem($i)->GetWindow;
$self->{presets_sizer}->Remove($i); # settings btn
$settings_btn->Destroy;
$choices->[-1]->Destroy;
pop @$choices;
}
$self->Layout;
}
sub on_config_change {
my $self = shift;
my ($config) = @_;
foreach my $opt_key (@{$self->{config}->diff($config)}) {
$self->{config}->set($opt_key, $config->get($opt_key));
if ($opt_key eq 'bed_shape') {
$self->{canvas}->update_bed_size;
$self->{canvas3D}->update_bed_size if $self->{canvas3D};
$self->{preview3D}->set_bed_shape($self->{config}->bed_shape)
if $self->{preview3D};
$self->update;
} elsif ($opt_key eq 'serial_port') {
if ($config->get('serial_port')) {
$self->{btn_print}->Show;
} else {
$self->{btn_print}->Hide;
}
$self->Layout;
} elsif ($opt_key eq 'octoprint_host') {
if ($config->get('octoprint_host')) {
$self->{btn_send_gcode}->Show;
} else {
$self->{btn_send_gcode}->Hide;
}
$self->Layout;
}
}
if ($self->{"right_sizer"}) {
$self->{"right_sizer"}->Hide($self->{"sliced_info_box"});
$self->{"right_sizer"}->Layout;
}
return if !$self->GetFrame->is_loaded;
# (re)start timer
$self->schedule_background_process;
}
sub list_item_deselected {
my ($self, $event) = @_;
return if $PreventListEvents;
if ($self->{list}->GetFirstSelected == -1) {
$self->select_object(undef);
$self->refresh_canvases;
}
}
sub list_item_selected {
my ($self, $event) = @_;
return if $PreventListEvents;
my $obj_idx = $event->GetIndex;
$self->select_object($obj_idx);
$self->refresh_canvases;
}
sub list_item_activated {
my ($self, $event, $obj_idx) = @_;
$obj_idx //= $event->GetIndex;
$self->object_settings_dialog($obj_idx);
}
sub object_cut_dialog {
my $self = shift;
my ($obj_idx) = @_;
@ -1818,9 +1939,17 @@ sub selection_changed {
if ($self->{object_info_size}) { # have we already loaded the info pane?
if ($have_sel) {
$self->{object_info_name}->SetLabel($object->name);
my $model_object = $self->{model}->objects->[$obj_idx];
$self->{object_info_copies}->SetLabel($model_object->instances_count);
my $model_instance = $model_object->instances->[0];
$self->{object_info_size}->SetLabel(sprintf("%.2f x %.2f x %.2f", @{$model_object->instance_bounding_box(0)->size}));
{
my $size_string = sprintf "%.2f x %.2f x %.2f", @{$model_object->instance_bounding_box(0)->size};
if ($model_instance->scaling_factor != 1) {
$size_string .= sprintf " (%s%%)", $model_instance->scaling_factor * 100;
}
$self->{object_info_size}->SetLabel($size_string);
}
$self->{object_info_materials}->SetLabel($model_object->materials_count);
my $raw_mesh = $model_object->raw_mesh;
@ -1845,7 +1974,7 @@ sub selection_changed {
$self->{object_info_facets}->SetLabel($object->facets);
}
} else {
$self->{"object_info_$_"}->SetLabel("") for qw(size volume facets materials manifold);
$self->{"object_info_$_"}->SetLabel("") for qw(name copies size volume facets materials manifold);
$self->{object_info_manifold_warning_icon}->Hide;
$self->{object_info_manifold}->SetToolTipString("");
}
@ -1862,15 +1991,6 @@ sub select_object {
$_->selected(0) for @{ $self->{objects} };
if (defined $obj_idx) {
$self->{objects}->[$obj_idx]->selected(1);
# We use this flag to avoid circular event handling
# Select() happens to fire a wxEVT_LIST_ITEM_SELECTED on Windows,
# whose event handler calls this method again and again and again
$PreventListEvents = 1;
$self->{list}->Select($obj_idx, 1);
$PreventListEvents = 0;
} else {
# TODO: deselect all in list
}
$self->selection_changed(1);
}
@ -1895,7 +2015,7 @@ sub validate_config {
my $self = shift;
eval {
$self->GetFrame->config->validate;
$self->config->validate;
};
return 0 if Slic3r::GUI::catch_error($self);
return 1;

View File

@ -1,5 +1,4 @@
# Included in ObjectSettingsDialog -> ObjectPartsPanel.
# Maintains, displays, adds and removes overrides of slicing parameters for an object and its modifier mesh.
# Maintains, displays, adds and removes overrides of slicing parameters.
package Slic3r::GUI::Plater::OverrideSettingsPanel;
use strict;
@ -19,10 +18,11 @@ use constant ICON_MODIFIERMESH => 2;
sub new {
my $class = shift;
my ($parent, %params) = @_;
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, $params{size} // wxDefaultSize, wxTAB_TRAVERSAL);
$self->{default_config} = Slic3r::Config->new;
$self->{config} = Slic3r::Config->new;
$self->{on_change} = $params{on_change};
$self->{editable} = 1;
$self->{fixed_options} = {};
$self->{sizer} = Wx::BoxSizer->new(wxVERTICAL);
@ -64,17 +64,20 @@ sub new {
return $self;
}
# Sets the config used to get the default values for user-added options.
sub set_default_config {
my ($self, $config) = @_;
$self->{default_config} = $config;
}
# Sets the target config, whose options will be displayed in the OptionsGroup.
sub set_config {
my ($self, $config) = @_;
$self->{config} = $config;
$self->update_optgroup;
}
# Sets the options listed in the Add button.
sub set_opt_keys {
my ($self, $opt_keys) = @_;
@ -85,6 +88,7 @@ sub set_opt_keys {
$self->{options} = [ sort { $self->{option_labels}{$a} cmp $self->{option_labels}{$b} } @$opt_keys ];
}
# Sets the options that user can't remove.
sub set_fixed_options {
my ($self, $opt_keys) = @_;
$self->{fixed_options} = { map {$_ => 1} @$opt_keys };
@ -97,6 +101,8 @@ sub update_optgroup {
$self->{options_sizer}->Clear(1);
return if !defined $self->{config};
$self->{btn_add}->Show($self->{editable});
my %categories = ();
foreach my $opt_key (@{$self->{config}->get_keys}) {
my $category = $Slic3r::Config::Options->{$opt_key}{category};
@ -104,7 +110,8 @@ sub update_optgroup {
push @{$categories{$category}}, $opt_key;
}
foreach my $category (sort keys %categories) {
my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
my $optgroup;
$optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
parent => $self,
title => $category,
config => $self->{config},
@ -116,10 +123,11 @@ sub update_optgroup {
extra_column => sub {
my ($line) = @_;
my $opt_key = $line->get_options->[0]->opt_id; # we assume that we have one option per line
my $opt_id = $line->get_options->[0]->opt_id; # we assume that we have one option per line
my ($opt_key, $opt_index) = @{ $optgroup->_opt_map->{$opt_id} };
# disallow deleting fixed options
return undef if $self->{fixed_options}{$opt_key};
return undef if $self->{fixed_options}{$opt_key} || !$self->{editable};
my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG),
wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE);
@ -132,7 +140,9 @@ sub update_optgroup {
},
);
foreach my $opt_key (sort @{$categories{$category}}) {
$optgroup->append_single_option_line($opt_key);
# For array options we override the first value.
my $opt_index = (ref($self->{config}->get($opt_key)) eq 'ARRAY') ? 0 : -1;
$optgroup->append_single_option_line($opt_key, $opt_index);
}
$self->{options_sizer}->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM, 0);
}
@ -154,4 +164,11 @@ sub disable {
$self->Disable;
}
# Shows or hides the Add button.
sub set_editable {
my ($self, $editable) = @_;
$self->{editable} = $editable;
}
1;

View File

@ -20,16 +20,6 @@ sub new {
},
label_width => 200,
);
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'mode',
type => 'select',
label => 'Mode',
tooltip => 'Choose between a simpler, basic mode and an expert mode with more options and more complicated interface.',
labels => ['Simple','Expert'],
values => ['simple','expert'],
default => $Slic3r::GUI::Settings->{_}{mode},
width => 100,
));
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'version_check',
type => 'bool',

52
lib/Slic3r/GUI/Preset.pm Normal file
View File

@ -0,0 +1,52 @@
package Slic3r::GUI::Preset;
use Moo;
use Unicode::Normalize;
has 'default' => (is => 'ro', default => sub { 0 });
has 'external' => (is => 'ro', default => sub { 0 });
has 'name' => (is => 'rw', required => 1);
has 'file' => (is => 'rw');
sub BUILD {
my ($self) = @_;
$self->name(Unicode::Normalize::NFC($self->name));
}
sub config {
my ($self, $keys, $extra_keys) = @_;
if ($self->default) {
return Slic3r::Config->new_from_defaults(@$keys);
} else {
if (!-e Slic3r::encode_path($self->file)) {
Slic3r::GUI::show_error(undef, "The selected preset does not exist anymore (" . $self->file . ").");
return undef;
}
my $external_config = $self->load_config;
if (!$keys) {
return $external_config;
} else {
# apply preset values on top of defaults
my $config = Slic3r::Config->new_from_defaults(@$keys);
$config->set($_, $external_config->get($_))
for grep $external_config->has($_), @$keys;
# For extra_keys we don't populate defaults.
if ($extra_keys) {
$config->set($_, $external_config->get($_))
for grep $external_config->has($_), @$extra_keys;
}
return $config;
}
}
}
sub load_config {
my ($self) = @_;
return Slic3r::Config->load($self->file);
}
1;

View File

@ -1,7 +1,4 @@
# The "Expert" tab at the right of the main tabbed window.
# The "Expert" is enabled by File->Preferences dialog.
package Slic3r::GUI::Tab;
package Slic3r::GUI::PresetEditor;
use strict;
use warnings;
use utf8;
@ -22,7 +19,7 @@ sub new {
# horizontal sizer
$self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL);
$self->{sizer}->SetSizeHints($self);
#$self->{sizer}->SetSizeHints($self);
$self->SetSizer($self->{sizer});
# left vertical sizer
@ -89,7 +86,6 @@ sub new {
EVT_CHOICE($parent, $self->{presets_choice}, sub {
$self->on_select_preset;
$self->_on_presets_changed;
});
EVT_BUTTON($self, $self->{btn_save_preset}, sub { $self->save_preset });
@ -106,12 +102,15 @@ sub new {
$self->{presets_choice}->Delete($i);
$self->current_preset(undef);
$self->select_preset(0);
$self->_on_presets_changed;
});
# C++ instance DynamicPrintConfig
$self->{config} = Slic3r::Config->new;
# Initialize the DynamicPrintConfig by default keys/values.
$self->{config}->apply(Slic3r::Config->new_from_defaults($self->options));
# Possible %params keys: no_controller
$self->build(%params);
$self->update_tree;
@ -128,6 +127,12 @@ sub get_current_preset {
return $self->get_preset($self->current_preset);
}
sub get_current_preset_config {
my ($self) = @_;
return $self->get_preset_config($self->get_current_preset);
}
sub get_preset {
my ($self, $i) = @_;
return $self->{presets}[$i];
@ -151,14 +156,15 @@ sub save_preset {
default => $default_name,
values => [ map $_->name, grep !$_->default && !$_->external, @{$self->{presets}} ],
);
return unless $dlg->ShowModal == wxID_OK;
return 0 unless $dlg->ShowModal == wxID_OK;
$name = $dlg->get_name;
}
$self->config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $self->name, $name);
$self->load_presets;
$self->select_preset_by_name($name);
$self->_on_presets_changed;
return 1;
}
sub on_value_change {
@ -166,11 +172,6 @@ sub on_value_change {
$self->{on_value_change} = $cb;
}
sub on_presets_changed {
my ($self, $cb) = @_;
$self->{on_presets_changed} = $cb;
}
# This method is supposed to be called whenever new values are loaded
# or changed by user (so also when a preset is loaded).
# propagate event to the parent
@ -183,40 +184,27 @@ sub _on_value_change {
sub _update {}
sub _on_presets_changed {
my $self = shift;
$self->{on_presets_changed}->(
$self->{presets},
scalar($self->{presets_choice}->GetSelection),
$self->is_dirty,
) if $self->{on_presets_changed};
}
sub on_preset_loaded {}
sub hidden_options {}
sub config { $_[0]->{config}->clone }
sub select_default_preset {
my $self = shift;
$self->select_preset(0);
}
sub select_preset {
my $self = shift;
$self->{presets_choice}->SetSelection($_[0]);
$self->on_select_preset;
my ($self, $i, $force) = @_;
$self->{presets_choice}->SetSelection($i);
$self->on_select_preset($force);
}
sub select_preset_by_name {
my ($self, $name) = @_;
my ($self, $name, $force) = @_;
$name = Unicode::Normalize::NFC($name);
$self->select_preset(first { $self->{presets}[$_]->name eq $name } 0 .. $#{$self->{presets}});
my $i = first { $self->{presets}[$_]->name eq $name } 0 .. $#{$self->{presets}};
return if !defined $i;
$self->select_preset($i, $force);
}
sub on_select_preset {
my $self = shift;
sub prompt_unsaved_changes {
my ($self) = @_;
if ($self->is_dirty) {
my $old_preset = $self->get_current_preset;
@ -233,27 +221,39 @@ sub on_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_choice}->SetSelection($self->current_preset);
# trigger the on_presets_changed event so that we also restore the previous value
# in the plater selector
$self->_on_presets_changed;
return;
my $confirm = Wx::MessageDialog->new($self, "$name has unsaved changes:\n$changes\n\nDo you want to save them?",
'Unsaved Changes', wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_QUESTION);
$confirm->SetYesNoCancelLabels('Save', 'Discard', 'Cancel');
my $res = $confirm->ShowModal;
if ($res == wxID_CANCEL) {
return 0;
} elsif ($res == wxID_YES) {
return $self->save_preset($old_preset->default ? undef : $old_preset->name);
}
}
$self->current_preset($self->{presets_choice}->GetSelection);
return 1;
}
sub on_select_preset {
my ($self, $force) = @_;
my $i = $self->{presets_choice}->GetSelection;
return if defined $self->current_preset && $i == $self->current_preset;
if (!$force && defined $self->current_preset && !$self->prompt_unsaved_changes) {
$self->{presets_choice}->SetSelection($self->current_preset);
return;
}
$self->current_preset($i);
my $preset = $self->get_current_preset;
my $preset_config = $self->get_preset_config($preset);
eval {
local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self);
foreach my $opt_key (@{$self->{config}->get_keys}) {
$self->{config}->set($opt_key, $preset_config->get($opt_key))
if $preset_config->has($opt_key);
}
$self->{config}->clear;
$self->{config}->apply($preset_config);
($preset->default || $preset->external)
? $self->{btn_delete_preset}->Disable
: $self->{btn_delete_preset}->Enable;
@ -261,12 +261,10 @@ sub on_select_preset {
$self->_update;
$self->on_preset_loaded;
$self->reload_config;
$Slic3r::GUI::Settings->{presets}{$self->name} = $preset->file ? basename($preset->file) : '';
};
if ($@) {
$@ = "I was unable to load the selected config file: $@";
Slic3r::GUI::catch_error($self);
$self->select_default_preset;
}
# use CallAfter because some field triggers schedule on_change calls using CallAfter,
@ -274,16 +272,8 @@ sub on_select_preset {
# preset dirty again
# (not sure this is true anymore now that update_dirty is idempotent)
wxTheApp->CallAfter(sub {
$self->_on_presets_changed;
$self->update_dirty;
});
wxTheApp->save_settings;
}
sub init_config_options {
my ($self, @opt_keys) = @_;
$self->{config}->apply(Slic3r::Config->new_from_defaults(@opt_keys));
}
sub add_options_page {
@ -296,7 +286,7 @@ sub add_options_page {
$self->{iconcount}++;
}
my $page = Slic3r::GUI::Tab::Page->new($self, $title, $self->{iconcount});
my $page = Slic3r::GUI::PresetEditor::Page->new($self, $title, $self->{iconcount});
$page->Hide;
$self->{sizer}->Add($page, 1, wxEXPAND | wxLEFT, 5);
push @{$self->{pages}}, $page;
@ -345,7 +335,6 @@ sub update_dirty {
}
}
$self->{presets_choice}->SetSelection($self->current_preset); # http://trac.wxwidgets.org/ticket/13769
$self->_on_presets_changed;
}
sub is_dirty {
@ -357,36 +346,23 @@ sub dirty_options {
my $self = shift;
return [] if !defined $self->current_preset; # happens during initialization
return $self->get_preset_config($self->get_current_preset)->diff($self->{config});
my $preset_config = $self->get_current_preset_config;
return [
(grep !$preset_config->has($_), @{$self->{config}->get_keys}),
(grep !$self->{config}->has($_), @{$preset_config->get_keys}),
@{ $self->{config}->diff($self->get_current_preset_config) },
];
}
sub load_presets {
my $self = shift;
$self->{presets} = [
Slic3r::GUI::Tab::Preset->new(
default => 1,
name => '- default -',
),
];
my %presets = wxTheApp->presets($self->name);
foreach my $preset_name (sort keys %presets) {
push @{$self->{presets}}, Slic3r::GUI::Tab::Preset->new(
name => $preset_name,
file => $presets{$preset_name},
);
}
$self->{presets} = [ wxTheApp->presets($self->name, 1) ];
$self->current_preset(undef);
$self->{presets_choice}->Clear;
$self->{presets_choice}->Append($_->name) for @{$self->{presets}};
{
# 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 || 0);
}
$self->_on_presets_changed;
$self->select_preset(0);
}
sub load_config_file {
@ -397,7 +373,7 @@ sub load_config_file {
my $i = first { $self->{presets}[$_]{file} eq $file && $self->{presets}[$_]{external} } 1..$#{$self->{presets}};
if (!$i) {
my $preset_name = basename($file); # keep the .ini suffix
push @{$self->{presets}}, Slic3r::GUI::Tab::Preset->new(
push @{$self->{presets}}, Slic3r::GUI::Preset->new(
file => $file,
name => $preset_name,
external => 1,
@ -407,7 +383,6 @@ sub load_config_file {
}
$self->{presets_choice}->SetSelection($i);
$self->on_select_preset;
$self->_on_presets_changed;
}
sub load_config {
@ -424,7 +399,11 @@ sub load_config {
sub get_preset_config {
my ($self, $preset) = @_;
return $preset->config($self->{config}->get_keys);
return $preset->config(
[ $self->options ],
[ $self->overriding_options ],
);
}
sub get_field {
@ -448,19 +427,64 @@ sub set_value {
return $changed;
}
package Slic3r::GUI::Tab::Print;
use base 'Slic3r::GUI::Tab';
sub _compatible_printers_widget {
my ($self) = @_;
return sub {
my ($parent) = @_;
my $btn = Wx::Button->new($parent, -1, "Set…", wxDefaultPosition, wxDefaultSize,
wxBU_LEFT | wxBU_EXACTFIT);
$btn->SetFont($Slic3r::GUI::small_font);
if ($Slic3r::GUI::have_button_icons) {
$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 @presets = map $_->name, grep !$_->default && !$_->external,
wxTheApp->presets('printer');
my $dlg = Wx::MultiChoiceDialog->new($self,
"Select the printers this profile is compatible with.\nIf none are selected, it will be considered compatible with all of them.",
"Compatible printers", \@presets);
my @selections = ();
foreach my $preset_name (@{ $self->{config}->get('compatible_printers') }) {
push @selections, first { $presets[$_] eq $preset_name } 0..$#presets;
}
$dlg->SetSelections(@selections);
if ($dlg->ShowModal == wxID_OK) {
my $value = [ @presets[$dlg->GetSelections] ];
$self->{config}->set('compatible_printers', $value);
$self->update_dirty;
$self->_on_value_change('compatible_printers', $value);
}
});
return $sizer;
};
}
sub options { die "Unimplemented options()"; }
sub overridable_options { () }
sub overriding_options { () }
package Slic3r::GUI::PresetEditor::Print;
use base 'Slic3r::GUI::PresetEditor';
use List::Util qw(first any);
use Wx qw(:icon :dialog :id);
use Wx qw(:icon :dialog :id :misc :button :sizer);
use Wx::Event qw(EVT_BUTTON);
sub name { 'print' }
sub title { 'Print Settings' }
sub build {
my $self = shift;
$self->init_config_options(qw(
sub options {
return qw(
layer_height first_layer_height
perimeters spiral_vase
top_solid_layers bottom_solid_layers
@ -497,9 +521,54 @@ sub build {
external_perimeter_extrusion_width infill_extrusion_width solid_infill_extrusion_width
top_infill_extrusion_width support_material_extrusion_width
infill_overlap bridge_flow_ratio
xy_size_compensation threads resolution
));
$self->{config}->set('print_settings_id', '');
xy_size_compensation threads resolution overridable compatible_printers
print_settings_id
)
}
sub build {
my $self = shift;
my $overridable_widget = sub {
my ($parent) = @_;
my $btn = Wx::Button->new($parent, -1, "Set…", wxDefaultPosition, wxDefaultSize,
wxBU_LEFT | wxBU_EXACTFIT);
$btn->SetFont($Slic3r::GUI::small_font);
if ($Slic3r::GUI::have_button_icons) {
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG));
}
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$sizer->Add($btn);
EVT_BUTTON($self, $btn, sub {
my %options = (
map { $_ => sprintf('%s > %s', $Slic3r::Config::Options->{$_}{category}, $Slic3r::Config::Options->{$_}{full_label} // $Slic3r::Config::Options->{$_}{label}) }
map @{$_->get_keys}, Slic3r::Config::PrintObject->new, Slic3r::Config::PrintRegion->new
);
my @opt_keys = sort { $options{$a} cmp $options{$b} } keys %options;
my $dlg = Wx::MultiChoiceDialog->new($self, "Selected options will be displayed in the plater screen for quick changes.",
"Overridable options",
[ map $options{$_}, @opt_keys ]);
my @selections = ();
foreach my $opt_key (@{ $self->{config}->get('overridable') }) {
push @selections, first { $opt_keys[$_] eq $opt_key } 0..$#opt_keys;
}
$dlg->SetSelections(@selections);
if ($dlg->ShowModal == wxID_OK) {
my $value = [ @opt_keys[$dlg->GetSelections] ];
$self->{config}->set('overridable', $value);
$self->update_dirty;
$self->_on_value_change('overridable', $value);
}
});
return $sizer;
};
{
my $page = $self->add_options_page('Layers and perimeters', 'layers.png');
@ -643,7 +712,7 @@ sub build {
}
{
my $page = $self->add_options_page('Multiple Extruders', 'funnel.png');
my $page = $self->add_options_page('Multiple extruders', 'funnel.png');
{
my $optgroup = $page->new_optgroup('Extruders');
$optgroup->append_single_option_line('perimeter_extruder');
@ -728,7 +797,7 @@ sub build {
}
{
my $page = $self->add_options_page('Notes', 'note.png');
my $page = $self->add_options_page('Notes and preferences', 'note.png');
{
my $optgroup = $page->new_optgroup('Notes',
label_width => 0,
@ -738,6 +807,23 @@ sub build {
$option->height(250);
$optgroup->append_single_option_line($option);
}
{
my $optgroup = $page->new_optgroup('Profile preferences');
{
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Overridable settings',
widget => $overridable_widget,
);
$optgroup->append_line($line);
}
{
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Compatible printers',
widget => $self->_compatible_printers_widget,
);
$optgroup->append_line($line);
}
}
}
}
@ -890,24 +976,34 @@ sub _update {
sub hidden_options { !$Slic3r::have_threads ? qw(threads) : () }
package Slic3r::GUI::Tab::Filament;
use base 'Slic3r::GUI::Tab';
package Slic3r::GUI::PresetEditor::Filament;
use base 'Slic3r::GUI::PresetEditor';
use Wx qw(wxTheApp);
sub name { 'filament' }
sub title { 'Filament Settings' }
sub build {
my $self = shift;
$self->init_config_options(qw(
sub options {
return qw(
filament_colour filament_diameter filament_notes filament_max_volumetric_speed extrusion_multiplier filament_density filament_cost
temperature first_layer_temperature bed_temperature first_layer_bed_temperature
fan_always_on cooling
fan_always_on cooling compatible_printers
min_fan_speed max_fan_speed bridge_fan_speed disable_fan_first_layers
fan_below_layer_time slowdown_below_layer_time min_print_speed
start_filament_gcode end_filament_gcode
));
$self->{config}->set('filament_settings_id', '');
filament_settings_id
);
}
sub overriding_options {
return (
Slic3r::GUI::PresetEditor::Printer->overridable_options,
);
}
sub build {
my $self = shift;
{
my $page = $self->add_options_page('Filament', 'spool.png');
@ -987,26 +1083,6 @@ sub build {
}
}
{
my $page = $self->add_options_page('Advanced', 'wrench.png');
{
my $optgroup = $page->new_optgroup('Print speed override');
$optgroup->append_single_option_line('filament_max_volumetric_speed', 0);
}
}
{
my $page = $self->add_options_page('Notes', 'note.png');
{
my $optgroup = $page->new_optgroup('Notes',
label_width => 0,
);
my $option = $optgroup->get_option('filament_notes', 0);
$option->full_width(1);
$option->height(250);
$optgroup->append_single_option_line($option);
}
}
{
my $page = $self->add_options_page('Custom G-code', 'cog.png');
{
@ -1028,6 +1104,81 @@ sub build {
$optgroup->append_single_option_line($option);
}
}
{
my $page = $self->add_options_page('Overrides', 'wrench.png');
{
my $optgroup = $page->new_optgroup('Profile preferences');
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Compatible printers',
widget => $self->_compatible_printers_widget,
);
$optgroup->append_line($line);
}
{
my $optgroup = $page->new_optgroup('Overrides');
$optgroup->append_single_option_line('filament_max_volumetric_speed', 0);
# Populate the overrides config.
my @overridable = $self->overriding_options;
$self->{overrides_config} = Slic3r::Config->new;
# Populate the defaults with the current preset.
$self->{overrides_default_config} = Slic3r::Config->new;
$self->{overrides_default_config}->apply_only
(wxTheApp->{mainframe}->{plater}->config, \@overridable);
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => '',
full_width => 1,
widget => sub {
my ($parent) = @_;
$self->{overrides_panel} = my $panel = Slic3r::GUI::Plater::OverrideSettingsPanel->new($parent,
size => [-1, 300],
on_change => sub {
$self->{config}->erase($_) for @overridable;
$self->{config}->apply($self->{overrides_config});
$self->update_dirty;
});
$panel->set_editable(1);
$panel->set_default_config($self->{overrides_default_config});
$panel->set_config($self->{overrides_config});
$panel->set_opt_keys([@overridable]);
return $panel;
},
);
$optgroup->append_line($line);
}
}
{
my $page = $self->add_options_page('Notes', 'note.png');
{
my $optgroup = $page->new_optgroup('Notes',
label_width => 0,
);
my $option = $optgroup->get_option('filament_notes', 0);
$option->full_width(1);
$option->height(250);
$optgroup->append_single_option_line($option);
}
}
}
sub reload_config {
my ($self) = @_;
$self->{overrides_config}->clear;
foreach my $opt_key (@{$self->{overrides_default_config}->get_keys}) {
if ($self->{config}->has($opt_key)) {
$self->{overrides_config}->set($opt_key, $self->{config}->get($opt_key));
}
}
$self->{overrides_panel}->update_optgroup;
$self->SUPER::reload_config;
}
sub _update {
@ -1071,19 +1222,16 @@ sub _update_description {
$self->{description_line}->SetText($msg);
}
package Slic3r::GUI::Tab::Printer;
use base 'Slic3r::GUI::Tab';
package Slic3r::GUI::PresetEditor::Printer;
use base 'Slic3r::GUI::PresetEditor';
use Wx qw(wxTheApp :sizer :button :bitmap :misc :id :icon :dialog);
use Wx::Event qw(EVT_BUTTON);
sub name { 'printer' }
sub title { 'Printer Settings' }
sub build {
my $self = shift;
my (%params) = @_;
$self->init_config_options(qw(
sub options {
return qw(
bed_shape z_offset z_steps_per_mm has_heatbed
gcode_flavor use_relative_e_distances
serial_port serial_speed
@ -1093,9 +1241,22 @@ sub build {
start_gcode end_gcode before_layer_gcode layer_gcode toolchange_gcode
nozzle_diameter extruder_offset
retract_length retract_lift retract_speed retract_restart_extra retract_before_travel retract_layer_change wipe
retract_length_toolchange retract_restart_extra_toolchange
));
$self->{config}->set('printer_settings_id', '');
retract_length_toolchange retract_restart_extra_toolchange retract_lift_above retract_lift_below
printer_settings_id
);
}
sub overridable_options {
return qw(
pressure_advance
retract_length retract_lift retract_speed retract_restart_extra
retract_before_travel retract_layer_change wipe
);
}
sub build {
my $self = shift;
my (%params) = @_;
$self->{extruders_count} = 1;
@ -1484,7 +1645,7 @@ sub load_config_file {
if @{ $self->{config}->nozzle_diameter } > 1;
}
package Slic3r::GUI::Tab::Page;
package Slic3r::GUI::PresetEditor::Page;
use Wx qw(wxTheApp :misc :panel :sizer);
use base 'Wx::ScrolledWindow';
@ -1603,33 +1764,4 @@ sub get_name {
return $self->{chosen_name};
}
package Slic3r::GUI::Tab::Preset;
use Moo;
has 'default' => (is => 'ro', default => sub { 0 });
has 'external' => (is => 'ro', default => sub { 0 });
has 'name' => (is => 'rw', required => 1);
has 'file' => (is => 'rw');
sub config {
my ($self, $keys) = @_;
if ($self->default) {
return Slic3r::Config->new_from_defaults(@$keys);
} else {
if (!-e Slic3r::encode_path($self->file)) {
Slic3r::GUI::show_error(undef, "The selected preset does not exist anymore (" . $self->file . ").");
return undef;
}
# apply preset values on top of defaults
my $config = Slic3r::Config->new_from_defaults(@$keys);
my $external_config = Slic3r::Config->load($self->file);
$config->set($_, $external_config->get($_))
for grep $external_config->has($_), @$keys;
return $config;
}
}
1;

View File

@ -0,0 +1,65 @@
package Slic3r::GUI::PresetEditorDialog;
use strict;
use warnings;
use Wx qw(:dialog :id :misc :sizer :button :icon wxTheApp);
use Wx::Event qw(EVT_CLOSE);
use base qw(Wx::Dialog Class::Accessor);
use utf8;
__PACKAGE__->mk_accessors(qw(preset_editor));
sub new {
my ($class, $parent) = @_;
my $self = $class->SUPER::new($parent, -1, "Settings", wxDefaultPosition, [800,500],
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxDIALOG_EX_METAL);
$self->preset_editor($self->preset_editor_class->new($self));
$self->SetTitle($self->preset_editor->title);
$self->preset_editor->load_presets;
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
$sizer->Add($self->preset_editor, 1, wxEXPAND);
$self->SetSizer($sizer);
#$sizer->SetSizeHints($self);
if (0) {
# This does not call the EVT_CLOSE below
my $buttons = $self->CreateStdDialogButtonSizer(wxCLOSE);
$sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
}
EVT_CLOSE($self, sub {
my (undef, $event) = @_;
if ($event->CanVeto && !$self->preset_editor->prompt_unsaved_changes) {
$event->Veto;
return;
}
# propagate event
$event->Skip;
});
return $self;
}
package Slic3r::GUI::PresetEditorDialog::Printer;
use base qw(Slic3r::GUI::PresetEditorDialog);
sub preset_editor_class { "Slic3r::GUI::PresetEditor::Printer" }
package Slic3r::GUI::PresetEditorDialog::Filament;
use base qw(Slic3r::GUI::PresetEditorDialog);
sub preset_editor_class { "Slic3r::GUI::PresetEditor::Filament" }
package Slic3r::GUI::PresetEditorDialog::Print;
use base qw(Slic3r::GUI::PresetEditorDialog);
sub preset_editor_class { "Slic3r::GUI::PresetEditor::Print" }
1;

View File

@ -44,7 +44,7 @@ sub new {
$self->config(Slic3r::Config->new_from_defaults(
qw(serial_port serial_speed bed_shape start_gcode end_gcode z_offset)
));
$self->config->apply(wxTheApp->{mainframe}->config);
$self->config->apply(wxTheApp->{mainframe}->{plater}->config);
my @optgroups = ();
{
@ -520,9 +520,6 @@ sub _close {
}
wxTheApp->{mainframe}->Show;
my $printer_tab = wxTheApp->{mainframe}{options_tabs}{printer};
$printer_tab->load_config($self->config);
$self->EndModal(wxID_OK);
}
@ -555,7 +552,7 @@ sub BUILD {
# init print
{
my $print = Slic3r::SLAPrint->new(wxTheApp->{mainframe}->{plater}->{model});
$print->apply_config(wxTheApp->{mainframe}->config);
$print->apply_config(wxTheApp->{mainframe}->{plater}->config);
$self->_print($print);
$self->screen->print($print);

View File

@ -10,7 +10,7 @@ sub new {
my $self = $class->SUPER::new($parent, -1, "SLA/DLP Print", wxDefaultPosition, wxDefaultSize);
$self->config(Slic3r::Config::SLAPrint->new);
$self->config->apply_dynamic(wxTheApp->{mainframe}->config);
$self->config->apply_dynamic(wxTheApp->{mainframe}->{plater}->config);
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
my $new_optgroup = sub {
@ -103,8 +103,6 @@ sub _accept {
return;
}
wxTheApp->{mainframe}->load_config($self->config->dynamic);
$self->EndModal(wxID_OK);
$self->Close; # needed on Linux
@ -113,6 +111,9 @@ sub _accept {
# this double invocation is needed for properly hiding the MainFrame
$projector->Show;
$projector->ShowModal;
# TODO: diff the new config with the selected presets and prompt the user for
# applying the changes to them.
}
1;

View File

@ -1,351 +0,0 @@
# The "Simple" Print Settings tab.
# The "Simple" mode is enabled by File->Preferences dialog.
package Slic3r::GUI::SimpleTab;
use strict;
use warnings;
use utf8;
use File::Basename qw(basename);
use List::Util qw(first);
use Wx qw(:bookctrl :dialog :keycode :icon :id :misc :panel :sizer :window :systemsettings);
use Wx::Event qw(EVT_BUTTON EVT_CHOICE EVT_KEY_DOWN);
use base 'Wx::ScrolledWindow';
sub new {
my $class = shift;
my ($parent, %params) = @_;
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL);
$self->SetScrollbars(1, 1, 1, 1);
$self->{config} = Slic3r::Config->new;
$self->{optgroups} = [];
$self->{vsizer} = Wx::BoxSizer->new(wxVERTICAL);
$self->SetSizer($self->{vsizer});
$self->build;
$self->_update;
{
my $label = Wx::StaticText->new($self, -1, "Want more options? Switch to the Expert Mode.", wxDefaultPosition, wxDefaultSize);
$label->SetFont(Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
$self->{vsizer}->Add($label, 0, wxEXPAND | wxALL, 10);
}
return $self;
}
sub init_config_options {
my ($self, @opt_keys) = @_;
$self->{config}->apply(Slic3r::Config->new_from_defaults(@opt_keys));
}
sub new_optgroup {
my ($self, $title, %params) = @_;
my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
parent => $self,
title => $title,
config => $self->{config},
label_width => $params{label_width} // 200,
on_change => sub { $self->_on_value_change(@_) },
);
push @{$self->{optgroups}}, $optgroup;
$self->{vsizer}->Add($optgroup->sizer, 0, wxEXPAND | wxALL, 10);
return $optgroup;
}
sub load_config_file {
my $self = shift;
my ($file) = @_;
my $config = Slic3r::Config->load($file);
$self->load_config($config);
}
sub load_config {
my $self = shift;
my ($config) = @_;
foreach my $opt_key (@{$self->{config}->get_keys}) {
next unless $config->has($opt_key);
$self->{config}->set($opt_key, $config->get($opt_key));
}
$_->reload_config for @{$self->{optgroups}};
$self->_update;
}
sub load_presets {}
sub is_dirty { 0 }
sub config { $_[0]->{config}->clone }
sub _update {}
sub on_value_change {
my ($self, $cb) = @_;
$self->{on_value_change} = $cb;
}
sub on_presets_changed {}
# propagate event to the parent
sub _on_value_change {
my $self = shift;
$self->{on_value_change}->(@_) if $self->{on_value_change};
$self->_update;
}
sub get_field {
my ($self, $opt_key, $opt_index) = @_;
foreach my $optgroup (@{ $self->{optgroups} }) {
my $field = $optgroup->get_fieldc($opt_key, $opt_index);
return $field if defined $field;
}
return undef;
}
package Slic3r::GUI::SimpleTab::Print;
use base 'Slic3r::GUI::SimpleTab';
use Wx qw(:sizer);
sub name { 'print' }
sub title { 'Print Settings' }
sub build {
my $self = shift;
$self->init_config_options(qw(
layer_height perimeters top_solid_layers bottom_solid_layers
fill_density fill_pattern top_infill_pattern bottom_infill_pattern
support_material support_material_spacing raft_layers
support_material_contact_distance dont_support_bridges
perimeter_speed infill_speed travel_speed
brim_width
xy_size_compensation
));
{
my $optgroup = $self->new_optgroup('General');
$optgroup->append_single_option_line('layer_height');
$optgroup->append_single_option_line('perimeters');
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Solid layers',
);
$line->append_option($optgroup->get_option('top_solid_layers'));
$line->append_option($optgroup->get_option('bottom_solid_layers'));
$optgroup->append_line($line);
}
{
my $optgroup = $self->new_optgroup('Infill');
$optgroup->append_single_option_line('fill_density');
$optgroup->append_single_option_line('fill_pattern');
$optgroup->append_single_option_line('top_infill_pattern');
$optgroup->append_single_option_line('bottom_infill_pattern');
}
{
my $optgroup = $self->new_optgroup('Support material');
$optgroup->append_single_option_line('support_material');
$optgroup->append_single_option_line('support_material_spacing');
$optgroup->append_single_option_line('support_material_contact_distance');
$optgroup->append_single_option_line('dont_support_bridges');
$optgroup->append_single_option_line('raft_layers');
}
{
my $optgroup = $self->new_optgroup('Speed');
$optgroup->append_single_option_line('perimeter_speed');
$optgroup->append_single_option_line('infill_speed');
$optgroup->append_single_option_line('travel_speed');
}
{
my $optgroup = $self->new_optgroup('Brim');
$optgroup->append_single_option_line('brim_width');
}
{
my $optgroup = $self->new_optgroup('Other');
$optgroup->append_single_option_line('xy_size_compensation');
}
}
sub _update {
my ($self) = @_;
my $config = $self->{config};
my $have_perimeters = $config->perimeters > 0;
$self->get_field($_)->toggle($have_perimeters)
for qw(perimeter_speed);
my $have_infill = $config->fill_density > 0;
my $have_solid_infill = $config->top_solid_layers > 0 || $config->bottom_solid_layers > 0;
$self->get_field($_)->toggle($have_infill)
for qw(fill_pattern);
$self->get_field($_)->toggle($have_solid_infill)
for qw(top_infill_pattern bottom_infill_pattern);
$self->get_field($_)->toggle($have_infill || $have_solid_infill)
for qw(infill_speed);
my $have_support_material = $config->support_material || $config->raft_layers > 0;
$self->get_field($_)->toggle($have_support_material)
for qw(support_material_spacing dont_support_bridges
support_material_contact_distance);
}
package Slic3r::GUI::SimpleTab::Filament;
use base 'Slic3r::GUI::SimpleTab';
sub name { 'filament' }
sub title { 'Filament Settings' }
sub build {
my $self = shift;
$self->init_config_options(qw(
filament_diameter extrusion_multiplier
temperature first_layer_temperature bed_temperature first_layer_bed_temperature
));
{
my $optgroup = $self->new_optgroup('Filament');
$optgroup->append_single_option_line('filament_diameter', 0);
$optgroup->append_single_option_line('extrusion_multiplier', 0);
}
{
my $optgroup = $self->new_optgroup('Temperature (°C)');
{
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Extruder',
);
$line->append_option($optgroup->get_option('first_layer_temperature', 0));
$line->append_option($optgroup->get_option('temperature', 0));
$optgroup->append_line($line);
}
{
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Bed',
);
$line->append_option($optgroup->get_option('first_layer_bed_temperature'));
$line->append_option($optgroup->get_option('bed_temperature'));
$optgroup->append_line($line);
}
}
}
package Slic3r::GUI::SimpleTab::Printer;
use base 'Slic3r::GUI::SimpleTab';
use Wx qw(:sizer :button :bitmap :misc :id);
use Wx::Event qw(EVT_BUTTON);
sub name { 'printer' }
sub title { 'Printer Settings' }
sub build {
my $self = shift;
$self->init_config_options(qw(
bed_shape
z_offset
gcode_flavor
nozzle_diameter
retract_length retract_lift wipe
start_gcode
end_gcode
));
{
my $bed_shape_widget = sub {
my ($parent) = @_;
my $btn = Wx::Button->new($parent, -1, "Set…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
$btn->SetFont($Slic3r::GUI::small_font);
if ($Slic3r::GUI::have_button_icons) {
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("cog.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->_on_value_change('bed_shape', $value);
}
});
return $sizer;
};
my $optgroup = $self->new_optgroup('Size and coordinates');
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Bed shape',
widget => $bed_shape_widget,
);
$optgroup->append_line($line);
$optgroup->append_single_option_line('z_offset');
}
{
my $optgroup = $self->new_optgroup('Firmware');
$optgroup->append_single_option_line('gcode_flavor');
}
{
my $optgroup = $self->new_optgroup('Extruder');
$optgroup->append_single_option_line('nozzle_diameter', 0);
}
{
my $optgroup = $self->new_optgroup('Retraction');
$optgroup->append_single_option_line('retract_length', 0);
$optgroup->append_single_option_line('retract_lift', 0);
$optgroup->append_single_option_line('wipe', 0);
}
{
my $optgroup = $self->new_optgroup('Start G-code',
label_width => 0,
);
my $option = $optgroup->get_option('start_gcode');
$option->full_width(1);
$option->height(150);
$optgroup->append_single_option_line($option);
}
{
my $optgroup = $self->new_optgroup('End G-code',
label_width => 0,
);
my $option = $optgroup->get_option('end_gcode');
$option->full_width(1);
$option->height(150);
$optgroup->append_single_option_line($option);
}
}
sub _update {
my ($self) = @_;
my $config = $self->{config};
my $have_retraction = $config->retract_length->[0] > 0;
$self->get_field($_, 0)->toggle($have_retraction)
for qw(retract_lift wipe);
}
1;

View File

@ -35,8 +35,6 @@ my %cli_options = ();
'autosave=s' => \$opt{autosave},
'ignore-nonexistent-config' => \$opt{ignore_nonexistent_config},
'no-controller' => \$opt{no_controller},
'no-plater' => \$opt{no_plater},
'gui-mode=s' => \$opt{gui_mode},
'datadir=s' => \$opt{datadir},
'export-svg' => \$opt{export_svg},
'merge|m' => \$opt{merge},
@ -77,28 +75,26 @@ if ($opt{load}) {
$opt{ignore_nonexistent_config} or die "Cannot find specified configuration file ($configfile).\n";
}
}
# expand shortcuts before applying, otherwise destination values would be already filled with defaults
$_->normalize for @external_configs;
}
# process command line options
my $cli_config = Slic3r::Config->new;
foreach my $c (@external_configs, Slic3r::Config->new_from_cli(%cli_options)) {
$c->normalize; # expand shortcuts before applying, otherwise destination values would be already filled with defaults
$cli_config->apply($c);
}
my $cli_config = Slic3r::Config->new_from_cli(%cli_options);
$cli_config->normalize; # expand shortcuts
# save configuration
if ($opt{save}) {
if (@{$cli_config->get_keys} > 0) {
$cli_config->save($opt{save});
my $config = $cli_config->clone;
$config->apply($_) for @external_configs;
if (@{$config->get_keys} > 0) {
$config->save($opt{save});
} else {
Slic3r::Config->new_from_defaults->save($opt{save});
}
}
# apply command line config on top of default config
my $config = Slic3r::Config->new_from_defaults;
$config->apply($cli_config);
# launch GUI
my $gui;
if ((!@ARGV || $opt{gui}) && !$opt{save} && eval "require Slic3r::GUI; 1") {
@ -106,9 +102,7 @@ if ((!@ARGV || $opt{gui}) && !$opt{save} && eval "require Slic3r::GUI; 1") {
no warnings 'once';
$Slic3r::GUI::datadir = Slic3r::decode_path($opt{datadir} // '');
$Slic3r::GUI::no_controller = $opt{no_controller};
$Slic3r::GUI::no_plater = $opt{no_plater};
$Slic3r::GUI::mode = $opt{gui_mode};
$Slic3r::GUI::autosave = $opt{autosave};
$Slic3r::GUI::autosave = Slic3r::decode_path($opt{autosave} // '');
}
$gui = Slic3r::GUI->new;
setlocale(LC_NUMERIC, 'C');
@ -117,7 +111,7 @@ if ((!@ARGV || $opt{gui}) && !$opt{save} && eval "require Slic3r::GUI; 1") {
$gui->{mainframe}->load_config($cli_config);
foreach my $input_file (@ARGV) {
$input_file = Slic3r::decode_path($input_file);
$gui->{mainframe}{plater}->load_file($input_file) unless $opt{no_plater};
$gui->{mainframe}{plater}->load_file($input_file);
}
});
$gui->MainLoop;
@ -126,6 +120,9 @@ if ((!@ARGV || $opt{gui}) && !$opt{save} && eval "require Slic3r::GUI; 1") {
die $@ if $@ && $opt{gui};
if (@ARGV) { # slicing from command line
# apply command line config on top of default config
my $config = Slic3r::Config->new_from_defaults;
$config->apply($cli_config);
$config->validate;
if ($opt{repair}) {
@ -327,7 +324,6 @@ $j
--gui Forces the GUI launch instead of command line slicing (if you
supply a model file, it will be loaded into the plater)
--no-plater Disable the plater tab
--gui-mode Overrides the configured mode (simple/expert)
--autosave <file> Automatically export current configuration to the specified file
Output options:

View File

@ -241,21 +241,24 @@ ConfigBase::has(const t_config_option_key &opt_key) {
void
ConfigBase::apply(const ConfigBase &other, bool ignore_nonexistent) {
// get list of option keys to apply
t_config_option_keys opt_keys = other.keys();
// apply all options
this->apply_only(other, other.keys(), ignore_nonexistent);
}
void
ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys &opt_keys, bool ignore_nonexistent) {
// loop through options and apply them
for (t_config_option_keys::const_iterator it = opt_keys.begin(); it != opt_keys.end(); ++it) {
ConfigOption* my_opt = this->option(*it, true);
for (const t_config_option_key &opt_key : opt_keys) {
ConfigOption* my_opt = this->option(opt_key, true);
if (my_opt == NULL) {
if (ignore_nonexistent == false) throw "Attempt to apply non-existent option";
continue;
}
// not the most efficient way, but easier than casting pointers to subclasses
bool res = my_opt->deserialize( other.option(*it)->serialize() );
bool res = my_opt->deserialize( other.option(opt_key)->serialize() );
if (!res) {
std::string error = "Unexpected failure when deserializing serialized value for " + *it;
std::string error = "Unexpected failure when deserializing serialized value for " + opt_key;
CONFESS(error.c_str());
}
}
@ -500,6 +503,11 @@ DynamicConfig::erase(const t_config_option_key &opt_key) {
this->options.erase(opt_key);
}
void
DynamicConfig::clear() {
this->options.clear();
}
void
DynamicConfig::read_cli(const int argc, const char** argv, t_config_option_keys* extra)
{

View File

@ -665,6 +665,7 @@ class ConfigBase
virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) = 0;
virtual t_config_option_keys keys() const = 0;
void apply(const ConfigBase &other, bool ignore_nonexistent = false);
void apply_only(const ConfigBase &other, const t_config_option_keys &opt_keys, bool ignore_nonexistent = false);
bool equals(ConfigBase &other);
t_config_option_keys diff(ConfigBase &other);
std::string serialize(const t_config_option_key &opt_key) const;
@ -691,6 +692,7 @@ class DynamicConfig : public virtual ConfigBase
virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false);
t_config_option_keys keys() const;
void erase(const t_config_option_key &opt_key);
void clear();
void read_cli(const int argc, const char **argv, t_config_option_keys* extra);
private:

View File

@ -131,6 +131,10 @@ PrintConfigDef::PrintConfigDef()
def->cli = "brim-width=f";
def->min = 0;
def->default_value = new ConfigOptionFloat(0);
def = this->add("compatible_printers", coStrings);
def->label = "Compatible printers";
def->default_value = new ConfigOptionStrings();
def = this->add("complete_objects", coBool);
def->label = "Complete individual objects";
@ -784,7 +788,7 @@ PrintConfigDef::PrintConfigDef()
def->tooltip = "Slic3r can upload G-code files to OctoPrint. This field should contain the API Key required for authentication.";
def->cli = "octoprint-apikey=s";
def->default_value = new ConfigOptionString("");
def = this->add("octoprint_host", coString);
def->label = "Host or IP";
def->tooltip = "Slic3r can upload G-code files to OctoPrint. This field should contain the hostname or IP address of the OctoPrint instance.";
@ -816,6 +820,14 @@ PrintConfigDef::PrintConfigDef()
def->tooltip = "Experimental option to adjust flow for overhangs (bridge flow will be used), to apply bridge speed to them and enable fan.";
def->cli = "overhangs!";
def->default_value = new ConfigOptionBool(true);
def = this->add("overridable", coStrings);
def->label = "Overridable options";
{
ConfigOptionStrings* opt = new ConfigOptionStrings();
opt->values.push_back("support_material");
def->default_value = opt;
}
def = this->add("perimeter_acceleration", coFloat);
def->label = "Perimeters";
@ -876,6 +888,7 @@ PrintConfigDef::PrintConfigDef()
def->multiline = true;
def->full_width = true;
def->height = 60;
def->default_value = new ConfigOptionStrings();
def = this->add("print_settings_id", coString);
def->default_value = new ConfigOptionString("");
@ -885,6 +898,7 @@ PrintConfigDef::PrintConfigDef()
def = this->add("pressure_advance", coFloat);
def->label = "Pressure advance";
def->category = "Extruder";
def->tooltip = "When set to a non-zero value, this experimental option enables pressure regulation. It's the K constant for the advance algorithm that pushes more or less filament upon speed changes. It's useful for Bowden-tube extruders. Reasonable values are in range 0-10.";
def->cli = "pressure-advance=f";
def->min = 0;
@ -918,6 +932,7 @@ PrintConfigDef::PrintConfigDef()
def = this->add("retract_before_travel", coFloats);
def->label = "Minimum travel after retraction";
def->category = "Retraction";
def->tooltip = "Retraction is not triggered when travel moves are shorter than this length.";
def->sidetext = "mm";
def->cli = "retract-before-travel=f@";
@ -929,6 +944,7 @@ PrintConfigDef::PrintConfigDef()
def = this->add("retract_layer_change", coBools);
def->label = "Retract on layer change";
def->category = "Retraction";
def->tooltip = "This flag enforces a retraction whenever a Z move is done.";
def->cli = "retract-layer-change!";
{
@ -939,6 +955,7 @@ PrintConfigDef::PrintConfigDef()
def = this->add("retract_length", coFloats);
def->label = "Length";
def->category = "Retraction";
def->full_label = "Retraction Length";
def->tooltip = "When retraction is triggered, filament is pulled back by the specified amount (the length is measured on raw filament, before it enters the extruder).";
def->sidetext = "mm (zero to disable)";
@ -963,6 +980,7 @@ PrintConfigDef::PrintConfigDef()
def = this->add("retract_lift", coFloats);
def->label = "Lift Z";
def->category = "Retraction";
def->tooltip = "If you set this to a positive value, Z is quickly raised every time a retraction is triggered. When using multiple extruders, only the setting for the first extruder will be considered.";
def->sidetext = "mm";
def->cli = "retract-lift=f@";
@ -975,6 +993,7 @@ PrintConfigDef::PrintConfigDef()
def = this->add("retract_lift_above", coFloats);
def->label = "Above Z";
def->full_label = "Only lift Z above";
def->category = "Retraction";
def->tooltip = "If you set this to a positive value, Z lift will only take place above the specified absolute Z. You can tune this setting for skipping lift on the first layers.";
def->sidetext = "mm";
def->cli = "retract-lift-above=f@";
@ -987,6 +1006,7 @@ PrintConfigDef::PrintConfigDef()
def = this->add("retract_lift_below", coFloats);
def->label = "Below Z";
def->full_label = "Only lift Z below";
def->category = "Retraction";
def->tooltip = "If you set this to a positive value, Z lift will only take place below the specified absolute Z. You can tune this setting for limiting lift to the first layers.";
def->sidetext = "mm";
def->cli = "retract-lift-below=f@";
@ -998,6 +1018,7 @@ PrintConfigDef::PrintConfigDef()
def = this->add("retract_restart_extra", coFloats);
def->label = "Extra length on restart";
def->category = "Retraction";
def->tooltip = "When the retraction is compensated after the travel move, the extruder will push this additional amount of filament. This setting is rarely needed.";
def->sidetext = "mm";
def->cli = "retract-restart-extra=f@";
@ -1020,7 +1041,7 @@ PrintConfigDef::PrintConfigDef()
def = this->add("retract_speed", coFloats);
def->label = "Speed";
def->full_label = "Retraction Speed";
def->category = "Retraction";
def->tooltip = "The speed for retractions (it only applies to the extruder motor). If you use the Firmware Retraction option, please note this value still affects the auto-speed pressure regulator.";
def->sidetext = "mm/s";
def->cli = "retract-speed=f@";
@ -1479,6 +1500,7 @@ PrintConfigDef::PrintConfigDef()
def = this->add("wipe", coBools);
def->label = "Wipe while retracting";
def->category = "Retraction";
def->tooltip = "This flag will move the nozzle while retracting to minimize the possible blob on leaky extruders.";
def->cli = "wipe!";
{

View File

@ -27,14 +27,19 @@
double get_abs_value(t_config_option_key opt_key, double ratio_over);
void apply(DynamicPrintConfig* other)
%code{% THIS->apply(*other, true); %};
void apply_only(DynamicPrintConfig* other, std::vector<std::string> opt_keys)
%code{% THIS->apply_only(*other, opt_keys, true); %};
std::vector<std::string> diff(DynamicPrintConfig* other)
%code{% RETVAL = THIS->diff(*other); %};
std::vector<std::string> diff_static(StaticPrintConfig* other)
%code{% RETVAL = THIS->diff(*other); %};
bool equals(DynamicPrintConfig* other)
%code{% RETVAL = THIS->equals(*other); %};
void apply_static(StaticPrintConfig* other)
%code{% THIS->apply(*other, true); %};
%name{get_keys} std::vector<std::string> keys();
void erase(t_config_option_key opt_key);
void clear();
void normalize();
%name{setenv} void setenv_();
double min_object_distance();

View File

@ -256,6 +256,12 @@ _constant()
void add_model_object(ModelObject* model_object, int idx = -1);
bool apply_config(DynamicPrintConfig* config)
%code%{ RETVAL = THIS->apply_config(*config); %};
bool apply_static_config(StaticPrintConfig* config)
%code%{
DynamicPrintConfig dpc;
dpc.apply(*config);
RETVAL = THIS->apply_config(dpc);
%};
bool has_infinite_skirt();
bool has_skirt();
std::string _validate()