mirror of
https://git.mirrors.martin98.com/https://github.com/slic3r/Slic3r.git
synced 2025-08-06 01:46:08 +08:00
Changes to the GUI. New preset editor dialog. Option overrides in plater and between configs. More.
This commit is contained in:
parent
c20d44c388
commit
5d77c160c6
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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) = @_;
|
||||
|
@ -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,10 +77,18 @@ sub new {
|
||||
EVT_CLOSE($self, sub {
|
||||
my (undef, $event) = @_;
|
||||
|
||||
if ($event->CanVeto && !$self->check_unsaved_changes) {
|
||||
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
|
||||
$Slic3r::GUI::Settings->{_}{main_frame_pos} = join ',', $self->GetScreenPositionXY;
|
||||
@ -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");
|
||||
}
|
||||
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,148 +571,32 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
sub _append_menu_item {
|
||||
|
@ -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;
|
||||
|
@ -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,9 +347,7 @@ sub new {
|
||||
$self->update;
|
||||
|
||||
{
|
||||
my $presets;
|
||||
if ($self->GetFrame->{mode} eq 'expert') {
|
||||
$presets = $self->{presets_sizer} = Wx::FlexGridSizer->new(3, 2, 1, 2);
|
||||
my $presets = $self->{presets_sizer} = Wx::FlexGridSizer->new(3, 3, 1, 2);
|
||||
$presets->AddGrowableCol(1, 1);
|
||||
$presets->SetFlexibleDirection(wxHORIZONTAL);
|
||||
my %group_labels = (
|
||||
@ -377,20 +357,42 @@ sub new {
|
||||
);
|
||||
$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, $choice);
|
||||
$self->_on_select_preset($group);
|
||||
});
|
||||
});
|
||||
|
||||
# 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 = shift;
|
||||
my ($group, $choice) = @_;
|
||||
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;
|
||||
|
||||
# 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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# call GetSelection() in scalar context as it's context-aware
|
||||
$self->{on_select_preset}->($group, scalar $choice->GetSelection)
|
||||
if $self->{on_select_preset};
|
||||
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;
|
||||
}
|
||||
|
||||
# get new config and generate on_config_change() event for updating plater and other things
|
||||
$self->on_config_change($self->GetFrame->config);
|
||||
return if !$self->GetFrame->is_loaded;
|
||||
|
||||
$self->config_changed;
|
||||
}
|
||||
|
||||
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,27 +580,67 @@ 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;
|
||||
$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;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!@presets) {
|
||||
unshift @presets, Slic3r::GUI::Preset->new(
|
||||
default => 1,
|
||||
name => '- default -',
|
||||
);
|
||||
}
|
||||
|
||||
$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) {
|
||||
foreach my $preset (@presets) {
|
||||
# load/generate the proper icon
|
||||
my $bitmap;
|
||||
if ($group eq 'filament') {
|
||||
my $config = $preset->config(['filament_colour']);
|
||||
my $rgb_hex = $config->filament_colour->[0];
|
||||
if ($preset->default) {
|
||||
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);
|
||||
@ -563,25 +655,113 @@ sub update_presets {
|
||||
$choice->AppendString($preset->name, $bitmap);
|
||||
}
|
||||
|
||||
if ($selected <= $#$presets) {
|
||||
my $preset_name = $choice->GetString($selected);
|
||||
if ($is_dirty) {
|
||||
$choice->SetString($selected, "$preset_name (modified)");
|
||||
}
|
||||
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';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#$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;
|
||||
}
|
||||
return if !$scale || $scale < 0;
|
||||
|
||||
$self->{list}->SetItem($obj_idx, 2, "$scale%");
|
||||
$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;
|
||||
|
@ -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;
|
||||
|
@ -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
52
lib/Slic3r/GUI/Preset.pm
Normal 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;
|
@ -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);
|
||||
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;
|
||||
|
||||
# trigger the on_presets_changed event so that we also restore the previous value
|
||||
# in the plater selector
|
||||
$self->_on_presets_changed;
|
||||
if ($res == wxID_CANCEL) {
|
||||
return 0;
|
||||
} elsif ($res == wxID_YES) {
|
||||
return $self->save_preset($old_preset->default ? undef : $old_preset->name);
|
||||
}
|
||||
}
|
||||
|
||||
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($self->{presets_choice}->GetSelection);
|
||||
$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;
|
65
lib/Slic3r/GUI/PresetEditorDialog.pm
Normal file
65
lib/Slic3r/GUI/PresetEditorDialog.pm
Normal 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;
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
32
slic3r.pl
32
slic3r.pl
@ -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:
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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:
|
||||
|
@ -132,6 +132,10 @@ PrintConfigDef::PrintConfigDef()
|
||||
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";
|
||||
def->tooltip = "When printing multiple objects or copies, this feature will complete each object before moving onto next one (and starting it from its bottom layer). This feature is useful to avoid the risk of ruined prints. Slic3r should warn and prevent you from extruder collisions, but beware.";
|
||||
@ -817,6 +821,14 @@ PrintConfigDef::PrintConfigDef()
|
||||
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";
|
||||
def->tooltip = "This is the acceleration your printer will use for perimeters. A high value like 9000 usually gives good results if your hardware is up to the job. Set zero to disable acceleration control for 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!";
|
||||
{
|
||||
|
@ -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();
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user