Merge branch 'master' into sender

Conflicts:
	Build.PL
	lib/Slic3r/GUI/MainFrame.pm
This commit is contained in:
Alessandro Ranellucci 2015-05-28 18:05:36 +02:00
commit 13b7316807
74 changed files with 1260 additions and 553 deletions

View File

@ -280,7 +280,7 @@ The author of the Silk icon set is Mark James.
Only retract before travel moves of this length in mm (default: 2) Only retract before travel moves of this length in mm (default: 2)
--retract-lift Lift Z by the given distance in mm when retracting (default: 0) --retract-lift Lift Z by the given distance in mm when retracting (default: 0)
--retract-layer-change --retract-layer-change
Enforce a retraction before each Z move (default: yes) Enforce a retraction before each Z move (default: no)
--wipe Wipe the nozzle while doing a retraction (default: no) --wipe Wipe the nozzle while doing a retraction (default: no)
Retraction options for multi-extruder setups: Retraction options for multi-extruder setups:

View File

@ -32,8 +32,6 @@ warn "Running Slic3r under Perl 5.16 is not supported nor recommended\n"
use FindBin; use FindBin;
our $var = "$FindBin::Bin/var"; our $var = "$FindBin::Bin/var";
use Encode;
use Encode::Locale;
use Moo 1.003001; use Moo 1.003001;
use Slic3r::XS; # import all symbols (constants etc.) before they get parsed use Slic3r::XS; # import all symbols (constants etc.) before they get parsed
@ -262,13 +260,17 @@ sub resume_all_threads {
} }
sub encode_path { sub encode_path {
my ($filename) = @_; my ($path) = @_;
return encode('locale_fs', $filename);
utf8::downgrade($path) if $^O eq 'MSWin32';
return $path;
} }
sub decode_path { sub decode_path {
my ($filename) = @_; my ($path) = @_;
return decode('locale_fs', $filename);
utf8::upgrade($path) if $^O eq 'MSWin32';
return $path;
} }
sub open { sub open {

View File

@ -6,6 +6,8 @@ extends 'Slic3r::Fill::Base';
use Slic3r::Geometry qw(scale unscale X); use Slic3r::Geometry qw(scale unscale X);
use Slic3r::Geometry::Clipper qw(offset offset2 union_pt_chained); use Slic3r::Geometry::Clipper qw(offset offset2 union_pt_chained);
sub no_sort { 1 }
sub fill_surface { sub fill_surface {
my $self = shift; my $self = shift;
my ($surface, %params) = @_; my ($surface, %params) = @_;
@ -36,7 +38,7 @@ sub fill_surface {
@loops = map Slic3r::Polygon->new(@$_), @loops = map Slic3r::Polygon->new(@$_),
reverse @{union_pt_chained(\@loops)}; reverse @{union_pt_chained(\@loops)};
# order paths using a nearest neighbor search # split paths using a nearest neighbor search
my @paths = (); my @paths = ();
my $last_pos = Slic3r::Point->new(0,0); my $last_pos = Slic3r::Point->new(0,0);
foreach my $loop (@loops) { foreach my $loop (@loops) {

View File

@ -21,7 +21,7 @@ has 'avoid_crossing_perimeters' => (is => 'rw', default => sub { Slic3r::GCode::
has 'enable_loop_clipping' => (is => 'rw', default => sub {1}); has 'enable_loop_clipping' => (is => 'rw', default => sub {1});
has 'enable_cooling_markers' => (is =>'rw', default => sub {0}); has 'enable_cooling_markers' => (is =>'rw', default => sub {0});
has 'layer_count' => (is => 'ro'); has 'layer_count' => (is => 'ro');
has '_layer_index' => (is => 'rw', default => sub {-1}); # just a counter has 'layer_index' => (is => 'rw', default => sub {-1}); # just a counter
has 'layer' => (is => 'rw'); has 'layer' => (is => 'rw');
has '_seam_position' => (is => 'ro', default => sub { {} }); # $object => pos has '_seam_position' => (is => 'ro', default => sub { {} }); # $object => pos
has 'first_layer' => (is => 'rw', default => sub {0}); # this flag triggers first layer speeds has 'first_layer' => (is => 'rw', default => sub {0}); # this flag triggers first layer speeds
@ -76,7 +76,7 @@ sub change_layer {
my ($self, $layer) = @_; my ($self, $layer) = @_;
$self->layer($layer); $self->layer($layer);
$self->_layer_index($self->_layer_index + 1); $self->layer_index($self->layer_index + 1);
$self->first_layer($layer->id == 0); $self->first_layer($layer->id == 0);
# avoid computing islands and overhangs if they're not needed # avoid computing islands and overhangs if they're not needed
@ -88,14 +88,14 @@ sub change_layer {
my $gcode = ""; my $gcode = "";
if (defined $self->layer_count) { if (defined $self->layer_count) {
$gcode .= $self->writer->update_progress($self->_layer_index, $self->layer_count); $gcode .= $self->writer->update_progress($self->layer_index, $self->layer_count);
} }
my $z = $layer->print_z + $self->config->z_offset; # in unscaled coordinates my $z = $layer->print_z + $self->config->z_offset; # in unscaled coordinates
if ($self->config->get_at('retract_layer_change', $self->writer->extruder->id) && $self->writer->will_move_z($z)) { if ($self->config->get_at('retract_layer_change', $self->writer->extruder->id) && $self->writer->will_move_z($z)) {
$gcode .= $self->retract; $gcode .= $self->retract;
} }
$gcode .= $self->writer->travel_to_z($z, 'move to next layer (' . $self->layer->id . ')'); $gcode .= $self->writer->travel_to_z($z, 'move to next layer (' . $self->layer_index . ')');
# forget last wiping path as wiping after raising Z is pointless # forget last wiping path as wiping after raising Z is pointless
$self->wipe->path(undef); $self->wipe->path(undef);

View File

@ -81,7 +81,7 @@ sub OnInit {
$self->{notifier} = Slic3r::GUI::Notifier->new; $self->{notifier} = Slic3r::GUI::Notifier->new;
# locate or create data directory # locate or create data directory
$datadir ||= Wx::StandardPaths::Get->GetUserDataDir; $datadir ||= Slic3r::decode_path(Wx::StandardPaths::Get->GetUserDataDir);
my $enc_datadir = Slic3r::encode_path($datadir); my $enc_datadir = Slic3r::encode_path($datadir);
Slic3r::debugf "Data directory: %s\n", $datadir; Slic3r::debugf "Data directory: %s\n", $datadir;
@ -297,7 +297,7 @@ sub open_model {
$dialog->Destroy; $dialog->Destroy;
return; return;
} }
my @input_files = $dialog->GetPaths; my @input_files = map Slic3r::decode_path($_), $dialog->GetPaths;
$dialog->Destroy; $dialog->Destroy;
return @input_files; return @input_files;

View File

@ -593,7 +593,7 @@ sub Resize {
-$x/2, $x/2, -$y/2, $y/2, -$x/2, $x/2, -$y/2, $y/2,
-$depth, 2*$depth, -$depth, 2*$depth,
); );
glMatrixMode(GL_MODELVIEW); glMatrixMode(GL_MODELVIEW);
} }
@ -937,7 +937,7 @@ package Slic3r::GUI::3DScene;
use base qw(Slic3r::GUI::3DScene::Base); use base qw(Slic3r::GUI::3DScene::Base);
use OpenGL qw(:glconstants :gluconstants :glufunctions); use OpenGL qw(:glconstants :gluconstants :glufunctions);
use List::Util qw(first); use List::Util qw(first min max);
use Slic3r::Geometry qw(scale unscale epsilon); use Slic3r::Geometry qw(scale unscale epsilon);
use Slic3r::Print::State ':steps'; use Slic3r::Print::State ':steps';
@ -1079,6 +1079,59 @@ sub load_print_object_slices {
); );
} }
sub load_print_toolpaths {
my ($self, $print) = @_;
return if !$print->step_done(STEP_SKIRT);
return if !$print->step_done(STEP_BRIM);
return if !$print->has_skirt && $print->config->brim_width == 0;
my $qverts = Slic3r::GUI::_3DScene::GLVertexArray->new;
my $tverts = Slic3r::GUI::_3DScene::GLVertexArray->new;
my %offsets = (); # print_z => [ qverts, tverts ]
my $skirt_height = 0; # number of layers
if ($print->has_infinite_skirt) {
$skirt_height = $print->total_layer_count;
} else {
$skirt_height = min($print->config->skirt_height, $print->total_layer_count);
}
$skirt_height ||= 1 if $print->config->brim_width > 0;
# get first $skirt_height layers (maybe this should be moved to a PrintObject method?)
my $object0 = $print->get_object(0);
my @layers = ();
push @layers, map $object0->get_layer($_-1), 1..min($skirt_height, $object0->layer_count);
push @layers, map $object0->get_support_layer($_-1), 1..min($skirt_height, $object0->support_layer_count);
@layers = sort { $a->print_z <=> $b->print_z } @layers;
@layers = @layers[0..($skirt_height-1)];
foreach my $i (0..($skirt_height-1)) {
my $top_z = $layers[$i]->print_z;
$offsets{$top_z} = [$qverts->size, $tverts->size];
if ($i == 0) {
$self->_extrusionentity_to_verts($print->brim, $top_z, Slic3r::Point->new(0,0), $qverts, $tverts);
}
$self->_extrusionentity_to_verts($print->skirt, $top_z, Slic3r::Point->new(0,0), $qverts, $tverts);
}
my $bb = Slic3r::Geometry::BoundingBoxf3->new;
{
my $pbb = $print->bounding_box;
$bb->merge_point(Slic3r::Pointf3->new_unscale(@{$pbb->min_point}));
$bb->merge_point(Slic3r::Pointf3->new_unscale(@{$pbb->max_point}));
}
push @{$self->volumes}, Slic3r::GUI::3DScene::Volume->new(
bounding_box => $bb,
color => COLORS->[2],
qverts => $qverts,
tverts => $tverts,
offsets => { %offsets },
);
}
sub load_print_object_toolpaths { sub load_print_object_toolpaths {
my ($self, $object) = @_; my ($self, $object) = @_;

View File

@ -3,7 +3,7 @@ use strict;
use warnings; use warnings;
use utf8; use utf8;
use Wx qw(:font :html :misc :sizer :systemsettings); use Wx qw(:font :html :misc :dialog :sizer :systemsettings);
use Wx::Event qw(EVT_HTML_LINK_CLICKED); use Wx::Event qw(EVT_HTML_LINK_CLICKED);
use Wx::Print; use Wx::Print;
use Wx::Html; use Wx::Html;
@ -12,7 +12,7 @@ use base 'Wx::Dialog';
sub new { sub new {
my $class = shift; my $class = shift;
my ($parent) = @_; my ($parent) = @_;
my $self = $class->SUPER::new($parent, -1, 'About Slic3r', wxDefaultPosition, [600, 300]); my $self = $class->SUPER::new($parent, -1, 'About Slic3r', wxDefaultPosition, [600, 300], &Wx::wxCLOSE_BOX);
$self->SetBackgroundColour(Wx::wxWHITE); $self->SetBackgroundColour(Wx::wxWHITE);
my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);

View File

@ -386,7 +386,7 @@ sub _load_stl {
$dialog->Destroy; $dialog->Destroy;
return; return;
} }
my $input_file = $dialog->GetPaths; my $input_file = Slic3r::decode_path($dialog->GetPaths);
$dialog->Destroy; $dialog->Destroy;
my $model = Slic3r::Model->read_from_file($input_file); my $model = Slic3r::Model->read_from_file($input_file);

View File

@ -141,8 +141,8 @@ sub _init_tabpanel {
if ($self->{plater}) { if ($self->{plater}) {
$self->{plater}->on_select_preset(sub { $self->{plater}->on_select_preset(sub {
my ($group, $preset) = @_; my ($group, $i) = @_;
$self->{options_tabs}{$group}->select_preset($preset); $self->{options_tabs}{$group}->select_preset($i);
}); });
# load initial config # load initial config
@ -158,38 +158,44 @@ sub _init_menubar {
{ {
$self->_append_menu_item($fileMenu, "&Load Config…\tCtrl+L", 'Load exported configuration file', sub { $self->_append_menu_item($fileMenu, "&Load Config…\tCtrl+L", 'Load exported configuration file', sub {
$self->load_config_file; $self->load_config_file;
}); }, undef, 'plugin_add.png');
$self->_append_menu_item($fileMenu, "&Export Config…\tCtrl+E", 'Export current configuration to file', sub { $self->_append_menu_item($fileMenu, "&Export Config…\tCtrl+E", 'Export current configuration to file', sub {
$self->export_config; $self->export_config;
}); }, undef, 'plugin_go.png');
$self->_append_menu_item($fileMenu, "&Load Config Bundle…", 'Load presets from a bundle', sub { $self->_append_menu_item($fileMenu, "&Load Config Bundle…", 'Load presets from a bundle', sub {
$self->load_configbundle; $self->load_configbundle;
}); }, undef, 'lorry_add.png');
$self->_append_menu_item($fileMenu, "&Export Config Bundle…", 'Export all presets to file', sub { $self->_append_menu_item($fileMenu, "&Export Config Bundle…", 'Export all presets to file', sub {
$self->export_configbundle; $self->export_configbundle;
}); }, undef, 'lorry_go.png');
$fileMenu->AppendSeparator(); $fileMenu->AppendSeparator();
my $repeat; my $repeat;
$self->_append_menu_item($fileMenu, "Q&uick Slice…\tCtrl+U", 'Slice file', sub { $self->_append_menu_item($fileMenu, "Q&uick Slice…\tCtrl+U", 'Slice file', sub {
$self->quick_slice; wxTheApp->CallAfter(sub {
$repeat->Enable(defined $Slic3r::GUI::MainFrame::last_input_file); $self->quick_slice;
}); $repeat->Enable(defined $Slic3r::GUI::MainFrame::last_input_file);
});
}, undef, 'cog_go.png');
$self->_append_menu_item($fileMenu, "Quick Slice and Save &As…\tCtrl+Alt+U", 'Slice file and save as', sub { $self->_append_menu_item($fileMenu, "Quick Slice and Save &As…\tCtrl+Alt+U", 'Slice file and save as', sub {
$self->quick_slice(save_as => 1); wxTheApp->CallAfter(sub {
$repeat->Enable(defined $Slic3r::GUI::MainFrame::last_input_file); $self->quick_slice(save_as => 1);
}); $repeat->Enable(defined $Slic3r::GUI::MainFrame::last_input_file);
});
}, undef, 'cog_go.png');
$repeat = $self->_append_menu_item($fileMenu, "&Repeat Last Quick Slice\tCtrl+Shift+U", 'Repeat last quick slice', sub { $repeat = $self->_append_menu_item($fileMenu, "&Repeat Last Quick Slice\tCtrl+Shift+U", 'Repeat last quick slice', sub {
$self->quick_slice(reslice => 1); wxTheApp->CallAfter(sub {
}); $self->quick_slice(reslice => 1);
});
}, undef, 'cog_go.png');
$repeat->Enable(0); $repeat->Enable(0);
$fileMenu->AppendSeparator(); $fileMenu->AppendSeparator();
$self->_append_menu_item($fileMenu, "Slice to SV&G…\tCtrl+G", 'Slice file to SVG', sub { $self->_append_menu_item($fileMenu, "Slice to SV&G…\tCtrl+G", 'Slice file to SVG', sub {
$self->quick_slice(save_as => 1, export_svg => 1); $self->quick_slice(save_as => 1, export_svg => 1);
}); }, undef, 'shape_handles.png');
$fileMenu->AppendSeparator(); $fileMenu->AppendSeparator();
$self->_append_menu_item($fileMenu, "Repair STL file…", 'Automatically repair an STL file', sub { $self->_append_menu_item($fileMenu, "Repair STL file…", 'Automatically repair an STL file', sub {
$self->repair_stl; $self->repair_stl;
}); }, undef, 'wrench.png');
$fileMenu->AppendSeparator(); $fileMenu->AppendSeparator();
$self->_append_menu_item($fileMenu, "Preferences…", 'Application preferences', sub { $self->_append_menu_item($fileMenu, "Preferences…", 'Application preferences', sub {
Slic3r::GUI::Preferences->new($self)->ShowModal; Slic3r::GUI::Preferences->new($self)->ShowModal;
@ -207,13 +213,13 @@ sub _init_menubar {
$self->{plater_menu} = Wx::Menu->new; $self->{plater_menu} = Wx::Menu->new;
$self->_append_menu_item($self->{plater_menu}, "Export G-code...", 'Export current plate as G-code', sub { $self->_append_menu_item($self->{plater_menu}, "Export G-code...", 'Export current plate as G-code', sub {
$plater->export_gcode; $plater->export_gcode;
}); }, undef, 'cog_go.png');
$self->_append_menu_item($self->{plater_menu}, "Export plate as STL...", 'Export current plate as STL', sub { $self->_append_menu_item($self->{plater_menu}, "Export plate as STL...", 'Export current plate as STL', sub {
$plater->export_stl; $plater->export_stl;
}); }, undef, 'brick_go.png');
$self->_append_menu_item($self->{plater_menu}, "Export plate as AMF...", 'Export current plate as AMF', sub { $self->_append_menu_item($self->{plater_menu}, "Export plate as AMF...", 'Export current plate as AMF', sub {
$plater->export_amf; $plater->export_amf;
}); }, undef, 'brick_go.png');
$self->{object_menu} = $self->{plater}->object_menu; $self->{object_menu} = $self->{plater}->object_menu;
$self->on_plater_selection_changed(0); $self->on_plater_selection_changed(0);
@ -226,22 +232,22 @@ sub _init_menubar {
if (!$self->{no_plater}) { if (!$self->{no_plater}) {
$self->_append_menu_item($windowMenu, "Select &Plater Tab\tCtrl+1", 'Show the plater', sub { $self->_append_menu_item($windowMenu, "Select &Plater Tab\tCtrl+1", 'Show the plater', sub {
$self->select_tab(0); $self->select_tab(0);
}); }, undef, 'application_view_tile.png');
$self->_append_menu_item($windowMenu, "Select &Controller Tab\tCtrl+T", 'Show the printer controller', sub { $self->_append_menu_item($windowMenu, "Select &Controller Tab\tCtrl+T", 'Show the printer controller', sub {
$self->select_tab(1); $self->select_tab(1);
}); }, undef, 'printer_empty.png');
$windowMenu->AppendSeparator(); $windowMenu->AppendSeparator();
$tab_offset += 2; $tab_offset += 2;
} }
$self->_append_menu_item($windowMenu, "Select P&rint Settings Tab\tCtrl+2", 'Show the print settings', sub { $self->_append_menu_item($windowMenu, "Select P&rint Settings Tab\tCtrl+2", 'Show the print settings', sub {
$self->select_tab($tab_offset+0); $self->select_tab($tab_offset+0);
}); }, undef, 'cog.png');
$self->_append_menu_item($windowMenu, "Select &Filament Settings Tab\tCtrl+3", 'Show the filament settings', sub { $self->_append_menu_item($windowMenu, "Select &Filament Settings Tab\tCtrl+3", 'Show the filament settings', sub {
$self->select_tab($tab_offset+1); $self->select_tab($tab_offset+1);
}); }, undef, 'spool.png');
$self->_append_menu_item($windowMenu, "Select Print&er Settings Tab\tCtrl+4", 'Show the printer settings', sub { $self->_append_menu_item($windowMenu, "Select Print&er Settings Tab\tCtrl+4", 'Show the printer settings', sub {
$self->select_tab($tab_offset+2); $self->select_tab($tab_offset+2);
}); }, undef, 'printer_empty.png');
} }
# Help menu # Help menu
@ -313,7 +319,7 @@ sub quick_slice {
$dialog->Destroy; $dialog->Destroy;
return; return;
} }
$input_file = $dialog->GetPaths; $input_file = Slic3r::decode_path($dialog->GetPaths);
$dialog->Destroy; $dialog->Destroy;
$last_input_file = $input_file unless $params{export_svg}; $last_input_file = $input_file unless $params{export_svg};
} else { } else {
@ -373,7 +379,7 @@ sub quick_slice {
$dlg->Destroy; $dlg->Destroy;
return; return;
} }
$output_file = $dlg->GetPath; $output_file = Slic3r::decode_path($dlg->GetPath);
$last_output_file = $output_file unless $params{export_svg}; $last_output_file = $output_file unless $params{export_svg};
$Slic3r::GUI::Settings->{_}{last_output_path} = dirname($output_file); $Slic3r::GUI::Settings->{_}{last_output_path} = dirname($output_file);
wxTheApp->save_settings; wxTheApp->save_settings;
@ -420,7 +426,7 @@ sub repair_stl {
$dialog->Destroy; $dialog->Destroy;
return; return;
} }
$input_file = $dialog->GetPaths; $input_file = Slic3r::decode_path($dialog->GetPaths);
$dialog->Destroy; $dialog->Destroy;
} }
@ -433,7 +439,7 @@ sub repair_stl {
$dlg->Destroy; $dlg->Destroy;
return undef; return undef;
} }
$output_file = $dlg->GetPath; $output_file = Slic3r::decode_path($dlg->GetPath);
$dlg->Destroy; $dlg->Destroy;
} }
@ -470,7 +476,7 @@ sub export_config {
my $dlg = Wx::FileDialog->new($self, 'Save configuration as:', $dir, $filename, my $dlg = Wx::FileDialog->new($self, 'Save configuration as:', $dir, $filename,
&Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if ($dlg->ShowModal == wxID_OK) { if ($dlg->ShowModal == wxID_OK) {
my $file = $dlg->GetPath; my $file = Slic3r::decode_path($dlg->GetPath);
$Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file);
wxTheApp->save_settings; wxTheApp->save_settings;
$last_config = $file; $last_config = $file;
@ -489,7 +495,7 @@ sub load_config_file {
my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', $dir, "config.ini", my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', $dir, "config.ini",
&Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
return unless $dlg->ShowModal == wxID_OK; return unless $dlg->ShowModal == wxID_OK;
($file) = $dlg->GetPaths; $file = Slic3r::decode_path($dlg->GetPaths);
$dlg->Destroy; $dlg->Destroy;
} }
$Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file);
@ -514,7 +520,7 @@ sub export_configbundle {
my $dlg = Wx::FileDialog->new($self, 'Save presets bundle as:', $dir, $filename, my $dlg = Wx::FileDialog->new($self, 'Save presets bundle as:', $dir, $filename,
&Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if ($dlg->ShowModal == wxID_OK) { if ($dlg->ShowModal == wxID_OK) {
my $file = $dlg->GetPath; my $file = Slic3r::decode_path($dlg->GetPath);
$Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file);
wxTheApp->save_settings; wxTheApp->save_settings;
@ -547,7 +553,7 @@ sub load_configbundle {
my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', $dir, "config.ini", my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', $dir, "config.ini",
&Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
return unless $dlg->ShowModal == wxID_OK; return unless $dlg->ShowModal == wxID_OK;
my ($file) = $dlg->GetPaths; my $file = Slic3r::decode_path($dlg->GetPaths);
$dlg->Destroy; $dlg->Destroy;
$Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file);
@ -601,6 +607,9 @@ sub load_config {
foreach my $tab (values %{$self->{options_tabs}}) { foreach my $tab (values %{$self->{options_tabs}}) {
$tab->load_config($config); $tab->load_config($config);
} }
if ($self->{plater}) {
$self->{plater}->on_config_change($config);
}
} }
sub config_wizard { sub config_wizard {
@ -643,13 +652,19 @@ sub config {
if (!$self->{plater} || $self->{plater}->filament_presets == 1 || $self->{mode} eq 'simple') { if (!$self->{plater} || $self->{plater}->filament_presets == 1 || $self->{mode} eq 'simple') {
$filament_config = $self->{options_tabs}{filament}->config; $filament_config = $self->{options_tabs}{filament}->config;
} else { } else {
# TODO: handle dirty presets.
# perhaps plater shouldn't expose dirty presets at all in multi-extruder environments.
my $i = -1; my $i = -1;
foreach my $preset_idx ($self->{plater}->filament_presets) { foreach my $preset_idx ($self->{plater}->filament_presets) {
$i++; $i++;
my $preset = $self->{options_tabs}{filament}->get_preset($preset_idx); my $config;
my $config = $self->{options_tabs}{filament}->get_preset_config($preset); if ($preset_idx == $self->{options_tabs}{filament}->current_preset) {
# the selected preset for this extruder is the one in the tab
# use the tab's config instead of the preset in case it is dirty
# perhaps plater shouldn't expose dirty presets at all in multi-extruder environments.
$config = $self->{options_tabs}{filament}->config;
} else {
my $preset = $self->{options_tabs}{filament}->get_preset($preset_idx);
$config = $self->{options_tabs}{filament}->get_preset_config($preset);
}
if (!$filament_config) { if (!$filament_config) {
$filament_config = $config->clone; $filament_config = $config->clone;
next; next;
@ -719,12 +734,23 @@ sub select_tab {
} }
sub _append_menu_item { sub _append_menu_item {
my ($self, $menu, $string, $description, $cb, $id) = @_; my ($self, $menu, $string, $description, $cb, $id, $icon) = @_;
$id //= &Wx::NewId(); $id //= &Wx::NewId();
my $item = $menu->Append($id, $string, $description); my $item = $menu->Append($id, $string, $description);
$self->_set_menu_item_icon($item, $icon);
EVT_MENU($self, $id, $cb); EVT_MENU($self, $id, $cb);
return $item; return $item;
} }
sub _set_menu_item_icon {
my ($self, $menuItem, $icon) = @_;
# SetBitmap was not available on OS X before Wx 0.9927
if ($icon && $menuItem->can('SetBitmap')) {
$menuItem->SetBitmap(Wx::Bitmap->new("$Slic3r::var/$icon", wxBITMAP_TYPE_PNG));
}
}
1; 1;

View File

@ -178,6 +178,11 @@ sub _build_field {
parent => $self->parent, parent => $self->parent,
option => $opt, option => $opt,
); );
} elsif ($type eq 'color') {
$field = Slic3r::GUI::OptionsGroup::Field::ColourPicker->new(
parent => $self->parent,
option => $opt,
);
} elsif ($type =~ /^(f|s|s@|percent)$/) { } elsif ($type =~ /^(f|s|s@|percent)$/) {
$field = Slic3r::GUI::OptionsGroup::Field::TextCtrl->new( $field = Slic3r::GUI::OptionsGroup::Field::TextCtrl->new(
parent => $self->parent, parent => $self->parent,

View File

@ -7,7 +7,6 @@ has 'parent' => (is => 'ro', required => 1);
has 'option' => (is => 'ro', required => 1); # Slic3r::GUI::OptionsGroup::Option has 'option' => (is => 'ro', required => 1); # Slic3r::GUI::OptionsGroup::Option
has 'on_change' => (is => 'rw', default => sub { sub {} }); has 'on_change' => (is => 'rw', default => sub { sub {} });
has 'on_kill_focus' => (is => 'rw', default => sub { sub {} }); has 'on_kill_focus' => (is => 'rw', default => sub { sub {} });
has 'wxSsizer' => (is => 'rw'); # alternatively, wxSizer object
has 'disable_change_event' => (is => 'rw', default => sub { 0 }); has 'disable_change_event' => (is => 'rw', default => sub { 0 });
# This method should not fire the on_change event # This method should not fire the on_change event
@ -128,6 +127,8 @@ extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
use Wx qw(:misc); use Wx qw(:misc);
use Wx::Event qw(EVT_SPINCTRL EVT_TEXT EVT_KILL_FOCUS); use Wx::Event qw(EVT_SPINCTRL EVT_TEXT EVT_KILL_FOCUS);
has 'tmp_value' => (is => 'rw');
sub BUILD { sub BUILD {
my ($self) = @_; my ($self) = @_;
@ -139,13 +140,27 @@ sub BUILD {
$self->_on_change($self->option->opt_id); $self->_on_change($self->option->opt_id);
}); });
EVT_TEXT($self->parent, $field, sub { EVT_TEXT($self->parent, $field, sub {
my ($s, $event) = @_;
# On OSX/Cocoa, wxSpinCtrl::GetValue() doesn't return the new value
# when it was changed from the text control, so the on_change callback
# gets the old one, and on_kill_focus resets the control to the old value.
# As a workaround, we get the new value from $event->GetString and store
# here temporarily so that we can return it from $self->get_value
$self->tmp_value($event->GetString);
$self->_on_change($self->option->opt_id); $self->_on_change($self->option->opt_id);
$self->tmp_value(undef);
}); });
EVT_KILL_FOCUS($field, sub { EVT_KILL_FOCUS($field, sub {
$self->_on_kill_focus($self->option->opt_id, @_); $self->_on_kill_focus($self->option->opt_id, @_);
}); });
} }
sub get_value {
my ($self) = @_;
return $self->tmp_value // $self->wxWindow->GetValue;
}
package Slic3r::GUI::OptionsGroup::Field::TextCtrl; package Slic3r::GUI::OptionsGroup::Field::TextCtrl;
use Moo; use Moo;
@ -261,6 +276,9 @@ use List::Util qw(first);
use Wx qw(wxTheApp :misc :combobox); use Wx qw(wxTheApp :misc :combobox);
use Wx::Event qw(EVT_COMBOBOX EVT_TEXT); use Wx::Event qw(EVT_COMBOBOX EVT_TEXT);
# if option has no 'values', indices are values
# if option has no 'labels', values are labels
sub BUILD { sub BUILD {
my ($self) = @_; my ($self) = @_;
@ -274,15 +292,15 @@ sub BUILD {
my $disable_change_event = $self->disable_change_event; my $disable_change_event = $self->disable_change_event;
$self->disable_change_event(1); $self->disable_change_event(1);
my $value = $field->GetSelection; my $idx = $field->GetSelection; # get index of selected value
my $label; my $label;
if ($self->option->values) { if ($self->option->labels && $idx <= $#{$self->option->labels}) {
$label = $value = $self->option->values->[$value]; $label = $self->option->labels->[$idx];
} elsif ($value <= $#{$self->option->labels}) { } elsif ($self->option->values && $idx <= $#{$self->option->values}) {
$label = $self->option->labels->[$value]; $label = $self->option->values->[$idx];
} else { } else {
$label = $value; $label = $idx;
} }
# The MSW implementation of wxComboBox will leave the field blank if we call # The MSW implementation of wxComboBox will leave the field blank if we call
@ -322,8 +340,8 @@ sub set_value {
$self->disable_change_event(0); $self->disable_change_event(0);
return; return;
} }
} } elsif ($self->option->labels && $value <= $#{$self->option->labels}) {
if ($self->option->labels && $value <= $#{$self->option->labels}) { # if we have no values, we expect value to be an index
$field->SetValue($self->option->labels->[$value]); $field->SetValue($self->option->labels->[$value]);
$self->disable_change_event(0); $self->disable_change_event(0);
return; return;
@ -351,6 +369,47 @@ sub get_value {
} }
package Slic3r::GUI::OptionsGroup::Field::ColourPicker;
use Moo;
extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
use Wx qw(:misc :colour);
use Wx::Event qw(EVT_COLOURPICKER_CHANGED);
sub BUILD {
my ($self) = @_;
my $field = Wx::ColourPickerCtrl->new($self->parent, -1,
$self->_string_to_colour($self->option->default), wxDefaultPosition,
$self->_default_size);
$self->wxWindow($field);
EVT_COLOURPICKER_CHANGED($self->parent, $field, sub {
$self->_on_change($self->option->opt_id);
});
}
sub set_value {
my ($self, $value) = @_;
$self->disable_change_event(1);
$self->wxWindow->SetColour($self->_string_to_colour($value));
$self->disable_change_event(0);
}
sub get_value {
my ($self) = @_;
return $self->wxWindow->GetColour->GetAsString(wxC2S_HTML_SYNTAX);
}
sub _string_to_colour {
my ($self, $string) = @_;
$string =~ s/^#//;
return Wx::Colour->new(unpack 'C*', pack 'H*', $string);
}
package Slic3r::GUI::OptionsGroup::Field::wxSizer; package Slic3r::GUI::OptionsGroup::Field::wxSizer;
use Moo; use Moo;
extends 'Slic3r::GUI::OptionsGroup::Field'; extends 'Slic3r::GUI::OptionsGroup::Field';

View File

@ -4,14 +4,14 @@ use warnings;
use utf8; use utf8;
use File::Basename qw(basename dirname); use File::Basename qw(basename dirname);
use List::Util qw(sum first); use List::Util qw(sum first max);
use Slic3r::Geometry qw(X Y Z MIN MAX scale unscale deg2rad); use Slic3r::Geometry qw(X Y Z MIN MAX scale unscale deg2rad);
use threads::shared qw(shared_clone); use threads::shared qw(shared_clone);
use Wx qw(:button :cursor :dialog :filedialog :keycode :icon :font :id :listctrl :misc use Wx qw(:button :cursor :dialog :filedialog :keycode :icon :font :id :listctrl :misc
:panel :sizer :toolbar :window wxTheApp :notebook); :panel :sizer :toolbar :window wxTheApp :notebook :combobox);
use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED
EVT_LIST_ITEM_DESELECTED EVT_LIST_ITEM_SELECTED EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL EVT_LIST_ITEM_DESELECTED EVT_LIST_ITEM_SELECTED EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL
EVT_CHOICE EVT_TIMER EVT_NOTEBOOK_PAGE_CHANGED); EVT_CHOICE EVT_COMBOBOX EVT_TIMER EVT_NOTEBOOK_PAGE_CHANGED);
use base 'Wx::Panel'; use base 'Wx::Panel';
use constant TB_ADD => &Wx::NewId; use constant TB_ADD => &Wx::NewId;
@ -36,7 +36,7 @@ our $ERROR_EVENT : shared = Wx::NewEventType;
our $EXPORT_COMPLETED_EVENT : shared = Wx::NewEventType; our $EXPORT_COMPLETED_EVENT : shared = Wx::NewEventType;
our $PROCESS_COMPLETED_EVENT : shared = Wx::NewEventType; our $PROCESS_COMPLETED_EVENT : shared = Wx::NewEventType;
use constant FILAMENT_CHOOSERS_SPACING => 3; use constant FILAMENT_CHOOSERS_SPACING => 0;
use constant PROCESS_DELAY => 0.5 * 1000; # milliseconds use constant PROCESS_DELAY => 0.5 * 1000; # milliseconds
my $PreventListEvents = 0; my $PreventListEvents = 0;
@ -335,12 +335,11 @@ sub new {
for my $group (qw(print filament printer)) { for my $group (qw(print filament printer)) {
my $text = Wx::StaticText->new($self, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); my $text = Wx::StaticText->new($self, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT);
$text->SetFont($Slic3r::GUI::small_font); $text->SetFont($Slic3r::GUI::small_font);
my $choice = Wx::Choice->new($self, -1, wxDefaultPosition, wxDefaultSize, []); my $choice = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY);
$choice->SetFont($Slic3r::GUI::small_font);
$self->{preset_choosers}{$group} = [$choice]; $self->{preset_choosers}{$group} = [$choice];
EVT_CHOICE($choice, $choice, sub { $self->_on_select_preset($group, @_) }); EVT_COMBOBOX($choice, $choice, sub { $self->_on_select_preset($group, @_) });
$presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); $presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4);
$presets->Add($choice, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM, 8); $presets->Add($choice, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM, 0);
} }
} }
@ -446,13 +445,33 @@ sub GetFrame {
sub update_presets { sub update_presets {
my $self = shift; my $self = shift;
my ($group, $items, $selected) = @_; my ($group, $presets, $selected) = @_;
foreach my $choice (@{ $self->{preset_choosers}{$group} }) { foreach my $choice (@{ $self->{preset_choosers}{$group} }) {
my $sel = $choice->GetSelection; my $sel = $choice->GetSelection;
$choice->Clear; $choice->Clear;
$choice->Append($_) for @$items; foreach my $preset (@$presets) {
$choice->SetSelection($sel) if $sel <= $#$items; my $bitmap;
if ($group eq 'filament') {
my $config = $preset->config(['filament_colour']);
my $rgb_hex = $config->filament_colour->[0];
if ($preset->default) {
$bitmap = Wx::Bitmap->new("$Slic3r::var/spool.png", wxBITMAP_TYPE_PNG);
} else {
$rgb_hex =~ s/^#//;
my @rgb = unpack 'C*', pack 'H*', $rgb_hex;
my $image = Wx::Image->new(16,16);
$image->SetRGB(Wx::Rect->new(0,0,16,16), @rgb);
$bitmap = Wx::Bitmap->new($image);
}
} elsif ($group eq 'print') {
$bitmap = Wx::Bitmap->new("$Slic3r::var/cog.png", wxBITMAP_TYPE_PNG);
} elsif ($group eq 'printer') {
$bitmap = Wx::Bitmap->new("$Slic3r::var/printer_empty.png", wxBITMAP_TYPE_PNG);
}
$choice->AppendString($preset->name, $bitmap);
}
$choice->SetSelection($sel) if $sel <= $#$presets;
} }
$self->{preset_choosers}{$group}[0]->SetSelection($selected); $self->{preset_choosers}{$group}[0]->SetSelection($selected);
} }
@ -497,8 +516,11 @@ sub load_model_objects {
my ($self, @model_objects) = @_; my ($self, @model_objects) = @_;
my $bed_centerf = $self->bed_centerf; my $bed_centerf = $self->bed_centerf;
my $bed_shape = Slic3r::Polygon->new_scale(@{$self->{config}->bed_shape});
my $bed_size = $bed_shape->bounding_box->size;
my $need_arrange = 0; my $need_arrange = 0;
my $scaled_down = 0;
my @obj_idx = (); my @obj_idx = ();
foreach my $model_object (@model_objects) { foreach my $model_object (@model_objects) {
my $o = $self->{model}->add_object($model_object); my $o = $self->{model}->add_object($model_object);
@ -516,6 +538,16 @@ sub load_model_objects {
$o->center_around_origin; # also aligns object to Z = 0 $o->center_around_origin; # also aligns object to Z = 0
$o->add_instance(offset => $bed_centerf); $o->add_instance(offset => $bed_centerf);
} }
{
# if the object is too large (more than 5 times the bed), scale it down
my $size = $o->bounding_box->size;
my $ratio = max(@$size[X,Y]) / unscale(max(@$bed_size[X,Y]));
if ($ratio > 5) {
$_->set_scaling_factor(1/$ratio) for @{$o->instances};
$scaled_down = 1;
}
}
$self->{print}->auto_assign_extruders($o); $self->{print}->auto_assign_extruders($o);
$self->{print}->add_model_object($o); $self->{print}->add_model_object($o);
@ -526,14 +558,15 @@ sub load_model_objects {
$need_arrange = 0; $need_arrange = 0;
} }
$self->objects_loaded(\@obj_idx, no_arrange => !$need_arrange); if ($scaled_down) {
} Slic3r::GUI::show_info(
$self,
sub objects_loaded { 'Your object appears to be too large, so it was automatically scaled down to fit your print bed.',
my $self = shift; 'Object too large?',
my ($obj_idxs, %params) = @_; );
}
foreach my $obj_idx (@$obj_idxs) { foreach my $obj_idx (@obj_idx) {
my $object = $self->{objects}[$obj_idx]; my $object = $self->{objects}[$obj_idx];
my $model_object = $self->{model}->objects->[$obj_idx]; my $model_object = $self->{model}->objects->[$obj_idx];
$self->{list}->InsertStringItem($obj_idx, $object->name); $self->{list}->InsertStringItem($obj_idx, $object->name);
@ -545,7 +578,7 @@ sub objects_loaded {
$self->make_thumbnail($obj_idx); $self->make_thumbnail($obj_idx);
} }
$self->arrange unless $params{no_arrange}; $self->arrange if $need_arrange;
$self->update; $self->update;
# zoom to objects # zoom to objects
@ -553,7 +586,7 @@ sub objects_loaded {
if $self->{canvas3D}; if $self->{canvas3D};
$self->{list}->Update; $self->{list}->Update;
$self->{list}->Select($obj_idxs->[-1], 1); $self->{list}->Select($obj_idx[-1], 1);
$self->object_list_changed; $self->object_list_changed;
$self->schedule_background_process; $self->schedule_background_process;
@ -691,6 +724,7 @@ sub rotate {
my $self = shift; my $self = shift;
my ($angle, $axis) = @_; my ($angle, $axis) = @_;
# angle is in degrees
$axis //= Z; $axis //= Z;
my ($obj_idx, $object) = $self->selected_object; my ($obj_idx, $object) = $self->selected_object;
@ -712,14 +746,14 @@ sub rotate {
$self->stop_background_process; $self->stop_background_process;
if ($axis == Z) { if ($axis == Z) {
my $new_angle = $model_instance->rotation + $angle; my $new_angle = $model_instance->rotation + deg2rad($angle);
$_->set_rotation($new_angle) for @{ $model_object->instances }; $_->set_rotation($new_angle) for @{ $model_object->instances };
$object->transform_thumbnail($self->{model}, $obj_idx); $object->transform_thumbnail($self->{model}, $obj_idx);
} else { } else {
# rotation around X and Y needs to be performed on mesh # rotation around X and Y needs to be performed on mesh
# so we first apply any Z rotation # so we first apply any Z rotation
if ($model_instance->rotation != 0) { if ($model_instance->rotation != 0) {
$model_object->rotate(deg2rad($model_instance->rotation), Z); $model_object->rotate($model_instance->rotation, Z);
$_->set_rotation(0) for @{ $model_object->instances }; $_->set_rotation(0) for @{ $model_object->instances };
} }
$model_object->rotate(deg2rad($angle), $axis); $model_object->rotate(deg2rad($angle), $axis);
@ -749,7 +783,7 @@ sub flip {
# apply Z rotation before flipping # apply Z rotation before flipping
if ($model_instance->rotation != 0) { if ($model_instance->rotation != 0) {
$model_object->rotate(deg2rad($model_instance->rotation), Z); $model_object->rotate($model_instance->rotation, Z);
$_->set_rotation(0) for @{ $model_object->instances }; $_->set_rotation(0) for @{ $model_object->instances };
} }
@ -788,7 +822,7 @@ sub changescale {
# apply Z rotation before scaling # apply Z rotation before scaling
if ($model_instance->rotation != 0) { if ($model_instance->rotation != 0) {
$model_object->rotate(deg2rad($model_instance->rotation), Z); $model_object->rotate($model_instance->rotation, Z);
$_->set_rotation(0) for @{ $model_object->instances }; $_->set_rotation(0) for @{ $model_object->instances };
} }
@ -1054,9 +1088,10 @@ sub export_gcode {
$dlg->Destroy; $dlg->Destroy;
return; return;
} }
$Slic3r::GUI::Settings->{_}{last_output_path} = dirname($dlg->GetPath); my $path = Slic3r::decode_path($dlg->GetPath);
$Slic3r::GUI::Settings->{_}{last_output_path} = dirname($path);
wxTheApp->save_settings; wxTheApp->save_settings;
$self->{export_gcode_output_file} = $Slic3r::GUI::MainFrame::last_output_file = $dlg->GetPath; $self->{export_gcode_output_file} = $Slic3r::GUI::MainFrame::last_output_file = $path;
$dlg->Destroy; $dlg->Destroy;
} }
@ -1066,6 +1101,11 @@ sub export_gcode {
$self->statusbar->SetCancelCallback(sub { $self->statusbar->SetCancelCallback(sub {
$self->stop_background_process; $self->stop_background_process;
$self->statusbar->SetStatusText("Export cancelled"); $self->statusbar->SetStatusText("Export cancelled");
$self->{export_gcode_output_file} = undef;
$self->{send_gcode_file} = undef;
# this updates buttons status
$self->object_list_changed;
}); });
# start background process, whose completion event handler # start background process, whose completion event handler
@ -1275,7 +1315,7 @@ sub _get_export_file {
$dlg->Destroy; $dlg->Destroy;
return undef; return undef;
} }
$output_file = $Slic3r::GUI::MainFrame::last_output_file = $dlg->GetPath; $output_file = $Slic3r::GUI::MainFrame::last_output_file = Slic3r::decode_path($dlg->GetPath);
$dlg->Destroy; $dlg->Destroy;
} }
return $output_file; return $output_file;
@ -1344,11 +1384,10 @@ sub on_extruders_change {
my $choices = $self->{preset_choosers}{filament}; my $choices = $self->{preset_choosers}{filament};
while (@$choices < $num_extruders) { while (@$choices < $num_extruders) {
my @presets = $choices->[0]->GetStrings; my @presets = $choices->[0]->GetStrings;
push @$choices, Wx::Choice->new($self, -1, wxDefaultPosition, [150, -1], [@presets]); push @$choices, Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [@presets], wxCB_READONLY);
$choices->[-1]->SetFont($Slic3r::GUI::small_font);
$self->{presets_sizer}->Insert(4 + ($#$choices-1)*2, 0, 0); $self->{presets_sizer}->Insert(4 + ($#$choices-1)*2, 0, 0);
$self->{presets_sizer}->Insert(5 + ($#$choices-1)*2, $choices->[-1], 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING); $self->{presets_sizer}->Insert(5 + ($#$choices-1)*2, $choices->[-1], 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING);
EVT_CHOICE($choices->[-1], $choices->[-1], sub { $self->_on_select_preset('filament', @_) }); EVT_COMBOBOX($choices->[-1], $choices->[-1], sub { $self->_on_select_preset('filament', @_) });
my $i = first { $choices->[-1]->GetString($_) eq ($Slic3r::GUI::Settings->{presets}{"filament_" . $#$choices} || '') } 0 .. $#presets; my $i = first { $choices->[-1]->GetString($_) eq ($Slic3r::GUI::Settings->{presets}{"filament_" . $#$choices} || '') } 0 .. $#presets;
$choices->[-1]->SetSelection($i || 0); $choices->[-1]->SetSelection($i || 0);
} }
@ -1616,26 +1655,27 @@ sub object_menu {
my $menu = Wx::Menu->new; my $menu = Wx::Menu->new;
$frame->_append_menu_item($menu, "Delete\tCtrl+Del", 'Remove the selected object', sub { $frame->_append_menu_item($menu, "Delete\tCtrl+Del", 'Remove the selected object', sub {
$self->remove; $self->remove;
}); }, undef, 'brick_delete.png');
$frame->_append_menu_item($menu, "Increase copies\tCtrl++", 'Place one more copy of the selected object', sub { $frame->_append_menu_item($menu, "Increase copies\tCtrl++", 'Place one more copy of the selected object', sub {
$self->increase; $self->increase;
}); }, undef, 'add.png');
$frame->_append_menu_item($menu, "Decrease copies\tCtrl+-", 'Remove one copy of the selected object', sub { $frame->_append_menu_item($menu, "Decrease copies\tCtrl+-", 'Remove one copy of the selected object', sub {
$self->decrease; $self->decrease;
}); }, undef, 'delete.png');
$frame->_append_menu_item($menu, "Set number of copies…", 'Change the number of copies of the selected object', sub { $frame->_append_menu_item($menu, "Set number of copies…", 'Change the number of copies of the selected object', sub {
$self->set_number_of_copies; $self->set_number_of_copies;
}); }, undef, 'textfield.png');
$menu->AppendSeparator(); $menu->AppendSeparator();
$frame->_append_menu_item($menu, "Rotate 45° clockwise", 'Rotate the selected object by 45° clockwise', sub { $frame->_append_menu_item($menu, "Rotate 45° clockwise", 'Rotate the selected object by 45° clockwise', sub {
$self->rotate(-45); $self->rotate(-45);
}); }, undef, 'arrow_rotate_clockwise.png');
$frame->_append_menu_item($menu, "Rotate 45° counter-clockwise", 'Rotate the selected object by 45° counter-clockwise', sub { $frame->_append_menu_item($menu, "Rotate 45° counter-clockwise", 'Rotate the selected object by 45° counter-clockwise', sub {
$self->rotate(+45); $self->rotate(+45);
}); }, undef, 'arrow_rotate_anticlockwise.png');
my $rotateMenu = Wx::Menu->new; my $rotateMenu = Wx::Menu->new;
$menu->AppendSubMenu($rotateMenu, "Rotate", 'Rotate the selected object by an arbitrary angle'); my $rotateMenuItem = $menu->AppendSubMenu($rotateMenu, "Rotate", 'Rotate the selected object by an arbitrary angle');
$frame->_set_menu_item_icon($rotateMenuItem, 'textfield.png');
$frame->_append_menu_item($rotateMenu, "Around X axis…", 'Rotate the selected object by an arbitrary angle around X axis', sub { $frame->_append_menu_item($rotateMenu, "Around X axis…", 'Rotate the selected object by an arbitrary angle around X axis', sub {
$self->rotate(undef, X); $self->rotate(undef, X);
}); });
@ -1647,7 +1687,8 @@ sub object_menu {
}); });
my $flipMenu = Wx::Menu->new; my $flipMenu = Wx::Menu->new;
$menu->AppendSubMenu($flipMenu, "Flip", 'Mirror the selected object'); my $flipMenuItem = $menu->AppendSubMenu($flipMenu, "Flip", 'Mirror the selected object');
$frame->_set_menu_item_icon($flipMenuItem, 'shape_flip_horizontal.png');
$frame->_append_menu_item($flipMenu, "Along X axis…", 'Mirror the selected object along the X axis', sub { $frame->_append_menu_item($flipMenu, "Along X axis…", 'Mirror the selected object along the X axis', sub {
$self->flip(X); $self->flip(X);
}); });
@ -1659,7 +1700,8 @@ sub object_menu {
}); });
my $scaleMenu = Wx::Menu->new; my $scaleMenu = Wx::Menu->new;
$menu->AppendSubMenu($scaleMenu, "Scale", 'Scale the selected object along a single axis'); my $scaleMenuItem = $menu->AppendSubMenu($scaleMenu, "Scale", 'Scale the selected object along a single axis');
$frame->_set_menu_item_icon($scaleMenuItem, 'arrow_out.png');
$frame->_append_menu_item($scaleMenu, "Uniformly…", 'Scale the selected object along the XYZ axes', sub { $frame->_append_menu_item($scaleMenu, "Uniformly…", 'Scale the selected object along the XYZ axes', sub {
$self->changescale(undef); $self->changescale(undef);
}); });
@ -1675,18 +1717,18 @@ sub object_menu {
$frame->_append_menu_item($menu, "Split", 'Split the selected object into individual parts', sub { $frame->_append_menu_item($menu, "Split", 'Split the selected object into individual parts', sub {
$self->split_object; $self->split_object;
}); }, undef, 'shape_ungroup.png');
$frame->_append_menu_item($menu, "Cut…", 'Open the 3D cutting tool', sub { $frame->_append_menu_item($menu, "Cut…", 'Open the 3D cutting tool', sub {
$self->object_cut_dialog; $self->object_cut_dialog;
}); }, undef, 'package.png');
$menu->AppendSeparator(); $menu->AppendSeparator();
$frame->_append_menu_item($menu, "Settings…", 'Open the object editor dialog', sub { $frame->_append_menu_item($menu, "Settings…", 'Open the object editor dialog', sub {
$self->object_settings_dialog; $self->object_settings_dialog;
}); }, undef, 'cog.png');
$menu->AppendSeparator(); $menu->AppendSeparator();
$frame->_append_menu_item($menu, "Export object as STL…", 'Export this single object as STL file', sub { $frame->_append_menu_item($menu, "Export object as STL…", 'Export this single object as STL file', sub {
$self->export_object_stl; $self->export_object_stl;
}); }, undef, 'brick_go.png');
return $menu; return $menu;
} }
@ -1763,7 +1805,7 @@ sub transform_thumbnail {
# the order of these transformations MUST be the same everywhere, including # the order of these transformations MUST be the same everywhere, including
# in Slic3r::Print->add_model_object() # in Slic3r::Print->add_model_object()
my $t = $self->thumbnail->clone; my $t = $self->thumbnail->clone;
$t->rotate(deg2rad($model_instance->rotation), Slic3r::Point->new(0,0)); $t->rotate($model_instance->rotation, Slic3r::Point->new(0,0));
$t->scale($model_instance->scaling_factor); $t->scale($model_instance->scaling_factor);
$self->transformed_thumbnail($t); $self->transformed_thumbnail($t);

View File

@ -84,6 +84,7 @@ sub reload_print {
} }
$self->{canvas}->bb($self->print->total_bounding_box); $self->{canvas}->bb($self->print->total_bounding_box);
$self->{canvas}->_dirty(1);
my %z = (); # z => 1 my %z = (); # z => 1
foreach my $object (@{$self->{print}->objects}) { foreach my $object (@{$self->{print}->objects}) {
@ -115,15 +116,23 @@ sub set_z {
package Slic3r::GUI::Plater::2DToolpaths::Canvas; package Slic3r::GUI::Plater::2DToolpaths::Canvas;
use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_MOUSEWHEEL EVT_MOUSE_EVENTS); use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS);
use OpenGL qw(:glconstants :glfunctions :glufunctions :gluconstants); use OpenGL qw(:glconstants :glfunctions :glufunctions :gluconstants);
use base qw(Wx::GLCanvas Class::Accessor); use base qw(Wx::GLCanvas Class::Accessor);
use Wx::GLCanvas qw(:all); use Wx::GLCanvas qw(:all);
use List::Util qw(min first); use List::Util qw(min max first);
use Slic3r::Geometry qw(scale unscale epsilon); use Slic3r::Geometry qw(scale unscale epsilon X Y);
use Slic3r::Print::State ':steps'; use Slic3r::Print::State ':steps';
__PACKAGE__->mk_accessors(qw(print z layers color init bb)); __PACKAGE__->mk_accessors(qw(
print z layers color init
bb
_camera_bb
_dirty
_zoom
_camera_target
_drag_start_xy
));
# make OpenGL::Array thread-safe # make OpenGL::Array thread-safe
{ {
@ -136,20 +145,99 @@ sub new {
my $self = $class->SUPER::new($parent); my $self = $class->SUPER::new($parent);
$self->print($print); $self->print($print);
$self->_zoom(1);
# 2D point in model space
$self->_camera_target(Slic3r::Pointf->new(0,0));
EVT_PAINT($self, sub { EVT_PAINT($self, sub {
my $dc = Wx::PaintDC->new($self); my $dc = Wx::PaintDC->new($self);
$self->Render($dc); $self->Render($dc);
}); });
EVT_SIZE($self, sub { EVT_SIZE($self, sub { $self->_dirty(1) });
EVT_IDLE($self, sub {
return unless $self->_dirty;
return if !$self->IsShownOnScreen; return if !$self->IsShownOnScreen;
$self->Resize( $self->GetSizeWH ); $self->Resize;
$self->Refresh; $self->Refresh;
}); });
EVT_MOUSEWHEEL($self, sub {
my ($self, $e) = @_;
my $old_zoom = $self->_zoom;
# Calculate the zoom delta and apply it to the current zoom factor
my $zoom = $e->GetWheelRotation() / $e->GetWheelDelta();
$zoom = max(min($zoom, 4), -4);
$zoom /= 10;
$self->_zoom($self->_zoom / (1-$zoom));
$self->_zoom(1) if $self->_zoom > 1; # prevent from zooming out too much
{
# In order to zoom around the mouse point we need to translate
# the camera target. This math is almost there but not perfect yet...
my $camera_bb_size = $self->_camera_bb->size;
my $size = Slic3r::Pointf->new($self->GetSizeWH);
my $pos = Slic3r::Pointf->new($e->GetPositionXY);
# calculate the zooming center in pixel coordinates relative to the viewport center
my $vec = Slic3r::Pointf->new($pos->x - $size->x/2, $pos->y - $size->y/2); #-
# calculate where this point will end up after applying the new zoom
my $vec2 = $vec->clone;
$vec2->scale($old_zoom / $self->_zoom);
# move the camera target by the difference of the two positions
$self->_camera_target->translate(
-($vec->x - $vec2->x) * $camera_bb_size->x / $size->x,
($vec->y - $vec2->y) * $camera_bb_size->y / $size->y, #//
);
}
$self->_dirty(1);
$self->Refresh;
});
EVT_MOUSE_EVENTS($self, \&mouse_event);
return $self; return $self;
} }
sub mouse_event {
my ($self, $e) = @_;
my $pos = Slic3r::Pointf->new($e->GetPositionXY);
if ($e->Entering && &Wx::wxMSW) {
# wxMSW needs focus in order to catch mouse wheel events
$self->SetFocus;
} elsif ($e->Dragging) {
if ($e->LeftIsDown || $e->MiddleIsDown || $e->RightIsDown) {
# if dragging, translate view
if (defined $self->_drag_start_xy) {
my $move = $self->_drag_start_xy->vector_to($pos); # in pixels
# get viewport and camera size in order to convert pixel to model units
my ($x, $y) = $self->GetSizeWH;
my $camera_bb_size = $self->_camera_bb->size;
# compute translation in model units
$self->_camera_target->translate(
-$move->x * $camera_bb_size->x / $x,
$move->y * $camera_bb_size->y / $y, # /**
);
$self->_dirty(1);
$self->Refresh;
}
$self->_drag_start_xy($pos);
}
} elsif ($e->LeftUp || $e->MiddleUp || $e->RightUp) {
$self->_drag_start_xy(undef);
} else {
$e->Skip();
}
}
sub set_z { sub set_z {
my ($self, $z) = @_; my ($self, $z) = @_;
@ -197,22 +285,6 @@ sub Render {
return; return;
} }
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
my $bb = $self->bb;
my ($x1, $y1, $x2, $y2) = ($bb->x_min, $bb->y_min, $bb->x_max, $bb->y_max);
my ($x, $y) = $self->GetSizeWH;
if (($x2 - $x1)/($y2 - $y1) > $x/$y) {
# adjust Y
my $new_y = $y * ($x2 - $x1) / $x;
$y1 = ($y2 + $y1)/2 - $new_y/2;
$y2 = $y1 + $new_y;
} else {
my $new_x = $x * ($y2 - $y1) / $y;
$x1 = ($x2 + $x1)/2 - $new_x/2;
$x2 = $x1 + $new_x;
}
glOrtho($x1, $x2, $y1, $y2, 0, 1);
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);
glMatrixMode(GL_MODELVIEW); glMatrixMode(GL_MODELVIEW);
glLoadIdentity(); glLoadIdentity();
@ -398,12 +470,71 @@ sub SetCurrent {
} }
sub Resize { sub Resize {
my ($self, $x, $y) = @_; my ($self) = @_;
return unless $self->GetContext; return unless $self->GetContext;
return unless $self->bb;
$self->_dirty(0);
$self->SetCurrent($self->GetContext); $self->SetCurrent($self->GetContext);
my ($x, $y) = $self->GetSizeWH;
glViewport(0, 0, $x, $y); glViewport(0, 0, $x, $y);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
my $bb = $self->bb->clone;
# center bounding box around origin before scaling it
my $bb_center = $bb->center;
$bb->translate(@{$bb_center->negative});
# scale bounding box according to zoom factor
$bb->scale($self->_zoom);
# reposition bounding box around original center
$bb->translate(@{$bb_center});
# translate camera
$bb->translate(@{$self->_camera_target});
# keep camera_bb within total bb
# (i.e. prevent user from panning outside the bounding box)
{
my @translate = (0,0);
if ($bb->x_min < $self->bb->x_min) {
$translate[X] += $self->bb->x_min - $bb->x_min;
}
if ($bb->y_min < $self->bb->y_min) {
$translate[Y] += $self->bb->y_min - $bb->y_min;
}
if ($bb->x_max > $self->bb->x_max) {
$translate[X] -= $bb->x_max - $self->bb->x_max;
}
if ($bb->y_max > $self->bb->y_max) {
$translate[Y] -= $bb->y_max - $self->bb->y_max;
}
$self->_camera_target->translate(@translate);
$bb->translate(@translate);
}
# save camera
$self->_camera_bb($bb);
my ($x1, $y1, $x2, $y2) = ($bb->x_min, $bb->y_min, $bb->x_max, $bb->y_max);
if (($x2 - $x1)/($y2 - $y1) > $x/$y) {
# adjust Y
my $new_y = $y * ($x2 - $x1) / $x;
$y1 = ($y2 + $y1)/2 - $new_y/2;
$y2 = $y1 + $new_y;
} else {
my $new_x = $x * ($y2 - $y1) / $y;
$x1 = ($x2 + $x1)/2 - $new_x/2;
$x2 = $x1 + $new_x;
}
glOrtho($x1, $x2, $y1, $y2, 0, 1);
glMatrixMode(GL_MODELVIEW);
} }
sub line { sub line {

View File

@ -116,6 +116,9 @@ sub load_print {
} }
if ($self->IsShown) { if ($self->IsShown) {
# load skirt and brim
$self->canvas->load_print_toolpaths($self->print);
foreach my $object (@{$self->print->objects}) { foreach my $object (@{$self->print->objects}) {
$self->canvas->load_print_object_toolpaths($object); $self->canvas->load_print_object_toolpaths($object);

View File

@ -161,6 +161,7 @@ sub perform_cut {
push @{$self->{new_model_objects}}, $lower_object; push @{$self->{new_model_objects}}, $lower_object;
if ($self->{cut_options}{rotate_lower}) { if ($self->{cut_options}{rotate_lower}) {
$lower_object->rotate(PI, X); $lower_object->rotate(PI, X);
$lower_object->center_around_origin; # align to Z = 0
} }
} }

View File

@ -99,6 +99,7 @@ sub new {
}); });
EVT_TREE_SEL_CHANGED($self, $tree, sub { EVT_TREE_SEL_CHANGED($self, $tree, sub {
my ($self, $event) = @_; my ($self, $event) = @_;
return if $self->{disable_tree_sel_changed_event};
$self->selection_changed; $self->selection_changed;
}); });
EVT_BUTTON($self, $self->{btn_load_part}, sub { $self->on_btn_load(0) }); EVT_BUTTON($self, $self->{btn_load_part}, sub { $self->on_btn_load(0) });
@ -118,7 +119,14 @@ sub reload_tree {
my $tree = $self->{tree}; my $tree = $self->{tree};
my $rootId = $tree->GetRootItem; my $rootId = $tree->GetRootItem;
# despite wxWidgets states that DeleteChildren "will not generate any events unlike Delete() method",
# the MSW implementation of DeleteChildren actually calls Delete() for each item, so
# EVT_TREE_SEL_CHANGED is being called, with bad effects (the event handler is called; this
# subroutine is never continued; an invisible EndModal is called on the dialog causing Plater
# to continue its logic and rescheduling the background process etc. GH #2774)
$self->{disable_tree_sel_changed_event} = 1;
$tree->DeleteChildren($rootId); $tree->DeleteChildren($rootId);
$self->{disable_tree_sel_changed_event} = 0;
my $selectedId = $rootId; my $selectedId = $rootId;
foreach my $volume_id (0..$#{$object->volumes}) { foreach my $volume_id (0..$#{$object->volumes}) {
@ -136,9 +144,13 @@ sub reload_tree {
} }
$tree->ExpandAll; $tree->ExpandAll;
# This will trigger the selection_changed() event
Slic3r::GUI->CallAfter(sub { Slic3r::GUI->CallAfter(sub {
$self->{tree}->SelectItem($selectedId); $self->{tree}->SelectItem($selectedId);
# SelectItem() should trigger EVT_TREE_SEL_CHANGED as per wxWidgets docs,
# but in fact it doesn't if the given item is already selected (this happens
# on first load)
$self->selection_changed;
}); });
} }

View File

@ -177,7 +177,7 @@ sub _update {}
sub _on_presets_changed { sub _on_presets_changed {
my $self = shift; my $self = shift;
$self->{on_presets_changed}->([$self->{presets_choice}->GetStrings], $self->{presets_choice}->GetSelection) $self->{on_presets_changed}->($self->{presets}, $self->{presets_choice}->GetSelection)
if $self->{on_presets_changed}; if $self->{on_presets_changed};
} }
@ -360,10 +360,10 @@ sub load_presets {
$self->current_preset(undef); $self->current_preset(undef);
$self->{presets_choice}->Clear; $self->{presets_choice}->Clear;
$self->{presets_choice}->Append($_->{name}) for @{$self->{presets}}; $self->{presets_choice}->Append($_->name) for @{$self->{presets}};
{ {
# load last used preset # load last used preset
my $i = first { basename($self->{presets}[$_]{file}) eq ($Slic3r::GUI::Settings->{presets}{$self->name} || '') } 1 .. $#{$self->{presets}}; my $i = first { basename($self->{presets}[$_]->file) eq ($Slic3r::GUI::Settings->{presets}{$self->name} || '') } 1 .. $#{$self->{presets}};
$self->select_preset($i || 0); $self->select_preset($i || 0);
} }
$self->_on_presets_changed; $self->_on_presets_changed;
@ -813,7 +813,7 @@ sub build {
my $self = shift; my $self = shift;
$self->init_config_options(qw( $self->init_config_options(qw(
filament_diameter extrusion_multiplier filament_colour filament_diameter extrusion_multiplier
temperature first_layer_temperature bed_temperature first_layer_bed_temperature temperature first_layer_temperature bed_temperature first_layer_bed_temperature
fan_always_on cooling fan_always_on cooling
min_fan_speed max_fan_speed bridge_fan_speed disable_fan_first_layers min_fan_speed max_fan_speed bridge_fan_speed disable_fan_first_layers
@ -824,6 +824,7 @@ sub build {
my $page = $self->add_options_page('Filament', 'spool.png'); my $page = $self->add_options_page('Filament', 'spool.png');
{ {
my $optgroup = $page->new_optgroup('Filament'); my $optgroup = $page->new_optgroup('Filament');
$optgroup->append_single_option_line('filament_colour', 0);
$optgroup->append_single_option_line('filament_diameter', 0); $optgroup->append_single_option_line('filament_diameter', 0);
$optgroup->append_single_option_line('extrusion_multiplier', 0); $optgroup->append_single_option_line('extrusion_multiplier', 0);
} }

View File

@ -27,9 +27,6 @@ our @EXPORT_OK = qw(
use constant PI => 4 * atan2(1, 1); use constant PI => 4 * atan2(1, 1);
use constant A => 0; use constant A => 0;
use constant B => 1; use constant B => 1;
use constant X => 0;
use constant Y => 1;
use constant Z => 2;
use constant X1 => 0; use constant X1 => 0;
use constant Y1 => 1; use constant Y1 => 1;
use constant X2 => 2; use constant X2 => 2;
@ -605,119 +602,4 @@ sub douglas_peucker2 {
return [ map $points->[$_], sort keys %keep ]; return [ map $points->[$_], sort keys %keep ];
} }
sub arrange {
my ($total_parts, $partx, $party, $dist, $bb) = @_;
my $linint = sub {
my ($value, $oldmin, $oldmax, $newmin, $newmax) = @_;
return ($value - $oldmin) * ($newmax - $newmin) / ($oldmax - $oldmin) + $newmin;
};
# use actual part size (the largest) plus separation distance (half on each side) in spacing algorithm
$partx += $dist;
$party += $dist;
my ($areax, $areay);
if (defined $bb) {
my $size = $bb->size;
($areax, $areay) = @$size[X,Y];
} else {
# bogus area size, large enough not to trigger the error below
$areax = $partx * $total_parts;
$areay = $party * $total_parts;
}
# this is how many cells we have available into which to put parts
my $cellw = int(($areax + $dist) / $partx);
my $cellh = int(($areay + $dist) / $party);
die "$total_parts parts won't fit in your print area!\n" if $total_parts > ($cellw * $cellh);
# width and height of space used by cells
my $w = $cellw * $partx;
my $h = $cellh * $party;
# left and right border positions of space used by cells
my $l = ($areax - $w) / 2;
my $r = $l + $w;
# top and bottom border positions
my $t = ($areay - $h) / 2;
my $b = $t + $h;
# list of cells, sorted by distance from center
my @cellsorder;
# work out distance for all cells, sort into list
for my $i (0..$cellw-1) {
for my $j (0..$cellh-1) {
my $cx = $linint->($i + 0.5, 0, $cellw, $l, $r);
my $cy = $linint->($j + 0.5, 0, $cellh, $t, $b);
my $xd = abs(($areax / 2) - $cx);
my $yd = abs(($areay / 2) - $cy);
my $c = {
location => [$cx, $cy],
index => [$i, $j],
distance => $xd * $xd + $yd * $yd - abs(($cellw / 2) - ($i + 0.5)),
};
BINARYINSERTIONSORT: {
my $index = $c->{distance};
my $low = 0;
my $high = @cellsorder;
while ($low < $high) {
my $mid = ($low + (($high - $low) / 2)) | 0;
my $midval = $cellsorder[$mid]->[0];
if ($midval < $index) {
$low = $mid + 1;
} elsif ($midval > $index) {
$high = $mid;
} else {
splice @cellsorder, $mid, 0, [$index, $c];
last BINARYINSERTIONSORT;
}
}
splice @cellsorder, $low, 0, [$index, $c];
}
}
}
# the extents of cells actually used by objects
my ($lx, $ty, $rx, $by) = (0, 0, 0, 0);
# now find cells actually used by objects, map out the extents so we can position correctly
for my $i (1..$total_parts) {
my $c = $cellsorder[$i - 1];
my $cx = $c->[1]->{index}->[0];
my $cy = $c->[1]->{index}->[1];
if ($i == 1) {
$lx = $rx = $cx;
$ty = $by = $cy;
} else {
$rx = $cx if $cx > $rx;
$lx = $cx if $cx < $lx;
$by = $cy if $cy > $by;
$ty = $cy if $cy < $ty;
}
}
# now we actually place objects into cells, positioned such that the left and bottom borders are at 0
my @positions = ();
for (1..$total_parts) {
my $c = shift @cellsorder;
my $cx = $c->[1]->{index}->[0] - $lx;
my $cy = $c->[1]->{index}->[1] - $ty;
push @positions, [$cx * $partx, $cy * $party];
}
if (defined $bb) {
$_->[X] += $bb->x_min for @positions;
$_->[Y] += $bb->y_min for @positions;
}
return @positions;
}
1; 1;

View File

@ -32,11 +32,6 @@ sub regions {
return [ map $self->get_region($_), 0..($self->region_count-1) ]; return [ map $self->get_region($_), 0..($self->region_count-1) ];
} }
sub merge_slices {
my ($self) = @_;
$_->merge_slices for @{$self->regions};
}
sub make_perimeters { sub make_perimeters {
my $self = shift; my $self = shift;
Slic3r::debugf "Making perimeters for layer %d\n", $self->id; Slic3r::debugf "Making perimeters for layer %d\n", $self->id;

View File

@ -95,7 +95,7 @@ sub process {
my $loop_number = $self->config->perimeters + ($surface->extra_perimeters || 0); my $loop_number = $self->config->perimeters + ($surface->extra_perimeters || 0);
$loop_number--; # 0-indexed loops $loop_number--; # 0-indexed loops
my @gaps = (); # ExPolygons my @gaps = (); # Polygons
my @last = @{$surface->expolygon->simplify_p(&Slic3r::SCALED_RESOLUTION)}; my @last = @{$surface->expolygon->simplify_p(&Slic3r::SCALED_RESOLUTION)};
if ($loop_number >= 0) { # no loops = -1 if ($loop_number >= 0) { # no loops = -1
@ -133,12 +133,12 @@ sub process {
# the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width # the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
# (actually, something larger than that still may exist due to mitering or other causes) # (actually, something larger than that still may exist due to mitering or other causes)
my $min_width = $pwidth / 4; my $min_width = $ext_pwidth / 4;
@thin_walls = @{offset2_ex($diff, -$min_width/2, +$min_width/2)}; @thin_walls = @{offset2_ex($diff, -$min_width/2, +$min_width/2)};
# the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop # the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
@thin_walls = grep $_->length > $pwidth*2, @thin_walls = grep $_->length > $ext_pwidth*2,
map @{$_->medial_axis($pwidth + $pspacing, $min_width)}, @thin_walls; map @{$_->medial_axis($ext_pwidth + $ext_pspacing, $min_width)}, @thin_walls;
Slic3r::debugf " %d thin walls detected\n", scalar(@thin_walls) if $Slic3r::debug; Slic3r::debugf " %d thin walls detected\n", scalar(@thin_walls) if $Slic3r::debug;
if (0) { if (0) {
@ -174,10 +174,10 @@ sub process {
# (but still long enough to escape the area threshold) that gap fill # (but still long enough to escape the area threshold) that gap fill
# won't be able to fill but we'd still remove from infill area # won't be able to fill but we'd still remove from infill area
my $diff = diff_ex( my $diff = diff_ex(
offset(\@last, -0.5*$pspacing), offset(\@last, -0.5*$distance),
offset(\@offsets, +0.5*$pspacing + 10), # safety offset offset(\@offsets, +0.5*$distance + 10), # safety offset
); );
push @gaps, grep abs($_->area) >= $gap_area_threshold, @$diff; push @gaps, map $_->clone, map @$_, grep abs($_->area) >= $gap_area_threshold, @$diff;
} }
} }
@ -278,20 +278,20 @@ sub process {
require "Slic3r/SVG.pm"; require "Slic3r/SVG.pm";
Slic3r::SVG::output( Slic3r::SVG::output(
"gaps.svg", "gaps.svg",
expolygons => \@gaps, expolygons => union_ex(\@gaps),
); );
} }
# where $pwidth < thickness < 2*$pspacing, infill with width = 1.5*$pwidth # where $pwidth < thickness < 2*$pspacing, infill with width = 2*$pwidth
# where 0.5*$pwidth < thickness < $pwidth, infill with width = 0.5*$pwidth # where 0.1*$pwidth < thickness < $pwidth, infill with width = 1*$pwidth
my @gap_sizes = ( my @gap_sizes = (
[ $pwidth, 2*$pspacing, unscale 1.5*$pwidth ], [ $pwidth, 2*$pspacing, unscale 2*$pwidth ],
[ 0.1*$pwidth, $pwidth, unscale 0.5*$pwidth ], [ 0.1*$pwidth, $pwidth, unscale 1*$pwidth ],
); );
foreach my $gap_size (@gap_sizes) { foreach my $gap_size (@gap_sizes) {
my @gap_fill = $self->_fill_gaps(@$gap_size, \@gaps); my @gap_fill = $self->_fill_gaps(@$gap_size, \@gaps);
$self->gap_fill->append($_) for @gap_fill; $self->gap_fill->append($_) for @gap_fill;
# Make sure we don't infill narrow parts that are already gap-filled # Make sure we don't infill narrow parts that are already gap-filled
# (we only consider this surface's gaps to reduce the diff() complexity). # (we only consider this surface's gaps to reduce the diff() complexity).
# Growing actual extrusions ensures that gaps not filled by medial axis # Growing actual extrusions ensures that gaps not filled by medial axis
@ -304,6 +304,7 @@ sub process {
->grow(scale $w/2)}; ->grow(scale $w/2)};
} @gap_fill; } @gap_fill;
@last = @{diff(\@last, \@filled)}; @last = @{diff(\@last, \@filled)};
@gaps = @{diff(\@gaps, \@filled)}; # prevent more gap fill here
} }
} }
@ -454,8 +455,8 @@ sub _fill_gaps {
$min *= (1 - &Slic3r::INSET_OVERLAP_TOLERANCE); $min *= (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
my $this = diff_ex( my $this = diff_ex(
offset2([ map @$_, @$gaps ], -$min/2, +$min/2), offset2($gaps, -$min/2, +$min/2),
offset2([ map @$_, @$gaps ], -$max/2, +$max/2), offset2($gaps, -$max/2, +$max/2),
1, 1,
); );

View File

@ -150,15 +150,19 @@ sub duplicate {
sub _arrange { sub _arrange {
my ($self, $sizes, $distance, $bb) = @_; my ($self, $sizes, $distance, $bb) = @_;
$bb //= Slic3r::Geometry::BoundingBoxf->new;
# we supply unscaled data to arrange() # we supply unscaled data to arrange()
return Slic3r::Geometry::arrange( return @{Slic3r::Geometry::arrange(
scalar(@$sizes), # number of parts scalar(@$sizes), # number of parts
max(map $_->x, @$sizes), # cell width Slic3r::Pointf->new(
max(map $_->y, @$sizes), # cell height , max(map $_->x, @$sizes), # cell width
max(map $_->y, @$sizes), # cell height ,
),
$distance, # distance between cells $distance, # distance between cells
$bb, # bounding box of the area to fill (can be undef) $bb, # bounding box of the area to fill (can be undef)
); )};
} }
sub print_info { sub print_info {
@ -262,37 +266,6 @@ sub add_instance {
} }
} }
sub rotate {
my ($self, $angle, $axis) = @_;
# we accept angle in radians but mesh currently uses degrees
$angle = rad2deg($angle);
if ($axis == X) {
$_->mesh->rotate_x($angle) for @{$self->volumes};
} elsif ($axis == Y) {
$_->mesh->rotate_y($angle) for @{$self->volumes};
} elsif ($axis == Z) {
$_->mesh->rotate_z($angle) for @{$self->volumes};
}
$self->set_origin_translation(Slic3r::Pointf3->new(0,0,0));
$self->invalidate_bounding_box;
}
sub flip {
my ($self, $axis) = @_;
if ($axis == X) {
$_->mesh->flip_x for @{$self->volumes};
} elsif ($axis == Y) {
$_->mesh->flip_y for @{$self->volumes};
} elsif ($axis == Z) {
$_->mesh->flip_z for @{$self->volumes};
}
$self->set_origin_translation(Slic3r::Pointf3->new(0,0,0));
$self->invalidate_bounding_box;
}
sub mesh_stats { sub mesh_stats {
my $self = shift; my $self = shift;

View File

@ -225,8 +225,8 @@ sub make_skirt {
my $skirt_height_z = -1; my $skirt_height_z = -1;
foreach my $object (@{$self->objects}) { foreach my $object (@{$self->objects}) {
my $skirt_height = $self->has_infinite_skirt my $skirt_height = $self->has_infinite_skirt
? scalar(@{$object->layers}) ? $object->layer_count
: min($self->config->skirt_height, scalar(@{$object->layers})); : min($self->config->skirt_height, $object->layer_count);
my $highest_layer = $object->get_layer($skirt_height - 1); my $highest_layer = $object->get_layer($skirt_height - 1);
$skirt_height_z = max($skirt_height_z, $highest_layer->print_z); $skirt_height_z = max($skirt_height_z, $highest_layer->print_z);
} }
@ -432,6 +432,11 @@ sub expanded_output_filepath {
$self->placeholder_parser->set(input_filename => $filename); $self->placeholder_parser->set(input_filename => $filename);
$self->placeholder_parser->set(input_filename_base => $filename_base); $self->placeholder_parser->set(input_filename_base => $filename_base);
# set other variables from model object
$self->placeholder_parser->set_multiple(
scale => [ map $_->model_object->instances->[0]->scaling_factor * 100 . "%", @{$self->objects} ],
);
if ($path && -d $path) { if ($path && -d $path) {
# if output path is an existing directory, we take that and append # if output path is an existing directory, we take that and append
# the specified filename format # the specified filename format

View File

@ -196,6 +196,7 @@ sub export {
if ($finished_objects > 0) { if ($finished_objects > 0) {
$gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y)); $gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y));
$gcodegen->enable_cooling_markers(0); # we're not filtering these moves through CoolingBuffer $gcodegen->enable_cooling_markers(0); # we're not filtering these moves through CoolingBuffer
$gcodegen->avoid_crossing_perimeters->use_external_mp_once(1);
print $fh $gcodegen->retract; print $fh $gcodegen->retract;
print $fh $gcodegen->travel_to( print $fh $gcodegen->travel_to(
Slic3r::Point->new(0,0), Slic3r::Point->new(0,0),
@ -203,6 +204,9 @@ sub export {
'move to origin position for next object', 'move to origin position for next object',
); );
$gcodegen->enable_cooling_markers(1); $gcodegen->enable_cooling_markers(1);
# disable motion planner when traveling to first object point
$gcodegen->avoid_crossing_perimeters->disable_once(1);
} }
my @layers = sort { $a->print_z <=> $b->print_z } @{$object->layers}, @{$object->support_layers}; my @layers = sort { $a->print_z <=> $b->print_z } @{$object->layers}, @{$object->support_layers};
@ -305,8 +309,8 @@ sub process_layer {
($layer->id > 0 || $self->print->config->brim_width == 0) ($layer->id > 0 || $self->print->config->brim_width == 0)
&& ($layer->id >= $self->print->config->skirt_height && !$self->print->has_infinite_skirt) && ($layer->id >= $self->print->config->skirt_height && !$self->print->has_infinite_skirt)
&& !defined(first { $_->config->bottom_solid_layers > $layer->id } @{$layer->regions}) && !defined(first { $_->config->bottom_solid_layers > $layer->id } @{$layer->regions})
&& !defined(first { @{$_->perimeters} > 1 } @{$layer->regions}) && !defined(first { $_->perimeters->items_count > 1 } @{$layer->regions})
&& !defined(first { @{$_->fills} > 0 } @{$layer->regions}) && !defined(first { $_->fills->items_count > 0 } @{$layer->regions})
); );
} }
@ -326,19 +330,20 @@ sub process_layer {
# set new layer - this will change Z and force a retraction if retract_layer_change is enabled # set new layer - this will change Z and force a retraction if retract_layer_change is enabled
$gcode .= $self->_gcodegen->placeholder_parser->process($self->print->config->before_layer_gcode, { $gcode .= $self->_gcodegen->placeholder_parser->process($self->print->config->before_layer_gcode, {
layer_num => $layer->id, layer_num => $self->_gcodegen->layer_index + 1,
layer_z => $layer->print_z, layer_z => $layer->print_z,
}) . "\n" if $self->print->config->before_layer_gcode; }) . "\n" if $self->print->config->before_layer_gcode;
$gcode .= $self->_gcodegen->change_layer($layer); $gcode .= $self->_gcodegen->change_layer($layer); # this will increase $self->_gcodegen->layer_index
$gcode .= $self->_gcodegen->placeholder_parser->process($self->print->config->layer_gcode, { $gcode .= $self->_gcodegen->placeholder_parser->process($self->print->config->layer_gcode, {
layer_num => $layer->id, layer_num => $self->_gcodegen->layer_index,
layer_z => $layer->print_z, layer_z => $layer->print_z,
}) . "\n" if $self->print->config->layer_gcode; }) . "\n" if $self->print->config->layer_gcode;
# extrude skirt # extrude skirt along raft layers and normal object layers
# (not along interlaced support material layers)
if (((values %{$self->_skirt_done}) < $self->print->config->skirt_height || $self->print->has_infinite_skirt) if (((values %{$self->_skirt_done}) < $self->print->config->skirt_height || $self->print->has_infinite_skirt)
&& !$self->_skirt_done->{$layer->print_z} && !$self->_skirt_done->{$layer->print_z}
&& !$layer->isa('Slic3r::Layer::Support')) { && (!$layer->isa('Slic3r::Layer::Support') || $layer->id < $object->config->raft_layers)) {
$self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0)); $self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0));
$self->_gcodegen->avoid_crossing_perimeters->use_external_mp(1); $self->_gcodegen->avoid_crossing_perimeters->use_external_mp(1);
my @extruder_ids = map { $_->id } @{$self->_gcodegen->writer->extruders}; my @extruder_ids = map { $_->id } @{$self->_gcodegen->writer->extruders};

View File

@ -57,16 +57,36 @@ sub slice {
# plus the extra distance required by the support material logic # plus the extra distance required by the support material logic
my $first_layer_height = $self->config->get_value('first_layer_height'); my $first_layer_height = $self->config->get_value('first_layer_height');
$print_z += $first_layer_height; $print_z += $first_layer_height;
$print_z += $self->config->layer_height * ($self->config->raft_layers - 1);
# use a large height
my $support_material_layer_height;
{
my @nozzle_diameters = (
map $self->print->config->get_at('nozzle_diameter', $_),
$self->config->support_material_extruder,
$self->config->support_material_interface_extruder,
);
$support_material_layer_height = 0.75 * min(@nozzle_diameters);
}
$print_z += $support_material_layer_height * ($self->config->raft_layers - 1);
# at this stage we don't know which nozzles are actually used for the first layer # compute the average of all nozzles used for printing the object
# so we compute the average of all of them my $nozzle_diameter;
my $nozzle_diameter = sum(@{$self->print->config->nozzle_diameter})/@{$self->print->config->nozzle_diameter}; {
my $distance = $self->_support_material->contact_distance($first_layer_height, $nozzle_diameter); my @nozzle_diameters = (
map $self->print->config->get_at('nozzle_diameter', $_), @{$self->print->object_extruders}
);
$nozzle_diameter = sum(@nozzle_diameters)/@nozzle_diameters;
}
my $distance = $self->_support_material->contact_distance($self->config->layer_height, $nozzle_diameter);
# force first layer print_z according to the contact distance # force first layer print_z according to the contact distance
# (the loop below will raise print_z by such height) # (the loop below will raise print_z by such height)
$first_object_layer_height = $nozzle_diameter; if ($self->config->support_material_contact_distance == 0) {
$first_object_layer_height = $distance;
} else {
$first_object_layer_height = $nozzle_diameter;
}
$first_object_layer_distance = $distance; $first_object_layer_distance = $distance;
} }

View File

@ -15,6 +15,7 @@ use POSIX qw(setlocale LC_NUMERIC);
use Slic3r; use Slic3r;
use Time::HiRes qw(gettimeofday tv_interval); use Time::HiRes qw(gettimeofday tv_interval);
$|++; $|++;
binmode STDOUT, ':utf8';
our %opt = (); our %opt = ();
my %cli_options = (); my %cli_options = ();
@ -93,7 +94,7 @@ my $gui;
if ((!@ARGV || $opt{gui}) && !$opt{save} && eval "require Slic3r::GUI; 1") { if ((!@ARGV || $opt{gui}) && !$opt{save} && eval "require Slic3r::GUI; 1") {
{ {
no warnings 'once'; no warnings 'once';
$Slic3r::GUI::datadir = Slic3r::decode_path($opt{datadir}); $Slic3r::GUI::datadir = Slic3r::decode_path($opt{datadir} // '');
$Slic3r::GUI::no_plater = $opt{no_plater}; $Slic3r::GUI::no_plater = $opt{no_plater};
$Slic3r::GUI::mode = $opt{gui_mode}; $Slic3r::GUI::mode = $opt{gui_mode};
$Slic3r::GUI::autosave = $opt{autosave}; $Slic3r::GUI::autosave = $opt{autosave};
@ -103,6 +104,7 @@ if ((!@ARGV || $opt{gui}) && !$opt{save} && eval "require Slic3r::GUI; 1") {
$gui->{mainframe}->load_config_file($_) for @{$opt{load}}; $gui->{mainframe}->load_config_file($_) for @{$opt{load}};
$gui->{mainframe}->load_config($cli_config); $gui->{mainframe}->load_config($cli_config);
foreach my $input_file (@ARGV) { foreach my $input_file (@ARGV) {
$input_file = Slic3r::decode_path($input_file);
$gui->{mainframe}{plater}->load_file($input_file) unless $opt{no_plater}; $gui->{mainframe}{plater}->load_file($input_file) unless $opt{no_plater};
} }
$gui->MainLoop; $gui->MainLoop;
@ -434,7 +436,7 @@ $j
Only retract before travel moves of this length in mm (default: $config->{retract_before_travel}[0]) Only retract before travel moves of this length in mm (default: $config->{retract_before_travel}[0])
--retract-lift Lift Z by the given distance in mm when retracting (default: $config->{retract_lift}[0]) --retract-lift Lift Z by the given distance in mm when retracting (default: $config->{retract_lift}[0])
--retract-layer-change --retract-layer-change
Enforce a retraction before each Z move (default: yes) Enforce a retraction before each Z move (default: no)
--wipe Wipe the nozzle while doing a retraction (default: no) --wipe Wipe the nozzle while doing a retraction (default: no)
Retraction options for multi-extruder setups: Retraction options for multi-extruder setups:

View File

@ -1,4 +1,4 @@
use Test::More tests => 13; use Test::More tests => 15;
use strict; use strict;
use warnings; use warnings;
@ -7,6 +7,7 @@ BEGIN {
use lib "$FindBin::Bin/../lib"; use lib "$FindBin::Bin/../lib";
} }
use List::Util qw(first);
use Slic3r; use Slic3r;
use Slic3r::Test; use Slic3r::Test;
@ -104,4 +105,29 @@ use Slic3r::Test;
} }
} }
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('before_layer_gcode', ';BEFORE [layer_num]');
$config->set('layer_gcode', ';CHANGE [layer_num]');
$config->set('support_material', 1);
$config->set('layer_height', 0.2);
my $print = Slic3r::Test::init_print('overhang', config => $config);
my $gcode = Slic3r::Test::gcode($print);
my @before = ();
my @change = ();
foreach my $line (split /\R+/, $gcode) {
if ($line =~ /;BEFORE (\d+)/) {
push @before, $1;
} elsif ($line =~ /;CHANGE (\d+)/) {
push @change, $1;
fail 'inconsistent layer_num before and after layer change'
if $1 != $before[-1];
}
}
is_deeply \@before, \@change, 'layer_num is consistent before and after layer changes';
ok !defined(first { $change[$_] != $change[$_-1]+1 } 1..$#change),
'layer_num grows continously'; # i.e. no duplicates or regressions
}
__END__ __END__

View File

@ -1,4 +1,4 @@
use Test::More tests => 22; use Test::More tests => 23;
use strict; use strict;
use warnings; use warnings;
@ -200,4 +200,21 @@ use Slic3r::Test;
like $gcode, qr/START:20mm_cube/, '[input_filename] is also available in custom G-code'; like $gcode, qr/START:20mm_cube/, '[input_filename] is also available in custom G-code';
} }
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('spiral_vase', 1);
my $print = Slic3r::Test::init_print('cube_with_hole', config => $config);
my $spiral = 0;
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
my ($self, $cmd, $args, $info) = @_;
if ($cmd eq 'G1' && exists $args->{E} && exists $args->{Z}) {
$spiral = 1;
}
});
ok !$spiral, 'spiral vase is correctly disabled on layers with multiple loops';
}
__END__ __END__

View File

@ -1,4 +1,4 @@
use Test::More tests => 25; use Test::More tests => 27;
use strict; use strict;
use warnings; use warnings;
@ -221,12 +221,41 @@ use Slic3r::Test;
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config->new_from_defaults;
$config->set('raft_layers', 3); $config->set('skirts', 0);
$config->set('start_gcode', '');
$config->set('raft_layers', 8);
$config->set('nozzle_diameter', [0.4, 1]); $config->set('nozzle_diameter', [0.4, 1]);
$config->set('layer_height', 0.1);
$config->set('first_layer_height', 0.8); $config->set('first_layer_height', 0.8);
$config->set('support_material_extruder', 2); $config->set('support_material_extruder', 2);
$config->set('support_material_interface_extruder', 2);
$config->set('support_material_contact_distance', 0);
my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
ok Slic3r::Test::gcode($print), 'first_layer_height is validated with support material extruder nozzle diameter when using raft layers'; ok my $gcode = Slic3r::Test::gcode($print), 'first_layer_height is validated with support material extruder nozzle diameter when using raft layers';
my $tool = undef;
my @z = (0);
my %layer_heights_by_tool = (); # tool => [ lh, lh... ]
Slic3r::GCode::Reader->new->parse($gcode, sub {
my ($self, $cmd, $args, $info) = @_;
if ($cmd =~ /^T(\d+)/) {
$tool = $1;
} elsif ($cmd eq 'G1' && exists $args->{Z} && $args->{Z} != $self->Z) {
push @z, $args->{Z};
} elsif ($info->{extruding} && $info->{dist_XY} > 0) {
$layer_heights_by_tool{$tool} ||= [];
push @{ $layer_heights_by_tool{$tool} }, $z[-1] - $z[-2];
}
});
ok !defined(first { $_ > $config->nozzle_diameter->[0] + epsilon }
@{ $layer_heights_by_tool{$config->perimeter_extruder-1} }),
'no object layer is thicker than nozzle diameter';
ok !defined(first { abs($_ - $config->layer_height) < epsilon }
@{ $layer_heights_by_tool{$config->support_material_extruder-1} }),
'no support material layer is as thin as object layers';
} }
__END__ __END__

View File

@ -1,4 +1,4 @@
use Test::More tests => 14; use Test::More tests => 21;
use strict; use strict;
use warnings; use warnings;
@ -133,4 +133,40 @@ if (0) {
ok sum(map $_->length, @$polylines) > $perimeter/2/4*3, 'medial axis has a reasonable length'; ok sum(map $_->length, @$polylines) > $perimeter/2/4*3, 'medial axis has a reasonable length';
} }
{
my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale(
[50, 100],
[300, 102],
[50, 104],
));
my $res = $expolygon->medial_axis(scale 4, scale 0.5);
is scalar(@$res), 1, 'medial axis of a narrow triangle is a single line';
ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length';
}
{
# GH #2474
my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new(
[91294454,31032190],[11294481,31032190],[11294481,29967810],[44969182,29967810],[89909960,29967808],[91294454,29967808]
));
my $polylines = $expolygon->medial_axis(1871238, 500000);
is scalar(@$polylines), 1, 'medial axis is a single polyline';
my $polyline = $polylines->[0];
my $expected_y = $expolygon->bounding_box->center->y; #;;
ok abs(sum(map $_->y, @$polyline) / @$polyline - $expected_y) < scaled_epsilon, #,,
'medial axis is horizontal and is centered';
# order polyline from left to right
$polyline->reverse if $polyline->first_point->x > $polyline->last_point->x;
my $polyline_bb = $polyline->bounding_box;
is $polyline->first_point->x, $polyline_bb->x_min, 'expected x_min';
is $polyline->last_point->x, $polyline_bb->x_max, 'expected x_max';
is_deeply [ map $_->x, @$polyline ], [ sort map $_->x, @$polyline ],
'medial axis is not self-overlapping';
}
__END__ __END__

View File

@ -33,7 +33,6 @@ my %opt = ();
# load config # load config
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config->new_from_defaults;
$config->set('skirts', 0);
if ($opt{load}) { if ($opt{load}) {
$config->apply(Slic3r::Config->load($opt{load})); $config->apply(Slic3r::Config->load($opt{load}));
} }
@ -84,6 +83,7 @@ sub OnInit {
if ($d3) { if ($d3) {
$canvas = Slic3r::GUI::3DScene->new($panel); $canvas = Slic3r::GUI::3DScene->new($panel);
$canvas->set_bed_shape($print->config->bed_shape); $canvas->set_bed_shape($print->config->bed_shape);
$canvas->load_print_toolpaths($print);
foreach my $object (@{$print->objects}) { foreach my $object (@{$print->objects}) {
#$canvas->load_print_object_slices($object); #$canvas->load_print_object_slices($object);

BIN
var/application_view_tile.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 B

BIN
var/lorry_add.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 B

BIN
var/lorry_go.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

BIN
var/plugin_add.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 B

BIN
var/plugin_go.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 B

BIN
var/shape_flip_horizontal.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 B

BIN
var/shape_handles.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

BIN
var/textfield.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 B

View File

@ -26,8 +26,14 @@ class ConfigOption {
virtual void setInt(int val) {}; virtual void setInt(int val) {};
}; };
class ConfigOptionVectorBase : public ConfigOption {
public:
virtual ~ConfigOptionVectorBase() {};
virtual std::vector<std::string> vserialize() const = 0;
};
template <class T> template <class T>
class ConfigOptionVector class ConfigOptionVector : public ConfigOptionVectorBase
{ {
public: public:
virtual ~ConfigOptionVector() {}; virtual ~ConfigOptionVector() {};
@ -63,7 +69,7 @@ class ConfigOptionFloat : public ConfigOption
}; };
}; };
class ConfigOptionFloats : public ConfigOption, public ConfigOptionVector<double> class ConfigOptionFloats : public ConfigOptionVector<double>
{ {
public: public:
@ -76,6 +82,16 @@ class ConfigOptionFloats : public ConfigOption, public ConfigOptionVector<double
return ss.str(); return ss.str();
}; };
std::vector<std::string> vserialize() const {
std::vector<std::string> vv;
for (std::vector<double>::const_iterator it = this->values.begin(); it != this->values.end(); ++it) {
std::ostringstream ss;
ss << *it;
vv.push_back(ss.str());
}
return vv;
};
bool deserialize(std::string str) { bool deserialize(std::string str) {
this->values.clear(); this->values.clear();
std::istringstream is(str); std::istringstream is(str);
@ -112,7 +128,7 @@ class ConfigOptionInt : public ConfigOption
}; };
}; };
class ConfigOptionInts : public ConfigOption, public ConfigOptionVector<int> class ConfigOptionInts : public ConfigOptionVector<int>
{ {
public: public:
@ -125,6 +141,16 @@ class ConfigOptionInts : public ConfigOption, public ConfigOptionVector<int>
return ss.str(); return ss.str();
}; };
std::vector<std::string> vserialize() const {
std::vector<std::string> vv;
for (std::vector<int>::const_iterator it = this->values.begin(); it != this->values.end(); ++it) {
std::ostringstream ss;
ss << *it;
vv.push_back(ss.str());
}
return vv;
};
bool deserialize(std::string str) { bool deserialize(std::string str) {
this->values.clear(); this->values.clear();
std::istringstream is(str); std::istringstream is(str);
@ -174,7 +200,7 @@ class ConfigOptionString : public ConfigOption
}; };
// semicolon-separated strings // semicolon-separated strings
class ConfigOptionStrings : public ConfigOption, public ConfigOptionVector<std::string> class ConfigOptionStrings : public ConfigOptionVector<std::string>
{ {
public: public:
@ -187,6 +213,10 @@ class ConfigOptionStrings : public ConfigOption, public ConfigOptionVector<std::
return ss.str(); return ss.str();
}; };
std::vector<std::string> vserialize() const {
return this->values;
};
bool deserialize(std::string str) { bool deserialize(std::string str) {
this->values.clear(); this->values.clear();
std::istringstream is(str); std::istringstream is(str);
@ -279,7 +309,7 @@ class ConfigOptionPoint : public ConfigOption
}; };
}; };
class ConfigOptionPoints : public ConfigOption, public ConfigOptionVector<Pointf> class ConfigOptionPoints : public ConfigOptionVector<Pointf>
{ {
public: public:
@ -294,6 +324,16 @@ class ConfigOptionPoints : public ConfigOption, public ConfigOptionVector<Pointf
return ss.str(); return ss.str();
}; };
std::vector<std::string> vserialize() const {
std::vector<std::string> vv;
for (Pointfs::const_iterator it = this->values.begin(); it != this->values.end(); ++it) {
std::ostringstream ss;
ss << *it;
vv.push_back(ss.str());
}
return vv;
};
bool deserialize(std::string str) { bool deserialize(std::string str) {
this->values.clear(); this->values.clear();
std::istringstream is(str); std::istringstream is(str);
@ -332,7 +372,7 @@ class ConfigOptionBool : public ConfigOption
}; };
}; };
class ConfigOptionBools : public ConfigOption, public ConfigOptionVector<bool> class ConfigOptionBools : public ConfigOptionVector<bool>
{ {
public: public:
@ -345,6 +385,16 @@ class ConfigOptionBools : public ConfigOption, public ConfigOptionVector<bool>
return ss.str(); return ss.str();
}; };
std::vector<std::string> vserialize() const {
std::vector<std::string> vv;
for (std::vector<bool>::const_iterator it = this->values.begin(); it != this->values.end(); ++it) {
std::ostringstream ss;
ss << (*it ? "1" : "0");
vv.push_back(ss.str());
}
return vv;
};
bool deserialize(std::string str) { bool deserialize(std::string str) {
this->values.clear(); this->values.clear();
std::istringstream is(str); std::istringstream is(str);

View File

@ -191,12 +191,25 @@ ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines)
// unless they represent closed loops // unless they represent closed loops
for (Polylines::iterator polyline = polylines->begin(); polyline != polylines->end(); ++polyline) { for (Polylines::iterator polyline = polylines->begin(); polyline != polylines->end(); ++polyline) {
if (polyline->points.front().distance_to(polyline->points.back()) < min_width) continue; if (polyline->points.front().distance_to(polyline->points.back()) < min_width) continue;
// TODO: we should *not* extend endpoints where other polylines start/end
// (such as T joints, which are returned as three polylines by MedialAxis)
polyline->extend_start(max_width); polyline->extend_start(max_width);
polyline->extend_end(max_width); polyline->extend_end(max_width);
} }
// clip again after extending endpoints to prevent them from exceeding the expolygon boundaries // clip again after extending endpoints to prevent them from exceeding the expolygon boundaries
intersection(*polylines, *this, polylines); intersection(*polylines, *this, polylines);
// remove too short polylines
// (we can't do this check before endpoints extension and clipping because we don't
// know how long will the endpoints be extended since it depends on polygon thickness
// which is variable - extension will be <= max_width/2 on each side)
for (size_t i = 0; i < polylines->size(); ++i) {
if ((*polylines)[i].length() < max_width) {
polylines->erase(polylines->begin() + i);
--i;
}
}
} }
void void

View File

@ -35,9 +35,15 @@ enum ExtrusionLoopRole {
class ExtrusionEntity class ExtrusionEntity
{ {
public: public:
virtual bool is_collection() const {
return false;
};
virtual bool is_loop() const { virtual bool is_loop() const {
return false; return false;
}; };
virtual bool can_reverse() const {
return true;
};
virtual ExtrusionEntity* clone() const = 0; virtual ExtrusionEntity* clone() const = 0;
virtual ~ExtrusionEntity() {}; virtual ~ExtrusionEntity() {};
virtual void reverse() = 0; virtual void reverse() = 0;
@ -92,6 +98,9 @@ class ExtrusionLoop : public ExtrusionEntity
bool is_loop() const { bool is_loop() const {
return true; return true;
}; };
bool can_reverse() const {
return false;
};
ExtrusionLoop* clone() const; ExtrusionLoop* clone() const;
bool make_clockwise(); bool make_clockwise();
bool make_counter_clockwise(); bool make_counter_clockwise();

View File

@ -86,7 +86,7 @@ ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEntityCo
Points endpoints; Points endpoints;
for (ExtrusionEntitiesPtr::iterator it = my_paths.begin(); it != my_paths.end(); ++it) { for (ExtrusionEntitiesPtr::iterator it = my_paths.begin(); it != my_paths.end(); ++it) {
endpoints.push_back((*it)->first_point()); endpoints.push_back((*it)->first_point());
if (no_reverse) { if (no_reverse || !(*it)->can_reverse()) {
endpoints.push_back((*it)->first_point()); endpoints.push_back((*it)->first_point());
} else { } else {
endpoints.push_back((*it)->last_point()); endpoints.push_back((*it)->last_point());
@ -99,7 +99,7 @@ ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEntityCo
int path_index = start_index/2; int path_index = start_index/2;
ExtrusionEntity* entity = my_paths.at(path_index); ExtrusionEntity* entity = my_paths.at(path_index);
// never reverse loops, since it's pointless for chained path and callers might depend on orientation // never reverse loops, since it's pointless for chained path and callers might depend on orientation
if (start_index % 2 && !no_reverse && !entity->is_loop()) { if (start_index % 2 && !no_reverse && entity->can_reverse()) {
entity->reverse(); entity->reverse();
} }
retval->entities.push_back(my_paths.at(path_index)); retval->entities.push_back(my_paths.at(path_index));
@ -121,6 +121,22 @@ ExtrusionEntityCollection::grow() const
return pp; return pp;
} }
/* Recursively count paths and loops contained in this collection */
size_t
ExtrusionEntityCollection::items_count() const
{
size_t count = 0;
for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) {
if ((*it)->is_collection()) {
ExtrusionEntityCollection* collection = dynamic_cast<ExtrusionEntityCollection*>(*it);
count += collection->items_count();
} else {
++count;
}
}
return count;
}
#ifdef SLIC3RXS #ifdef SLIC3RXS
// there is no ExtrusionLoop::Collection or ExtrusionEntity::Collection // there is no ExtrusionLoop::Collection or ExtrusionEntity::Collection
REGISTER_CLASS(ExtrusionEntityCollection, "ExtrusionPath::Collection"); REGISTER_CLASS(ExtrusionEntityCollection, "ExtrusionPath::Collection");

View File

@ -16,6 +16,12 @@ class ExtrusionEntityCollection : public ExtrusionEntity
ExtrusionEntityCollection(): no_sort(false) {}; ExtrusionEntityCollection(): no_sort(false) {};
ExtrusionEntityCollection(const ExtrusionEntityCollection &collection); ExtrusionEntityCollection(const ExtrusionEntityCollection &collection);
ExtrusionEntityCollection& operator= (const ExtrusionEntityCollection &other); ExtrusionEntityCollection& operator= (const ExtrusionEntityCollection &other);
bool is_collection() const {
return true;
};
bool can_reverse() const {
return !this->no_sort;
};
void swap (ExtrusionEntityCollection &c); void swap (ExtrusionEntityCollection &c);
void chained_path(ExtrusionEntityCollection* retval, bool no_reverse = false, std::vector<size_t>* orig_indices = NULL) const; void chained_path(ExtrusionEntityCollection* retval, bool no_reverse = false, std::vector<size_t>* orig_indices = NULL) const;
void chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse = false, std::vector<size_t>* orig_indices = NULL) const; void chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse = false, std::vector<size_t>* orig_indices = NULL) const;
@ -23,6 +29,7 @@ class ExtrusionEntityCollection : public ExtrusionEntity
Point first_point() const; Point first_point() const;
Point last_point() const; Point last_point() const;
Polygons grow() const; Polygons grow() const;
size_t items_count() const;
}; };
} }

View File

@ -161,6 +161,133 @@ simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval)
Slic3r::simplify_polygons(pp, retval); Slic3r::simplify_polygons(pp, retval);
} }
double
linint(double value, double oldmin, double oldmax, double newmin, double newmax)
{
return (value - oldmin) * (newmax - newmin) / (oldmax - oldmin) + newmin;
}
Pointfs
arrange(size_t total_parts, Pointf part, coordf_t dist, const BoundingBoxf &bb)
{
// use actual part size (the largest) plus separation distance (half on each side) in spacing algorithm
part.x += dist;
part.y += dist;
Pointf area;
if (bb.defined) {
area = bb.size();
} else {
// bogus area size, large enough not to trigger the error below
area.x = part.x * total_parts;
area.y = part.y * total_parts;
}
// this is how many cells we have available into which to put parts
size_t cellw = floor((area.x + dist) / part.x);
size_t cellh = floor((area.x + dist) / part.x);
if (total_parts > (cellw * cellh))
CONFESS("%zu parts won't fit in your print area!\n", total_parts);
// total space used by cells
Pointf cells(cellw * part.x, cellh * part.y);
// bounding box of total space used by cells
BoundingBoxf cells_bb;
cells_bb.merge(Pointf(0,0)); // min
cells_bb.merge(cells); // max
// center bounding box to area
cells_bb.translate(
-(area.x - cells.x) / 2,
-(area.y - cells.y) / 2
);
// list of cells, sorted by distance from center
std::vector<ArrangeItemIndex> cellsorder;
// work out distance for all cells, sort into list
for (size_t i = 0; i <= cellw-1; ++i) {
for (size_t j = 0; j <= cellh-1; ++j) {
coordf_t cx = linint(i + 0.5, 0, cellw, cells_bb.min.x, cells_bb.max.x);
coordf_t cy = linint(j + 0.5, 0, cellh, cells_bb.max.y, cells_bb.min.y);
coordf_t xd = fabs((area.x / 2) - cx);
coordf_t yd = fabs((area.y / 2) - cy);
ArrangeItem c;
c.pos.x = cx;
c.pos.y = cy;
c.index_x = i;
c.index_y = j;
c.dist = xd * xd + yd * yd - fabs((cellw / 2) - (i + 0.5));
// binary insertion sort
{
coordf_t index = c.dist;
size_t low = 0;
size_t high = cellsorder.size();
while (low < high) {
size_t mid = (low + ((high - low) / 2)) | 0;
coordf_t midval = cellsorder[mid].index;
if (midval < index) {
low = mid + 1;
} else if (midval > index) {
high = mid;
} else {
cellsorder.insert(cellsorder.begin() + mid, ArrangeItemIndex(index, c));
goto ENDSORT;
}
}
cellsorder.insert(cellsorder.begin() + low, ArrangeItemIndex(index, c));
}
ENDSORT: true;
}
}
// the extents of cells actually used by objects
coordf_t lx = 0;
coordf_t ty = 0;
coordf_t rx = 0;
coordf_t by = 0;
// now find cells actually used by objects, map out the extents so we can position correctly
for (size_t i = 1; i <= total_parts; ++i) {
ArrangeItemIndex c = cellsorder[i - 1];
coordf_t cx = c.item.index_x;
coordf_t cy = c.item.index_y;
if (i == 1) {
lx = rx = cx;
ty = by = cy;
} else {
if (cx > rx) rx = cx;
if (cx < lx) lx = cx;
if (cy > by) by = cy;
if (cy < ty) ty = cy;
}
}
// now we actually place objects into cells, positioned such that the left and bottom borders are at 0
Pointfs positions;
for (size_t i = 1; i <= total_parts; ++i) {
ArrangeItemIndex c = cellsorder.front();
cellsorder.erase(cellsorder.begin());
coordf_t cx = c.item.index_x - lx;
coordf_t cy = c.item.index_y - ty;
positions.push_back(Pointf(cx * part.x, cy * part.y));
}
if (bb.defined) {
for (Pointfs::iterator p = positions.begin(); p != positions.end(); ++p) {
p->x += bb.min.x;
p->y += bb.min.y;
}
}
return positions;
}
Line Line
MedialAxis::edge_to_line(const VD::edge_type &edge) const MedialAxis::edge_to_line(const VD::edge_type &edge) const
{ {
@ -198,8 +325,11 @@ MedialAxis::build(Polylines* polylines)
} }
*/ */
typedef const VD::vertex_type vert_t;
typedef const VD::edge_type edge_t;
// collect valid edges (i.e. prune those not belonging to MAT) // collect valid edges (i.e. prune those not belonging to MAT)
// note: this keeps twins, so it contains twice the number of the valid edges // note: this keeps twins, so it inserts twice the number of the valid edges
this->edges.clear(); this->edges.clear();
for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) { for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) {
// if we only process segments representing closed loops, none if the // if we only process segments representing closed loops, none if the
@ -209,61 +339,64 @@ MedialAxis::build(Polylines* polylines)
} }
// count valid segments for each vertex // count valid segments for each vertex
std::map< const VD::vertex_type*,std::set<const VD::edge_type*> > vertex_edges; std::map< vert_t*,std::set<edge_t*> > vertex_edges; // collects edges connected for each vertex
std::set<const VD::vertex_type*> entry_nodes; std::set<vert_t*> startpoints; // collects all vertices having a single starting edge
for (VD::const_vertex_iterator vertex = this->vd.vertices().begin(); vertex != this->vd.vertices().end(); ++vertex) { for (VD::const_vertex_iterator it = this->vd.vertices().begin(); it != this->vd.vertices().end(); ++it) {
// get a reference to the list of valid edges originating from this vertex vert_t* vertex = &*it;
std::set<const VD::edge_type*>& edges = vertex_edges[&*vertex];
// get one random edge originating from this vertex // loop through all edges originating from this vertex
const VD::edge_type* edge = vertex->incident_edge(); // starting from a random one
edge_t* edge = vertex->incident_edge();
do { do {
if (this->edges.count(edge) > 0) // only count valid edges // if this edge was not pruned by our filter above,
edges.insert(edge); // add it to vertex_edges
edge = edge->rot_next(); // next edge originating from this vertex if (this->edges.count(edge) > 0)
vertex_edges[vertex].insert(edge);
// continue looping next edge originating from this vertex
edge = edge->rot_next();
} while (edge != vertex->incident_edge()); } while (edge != vertex->incident_edge());
// if there's only one edge starting at this vertex then it's a leaf // if there's only one edge starting at this vertex then it's an endpoint
size_t edge_count = edges.size(); if (vertex_edges[vertex].size() == 1) {
if (edge_count == 1) { startpoints.insert(vertex);
entry_nodes.insert(&*vertex);
} }
} }
// prune recursively // prune startpoints recursively if extreme segments are not valid
while (!entry_nodes.empty()) { while (!startpoints.empty()) {
// get a random entry node // get a random entry node
const VD::vertex_type* v = *entry_nodes.begin(); vert_t* v = *startpoints.begin();
// get edge starting from v // get edge starting from v
assert(!vertex_edges[v].empty()); assert(vertex_edges[v].size() == 1);
const VD::edge_type* edge = *vertex_edges[v].begin(); edge_t* edge = *vertex_edges[v].begin();
if (!this->is_valid_edge(*edge)) { if (!this->is_valid_edge(*edge)) {
// if edge is not valid, erase it from edge list // if edge is not valid, erase it and its twin from edge list
(void)this->edges.erase(edge); (void)this->edges.erase(edge);
(void)this->edges.erase(edge->twin()); (void)this->edges.erase(edge->twin());
// decrement edge counters for the affected nodes // decrement edge counters for the affected nodes
const VD::vertex_type* v1 = edge->vertex1(); vert_t* v1 = edge->vertex1();
(void)vertex_edges[v].erase(edge); (void)vertex_edges[v].erase(edge);
(void)vertex_edges[v1].erase(edge->twin()); (void)vertex_edges[v1].erase(edge->twin());
// also, check whether the end vertex is a new leaf // also, check whether the end vertex is a new leaf
if (vertex_edges[v1].size() == 1) { if (vertex_edges[v1].size() == 1) {
entry_nodes.insert(v1); startpoints.insert(v1);
} else if (vertex_edges[v1].empty()) { } else if (vertex_edges[v1].empty()) {
entry_nodes.erase(v1); startpoints.erase(v1);
} }
} }
// remove node from the set to prevent it from being visited again // remove node from the set to prevent it from being visited again
entry_nodes.erase(v); startpoints.erase(v);
} }
// iterate through the valid edges to build polylines // iterate through the valid edges to build polylines
while (!this->edges.empty()) { while (!this->edges.empty()) {
const VD::edge_type& edge = **this->edges.begin(); edge_t &edge = **this->edges.begin();
// start a polyline // start a polyline
Polyline polyline; Polyline polyline;
@ -278,13 +411,14 @@ MedialAxis::build(Polylines* polylines)
this->process_edge_neighbors(edge, &polyline.points); this->process_edge_neighbors(edge, &polyline.points);
// get previous points // get previous points
Points pp; {
this->process_edge_neighbors(*edge.twin(), &pp); Points pp;
polyline.points.insert(polyline.points.begin(), pp.rbegin(), pp.rend()); this->process_edge_neighbors(*edge.twin(), &pp);
polyline.points.insert(polyline.points.begin(), pp.rbegin(), pp.rend());
}
// append polyline to result if it's not too small // append polyline to result
if (polyline.length() > this->max_width) polylines->push_back(polyline);
polylines->push_back(polyline);
} }
} }
@ -322,69 +456,59 @@ MedialAxis::is_valid_edge(const VD::edge_type& edge) const
"thin" nature of our input, these edges will be very short and not part of "thin" nature of our input, these edges will be very short and not part of
our wanted output. */ our wanted output. */
// retrieve the original line segments which generated the edge we're checking
const VD::cell_type &cell1 = *edge.cell(); const VD::cell_type &cell1 = *edge.cell();
const VD::cell_type &cell2 = *edge.twin()->cell(); const VD::cell_type &cell2 = *edge.twin()->cell();
if (cell1.contains_segment() && cell2.contains_segment()) { if (!cell1.contains_segment() || !cell2.contains_segment()) return false;
Line segment1 = this->retrieve_segment(cell1); const Line &segment1 = this->retrieve_segment(cell1);
Line segment2 = this->retrieve_segment(cell2); const Line &segment2 = this->retrieve_segment(cell2);
if (segment1.a == segment2.b || segment1.b == segment2.a) return false;
// calculate the relative angle between the two boundary segments
// calculate relative angle between the two boundary segments double angle = fabs(segment2.orientation() - segment1.orientation());
double angle = fabs(segment2.orientation() - segment1.orientation());
// fabs(angle) ranges from 0 (collinear, same direction) to PI (collinear, opposite direction)
// fabs(angle) ranges from 0 (collinear, same direction) to PI (collinear, opposite direction) // we're interested only in segments close to the second case (facing segments)
// we're interested only in segments close to the second case (facing segments) // so we allow some tolerance.
// so we allow some tolerance (say, 30°) // this filter ensures that we're dealing with a narrow/oriented area (longer than thick)
if (angle < PI*2/3 ) { if (fabs(angle - PI) > PI/5) {
return false; return false;
}
// each vertex is equidistant to both cell segments
// but such distance might differ between the two vertices;
// in this case it means the shape is getting narrow (like a corner)
// and we might need to skip the edge since it's not really part of
// our skeleton
Point v0( edge.vertex0()->x(), edge.vertex0()->y() );
Point v1( edge.vertex1()->x(), edge.vertex1()->y() );
double dist0 = v0.perp_distance_to(segment1);
double dist1 = v1.perp_distance_to(segment1);
/*
double diff = fabs(dist1 - dist0);
double dist_between_segments1 = segment1.a.distance_to(segment2);
double dist_between_segments2 = segment1.b.distance_to(segment2);
printf("w = %f/%f, dist0 = %f, dist1 = %f, diff = %f, seg1len = %f, seg2len = %f, edgelen = %f, s2s = %f / %f\n",
unscale(this->max_width), unscale(this->min_width),
unscale(dist0), unscale(dist1), unscale(diff),
unscale(segment1.length()), unscale(segment2.length()),
unscale(this->edge_to_line(edge).length()),
unscale(dist_between_segments1), unscale(dist_between_segments2)
);
*/
// if this segment is the centerline for a very thin area, we might want to skip it
// in case the area is too thin
if (dist0 < this->min_width/2 || dist1 < this->min_width/2) {
//printf(" => too thin, skipping\n");
return false;
}
/*
// if distance between this edge and the thin area boundary is greater
// than half the max width, then it's not a true medial axis segment
if (dist1 > this->width*2) {
printf(" => too fat, skipping\n");
//return false;
}
*/
return true;
} }
return false; // each edge vertex is equidistant to both cell segments
// but such distance might differ between the two vertices;
// in this case it means the shape is getting narrow (like a corner)
// and we might need to skip the edge since it's not really part of
// our skeleton
// get perpendicular distance of each edge vertex to the segment(s)
double dist0 = segment1.a.distance_to(segment2.b);
double dist1 = segment1.b.distance_to(segment2.a);
/*
Line line = this->edge_to_line(edge);
double diff = fabs(dist1 - dist0);
double dist_between_segments1 = segment1.a.distance_to(segment2);
double dist_between_segments2 = segment1.b.distance_to(segment2);
printf("w = %f/%f, dist0 = %f, dist1 = %f, diff = %f, seg1len = %f, seg2len = %f, edgelen = %f, s2s = %f / %f\n",
unscale(this->max_width), unscale(this->min_width),
unscale(dist0), unscale(dist1), unscale(diff),
unscale(segment1.length()), unscale(segment2.length()),
unscale(line.length()),
unscale(dist_between_segments1), unscale(dist_between_segments2)
);
*/
// if this edge is the centerline for a very thin area, we might want to skip it
// in case the area is too thin
if (dist0 < this->min_width && dist1 < this->min_width) {
//printf(" => too thin, skipping\n");
return false;
}
return true;
} }
Line const Line&
MedialAxis::retrieve_segment(const VD::cell_type& cell) const MedialAxis::retrieve_segment(const VD::cell_type& cell) const
{ {
VD::cell_type::source_index_type index = cell.source_index() - this->points.size(); VD::cell_type::source_index_type index = cell.source_index() - this->points.size();

View File

@ -23,6 +23,21 @@ double rad2deg_dir(double angle);
double deg2rad(double angle); double deg2rad(double angle);
void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval); void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval);
class ArrangeItem {
public:
Pointf pos;
size_t index_x, index_y;
coordf_t dist;
};
class ArrangeItemIndex {
public:
coordf_t index;
ArrangeItem item;
ArrangeItemIndex(coordf_t _index, ArrangeItem _item) : index(_index), item(_item) {};
};
double linint(double value, double oldmin, double oldmax, double newmin, double newmax);
Pointfs arrange(size_t total_parts, Pointf part, coordf_t dist, const BoundingBoxf &bb = BoundingBoxf());
class MedialAxis { class MedialAxis {
public: public:
Points points; Points points;
@ -39,7 +54,7 @@ class MedialAxis {
Line edge_to_line(const VD::edge_type &edge) const; Line edge_to_line(const VD::edge_type &edge) const;
void process_edge_neighbors(const voronoi_diagram<double>::edge_type& edge, Points* points); void process_edge_neighbors(const voronoi_diagram<double>::edge_type& edge, Points* points);
bool is_valid_edge(const voronoi_diagram<double>::edge_type& edge) const; bool is_valid_edge(const voronoi_diagram<double>::edge_type& edge) const;
Line retrieve_segment(const voronoi_diagram<double>::cell_type& cell) const; const Line& retrieve_segment(const voronoi_diagram<double>::cell_type& cell) const;
}; };
} } } }

