From f2f9178e0708142722104884ad9d7fdf8440b131 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Oct 2011 11:55:32 +0200 Subject: [PATCH] GUI --- Build.PL | 4 +- README.markdown | 8 +- lib/Slic3r.pm | 2 + lib/Slic3r/Config.pm | 72 ++++++++++ lib/Slic3r/GUI.pm | 44 ++++++ lib/Slic3r/GUI/OptionsGroup.pm | 50 +++++++ lib/Slic3r/GUI/SkeinPanel.pm | 255 +++++++++++++++++++++++++++++++++ lib/Slic3r/Skein.pm | 34 +++++ slic3r.pl | 92 ++---------- 9 files changed, 478 insertions(+), 83 deletions(-) create mode 100644 lib/Slic3r/Config.pm create mode 100644 lib/Slic3r/GUI.pm create mode 100644 lib/Slic3r/GUI/OptionsGroup.pm create mode 100644 lib/Slic3r/GUI/SkeinPanel.pm create mode 100644 lib/Slic3r/Skein.pm diff --git a/Build.PL b/Build.PL index 42337b8db..5733247fb 100644 --- a/Build.PL +++ b/Build.PL @@ -8,11 +8,13 @@ my $build = Module::Build->new( license => 'perl', requires => { 'CAD::Format::STL' => '0', + 'File::Basename' => '0', 'Getopt::Long' => '0', - 'Math::Clipper' => '1.00', + 'Math::Clipper' => '1.01', 'Math::Geometry::Planar' => '0', 'Moo' => '0', 'Time::HiRes' => '0', + 'XXX' => '0', }, build_requires => { 'Test::More' => '0.10', diff --git a/README.markdown b/README.markdown index 6e656feda..2a2617f20 100644 --- a/README.markdown +++ b/README.markdown @@ -63,8 +63,12 @@ Download the package, open a terminal and cd to its directory. Then type: perl Build.PL -This will install any required dependency. If you want to install slic3r.pl -in your system path, type this as root: +This will install any required dependency. If you want the GUI, you should +also install Wx using the following command (as root): + + cpan Wx + +If you want to install slic3r.pl in your system path, type this as root: ./Build install diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 6098f95ea..1273c2f52 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -8,6 +8,7 @@ sub debugf { printf @_ if $debug; } +use Slic3r::Config; use Slic3r::Extruder; use Slic3r::ExtrusionLoop; use Slic3r::ExtrusionPath; @@ -21,6 +22,7 @@ use Slic3r::Point; use Slic3r::Polyline; use Slic3r::Polyline::Closed; use Slic3r::Print; +use Slic3r::Skein; use Slic3r::STL; use Slic3r::Surface; use Slic3r::Surface::Collection; diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm new file mode 100644 index 000000000..d4b40c0eb --- /dev/null +++ b/lib/Slic3r/Config.pm @@ -0,0 +1,72 @@ +package Slic3r::Config; +use strict; +use warnings; + +use constant PI => 4 * atan2(1, 1); + +sub validate { + my $class = shift; + + # --layer-height + die "Invalid value for --layer-height\n" + if $Slic3r::layer_height <= 0; + die "--layer-height must be a multiple of print resolution\n" + if $Slic3r::layer_height / $Slic3r::resolution % 1 != 0; + + # --filament-diameter + die "Invalid value for --filament-diameter\n" + if $Slic3r::filament_diameter < 1; + + # --nozzle-diameter + die "Invalid value for --nozzle-diameter\n" + if $Slic3r::nozzle_diameter < 0; + die "--layer-height can't be greater than --nozzle-diameter\n" + if $Slic3r::layer_height > $Slic3r::nozzle_diameter; + $Slic3r::flow_width = ($Slic3r::nozzle_diameter**2) + * $Slic3r::thickness_ratio * PI / (4 * $Slic3r::layer_height); + + my $max_flow_width = $Slic3r::layer_height + $Slic3r::nozzle_diameter; + if ($Slic3r::flow_width > $max_flow_width) { + $Slic3r::thickness_ratio = $max_flow_width / $Slic3r::flow_width; + $Slic3r::flow_width = $max_flow_width; + } + + Slic3r::debugf "Flow width = $Slic3r::flow_width\n"; + + # --perimeters + die "Invalid value for --perimeters\n" + if $Slic3r::perimeter_offsets < 1; + + # --solid-layers + die "Invalid value for --solid-layers\n" + if $Slic3r::solid_layers < 1; + + # --print-center + die "Invalid value for --print-center\n" + if !ref $Slic3r::print_center + && (!$Slic3r::print_center || $Slic3r::print_center !~ /^\d+,\d+$/); + $Slic3r::print_center = [ split /,/, $Slic3r::print_center ] + if !ref $Slic3r::print_center; + + # --fill-density + die "Invalid value for --fill-density\n" + if $Slic3r::fill_density < 0 || $Slic3r::fill_density > 1; + + # --scale + die "Invalid value for --scale\n" + if $Slic3r::scale <= 0; + + # --multiply-x + die "Invalid value for --multiply-x\n" + if $Slic3r::multiply_x < 1; + + # --multiply-y + die "Invalid value for --multiply-y\n" + if $Slic3r::multiply_y < 1; + + # --multiply-distance + die "Invalid value for --multiply-distance\n" + if $Slic3r::multiply_distance < 1; +} + +1; diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm new file mode 100644 index 000000000..aa3f4396a --- /dev/null +++ b/lib/Slic3r/GUI.pm @@ -0,0 +1,44 @@ +package Slic3r::GUI; +use strict; +use warnings; + +use Slic3r::GUI::OptionsGroup; +use Slic3r::GUI::SkeinPanel; + +use Wx qw(:sizer :frame wxID_EXIT wxID_ABOUT); +use Wx::Event qw(EVT_MENU); +use base 'Wx::App'; + +sub OnInit { + my $self = shift; + + #$self->SetIcon(Wx::Icon->new("path/to/my/icon.gif", wxBITMAP_TYPE_GIF) ); + + my $frame = Wx::Frame->new( undef, -1, 'Slic3r', [-1, -1], Wx::wxDefaultSize, + wxDEFAULT_FRAME_STYLE ^ (wxRESIZE_BORDER | wxMAXIMIZE_BOX) ); + + # menubar + my $menubar = Wx::MenuBar->new; + $frame->SetMenuBar($menubar); + EVT_MENU($frame, wxID_EXIT, sub {$_[0]->Close(1)}); + EVT_MENU($frame, wxID_ABOUT, \&About); + + my $panel = Slic3r::GUI::SkeinPanel->new($frame); + my $box = Wx::BoxSizer->new(wxVERTICAL); + $box->Add($panel, 0, wxALL, 20); + + $frame->SetSizerAndFit($box); + $frame->Show; +} + +sub About { + my $frame = shift; + + my $info = Wx::AboutDialogInfo->new; + $info->SetName('Slic3r'); + $info->AddDeveloper('Alessandro Ranellucci'); + + Wx::AboutBox($info); +} + +1; diff --git a/lib/Slic3r/GUI/OptionsGroup.pm b/lib/Slic3r/GUI/OptionsGroup.pm new file mode 100644 index 000000000..aeb258182 --- /dev/null +++ b/lib/Slic3r/GUI/OptionsGroup.pm @@ -0,0 +1,50 @@ +package Slic3r::GUI::OptionsGroup; +use strict; +use warnings; + +use Wx qw(:sizer); +use Wx::Event qw(EVT_TEXT EVT_CHECKBOX); +use base 'Wx::StaticBoxSizer'; + +sub new { + my $class = shift; + my ($parent, %p) = @_; + + 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); + + foreach my $opt (@{$p{options}}) { + my $label = Wx::StaticText->new($parent, -1, "$opt->{label}:", Wx::wxDefaultPosition, [180,-1]); + my $field; + if ($opt->{type} =~ /^(i|f)$/) { + $field = Wx::TextCtrl->new($parent, -1, ${$opt->{value}}); + EVT_TEXT($parent, $field, sub { ${$opt->{value}} = $field->GetValue }); + } elsif ($opt->{type} eq 'bool') { + $field = Wx::CheckBox->new($parent, -1, ""); + $field->SetValue(${$opt->{value}}); + EVT_TEXT($parent, $field, sub { ${$opt->{value}} = $field->GetValue }); + } elsif ($opt->{type} eq 'point') { + $field = Wx::BoxSizer->new(wxHORIZONTAL); + my $field_size = Wx::Size->new(40, -1); + $field->Add($_) for ( + Wx::StaticText->new($parent, -1, "x:"), + my $x_field = Wx::TextCtrl->new($parent, -1, ${$opt->{value}}->[0], Wx::wxDefaultPosition, $field_size), + Wx::StaticText->new($parent, -1, " y:"), + my $y_field = Wx::TextCtrl->new($parent, -1, ${$opt->{value}}->[1], Wx::wxDefaultPosition, $field_size), + ); + EVT_TEXT($parent, $x_field, sub { ${$opt->{value}}->[0] = $x_field->GetValue }); + EVT_TEXT($parent, $y_field, sub { ${$opt->{value}}->[1] = $y_field->GetValue }); + } else { + die "Unsupported option type: " . $opt->{type}; + } + $grid_sizer->Add($_) for $label, $field; + } + + $self->Add($grid_sizer, 0, wxEXPAND); + + return $self; +} + +1; diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm new file mode 100644 index 000000000..ebb6f08d0 --- /dev/null +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -0,0 +1,255 @@ +package Slic3r::GUI::SkeinPanel; +use strict; +use warnings; + +use File::Basename qw(basename); +use Wx qw(:sizer :progressdialog wxOK wxICON_INFORMATION wxICON_ERROR wxID_OK wxFD_OPEN); +use Wx::Event qw(EVT_BUTTON); +use base 'Wx::Panel'; + +sub new { + my $class = shift; + my ($parent) = @_; + my $self = $class->SUPER::new($parent, -1); + + my %panels = ( + printer => Slic3r::GUI::OptionsGroup->new($self, + title => 'Printer', + options => [ + { + label => 'Nozzle diameter', + value => \$Slic3r::nozzle_diameter, + type => 'f', + }, + { + label => 'Print center', + value => \$Slic3r::print_center, + type => 'point', + }, + { + label => 'Use relative E distances', + value => \$Slic3r::use_relative_e_distances, + type => 'bool', + }, + { + label => 'Z offset', + value => \$Slic3r::z_offset, + type => 'f', + }, + ], + ), + + filament => Slic3r::GUI::OptionsGroup->new($self, + title => 'Filament', + options => [ + { + label => 'Diameter (mm)', + value => \$Slic3r::filament_diameter, + type => 'f', + }, + { + label => 'Packing density (mm)', + value => \$Slic3r::filament_packing_density, + type => 'f', + }, + ], + ), + + speed => Slic3r::GUI::OptionsGroup->new($self, + title => 'Speed', + options => [ + { + label => 'Print feed rate (mm/s)', + value => \$Slic3r::print_feed_rate, + type => 'f', + }, + { + label => 'Travel feed rate (mm/s)', + value => \$Slic3r::travel_feed_rate, + type => 'f', + }, + { + label => 'Perimeter feed rate (mm/s)', + value => \$Slic3r::perimeter_feed_rate, + type => 'f', + }, + { + label => 'Bottom layer ratio', + value => \$Slic3r::bottom_layer_speed_ratio, + type => 'f', + }, + ], + ), + + accuracy => Slic3r::GUI::OptionsGroup->new($self, + title => 'Accuracy', + options => [ + { + label => 'Layer height (mm)', + value => \$Slic3r::layer_height, + type => 'f', + }, + ], + ), + + print => Slic3r::GUI::OptionsGroup->new($self, + title => 'Print settings', + options => [ + { + label => 'Perimeters', + value => \$Slic3r::perimeter_offsets, + type => 'i', + }, + { + label => 'Solid layers', + value => \$Slic3r::solid_layers, + type => 'i', + }, + { + label => 'Fill density', + value => \$Slic3r::fill_density, + type => 'f', + }, + { + label => 'Fill angle (°)', + value => \$Slic3r::fill_angle, + type => 'i', + }, + { + label => 'Temperature (°C)', + value => \$Slic3r::temperature, + type => 'i', + }, + ], + ), + + retract => Slic3r::GUI::OptionsGroup->new($self, + title => 'Retraction', + options => [ + { + label => 'Length (mm)', + value => \$Slic3r::retract_length, + type => 'f', + }, + { + label => 'Speed (mm/s)', + value => \$Slic3r::retract_speed, + type => 'i', + }, + { + label => 'Extra length on restart (mm)', + value => \$Slic3r::retract_restart_extra, + type => 'f', + }, + { + label => 'Minimum travel after retraction (mm)', + value => \$Slic3r::retract_before_travel, + type => 'f', + }, + ], + ), + + skirt => Slic3r::GUI::OptionsGroup->new($self, + title => 'Skirt', + options => [ + { + label => 'Loops', + value => \$Slic3r::skirts, + type => 'i', + }, + { + label => 'Distance from object (mm)', + value => \$Slic3r::skirt_distance, + type => 'i', + }, + ], + ), + + transform => Slic3r::GUI::OptionsGroup->new($self, + title => 'Transform', + options => [ + { + label => 'Scale', + value => \$Slic3r::scale, + type => 'f', + }, + { + label => 'Rotate (°)', + value => \$Slic3r::rotate, + type => 'i', + }, + { + label => 'Multiply along X', + value => \$Slic3r::multiply_x, + type => 'i', + }, + { + label => 'Multiply along Y', + value => \$Slic3r::multiply_y, + type => 'i', + }, + { + label => 'Multiply distance', + value => \$Slic3r::multiply_distance, + type => 'i', + }, + ], + ), + ); + + $panels{slice} = Wx::BoxSizer->new(wxVERTICAL); + $panels{slice}->Add(-1, 20); # empty space before button + my $slice_button = Wx::Button->new($self, -1, "Slice..."); + $panels{slice}->Add($slice_button, 0, wxALIGN_CENTER); + EVT_BUTTON($self, $slice_button, \&do_slice); + + my @cols = ( + [qw(printer filament speed transform)], [qw(accuracy print retract skirt slice)], + ); + + my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); + foreach my $col (@cols) { + my $vertical_sizer = Wx::BoxSizer->new(wxVERTICAL); + $vertical_sizer->Add($panels{$_}, 0, wxEXPAND | wxRIGHT, 10) for @$col; + $sizer->Add($vertical_sizer); + } + + $sizer->SetSizeHints($self); + $self->SetSizer($sizer); + + return $self; +} + +sub do_slice { + my $self = shift; + + eval { + # validate configuration + Slic3r::Config->validate; + + # select input file + my $dialog = Wx::FileDialog->new($self, 'Choose a STL file to slice:', "", "", "*.stl", wxFD_OPEN); + return unless $dialog->ShowModal == wxID_OK; + my ($input_file) = $dialog->GetPaths; + my $input_file_basename = basename($input_file); + + # show processbar dialog + my $process_dialog = Wx::ProgressDialog->new('Slicing...', "Processing $input_file_basename...", + 100, $self, wxPD_APP_MODAL); + $process_dialog->Pulse; + my $skein = Slic3r::Skein->new( + input_file => $input_file, + ); + $skein->go; + $process_dialog->Destroy; + + Wx::MessageDialog->new($self, "$input_file_basename was successfully sliced.", 'Done!', + wxOK | wxICON_INFORMATION)->ShowModal; + }; + + if (my $err = $@) { + Wx::MessageDialog->new($self, $err, 'Error', wxOK | wxICON_ERROR)->ShowModal; + } +} + +1; diff --git a/lib/Slic3r/Skein.pm b/lib/Slic3r/Skein.pm new file mode 100644 index 000000000..eedc570d1 --- /dev/null +++ b/lib/Slic3r/Skein.pm @@ -0,0 +1,34 @@ +package Slic3r::Skein; +use Moo; + +use Time::HiRes qw(gettimeofday tv_interval); + +has 'input_file' => (is => 'ro', required => 1); +has 'output_file' => (is => 'rw', required => 0); + +sub go { + my $self = shift; + + die "Input file must have .stl extension\n" + if $self->input_file !~ /\.stl$/i; + + my $t0 = [gettimeofday]; + my $print = Slic3r::Print->new_from_stl($self->input_file); + $print->extrude_perimeters; + $print->remove_small_features; + $print->extrude_fills; + + + if (!$self->output_file) { + my $output_file = $self->input_file; + $output_file =~ s/\.stl$/.gcode/i; + $self->output_file($output_file); + } + $print->export_gcode($self->output_file); + + my $processing_time = tv_interval($t0); + printf "Done. Process took %d minutes and %.3f seconds\n", + int($processing_time/60), $processing_time - int($processing_time/60)*60; +} + +1; diff --git a/slic3r.pl b/slic3r.pl index 0e6e15388..ff6a0358b 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -10,11 +10,8 @@ BEGIN { use Getopt::Long; use Slic3r; -use Time::HiRes qw(gettimeofday tv_interval); use XXX; -use constant PI => 4 * atan2(1, 1); - my %opt; GetOptions( 'help' => sub { usage() }, @@ -59,7 +56,7 @@ GetOptions( 'skirt-distance=i' => \$Slic3r::skirt_distance, # transform options - 'scale=i' => \$Slic3r::scale, + 'scale=f' => \$Slic3r::scale, 'rotate=i' => \$Slic3r::rotate, 'multiply-x=i' => \$Slic3r::multiply_x, 'multiply-y=i' => \$Slic3r::multiply_y, @@ -67,89 +64,24 @@ GetOptions( ); # validate configuration -{ - # --layer-height - die "Invalid value for --layer-height\n" - if $Slic3r::layer_height < 0; - die "--layer-height must be a multiple of print resolution\n" - if $Slic3r::layer_height / $Slic3r::resolution % 1 != 0; - - # --filament-diameter - die "Invalid value for --filament-diameter\n" - if $Slic3r::filament_diameter < 1; - - # --nozzle-diameter - die "Invalid value for --nozzle-diameter\n" - if $Slic3r::nozzle_diameter < 0; - die "--layer-height can't be greater than --nozzle-diameter\n" - if $Slic3r::layer_height > $Slic3r::nozzle_diameter; - $Slic3r::flow_width = ($Slic3r::nozzle_diameter**2) - * $Slic3r::thickness_ratio * PI / (4 * $Slic3r::layer_height); - - my $max_flow_width = $Slic3r::layer_height + $Slic3r::nozzle_diameter; - if ($Slic3r::flow_width > $max_flow_width) { - $Slic3r::thickness_ratio = $max_flow_width / $Slic3r::flow_width; - $Slic3r::flow_width = $max_flow_width; - } - - Slic3r::debugf "Flow width = $Slic3r::flow_width\n"; - - # --perimeters - die "Invalid value for --perimeters\n" - if $Slic3r::perimeter_offsets < 1; - - # --solid-layers - die "Invalid value for --solid-layers\n" - if $Slic3r::solid_layers < 1; - - # --print-center - die "Invalid value for --print-center\n" - if !ref $Slic3r::print_center - && (!$Slic3r::print_center || $Slic3r::print_center !~ /^\d+,\d+$/); - $Slic3r::print_center = [ split /,/, $Slic3r::print_center ] - if !ref $Slic3r::print_center; - - # --fill-density - die "Invalid value for --fill-density\n" - if $Slic3r::fill_density < 0 || $Slic3r::fill_density > 1; - - # --scale - die "Invalid value for --scale\n" - if $Slic3r::scale <= 0; - - # --multiply-x - die "Invalid value for --multiply-x\n" - if $Slic3r::multiply_x < 1; - - # --multiply-y - die "Invalid value for --multiply-y\n" - if $Slic3r::multiply_y < 1; - - # --multiply-distance - die "Invalid value for --multiply-distance\n" - if $Slic3r::multiply_distance < 1; +Slic3r::Config->validate; + +# start GUI +if (!@ARGV && eval "require Slic3r::GUI; 1") { + Slic3r::GUI->new->MainLoop; + exit; } my $action = 'skein'; if ($action eq 'skein') { my $input_file = $ARGV[0] or usage(1); - die "Input file must have .stl extension\n" - if $input_file !~ /\.stl$/i; - my $t0 = [gettimeofday]; - my $print = Slic3r::Print->new_from_stl($input_file); - $print->extrude_perimeters; - $print->remove_small_features; - $print->extrude_fills; - - my $output_file = $input_file; - $output_file =~ s/\.stl$/.gcode/i; - $print->export_gcode($opt{output} || $output_file); - - my $processing_time = tv_interval($t0); - printf "Done. Process took %d minutes and %.3f seconds\n", - int($processing_time/60), $processing_time - int($processing_time/60)*60; + my $skein = Slic3r::Skein->new( + input_file => $input_file, + output_file => $opt{output}, + ); + $skein->go; } sub usage {