Merge branch 'new-gui'

Conflicts:
	lib/Slic3r.pm
	lib/Slic3r/Config.pm
	lib/Slic3r/GUI/OptionsGroup.pm
	lib/Slic3r/GUI/SkeinPanel.pm
	lib/Slic3r/Print.pm
This commit is contained in:
Alessandro Ranellucci 2012-07-18 15:48:04 +02:00
commit f173096a6c
32 changed files with 1854 additions and 450 deletions

View file

@ -0,0 +1,109 @@
package Slic3r::GUI::AboutDialog;
use strict;
use warnings;
use utf8;
use Wx qw(:font :html :misc :sizer :systemsettings);
use Wx::Event qw(EVT_HTML_LINK_CLICKED);
use Wx::Print;
use Wx::Html;
use base 'Wx::Dialog';
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, -1, 'About Slic3r', wxDefaultPosition, [600, 270]);
$self->SetBackgroundColour(Wx::wxWHITE);
my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
$self->SetSizer($hsizer);
# logo
my $logo = Slic3r::GUI::AboutDialog::Logo->new($self, -1, wxDefaultPosition, wxDefaultSize);
$logo->SetBackgroundColour(Wx::wxWHITE);
$hsizer->Add($logo, 0, wxEXPAND | wxLEFT | wxRIGHT, 30);
my $vsizer = Wx::BoxSizer->new(wxVERTICAL);
$hsizer->Add($vsizer, 1, wxEXPAND, 0);
# title
my $title = Wx::StaticText->new($self, -1, 'Slic3r', wxDefaultPosition, wxDefaultSize);
my $title_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
$title_font->SetWeight(wxFONTWEIGHT_BOLD);
$title_font->SetFamily(wxFONTFAMILY_ROMAN);
$title_font->SetPointSize(24);
$title->SetFont($title_font);
$vsizer->Add($title, 0, wxALIGN_LEFT | wxTOP, 30);
# version
my $version = Wx::StaticText->new($self, -1, "Version $Slic3r::VERSION", wxDefaultPosition, wxDefaultSize);
my $version_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
$version_font->SetPointSize(&Wx::wxMSW ? 9 : 11);
$version->SetFont($version_font);
$vsizer->Add($version, 0, wxALIGN_LEFT | wxBOTTOM, 10);
# text
my $text =
'<html>' .
'<body bgcolor="#ffffff" link="#808080">' .
'<font color="#808080">' .
'Copyright &copy; 2011-2012 Alessandro Ranellucci. All rights reserved. ' .
'<a href="http://slic3r.org/">Slic3r</a> is licensed under the ' .
'<a href="http://www.gnu.org/licenses/agpl-3.0.html">GNU Affero General Public License, version 3</a>.' .
'<br /><br /><br />' .
'Slic3r logo designed by Corey Daniels, <a href="http://www.famfamfam.com/lab/icons/silk/">Silk Icon Set</a> designed by Mark James. ' .
'Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess and numerous others.' .
'</font>' .
'</body>' .
'</html>';
my $html = Wx::HtmlWindow->new($self, -1, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_NEVER);
my $font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
my $size = &Wx::wxMSW ? 8 : 10;
$html->SetFonts($font->GetFaceName, $font->GetFaceName, [$size, $size, $size, $size, $size, $size, $size]);
$html->SetBorders(2);
$html->SetPage($text);
$vsizer->Add($html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 20);
EVT_HTML_LINK_CLICKED($self, $html, \&link_clicked);
return $self;
}
sub link_clicked {
my ($self, $event) = @_;
Wx::LaunchDefaultBrowser($event->GetLinkInfo->GetHref);
$event->Skip(0);
}
package Slic3r::GUI::AboutDialog::Logo;
use Wx qw(:bitmap :dc);
use Wx::Event qw(EVT_PAINT);
use base 'Wx::Panel';
sub new {
my $class = shift;
my $self = $class->SUPER::new(@_);
$self->{logo} = Wx::Bitmap->new("$Slic3r::var/Slic3r_192px.png", wxBITMAP_TYPE_PNG);
$self->SetMinSize(Wx::Size->new($self->{logo}->GetWidth, $self->{logo}->GetHeight));
EVT_PAINT($self, \&repaint);
return $self;
}
sub repaint {
my ($self, $event) = @_;
my $dc = Wx::PaintDC->new($self);
$dc->SetBackgroundMode(wxTRANSPARENT);
my $size = $self->GetSize;
my $logo_w = $self->{logo}->GetWidth;
my $logo_h = $self->{logo}->GetHeight;
$dc->DrawBitmap($self->{logo}, ($size->GetWidth - $logo_w) / 2, ($size->GetHeight - $logo_h) / 2, 1);
$event->Skip;
}
1;

View file

