Merge branch 'master' into sender
Conflicts: Build.PL lib/Slic3r/GUI/MainFrame.pm
@ -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:
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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) = @_;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
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 {
|
||||
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 {
|
||||
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 $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);
|
||||
my $config = $self->{options_tabs}{filament}->get_preset_config($preset);
|
||||
$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;
|
||||
|
@ -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,
|
||||
|
@ -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';
|
||||
|
@ -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);
|
||||
@ -517,6 +539,16 @@ sub load_model_objects {
|
||||
$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);
|
||||
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?',
|
||||
);
|
||||
}
|
||||
|
||||
sub objects_loaded {
|
||||
my $self = shift;
|
||||
my ($obj_idxs, %params) = @_;
|
||||
|
||||
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);
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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,15 +278,15 @@ 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);
|
||||
@ -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,
|
||||
);
|
||||
|
||||
|
@ -151,14 +151,18 @@ 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
|
||||
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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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};
|
||||
|
@ -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);
|
||||
|
||||
# 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);
|
||||
# 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);
|
||||
|
||||
# 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)
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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__
|
||||
|
19
t/gcode.t
@ -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__
|
||||
|
35
t/support.t
@ -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__
|
||||
|
38
t/thin.t
@ -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__
|
||||
|
@ -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
After Width: | Height: | Size: 465 B |
BIN
var/lorry_add.png
Executable file
After Width: | Height: | Size: 689 B |
BIN
var/lorry_go.png
Executable file
After Width: | Height: | Size: 699 B |
BIN
var/plugin_add.png
Executable file
After Width: | Height: | Size: 691 B |
BIN
var/plugin_go.png
Executable file
After Width: | Height: | Size: 694 B |
BIN
var/shape_flip_horizontal.png
Executable file
After Width: | Height: | Size: 403 B |
BIN
var/shape_handles.png
Executable file
After Width: | Height: | Size: 538 B |
BIN
var/textfield.png
Executable file
After Width: | Height: | Size: 153 B |
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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,12 +411,13 @@ 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());
|
||||
}
|
||||
|
||||
// append polyline to result if it's not too small
|
||||
if (polyline.length() > this->max_width)
|
||||
// append polyline to result
|
||||
polylines->push_back(polyline);
|
||||
}
|
||||
}
|
||||
@ -322,34 +456,36 @@ 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;
|
||||
if (!cell1.contains_segment() || !cell2.contains_segment()) return false;
|
||||
const Line &segment1 = this->retrieve_segment(cell1);
|
||||
const Line &segment2 = this->retrieve_segment(cell2);
|
||||
|
||||
// calculate relative angle between the two boundary segments
|
||||
// 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 (say, 30°)
|
||||
if (angle < PI*2/3 ) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
// each vertex is equidistant to both cell segments
|
||||
// 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
|
||||
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);
|
||||
|
||||
// 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);
|
||||
@ -357,34 +493,22 @@ MedialAxis::is_valid_edge(const VD::edge_type& edge) const
|
||||
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(line.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
|
||||
// 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/2 || dist1 < this->min_width/2) {
|
||||
if (dist0 < this->min_width && dist1 < this->min_width) {
|
||||
//printf(" => too thin, skipping\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
// if distance between this edge and the thin area boundary is greater
|
||||
// than half the max width, then it's not a true medial axis segment
|
||||
if (dist1 > this->width*2) {
|
||||
printf(" => too fat, skipping\n");
|
||||
//return false;
|
||||
}
|
||||
*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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();
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
} }
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
const ConfigOption* opt = config.option(key);
|
||||
|
||||
if (const ConfigOptionVectorBase* optv = dynamic_cast<const ConfigOptionVectorBase*>(opt)) {
|
||||
// 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;
|
||||
this->set(key, optv->vserialize());
|
||||
} else if (const ConfigOptionPoint* optp = dynamic_cast<const ConfigOptionPoint*>(opt)) {
|
||||
this->_single[key] = optp->serialize();
|
||||
|
||||
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;
|
||||
Pointf val = *optp;
|
||||
this->_multiple[key + "_X"] = val.x;
|
||||
this->_multiple[key + "_Y"] = val.y;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
} 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 (std::vector<std::string>::const_iterator v = values.begin(); v != values.end(); ++v) {
|
||||
std::stringstream ss;
|
||||
ss << key << "_" << (v - values.begin());
|
||||
|
||||
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();
|
||||
this->_multiple[ ss.str() ] = *v;
|
||||
if (v == values.begin()) {
|
||||
this->_multiple[key] = *v;
|
||||
}
|
||||
|
||||
if (vals.size() > 0) {
|
||||
std::stringstream val_stm;
|
||||
val_stm << vals[0];
|
||||
this->_multiple[key] = val_stm.str();
|
||||
}
|
||||
this->_single.erase(key);
|
||||
}
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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.";
|
||||
|
@ -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;
|
||||
|
@ -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") {
|
||||
|
@ -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()
|
||||
|
@ -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();
|
||||
|
@ -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"): */
|
||||
|
@ -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';
|
||||
@ -38,6 +38,8 @@ ok !$point->coincides_with($point2), 'coincides_with';
|
||||
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(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()';
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -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)
|
||||
);
|
||||
|
@ -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__
|
||||
|
@ -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__
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
||||
%}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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%{
|
||||
|
@ -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; %};
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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{%
|
||||
|