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)
--retract-lift Lift Z by the given distance in mm when retracting (default: 0)
--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)
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;
our $var = "$FindBin::Bin/var";
use Encode;
use Encode::Locale;
use Moo 1.003001;
use Slic3r::XS; # import all symbols (constants etc.) before they get parsed
@ -262,13 +260,17 @@ sub resume_all_threads {
}
sub encode_path {
my ($filename) = @_;
return encode('locale_fs', $filename);
my ($path) = @_;
utf8::downgrade($path) if $^O eq 'MSWin32';
return $path;
}
sub decode_path {
my ($filename) = @_;
return decode('locale_fs', $filename);
my ($path) = @_;
utf8::upgrade($path) if $^O eq 'MSWin32';
return $path;
}
sub open {

View File

@ -6,6 +6,8 @@ extends 'Slic3r::Fill::Base';
use Slic3r::Geometry qw(scale unscale X);
use Slic3r::Geometry::Clipper qw(offset offset2 union_pt_chained);
sub no_sort { 1 }
sub fill_surface {
my $self = shift;
my ($surface, %params) = @_;
@ -36,7 +38,7 @@ sub fill_surface {
@loops = map Slic3r::Polygon->new(@$_),
reverse @{union_pt_chained(\@loops)};
# order paths using a nearest neighbor search
# split paths using a nearest neighbor search
my @paths = ();
my $last_pos = Slic3r::Point->new(0,0);
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_cooling_markers' => (is =>'rw', default => sub {0});
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 '_seam_position' => (is => 'ro', default => sub { {} }); # $object => pos
has 'first_layer' => (is => 'rw', default => sub {0}); # this flag triggers first layer speeds
@ -76,7 +76,7 @@ sub change_layer {
my ($self, $layer) = @_;
$self->layer($layer);
$self->_layer_index($self->_layer_index + 1);
$self->layer_index($self->layer_index + 1);
$self->first_layer($layer->id == 0);
# avoid computing islands and overhangs if they're not needed
@ -88,14 +88,14 @@ sub change_layer {
my $gcode = "";
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
if ($self->config->get_at('retract_layer_change', $self->writer->extruder->id) && $self->writer->will_move_z($z)) {
$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
$self->wipe->path(undef);

View File

@ -81,7 +81,7 @@ sub OnInit {
$self->{notifier} = Slic3r::GUI::Notifier->new;
# 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);
Slic3r::debugf "Data directory: %s\n", $datadir;
@ -297,7 +297,7 @@ sub open_model {
$dialog->Destroy;
return;
}
my @input_files = $dialog->GetPaths;
my @input_files = map Slic3r::decode_path($_), $dialog->GetPaths;
$dialog->Destroy;
return @input_files;

View File

@ -593,7 +593,7 @@ sub Resize {
-$x/2, $x/2, -$y/2, $y/2,
-$depth, 2*$depth,
);
glMatrixMode(GL_MODELVIEW);
}
@ -937,7 +937,7 @@ package Slic3r::GUI::3DScene;
use base qw(Slic3r::GUI::3DScene::Base);
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::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 {
my ($self, $object) = @_;

View File

@ -3,7 +3,7 @@ use strict;
use warnings;
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::Print;
use Wx::Html;
@ -12,7 +12,7 @@ use base 'Wx::Dialog';
sub new {
my $class = shift;
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);
my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);

View File

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

View File

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

View File

@ -178,6 +178,11 @@ sub _build_field {
parent => $self->parent,
option => $opt,
);
} elsif ($type eq 'color') {
$field = Slic3r::GUI::OptionsGroup::Field::ColourPicker->new(
parent => $self->parent,
option => $opt,
);
} elsif ($type =~ /^(f|s|s@|percent)$/) {
$field = Slic3r::GUI::OptionsGroup::Field::TextCtrl->new(
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 'on_change' => (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 });
# 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::Event qw(EVT_SPINCTRL EVT_TEXT EVT_KILL_FOCUS);
has 'tmp_value' => (is => 'rw');
sub BUILD {
my ($self) = @_;
@ -139,13 +140,27 @@ sub BUILD {
$self->_on_change($self->option->opt_id);
});
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->tmp_value(undef);
});
EVT_KILL_FOCUS($field, sub {
$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;
use Moo;
@ -261,6 +276,9 @@ use List::Util qw(first);
use Wx qw(wxTheApp :misc :combobox);
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 {
my ($self) = @_;
@ -274,15 +292,15 @@ sub BUILD {
my $disable_change_event = $self->disable_change_event;
$self->disable_change_event(1);
my $value = $field->GetSelection;
my $idx = $field->GetSelection; # get index of selected value
my $label;
if ($self->option->values) {
$label = $value = $self->option->values->[$value];
} elsif ($value <= $#{$self->option->labels}) {
$label = $self->option->labels->[$value];
if ($self->option->labels && $idx <= $#{$self->option->labels}) {
$label = $self->option->labels->[$idx];
} elsif ($self->option->values && $idx <= $#{$self->option->values}) {
$label = $self->option->values->[$idx];
} else {
$label = $value;
$label = $idx;
}
# 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);
return;
}
}
if ($self->option->labels && $value <= $#{$self->option->labels}) {
} elsif ($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]);
$self->disable_change_event(0);
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;
use Moo;
extends 'Slic3r::GUI::OptionsGroup::Field';

View File

@ -4,14 +4,14 @@ use warnings;
use utf8;
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 threads::shared qw(shared_clone);
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
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 constant TB_ADD => &Wx::NewId;
@ -36,7 +36,7 @@ our $ERROR_EVENT : shared = Wx::NewEventType;
our $EXPORT_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
my $PreventListEvents = 0;
@ -335,12 +335,11 @@ sub new {
for my $group (qw(print filament printer)) {
my $text = Wx::StaticText->new($self, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT);
$text->SetFont($Slic3r::GUI::small_font);
my $choice = Wx::Choice->new($self, -1, wxDefaultPosition, wxDefaultSize, []);
$choice->SetFont($Slic3r::GUI::small_font);
my $choice = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY);
$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($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 {
my $self = shift;
my ($group, $items, $selected) = @_;
my ($group, $presets, $selected) = @_;
foreach my $choice (@{ $self->{preset_choosers}{$group} }) {
my $sel = $choice->GetSelection;
$choice->Clear;
$choice->Append($_) for @$items;
$choice->SetSelection($sel) if $sel <= $#$items;
foreach my $preset (@$presets) {
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);
}
@ -497,8 +516,11 @@ sub load_model_objects {
my ($self, @model_objects) = @_;
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 $scaled_down = 0;
my @obj_idx = ();
foreach my $model_object (@model_objects) {
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->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}->add_model_object($o);
@ -526,14 +558,15 @@ sub load_model_objects {
$need_arrange = 0;
}
$self->objects_loaded(\@obj_idx, no_arrange => !$need_arrange);
}
sub objects_loaded {
my $self = shift;
my ($obj_idxs, %params) = @_;
if ($scaled_down) {
Slic3r::GUI::show_info(
$self,
'Your object appears to be too large, so it was automatically scaled down to fit your print bed.',
'Object too large?',
);
}
foreach my $obj_idx (@$obj_idxs) {
foreach my $obj_idx (@obj_idx) {
my $object = $self->{objects}[$obj_idx];
my $model_object = $self->{model}->objects->[$obj_idx];
$self->{list}->InsertStringItem($obj_idx, $object->name);
@ -545,7 +578,7 @@ sub objects_loaded {
$self->make_thumbnail($obj_idx);
}
$self->arrange unless $params{no_arrange};
$self->arrange if $need_arrange;
$self->update;
# zoom to objects
@ -553,7 +586,7 @@ sub objects_loaded {
if $self->{canvas3D};
$self->{list}->Update;
$self->{list}->Select($obj_idxs->[-1], 1);
$self->{list}->Select($obj_idx[-1], 1);
$self->object_list_changed;
$self->schedule_background_process;
@ -691,6 +724,7 @@ sub rotate {
my $self = shift;
my ($angle, $axis) = @_;
# angle is in degrees
$axis //= Z;
my ($obj_idx, $object) = $self->selected_object;
@ -712,14 +746,14 @@ sub rotate {
$self->stop_background_process;
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 };
$object->transform_thumbnail($self->{model}, $obj_idx);
} else {
# rotation around X and Y needs to be performed on mesh
# so we first apply any Z rotation
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 };
}
$model_object->rotate(deg2rad($angle), $axis);
@ -749,7 +783,7 @@ sub flip {
# apply Z rotation before flipping
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 };
}
@ -788,7 +822,7 @@ sub changescale {
# apply Z rotation before scaling
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 };
}
@ -1054,9 +1088,10 @@ sub export_gcode {
$dlg->Destroy;
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;
$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;
}
@ -1066,6 +1101,11 @@ sub export_gcode {
$self->statusbar->SetCancelCallback(sub {
$self->stop_background_process;
$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
@ -1275,7 +1315,7 @@ sub _get_export_file {
$dlg->Destroy;
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;
}
return $output_file;
@ -1344,11 +1384,10 @@ sub on_extruders_change {
my $choices = $self->{preset_choosers}{filament};
while (@$choices < $num_extruders) {
my @presets = $choices->[0]->GetStrings;
push @$choices, Wx::Choice->new($self, -1, wxDefaultPosition, [150, -1], [@presets]);
$choices->[-1]->SetFont($Slic3r::GUI::small_font);
push @$choices, Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [@presets], wxCB_READONLY);
$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);
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;
$choices->[-1]->SetSelection($i || 0);
}
@ -1616,26 +1655,27 @@ sub object_menu {
my $menu = Wx::Menu->new;
$frame->_append_menu_item($menu, "Delete\tCtrl+Del", 'Remove the selected object', sub {
$self->remove;
});
}, undef, 'brick_delete.png');
$frame->_append_menu_item($menu, "Increase copies\tCtrl++", 'Place one more copy of the selected object', sub {
$self->increase;
});
}, undef, 'add.png');
$frame->_append_menu_item($menu, "Decrease copies\tCtrl+-", 'Remove one copy of the selected object', sub {
$self->decrease;
});
}, undef, 'delete.png');
$frame->_append_menu_item($menu, "Set number of copies…", 'Change the number of copies of the selected object', sub {
$self->set_number_of_copies;
});
}, undef, 'textfield.png');
$menu->AppendSeparator();
$frame->_append_menu_item($menu, "Rotate 45° clockwise", 'Rotate the selected object by 45° clockwise', sub {
$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 {
$self->rotate(+45);
});
}, undef, 'arrow_rotate_anticlockwise.png');
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 {
$self->rotate(undef, X);
});
@ -1647,7 +1687,8 @@ sub object_menu {
});
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 {
$self->flip(X);
});
@ -1659,7 +1700,8 @@ sub object_menu {
});
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 {
$self->changescale(undef);
});
@ -1675,18 +1717,18 @@ sub object_menu {
$frame->_append_menu_item($menu, "Split", 'Split the selected object into individual parts', sub {
$self->split_object;
});
}, undef, 'shape_ungroup.png');
$frame->_append_menu_item($menu, "Cut…", 'Open the 3D cutting tool', sub {
$self->object_cut_dialog;
});
}, undef, 'package.png');
$menu->AppendSeparator();
$frame->_append_menu_item($menu, "Settings…", 'Open the object editor dialog', sub {
$self->object_settings_dialog;
});
}, undef, 'cog.png');
$menu->AppendSeparator();
$frame->_append_menu_item($menu, "Export object as STL…", 'Export this single object as STL file', sub {
$self->export_object_stl;
});
}, undef, 'brick_go.png');
return $menu;
}
@ -1763,7 +1805,7 @@ sub transform_thumbnail {
# the order of these transformations MUST be the same everywhere, including
# in Slic3r::Print->add_model_object()
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);
$self->transformed_thumbnail($t);