@ -0,0 +1,469 @@
package Slic3r::GUI::ConfigWizard;
use strict;
use warnings;
use utf8;
use Wx;
use base 'Wx::Wizard';
# adhere to various human interface guidelines
our $wizard = 'Wizard';
$wizard = 'Assistant' if &Wx::wxMAC || &Wx::wxGTK;
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, -1, "Configuration $wizard");
# Start from sane defaults
$self->{old} = Slic3r::Config->current;
Slic3r::Config->load_hash($Slic3r::Defaults, undef, 1);
$self->add_page(Slic3r::GUI::ConfigWizard::Page::Welcome->new($self));
$self->add_page(Slic3r::GUI::ConfigWizard::Page::Firmware->new($self));
$self->add_page(Slic3r::GUI::ConfigWizard::Page::Bed->new($self));
$self->add_page(Slic3r::GUI::ConfigWizard::Page::Nozzle->new($self));
$self->add_page(Slic3r::GUI::ConfigWizard::Page::Filament->new($self));
$self->add_page(Slic3r::GUI::ConfigWizard::Page::Temperature->new($self));
$self->add_page(Slic3r::GUI::ConfigWizard::Page::BedTemperature->new($self));
$self->add_page(Slic3r::GUI::ConfigWizard::Page::Finished->new($self));
$_->build_index for @{$self->{pages}};
return $self;
}
sub add_page {
my $self = shift;
my ($page) = @_;
my $n = push @{$self->{pages}}, $page;
# add first page to the page area sizer
$self->GetPageAreaSizer->Add($page) if $n == 1;
# link pages
$self->{pages}[$n-2]->set_next_page($page) if $n >= 2;
$page->set_previous_page($self->{pages}[$n-2]) if $n >= 2;
}
sub run {
my $self = shift;
my $modified;
if (Wx::Wizard::RunWizard($self, $self->{pages}[0])) {
$_->apply for @{$self->{pages}};
$modified = 1;
} else {
Slic3r::Config->load_hash($self->{old}, undef, 1);
$modified = 0;
}
$self->Destroy;
return $modified;
}
package Slic3r::GUI::ConfigWizard::Option;
use Wx qw(:combobox :misc :sizer :textctrl);
use Wx::Event qw(EVT_CHECKBOX EVT_COMBOBOX EVT_SPINCTRL EVT_TEXT);
use base 'Wx::StaticBoxSizer';
sub new {
my $class = shift;
my ($parent, %params) = @_;
my $box = Wx::StaticBox->new($parent, -1, '');
my $self = $class->SUPER::new($box, wxHORIZONTAL);
my $label_width = 200;
my $opt_key = $params{option};
my $opt = $Slic3r::Config::Options->{$opt_key};
my $callback = $params{callback} || sub {};
# label
my $label = Wx::StaticText->new($parent, -1, "$opt->{label}:", wxDefaultPosition, wxDefaultSize);
$label->Wrap($label_width);
$self->Add($label, 1, wxEXPAND);
# input field(s) and unit
my $field;
if ($opt->{type} =~ /^(i|f|s|s@)$/) {
my $style = $opt->{multiline} ? wxTE_MULTILINE : 0;
my $size = Wx::Size->new($opt->{width} || -1, $opt->{height} || -1);
my ($get, $set) = $opt->{type} eq 's@' ? qw(serialize deserialize) : qw(get_raw set);
if ($opt->{type} eq 'i') {
my $value = Slic3r::Config->$get($opt_key);
$field = Wx::SpinCtrl->new($parent, -1, $value, wxDefaultPosition, $size, $style, $opt->{min} || 0, $opt->{max} || 100, $value);
EVT_SPINCTRL($parent, $field, sub { $callback->($opt_key, $field->GetValue) });
} else {
$field = Wx::TextCtrl->new($parent, -1, Slic3r::Config->$get($opt_key), wxDefaultPosition, $size, $style);
EVT_TEXT($parent, $field, sub { $callback->($opt_key, $field->GetValue) });
}
} elsif ($opt->{type} eq 'bool') {
$field = Wx::CheckBox->new($parent, -1, '');
$field->SetValue(Slic3r::Config->get_raw($opt_key));
EVT_CHECKBOX($parent, $field, sub { $callback->($opt_key, $field->GetValue) });
} elsif ($opt->{type} eq 'point') {
$field = Wx::BoxSizer->new(wxHORIZONTAL);
my $field_size = Wx::Size->new(40, -1);
my $value = Slic3r::Config->get_raw($opt_key);
my @items = (
Wx::StaticText->new($parent, -1, 'x:'),
my $x_field = Wx::TextCtrl->new($parent, -1, $value->[0], wxDefaultPosition, $field_size),
Wx::StaticText->new($parent, -1, ' y:'),
my $y_field = Wx::TextCtrl->new($parent, -1, $value->[1], wxDefaultPosition, $field_size),
);
$field->Add($_) for @items;
EVT_TEXT($parent, $x_field, sub { $callback->($opt_key, [$x_field->GetValue, $y_field->GetValue]) });
EVT_TEXT($parent, $y_field, sub { $callback->($opt_key, [$x_field->GetValue, $y_field->GetValue]) });
} elsif ($opt->{type} eq 'select') {
$field = Wx::ComboBox->new($parent, -1, '', wxDefaultPosition, wxDefaultSize, $opt->{labels} || $opt->{values}, wxCB_READONLY);
my $value = Slic3r::Config->get_raw($opt_key);
$field->SetSelection(grep $opt->{values}[$_] eq $value, 0..$#{$opt->{values}});
EVT_COMBOBOX($parent, $field, sub { $callback->($opt_key, $opt->{values}[$field->GetSelection]) });
} else {
die 'Unsupported option type: ' . $opt->{type};
}
if ($opt->{sidetext}) {
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$sizer->Add($field);
my $sidetext = Wx::StaticText->new($parent, -1, $opt->{sidetext}, wxDefaultPosition, wxDefaultSize);
$sizer->Add($sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4);
$self->Add($sizer);
} else {
$self->Add($field, 0, $opt->{full_width} ? wxEXPAND : 0);
}
return $self;
}
package Slic3r::GUI::ConfigWizard::Index;
use Wx qw(:bitmap :dc :font :misc :sizer :systemsettings :window);
use Wx::Event qw(EVT_ERASE_BACKGROUND EVT_PAINT);
use base 'Wx::Panel';
sub new {
my $class = shift;
my ($parent, $title) = @_;
my $self = $class->SUPER::new($parent);
push @{$self->{titles}}, $title;
$self->{own_index} = 0;
$self->{bullets}->{before} = Wx::Bitmap->new("$Slic3r::var/bullet_black.png", wxBITMAP_TYPE_PNG);
$self->{bullets}->{own} = Wx::Bitmap->new("$Slic3r::var/bullet_blue.png", wxBITMAP_TYPE_PNG);
$self->{bullets}->{after} = Wx::Bitmap->new("$Slic3r::var/bullet_white.png", wxBITMAP_TYPE_PNG);
$self->{background} = Wx::Bitmap->new("$Slic3r::var/Slic3r_192px_transparent.png", wxBITMAP_TYPE_PNG);
$self->SetMinSize(Wx::Size->new($self->{background}->GetWidth, $self->{background}->GetHeight));
EVT_PAINT($self, \&repaint);
return $self;
}
sub repaint {
my ($self, $event) = @_;
my $size = $self->GetClientSize;
my $gab = 5;
my $dc = Wx::PaintDC->new($self);
$dc->SetBackgroundMode(wxTRANSPARENT);
$dc->SetFont($self->GetFont);
$dc->SetTextForeground($self->GetForegroundColour);
my $background_h = $self->{background}->GetHeight;
my $background_w = $self->{background}->GetWidth;
$dc->DrawBitmap($self->{background}, ($size->GetWidth - $background_w) / 2, ($size->GetHeight - $background_h) / 2, 1);
my $label_h = $self->{bullets}->{own}->GetHeight;
$label_h = $dc->GetCharHeight if $dc->GetCharHeight > $label_h;
my $label_w = $size->GetWidth;
my $i = 0;
foreach (@{$self->{titles}}) {
my $bullet = $self->{bullets}->{own};
$bullet = $self->{bullets}->{before} if $i < $self->{own_index};
$bullet = $self->{bullets}->{after} if $i > $self->{own_index};
$dc->SetTextForeground(Wx::Colour->new(128, 128, 128)) if $i > $self->{own_index};
$dc->DrawLabel($_, $bullet, Wx::Rect->new(0, $i * ($label_h + $gab), $label_w, $label_h));
$i++;
}
$event->Skip;
}
sub prepend_title {
my $self = shift;
my ($title) = @_;
unshift @{$self->{titles}}, $title;
$self->{own_index}++;
$self->Refresh;
}
sub append_title {
my $self = shift;
my ($title) = @_;
push @{$self->{titles}}, $title;
$self->Refresh;
}
package Slic3r::GUI::ConfigWizard::Page;
use Wx qw(:font :misc :sizer :staticline :systemsettings);
use base 'Wx::WizardPage';
sub new {
my $class = shift;
my ($parent, $title, $short_title) = @_;
my $self = $class->SUPER::new($parent);
my $sizer = Wx::FlexGridSizer->new(0, 2, 10, 10);
$sizer->AddGrowableCol(1, 1);
$sizer->AddGrowableRow(1, 1);
$sizer->AddStretchSpacer(0);
$self->SetSizer($sizer);
# title
my $text = Wx::StaticText->new($self, -1, $title, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
my $bold_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
$bold_font->SetWeight(wxFONTWEIGHT_BOLD);
$bold_font->SetPointSize(14);
$text->SetFont($bold_font);
$sizer->Add($text, 0, wxALIGN_LEFT, 0);
# index
$self->{short_title} = $short_title ? $short_title : $title;
$self->{index} = Slic3r::GUI::ConfigWizard::Index->new($self, $self->{short_title});
$sizer->Add($self->{index}, 1, wxEXPAND | wxTOP | wxRIGHT, 10);
# contents
$self->{width} = 400;
$self->{vsizer} = Wx::BoxSizer->new(wxVERTICAL);
$sizer->Add($self->{vsizer}, 1, wxEXPAND, 0);
return $self;
}
sub append_text {
my $self = shift;
my ($text) = @_;
my $para = Wx::StaticText->new($self, -1, $text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
$para->Wrap($self->{width});
$para->SetMinSize([$self->{width}, -1]);
$self->{vsizer}->Add($para, 0, wxALIGN_LEFT | wxTOP | wxBOTTOM, 10);
}
sub append_option {
my $self = shift;
my ($opt_key) = @_;
my $option = Slic3r::GUI::ConfigWizard::Option->new($self, option => $opt_key,
callback => sub {
my ($opt_key, $value) = @_;
$self->{options}->{$opt_key} = $value;
});
$self->{vsizer}->Add($option, 0, wxEXPAND | wxTOP | wxBOTTOM, 10);
}
sub apply {
my $self = shift;
Slic3r::Config->set($_, $self->{options}->{$_}) foreach (keys %{$self->{options}});
}
sub set_previous_page {
my $self = shift;
my ($previous_page) = @_;
$self->{previous_page} = $previous_page;
}
sub GetPrev {
my $self = shift;
return $self->{previous_page};
}
sub set_next_page {
my $self = shift;
my ($next_page) = @_;
$self->{next_page} = $next_page;
}
sub GetNext {
my $self = shift;
return $self->{next_page};
}
sub get_short_title {
my $self = shift;
return $self->{short_title};
}
sub build_index {
my $self = shift;
my $page = $self;
$self->{index}->prepend_title($page->get_short_title) while ($page = $page->GetPrev);
$page = $self;
$self->{index}->append_title($page->get_short_title) while ($page = $page->GetNext);
}
package Slic3r::GUI::ConfigWizard::Page::Welcome;
use base 'Slic3r::GUI::ConfigWizard::Page';
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, "Welcome to the Slic3r Configuration $wizard", 'Welcome');
$self->append_text('Hello, welcome to Slic3r! This '.lc($wizard).' helps you with the initial configuration; just a few settings and you will be ready to print.');
$self->append_text('To import an existing configuration instead, cancel this '.lc($wizard).' and use the Open Config menu item found in the File menu.');
$self->append_text('To continue, click Next.');
return $self;
}
package Slic3r::GUI::ConfigWizard::Page::Firmware;
use base 'Slic3r::GUI::ConfigWizard::Page';
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, 'Firmware Type');
$self->append_text('Choose the type of firmware used by your printer, then click Next.');
$self->append_option('gcode_flavor');
return $self;
}
package Slic3r::GUI::ConfigWizard::Page::Bed;
use base 'Slic3r::GUI::ConfigWizard::Page';
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, 'Bed Size');
$self->append_text('Enter the size of your printers bed, then click Next.');
$self->append_option('bed_size');
return $self;
}
sub apply {
my $self = shift;
$self->SUPER::apply;
# set print_center to centre of bed_size
my $bed_size = Slic3r::Config->get_raw('bed_size');
Slic3r::Config->set('print_center', [$bed_size->[0]/2, $bed_size->[1]/2]);
}
package Slic3r::GUI::ConfigWizard::Page::Nozzle;
use base 'Slic3r::GUI::ConfigWizard::Page';
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, 'Nozzle Diameter');
$self->append_text('Enter the diameter of your printers hot end nozzle, then click Next.');
$self->append_option('nozzle_diameter');
return $self;
}
sub apply {
my $self = shift;
$self->SUPER::apply;
# set first_layer_height + layer_height based on nozzle_diameter
my $nozzle = Slic3r::Config->get_raw('nozzle_diameter');
Slic3r::Config->set('first_layer_height', $nozzle);
Slic3r::Config->set('layer_height', $nozzle - 0.1);
}
package Slic3r::GUI::ConfigWizard::Page::Filament;
use base 'Slic3r::GUI::ConfigWizard::Page';
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, 'Filament Diameter');
$self->append_text('Enter the diameter of your filament, then click Next.');
$self->append_text('Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average.');
$self->append_option('filament_diameter');
return $self;
}
package Slic3r::GUI::ConfigWizard::Page::Temperature;
use base 'Slic3r::GUI::ConfigWizard::Page';
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, 'Extrusion Temperature');
$self->append_text('Enter the temperature needed for extruding your filament, then click Next.');
$self->append_text('A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS.');
$self->append_option('temperature');
return $self;
}
sub apply {
my $self = shift;
$self->SUPER::apply;
# set first_layer_temperature to temperature + 5
my $temperature = Slic3r::Config->get_raw('temperature');
Slic3r::Config->set('first_layer_temperature', $temperature + 5);
}
package Slic3r::GUI::ConfigWizard::Page::BedTemperature;
use base 'Slic3r::GUI::ConfigWizard::Page';
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, 'Bed Temperature');
$self->append_text('Enter the bed temperature needed for getting your filament to stick to your heated bed, then click Next.');
$self->append_text('A rule of thumb is 60 °C for PLA and 110 °C for ABS.');
$self->append_option('bed_temperature');
return $self;
}
sub apply {
my $self = shift;
$self->SUPER::apply;
# set first_layer_bed_temperature to temperature + 5
my $temperature = Slic3r::Config->get_raw('bed_temperature');
Slic3r::Config->set('first_layer_bed_temperature', $temperature + 5);
}
package Slic3r::GUI::ConfigWizard::Page::Finished;
use base 'Slic3r::GUI::ConfigWizard::Page';
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, 'Congratulations!', 'Finish');
$self->append_text("You have successfully completed the Slic3r Configuration $wizard. " .
'Slic3r is now configured for your printer and filament.');
$self->append_text('To close this '.lc($wizard).' and apply the newly created configuration, click Finish.');
return $self;
}
1;

