diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 887d35320..380120a1d 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -21,6 +21,7 @@ use constant MI_QUICK_SLICE => &Wx::NewId; use constant MI_REPEAT_QUICK => &Wx::NewId; use constant MI_QUICK_SAVE_AS => &Wx::NewId; use constant MI_SLICE_SVG => &Wx::NewId; +use constant MI_COMBINE_STLS => &Wx::NewId; use constant MI_PLATER_EXPORT_GCODE => &Wx::NewId; use constant MI_PLATER_EXPORT_STL => &Wx::NewId; @@ -90,6 +91,8 @@ sub OnInit { $fileMenu->AppendSeparator(); $fileMenu->Append(MI_SLICE_SVG, "Slice to SV&G…\tCtrl+G", 'Slice file to SVG'); $fileMenu->AppendSeparator(); + $fileMenu->Append(MI_COMBINE_STLS, "Combine multi-material STL files…", 'Combine multiple STL files into a single multi-material AMF file'); + $fileMenu->AppendSeparator(); $fileMenu->Append(wxID_EXIT, "&Quit", 'Quit Slic3r'); EVT_MENU($frame, MI_LOAD_CONF, sub { $self->{skeinpanel}->load_config_file }); EVT_MENU($frame, MI_EXPORT_CONF, sub { $self->{skeinpanel}->export_config }); @@ -99,6 +102,7 @@ sub OnInit { EVT_MENU($frame, MI_QUICK_SAVE_AS, sub { $self->{skeinpanel}->do_slice(save_as => 1); $repeat->Enable(defined $Slic3r::GUI::SkeinPanel::last_input_file) }); EVT_MENU($frame, MI_SLICE_SVG, sub { $self->{skeinpanel}->do_slice(save_as => 1, export_svg => 1) }); + EVT_MENU($frame, MI_COMBINE_STLS, sub { $self->{skeinpanel}->combine_stls }); EVT_MENU($frame, wxID_EXIT, sub {$_[0]->Close(0)}); } diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index e68b72778..1c9f910a2 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -502,7 +502,7 @@ sub export_gcode { { $self->{output_file} = $print->expanded_output_filepath($self->{output_file}, $self->{objects}[0]->input_file); my $dlg = Wx::FileDialog->new($self, 'Save G-code file as:', dirname($self->{output_file}), - basename($self->{output_file}), $Slic3r::GUI::SkeinPanel::gcode_wildcard, wxFD_SAVE); + basename($self->{output_file}), &Slic3r::GUI::SkeinPanel::FILE_WILDCARDS->{gcode}, wxFD_SAVE); if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; return; @@ -654,7 +654,7 @@ sub _get_export_file { $output_file = $self->_init_print->expanded_output_filepath($output_file, $self->{objects}[0]->input_file); $output_file =~ s/\.gcode$/$suffix/i; my $dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file), - basename($output_file), $Slic3r::GUI::SkeinPanel::model_wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + basename($output_file), &Slic3r::GUI::SkeinPanel::MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; return undef; diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index 6712b52f3..8cdc8d7b2 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -13,6 +13,16 @@ our $last_input_file; our $last_output_file; our $last_config; +use constant FILE_WILDCARDS => { + stl => 'STL files (*.stl)|*.stl;*.STL', + obj => 'OBJ files (*.obj)|*.obj;*.OBJ', + amf => 'AMF files (*.amf)|*.amf;*.AMF;*.xml;*.XML', + ini => 'INI files *.ini|*.ini;*.INI', + gcode => 'G-code files *.gcode|*.gcode;*.GCODE|G-code files *.g|*.g;*.G', + svg => 'SVG files *.svg|*.svg;*.SVG', +}; +use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(stl obj amf)}; + sub new { my $class = shift; my ($parent) = @_; @@ -41,11 +51,6 @@ sub new { return $self; } -our $model_wildcard = "STL files (*.stl)|*.stl;*.STL|OBJ files (*.obj)|*.obj;*.OBJ|AMF files (*.amf)|*.amf;*.AMF;*.xml;*.XML"; -our $ini_wildcard = "INI files *.ini|*.ini;*.INI"; -our $gcode_wildcard = "G-code files *.gcode|*.gcode;*.GCODE|G-code files *.g|*.g;*.G"; -our $svg_wildcard = "SVG files *.svg|*.svg;*.SVG"; - sub do_slice { my $self = shift; my %params = @_; @@ -70,7 +75,7 @@ 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 | 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; @@ -107,7 +112,7 @@ sub do_slice { $output_file = $print->expanded_output_filepath($output_file); $output_file =~ s/\.gcode$/.svg/i if $params{export_svg}; my $dlg = Wx::FileDialog->new($self, 'Save ' . ($params{export_svg} ? 'SVG' : 'G-code') . ' file as:', dirname($output_file), - basename($output_file), $params{export_svg} ? $svg_wildcard : $gcode_wildcard, wxFD_SAVE); + basename($output_file), $params{export_svg} ? FILE_WILDCARDS->{svg} : FILE_WILDCARDS->{gcode}, wxFD_SAVE); if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; return; @@ -172,7 +177,7 @@ sub export_config { my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; my $filename = $last_config ? basename($last_config) : "config.ini"; my $dlg = Wx::FileDialog->new($self, 'Save configuration as:', $dir, $filename, - $ini_wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if ($dlg->ShowModal == wxID_OK) { my $file = $dlg->GetPath; $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); @@ -191,7 +196,7 @@ sub load_config_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", - $ini_wildcard, wxFD_OPEN | wxFD_FILE_MUST_EXIST); + FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); return unless $dlg->ShowModal == wxID_OK; ($file) = $dlg->GetPaths; $dlg->Destroy; @@ -221,6 +226,58 @@ sub config_wizard { } } +sub combine_stls { + my $self = shift; + + # get input files + my @input_files = (); + my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; + { + my $dlg_message = 'Choose one or more files to combine (STL/OBJ)'; + while (1) { + my $dialog = Wx::FileDialog->new($self, "$dlg_message:", $dir, "", MODEL_WILDCARD, + wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); + if ($dialog->ShowModal != wxID_OK) { + $dialog->Destroy; + last; + } + push @input_files, $dialog->GetPaths; + $dialog->Destroy; + $dlg_message .= " or hit Cancel if you have finished"; + $dir = dirname($input_files[0]); + } + return if !@input_files; + } + + # get output file + my $output_file = $input_files[0]; + { + $output_file =~ s/\.(?:stl|obj)$/.amf.xml/i; + my $dlg = Wx::FileDialog->new($self, 'Save multi-material AMF file as:', dirname($output_file), + basename($output_file), FILE_WILDCARDS->{amf}, wxFD_SAVE); + if ($dlg->ShowModal != wxID_OK) { + $dlg->Destroy; + return; + } + $output_file = $dlg->GetPath; + } + + my @models = map Slic3r::Model->read_from_file($_), @input_files; + my $new_model = Slic3r::Model->new; + my $new_object = $new_model->add_object; + for my $m (0 .. $#models) { + my $model = $models[$m]; + $new_model->set_material($m, { Name => basename($input_files[$m]) }); + $new_object->add_volume( + material_id => $m, + facets => $model->objects->[0]->volumes->[0]->facets, + vertices => $model->objects->[0]->vertices, + ); + } + + Slic3r::Format::AMF->write_file($output_file, $new_model); +} + =head2 config This method collects all config values from the tabs and merges them into a single config object. diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index 0914ea1c6..1e2a6b2ba 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -77,8 +77,20 @@ has 'instances' => (is => 'rw'); sub add_volume { my $self = shift; + my %args = @_; - my $volume = Slic3r::Model::Volume->new(object => $self, @_); + if (my $vertices = delete $args{vertices}) { + my $v_offset = @{$self->vertices}; + push @{$self->vertices}, @$vertices; + + @{$args{facets}} = map { + my $f = [@$_]; + $f->[$_] += $v_offset for -3..-1; + $f; + } @{$args{facets}}; + } + + my $volume = Slic3r::Model::Volume->new(object => $self, %args); push @{$self->volumes}, $volume; return $volume; } diff --git a/utils/stl-to-amf.pl b/utils/stl-to-amf.pl index e6fc1fa8d..e1adec7ce 100755 --- a/utils/stl-to-amf.pl +++ b/utils/stl-to-amf.pl @@ -35,19 +35,11 @@ my %opt = (); my $new_object = $new_model->add_object; for my $m (0 .. $#models) { my $model = $models[$m]; - my $v_offset = @{$new_object->vertices}; - push @{$new_object->vertices}, @{$model->objects->[0]->vertices}; - my @new_facets = map { - my $f = [@$_]; - $f->[$_] += $v_offset for -3..-1; - $f; - } @{ $model->objects->[0]->volumes->[0]->facets }; - - my $material_id = scalar keys %{$new_model->materials}; - $new_model->set_material($material_id, { Name => basename($ARGV[$m]) }); + $new_model->set_material($m, { Name => basename($ARGV[$m]) }); $new_object->add_volume( - material_id => $material_id, - facets => [@new_facets], + material_id => $m, + facets => $model->objects->[0]->volumes->[0]->facets, + vertices => $model->objects->[0]->vertices, ); } } else {