View File

@ -84,6 +84,7 @@ sub reload_print {
}
$self->{canvas}->bb($self->print->total_bounding_box);
$self->{canvas}->_dirty(1);
my %z = (); # z => 1
foreach my $object (@{$self->{print}->objects}) {
@ -115,15 +116,23 @@ sub set_z {
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 base qw(Wx::GLCanvas Class::Accessor);
use Wx::GLCanvas qw(:all);
use List::Util qw(min first);
use Slic3r::Geometry qw(scale unscale epsilon);
use List::Util qw(min max first);
use Slic3r::Geometry qw(scale unscale epsilon X Y);
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
{
@ -136,20 +145,99 @@ sub new {
my $self = $class->SUPER::new($parent);
$self->print($print);
$self->_zoom(1);
# 2D point in model space
$self->_camera_target(Slic3r::Pointf->new(0,0));
EVT_PAINT($self, sub {
my $dc = Wx::PaintDC->new($self);
$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;
$self->Resize( $self->GetSizeWH );
$self->Resize;
$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;
}
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 {
my ($self, $z) = @_;
@ -197,22 +285,6 @@ sub Render {
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);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
@ -398,12 +470,71 @@ sub SetCurrent {
}
sub Resize {
my ($self, $x, $y) = @_;
my ($self) = @_;
return unless $self->GetContext;
return unless $self->bb;
$self->_dirty(0);
$self->SetCurrent($self->GetContext);
my ($x, $y) = $self->GetSizeWH;
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 {

View File

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

View File

@ -161,6 +161,7 @@ sub perform_cut {
push @{$self->{new_model_objects}}, $lower_object;
if ($self->{cut_options}{rotate_lower}) {
$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 {
my ($self, $event) = @_;
return if $self->{disable_tree_sel_changed_event};
$self->selection_changed;
});
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 $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);
$self->{disable_tree_sel_changed_event} = 0;
my $selectedId = $rootId;
foreach my $volume_id (0..$#{$object->volumes}) {
@ -136,9 +144,13 @@ sub reload_tree {
}
$tree->ExpandAll;
# This will trigger the selection_changed() event
Slic3r::GUI->CallAfter(sub {
$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 {
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};
}
@ -360,10 +360,10 @@ sub load_presets {
$self->current_preset(undef);
$self->{presets_choice}->Clear;
$self->{presets_choice}->Append($_->{name}) for @{$self->{presets}};
$self->{presets_choice}->Append($_->name) for @{$self->{presets}};
{
# load last used preset
my $i = first { basename($self->{presets}[$_]{file}) eq ($Slic3r::GUI::Settings->{presets}{$self->name} || '') } 1 .. $#{$self->{presets}};
my $i = first { basename($self->{presets}[$_]->file) eq ($Slic3r::GUI::Settings->{presets}{$self->name} || '') } 1 .. $#{$self->{presets}};
$self->select_preset($i || 0);
}
$self->_on_presets_changed;
@ -813,7 +813,7 @@ sub build {
my $self = shift;
$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
fan_always_on cooling
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 $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('extrusion_multiplier', 0);
}

View File

@ -27,9 +27,6 @@ our @EXPORT_OK = qw(
use constant PI => 4 * atan2(1, 1);
use constant A => 0;
use constant B => 1;
use constant X => 0;
use constant Y => 1;
use constant Z => 2;
use constant X1 => 0;
use constant Y1 => 1;
use constant X2 => 2;
@ -605,119 +602,4 @@ sub douglas_peucker2 {
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;

View File

@ -32,11 +32,6 @@ sub regions {
return [ map $self->get_region($_), 0..($self->region_count-1) ];
}
sub merge_slices {
my ($self) = @_;
$_->merge_slices for @{$self->regions};
}
sub make_perimeters {
my $self = shift;
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);
$loop_number--; # 0-indexed loops
my @gaps = (); # ExPolygons
my @gaps = (); # Polygons
my @last = @{$surface->expolygon->simplify_p(&Slic3r::SCALED_RESOLUTION)};
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
# (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)};
# the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
@thin_walls = grep $_->length > $pwidth*2,
map @{$_->medial_axis($pwidth + $pspacing, $min_width)}, @thin_walls;
@thin_walls = grep $_->length > $ext_pwidth*2,
map @{$_->medial_axis($ext_pwidth + $ext_pspacing, $min_width)}, @thin_walls;
Slic3r::debugf " %d thin walls detected\n", scalar(@thin_walls) if $Slic3r::debug;
if (0) {
@ -174,10 +174,10 @@ sub process {
# (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
my $diff = diff_ex(
offset(\@last, -0.5*$pspacing),
offset(\@offsets, +0.5*$pspacing + 10), # safety offset
offset(\@last, -0.5*$distance),
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";
Slic3r::SVG::output(
"gaps.svg",
expolygons => \@gaps,
expolygons => union_ex(\@gaps),
);
}
# where $pwidth < thickness < 2*$pspacing, infill with width = 1.5*$pwidth
# where 0.5*$pwidth < thickness < $pwidth, infill with width = 0.5*$pwidth
# where $pwidth < thickness < 2*$pspacing, infill with width = 2*$pwidth
# where 0.1*$pwidth < thickness < $pwidth, infill with width = 1*$pwidth
my @gap_sizes = (
[ $pwidth, 2*$pspacing, unscale 1.5*$pwidth ],
[ 0.1*$pwidth, $pwidth, unscale 0.5*$pwidth ],
[ $pwidth, 2*$pspacing, unscale 2*$pwidth ],
[ 0.1*$pwidth, $pwidth, unscale 1*$pwidth ],
);
foreach my $gap_size (@gap_sizes) {
my @gap_fill = $self->_fill_gaps(@$gap_size, \@gaps);
$self->gap_fill->append($_) for @gap_fill;
# 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).
# Growing actual extrusions ensures that gaps not filled by medial axis
@ -304,6 +304,7 @@ sub process {
->grow(scale $w/2)};
} @gap_fill;
@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);
my $this = diff_ex(
offset2([ map @$_, @$gaps ], -$min/2, +$min/2),
offset2([ map @$_, @$gaps ], -$max/2, +$max/2),
offset2($gaps, -$min/2, +$min/2),
offset2($gaps, -$max/2, +$max/2),
1,
);

View File

@ -150,15 +150,19 @@ sub duplicate {
sub _arrange {
my ($self, $sizes, $distance, $bb) = @_;
$bb //= Slic3r::Geometry::BoundingBoxf->new;
# we supply unscaled data to arrange()
return Slic3r::Geometry::arrange(
return @{Slic3r::Geometry::arrange(
scalar(@$sizes), # number of parts
max(map $_->x, @$sizes), # cell width
max(map $_->y, @$sizes), # cell height ,
Slic3r::Pointf->new(
max(map $_->x, @$sizes), # cell width
max(map $_->y, @$sizes), # cell height ,
),
$distance, # distance between cells
$bb, # bounding box of the area to fill (can be undef)
);
)};
}
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 {
my $self = shift;

View File

@ -225,8 +225,8 @@ sub make_skirt {
my $skirt_height_z = -1;
foreach my $object (@{$self->objects}) {
my $skirt_height = $self->has_infinite_skirt
? scalar(@{$object->layers})
: min($self->config->skirt_height, scalar(@{$object->layers}));
? $object->layer_count
: min($self->config->skirt_height, $object->layer_count);
my $highest_layer = $object->get_layer($skirt_height - 1);
$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_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 output path is an existing directory, we take that and append
# the specified filename format

View File

@ -196,6 +196,7 @@ sub export {
if ($finished_objects > 0) {
$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->avoid_crossing_perimeters->use_external_mp_once(1);
print $fh $gcodegen->retract;
print $fh $gcodegen->travel_to(
Slic3r::Point->new(0,0),
@ -203,6 +204,9 @@ sub export {
'move to origin position for next object',
);
$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};
@ -305,8 +309,8 @@ sub process_layer {
($layer->id > 0 || $self->print->config->brim_width == 0)
&& ($layer->id >= $self->print->config->skirt_height && !$self->print->has_infinite_skirt)
&& !defined(first { $_->config->bottom_solid_layers > $layer->id } @{$layer->regions})
&& !defined(first { @{$_->perimeters} > 1 } @{$layer->regions})
&& !defined(first { @{$_->fills} > 0 } @{$layer->regions})
&& !defined(first { $_->perimeters->items_count > 1 } @{$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
$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,
}) . "\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, {
layer_num => $layer->id,
layer_num => $self->_gcodegen->layer_index,
layer_z => $layer->print_z,
}) . "\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)
&& !$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->avoid_crossing_perimeters->use_external_mp(1);
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
my $first_layer_height = $self->config->get_value('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
# so we compute the average of all of them
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);
# compute the average of all nozzles used for printing the object
my $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
# (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;
}

View File

@ -15,6 +15,7 @@ use POSIX qw(setlocale LC_NUMERIC);
use Slic3r;
use Time::HiRes qw(gettimeofday tv_interval);
$|++;
binmode STDOUT, ':utf8';
our %opt = ();
my %cli_options = ();
@ -93,7 +94,7 @@ my $gui;
if ((!@ARGV || $opt{gui}) && !$opt{save} && eval "require Slic3r::GUI; 1") {
{
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::mode = $opt{gui_mode};
$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($cli_config);
foreach my $input_file (@ARGV) {
$input_file = Slic3r::decode_path($input_file);
$gui->{mainframe}{plater}->load_file($input_file) unless $opt{no_plater};
}
$gui->MainLoop;
@ -434,7 +436,7 @@ $j
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-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)
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 warnings;
@ -7,6 +7,7 @@ BEGIN {
use lib "$FindBin::Bin/../lib";
}
use List::Util qw(first);
use Slic3r;
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__

View File

@ -1,4 +1,4 @@
use Test::More tests => 22;
use Test::More tests => 23;
use strict;
use warnings;
@ -200,4 +200,21 @@ use Slic3r::Test;
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__

View File

@ -1,4 +1,4 @@
use Test::More tests => 25;
use Test::More tests => 27;
use strict;
use warnings;
@ -221,12 +221,41 @@ use Slic3r::Test;
{
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('layer_height', 0.1);
$config->set('first_layer_height', 0.8);
$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);
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__

View File

@ -1,4 +1,4 @@
use Test::More tests => 14;
use Test::More tests => 21;
use strict;
use warnings;
@ -133,4 +133,40 @@ if (0) {
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__

View File

@ -33,7 +33,6 @@ my %opt = ();
# load config
my $config = Slic3r::Config->new_from_defaults;
$config->set('skirts', 0);
if ($opt{load}) {
$config->apply(Slic3r::Config->load($opt{load}));
}
@ -84,6 +83,7 @@ sub OnInit {
if ($d3) {
$canvas = Slic3r::GUI::3DScene->new($panel);
$canvas->set_bed_shape($print->config->bed_shape);
$canvas->load_print_toolpaths($print);
foreach my $object (@{$print->objects}) {
#$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) {};
};
class ConfigOptionVectorBase : public ConfigOption {
public:
virtual ~ConfigOptionVectorBase() {};
virtual std::vector<std::string> vserialize() const = 0;
};
template <class T>
class ConfigOptionVector
class ConfigOptionVector : public ConfigOptionVectorBase
{
public:
virtual ~ConfigOptionVector() {};
@ -63,7 +69,7 @@ class ConfigOptionFloat : public ConfigOption
};
};
class ConfigOptionFloats : public ConfigOption, public ConfigOptionVector<double>
class ConfigOptionFloats : public ConfigOptionVector<double>
{
public:
@ -76,6 +82,16 @@ class ConfigOptionFloats : public ConfigOption, public ConfigOptionVector<double
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) {
this->values.clear();
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:
@ -125,6 +141,16 @@ class ConfigOptionInts : public ConfigOption, public ConfigOptionVector<int>
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) {
this->values.clear();
std::istringstream is(str);
@ -174,7 +200,7 @@ class ConfigOptionString : public ConfigOption
};
// semicolon-separated strings
class ConfigOptionStrings : public ConfigOption, public ConfigOptionVector<std::string>
class ConfigOptionStrings : public ConfigOptionVector<std::string>
{
public:
@ -187,6 +213,10 @@ class ConfigOptionStrings : public ConfigOption, public ConfigOptionVector<std::
return ss.str();
};
std::vector<std::string> vserialize() const {
return this->values;
};
bool deserialize(std::string str) {
this->values.clear();
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:
@ -294,6 +324,16 @@ class ConfigOptionPoints : public ConfigOption, public ConfigOptionVector<Pointf
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) {
this->values.clear();
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:
@ -345,6 +385,16 @@ class ConfigOptionBools : public ConfigOption, public ConfigOptionVector<bool>
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) {
this->values.clear();
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
for (Polylines::iterator polyline = polylines->begin(); polyline != polylines->end(); ++polyline) {
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_end(max_width);
}
// clip again after extending endpoints to prevent them from exceeding the expolygon boundaries
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

View File

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

View File

@ -86,7 +86,7 @@ ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEntityCo
Points endpoints;
for (ExtrusionEntitiesPtr::iterator it = my_paths.begin(); it != my_paths.end(); ++it) {
endpoints.push_back((*it)->first_point());
if (no_reverse) {
if (no_reverse || !(*it)->can_reverse()) {
endpoints.push_back((*it)->first_point());
} else {
endpoints.push_back((*it)->last_point());
@ -99,7 +99,7 @@ ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEntityCo
int path_index = start_index/2;
ExtrusionEntity* entity = my_paths.at(path_index);
// 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();
}
retval->entities.push_back(my_paths.at(path_index));
@ -121,6 +121,22 @@ ExtrusionEntityCollection::grow() const
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
// there is no ExtrusionLoop::Collection or ExtrusionEntity::Collection
REGISTER_CLASS(ExtrusionEntityCollection, "ExtrusionPath::Collection");

View File

@ -16,6 +16,12 @@ class ExtrusionEntityCollection : public ExtrusionEntity
ExtrusionEntityCollection(): no_sort(false) {};
ExtrusionEntityCollection(const ExtrusionEntityCollection &collection);
ExtrusionEntityCollection& operator= (const ExtrusionEntityCollection &other);
bool is_collection() const {
return true;
};
bool can_reverse() const {
return !this->no_sort;
};
void swap (ExtrusionEntityCollection &c);
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;
@ -23,6 +29,7 @@ class ExtrusionEntityCollection : public ExtrusionEntity
Point first_point() const;
Point last_point() 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);
}
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
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)
// 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();
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
@ -209,61 +339,64 @@ MedialAxis::build(Polylines* polylines)
}
// count valid segments for each vertex
std::map< const VD::vertex_type*,std::set<const VD::edge_type*> > vertex_edges;
std::set<const VD::vertex_type*> entry_nodes;
for (VD::const_vertex_iterator vertex = this->vd.vertices().begin(); vertex != this->vd.vertices().end(); ++vertex) {
// get a reference to the list of valid edges originating from this vertex
std::set<const VD::edge_type*>& edges = vertex_edges[&*vertex];
std::map< vert_t*,std::set<edge_t*> > vertex_edges; // collects edges connected for each vertex
std::set<vert_t*> startpoints; // collects all vertices having a single starting edge
for (VD::const_vertex_iterator it = this->vd.vertices().begin(); it != this->vd.vertices().end(); ++it) {
vert_t* vertex = &*it;
// get one random edge originating from this vertex
const VD::edge_type* edge = vertex->incident_edge();
// loop through all edges originating from this vertex
// starting from a random one
edge_t* edge = vertex->incident_edge();
do {
if (this->edges.count(edge) > 0) // only count valid edges
edges.insert(edge);
edge = edge->rot_next(); // next edge originating from this vertex
// if this edge was not pruned by our filter above,
// add it to vertex_edges
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());
// if there's only one edge starting at this vertex then it's a leaf
size_t edge_count = edges.size();
if (edge_count == 1) {
entry_nodes.insert(&*vertex);
// if there's only one edge starting at this vertex then it's an endpoint
if (vertex_edges[vertex].size() == 1) {
startpoints.insert(vertex);
}
}
// prune recursively
while (!entry_nodes.empty()) {
// prune startpoints recursively if extreme segments are not valid
while (!startpoints.empty()) {
// get a random entry node
const VD::vertex_type* v = *entry_nodes.begin();
vert_t* v = *startpoints.begin();
// get edge starting from v
assert(!vertex_edges[v].empty());
const VD::edge_type* edge = *vertex_edges[v].begin();
assert(vertex_edges[v].size() == 1);
edge_t* edge = *vertex_edges[v].begin();
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->twin());
// 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[v1].erase(edge->twin());
// also, check whether the end vertex is a new leaf
if (vertex_edges[v1].size() == 1) {
entry_nodes.insert(v1);
startpoints.insert(v1);
} 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
entry_nodes.erase(v);
startpoints.erase(v);
}
// iterate through the valid edges to build polylines
while (!this->edges.empty()) {
const VD::edge_type& edge = **this->edges.begin();
edge_t &edge = **this->edges.begin();
// start a polyline
Polyline polyline;
@ -278,13 +411,14 @@ MedialAxis::build(Polylines* polylines)
this->process_edge_neighbors(edge, &polyline.points);
// get previous points
Points pp;
this->process_edge_neighbors(*edge.twin(), &pp);
polyline.points.insert(polyline.points.begin(), pp.rbegin(), pp.rend());
{
Points pp;
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
if (polyline.length() > this->max_width)
polylines->push_back(polyline);
// append polyline to result
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
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 &cell2 = *edge.twin()->cell();
if (cell1.contains_segment() && cell2.contains_segment()) {
Line segment1 = this->retrieve_segment(cell1);
Line segment2 = this->retrieve_segment(cell2);
if (segment1.a == segment2.b || segment1.b == segment2.a) return false;
// calculate relative angle between the two boundary segments
double angle = fabs(segment2.orientation() - segment1.orientation());
// 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)
// so we allow some tolerance (say, 30°)
if (angle < PI*2/3 ) {
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;
if (!cell1.contains_segment() || !cell2.contains_segment()) return false;
const Line &segment1 = this->retrieve_segment(cell1);
const Line &segment2 = this->retrieve_segment(cell2);
// calculate the relative angle between the two boundary segments
double angle = fabs(segment2.orientation() - segment1.orientation());
// 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)
// so we allow some tolerance.
// this filter ensures that we're dealing with a narrow/oriented area (longer than thick)
if (fabs(angle - PI) > PI/5) {
return false;
}
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
{
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);
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 {
public:
Points points;
@ -39,7 +54,7 @@ class MedialAxis {
Line edge_to_line(const VD::edge_type &edge) const;
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;
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>
bool
Layer::any_internal_region_slice_contains(const T &item) const

View File

@ -47,7 +47,7 @@ class LayerRegion
PolylineCollection unsupported_bridge_edges;
// 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;
// ordered collection of extrusion paths to fill surfaces
@ -95,6 +95,7 @@ class Layer {
LayerRegion* add_region(PrintRegion* print_region);
void make_slices();
void merge_slices();
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;

View File

@ -1,4 +1,5 @@
#include "Model.hpp"
#include "Geometry.hpp"
namespace Slic3r {
@ -481,7 +482,12 @@ ModelObject::center_around_origin()
if (!this->instances.empty()) {
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();
}
@ -514,6 +520,26 @@ ModelObject::scale(const Pointf3 &versor)
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
ModelObject::materials_count() const
{

View File

@ -129,6 +129,8 @@ class ModelObject
void translate(const Vectorf3 &vector);
void translate(coordf_t x, coordf_t y, coordf_t z);
void scale(const Pointf3 &versor);
void rotate(float angle, const Axis &axis);
void flip(const Axis &axis);
size_t materials_count() const;
size_t facets_count() const;
bool needed_repair() const;
@ -175,7 +177,7 @@ class ModelInstance
{
friend class ModelObject;
public:
double rotation; // around mesh center point
double rotation; // in radians around mesh center point
double scaling_factor;
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_min;
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->_single["month"] = this->_int_to_string(1 + timeinfo->tm_mon);
this->_single["day"] = this->_int_to_string(timeinfo->tm_mday);
this->_single["hour"] = this->_int_to_string(timeinfo->tm_hour);
this->_single["minute"] = this->_int_to_string(timeinfo->tm_min);
this->_single["second"] = this->_int_to_string(timeinfo->tm_sec);
}
std::string
PlaceholderParser::_int_to_string(int value) const
{
std::ostringstream ss;
ss << value;
return ss.str();
this->set("year", 1900 + timeinfo->tm_year);
this->set("month", 1 + timeinfo->tm_mon);
this->set("day", timeinfo->tm_mday);
this->set("hour", timeinfo->tm_hour);
this->set("minute", timeinfo->tm_min);
this->set("second", timeinfo->tm_sec);
}
void PlaceholderParser::apply_config(DynamicPrintConfig &config)
@ -66,53 +58,20 @@ void PlaceholderParser::apply_config(DynamicPrintConfig &config)
i != opt_keys.end(); ++i)
{
const t_config_option_key &key = *i;
// set placeholders for options with multiple values
const ConfigOptionDef &def = (*config.def)[key];
switch (def.type) {
case coFloats:
this->set_multiple_from_vector(key,
*(ConfigOptionFloats*)config.option(key));
break;
case coInts:
this->set_multiple_from_vector(key,
*(ConfigOptionInts*)config.option(key));
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:
const ConfigOption* opt = config.option(key);
if (const ConfigOptionVectorBase* optv = dynamic_cast<const ConfigOptionVectorBase*>(opt)) {
// set placeholders for options with multiple values
this->set(key, optv->vserialize());
} else if (const ConfigOptionPoint* optp = dynamic_cast<const ConfigOptionPoint*>(opt)) {
this->_single[key] = optp->serialize();
Pointf val = *optp;
this->_multiple[key + "_X"] = val.x;
this->_multiple[key + "_Y"] = val.y;
} else {
// set single-value placeholders
this->_single[key] = config.serialize(key);
break;
this->_single[key] = opt->serialize();
}
}
}
@ -121,34 +80,30 @@ void
PlaceholderParser::set(const std::string &key, const std::string &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 PlaceholderParser::set_multiple_from_vector(const std::string &key,
ConfigOptionVector<T> &opt)
void
PlaceholderParser::set(const std::string &key, const std::vector<std::string> &values)
{
const std::vector<T> &vals = opt.values;
for (size_t i = 0; i < vals.size(); ++i) {
std::stringstream multikey_stm;
multikey_stm << key << "_" << i;
std::stringstream val_stm;
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();
for (std::vector<std::string>::const_iterator v = values.begin(); v != values.end(); ++v) {
std::stringstream ss;
ss << key << "_" << (v - values.begin());
this->_multiple[ ss.str() ] = *v;
if (v == values.begin()) {
this->_multiple[key] = *v;
}
}
this->_single.erase(key);
}
#ifdef SLIC3RXS

View File

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

View File

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

View File

@ -5,6 +5,7 @@
#include <vector>
#include <math.h>
#include <string>
#include <sstream>
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) {};
};
std::ostream& operator<<(std::ostream &stm, const Pointf &pointf);
class Pointf
{
public:

View File

@ -139,11 +139,31 @@ Polyline::simplify_by_visibility(const T &area)
Points &pp = this->points;
// find first point in area
size_t start = 0;
while (start < pp.size() && !area.contains(pp[start])) {
start++;
size_t s = 0;
while (s < pp.size() && !area.contains(pp[s])) {
++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) {
// find the farthest point to which we can build
// 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<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") {
steps.insert(psBrim);
steps.insert(psSkirt);
} else if (*opt_key == "nozzle_diameter") {
} else if (*opt_key == "nozzle_diameter"
|| *opt_key == "resolution") {
osteps.insert(posSlice);
} else if (*opt_key == "avoid_crossing_perimeters"
|| *opt_key == "bed_shape"
@ -300,7 +301,7 @@ Print::step_done(PrintObjectStep step) const
// returns 0-based indices of used extruders
std::set<size_t>
Print::extruders() const
Print::object_extruders() const
{
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)
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) {
if ((*object)->has_support_material()) {
extruders.insert((*object)->config.support_material_extruder - 1);
@ -326,6 +337,18 @@ Print::extruders() const
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
Print::_simplify_slices(double distance)
{

View File

@ -199,6 +199,8 @@ class Print
Flow brim_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;
void _simplify_slices(double distance);
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"].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"].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.";

View File

@ -409,6 +409,7 @@ class PrintConfig : public GCodeConfig
ConfigOptionPoints extruder_offset;
ConfigOptionBool fan_always_on;
ConfigOptionInt fan_below_layer_time;
ConfigOptionStrings filament_colour;
ConfigOptionFloat first_layer_acceleration;
ConfigOptionInt first_layer_bed_temperature;
ConfigOptionFloatOrPercent first_layer_extrusion_width;
@ -464,6 +465,8 @@ class PrintConfig : public GCodeConfig
this->extruder_offset.values[0] = Pointf(0,0);
this->fan_always_on.value = false;
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_bed_temperature.value = 0;
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 == "fan_always_on") return &this->fan_always_on;
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_bed_temperature") return &this->first_layer_bed_temperature;
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 == "external_perimeters_first") {
steps.insert(posPerimeters);
} else if (*opt_key == "resolution"
|| *opt_key == "layer_height"
} else if (*opt_key == "layer_height"
|| *opt_key == "first_layer_height"
|| *opt_key == "xy_size_compensation"
|| *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);
}
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)
{
stl_rotate_x(&(this->stl), angle);
stl_invalidate_shared_vertices(&this->stl);
this->rotate(angle, X);
}
void TriangleMesh::rotate_y(float angle)
{
stl_rotate_y(&(this->stl), angle);
stl_invalidate_shared_vertices(&this->stl);
this->rotate(angle, Y);
}
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);
}
void TriangleMesh::flip_x()
{
stl_mirror_yz(&this->stl);
stl_invalidate_shared_vertices(&this->stl);
this->flip(X);
}
void TriangleMesh::flip_y()
{
stl_mirror_xz(&this->stl);
stl_invalidate_shared_vertices(&this->stl);
this->flip(Y);
}
void TriangleMesh::flip_z()
{
stl_mirror_xy(&this->stl);
stl_invalidate_shared_vertices(&this->stl);
this->flip(Z);
}
void TriangleMesh::align_to_origin()

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ use strict;
use warnings;
use Slic3r::XS;
use Test::More tests => 16;
use Test::More tests => 18;
my $points = [
[100, 100],
@ -87,4 +87,11 @@ is scalar(@{$collection->[1]}), 1, 'appended collection was duplicated';
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__

View File

@ -4,7 +4,7 @@ use strict;
use warnings;
use Slic3r::XS;
use Test::More tests => 8;
use Test::More tests => 9;
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, 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__

View File

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

View File

@ -8,6 +8,9 @@
%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
@ -87,4 +90,17 @@ simplify_polygons(polygons, tolerance)
OUTPUT:
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; %};
void make_slices();
void merge_slices();
bool any_internal_region_slice_contains_polyline(Polyline* polyline)
%code%{ RETVAL = THIS->any_internal_region_slice_contains(*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 scale_xyz(Pointf3* versor)
%code{% THIS->scale(*versor); %};
void rotate(float angle, Axis axis);
void flip(Axis axis);
Model* cut(double z)
%code%{

View File

@ -14,6 +14,8 @@
void apply_config(DynamicPrintConfig *config)
%code%{ THIS->apply_config(*config); %};
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)
%code%{ THIS->_single[k] = v; %};

View File

@ -161,6 +161,22 @@ _constant()
void set_step_started(PrintStep 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()
%code%{
std::set<size_t> extruders = THIS->extruders();

View File

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

View File

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