View file

@ -2,14 +2,14 @@ package Slic3r::GUI::OptionsGroup;
use strict;
use warnings;
use Wx qw(:sizer wxSYS_DEFAULT_GUI_FONT);
use Wx::Event qw(EVT_TEXT EVT_CHECKBOX EVT_CHOICE);
use Wx qw(:combobox :font :misc :sizer :systemsettings :textctrl);
use Wx::Event qw(EVT_CHECKBOX EVT_COMBOBOX EVT_SPINCTRL EVT_TEXT);
use base 'Wx::StaticBoxSizer';
# not very elegant, but this solution is temporary waiting for a better GUI
our @reload_callbacks = ();
our %fields = (); # $key => [$control]
our %reload_callbacks = (); # key => $cb
our %fields = (); # $key => [$control]
sub new {
my $class = shift;
@ -18,34 +18,31 @@ sub new {
my $box = Wx::StaticBox->new($parent, -1, $p{title});
my $self = $class->SUPER::new($box, wxVERTICAL);
my $grid_sizer = Wx::FlexGridSizer->new(scalar(@{$p{options}}), 2, 2, 0);
#grab the default font, to fix Windows font issues/keep things consistent
my $bold_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
$bold_font->SetWeight(&Wx::wxFONTWEIGHT_BOLD);
my $grid_sizer = Wx::FlexGridSizer->new(scalar(@{$p{options}}), 2, ($p{no_labels} ? 1 : 2), 0);
$grid_sizer->SetFlexibleDirection(wxHORIZONTAL);
$grid_sizer->AddGrowableCol($p{no_labels} ? 0 : 1);
my $sidetext_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
my $onChange = $p{on_change} || sub {};
foreach my $opt_key (@{$p{options}}) {
my $index;
$opt_key =~ s/#(\d+)$// and $index = $1;
my $opt = $Slic3r::Config::Options->{$opt_key};
my $label = Wx::StaticText->new($parent, -1, "$opt->{label}:", Wx::wxDefaultPosition,
[$p{label_width} || 180, -1]);
$label->Wrap($p{label_width} || 180); # needed to avoid Linux/GTK bug
my $label;
if (!$p{no_labels}) {print $opt_key, "\n" if !defined $opt->{label};
$label = Wx::StaticText->new($parent, -1, "$opt->{label}:", wxDefaultPosition, [$p{label_width} || 180, -1]);
$label->Wrap($p{label_width} || 180) ; # needed to avoid Linux/GTK bug
$grid_sizer->Add($label);
}
#set the bold font point size to the same size as all the other labels (for consistency)
$bold_font->SetPointSize($label->GetFont()->GetPointSize());
$label->SetFont($bold_font) if $opt->{important};
my $field;
if ($opt->{type} =~ /^(i|f|s|s@)$/) {
my $style = 0;
my $size = Wx::wxDefaultSize;
if ($opt->{multiline}) {
$style = &Wx::wxTE_MULTILINE;
$size = Wx::Size->new($opt->{width} || -1, $opt->{height} || -1);
}
$style = wxTE_MULTILINE if $opt->{multiline};
my $size = Wx::Size->new($opt->{width} || -1, $opt->{height} || -1);
# if it's an array type but no index was specified, use the serialized version
my ($get_m, $set_m) = $opt->{type} =~ /\@$/ && !defined $index
@ -57,8 +54,10 @@ sub new {
$val = $val->[$index] if defined $index;
return $val;
};
$field = Wx::TextCtrl->new($parent, -1, $get->(), Wx::wxDefaultPosition, $size, $style);
push @reload_callbacks, sub { $field->SetValue($get->()) };
$field = $opt->{type} eq 'i'
? Wx::SpinCtrl->new($parent, -1, $get->(), wxDefaultPosition, $size, $style, $opt->{min} || 0, $opt->{max} || 100, $get->())
: Wx::TextCtrl->new($parent, -1, $get->(), wxDefaultPosition, $size, $style);
$reload_callbacks{$opt_key} = sub { $field->SetValue($get->()) };
my $set = sub {
my $val = $field->GetValue;
@ -67,51 +66,70 @@ sub new {
} else {
Slic3r::Config->$set_m($opt_key, $val);
}
$onChange->($opt_key);
};
EVT_TEXT($parent, $field, sub { $set->() });
$opt->{type} eq 'i'
? EVT_SPINCTRL($parent, $field, $set)
: EVT_TEXT($parent, $field, $set);
} elsif ($opt->{type} eq 'bool') {
$field = Wx::CheckBox->new($parent, -1, "");
$field->SetValue(Slic3r::Config->get_raw($opt_key));
EVT_CHECKBOX($parent, $field, sub { Slic3r::Config->set($opt_key, $field->GetValue) });
push @reload_callbacks, sub { $field->SetValue(Slic3r::Config->get_raw($opt_key)) };
EVT_CHECKBOX($parent, $field, sub { Slic3r::Config->set($opt_key, $field->GetValue); $onChange->($opt_key) });
$reload_callbacks{$opt_key} = sub { $field->SetValue(Slic3r::Config->get_raw($opt_key)) };
} elsif ($opt->{type} eq 'point') {
$field = Wx::BoxSizer->new(wxHORIZONTAL);
my $field_size = Wx::Size->new(40, -1);
my $value = Slic3r::Config->get_raw($opt_key);
$field->Add($_) for (
my @items = (
Wx::StaticText->new($parent, -1, "x:"),
my $x_field = Wx::TextCtrl->new($parent, -1, $value->[0], Wx::wxDefaultPosition, $field_size),
my $x_field = Wx::TextCtrl->new($parent, -1, $value->[0], wxDefaultPosition, $field_size),
Wx::StaticText->new($parent, -1, " y:"),
my $y_field = Wx::TextCtrl->new($parent, -1, $value->[1], Wx::wxDefaultPosition, $field_size),
my $y_field = Wx::TextCtrl->new($parent, -1, $value->[1], wxDefaultPosition, $field_size),
);
$field->Add($_) for @items;
if ($opt->{tooltip}) {
$_->SetToolTipString($opt->{tooltip}) for @items;
}
my $set_value = sub {
my ($i, $value) = @_;
my $val = Slic3r::Config->get_raw($opt_key);
$val->[$i] = $value;
Slic3r::Config->set($opt_key, $val);
};
EVT_TEXT($parent, $x_field, sub { $set_value->(0, $x_field->GetValue) });
EVT_TEXT($parent, $y_field, sub { $set_value->(1, $y_field->GetValue) });
push @reload_callbacks, sub {
EVT_TEXT($parent, $x_field, sub { $set_value->(0, $x_field->GetValue); $onChange->($opt_key) });
EVT_TEXT($parent, $y_field, sub { $set_value->(1, $y_field->GetValue); $onChange->($opt_key) });
$reload_callbacks{$opt_key} = sub {
my $value = Slic3r::Config->get_raw($opt_key);
$x_field->SetValue($value->[0]);
$y_field->SetValue($value->[1]);
};
$fields{$opt_key} = [$x_field, $y_field];
} elsif ($opt->{type} eq 'select') {
$field = Wx::Choice->new($parent, -1, Wx::wxDefaultPosition, Wx::wxDefaultSize, $opt->{labels} || $opt->{values});
EVT_CHOICE($parent, $field, sub {
$field = Wx::ComboBox->new($parent, -1, "", wxDefaultPosition, wxDefaultSize, $opt->{labels} || $opt->{values}, wxCB_READONLY);
EVT_COMBOBOX($parent, $field, sub {
Slic3r::Config->set($opt_key, $opt->{values}[$field->GetSelection]);
$onChange->($opt_key);
});
push @reload_callbacks, sub {
$reload_callbacks{$opt_key} = sub {
my $value = Slic3r::Config->get_raw($opt_key);
$field->SetSelection(grep $opt->{values}[$_] eq $value, 0..$#{$opt->{values}});
};
$reload_callbacks[-1]->();
$reload_callbacks{$opt_key}->();
} else {
die "Unsupported option type: " . $opt->{type};
}
$grid_sizer->Add($_) for $label, $field;
$label->SetToolTipString($opt->{tooltip}) if $label && $opt->{tooltip};
$field->SetToolTipString($opt->{tooltip}) if $opt->{tooltip} && $field->can('SetToolTipString');
if ($opt->{sidetext}) {
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$sizer->Add($field);
my $sidetext = Wx::StaticText->new($parent, -1, $opt->{sidetext}, wxDefaultPosition, wxDefaultSize);
$sidetext->SetFont($sidetext_font);
$sizer->Add($sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL , 4);
$grid_sizer->Add($sizer);
} else {
$grid_sizer->Add($field, 0, $opt->{full_width} ? wxEXPAND : 0);
}
$fields{$opt_key} ||= [$field];
}

View file

@ -8,10 +8,8 @@ use Math::ConvexHull qw(convex_hull);
use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 scale unscale);
use Slic3r::Geometry::Clipper qw(JT_ROUND);
use threads::shared qw(shared_clone);
use Wx qw(:sizer :progressdialog wxOK wxICON_INFORMATION wxICON_WARNING wxICON_ERROR wxICON_QUESTION
wxOK wxCANCEL wxID_OK wxFD_OPEN wxFD_SAVE wxDEFAULT wxNORMAL);
use Wx::Event qw(EVT_BUTTON EVT_PAINT EVT_MOUSE_EVENTS EVT_LIST_ITEM_SELECTED EVT_LIST_ITEM_DESELECTED
EVT_COMMAND EVT_TOOL);
use Wx qw(:bitmap :brush :button :dialog :filedialog :font :icon :id :listctrl :misc :pen :sizer :toolbar :window);
use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_LIST_ITEM_DESELECTED EVT_LIST_ITEM_SELECTED EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL);
use base 'Wx::Panel';
use constant TB_MORE => 1;
@ -33,58 +31,58 @@ sub new {
my ($parent) = @_;
my $self = $class->SUPER::new($parent, -1);
$self->{canvas} = Wx::Panel->new($self, -1, [-1, -1], [300, 300]);
$self->{canvas} = Wx::Panel->new($self, -1, wxDefaultPosition, [300, 300]);
$self->{canvas}->SetBackgroundColour(Wx::wxWHITE);
EVT_PAINT($self->{canvas}, \&repaint);
EVT_MOUSE_EVENTS($self->{canvas}, \&mouse_event);
$self->{objects_brush} = Wx::Brush->new(Wx::Colour->new(210,210,210), &Wx::wxSOLID);
$self->{selected_brush} = Wx::Brush->new(Wx::Colour->new(255,128,128), &Wx::wxSOLID);
$self->{transparent_brush} = Wx::Brush->new(Wx::Colour->new(0,0,0), &Wx::wxTRANSPARENT);
$self->{grid_pen} = Wx::Pen->new(Wx::Colour->new(230,230,230), 1, &Wx::wxSOLID);
$self->{print_center_pen} = Wx::Pen->new(Wx::Colour->new(200,200,200), 1, &Wx::wxSOLID);
$self->{clearance_pen} = Wx::Pen->new(Wx::Colour->new(0,0,200), 1, &Wx::wxSOLID);
$self->{skirt_pen} = Wx::Pen->new(Wx::Colour->new(150,150,150), 1, &Wx::wxSOLID);
$self->{objects_brush} = Wx::Brush->new(Wx::Colour->new(210,210,210), wxSOLID);
$self->{selected_brush} = Wx::Brush->new(Wx::Colour->new(255,128,128), wxSOLID);
$self->{transparent_brush} = Wx::Brush->new(Wx::Colour->new(0,0,0), wxTRANSPARENT);
$self->{grid_pen} = Wx::Pen->new(Wx::Colour->new(230,230,230), 1, wxSOLID);
$self->{print_center_pen} = Wx::Pen->new(Wx::Colour->new(200,200,200), 1, wxSOLID);
$self->{clearance_pen} = Wx::Pen->new(Wx::Colour->new(0,0,200), 1, wxSOLID);
$self->{skirt_pen} = Wx::Pen->new(Wx::Colour->new(150,150,150), 1, wxSOLID);
$self->{list} = Wx::ListView->new($self, -1, [-1, -1], [-1, 180], &Wx::wxLC_SINGLE_SEL | &Wx::wxLC_REPORT | &Wx::wxBORDER_DEFAULT);
$self->{list}->InsertColumn(0, "Name", &Wx::wxLIST_FORMAT_LEFT, 300);
$self->{list}->InsertColumn(1, "Copies", &Wx::wxLIST_FORMAT_CENTER, 50);
$self->{list}->InsertColumn(2, "Scale", &Wx::wxLIST_FORMAT_CENTER, 50);
$self->{list} = Wx::ListView->new($self, -1, wxDefaultPosition, [-1, 180], wxLC_SINGLE_SEL | wxLC_REPORT | wxBORDER_SUNKEN);
$self->{list}->InsertColumn(0, "Name", wxLIST_FORMAT_LEFT, 300);
$self->{list}->InsertColumn(1, "Copies", wxLIST_FORMAT_CENTER, 50);
$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);
# toolbar for object manipulation
if (!&Wx::wxMSW) {
Wx::ToolTip::Enable(1);
$self->{htoolbar} = Wx::ToolBar->new($self, -1, [-1, -1], [-1, -1], &Wx::wxTB_HORIZONTAL | &Wx::wxTB_HORZ_TEXT);
$self->{htoolbar}->AddTool(TB_MORE, "More", Wx::Bitmap->new("$Slic3r::var/add.png", &Wx::wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_LESS, "Less", Wx::Bitmap->new("$Slic3r::var/delete.png", &Wx::wxBITMAP_TYPE_PNG), '');
$self->{htoolbar} = Wx::ToolBar->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTB_HORIZONTAL | wxTB_TEXT | wxBORDER_SIMPLE);
$self->{htoolbar}->AddTool(TB_MORE, "More", Wx::Bitmap->new("$Slic3r::var/add.png", wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_LESS, "Less", Wx::Bitmap->new("$Slic3r::var/delete.png", wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddSeparator;
$self->{htoolbar}->AddTool(TB_45CCW, "45° ccw", Wx::Bitmap->new("$Slic3r::var/arrow_rotate_anticlockwise.png", &Wx::wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_45CW, "45° cw", Wx::Bitmap->new("$Slic3r::var/arrow_rotate_clockwise.png", &Wx::wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_ROTATE, "Rotate...", Wx::Bitmap->new("$Slic3r::var/arrow_rotate_clockwise.png", &Wx::wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_45CCW, "45° ccw", Wx::Bitmap->new("$Slic3r::var/arrow_rotate_anticlockwise.png", wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_45CW, "45° cw", Wx::Bitmap->new("$Slic3r::var/arrow_rotate_clockwise.png", wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_ROTATE, "Rotate", Wx::Bitmap->new("$Slic3r::var/arrow_rotate_clockwise.png", wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddSeparator;
$self->{htoolbar}->AddTool(TB_SCALE, "Scale...", Wx::Bitmap->new("$Slic3r::var/arrow_out.png", &Wx::wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_SCALE, "Scale", Wx::Bitmap->new("$Slic3r::var/arrow_out.png", wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddSeparator;
$self->{htoolbar}->AddTool(TB_SPLIT, "Split", Wx::Bitmap->new("$Slic3r::var/shape_ungroup.png", &Wx::wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_SPLIT, "Split", Wx::Bitmap->new("$Slic3r::var/shape_ungroup.png", wxBITMAP_TYPE_PNG), '');
} else {
my %tbar_buttons = (increase => "More", decrease => "Less", rotate45ccw => "45°", rotate45cw => "45°",
rotate => "Rotate…", changescale => "Scale…", split => "Split");
$self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL);
for (qw(increase decrease rotate45ccw rotate45cw rotate changescale split)) {
$self->{"btn_$_"} = Wx::Button->new($self, -1, $tbar_buttons{$_}, [-1,-1], [-1,-1], &Wx::wxBU_EXACTFIT);
$self->{"btn_$_"} = Wx::Button->new($self, -1, $tbar_buttons{$_}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
$self->{btoolbar}->Add($self->{"btn_$_"});
}
}
# general buttons
$self->{btn_load} = Wx::Button->new($self, -1, "Add…", [-1,-1], [-1,-1], &Wx::wxBU_LEFT);
$self->{btn_remove} = Wx::Button->new($self, -1, "Delete", [-1,-1], [-1,-1], &Wx::wxBU_LEFT);
$self->{btn_reset} = Wx::Button->new($self, -1, "Delete All", [-1,-1], [-1,-1], &Wx::wxBU_LEFT);
$self->{btn_arrange} = Wx::Button->new($self, -1, "Autoarrange", [-1,-1], [-1,-1], &Wx::wxBU_LEFT);
$self->{btn_export_gcode} = Wx::Button->new($self, -1, "Export G-code…", [-1,-1], [-1,-1], &Wx::wxBU_LEFT);
$self->{btn_load} = Wx::Button->new($self, -1, "Add…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
$self->{btn_remove} = Wx::Button->new($self, -1, "Delete", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
$self->{btn_reset} = Wx::Button->new($self, -1, "Delete All", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
$self->{btn_arrange} = Wx::Button->new($self, -1, "Autoarrange", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
$self->{btn_export_gcode} = Wx::Button->new($self, -1, "Export G-code…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
$self->{btn_export_gcode}->SetDefault;
$self->{btn_export_stl} = Wx::Button->new($self, -1, "Export STL…", [-1,-1], [-1,-1], &Wx::wxBU_LEFT);
$self->{btn_export_stl} = Wx::Button->new($self, -1, "Export STL…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
if (&Wx::wxVERSION_STRING =~ / 2\.9\.[1-9]/) {
my %icons = qw(
@ -104,7 +102,7 @@ sub new {
split shape_ungroup.png
);
for (grep $self->{"btn_$_"}, keys %icons) {
$self->{"btn_$_"}->SetBitmap(Wx::Bitmap->new("$Slic3r::var/$icons{$_}", &Wx::wxBITMAP_TYPE_PNG));
$self->{"btn_$_"}->SetBitmap(Wx::Bitmap->new("$Slic3r::var/$icons{$_}", wxBITMAP_TYPE_PNG));
}
}
$self->selection_changed(0);
@ -148,7 +146,7 @@ sub new {
my ($self, $event) = @_;
my ($percent, $message) = @{$event->GetData};
$self->statusbar->SetProgress($percent);
$self->statusbar->SetStatusText("$message...");
$self->statusbar->SetStatusText("$message");
});
EVT_COMMAND($self, -1, $MESSAGE_DIALOG_EVENT, sub {
@ -185,17 +183,35 @@ sub new {
my $list_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$list_sizer->Add($self->{list}, 1, wxEXPAND | wxALL, 0);
$list_sizer->Add($self->{vtoolbar}, 0, wxEXPAND, 0) if $self->{vtoolbar};
my $vertical_sizer = Wx::BoxSizer->new(wxVERTICAL);
$vertical_sizer->Add($self->{htoolbar}, 0, wxEXPAND, 0) if $self->{htoolbar};
$vertical_sizer->Add($self->{btoolbar}, 0, wxEXPAND, 0) if $self->{btoolbar};
$vertical_sizer->Add($list_sizer, 0, wxEXPAND | &Wx::wxBOTTOM, 10);
$vertical_sizer->Add($list_sizer, 0, wxEXPAND | wxBOTTOM, 10);
$vertical_sizer->Add($buttons, 0, wxEXPAND);
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$sizer->Add($self->{canvas}, 0, wxALL, 10);
$sizer->Add($vertical_sizer, 1, wxEXPAND | wxALL, 10);
my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
$hsizer->Add($self->{canvas}, 0, wxALL, 10);
$hsizer->Add($vertical_sizer, 1, wxEXPAND | wxALL, 10);
my $presets = Wx::BoxSizer->new(wxHORIZONTAL);
my %group_labels = (
print => 'Print settings',
filament => 'Filament',
printer => 'Printer',
);
$self->{preset_choosers} = {};
for my $group (qw(print filament printer)) {
my $text = Wx::StaticText->new($self, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT);
$self->{preset_choosers}{$group} = Wx::Choice->new($self, -1, wxDefaultPosition, [150, -1], []);
$presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4);
$presets->Add($self->{preset_choosers}{$group}, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 15);
}
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
$sizer->Add($hsizer, 0, wxEXPAND | wxBOTTOM, 10);
$sizer->Add($presets, 0, wxEXPAND | wxLEFT | wxRIGHT, 20);
$sizer->SetSizeHints($self);
$self->SetSizer($sizer);
}
@ -206,7 +222,7 @@ sub load {
my $self = shift;
my $dir = $Slic3r::GUI::SkeinPanel::last_skein_dir || $Slic3r::GUI::SkeinPanel::last_config_dir || "";
my $dialog = Wx::FileDialog->new($self, 'Choose one or more files (STL/OBJ/AMF):', $dir, "", $Slic3r::GUI::SkeinPanel::model_wildcard, wxFD_OPEN | &Wx::wxFD_MULTIPLE | &Wx::wxFD_FILE_MUST_EXIST);
my $dialog = Wx::FileDialog->new($self, 'Choose one or more files (STL/OBJ/AMF):', $dir, "", $Slic3r::GUI::SkeinPanel::model_wildcard, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
if ($dialog->ShowModal != wxID_OK) {
$dialog->Destroy;
return;
@ -222,7 +238,7 @@ sub load_file {
$Slic3r::GUI::SkeinPanel::last_skein_dir = dirname($input_file);
my $process_dialog = Wx::ProgressDialog->new('Loading...', "Processing input file...", 100, $self, 0);
my $process_dialog = Wx::ProgressDialog->new('Loading…', "Processing input file…", 100, $self, 0);
$process_dialog->Pulse;
local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self);
$self->{print}->add_object_from_file($input_file);
@ -336,7 +352,7 @@ sub rotate {
return if !$angle || $angle == -1;
}
$self->statusbar->SetStatusText("Rotating object...");
$self->statusbar->SetStatusText("Rotating object");
$self->statusbar->StartBusy;
# rotate, realign to 0,0 and update size
@ -374,7 +390,7 @@ sub changescale {
$scale = Wx::GetNumberFromUser("", "Enter the scale % for the selected object:", "Scale", $scale*100, 0, 5000, $self);
return if !$scale || $scale == -1;
$self->statusbar->SetStatusText("Scaling object...");
$self->statusbar->SetStatusText("Scaling object");
$self->statusbar->StartBusy;
my $object = $self->{print}->objects->[$obj_idx];
@ -425,7 +441,7 @@ sub export_gcode {
my $self = shift;
if ($self->{export_thread}) {
Wx::MessageDialog->new($self, "Another slicing job is currently running.", 'Error', wxOK | &Wx::wxICON_ERROR)->ShowModal;
Wx::MessageDialog->new($self, "Another slicing job is currently running.", 'Error', wxOK | wxICON_ERROR)->ShowModal;
return;
}
@ -471,7 +487,7 @@ sub export_gcode {
progressbar => sub {
my ($percent, $message) = @_;
$self->statusbar->SetProgress($percent);
$self->statusbar->SetStatusText("$message...");
$self->statusbar->SetStatusText("$message");
},
message_dialog => sub { Wx::MessageDialog->new($self, @_)->ShowModal },
on_completed => sub { $self->on_export_completed(@_) },
@ -515,12 +531,13 @@ sub export_gcode2 {
}
my $message = "Your files were successfully sliced";
$message .= sprintf " in %d minutes and %.3f seconds",
int($print->processing_time/60),
$print->processing_time - int($print->processing_time/60)*60
if $print->processing_time;
if ($print->processing_time) {
$message .= ' in';
my $minutes = int($print->processing_time/60);
$message .= sprintf " %d minutes and", $minutes if $minutes;
$message .= sprintf " %.1f seconds", $print->processing_time - $minutes*60;
}
$message .= ".";
Slic3r::GUI::notify($message);
$params{on_completed}->($message);
$print->cleanup;
};
@ -536,12 +553,11 @@ sub on_export_completed {
$self->statusbar->SetCancelCallback(undef);
$self->statusbar->StopBusy;
$self->statusbar->SetStatusText("G-code file exported to $self->{output_file}");
Wx::MessageDialog->new($self, $message, 'Done!', wxOK | wxICON_INFORMATION)->ShowModal;
&Wx::wxTheApp->notify($message);
}
sub on_export_failed {
my $self = shift;
my ($message) = @_;
$self->{export_thread}->detach if $self->{export_thread};
$self->{export_thread} = undef;
@ -561,7 +577,7 @@ sub export_stl {
$output_file = $print->expanded_output_filepath($output_file);
$output_file =~ s/\.gcode$/.stl/i;
my $dlg = Wx::FileDialog->new($self, 'Save STL file as:', dirname($output_file),
basename($output_file), $Slic3r::GUI::SkeinPanel::model_wildcard, wxFD_SAVE | &Wx::wxFD_OVERWRITE_PROMPT);
basename($output_file), $Slic3r::GUI::SkeinPanel::model_wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if ($dlg->ShowModal != wxID_OK) {
$dlg->Destroy;
return;
@ -674,12 +690,12 @@ sub repaint {
$dc->DrawLine(0, $size[Y]/2, $size[X], $size[Y]/2);
$dc->SetTextForeground(Wx::Colour->new(0,0,0));
$dc->SetFont(Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL));
$dc->DrawLabel("X = " . $Slic3r::print_center->[X], Wx::Rect->new(0, 0, $self->GetSize->GetWidth, $self->GetSize->GetHeight), &Wx::wxALIGN_CENTER_HORIZONTAL | &Wx::wxALIGN_BOTTOM);
$dc->DrawLabel("X = " . $Slic3r::print_center->[X], Wx::Rect->new(0, 0, $self->GetSize->GetWidth, $self->GetSize->GetHeight), wxALIGN_CENTER_HORIZONTAL | wxALIGN_BOTTOM);
$dc->DrawRotatedText("Y = " . $Slic3r::print_center->[Y], 0, $size[Y]/2+15, 90);
}
# draw frame
$dc->SetPen(Wx::wxBLACK_PEN);
$dc->SetPen(wxBLACK_PEN);
$dc->SetBrush($parent->{transparent_brush});
$dc->DrawRectangle(0, 0, @size);
@ -687,11 +703,11 @@ sub repaint {
if (!@{$print->objects}) {
$dc->SetTextForeground(Wx::Colour->new(150,50,50));
$dc->SetFont(Wx::Font->new(14, wxDEFAULT, wxNORMAL, wxNORMAL));
$dc->DrawLabel("Drag your objects here", Wx::Rect->new(0, 0, $self->GetSize->GetWidth, $self->GetSize->GetHeight), &Wx::wxALIGN_CENTER_HORIZONTAL | &Wx::wxALIGN_CENTER_VERTICAL);
$dc->DrawLabel("Drag your objects here", Wx::Rect->new(0, 0, $self->GetSize->GetWidth, $self->GetSize->GetHeight), wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL);
}
# draw thumbnails
$dc->SetPen(Wx::wxBLACK_PEN);
$dc->SetPen(wxBLACK_PEN);
@{$parent->{object_previews}} = ();
for my $obj_idx (0 .. $#{$print->objects}) {
next unless $parent->{thumbnails}[$obj_idx];
@ -834,7 +850,6 @@ sub _y {
}
package Slic3r::GUI::Plater::DropTarget;
use Wx::DND;
use base 'Wx::FileDropTarget';

View file

@ -5,8 +5,7 @@ use utf8;
use File::Basename qw(basename dirname);
use Slic3r::Geometry qw(X Y);
use Wx qw(:sizer :progressdialog wxOK wxICON_INFORMATION wxICON_WARNING wxICON_ERROR wxICON_QUESTION
wxOK wxCANCEL wxID_OK wxFD_OPEN wxFD_SAVE wxDEFAULT wxNORMAL);
use Wx qw(:dialog :filedialog :font :icon :id :misc :notebook :sizer);
use Wx::Event qw(EVT_BUTTON);
use base 'Wx::Panel';
@ -21,145 +20,24 @@ sub new {
my ($parent) = @_;
my $self = $class->SUPER::new($parent, -1);
no warnings 'qw';
my %panels = (
printer => {
title => 'Printer',
options => [qw(nozzle_diameter#0 bed_size print_center z_offset gcode_flavor use_relative_e_distances)],
},
filament => {
title => 'Filament',
options => [qw(filament_diameter#0 extrusion_multiplier#0 temperature#0 first_layer_temperature#0 bed_temperature first_layer_bed_temperature)],
},
print_speed => {
title => 'Print speed',
options => [qw(perimeter_speed small_perimeter_speed external_perimeter_speed infill_speed solid_infill_speed top_solid_infill_speed bridge_speed)],
},
speed => {
title => 'Other speed settings',
options => [qw(travel_speed first_layer_speed)],
},
accuracy => {
title => 'Accuracy',
options => [qw(layer_height first_layer_height infill_every_layers)],
},
print => {
title => 'Print settings',
options => [qw(perimeters solid_layers fill_density fill_angle fill_pattern solid_fill_pattern randomize_start)],
},
retract => {
title => 'Retraction',
options => [qw(retract_length retract_lift retract_speed retract_restart_extra retract_before_travel)],
},
cooling => {
title => 'Cooling',
options => [qw(cooling min_fan_speed max_fan_speed bridge_fan_speed fan_below_layer_time slowdown_below_layer_time min_print_speed disable_fan_first_layers fan_always_on)],
label_width => 450,
},
skirt => {
title => 'Skirt',
options => [qw(skirts skirt_distance skirt_height brim_width)],
},
gcode => {
title => 'G-code',
options => [qw(start_gcode end_gcode layer_gcode gcode_comments post_process)],
label_width => 260,
},
sequential_printing => {
title => 'Sequential printing',
options => [qw(complete_objects extruder_clearance_radius extruder_clearance_height)],
},
extrusion => {
title => 'Extrusion',
options => [qw(extrusion_width first_layer_extrusion_width perimeter_extrusion_width infill_extrusion_width support_material_extrusion_width bridge_flow_ratio)],
},
output => {
title => 'Output',
options => [qw(output_filename_format duplicate_distance)],
},
other => {
title => 'Other',
options => [ ($Slic3r::have_threads ? qw(threads) : ()), qw(extra_perimeters) ],
},
notes => {
title => 'Notes',
options => [qw(notes)],
},
support_material => {
title => 'Support material',
options => [qw(support_material support_material_threshold support_material_pattern support_material_spacing support_material_angle support_material_extruder)],
},
);
$self->{panels} = \%panels;
my $tabpanel = Wx::Notebook->new($self, -1, Wx::wxDefaultPosition, Wx::wxDefaultSize, &Wx::wxNB_TOP);
my $make_tab = sub {
my @cols = @_;
my $tab = Wx::Panel->new($tabpanel, -1);
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
foreach my $col (@cols) {
my $vertical_sizer = Wx::BoxSizer->new(wxVERTICAL);
for my $optgroup (@$col) {
next unless @{ $panels{$optgroup}{options} };
my $optpanel = Slic3r::GUI::OptionsGroup->new($tab, %{$panels{$optgroup}});
$vertical_sizer->Add($optpanel, 0, wxEXPAND | wxALL, 10);
}
$sizer->Add($vertical_sizer);
}
$tab->SetSizer($sizer);
return $tab;
my $tabpanel = Wx::Notebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP);
$tabpanel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($tabpanel), "Plater");
$self->{options_tabs} = {
print => Slic3r::GUI::Tab::Print->new ($tabpanel, sync_presets_with => $self->{plater}{preset_choosers}{print}),
filament => Slic3r::GUI::Tab::Filament->new ($tabpanel, sync_presets_with => $self->{plater}{preset_choosers}{filament}),
printer => Slic3r::GUI::Tab::Printer->new ($tabpanel, sync_presets_with => $self->{plater}{preset_choosers}{printer}),
};
my @tabs = (
$make_tab->([qw(accuracy skirt support_material)], [qw(print retract)]),
$make_tab->([qw(cooling notes)]),
$make_tab->([qw(printer filament)], [qw(print_speed speed)]),
$make_tab->([qw(gcode)]),
$make_tab->([qw(extrusion)], [qw(output other sequential_printing)]),
);
$tabpanel->AddPage(Slic3r::GUI::Plater->new($tabpanel), "Plater");
$tabpanel->AddPage($tabs[0], "Print Settings");
$tabpanel->AddPage($tabs[1], "Cooling");
$tabpanel->AddPage($tabs[2], "Printer and Filament");
$tabpanel->AddPage($tabs[3], "G-code");
$tabpanel->AddPage($tabs[4], "Advanced");
my $buttons_sizer;
{
$buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
my $slice_button = Wx::Button->new($self, -1, "Quick slice…");
$slice_button->SetDefault();
$buttons_sizer->Add($slice_button, 0, wxRIGHT, 20);
EVT_BUTTON($self, $slice_button, sub { $self->do_slice });
my $save_button = Wx::Button->new($self, -1, "Save config...");
$buttons_sizer->Add($save_button, 0, wxRIGHT, 5);
EVT_BUTTON($self, $save_button, sub { $self->save_config });
my $load_button = Wx::Button->new($self, -1, "Load config...");
$buttons_sizer->Add($load_button, 0, wxRIGHT, 5);
EVT_BUTTON($self, $load_button, sub { $self->load_config });
my $text = Wx::StaticText->new($self, -1, "Remember to check for updates at http://slic3r.org/\nVersion: $Slic3r::VERSION", Wx::wxDefaultPosition, Wx::wxDefaultSize, wxALIGN_RIGHT);
my $font = Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL);
$text->SetFont($font);
$buttons_sizer->Add($text, 1, wxEXPAND | wxALIGN_RIGHT);
}
$tabpanel->AddPage($self->{options_tabs}{print}, $self->{options_tabs}{print}->title);
$tabpanel->AddPage($self->{options_tabs}{filament}, $self->{options_tabs}{filament}->title);
$tabpanel->AddPage($self->{options_tabs}{printer}, $self->{options_tabs}{printer}->title);
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
$sizer->Add($buttons_sizer, 0, wxEXPAND | wxALL, 10);
$sizer->Add($tabpanel);
$sizer->Add($tabpanel, 1, wxEXPAND);
$sizer->SetSizeHints($self);
$self->SetSizer($sizer);
$self->Layout;
$_->() for @Slic3r::GUI::OptionsGroup::reload_callbacks;
return $self;
}
@ -182,7 +60,7 @@ sub do_slice {
$copies = $Slic3r::duplicate if $Slic3r::duplicate > 1;
if ($copies > 1) {
my $confirmation = Wx::MessageDialog->new($self, "Are you sure you want to slice $copies copies?",
'Confirm', wxICON_QUESTION | wxOK | wxCANCEL);
'Multiple Copies', wxICON_QUESTION | wxOK | wxCANCEL);
return unless $confirmation->ShowModal == wxID_OK;
}
@ -191,23 +69,23 @@ sub do_slice {
my $input_file;
if (!$params{reslice}) {
my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF):', $dir, "", $model_wildcard, wxFD_OPEN | &Wx::wxFD_FILE_MUST_EXIST);
my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF):', $dir, "", $model_wildcard, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if ($dialog->ShowModal != wxID_OK) {
$dialog->Destroy;
return;
}
$input_file = $dialog->GetPaths;
$dialog->Destroy;
$last_input_file = $input_file;
$last_input_file = $input_file unless $params{export_svg};
} else {
if (!defined $last_input_file) {
Wx::MessageDialog->new($self, "No previously sliced file",
'Confirm', wxICON_ERROR | wxOK)->ShowModal();
Wx::MessageDialog->new($self, "No previously sliced file.",
'Error', wxICON_ERROR | wxOK)->ShowModal();
return;
}
if (! -e $last_input_file) {
Wx::MessageDialog->new($self, "Cannot find previously sliced file!",
'Confirm', wxICON_ERROR | wxOK)->ShowModal();
Wx::MessageDialog->new($self, "Previously sliced file ($last_input_file) not found.",
'File Not Found', wxICON_ERROR | wxOK)->ShowModal();
return;
}
$input_file = $last_input_file;
@ -232,12 +110,13 @@ sub do_slice {
$dlg->Destroy;
return;
}
$output_file = $last_output_file = $dlg->GetPath;
$output_file = $dlg->GetPath;
$last_output_file = $output_file unless $params{export_svg};
$dlg->Destroy;
}
# show processbar dialog
$process_dialog = Wx::ProgressDialog->new('Slicing...', "Processing $input_file_basename...",
$process_dialog = Wx::ProgressDialog->new('Slicing…', "Processing $input_file_basename…",
100, $self, 0);
$process_dialog->Pulse;
@ -249,7 +128,7 @@ sub do_slice {
status_cb => sub {
my ($percent, $message) = @_;
if (&Wx::wxVERSION_STRING =~ / 2\.(8\.|9\.[2-9])/) {
$process_dialog->Update($percent, "$message...");
$process_dialog->Update($percent, "$message");
}
},
);
@ -264,13 +143,15 @@ sub do_slice {
undef $process_dialog;
my $message = "$input_file_basename was successfully sliced";
$message .= sprintf " in %d minutes and %.3f seconds",
int($print->processing_time/60),
$print->processing_time - int($print->processing_time/60)*60
if $print->processing_time;
if ($print->processing_time) {
$message .= ' in';
my $minutes = int($print->processing_time/60);
$message .= sprintf " %d minutes and", $minutes if $minutes;
$message .= sprintf " %.1f seconds", $print->processing_time - $minutes*60;
}
$message .= ".";
Slic3r::GUI::notify($message);
Wx::MessageDialog->new($self, $message, 'Done!',
&Wx::wxTheApp->notify($message);
Wx::MessageDialog->new($self, $message, 'Slicing Done!',
wxOK | wxICON_INFORMATION)->ShowModal;
};
Slic3r::GUI::catch_error($self, sub { $process_dialog->Destroy if $process_dialog });
@ -289,7 +170,7 @@ sub save_config {
my $dir = $last_config ? dirname($last_config) : $last_config_dir || $last_skein_dir || "";
my $filename = $last_config ? basename($last_config) : "config.ini";
my $dlg = Wx::FileDialog->new($self, 'Save configuration as:', $dir, $filename,
$ini_wildcard, wxFD_SAVE | &Wx::wxFD_OVERWRITE_PROMPT);
$ini_wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if ($dlg->ShowModal == wxID_OK) {
my $file = $dlg->GetPath;
$last_config_dir = dirname($file);
@ -301,22 +182,44 @@ sub save_config {
sub load_config {
my $self = shift;
my ($file) = @_;
my $dir = $last_config ? dirname($last_config) : $last_config_dir || $last_skein_dir || "";
my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', $dir, "config.ini",
$ini_wildcard, wxFD_OPEN | &Wx::wxFD_FILE_MUST_EXIST);
if ($dlg->ShowModal == wxID_OK) {
my ($file) = $dlg->GetPaths;
$last_config_dir = dirname($file);
$last_config = $file;
eval {
local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self);
Slic3r::Config->load($file);
};
Slic3r::GUI::catch_error($self);
$_->() for @Slic3r::GUI::OptionsGroup::reload_callbacks;
if (!$file) {
return unless $self->check_unsaved_changes;
my $dir = $last_config ? dirname($last_config) : $last_config_dir || $last_skein_dir || "";
my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', $dir, "config.ini",
$ini_wildcard, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
return unless $dlg->ShowModal == wxID_OK;
($file) = $dlg->GetPaths;
$dlg->Destroy;
}
$dlg->Destroy;
$last_config_dir = dirname($file);
$last_config = $file;
$_->external_config_loaded($file) for values %{$self->{options_tabs}};
}
sub config_wizard {
my $self = shift;
return unless $self->check_unsaved_changes;
if (Slic3r::GUI::ConfigWizard->new($self)->run) {
$_->() for values %Slic3r::GUI::OptionsGroup::reload_callbacks;
$_->set_dirty(1) for values %{$self->{options_tabs}};
}
}
sub check_unsaved_changes {
my $self = shift;
my @dirty = map $_->title, grep $_->is_dirty, values %{$self->{options_tabs}};
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;
}
1;

556
lib/Slic3r/GUI/Tab.pm Normal file
View file

@ -0,0 +1,556 @@
package Slic3r::GUI::Tab;
use strict;
use warnings;
use utf8;
use File::Basename qw(basename);
use List::Util qw(first);
use Wx qw(:bookctrl :dialog :icon :id :misc :sizer :treectrl :window);
use Wx::Event qw(EVT_BUTTON EVT_CHOICE EVT_TREE_SEL_CHANGED);
use base 'Wx::Panel';
sub new {
my $class = shift;
my ($parent, $title, %params) = @_;
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxBK_LEFT);
$self->{title} = $title;
$self->{sync_presets_with} = $params{sync_presets_with};
EVT_CHOICE($parent, $self->{sync_presets_with}, sub {
$self->{presets_choice}->SetSelection($self->{sync_presets_with}->GetSelection);
$self->on_select_preset;
});
# horizontal sizer
$self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL);
$self->{sizer}->SetSizeHints($self);
$self->SetSizer($self->{sizer});
# left vertical sizer
my $left_sizer = Wx::BoxSizer->new(wxVERTICAL);
$self->{sizer}->Add($left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 3);
my $left_col_width = 150;
# preset chooser
{
# choice menu
$self->{presets_choice} = Wx::Choice->new($self, -1, wxDefaultPosition, [$left_col_width, -1], []);
$self->{presets_choice}->SetFont($Slic3r::GUI::small_font);
# buttons
$self->{btn_save_preset} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new("$Slic3r::var/disk.png", wxBITMAP_TYPE_PNG));
$self->{btn_delete_preset} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new("$Slic3r::var/delete.png", wxBITMAP_TYPE_PNG));
$self->{btn_save_preset}->SetToolTipString("Save current " . lc($title));
$self->{btn_delete_preset}->SetToolTipString("Delete this preset");
$self->{btn_delete_preset}->Disable;
### These cause GTK warnings:
###my $box = Wx::StaticBox->new($self, -1, "Presets:", wxDefaultPosition, [$left_col_width, 50]);
###my $hsizer = Wx::StaticBoxSizer->new($box, wxHORIZONTAL);
my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
$left_sizer->Add($hsizer, 0, wxEXPAND | wxBOTTOM, 5);
$hsizer->Add($self->{presets_choice}, 1, wxRIGHT | wxALIGN_CENTER_VERTICAL, 3);
$hsizer->Add($self->{btn_save_preset}, 0, wxALIGN_CENTER_VERTICAL);
$hsizer->Add($self->{btn_delete_preset}, 0, wxALIGN_CENTER_VERTICAL);
}
# tree
$self->{treectrl} = Wx::TreeCtrl->new($self, -1, wxDefaultPosition, [$left_col_width, -1], wxTR_NO_BUTTONS | wxTR_HIDE_ROOT | wxTR_SINGLE | wxTR_NO_LINES | wxBORDER_SUNKEN);
$left_sizer->Add($self->{treectrl}, 1, wxEXPAND);
$self->{icons} = Wx::ImageList->new(16, 16, 1);
$self->{treectrl}->AssignImageList($self->{icons});
$self->{iconcount} = -1;
$self->{treectrl}->AddRoot("root");
$self->{pages} = {};
$self->{treectrl}->SetIndent(0);
EVT_TREE_SEL_CHANGED($parent, $self->{treectrl}, sub {
$_->Hide for values %{$self->{pages}};
$self->{sizer}->Remove(1);
my $page = $self->{pages}->{ $self->{treectrl}->GetItemText($self->{treectrl}->GetSelection) };
$page->Show;
$self->{sizer}->Add($page, 1, wxEXPAND | wxLEFT, 5);
$self->{sizer}->Layout;
});
EVT_CHOICE($parent, $self->{presets_choice}, sub {
$self->on_select_preset;
$self->sync_presets;
});
EVT_BUTTON($self, $self->{btn_save_preset}, sub {
my $preset = $self->current_preset;
my $default_name = $preset->{default} ? 'Untitled' : basename($preset->{name});
$default_name =~ s/\.ini$//i;
my $dlg = Slic3r::GUI::SavePresetWindow->new($self,
title => lc($title),
default => $default_name,
values => [ map { my $name = $_->{name}; $name =~ s/\.ini$//i; $name } @{$self->{presets}} ],
);
return unless $dlg->ShowModal == wxID_OK;
my $file = sprintf "$Slic3r::GUI::datadir/$self->{presets_group}/%s.ini", $dlg->get_name;
Slic3r::Config->save($file, $self->{presets_group});
$self->set_dirty(0);
$self->load_presets;
$self->{presets_choice}->SetSelection(first { basename($self->{presets}[$_]{file}) eq $dlg->get_name . ".ini" } 1 .. $#{$self->{presets}});
$self->on_select_preset;
$self->sync_presets;
});
EVT_BUTTON($self, $self->{btn_delete_preset}, sub {
my $i = $self->{presets_choice}->GetSelection;
return if $i == 0; # this shouldn't happen but let's trap it anyway
my $res = Wx::MessageDialog->new($self, "Are you sure you want to delete the selected preset?", 'Delete Preset', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION)->ShowModal;
return unless $res == wxID_YES;
if (-e $self->{presets}[$i-1]{file}) {
unlink $self->{presets}[$i-1]{file};
}
splice @{$self->{presets}}, $i-1, 1;
$self->{presets_choice}->Delete($i);
$self->{presets_choice}->SetSelection(0);
$self->on_select_preset;
$self->sync_presets;
});
return $self;
}
sub current_preset {
my $self = shift;
return $self->{presets}[ $self->{presets_choice}->GetSelection ];
}
sub on_select_preset {
my $self = shift;
if (defined $self->{dirty}) {
# TODO: prompt user?
$self->set_dirty(0);
}
my $preset = $self->current_preset;
if ($preset->{default}) {
# default settings: disable the delete button
Slic3r::Config->load_hash($Slic3r::Defaults, $self->{presets_group}, 1);
$self->{btn_delete_preset}->Disable;
} else {
if (!-e $preset->{file}) {
Slic3r::GUI::show_error($self, "The selected preset does not exist anymore ($preset->{file}).");
return;
}
eval {
local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self);
Slic3r::Config->load($preset->{file}, $self->{presets_group});
};
Slic3r::GUI::catch_error($self);
$preset->{external}
? $self->{btn_delete_preset}->Disable
: $self->{btn_delete_preset}->Enable;
}
$_->() for @Slic3r::GUI::OptionsGroup::reload_callbacks{@{$Slic3r::Config::Groups{$self->{presets_group}}}};
$self->set_dirty(0);
$Slic3r::Settings->{presets}{$self->{presets_group}} = $preset->{file} ? basename($preset->{file}) : '';
Slic3r::Config->save_settings("$Slic3r::GUI::datadir/slic3r.ini");
}
sub add_options_page {
my $self = shift;
my $title = shift;
my $icon = (ref $_[1]) ? undef : shift;
my $page = Slic3r::GUI::Tab::Page->new($self, @_, on_change => sub {
$self->set_dirty(1);
$self->sync_presets;
});
my $bitmap = $icon
? Wx::Bitmap->new("$Slic3r::var/$icon", wxBITMAP_TYPE_PNG)
: undef;
if ($bitmap) {
$self->{icons}->Add($bitmap);
$self->{iconcount}++;
}
$page->Hide;
my $itemId = $self->{treectrl}->AppendItem($self->{treectrl}->GetRootItem, $title, $self->{iconcount});
$self->{pages}{$title} = $page;
if (keys %{$self->{pages}} == 1) {
$self->{treectrl}->SelectItem($itemId);
}
}
sub set_dirty {
my $self = shift;
my ($dirty) = @_;
my $i = $self->{dirty} // $self->{presets_choice}->GetSelection; #/
my $text = $self->{presets_choice}->GetString($i);
if ($dirty) {
$self->{dirty} = $i;
if ($text !~ / \(modified\)$/) {
$self->{presets_choice}->SetString($i, "$text (modified)");
$self->{presets_choice}->SetSelection($i); # wxMSW needs this after every SetString()
}
} else {
$self->{dirty} = undef;
$text =~ s/ \(modified\)$//;
$self->{presets_choice}->SetString($i, $text);
$self->{presets_choice}->SetSelection($i); # wxMSW needs this after every SetString()
}
$self->sync_presets;
}
sub is_dirty {
my $self = shift;
return (defined $self->{dirty});
}
sub title {
my $self = shift;
return $self->{title};
}
sub load_presets {
my $self = shift;
my ($group) = @_;
$self->{presets_group} ||= $group;
$self->{presets} = [{
default => 1,
name => '- default -',
}];
opendir my $dh, "$Slic3r::GUI::datadir/$self->{presets_group}" or die "Failed to read directory $Slic3r::GUI::datadir/$self->{presets_group} (errno: $!)\n";
foreach my $file (sort grep /\.ini$/i, readdir $dh) {
my $name = basename($file);
$name =~ s/\.ini$//;
push @{$self->{presets}}, {
file => "$Slic3r::GUI::datadir/$self->{presets_group}/$file",
name => $name,
};
}
closedir $dh;
$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::Settings->{presets}{$self->{presets_group}} || '') } 1 .. $#{$self->{presets}};
$self->{presets_choice}->SetSelection($i || 0);
$self->on_select_preset;
}
$self->sync_presets;
}
sub external_config_loaded {
my $self = shift;
my ($file) = @_;
# look for the loaded config among the existing menu items
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}}, {
file => $file,
name => $preset_name,
external => 1,
};
$self->{presets_choice}->Append($preset_name);
$i = $#{$self->{presets}};
}
$self->{presets_choice}->SetSelection($i);
$self->on_select_preset;
$self->sync_presets;
}
sub sync_presets {
my $self = shift;
return unless $self->{sync_presets_with};
$self->{sync_presets_with}->Clear;
foreach my $item ($self->{presets_choice}->GetStrings) {
$self->{sync_presets_with}->Append($item);
}
$self->{sync_presets_with}->SetSelection($self->{presets_choice}->GetSelection);
}
package Slic3r::GUI::Tab::Print;
use base 'Slic3r::GUI::Tab';
sub new {
my $class = shift;
my ($parent, %params) = @_;
my $self = $class->SUPER::new($parent, 'Print Settings', %params);
$self->add_options_page('Layers and perimeters', 'layers.png', optgroups => [
{
title => 'Layer height',
options => [qw(layer_height first_layer_height)],
},
{
title => 'Vertical shells',
options => [qw(perimeters randomize_start extra_perimeters)],
},
{
title => 'Horizontal shells',
options => [qw(solid_layers)],
},
]);
$self->add_options_page('Infill', 'shading.png', optgroups => [
{
title => 'Infill',
options => [qw(fill_density fill_angle fill_pattern solid_fill_pattern infill_every_layers)],
},
]);
$self->add_options_page('Speed', 'time.png', optgroups => [
{
title => 'Speed for print moves',
options => [qw(perimeter_speed small_perimeter_speed infill_speed solid_infill_speed top_solid_infill_speed bridge_speed)],
},
{
title => 'Speed for non-print moves',
options => [qw(travel_speed)],
},
{
title => 'Modifiers',
options => [qw(first_layer_speed)],
},
]);
$self->add_options_page('Skirt and brim', 'box.png', optgroups => [
{
title => 'Skirt',
options => [qw(skirts skirt_distance skirt_height)],
},
{
title => 'Brim',
options => [qw(brim_width)],
},
]);
$self->add_options_page('Support material', 'building.png', optgroups => [
{
title => 'Support material',
options => [qw(support_material support_material_threshold support_material_pattern support_material_spacing support_material_angle)],
},
]);
$self->add_options_page('Notes', 'note.png', optgroups => [
{
title => 'Notes',
no_labels => 1,
options => [qw(notes)],
},
]);
$self->add_options_page('Output options', 'page_white_go.png', optgroups => [
{
title => 'Sequential printing',
options => [qw(complete_objects extruder_clearance_radius extruder_clearance_height)],
},
{
title => 'Output file',
options => [qw(gcode_comments output_filename_format)],
},
{
title => 'Post-processing scripts',
no_labels => 1,
options => [qw(post_process)],
},
]);
$self->add_options_page('Advanced', 'wrench.png', optgroups => [
{
title => 'Extrusion width',
label_width => 180,
options => [qw(extrusion_width first_layer_extrusion_width perimeter_extrusion_width infill_extrusion_width)],
},
{
title => 'Flow',
options => [qw(bridge_flow_ratio)],
},
{
title => 'Other',
options => [qw(duplicate_distance), ($Slic3r::have_threads ? qw(threads) : ())],
},
]);
$self->load_presets('print');
return $self;
}
package Slic3r::GUI::Tab::Filament;
use base 'Slic3r::GUI::Tab';
sub new {
my $class = shift;
my ($parent, %params) = @_;
my $self = $class->SUPER::new($parent, 'Filament Settings', %params);
$self->add_options_page('Filament', 'spool.png', optgroups => [
{
title => 'Filament',
options => ['filament_diameter#0', 'extrusion_multiplier#0'],
},
{
title => 'Temperature',
options => ['temperature#0', 'first_layer_temperature#0', qw(bed_temperature first_layer_bed_temperature)],
},
]);
$self->add_options_page('Cooling', 'hourglass.png', optgroups => [
{
title => 'Enable',
options => [qw(cooling)],
},
{
title => 'Fan settings',
options => [qw(min_fan_speed max_fan_speed bridge_fan_speed disable_fan_first_layers fan_always_on)],
},
{
title => 'Cooling thresholds',
label_width => 250,
options => [qw(fan_below_layer_time slowdown_below_layer_time min_print_speed)],
},
]);
$self->load_presets('filament');
return $self;
}
package Slic3r::GUI::Tab::Printer;
use base 'Slic3r::GUI::Tab';
sub new {
my $class = shift;
my ($parent, %params) = @_;
my $self = $class->SUPER::new($parent, 'Printer Settings', %params);
$self->add_options_page('General', 'printer_empty.png', optgroups => [
{
title => 'Size and coordinates',
options => [qw(bed_size print_center z_offset)],
},
{
title => 'Firmware',
options => [qw(gcode_flavor use_relative_e_distances)],
},
]);
$self->add_options_page('Extruder 1', 'funnel.png', optgroups => [
{
title => 'Size',
options => ['nozzle_diameter#0'],
},
{
title => 'Retraction',
options => [qw(retract_length retract_lift retract_speed retract_restart_extra retract_before_travel)],
},
]);
$self->add_options_page('Custom G-code', 'cog.png', optgroups => [
{
title => 'Start G-code',
no_labels => 1,
options => [qw(start_gcode)],
},
{
title => 'End G-code',
no_labels => 1,
options => [qw(end_gcode)],
},
{
title => 'Layer change G-code',
no_labels => 1,
options => [qw(layer_gcode)],
},
]);
$self->load_presets('printer');
return $self;
}
package Slic3r::GUI::Tab::Page;
use Wx qw(:sizer);
use base 'Wx::ScrolledWindow';
sub new {
my $class = shift;
my ($parent, %params) = @_;
my $self = $class->SUPER::new($parent, -1);
$self->SetScrollbars(1, 1, 1, 1);
$self->{vsizer} = Wx::BoxSizer->new(wxVERTICAL);
$self->SetSizer($self->{vsizer});
if ($params{optgroups}) {
$self->append_optgroup(%$_, on_change => $params{on_change}) for @{$params{optgroups}};
}
return $self;
}
sub append_optgroup {
my $self = shift;
my $optgroup = Slic3r::GUI::OptionsGroup->new($self, label_width => 200, @_);
$self->{vsizer}->Add($optgroup, 0, wxEXPAND | wxALL, 5);
}
package Slic3r::GUI::SavePresetWindow;
use Wx qw(:combobox :dialog :id :misc :sizer);
use Wx::Event qw(EVT_BUTTON EVT_TEXT_ENTER);
use base 'Wx::Dialog';
sub new {
my $class = shift;
my ($parent, %params) = @_;
my $self = $class->SUPER::new($parent, -1, "Save preset", wxDefaultPosition, wxDefaultSize);
my $text = Wx::StaticText->new($self, -1, "Save " . lc($params{title}) . " as:", wxDefaultPosition, wxDefaultSize);
$self->{combo} = Wx::ComboBox->new($self, -1, $params{default}, wxDefaultPosition, wxDefaultSize, $params{values},
wxTE_PROCESS_ENTER);
my $buttons = $self->CreateStdDialogButtonSizer(wxOK | wxCANCEL);
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
$sizer->Add($text, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10);
$sizer->Add($self->{combo}, 0, wxEXPAND | wxLEFT | wxRIGHT, 10);
$sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
EVT_BUTTON($self, wxID_OK, \&accept);
EVT_TEXT_ENTER($self, $self->{combo}, \&accept);
$self->SetSizer($sizer);
$sizer->SetSizeHints($self);
return $self;
}
sub accept {
my ($self, $event) = @_;
if (($self->{chosen_name} = $self->{combo}->GetValue)) {
if ($self->{chosen_name} =~ /^[a-z0-9 _-]+$/i) {
$self->EndModal(wxID_OK);
} else {
Slic3r::GUI::show_error($self, "The supplied name is not valid.");
}
}
}
sub get_name {
my $self = shift;
return $self->{chosen_name};
}
1;