View File

@ -126,6 +126,14 @@ Layer::make_slices()
} }
} }
void
Layer::merge_slices()
{
FOREACH_LAYERREGION(this, layerm) {
(*layerm)->merge_slices();
}
}
template <class T> template <class T>
bool bool
Layer::any_internal_region_slice_contains(const T &item) const Layer::any_internal_region_slice_contains(const T &item) const

View File

@ -47,7 +47,7 @@ class LayerRegion
PolylineCollection unsupported_bridge_edges; PolylineCollection unsupported_bridge_edges;
// ordered collection of extrusion paths/loops to build all perimeters // ordered collection of extrusion paths/loops to build all perimeters
// (this collection contains both ExtrusionPath and ExtrusionLoop objects) // (this collection contains only ExtrusionEntityCollection objects)
ExtrusionEntityCollection perimeters; ExtrusionEntityCollection perimeters;
// ordered collection of extrusion paths to fill surfaces // ordered collection of extrusion paths to fill surfaces
@ -95,6 +95,7 @@ class Layer {
LayerRegion* add_region(PrintRegion* print_region); LayerRegion* add_region(PrintRegion* print_region);
void make_slices(); void make_slices();
void merge_slices();
template <class T> bool any_internal_region_slice_contains(const T &item) const; template <class T> bool any_internal_region_slice_contains(const T &item) const;
template <class T> bool any_bottom_region_slice_contains(const T &item) const; template <class T> bool any_bottom_region_slice_contains(const T &item) const;

View File

@ -1,4 +1,5 @@
#include "Model.hpp" #include "Model.hpp"
#include "Geometry.hpp"
namespace Slic3r { namespace Slic3r {
@ -481,7 +482,12 @@ ModelObject::center_around_origin()
if (!this->instances.empty()) { if (!this->instances.empty()) {
for (ModelInstancePtrs::const_iterator i = this->instances.begin(); i != this->instances.end(); ++i) { for (ModelInstancePtrs::const_iterator i = this->instances.begin(); i != this->instances.end(); ++i) {
(*i)->offset.translate(-vector.x, -vector.y); // apply rotation and scaling to vector as well before translating instance,
// in order to leave final position unaltered
Vectorf3 v = vector.negative();
v.rotate((*i)->rotation, (*i)->offset);
v.scale((*i)->scaling_factor);
(*i)->offset.translate(v.x, v.y);
} }
this->update_bounding_box(); this->update_bounding_box();
} }
@ -514,6 +520,26 @@ ModelObject::scale(const Pointf3 &versor)
this->invalidate_bounding_box(); this->invalidate_bounding_box();
} }
void
ModelObject::rotate(float angle, const Axis &axis)
{
for (ModelVolumePtrs::const_iterator v = this->volumes.begin(); v != this->volumes.end(); ++v) {
(*v)->mesh.rotate(angle, axis);
}
this->origin_translation = Pointf3(0,0,0);
this->invalidate_bounding_box();
}
void
ModelObject::flip(const Axis &axis)
{
for (ModelVolumePtrs::const_iterator v = this->volumes.begin(); v != this->volumes.end(); ++v) {
(*v)->mesh.flip(axis);
}
this->origin_translation = Pointf3(0,0,0);
this->invalidate_bounding_box();
}
size_t size_t
ModelObject::materials_count() const ModelObject::materials_count() const
{ {

View File

@ -129,6 +129,8 @@ class ModelObject
void translate(const Vectorf3 &vector); void translate(const Vectorf3 &vector);
void translate(coordf_t x, coordf_t y, coordf_t z); void translate(coordf_t x, coordf_t y, coordf_t z);
void scale(const Pointf3 &versor); void scale(const Pointf3 &versor);
void rotate(float angle, const Axis &axis);
void flip(const Axis &axis);
size_t materials_count() const; size_t materials_count() const;
size_t facets_count() const; size_t facets_count() const;
bool needed_repair() const; bool needed_repair() const;
@ -175,7 +177,7 @@ class ModelInstance
{ {
friend class ModelObject; friend class ModelObject;
public: public:
double rotation; // around mesh center point double rotation; // in radians around mesh center point
double scaling_factor; double scaling_factor;
Pointf offset; // in unscaled coordinates Pointf offset; // in unscaled coordinates

View File

@ -29,22 +29,14 @@ PlaceholderParser::update_timestamp()
ss << std::setw(2) << std::setfill('0') << timeinfo->tm_hour; ss << std::setw(2) << std::setfill('0') << timeinfo->tm_hour;
ss << std::setw(2) << std::setfill('0') << timeinfo->tm_min; ss << std::setw(2) << std::setfill('0') << timeinfo->tm_min;
ss << std::setw(2) << std::setfill('0') << timeinfo->tm_sec; ss << std::setw(2) << std::setfill('0') << timeinfo->tm_sec;
this->_single["timestamp"] = ss.str(); this->set("timestamp", ss.str());
} }
this->_single["year"] = this->_int_to_string(1900 + timeinfo->tm_year); this->set("year", 1900 + timeinfo->tm_year);
this->_single["month"] = this->_int_to_string(1 + timeinfo->tm_mon); this->set("month", 1 + timeinfo->tm_mon);
this->_single["day"] = this->_int_to_string(timeinfo->tm_mday); this->set("day", timeinfo->tm_mday);
this->_single["hour"] = this->_int_to_string(timeinfo->tm_hour); this->set("hour", timeinfo->tm_hour);
this->_single["minute"] = this->_int_to_string(timeinfo->tm_min); this->set("minute", timeinfo->tm_min);
this->_single["second"] = this->_int_to_string(timeinfo->tm_sec); this->set("second", timeinfo->tm_sec);
}
std::string
PlaceholderParser::_int_to_string(int value) const
{
std::ostringstream ss;
ss << value;
return ss.str();
} }
void PlaceholderParser::apply_config(DynamicPrintConfig &config) void PlaceholderParser::apply_config(DynamicPrintConfig &config)
@ -66,53 +58,20 @@ void PlaceholderParser::apply_config(DynamicPrintConfig &config)
i != opt_keys.end(); ++i) i != opt_keys.end(); ++i)
{ {
const t_config_option_key &key = *i; const t_config_option_key &key = *i;
const ConfigOption* opt = config.option(key);
// set placeholders for options with multiple values
const ConfigOptionDef &def = (*config.def)[key]; if (const ConfigOptionVectorBase* optv = dynamic_cast<const ConfigOptionVectorBase*>(opt)) {
switch (def.type) { // set placeholders for options with multiple values
case coFloats: this->set(key, optv->vserialize());
this->set_multiple_from_vector(key, } else if (const ConfigOptionPoint* optp = dynamic_cast<const ConfigOptionPoint*>(opt)) {
*(ConfigOptionFloats*)config.option(key)); this->_single[key] = optp->serialize();
break;
Pointf val = *optp;
case coInts: this->_multiple[key + "_X"] = val.x;
this->set_multiple_from_vector(key, this->_multiple[key + "_Y"] = val.y;
*(ConfigOptionInts*)config.option(key)); } else {
break;
case coStrings:
this->set_multiple_from_vector(key,
*(ConfigOptionStrings*)config.option(key));
break;
case coPoints:
this->set_multiple_from_vector(key,
*(ConfigOptionPoints*)config.option(key));
break;
case coBools:
this->set_multiple_from_vector(key,
*(ConfigOptionBools*)config.option(key));
break;
case coPoint:
{
const ConfigOptionPoint &opt =
*(ConfigOptionPoint*)config.option(key);
this->_single[key] = opt.serialize();
Pointf val = opt;
this->_multiple[key + "_X"] = val.x;
this->_multiple[key + "_Y"] = val.y;
}
break;
default:
// set single-value placeholders // set single-value placeholders
this->_single[key] = config.serialize(key); this->_single[key] = opt->serialize();
break;
} }
} }
} }
@ -121,34 +80,30 @@ void
PlaceholderParser::set(const std::string &key, const std::string &value) PlaceholderParser::set(const std::string &key, const std::string &value)
{ {
this->_single[key] = value; this->_single[key] = value;
this->_multiple.erase(key);
} }
std::ostream& operator<<(std::ostream &stm, const Pointf &pointf) void
PlaceholderParser::set(const std::string &key, int value)
{ {
return stm << pointf.x << "," << pointf.y; std::ostringstream ss;
ss << value;
this->set(key, ss.str());
} }
template<class T> void
void PlaceholderParser::set_multiple_from_vector(const std::string &key, PlaceholderParser::set(const std::string &key, const std::vector<std::string> &values)
ConfigOptionVector<T> &opt)
{ {
const std::vector<T> &vals = opt.values; for (std::vector<std::string>::const_iterator v = values.begin(); v != values.end(); ++v) {
std::stringstream ss;
for (size_t i = 0; i < vals.size(); ++i) { ss << key << "_" << (v - values.begin());
std::stringstream multikey_stm;
multikey_stm << key << "_" << i; this->_multiple[ ss.str() ] = *v;
if (v == values.begin()) {
std::stringstream val_stm; this->_multiple[key] = *v;
val_stm << vals[i]; }
this->_multiple[multikey_stm.str()] = val_stm.str();
}
if (vals.size() > 0) {
std::stringstream val_stm;
val_stm << vals[0];
this->_multiple[key] = val_stm.str();
} }
this->_single.erase(key);
} }
#ifdef SLIC3RXS #ifdef SLIC3RXS

View File

@ -5,6 +5,7 @@
#include <myinit.h> #include <myinit.h>
#include <map> #include <map>
#include <string> #include <string>
#include <vector>
#include "PrintConfig.hpp" #include "PrintConfig.hpp"
@ -20,12 +21,8 @@ class PlaceholderParser
void update_timestamp(); void update_timestamp();
void apply_config(DynamicPrintConfig &config); void apply_config(DynamicPrintConfig &config);
void set(const std::string &key, const std::string &value); void set(const std::string &key, const std::string &value);
void set(const std::string &key, int value);
private: void set(const std::string &key, const std::vector<std::string> &values);
template<class T>
void set_multiple_from_vector(
const std::string &key, ConfigOptionVector<T> &opt);
std::string _int_to_string(int value) const;
}; };
} }

View File

@ -3,7 +3,6 @@
#include "MultiPoint.hpp" #include "MultiPoint.hpp"
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <sstream>
namespace Slic3r { namespace Slic3r {
@ -340,6 +339,12 @@ REGISTER_CLASS(Point3, "Point3");
#endif #endif
std::ostream&
operator<<(std::ostream &stm, const Pointf &pointf)
{
return stm << pointf.x << "," << pointf.y;
}
void void
Pointf::scale(double factor) Pointf::scale(double factor)
{ {

View File

@ -5,6 +5,7 @@
#include <vector> #include <vector>
#include <math.h> #include <math.h>
#include <string> #include <string>
#include <sstream>
namespace Slic3r { namespace Slic3r {
@ -77,6 +78,8 @@ class Point3 : public Point
explicit Point3(coord_t _x = 0, coord_t _y = 0, coord_t _z = 0): Point(_x, _y), z(_z) {}; explicit Point3(coord_t _x = 0, coord_t _y = 0, coord_t _z = 0): Point(_x, _y), z(_z) {};
}; };
std::ostream& operator<<(std::ostream &stm, const Pointf &pointf);
class Pointf class Pointf
{ {
public: public:

View File

@ -139,11 +139,31 @@ Polyline::simplify_by_visibility(const T &area)
Points &pp = this->points; Points &pp = this->points;
// find first point in area // find first point in area
size_t start = 0; size_t s = 0;
while (start < pp.size() && !area.contains(pp[start])) { while (s < pp.size() && !area.contains(pp[s])) {
start++; ++s;
} }
// find last point in area
size_t e = pp.size()-1;
while (e > 0 && !area.contains(pp[e])) {
--e;
}
// this ugly algorithm resembles a binary search
while (e > s + 1) {
size_t mid = (s + e) / 2;
if (area.contains(Line(pp[s], pp[mid]))) {
pp.erase(pp.begin() + s + 1, pp.begin() + mid);
// repeat recursively until no further simplification is possible
++s;
e = pp.size()-1;
} else {
e = mid;
}
}
/*
// The following implementation is complete but it's not efficient at all:
for (size_t s = start; s < pp.size() && !pp.empty(); ++s) { for (size_t s = start; s < pp.size() && !pp.empty(); ++s) {
// find the farthest point to which we can build // find the farthest point to which we can build
// a line that is contained in the supplied area // a line that is contained in the supplied area
@ -158,6 +178,7 @@ Polyline::simplify_by_visibility(const T &area)
} }
} }
} }
*/
} }
template void Polyline::simplify_by_visibility<ExPolygon>(const ExPolygon &area); template void Polyline::simplify_by_visibility<ExPolygon>(const ExPolygon &area);
template void Polyline::simplify_by_visibility<ExPolygonCollection>(const ExPolygonCollection &area); template void Polyline::simplify_by_visibility<ExPolygonCollection>(const ExPolygonCollection &area);

View File

@ -172,7 +172,8 @@ Print::invalidate_state_by_config_options(const std::vector<t_config_option_key>
} else if (*opt_key == "brim_width") { } else if (*opt_key == "brim_width") {
steps.insert(psBrim); steps.insert(psBrim);
steps.insert(psSkirt); steps.insert(psSkirt);
} else if (*opt_key == "nozzle_diameter") { } else if (*opt_key == "nozzle_diameter"
|| *opt_key == "resolution") {
osteps.insert(posSlice); osteps.insert(posSlice);
} else if (*opt_key == "avoid_crossing_perimeters" } else if (*opt_key == "avoid_crossing_perimeters"
|| *opt_key == "bed_shape" || *opt_key == "bed_shape"
@ -300,7 +301,7 @@ Print::step_done(PrintObjectStep step) const
// returns 0-based indices of used extruders // returns 0-based indices of used extruders
std::set<size_t> std::set<size_t>
Print::extruders() const Print::object_extruders() const
{ {
std::set<size_t> extruders; std::set<size_t> extruders;
@ -316,6 +317,16 @@ Print::extruders() const
if ((*region)->config.top_solid_layers.value > 0 || (*region)->config.bottom_solid_layers.value > 0) if ((*region)->config.top_solid_layers.value > 0 || (*region)->config.bottom_solid_layers.value > 0)
extruders.insert((*region)->config.solid_infill_extruder - 1); extruders.insert((*region)->config.solid_infill_extruder - 1);
} }
return extruders;
}
// returns 0-based indices of used extruders
std::set<size_t>
Print::support_material_extruders() const
{
std::set<size_t> extruders;
FOREACH_OBJECT(this, object) { FOREACH_OBJECT(this, object) {
if ((*object)->has_support_material()) { if ((*object)->has_support_material()) {
extruders.insert((*object)->config.support_material_extruder - 1); extruders.insert((*object)->config.support_material_extruder - 1);
@ -326,6 +337,18 @@ Print::extruders() const
return extruders; return extruders;
} }
// returns 0-based indices of used extruders
std::set<size_t>
Print::extruders() const
{
std::set<size_t> extruders = this->object_extruders();
std::set<size_t> s_extruders = this->support_material_extruders();
extruders.insert(s_extruders.begin(), s_extruders.end());
return extruders;
}
void void
Print::_simplify_slices(double distance) Print::_simplify_slices(double distance)
{ {

View File

@ -199,6 +199,8 @@ class Print
Flow brim_flow() const; Flow brim_flow() const;
Flow skirt_flow() const; Flow skirt_flow() const;
std::set<size_t> object_extruders() const;
std::set<size_t> support_material_extruders() const;
std::set<size_t> extruders() const; std::set<size_t> extruders() const;
void _simplify_slices(double distance); void _simplify_slices(double distance);
double max_allowed_layer_height() const; double max_allowed_layer_height() const;

View File

@ -232,6 +232,12 @@ PrintConfigDef::build_def() {
Options["fan_below_layer_time"].min = 0; Options["fan_below_layer_time"].min = 0;
Options["fan_below_layer_time"].max = 1000; Options["fan_below_layer_time"].max = 1000;
Options["filament_colour"].type = coStrings;
Options["filament_colour"].label = "Color";
Options["filament_colour"].tooltip = "This is only used in the Slic3r interface as a visual help.";
Options["filament_colour"].cli = "filament-color=s@";
Options["filament_colour"].gui_type = "color";
Options["filament_diameter"].type = coFloats; Options["filament_diameter"].type = coFloats;
Options["filament_diameter"].label = "Diameter"; Options["filament_diameter"].label = "Diameter";
Options["filament_diameter"].tooltip = "Enter your filament diameter here. Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average."; Options["filament_diameter"].tooltip = "Enter your filament diameter here. Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average.";

View File

@ -409,6 +409,7 @@ class PrintConfig : public GCodeConfig
ConfigOptionPoints extruder_offset; ConfigOptionPoints extruder_offset;
ConfigOptionBool fan_always_on; ConfigOptionBool fan_always_on;
ConfigOptionInt fan_below_layer_time; ConfigOptionInt fan_below_layer_time;
ConfigOptionStrings filament_colour;
ConfigOptionFloat first_layer_acceleration; ConfigOptionFloat first_layer_acceleration;
ConfigOptionInt first_layer_bed_temperature; ConfigOptionInt first_layer_bed_temperature;
ConfigOptionFloatOrPercent first_layer_extrusion_width; ConfigOptionFloatOrPercent first_layer_extrusion_width;
@ -464,6 +465,8 @@ class PrintConfig : public GCodeConfig
this->extruder_offset.values[0] = Pointf(0,0); this->extruder_offset.values[0] = Pointf(0,0);
this->fan_always_on.value = false; this->fan_always_on.value = false;
this->fan_below_layer_time.value = 60; this->fan_below_layer_time.value = 60;
this->filament_colour.values.resize(1);
this->filament_colour.values[0] = "#FFFFFF";
this->first_layer_acceleration.value = 0; this->first_layer_acceleration.value = 0;
this->first_layer_bed_temperature.value = 0; this->first_layer_bed_temperature.value = 0;
this->first_layer_extrusion_width.value = 200; this->first_layer_extrusion_width.value = 200;
@ -523,6 +526,7 @@ class PrintConfig : public GCodeConfig
if (opt_key == "extruder_offset") return &this->extruder_offset; if (opt_key == "extruder_offset") return &this->extruder_offset;
if (opt_key == "fan_always_on") return &this->fan_always_on; if (opt_key == "fan_always_on") return &this->fan_always_on;
if (opt_key == "fan_below_layer_time") return &this->fan_below_layer_time; if (opt_key == "fan_below_layer_time") return &this->fan_below_layer_time;
if (opt_key == "filament_colour") return &this->filament_colour;
if (opt_key == "first_layer_acceleration") return &this->first_layer_acceleration; if (opt_key == "first_layer_acceleration") return &this->first_layer_acceleration;
if (opt_key == "first_layer_bed_temperature") return &this->first_layer_bed_temperature; if (opt_key == "first_layer_bed_temperature") return &this->first_layer_bed_temperature;
if (opt_key == "first_layer_extrusion_width") return &this->first_layer_extrusion_width; if (opt_key == "first_layer_extrusion_width") return &this->first_layer_extrusion_width;

View File

@ -222,8 +222,7 @@ PrintObject::invalidate_state_by_config_options(const std::vector<t_config_optio
|| *opt_key == "thin_walls" || *opt_key == "thin_walls"
|| *opt_key == "external_perimeters_first") { || *opt_key == "external_perimeters_first") {
steps.insert(posPerimeters); steps.insert(posPerimeters);
} else if (*opt_key == "resolution" } else if (*opt_key == "layer_height"
|| *opt_key == "layer_height"
|| *opt_key == "first_layer_height" || *opt_key == "first_layer_height"
|| *opt_key == "xy_size_compensation" || *opt_key == "xy_size_compensation"
|| *opt_key == "raft_layers") { || *opt_key == "raft_layers") {

View File

@ -200,40 +200,61 @@ void TriangleMesh::translate(float x, float y, float z)
stl_invalidate_shared_vertices(&this->stl); stl_invalidate_shared_vertices(&this->stl);
} }
void TriangleMesh::rotate(float angle, const Axis &axis)
{
// admesh uses degrees
angle = Slic3r::Geometry::rad2deg(angle);
if (axis == X) {
stl_rotate_x(&(this->stl), angle);
} else if (axis == Y) {
stl_rotate_y(&(this->stl), angle);
} else if (axis == Z) {
stl_rotate_z(&(this->stl), angle);
}
stl_invalidate_shared_vertices(&this->stl);
}
void TriangleMesh::rotate_x(float angle) void TriangleMesh::rotate_x(float angle)
{ {
stl_rotate_x(&(this->stl), angle); this->rotate(angle, X);
stl_invalidate_shared_vertices(&this->stl);
} }
void TriangleMesh::rotate_y(float angle) void TriangleMesh::rotate_y(float angle)
{ {
stl_rotate_y(&(this->stl), angle); this->rotate(angle, Y);
stl_invalidate_shared_vertices(&this->stl);
} }
void TriangleMesh::rotate_z(float angle) void TriangleMesh::rotate_z(float angle)
{ {
stl_rotate_z(&(this->stl), angle); this->rotate(angle, Z);
}
void TriangleMesh::flip(const Axis &axis)
{
if (axis == X) {
stl_mirror_yz(&this->stl);
} else if (axis == Y) {
stl_mirror_xz(&this->stl);
} else if (axis == Z) {
stl_mirror_xy(&this->stl);
}
stl_invalidate_shared_vertices(&this->stl); stl_invalidate_shared_vertices(&this->stl);
} }
void TriangleMesh::flip_x() void TriangleMesh::flip_x()
{ {
stl_mirror_yz(&this->stl); this->flip(X);
stl_invalidate_shared_vertices(&this->stl);
} }
void TriangleMesh::flip_y() void TriangleMesh::flip_y()
{ {
stl_mirror_xz(&this->stl); this->flip(Y);
stl_invalidate_shared_vertices(&this->stl);
} }
void TriangleMesh::flip_z() void TriangleMesh::flip_z()
{ {
stl_mirror_xy(&this->stl); this->flip(Z);
stl_invalidate_shared_vertices(&this->stl);
} }
void TriangleMesh::align_to_origin() void TriangleMesh::align_to_origin()

View File

@ -32,9 +32,11 @@ class TriangleMesh
void scale(float factor); void scale(float factor);
void scale(const Pointf3 &versor); void scale(const Pointf3 &versor);
void translate(float x, float y, float z); void translate(float x, float y, float z);
void rotate(float angle, const Axis &axis);
void rotate_x(float angle); void rotate_x(float angle);
void rotate_y(float angle); void rotate_y(float angle);
void rotate_z(float angle); void rotate_z(float angle);
void flip(const Axis &axis);
void flip_x(); void flip_x();
void flip_y(); void flip_y();
void flip_z(); void flip_z();

View File

@ -6,7 +6,7 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#define SLIC3R_VERSION "1.2.7-dev" #define SLIC3R_VERSION "1.2.8-dev"
#define EPSILON 1e-4 #define EPSILON 1e-4
#define SCALING_FACTOR 0.000001 #define SCALING_FACTOR 0.000001
@ -17,7 +17,12 @@
typedef long coord_t; typedef long coord_t;
typedef double coordf_t; typedef double coordf_t;
namespace Slic3r {} namespace Slic3r {
// TODO: make sure X = 0
enum Axis { X, Y, Z };
}
using namespace Slic3r; using namespace Slic3r;
/* Implementation of CONFESS("foo"): */ /* Implementation of CONFESS("foo"): */

View File

@ -4,7 +4,7 @@ use strict;
use warnings; use warnings;
use Slic3r::XS; use Slic3r::XS;
use Test::More tests => 22; use Test::More tests => 24;
my $point = Slic3r::Point->new(10, 15); my $point = Slic3r::Point->new(10, 15);
is_deeply [ @$point ], [10, 15], 'point roundtrip'; is_deeply [ @$point ], [10, 15], 'point roundtrip';
@ -32,12 +32,14 @@ ok !$point->coincides_with($point2), 'coincides_with';
{ {
my $line = Slic3r::Line->new([0,0], [100,0]); my $line = Slic3r::Line->new([0,0], [100,0]);
is +Slic3r::Point->new(0,0)->distance_to_line($line), 0, 'distance_to_line()'; is +Slic3r::Point->new(0,0) ->distance_to_line($line), 0, 'distance_to_line()';
is +Slic3r::Point->new(100,0)->distance_to_line($line), 0, 'distance_to_line()'; is +Slic3r::Point->new(100,0)->distance_to_line($line), 0, 'distance_to_line()';
is +Slic3r::Point->new(50,0)->distance_to_line($line), 0, 'distance_to_line()'; is +Slic3r::Point->new(50,0) ->distance_to_line($line), 0, 'distance_to_line()';
is +Slic3r::Point->new(150,0)->distance_to_line($line), 50, 'distance_to_line()'; is +Slic3r::Point->new(150,0)->distance_to_line($line), 50, 'distance_to_line()';
is +Slic3r::Point->new(0,50)->distance_to_line($line), 50, 'distance_to_line()'; is +Slic3r::Point->new(0,50) ->distance_to_line($line), 50, 'distance_to_line()';
is +Slic3r::Point->new(50,50)->distance_to_line($line), 50, 'distance_to_line()'; is +Slic3r::Point->new(50,50)->distance_to_line($line), 50, 'distance_to_line()';
is +Slic3r::Point->new(50,50) ->perp_distance_to_line($line), 50, 'perp_distance_to_line()';
is +Slic3r::Point->new(150,50)->perp_distance_to_line($line), 50, 'perp_distance_to_line()';
} }
{ {

View File

@ -4,7 +4,7 @@ use strict;
use warnings; use warnings;
use Slic3r::XS; use Slic3r::XS;
use Test::More tests => 21; use Test::More tests => 18;
my $points = [ my $points = [
[100, 100], [100, 100],
@ -88,7 +88,8 @@ is_deeply $polyline->pp, [ @$points, @$points ], 'append_polyline';
is scalar(@$p2), 4, 'split_at'; is scalar(@$p2), 4, 'split_at';
} }
{ # disabled because we now use a more efficient but incomplete algorithm
if (0) {
my $polyline = Slic3r::Polyline->new( my $polyline = Slic3r::Polyline->new(
map [$_,10], (0,10,20,30,40,50,60) map [$_,10], (0,10,20,30,40,50,60)
); );

View File

@ -4,7 +4,7 @@ use strict;
use warnings; use warnings;
use Slic3r::XS; use Slic3r::XS;
use Test::More tests => 16; use Test::More tests => 18;
my $points = [ my $points = [
[100, 100], [100, 100],
@ -87,4 +87,11 @@ is scalar(@{$collection->[1]}), 1, 'appended collection was duplicated';
pass 'chained_path with no_sort'; pass 'chained_path with no_sort';
} }
{
my $coll2 = $collection->clone;
ok !$coll2->no_sort, 'expected no_sort value';
$coll2->no_sort(1);
ok $coll2->clone->no_sort, 'no_sort is kept after clone';
}
__END__ __END__

View File

@ -4,7 +4,7 @@ use strict;
use warnings; use warnings;
use Slic3r::XS; use Slic3r::XS;
use Test::More tests => 8; use Test::More tests => 9;
use constant PI => 4 * atan2(1, 1); use constant PI => 4 * atan2(1, 1);
@ -31,4 +31,11 @@ use constant PI => 4 * atan2(1, 1);
ok !Slic3r::Geometry::directions_parallel_within(PI/2, PI, 0), 'directions_parallel_within'; ok !Slic3r::Geometry::directions_parallel_within(PI/2, PI, 0), 'directions_parallel_within';
ok !Slic3r::Geometry::directions_parallel_within(PI/2, PI, PI/180), 'directions_parallel_within'; ok !Slic3r::Geometry::directions_parallel_within(PI/2, PI, PI/180), 'directions_parallel_within';
} }
{
my $positions = Slic3r::Geometry::arrange(4, Slic3r::Pointf->new(20, 20),
5, Slic3r::Geometry::BoundingBoxf->new);
is scalar(@$positions), 4, 'arrange() returns expected number of positions';
}
__END__ __END__

View File

@ -7,6 +7,8 @@
%name{Slic3r::ExtrusionPath::Collection} class ExtrusionEntityCollection { %name{Slic3r::ExtrusionPath::Collection} class ExtrusionEntityCollection {
%name{_new} ExtrusionEntityCollection(); %name{_new} ExtrusionEntityCollection();
Clone<ExtrusionEntityCollection> clone()
%code{% RETVAL = THIS->clone(); %};
void reverse(); void reverse();
void clear() void clear()
%code{% THIS->entities.clear(); %}; %code{% THIS->entities.clear(); %};
@ -24,6 +26,8 @@
Clone<Point> last_point(); Clone<Point> last_point();
int count() int count()
%code{% RETVAL = THIS->entities.size(); %}; %code{% RETVAL = THIS->entities.size(); %};
int items_count()
%code{% RETVAL = THIS->items_count(); %};
bool empty() bool empty()
%code{% RETVAL = THIS->entities.empty(); %}; %code{% RETVAL = THIS->entities.empty(); %};
std::vector<size_t> orig_indices() std::vector<size_t> orig_indices()

View File

@ -8,6 +8,9 @@
%package{Slic3r::Geometry}; %package{Slic3r::Geometry};
Pointfs arrange(size_t total_parts, Pointf* part, coordf_t dist, BoundingBoxf* bb)
%code{% RETVAL = Slic3r::Geometry::arrange(total_parts, *part, dist, *bb); %};
%{ %{
bool bool
@ -87,4 +90,17 @@ simplify_polygons(polygons, tolerance)
OUTPUT: OUTPUT:
RETVAL RETVAL
IV
_constant()
ALIAS:
X = X
Y = Y
Z = Z
PROTOTYPE:
CODE:
RETVAL = ix;
OUTPUT: RETVAL
%} %}

View File

@ -71,6 +71,7 @@
%code%{ RETVAL = (int)(intptr_t)THIS; %}; %code%{ RETVAL = (int)(intptr_t)THIS; %};
void make_slices(); void make_slices();
void merge_slices();
bool any_internal_region_slice_contains_polyline(Polyline* polyline) bool any_internal_region_slice_contains_polyline(Polyline* polyline)
%code%{ RETVAL = THIS->any_internal_region_slice_contains(*polyline); %}; %code%{ RETVAL = THIS->any_internal_region_slice_contains(*polyline); %};
bool any_bottom_region_slice_contains_polyline(Polyline* polyline) bool any_bottom_region_slice_contains_polyline(Polyline* polyline)

View File

@ -182,6 +182,8 @@ ModelMaterial::attributes()
void translate(double x, double y, double z); void translate(double x, double y, double z);
void scale_xyz(Pointf3* versor) void scale_xyz(Pointf3* versor)
%code{% THIS->scale(*versor); %}; %code{% THIS->scale(*versor); %};
void rotate(float angle, Axis axis);
void flip(Axis axis);
Model* cut(double z) Model* cut(double z)
%code%{ %code%{

View File

@ -14,6 +14,8 @@
void apply_config(DynamicPrintConfig *config) void apply_config(DynamicPrintConfig *config)
%code%{ THIS->apply_config(*config); %}; %code%{ THIS->apply_config(*config); %};
void set(std::string key, std::string value); void set(std::string key, std::string value);
void set_multiple(std::string key, std::vector<std::string> values)
%code%{ THIS->set(key, values); %};
void _single_set(std::string k, std::string v) void _single_set(std::string k, std::string v)
%code%{ THIS->_single[k] = v; %}; %code%{ THIS->_single[k] = v; %};

View File

@ -161,6 +161,22 @@ _constant()
void set_step_started(PrintStep step) void set_step_started(PrintStep step)
%code%{ THIS->state.set_started(step); %}; %code%{ THIS->state.set_started(step); %};
std::vector<int> object_extruders()
%code%{
std::set<size_t> extruders = THIS->object_extruders();
RETVAL.reserve(extruders.size());
for (std::set<size_t>::const_iterator e = extruders.begin(); e != extruders.end(); ++e) {
RETVAL.push_back(*e);
}
%};
std::vector<int> support_material_extruders()
%code%{
std::set<size_t> extruders = THIS->support_material_extruders();
RETVAL.reserve(extruders.size());
for (std::set<size_t>::const_iterator e = extruders.begin(); e != extruders.end(); ++e) {
RETVAL.push_back(*e);
}
%};
std::vector<int> extruders() std::vector<int> extruders()
%code%{ %code%{
std::set<size_t> extruders = THIS->extruders(); std::set<size_t> extruders = THIS->extruders();

View File

@ -188,6 +188,7 @@ Clone<BridgeDetector> O_OBJECT_SLIC3R_T
GLVertexArray* O_OBJECT_SLIC3R GLVertexArray* O_OBJECT_SLIC3R
Axis T_UV
ExtrusionLoopRole T_UV ExtrusionLoopRole T_UV
ExtrusionRole T_UV ExtrusionRole T_UV
FlowRole T_UV FlowRole T_UV

View File

@ -176,6 +176,12 @@
%typemap{SupportLayerPtrs*}; %typemap{SupportLayerPtrs*};
%typemap{Axis}{parsed}{
%cpp_type{Axis};
%precall_code{%
$CVar = (Axis)SvUV($PerlVar);
%};
};
%typemap{SurfaceType}{parsed}{ %typemap{SurfaceType}{parsed}{
%cpp_type{SurfaceType}; %cpp_type{SurfaceType};
%precall_code{% %precall_code{%