Merge branch 'master' into sender

This commit is contained in:
Alessandro Ranellucci 2015-03-06 22:15:43 +01:00
commit d2172b4383
47 changed files with 860 additions and 429 deletions

View file

@ -2,9 +2,9 @@ language: perl
install: true install: true
script: perl ./Build.PL script: perl ./Build.PL
perl: perl:
- "5.12"
- "5.14" - "5.14"
- "5.18" - "5.18"
- "5.20"
branches: branches:
only: only:
- master - master

View file

@ -8,7 +8,7 @@ Slic3r [![Build Status](https://travis-ci.org/alexrj/Slic3r.png?branch=master)](
Slic3r takes 3D models (STL, OBJ, AMF) and converts them into G-code instructions for Slic3r takes 3D models (STL, OBJ, AMF) and converts them into G-code instructions for
3D printers. It's compatible with any modern printer based on the RepRap toolchain, 3D printers. It's compatible with any modern printer based on the RepRap toolchain,
including all those based on the Marlin, Sprinter and Repetier firmware. It also works including all those based on the Marlin, Sprinter and Repetier firmware. It also works
with Mach3 and LinuxCNC controllers. with Mach3, LinuxCNC and Machinekit controllers.
See the [project homepage](http://slic3r.org/) at slic3r.org and the See the [project homepage](http://slic3r.org/) at slic3r.org and the
[manual](http://manual.slic3r.org/) for more information. [manual](http://manual.slic3r.org/) for more information.
@ -30,7 +30,7 @@ Key features are:
* **multi-platform** (Linux/Mac/Win) and packaged as standalone-app with no dependencies required * **multi-platform** (Linux/Mac/Win) and packaged as standalone-app with no dependencies required
* complete **command-line interface** to use it with no GUI * complete **command-line interface** to use it with no GUI
* multi-material **(multiple extruders)** object printing * multi-material **(multiple extruders)** object printing
* multiple G-code flavors supported (RepRap, Makerbot, Mach3 etc.) * multiple G-code flavors supported (RepRap, Makerbot, Mach3, Machinekit etc.)
* ability to plate **multiple objects having distinct print settings** * ability to plate **multiple objects having distinct print settings**
* **multithread** processing * **multithread** processing
* **STL auto-repair** (tolerance for broken models) * **STL auto-repair** (tolerance for broken models)
@ -109,6 +109,8 @@ The author of the Silk icon set is Mark James.
-j, --threads <num> Number of threads to use (1+, default: 2) -j, --threads <num> Number of threads to use (1+, default: 2)
GUI options: GUI options:
--gui Forces the GUI launch instead of command line slicing (if you
supply a model file, it will be loaded into the plater)
--no-plater Disable the plater tab --no-plater Disable the plater tab
--gui-mode Overrides the configured mode (simple/expert) --gui-mode Overrides the configured mode (simple/expert)
--autosave <file> Automatically export current configuration to the specified file --autosave <file> Automatically export current configuration to the specified file
@ -130,7 +132,7 @@ The author of the Silk icon set is Mark James.
(default: 100,100) (default: 100,100)
--z-offset Additional height in mm to add to vertical coordinates --z-offset Additional height in mm to add to vertical coordinates
(+/-, default: 0) (+/-, default: 0)
--gcode-flavor The type of G-code to generate (reprap/teacup/makerware/sailfish/mach3/no-extrusion, --gcode-flavor The type of G-code to generate (reprap/teacup/makerware/sailfish/mach3/machinekit/no-extrusion,
default: reprap) default: reprap)
--use-relative-e-distances Enable this to get relative E values (default: no) --use-relative-e-distances Enable this to get relative E values (default: no)
--use-firmware-retraction Enable firmware-controlled retraction using G10/G11 (default: no) --use-firmware-retraction Enable firmware-controlled retraction using G10/G11 (default: no)

View file

@ -243,7 +243,7 @@ sub validate {
if !first { $_ eq $self->gcode_flavor } @{$Options->{gcode_flavor}{values}}; if !first { $_ eq $self->gcode_flavor } @{$Options->{gcode_flavor}{values}};
die "--use-firmware-retraction is only supported by Marlin firmware\n" die "--use-firmware-retraction is only supported by Marlin firmware\n"
if $self->use_firmware_retraction && $self->gcode_flavor ne 'reprap'; if $self->use_firmware_retraction && $self->gcode_flavor ne 'reprap' && $self->gcode_flavor ne 'machinekit';
die "--use-firmware-retraction is not compatible with --wipe\n" die "--use-firmware-retraction is not compatible with --wipe\n"
if $self->use_firmware_retraction && first {$_} @{$self->wipe}; if $self->use_firmware_retraction && first {$_} @{$self->wipe};

View file

@ -49,9 +49,10 @@ sub make_fill {
Slic3r::debugf "Filling layer %d:\n", $layerm->id; Slic3r::debugf "Filling layer %d:\n", $layerm->id;
my $fill_density = $layerm->config->fill_density; my $fill_density = $layerm->config->fill_density;
my $infill_flow = $layerm->flow(FLOW_ROLE_INFILL); my $infill_flow = $layerm->flow(FLOW_ROLE_INFILL);
my $solid_infill_flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL); my $solid_infill_flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL);
my $top_solid_infill_flow = $layerm->flow(FLOW_ROLE_TOP_SOLID_INFILL);
my @surfaces = (); my @surfaces = ();
@ -75,7 +76,7 @@ sub make_fill {
if ($groups[$i][0]->is_solid && (!$groups[$i][0]->is_bridge || $layerm->id == 0)) { if ($groups[$i][0]->is_solid && (!$groups[$i][0]->is_bridge || $layerm->id == 0)) {
$is_solid[$i] = 1; $is_solid[$i] = 1;
$fw[$i] = ($groups[$i][0]->surface_type == S_TYPE_TOP) $fw[$i] = ($groups[$i][0]->surface_type == S_TYPE_TOP)
? $layerm->flow(FLOW_ROLE_TOP_SOLID_INFILL)->width ? $top_solid_infill_flow->width
: $solid_infill_flow->width; : $solid_infill_flow->width;
$pattern[$i] = $groups[$i][0]->is_external $pattern[$i] = $groups[$i][0]->is_external
? $layerm->config->external_fill_pattern ? $layerm->config->external_fill_pattern

View file

@ -584,11 +584,14 @@ sub wipe {
$gcode .= $gcodegen->writer->extrude_to_xy( $gcode .= $gcodegen->writer->extrude_to_xy(
$gcodegen->point_to_gcode($line->b), $gcodegen->point_to_gcode($line->b),
-$dE, -$dE,
'retract' . ($gcodegen->enable_cooling_markers ? ';_WIPE' : ''), 'wipe and retract' . ($gcodegen->enable_cooling_markers ? ';_WIPE' : ''),
); );
$retracted += $dE; $retracted += $dE;
} }
$gcodegen->writer->extruder->set_retracted($gcodegen->writer->extruder->retracted + $retracted); $gcodegen->writer->extruder->set_retracted($gcodegen->writer->extruder->retracted + $retracted);
# prevent wiping again on same path
$self->path(undef);
} }
return $gcode; return $gcode;

View file

@ -34,7 +34,7 @@ our $have_LWP = eval "use LWP::UserAgent; 1";
use Wx 0.9901 qw(:bitmap :dialog :icon :id :misc :systemsettings :toplevelwindow use Wx 0.9901 qw(:bitmap :dialog :icon :id :misc :systemsettings :toplevelwindow
:filedialog); :filedialog);
use Wx::Event qw(EVT_IDLE); use Wx::Event qw(EVT_IDLE EVT_COMMAND);
use base 'Wx::App'; use base 'Wx::App';
use constant FILE_WILDCARDS => { use constant FILE_WILDCARDS => {
@ -70,6 +70,8 @@ our $medium_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
$medium_font->SetPointSize(12); $medium_font->SetPointSize(12);
our $grey = Wx::Colour->new(200,200,200); our $grey = Wx::Colour->new(200,200,200);
our $VERSION_CHECK_EVENT : shared = Wx::NewEventType;
sub OnInit { sub OnInit {
my ($self) = @_; my ($self) = @_;
@ -143,6 +145,25 @@ sub OnInit {
} }
}); });
EVT_COMMAND($self, -1, $VERSION_CHECK_EVENT, sub {
my ($self, $event) = @_;
my ($success, $response, $manual_check) = @{$event->GetData};
if ($success) {
if ($response =~ /^obsolete ?= ?([a-z0-9.-]+,)*\Q$Slic3r::VERSION\E(?:,|$)/) {
my $res = Wx::MessageDialog->new(undef, "A new version is available. Do you want to open the Slic3r website now?",
'Update', wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_INFORMATION | wxICON_ERROR)->ShowModal;
Wx::LaunchDefaultBrowser('http://slic3r.org/') if $res == wxID_YES;
} else {
Slic3r::GUI::show_info(undef, "You're using the latest version. No updates are available.") if $manual_check;
}
$Settings->{_}{last_version_check} = time();
$self->save_settings;
} else {
Slic3r::GUI::show_error(undef, "Failed to check for updates. Try later.") if $manual_check;
}
});
return 1; return 1;
} }
@ -239,7 +260,7 @@ sub have_version_check {
} }
sub check_version { sub check_version {
my ($self, %p) = @_; my ($self, $manual_check) = @_;
Slic3r::debugf "Checking for updates...\n"; Slic3r::debugf "Checking for updates...\n";
@ -248,19 +269,9 @@ sub check_version {
my $ua = LWP::UserAgent->new; my $ua = LWP::UserAgent->new;
$ua->timeout(10); $ua->timeout(10);
my $response = $ua->get('http://slic3r.org/updatecheck'); my $response = $ua->get('http://slic3r.org/updatecheck');
if ($response->is_success) { Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $VERSION_CHECK_EVENT,
if ($response->decoded_content =~ /^obsolete ?= ?([a-z0-9.-]+,)*\Q$Slic3r::VERSION\E(?:,|$)/) { threads::shared::shared_clone([ $response->is_success, $response->decoded_content, $manual_check ])));
my $res = Wx::MessageDialog->new(undef, "A new version is available. Do you want to open the Slic3r website now?",
'Update', wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_INFORMATION | wxICON_ERROR)->ShowModal;
Wx::LaunchDefaultBrowser('http://slic3r.org/') if $res == wxID_YES;
} else {
Slic3r::GUI::show_info(undef, "You're using the latest version. No updates are available.") if $p{manual};
}
$Settings->{_}{last_version_check} = time();
$self->save_settings;
} else {
Slic3r::GUI::show_error(undef, "Failed to check for updates. Try later.") if $p{manual};
}
Slic3r::thread_cleanup(); Slic3r::thread_cleanup();
})->detach; })->detach;
} }

View file

@ -16,6 +16,7 @@ __PACKAGE__->mk_accessors( qw(_quat _dirty init
enable_cutting enable_cutting
enable_picking enable_picking
enable_moving enable_moving
on_viewport_changed
on_hover on_hover
on_select on_select
on_double_click on_double_click
@ -108,6 +109,7 @@ sub new {
-($pos->y - $size->y/2) * ($zoom) / $self->_zoom, -($pos->y - $size->y/2) * ($zoom) / $self->_zoom,
0, 0,
) if 0; ) if 0;
$self->on_viewport_changed->() if $self->on_viewport_changed;
$self->_dirty(1); $self->_dirty(1);
$self->Refresh; $self->Refresh;
}); });
@ -207,6 +209,7 @@ sub mouse_event {
); );
$self->_quat(mulquats($self->_quat, \@quat)); $self->_quat(mulquats($self->_quat, \@quat));
} }
$self->on_viewport_changed->() if $self->on_viewport_changed;
$self->Refresh; $self->Refresh;
} }
$self->_drag_start_pos($pos); $self->_drag_start_pos($pos);
@ -220,6 +223,7 @@ sub mouse_event {
$self->_camera_target->translate( $self->_camera_target->translate(
@{$orig->vector_to($cur_pos)->negative}, @{$orig->vector_to($cur_pos)->negative},
); );
$self->on_viewport_changed->() if $self->on_viewport_changed;
$self->Refresh; $self->Refresh;
} }
$self->_drag_start_xy($pos); $self->_drag_start_xy($pos);
@ -256,6 +260,17 @@ sub reset_objects {
$self->_dirty(1); $self->_dirty(1);
} }
sub set_viewport_from_scene {
my ($self, $scene) = @_;
$self->_sphi($scene->_sphi);
$self->_stheta($scene->_stheta);
$self->_camera_target($scene->_camera_target);
$self->_zoom($scene->_zoom);
$self->_quat($scene->_quat);
$self->_dirty(1);
}
sub zoom_to_bounding_box { sub zoom_to_bounding_box {
my ($self, $bb) = @_; my ($self, $bb) = @_;
@ -267,6 +282,8 @@ sub zoom_to_bounding_box {
# center view around bounding box center # center view around bounding box center
$self->_camera_target($bb->center); $self->_camera_target($bb->center);
$self->on_viewport_changed->() if $self->on_viewport_changed;
} }
sub zoom_to_bed { sub zoom_to_bed {
@ -347,25 +364,24 @@ sub set_bed_shape {
} }
{ {
my @lines = (); my @polylines = ();
for (my $x = $bed_bb->x_min; $x <= $bed_bb->x_max; $x += scale 10) { for (my $x = $bed_bb->x_min; $x <= $bed_bb->x_max; $x += scale 10) {
push @lines, Slic3r::Polyline->new([$x,$bed_bb->y_min], [$x,$bed_bb->y_max]); push @polylines, Slic3r::Polyline->new([$x,$bed_bb->y_min], [$x,$bed_bb->y_max]);
} }
for (my $y = $bed_bb->y_min; $y <= $bed_bb->y_max; $y += scale 10) { for (my $y = $bed_bb->y_min; $y <= $bed_bb->y_max; $y += scale 10) {
push @lines, Slic3r::Polyline->new([$bed_bb->x_min,$y], [$bed_bb->x_max,$y]); push @polylines, Slic3r::Polyline->new([$bed_bb->x_min,$y], [$bed_bb->x_max,$y]);
} }
# clip with a slightly grown expolygon because our lines lay on the contours and # clip with a slightly grown expolygon because our lines lay on the contours and
# may get erroneously clipped # may get erroneously clipped
@lines = @{intersection_pl(\@lines, [ @{$expolygon->offset(+scaled_epsilon)} ])}; my @lines = map Slic3r::Line->new(@$_[0,-1]),
@{intersection_pl(\@polylines, [ @{$expolygon->offset(+scaled_epsilon)} ])};
# append bed contours # append bed contours
foreach my $line (map @{$_->lines}, @$expolygon) { push @lines, map @{$_->lines}, @$expolygon;
push @lines, $line->as_polyline;
}
my @points = (); my @points = ();
foreach my $polyline (@lines) { foreach my $line (@lines) {
push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$polyline; #)) push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$line; #))
} }
$self->bed_grid_lines(OpenGL::Array->new_list(GL_FLOAT, @points)); $self->bed_grid_lines(OpenGL::Array->new_list(GL_FLOAT, @points));
} }
@ -572,9 +588,10 @@ sub Resize {
glMatrixMode(GL_PROJECTION); glMatrixMode(GL_PROJECTION);
glLoadIdentity(); glLoadIdentity();
my $depth = 10 * max(@{ $self->max_bounding_box->size });
glOrtho( glOrtho(
-$x/2, $x/2, -$y/2, $y/2, -$x/2, $x/2, -$y/2, $y/2,
-200, 10 * max(@{ $self->max_bounding_box->size }), -$depth, 2*$depth,
); );
glMatrixMode(GL_MODELVIEW); glMatrixMode(GL_MODELVIEW);

View file

@ -307,7 +307,7 @@ sub _repaint_canvas {
@polylines = @{intersection_pl(\@polylines, [$bed_polygon])}; @polylines = @{intersection_pl(\@polylines, [$bed_polygon])};
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(230,230,230), 1, wxSOLID)); $dc->SetPen(Wx::Pen->new(Wx::Colour->new(230,230,230), 1, wxSOLID));
$dc->DrawLine(map @{$to_pixel->([map unscale($_), @$_])}, @$_) for @polylines; $dc->DrawLine(map @{$to_pixel->([map unscale($_), @$_])}, @$_[0,-1]) for @polylines;
} }
# draw bed contour # draw bed contour

View file

@ -255,7 +255,7 @@ sub _init_menubar {
Wx::LaunchDefaultBrowser('http://slic3r.org/'); Wx::LaunchDefaultBrowser('http://slic3r.org/');
}); });
my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", 'Check for new Slic3r versions', sub { my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", 'Check for new Slic3r versions', sub {
wxTheApp->check_version(manual => 1); wxTheApp->check_version(1);
}); });
$versioncheck->Enable(wxTheApp->have_version_check); $versioncheck->Enable(wxTheApp->have_version_check);
$self->_append_menu_item($helpMenu, "Slic3r &Manual", 'Open the Slic3r manual in your browser', sub { $self->_append_menu_item($helpMenu, "Slic3r &Manual", 'Open the Slic3r manual in your browser', sub {
@ -678,7 +678,7 @@ sub config {
} else { } else {
my $extruders_count = $self->{options_tabs}{printer}{extruders_count}; my $extruders_count = $self->{options_tabs}{printer}{extruders_count};
$config->set("${_}_extruder", min($config->get("${_}_extruder"), $extruders_count)) $config->set("${_}_extruder", min($config->get("${_}_extruder"), $extruders_count))
for qw(perimeter infill support_material support_material_interface); for qw(perimeter infill solid_infill support_material support_material_interface);
} }
return $config; return $config;

View file

@ -96,6 +96,9 @@ sub new {
$self->{canvas3D}->set_on_double_click($on_double_click); $self->{canvas3D}->set_on_double_click($on_double_click);
$self->{canvas3D}->set_on_right_click(sub { $on_right_click->($self->{canvas3D}, @_); }); $self->{canvas3D}->set_on_right_click(sub { $on_right_click->($self->{canvas3D}, @_); });
$self->{canvas3D}->set_on_instances_moved($on_instances_moved); $self->{canvas3D}->set_on_instances_moved($on_instances_moved);
$self->{canvas3D}->on_viewport_changed(sub {
$self->{preview3D}->canvas->set_viewport_from_scene($self->{canvas3D});
});
} }
# Initialize 2D preview canvas # Initialize 2D preview canvas
@ -109,6 +112,9 @@ sub new {
# Initialize 3D toolpaths preview # Initialize 3D toolpaths preview
if ($Slic3r::GUI::have_OpenGL) { if ($Slic3r::GUI::have_OpenGL) {
$self->{preview3D} = Slic3r::GUI::Plater::3DPreview->new($self->{preview_notebook}, $self->{print}); $self->{preview3D} = Slic3r::GUI::Plater::3DPreview->new($self->{preview_notebook}, $self->{print});
$self->{preview3D}->canvas->on_viewport_changed(sub {
$self->{canvas3D}->set_viewport_from_scene($self->{preview3D}->canvas);
});
$self->{preview_notebook}->AddPage($self->{preview3D}, 'Preview'); $self->{preview_notebook}->AddPage($self->{preview3D}, 'Preview');
$self->{preview3D_page_idx} = $self->{preview_notebook}->GetPageCount-1; $self->{preview3D_page_idx} = $self->{preview_notebook}->GetPageCount-1;
} }
@ -988,9 +994,13 @@ sub pause_background_process {
if ($self->{process_thread} || $self->{export_thread}) { if ($self->{process_thread} || $self->{export_thread}) {
Slic3r::pause_all_threads(); Slic3r::pause_all_threads();
return 1;
} elsif (defined $self->{apply_config_timer} && $self->{apply_config_timer}->IsRunning) { } elsif (defined $self->{apply_config_timer} && $self->{apply_config_timer}->IsRunning) {
$self->{apply_config_timer}->Stop; $self->{apply_config_timer}->Stop;
return 1;
} }
return 0;
} }
sub resume_background_process { sub resume_background_process {
@ -1312,9 +1322,14 @@ sub update {
$self->{model}->center_instances_around_point($self->bed_centerf); $self->{model}->center_instances_around_point($self->bed_centerf);
} }
$self->pause_background_process; my $running = $self->pause_background_process;
my $invalidated = $self->{print}->reload_model_instances(); my $invalidated = $self->{print}->reload_model_instances();
if ($invalidated) {
# The mere fact that no steps were invalidated when reloading model instances
# doesn't mean that all steps were done: for example, validation might have
# failed upon previous instance move, so we have no running thread and no steps
# are invalidated on this move, thus we need to schedule a new run.
if ($invalidated || !$running) {
$self->schedule_background_process; $self->schedule_background_process;
} else { } else {
$self->resume_background_process; $self->resume_background_process;
@ -1323,12 +1338,6 @@ sub update {
$self->refresh_canvases; $self->refresh_canvases;
} }
sub on_model_instances_changed {
my ($self) = @_;
}
sub on_extruders_change { sub on_extruders_change {
my ($self, $num_extruders) = @_; my ($self, $num_extruders) = @_;

View file

@ -276,7 +276,7 @@ sub update_bed_size {
push @polylines, Slic3r::Polyline->new([$bb->x_min, $y], [$bb->x_max, $y]); push @polylines, Slic3r::Polyline->new([$bb->x_min, $y], [$bb->x_max, $y]);
} }
@polylines = @{intersection_pl(\@polylines, [$polygon])}; @polylines = @{intersection_pl(\@polylines, [$polygon])};
$self->{grid} = [ map $self->scaled_points_to_pixel(\@$_, 1), @polylines ]; $self->{grid} = [ map $self->scaled_points_to_pixel([ @$_[0,-1] ], 1), @polylines ];
} }
} }

View file

@ -291,7 +291,7 @@ sub Render {
$brim_drawn = 1; $brim_drawn = 1;
} }
if ($self->print->step_done(STEP_SKIRT) if ($self->print->step_done(STEP_SKIRT)
&& ($self->print->config->skirt_height == -1 || $self->print->config->skirt_height > $layer->id) && ($self->print->has_infinite_skirt() || $self->print->config->skirt_height > $layer->id)
&& !$skirt_drawn) { && !$skirt_drawn) {
$self->color([0, 0, 0]); $self->color([0, 0, 0]);
$self->_draw(undef, $print_z, $_) for @{$self->print->skirt}; $self->_draw(undef, $print_z, $_) for @{$self->print->skirt};

View file

@ -37,9 +37,9 @@ sub new {
# buttons # buttons
$self->{btn_save_preset} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new("$Slic3r::var/disk.png", wxBITMAP_TYPE_PNG), $self->{btn_save_preset} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new("$Slic3r::var/disk.png", wxBITMAP_TYPE_PNG),
wxDefaultPosition, [16,16], wxBORDER_NONE); wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
$self->{btn_delete_preset} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new("$Slic3r::var/delete.png", wxBITMAP_TYPE_PNG), $self->{btn_delete_preset} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new("$Slic3r::var/delete.png", wxBITMAP_TYPE_PNG),
wxDefaultPosition, [16,16], wxBORDER_NONE); wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
$self->{btn_save_preset}->SetToolTipString("Save current " . lc($self->title)); $self->{btn_save_preset}->SetToolTipString("Save current " . lc($self->title));
$self->{btn_delete_preset}->SetToolTipString("Delete this preset"); $self->{btn_delete_preset}->SetToolTipString("Delete this preset");
$self->{btn_delete_preset}->Disable; $self->{btn_delete_preset}->Disable;
@ -745,11 +745,13 @@ sub _update {
perimeter_speed small_perimeter_speed external_perimeter_speed); perimeter_speed small_perimeter_speed external_perimeter_speed);
my $have_infill = $config->fill_density > 0; my $have_infill = $config->fill_density > 0;
# infill_extruder uses the same logic as in Print::extruders()
$self->get_field($_)->toggle($have_infill) $self->get_field($_)->toggle($have_infill)
for qw(fill_pattern infill_every_layers infill_only_where_needed solid_infill_every_layers for qw(fill_pattern infill_every_layers infill_only_where_needed solid_infill_every_layers
solid_infill_below_area infill_extruder); solid_infill_below_area infill_extruder);
my $have_solid_infill = ($config->top_solid_layers > 0) || ($config->bottom_solid_layers > 0); my $have_solid_infill = ($config->top_solid_layers > 0) || ($config->bottom_solid_layers > 0);
# solid_infill_extruder uses the same logic as in Print::extruders()
$self->get_field($_)->toggle($have_solid_infill) $self->get_field($_)->toggle($have_solid_infill)
for qw(external_fill_pattern infill_first solid_infill_extruder solid_infill_extrusion_width for qw(external_fill_pattern infill_first solid_infill_extruder solid_infill_extrusion_width
solid_infill_speed); solid_infill_speed);
@ -772,6 +774,7 @@ sub _update {
for qw(skirt_distance skirt_height); for qw(skirt_distance skirt_height);
my $have_brim = $config->brim_width > 0; my $have_brim = $config->brim_width > 0;
# perimeter_extruder uses the same logic as in Print::extruders()
$self->get_field('perimeter_extruder')->toggle($have_perimeters || $have_brim); $self->get_field('perimeter_extruder')->toggle($have_perimeters || $have_brim);
my $have_support_material = $config->support_material || $config->raft_layers > 0; my $have_support_material = $config->support_material || $config->raft_layers > 0;

View file

@ -286,7 +286,7 @@ sub process {
# where 0.5*$pwidth < thickness < $pwidth, infill with width = 0.5*$pwidth # where 0.5*$pwidth < thickness < $pwidth, infill with width = 0.5*$pwidth
my @gap_sizes = ( my @gap_sizes = (
[ $pwidth, 2*$pspacing, unscale 1.5*$pwidth ], [ $pwidth, 2*$pspacing, unscale 1.5*$pwidth ],
[ 0.5*$pwidth, $pwidth, unscale 0.5*$pwidth ], [ 0.1*$pwidth, $pwidth, unscale 0.5*$pwidth ],
); );
foreach my $gap_size (@gap_sizes) { foreach my $gap_size (@gap_sizes) {
my @gap_fill = $self->_fill_gaps(@$gap_size, \@gaps); my @gap_fill = $self->_fill_gaps(@$gap_size, \@gaps);
@ -311,12 +311,22 @@ sub process {
# we offset by half the perimeter spacing (to get to the actual infill boundary) # we offset by half the perimeter spacing (to get to the actual infill boundary)
# and then we offset back and forth by half the infill spacing to only consider the # and then we offset back and forth by half the infill spacing to only consider the
# non-collapsing regions # non-collapsing regions
my $inset = 0;
if ($loop_number == 0) {
# one loop
$inset += $ext_pspacing/2;
} elsif ($loop_number > 0) {
# two or more loops
$inset += $pspacing/2;
}
$inset -= $self->config->get_abs_value_over('infill_overlap', $pwidth);
my $min_perimeter_infill_spacing = $ispacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE); my $min_perimeter_infill_spacing = $ispacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
$self->fill_surfaces->append($_) $self->fill_surfaces->append($_)
for map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL), # use a bogus surface type for map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL), # use a bogus surface type
@{offset2_ex( @{offset2_ex(
[ map @{$_->simplify_p(&Slic3r::SCALED_RESOLUTION)}, @{union_ex(\@last)} ], [ map @{$_->simplify_p(&Slic3r::SCALED_RESOLUTION)}, @{union_ex(\@last)} ],
-($pspacing/2 - $self->config->get_abs_value_over('infill_overlap', $pwidth) + $min_perimeter_infill_spacing/2), -$inset -$min_perimeter_infill_spacing/2,
+$min_perimeter_infill_spacing/2, +$min_perimeter_infill_spacing/2,
)}; )};
} }
@ -384,8 +394,8 @@ sub _traverse_loops {
push @paths, Slic3r::ExtrusionPath->new( push @paths, Slic3r::ExtrusionPath->new(
polyline => $loop->polygon->split_at_first_point, polyline => $loop->polygon->split_at_first_point,
role => $role, role => $role,
mm3_per_mm => $self->_mm3_per_mm, mm3_per_mm => ($is_external ? $self->_ext_mm3_per_mm : $self->_mm3_per_mm),
width => $self->perimeter_flow->width, width => ($is_external ? $self->ext_perimeter_flow->width : $self->perimeter_flow->width),
height => $self->layer_height, height => $self->layer_height,
); );
} }
@ -441,11 +451,14 @@ sub _traverse_loops {
sub _fill_gaps { sub _fill_gaps {
my ($self, $min, $max, $w, $gaps) = @_; my ($self, $min, $max, $w, $gaps) = @_;
$min *= (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
my $this = diff_ex( my $this = diff_ex(
offset2([ map @$_, @$gaps ], -$min/2, +$min/2), offset2([ map @$_, @$gaps ], -$min/2, +$min/2),
offset2([ map @$_, @$gaps ], -$max/2, +$max/2), offset2([ map @$_, @$gaps ], -$max/2, +$max/2),
1, 1,
); );
my @polylines = map @{$_->medial_axis($max, $min/2)}, @$this; my @polylines = map @{$_->medial_axis($max, $min/2)}, @$this;
return if !@polylines; return if !@polylines;

View file

@ -89,7 +89,8 @@ sub export_gcode {
$self->config->setenv; $self->config->setenv;
for my $script (@{$self->config->post_process}) { for my $script (@{$self->config->post_process}) {
Slic3r::debugf " '%s' '%s'\n", $script, $output_file; Slic3r::debugf " '%s' '%s'\n", $script, $output_file;
if (!-x $script) { # -x doesn't return true on Windows except for .exe files
if (($^O eq 'MSWin32') ? !(-e $script) : !(-x $script)) {
die "The configured post-processing script is not executable: check permissions. ($script)\n"; die "The configured post-processing script is not executable: check permissions. ($script)\n";
} }
system($script, $output_file); system($script, $output_file);
@ -101,9 +102,6 @@ sub export_svg {
my $self = shift; my $self = shift;
my %params = @_; my %params = @_;
# is this needed?
$self->init_extruders;
$_->slice for @{$self->objects}; $_->slice for @{$self->objects};
my $fh = $params{output_fh}; my $fh = $params{output_fh};
@ -208,8 +206,7 @@ sub make_skirt {
# checking whether we need to generate them # checking whether we need to generate them
$self->skirt->clear; $self->skirt->clear;
if (($self->config->skirts == 0 || $self->config->skirt_height == 0) if (!$self->has_skirt) {
&& (!$self->config->ooze_prevention || @{$self->extruders} == 1)) {
$self->set_step_done(STEP_SKIRT); $self->set_step_done(STEP_SKIRT);
return; return;
} }
@ -219,7 +216,7 @@ sub make_skirt {
# The skirt_height option from config is expressed in layers, but our # The skirt_height option from config is expressed in layers, but our
# object might have different layer heights, so we need to find the print_z # object might have different layer heights, so we need to find the print_z
# of the highest layer involved. # of the highest layer involved.
# Note that unless skirt_height == -1 (which means it's printed on all layers) # Note that unless has_infinite_skirt() == true
# the actual skirt might not reach this $skirt_height_z value since the print # the actual skirt might not reach this $skirt_height_z value since the print
# order of objects on each layer is not guaranteed and will not generally # order of objects on each layer is not guaranteed and will not generally
# include the thickest object first. It is just guaranteed that a skirt is # include the thickest object first. It is just guaranteed that a skirt is
@ -227,10 +224,9 @@ sub make_skirt {
# $skirt_height_z in this case is the highest possible skirt height for safety. # $skirt_height_z in this case is the highest possible skirt height for safety.
my $skirt_height_z = -1; my $skirt_height_z = -1;
foreach my $object (@{$self->objects}) { foreach my $object (@{$self->objects}) {
my $skirt_height = ($self->config->skirt_height == -1 || $self->config->ooze_prevention) my $skirt_height = $self->has_infinite_skirt
? scalar(@{$object->layers}) ? scalar(@{$object->layers})
: min($self->config->skirt_height, scalar(@{$object->layers})); : min($self->config->skirt_height, scalar(@{$object->layers}));
my $highest_layer = $object->get_layer($skirt_height - 1); my $highest_layer = $object->get_layer($skirt_height - 1);
$skirt_height_z = max($skirt_height_z, $highest_layer->print_z); $skirt_height_z = max($skirt_height_z, $highest_layer->print_z);
} }
@ -278,10 +274,13 @@ sub make_skirt {
my @extruders_e_per_mm = (); my @extruders_e_per_mm = ();
my $extruder_idx = 0; my $extruder_idx = 0;
my $skirts = $self->config->skirts;
$skirts ||= 1 if $self->has_infinite_skirt;
# draw outlines from outside to inside # draw outlines from outside to inside
# loop while we have less skirts than required or any extruder hasn't reached the min length if any # loop while we have less skirts than required or any extruder hasn't reached the min length if any
my $distance = scale max($self->config->skirt_distance, $self->config->brim_width); my $distance = scale max($self->config->skirt_distance, $self->config->brim_width);
for (my $i = $self->config->skirts; $i > 0; $i--) { for (my $i = $skirts; $i > 0; $i--) {
$distance += scale $spacing; $distance += scale $spacing;
my $loop = offset([$convex_hull], $distance, 1, JT_ROUND, scale(0.1))->[0]; my $loop = offset([$convex_hull], $distance, 1, JT_ROUND, scale(0.1))->[0];
$self->skirt->append(Slic3r::ExtrusionLoop->new_from_paths( $self->skirt->append(Slic3r::ExtrusionLoop->new_from_paths(
@ -428,10 +427,10 @@ sub expanded_output_filepath {
my $filename = my $filename_base = basename($input_file); my $filename = my $filename_base = basename($input_file);
$filename_base =~ s/\.[^.]+$//; # without suffix $filename_base =~ s/\.[^.]+$//; # without suffix
my $extra = {
input_filename => $filename, # set filename in placeholder parser so that it's available also in custom G-code
input_filename_base => $filename_base, $self->placeholder_parser->set(input_filename => $filename);
}; $self->placeholder_parser->set(input_filename_base => $filename_base);
if ($path && -d $path) { if ($path && -d $path) {
# if output path is an existing directory, we take that and append # if output path is an existing directory, we take that and append
@ -447,7 +446,7 @@ sub expanded_output_filepath {
# make sure we use an up-to-date timestamp # make sure we use an up-to-date timestamp
$self->placeholder_parser->update_timestamp; $self->placeholder_parser->update_timestamp;
return $self->placeholder_parser->process($path, $extra); return $self->placeholder_parser->process($path);
} }
# This method assigns extruders to the volumes having a material # This method assigns extruders to the volumes having a material

View file

@ -195,12 +195,14 @@ sub export {
# no collision happens hopefully. # no collision happens hopefully.
if ($finished_objects > 0) { if ($finished_objects > 0) {
$gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y)); $gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y));
$gcodegen->enable_cooling_markers(0); # we're not filtering these moves through CoolingBuffer
print $fh $gcodegen->retract; print $fh $gcodegen->retract;
print $fh $gcodegen->travel_to( print $fh $gcodegen->travel_to(
Slic3r::Point->new(0,0), Slic3r::Point->new(0,0),
undef, undef,
'move to origin position for next object', 'move to origin position for next object',
); );
$gcodegen->enable_cooling_markers(1);
} }
my @layers = sort { $a->print_z <=> $b->print_z } @{$object->layers}, @{$object->support_layers}; my @layers = sort { $a->print_z <=> $b->print_z } @{$object->layers}, @{$object->support_layers};
@ -217,6 +219,7 @@ sub export {
} }
$self->flush_filters; $self->flush_filters;
$finished_objects++; $finished_objects++;
$self->_second_layer_things_done(0);
} }
} }
} else { } else {
@ -249,6 +252,7 @@ sub export {
print $fh $gcodegen->writer->set_fan(0); print $fh $gcodegen->writer->set_fan(0);
printf $fh "%s\n", $gcodegen->placeholder_parser->process($self->config->end_gcode); printf $fh "%s\n", $gcodegen->placeholder_parser->process($self->config->end_gcode);
print $fh $gcodegen->writer->update_progress($gcodegen->layer_count, $gcodegen->layer_count, 1); # 100% print $fh $gcodegen->writer->update_progress($gcodegen->layer_count, $gcodegen->layer_count, 1); # 100%
print $fh $gcodegen->writer->postamble;
# get filament stats # get filament stats
$self->print->clear_filament_stats; $self->print->clear_filament_stats;
@ -299,7 +303,7 @@ sub process_layer {
if (defined $self->_spiral_vase) { if (defined $self->_spiral_vase) {
$self->_spiral_vase->enable( $self->_spiral_vase->enable(
($layer->id > 0 || $self->print->config->brim_width == 0) ($layer->id > 0 || $self->print->config->brim_width == 0)
&& ($layer->id >= $self->print->config->skirt_height && $self->print->config->skirt_height != -1) && ($layer->id >= $self->print->config->skirt_height && !$self->print->has_infinite_skirt)
&& !defined(first { $_->config->bottom_solid_layers > $layer->id } @{$layer->regions}) && !defined(first { $_->config->bottom_solid_layers > $layer->id } @{$layer->regions})
&& !defined(first { @{$_->perimeters} > 1 } @{$layer->regions}) && !defined(first { @{$_->perimeters} > 1 } @{$layer->regions})
&& !defined(first { @{$_->fills} > 0 } @{$layer->regions}) && !defined(first { @{$_->fills} > 0 } @{$layer->regions})
@ -332,14 +336,15 @@ sub process_layer {
}) . "\n" if $self->print->config->layer_gcode; }) . "\n" if $self->print->config->layer_gcode;
# extrude skirt # extrude skirt
if (((values %{$self->_skirt_done}) < $self->print->config->skirt_height || $self->print->config->skirt_height == -1) if (((values %{$self->_skirt_done}) < $self->print->config->skirt_height || $self->print->has_infinite_skirt)
&& !$self->_skirt_done->{$layer->print_z}) { && !$self->_skirt_done->{$layer->print_z}
&& !$layer->isa('Slic3r::Layer::Support')) {
$self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0)); $self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0));
$self->_gcodegen->avoid_crossing_perimeters->use_external_mp(1); $self->_gcodegen->avoid_crossing_perimeters->use_external_mp(1);
my @extruder_ids = map { $_->id } @{$self->_gcodegen->writer->extruders}; my @extruder_ids = map { $_->id } @{$self->_gcodegen->writer->extruders};
$gcode .= $self->_gcodegen->set_extruder($extruder_ids[0]); $gcode .= $self->_gcodegen->set_extruder($extruder_ids[0]);
# skip skirt if we have a large brim # skip skirt if we have a large brim
if ($layer->id < $self->print->config->skirt_height || $self->print->config->skirt_height == -1) { if ($layer->id < $self->print->config->skirt_height || $self->print->has_infinite_skirt) {
my $skirt_flow = $self->print->skirt_flow; my $skirt_flow = $self->print->skirt_flow;
# distribute skirt loops across all extruders # distribute skirt loops across all extruders

View file

@ -6,7 +6,7 @@ use List::Util qw(min max sum first);
use Slic3r::Flow ':roles'; use Slic3r::Flow ':roles';
use Slic3r::Geometry qw(X Y Z PI scale unscale chained_path); use Slic3r::Geometry qw(X Y Z PI scale unscale chained_path);
use Slic3r::Geometry::Clipper qw(diff diff_ex intersection intersection_ex union union_ex use Slic3r::Geometry::Clipper qw(diff diff_ex intersection intersection_ex union union_ex
offset offset_ex offset2 offset2_ex CLIPPER_OFFSET_SCALE JT_MITER); offset offset_ex offset2 offset2_ex intersection_ppl CLIPPER_OFFSET_SCALE JT_MITER);
use Slic3r::Print::State ':steps'; use Slic3r::Print::State ':steps';
use Slic3r::Surface ':types'; use Slic3r::Surface ':types';
@ -344,7 +344,6 @@ sub make_perimeters {
my $self = shift; my $self = shift;
# prerequisites # prerequisites
$self->print->init_extruders;
$self->slice; $self->slice;
return if $self->step_done(STEP_PERIMETERS); return if $self->step_done(STEP_PERIMETERS);
@ -369,51 +368,59 @@ sub make_perimeters {
my $region = $self->print->regions->[$region_id]; my $region = $self->print->regions->[$region_id];
my $region_perimeters = $region->config->perimeters; my $region_perimeters = $region->config->perimeters;
if ($region->config->extra_perimeters && $region_perimeters > 0 && $region->config->fill_density > 0) { next if !$region->config->extra_perimeters;
for my $i (0 .. ($self->layer_count - 2)) { next if $region_perimeters == 0;
my $layerm = $self->get_layer($i)->regions->[$region_id]; next if $region->config->fill_density == 0;
my $upper_layerm = $self->get_layer($i+1)->regions->[$region_id];
my $perimeter_spacing = $layerm->flow(FLOW_ROLE_PERIMETER)->scaled_spacing; for my $i (0 .. ($self->layer_count - 2)) {
my $ext_perimeter_spacing = $layerm->flow(FLOW_ROLE_EXTERNAL_PERIMETER)->scaled_spacing; my $layerm = $self->get_layer($i)->get_region($region_id);
my $upper_layerm = $self->get_layer($i+1)->get_region($region_id);
my $overlap = $perimeter_spacing; # one perimeter
my $perimeter_spacing = $layerm->flow(FLOW_ROLE_PERIMETER)->scaled_spacing;
my $diff = diff( my $ext_perimeter_flow = $layerm->flow(FLOW_ROLE_EXTERNAL_PERIMETER);
offset([ map @{$_->expolygon}, @{$layerm->slices} ], -($ext_perimeter_spacing + ($region_perimeters-1) * $perimeter_spacing)), my $ext_perimeter_width = $ext_perimeter_flow->scaled_width;
offset([ map @{$_->expolygon}, @{$upper_layerm->slices} ], -$overlap), my $ext_perimeter_spacing = $ext_perimeter_flow->scaled_spacing;
);
next if !@$diff; foreach my $slice (@{$layerm->slices}) {
# if we need more perimeters, $diff should contain a narrow region that we can collapse while (1) {
# compute the total thickness of perimeters
# we use a higher miterLimit here to handle areas with acute angles my $perimeters_thickness = $ext_perimeter_width/2 + $ext_perimeter_spacing/2
# in those cases, the default miterLimit would cut the corner and we'd + ($region_perimeters-1 + $slice->extra_perimeters) * $perimeter_spacing;
# get a triangle that would trigger a non-needed extra perimeter
$diff = diff( # define a critical area where we don't want the upper slice to fall into
$diff, # (it should either lay over our perimeters or outside this area)
offset2($diff, -$perimeter_spacing, +$perimeter_spacing, CLIPPER_OFFSET_SCALE, JT_MITER, 5), my $critical_area_depth = $perimeter_spacing*1.5;
1, my $critical_area = diff(
); offset($slice->expolygon->arrayref, -$perimeters_thickness),
next if !@$diff; offset($slice->expolygon->arrayref, -($perimeters_thickness + $critical_area_depth)),
# diff contains the collapsed area );
foreach my $slice (@{$layerm->slices}) { # check whether a portion of the upper slices falls inside the critical area
my $extra_perimeters = 0; my $intersection = intersection_ppl(
CYCLE: while (1) { [ map $_->p, @{$upper_layerm->slices} ],
# compute polygons representing the thickness of the hypotetical new internal perimeter $critical_area,
# of our slice );
$extra_perimeters++;
my $hypothetical_perimeter = diff( # only add an additional loop if at least 30% of the slice loop would benefit from it
offset($slice->expolygon->arrayref, -($perimeter_spacing * ($region_perimeters + $extra_perimeters-1))), my $total_loop_length = sum(map $_->length, map $_->p, @{$upper_layerm->slices}) // 0;
offset($slice->expolygon->arrayref, -($perimeter_spacing * ($region_perimeters + $extra_perimeters))), my $total_intersection_length = sum(map $_->length, @$intersection) // 0;
last unless $total_intersection_length > $total_loop_length*0.3;
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"extra.svg",
no_arrows => 1,
expolygons => union_ex($critical_area),
polylines => [ map $_->split_at_first_point, map $_->p, @{$upper_layerm->slices} ],
); );
last CYCLE if !@$hypothetical_perimeter; # no extra perimeter is possible
# only add the perimeter if there's an intersection with the collapsed area
last CYCLE if !@{ intersection($diff, $hypothetical_perimeter) };
Slic3r::debugf " adding one more perimeter at layer %d\n", $layerm->id;
$slice->extra_perimeters($extra_perimeters);
} }
$slice->extra_perimeters($slice->extra_perimeters + 1);
} }
Slic3r::debugf " adding %d more perimeter(s) at layer %d\n",
$slice->extra_perimeters, $layerm->id
if $slice->extra_perimeters > 0;
} }
} }
} }
@ -524,7 +531,6 @@ sub generate_support_material {
my $self = shift; my $self = shift;
# prerequisites # prerequisites
$self->print->init_extruders;
$self->slice; $self->slice;
return if $self->step_done(STEP_SUPPORTMATERIAL); return if $self->step_done(STEP_SUPPORTMATERIAL);
@ -547,7 +553,7 @@ sub _support_material {
my ($self) = @_; my ($self) = @_;
my $first_layer_flow = Slic3r::Flow->new_from_width( my $first_layer_flow = Slic3r::Flow->new_from_width(
width => ($self->config->first_layer_extrusion_width || $self->config->support_material_extrusion_width), width => ($self->print->config->first_layer_extrusion_width || $self->config->support_material_extrusion_width),
role => FLOW_ROLE_SUPPORT_MATERIAL, role => FLOW_ROLE_SUPPORT_MATERIAL,
nozzle_diameter => $self->print->config->nozzle_diameter->[ $self->config->support_material_extruder-1 ] nozzle_diameter => $self->print->config->nozzle_diameter->[ $self->config->support_material_extruder-1 ]
// $self->print->config->nozzle_diameter->[0], // $self->print->config->nozzle_diameter->[0],
@ -650,7 +656,7 @@ sub detect_surfaces_type {
# if we have raft layers, consider bottom layer as a bridge # if we have raft layers, consider bottom layer as a bridge
# just like any other bottom surface lying on the void # just like any other bottom surface lying on the void
if ($self->config->raft_layers > 0) { if ($self->config->raft_layers > 0 && $self->config->support_material_contact_distance > 0) {
$_->surface_type(S_TYPE_BOTTOMBRIDGE) for @bottom; $_->surface_type(S_TYPE_BOTTOMBRIDGE) for @bottom;
} else { } else {
$_->surface_type(S_TYPE_BOTTOM) for @bottom; $_->surface_type(S_TYPE_BOTTOM) for @bottom;
@ -714,61 +720,95 @@ sub clip_fill_surfaces {
# We only want infill under ceilings; this is almost like an # We only want infill under ceilings; this is almost like an
# internal support material. # internal support material.
my $additional_margin = scale 3*0; # proceed top-down skipping bottom layer
my $upper_internal = [];
my $overhangs = []; # arrayref of polygons for my $layer_id (reverse 1..($self->layer_count - 1)) {
for my $layer_id (reverse 0..($self->layer_count - 1)) { my $layer = $self->get_layer($layer_id);
my $layer = $self->get_layer($layer_id); my $lower_layer = $self->get_layer($layer_id-1);
my @layer_internal = (); # arrayref of Surface objects
my @new_internal = (); # arrayref of Surface objects
# clip this layer's internal surfaces to @overhangs # detect things that we need to support
foreach my $layerm (@{$layer->regions}) { my $overhangs = []; # Polygons
# we need to support any solid surface
push @$overhangs, map $_->p,
grep $_->is_solid, map @{$_->fill_surfaces}, @{$layer->regions};
# we also need to support perimeters when there's at least one full
# unsupported loop
{
# get perimeters area as the difference between slices and fill_surfaces
my $perimeters = diff(
[ map @$_, @{$layer->slices} ],
[ map $_->p, map @{$_->fill_surfaces}, @{$layer->regions} ],
);
# only consider the area that is not supported by lower perimeters
$perimeters = intersection(
$perimeters,
[ map $_->p, map @{$_->fill_surfaces}, @{$lower_layer->regions} ],
1,
);
# only consider perimeter areas that are at least one extrusion width thick
my $pw = min(map $_->flow(FLOW_ROLE_PERIMETER)->scaled_width, @{$layer->regions});
$perimeters = offset2($perimeters, -$pw, +$pw);
# append such thick perimeters to the areas that need support
push @$overhangs, @$perimeters;
}
# find new internal infill
$upper_internal = my $new_internal = intersection(
[
@$overhangs,
@$upper_internal,
],
[
# our current internal fill boundaries
map $_->p,
grep $_->surface_type == S_TYPE_INTERNAL || $_->surface_type == S_TYPE_INTERNALVOID,
map @{$_->fill_surfaces}, @{$lower_layer->regions}
],
);
# apply new internal infill to regions
foreach my $layerm (@{$lower_layer->regions}) {
my (@internal, @other) = (); my (@internal, @other) = ();
foreach my $surface (map $_->clone, @{$layerm->fill_surfaces}) { foreach my $surface (map $_->clone, @{$layerm->fill_surfaces}) {
if ($surface->surface_type == S_TYPE_INTERNAL) { if ($surface->surface_type == S_TYPE_INTERNAL || $surface->surface_type == S_TYPE_INTERNALVOID) {
push @internal, $surface; push @internal, $surface;
} else { } else {
push @other, $surface; push @other, $surface;
} }
} }
# keep all the original internal surfaces to detect overhangs in this layer my @new = map Slic3r::Surface->new(
push @layer_internal, @internal;
push @new_internal, my @new = map Slic3r::Surface->new(
expolygon => $_, expolygon => $_,
surface_type => S_TYPE_INTERNAL, surface_type => S_TYPE_INTERNAL,
), ),
@{intersection_ex( @{intersection_ex(
[ map $_->p, @internal ], [ map $_->p, @internal ],
$overhangs, $new_internal,
1,
)}; )};
push @new, map Slic3r::Surface->new( push @other, map Slic3r::Surface->new(
expolygon => $_, expolygon => $_,
surface_type => S_TYPE_INTERNALVOID, surface_type => S_TYPE_INTERNALVOID,
), ),
@{diff_ex( @{diff_ex(
[ map $_->p, @internal ], [ map $_->p, @internal ],
$overhangs, $new_internal,
1,
)}; )};
# If there are voids it means that our internal infill is not adjacent to
# perimeters. In this case it would be nice to add a loop around infill to
# make it more robust and nicer. TODO.
$layerm->fill_surfaces->clear; $layerm->fill_surfaces->clear;
$layerm->fill_surfaces->append($_) for (@new, @other); $layerm->fill_surfaces->append($_) for (@new, @other);
} }
# get this layer's overhangs defined as the full slice minus the internal infill
# (thus we also consider perimeters)
if ($layer_id > 0) {
my $solid = diff(
[ map $_->p, map @{$_->fill_surfaces}, @{$layer->regions} ],
[ map $_->p, @layer_internal ],
);
$overhangs = offset($solid, +$additional_margin);
push @$overhangs, map $_->p, @new_internal; # propagate upper overhangs
}
} }
} }

View file

@ -4,7 +4,7 @@ use warnings;
require Exporter; require Exporter;
our @ISA = qw(Exporter); our @ISA = qw(Exporter);
our @EXPORT_OK = qw(STEP_INIT_EXTRUDERS STEP_SLICE STEP_PERIMETERS STEP_PREPARE_INFILL our @EXPORT_OK = qw(STEP_SLICE STEP_PERIMETERS STEP_PREPARE_INFILL
STEP_INFILL STEP_SUPPORTMATERIAL STEP_SKIRT STEP_BRIM); STEP_INFILL STEP_SUPPORTMATERIAL STEP_SKIRT STEP_BRIM);
our %EXPORT_TAGS = (steps => \@EXPORT_OK); our %EXPORT_TAGS = (steps => \@EXPORT_OK);

View file

@ -723,6 +723,11 @@ sub generate_toolpaths {
if (@$base) { if (@$base) {
my $filler = $fillers{support}; my $filler = $fillers{support};
$filler->angle($angles[ ($layer_id) % @angles ]); $filler->angle($angles[ ($layer_id) % @angles ]);
# We don't use $base_flow->spacing because we need a constant spacing
# value that guarantees that all layers are correctly aligned.
$filler->spacing($flow->spacing);
my $density = $support_density; my $density = $support_density;
my $base_flow = $_flow; my $base_flow = $_flow;
@ -737,6 +742,10 @@ sub generate_toolpaths {
$filler->angle($self->object_config->support_material_angle + 90); $filler->angle($self->object_config->support_material_angle + 90);
$density = 0.5; $density = 0.5;
$base_flow = $self->first_layer_flow; $base_flow = $self->first_layer_flow;
# use the proper spacing for first layer as we don't need to align
# its pattern to the other layers
$filler->spacing($base_flow->spacing);
} else { } else {
# draw a perimeter all around support infill # draw a perimeter all around support infill
# TODO: use brim ordering algorithm # TODO: use brim ordering algorithm
@ -753,10 +762,6 @@ sub generate_toolpaths {
$to_infill = offset_ex([ map @$_, @$to_infill ], -$_flow->scaled_spacing); $to_infill = offset_ex([ map @$_, @$to_infill ], -$_flow->scaled_spacing);
} }
# We don't use $base_flow->spacing because we need a constant spacing
# value that guarantees that all layers are correctly aligned.
$filler->spacing($flow->spacing);
my $mm3_per_mm = $base_flow->mm3_per_mm; my $mm3_per_mm = $base_flow->mm3_per_mm;
foreach my $expolygon (@$to_infill) { foreach my $expolygon (@$to_infill) {
my @p = $filler->fill_surface( my @p = $filler->fill_surface(

View file

@ -90,7 +90,7 @@ $config->apply($cli_config);
# launch GUI # launch GUI
my $gui; my $gui;
if (!@ARGV && !$opt{save} && eval "require Slic3r::GUI; 1") { if ((!@ARGV || $opt{gui}) && !$opt{save} && eval "require Slic3r::GUI; 1") {
{ {
no warnings 'once'; no warnings 'once';
$Slic3r::GUI::datadir = Slic3r::decode_path($opt{datadir}); $Slic3r::GUI::datadir = Slic3r::decode_path($opt{datadir});
@ -102,6 +102,9 @@ if (!@ARGV && !$opt{save} && eval "require Slic3r::GUI; 1") {
setlocale(LC_NUMERIC, 'C'); setlocale(LC_NUMERIC, 'C');
$gui->{mainframe}->load_config_file($_) for @{$opt{load}}; $gui->{mainframe}->load_config_file($_) for @{$opt{load}};
$gui->{mainframe}->load_config($cli_config); $gui->{mainframe}->load_config($cli_config);
foreach my $input_file (@ARGV) {
$gui->{mainframe}{plater}->load_file($input_file) unless $opt{no_plater};
}
$gui->MainLoop; $gui->MainLoop;
exit; exit;
} }
@ -261,6 +264,8 @@ Usage: slic3r.pl [ OPTIONS ] [ file.stl ] [ file2.stl ] ...
$j $j
GUI options: GUI options:
--gui Forces the GUI launch instead of command line slicing (if you
supply a model file, it will be loaded into the plater)
--no-plater Disable the plater tab --no-plater Disable the plater tab
--gui-mode Overrides the configured mode (simple/expert) --gui-mode Overrides the configured mode (simple/expert)
--autosave <file> Automatically export current configuration to the specified file --autosave <file> Automatically export current configuration to the specified file
@ -282,7 +287,7 @@ $j
(default: 100,100) (default: 100,100)
--z-offset Additional height in mm to add to vertical coordinates --z-offset Additional height in mm to add to vertical coordinates
(+/-, default: $config->{z_offset}) (+/-, default: $config->{z_offset})
--gcode-flavor The type of G-code to generate (reprap/teacup/makerware/sailfish/mach3/no-extrusion, --gcode-flavor The type of G-code to generate (reprap/teacup/makerware/sailfish/mach3/machinekit/no-extrusion,
default: $config->{gcode_flavor}) default: $config->{gcode_flavor})
--use-relative-e-distances Enable this to get relative E values (default: no) --use-relative-e-distances Enable this to get relative E values (default: no)
--use-firmware-retraction Enable firmware-controlled retraction using G10/G11 (default: no) --use-firmware-retraction Enable firmware-controlled retraction using G10/G11 (default: no)

View file

@ -124,7 +124,6 @@ if (0) {
# copy of Print::export_gcode() up to the point # copy of Print::export_gcode() up to the point
# after fill surfaces are combined # after fill surfaces are combined
$self->init_extruders;
$_->slice for @{$self->objects}; $_->slice for @{$self->objects};
$_->make_perimeters for @{$self->objects}; $_->make_perimeters for @{$self->objects};
$_->detect_surfaces_type for @{$self->objects}; $_->detect_surfaces_type for @{$self->objects};

View file

@ -12,7 +12,7 @@ BEGIN {
use List::Util qw(first sum); use List::Util qw(first sum);
use Slic3r; use Slic3r;
use Slic3r::Geometry qw(X Y scale unscale convex_hull); use Slic3r::Geometry qw(X Y scale unscale convex_hull);
use Slic3r::Geometry::Clipper qw(union diff_ex offset); use Slic3r::Geometry::Clipper qw(union diff diff_ex offset offset2_ex);
use Slic3r::Surface qw(:types); use Slic3r::Surface qw(:types);
use Slic3r::Test; use Slic3r::Test;
@ -20,7 +20,6 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
{ {
my $print = Slic3r::Print->new; my $print = Slic3r::Print->new;
$print->init_extruders;
my $filler = Slic3r::Fill::Rectilinear->new( my $filler = Slic3r::Fill::Rectilinear->new(
print => $print, print => $print,
bounding_box => Slic3r::Geometry::BoundingBox->new_from_points([ Slic3r::Point->new(0, 0), Slic3r::Point->new(10, 10) ]), bounding_box => Slic3r::Geometry::BoundingBox->new_from_points([ Slic3r::Point->new(0, 0), Slic3r::Point->new(10, 10) ]),
@ -249,18 +248,27 @@ for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) {
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config->new_from_defaults;
$config->set('skirts', 0); $config->set('skirts', 0);
$config->set('perimeters', 0); $config->set('perimeters', 1);
$config->set('fill_density', 0); $config->set('fill_density', 0);
$config->set('top_solid_layers', 0); $config->set('top_solid_layers', 0);
$config->set('bottom_solid_layers', 0); $config->set('bottom_solid_layers', 0);
$config->set('solid_infill_below_area', 20000000); $config->set('solid_infill_below_area', 20000000);
$config->set('solid_infill_every_layers', 2); $config->set('solid_infill_every_layers', 2);
$config->set('perimeter_speed', 99);
$config->set('external_perimeter_speed', 99);
$config->set('cooling', 0);
$config->set('first_layer_speed', '100%');
my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
my %layers_with_extrusion = (); my %layers_with_extrusion = ();
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
my ($self, $cmd, $args, $info) = @_; my ($self, $cmd, $args, $info) = @_;
$layers_with_extrusion{$self->Z} = 1 if $info->{extruding};
if ($cmd eq 'G1' && $info->{dist_XY} > 0 && $info->{extruding}) {
if (($args->{F} // $self->F) != $config->perimeter_speed*60) {
$layers_with_extrusion{$self->Z} = ($args->{F} // $self->F);
}
}
}); });
ok !%layers_with_extrusion, ok !%layers_with_extrusion,
@ -276,6 +284,7 @@ for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) {
$config->set('first_layer_height', 0.2); $config->set('first_layer_height', 0.2);
$config->set('nozzle_diameter', [0.35]); $config->set('nozzle_diameter', [0.35]);
$config->set('infill_extruder', 2); $config->set('infill_extruder', 2);
$config->set('solid_infill_extruder', 2);
$config->set('infill_extrusion_width', 0.52); $config->set('infill_extrusion_width', 0.52);
$config->set('solid_infill_extrusion_width', 0.52); $config->set('solid_infill_extrusion_width', 0.52);
$config->set('first_layer_extrusion_width', 0); $config->set('first_layer_extrusion_width', 0);
@ -302,7 +311,9 @@ for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) {
my $grow_d = scale($config->infill_extrusion_width)/2; my $grow_d = scale($config->infill_extrusion_width)/2;
my $layer0_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.2} } ]); my $layer0_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.2} } ]);
my $layer1_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.4} } ]); my $layer1_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.4} } ]);
my $diff = [ grep { $_->area > 2*(($grow_d*2)**2) } @{diff_ex($layer0_infill, $layer1_infill)} ]; my $diff = diff($layer0_infill, $layer1_infill);
$diff = offset2_ex($diff, -$grow_d, +$grow_d);
$diff = [ grep { $_->area > 2*(($grow_d*2)**2) } @$diff ];
is scalar(@$diff), 0, 'no missing parts in solid shell when fill_density is 0'; is scalar(@$diff), 0, 'no missing parts in solid shell when fill_density is 0';
} }

View file

@ -1,4 +1,4 @@
use Test::More tests => 20; use Test::More tests => 22;
use strict; use strict;
use warnings; use warnings;
@ -86,6 +86,7 @@ use Slic3r::Test;
# - no hard-coded "E" are generated # - no hard-coded "E" are generated
# - Z moves are correctly generated for both objects # - Z moves are correctly generated for both objects
# - no travel moves go outside skirt # - no travel moves go outside skirt
# - temperatures are set correctly
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config->new_from_defaults;
$config->set('gcode_comments', 1); $config->set('gcode_comments', 1);
$config->set('complete_objects', 1); $config->set('complete_objects', 1);
@ -93,11 +94,14 @@ use Slic3r::Test;
$config->set('start_gcode', ''); # prevent any default extra Z move $config->set('start_gcode', ''); # prevent any default extra Z move
$config->set('layer_height', 0.4); $config->set('layer_height', 0.4);
$config->set('first_layer_height', 0.4); $config->set('first_layer_height', 0.4);
$config->set('temperature', [200]);
$config->set('first_layer_temperature', [210]);
my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2); my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2);
ok my $gcode = Slic3r::Test::gcode($print), "complete_objects"; ok my $gcode = Slic3r::Test::gcode($print), "complete_objects";
my @z_moves = (); my @z_moves = ();
my @travel_moves = (); # array of scaled points my @travel_moves = (); # array of scaled points
my @extrusions = (); # array of scaled points my @extrusions = (); # array of scaled points
my @temps = ();
Slic3r::GCode::Reader->new->parse($gcode, sub { Slic3r::GCode::Reader->new->parse($gcode, sub {
my ($self, $cmd, $args, $info) = @_; my ($self, $cmd, $args, $info) = @_;
fail 'unexpected E argument' if defined $args->{E}; fail 'unexpected E argument' if defined $args->{E};
@ -112,6 +116,8 @@ use Slic3r::Test;
push @travel_moves, Slic3r::Point->new_scale($info->{new_X}, $info->{new_Y}) push @travel_moves, Slic3r::Point->new_scale($info->{new_X}, $info->{new_Y})
if @extrusions; # skip initial travel move to first skirt point if @extrusions; # skip initial travel move to first skirt point
} }
} elsif ($cmd eq 'M104' || $cmd eq 'M109') {
push @temps, $args->{S} if !@temps || $args->{S} != $temps[-1];
} }
}); });
my $layer_count = 20/0.4; # cube is 20mm tall my $layer_count = 20/0.4; # cube is 20mm tall
@ -120,6 +126,8 @@ use Slic3r::Test;
my $convex_hull = convex_hull(\@extrusions); my $convex_hull = convex_hull(\@extrusions);
ok !(defined first { !$convex_hull->contains_point($_) } @travel_moves), 'all travel moves happen within skirt'; ok !(defined first { !$convex_hull->contains_point($_) } @travel_moves), 'all travel moves happen within skirt';
is_deeply \@temps, [210, 200, 210, 200, 0], 'expected temperature changes';
} }
{ {
@ -184,4 +192,12 @@ use Slic3r::Test;
} }
} }
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('start_gcode', 'START:[input_filename]');
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
my $gcode = Slic3r::Test::gcode($print);
like $gcode, qr/START:20mm_cube/, '[input_filename] is also available in custom G-code';
}
__END__ __END__

File diff suppressed because one or more lines are too long

View file

@ -72,6 +72,9 @@ use Slic3r::Test;
$config->set('bottom_solid_layers', 0); $config->set('bottom_solid_layers', 0);
ok $test->(), "no shells are applied when both top and bottom are set to zero"; ok $test->(), "no shells are applied when both top and bottom are set to zero";
$config->set('perimeters', 1);
$config->set('top_solid_layers', 3);
$config->set('bottom_solid_layers', 3);
$config->set('fill_density', 0); $config->set('fill_density', 0);
ok $test->(), "proper number of shells is applied even when fill density is none"; ok $test->(), "proper number of shells is applied even when fill density is none";
} }

View file

@ -1,4 +1,4 @@
use Test::More tests => 20; use Test::More tests => 25;
use strict; use strict;
use warnings; use warnings;
@ -20,7 +20,6 @@ use Slic3r::Test;
my $test = sub { my $test = sub {
my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
$print->print->init_extruders;
my $flow = $print->print->objects->[0]->support_material_flow; my $flow = $print->print->objects->[0]->support_material_flow;
my $support = Slic3r::Print::SupportMaterial->new( my $support = Slic3r::Print::SupportMaterial->new(
object_config => $print->print->objects->[0]->config, object_config => $print->print->objects->[0]->config,
@ -180,4 +179,54 @@ use Slic3r::Test;
$test->(70); $test->(70);
} }
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('brim_width', 0);
$config->set('skirts', 0);
$config->set('support_material', 1);
$config->set('top_solid_layers', 0); # so that we don't have the internal bridge over infill
$config->set('bridge_speed', 99);
$config->set('cooling', 0);
$config->set('first_layer_speed', '100%');
my $test = sub {
my $print = Slic3r::Test::init_print('overhang', config => $config);
my $has_bridge_speed = 0;
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
my ($self, $cmd, $args, $info) = @_;
if ($info->{extruding}) {
if (($args->{F} // $self->F) == $config->bridge_speed*60) {
$has_bridge_speed = 1;
}
}
});
return $has_bridge_speed;
};
$config->set('support_material_contact_distance', 0.2);
ok $test->(), 'bridge speed is used when support_material_contact_distance > 0';
$config->set('support_material_contact_distance', 0);
ok !$test->(), 'bridge speed is not used when support_material_contact_distance == 0';
$config->set('raft_layers', 5);
$config->set('support_material_contact_distance', 0.2);
ok $test->(), 'bridge speed is used when raft_layers > 0 and support_material_contact_distance > 0';
$config->set('support_material_contact_distance', 0);
ok !$test->(), 'bridge speed is not used when raft_layers > 0 and support_material_contact_distance == 0';
}
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('raft_layers', 3);
$config->set('nozzle_diameter', [0.4, 1]);
$config->set('first_layer_height', 0.8);
$config->set('support_material_extruder', 2);
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';
}
__END__ __END__

View file

@ -22,7 +22,7 @@ _arguments -S \
'*--nozzle-diameter[specify nozzle diameter]:nozzle diameter in mm' \ '*--nozzle-diameter[specify nozzle diameter]:nozzle diameter in mm' \
'--print-center[specify print center coordinates]:print center coordinates in mm,mm' \ '--print-center[specify print center coordinates]:print center coordinates in mm,mm' \
'--z-offset[specify Z-axis offset]:Z-axis offset in mm' \ '--z-offset[specify Z-axis offset]:Z-axis offset in mm' \
'--gcode-flavor[specify the type of G-code to generate]:G-code flavor:(reprap teacup makerware sailfish mach3 no-extrusion)' \ '--gcode-flavor[specify the type of G-code to generate]:G-code flavor:(reprap teacup makerware sailfish mach3 machinekit no-extrusion)' \
'(--use-relative-e-distances --no-use-relative-e-distances)'--{no-,}use-relative-e-distances'[disable/enable relative E values]' \ '(--use-relative-e-distances --no-use-relative-e-distances)'--{no-,}use-relative-e-distances'[disable/enable relative E values]' \
'--extrusion-axis[specify letter associated with the extrusion axis]:extrusion axis letter' \ '--extrusion-axis[specify letter associated with the extrusion axis]:extrusion axis letter' \
'(--gcode-arcs --no-gcode-arcs)'--{no-,}gcode-arcs'[disable/enable G2/G3 commands for native arcs]' \ '(--gcode-arcs --no-gcode-arcs)'--{no-,}gcode-arcs'[disable/enable G2/G3 commands for native arcs]' \

View file

@ -63,7 +63,7 @@ my $build = Module::Build::WithXSpp->new(
dist_abstract => 'XS code for Slic3r', dist_abstract => 'XS code for Slic3r',
build_requires => {qw( build_requires => {qw(
ExtUtils::ParseXS 3.18 ExtUtils::ParseXS 3.18
ExtUtils::Typemap 1.00 ExtUtils::Typemaps 1.00
ExtUtils::Typemaps::Default 1.05 ExtUtils::Typemaps::Default 1.05
ExtUtils::XSpp 0.17 ExtUtils::XSpp 0.17
Module::Build 0.3601 Module::Build 0.3601

View file

@ -135,7 +135,21 @@ stl_count_facets(stl_file *stl, char *file) {
/* Otherwise, if the .STL file is ASCII, then do the following */ /* Otherwise, if the .STL file is ASCII, then do the following */
else { else {
/* Reopen the file in text mode (for getting correct newlines on Windows) */ /* Reopen the file in text mode (for getting correct newlines on Windows) */
freopen(file, "r", stl->fp); // fix to silence a warning about unused return value.
// obviously if it fails we have problems....
stl->fp = freopen(file, "r", stl->fp);
// do another null check to be safe
if(stl->fp == NULL) {
error_msg = (char*)
malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */
sprintf(error_msg, "stl_initialize: Couldn't open %s for reading",
file);
perror(error_msg);
free(error_msg);
stl->error = 1;
return;
}
/* Find the number of facets */ /* Find the number of facets */
j = 0; j = 0;

View file

@ -1,10 +1,10 @@
/******************************************************************************* /*******************************************************************************
* * * *
* Author : Angus Johnson * * Author : Angus Johnson *
* Version : 6.2.1 * * Version : 6.2.9 *
* Date : 31 October 2014 * * Date : 16 February 2015 *
* Website : http://www.angusj.com * * Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2014 * * Copyright : Angus Johnson 2010-2015 *
* * * *
* License: * * License: *
* Use, modification & distribution is subject to Boost Software License Ver 1. * * Use, modification & distribution is subject to Boost Software License Ver 1. *
@ -381,13 +381,6 @@ Int128 Int128Mul (long64 lhs, long64 rhs)
// Miscellaneous global functions // Miscellaneous global functions
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void Swap(cInt& val1, cInt& val2)
{
cInt tmp = val1;
val1 = val2;
val2 = tmp;
}
//------------------------------------------------------------------------------
bool Orientation(const Path &poly) bool Orientation(const Path &poly)
{ {
return Area(poly) >= 0; return Area(poly) >= 0;
@ -435,11 +428,11 @@ bool PointIsVertex(const IntPoint &Pt, OutPt *pp)
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
int PointInPolygon (const IntPoint &pt, const Path &path) //See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos
//http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf
int PointInPolygon(const IntPoint &pt, const Path &path)
{ {
//returns 0 if false, +1 if true, -1 if pt ON polygon boundary //returns 0 if false, +1 if true, -1 if pt ON polygon boundary
//See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos
//http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf
int result = 0; int result = 0;
size_t cnt = path.size(); size_t cnt = path.size();
if (cnt < 3) return 0; if (cnt < 3) return 0;
@ -758,9 +751,9 @@ inline void ReverseHorizontal(TEdge &e)
//swap horizontal edges' Top and Bottom x's so they follow the natural //swap horizontal edges' Top and Bottom x's so they follow the natural
//progression of the bounds - ie so their xbots will align with the //progression of the bounds - ie so their xbots will align with the
//adjoining lower edge. [Helpful in the ProcessHorizontal() method.] //adjoining lower edge. [Helpful in the ProcessHorizontal() method.]
Swap(e.Top.X, e.Bot.X); std::swap(e.Top.X, e.Bot.X);
#ifdef use_xyz #ifdef use_xyz
Swap(e.Top.Z, e.Bot.Z); std::swap(e.Top.Z, e.Bot.Z);
#endif #endif
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -866,8 +859,8 @@ bool Pt2IsBetweenPt1AndPt3(const IntPoint pt1,
bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b) bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b)
{ {
if (seg1a > seg1b) Swap(seg1a, seg1b); if (seg1a > seg1b) std::swap(seg1a, seg1b);
if (seg2a > seg2b) Swap(seg2a, seg2b); if (seg2a > seg2b) std::swap(seg2a, seg2b);
return (seg1a < seg2b) && (seg2a < seg1b); return (seg1a < seg2b) && (seg2a < seg1b);
} }
@ -976,16 +969,13 @@ TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward)
EStart = E->Prev; EStart = E->Prev;
else else
EStart = E->Next; EStart = E->Next;
if (EStart->OutIdx != Skip) if (IsHorizontal(*EStart)) //ie an adjoining horizontal skip edge
{
if (IsHorizontal(*EStart)) //ie an adjoining horizontal skip edge
{ {
if (EStart->Bot.X != E->Bot.X && EStart->Top.X != E->Bot.X) if (EStart->Bot.X != E->Bot.X && EStart->Top.X != E->Bot.X)
ReverseHorizontal(*E); ReverseHorizontal(*E);
} }
else if (EStart->Bot.X != E->Bot.X) else if (EStart->Bot.X != E->Bot.X)
ReverseHorizontal(*E); ReverseHorizontal(*E);
}
} }
EStart = E; EStart = E;
@ -1000,11 +990,7 @@ TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward)
//unless a Skip edge is encountered when that becomes the top divide //unless a Skip edge is encountered when that becomes the top divide
Horz = Result; Horz = Result;
while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev; while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev;
if (Horz->Prev->Top.X == Result->Next->Top.X) if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev;
{
if (!NextIsForward) Result = Horz->Prev;
}
else if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev;
} }
while (E != Result) while (E != Result)
{ {
@ -1024,11 +1010,8 @@ TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward)
{ {
Horz = Result; Horz = Result;
while (IsHorizontal(*Horz->Next)) Horz = Horz->Next; while (IsHorizontal(*Horz->Next)) Horz = Horz->Next;
if (Horz->Next->Top.X == Result->Prev->Top.X) if (Horz->Next->Top.X == Result->Prev->Top.X ||
{ Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next;
if (!NextIsForward) Result = Horz->Next;
}
else if (Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next;
} }
while (E != Result) while (E != Result)
@ -1155,17 +1138,17 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed)
return false; return false;
} }
E->Prev->OutIdx = Skip; E->Prev->OutIdx = Skip;
if (E->Prev->Bot.X < E->Prev->Top.X) ReverseHorizontal(*E->Prev);
MinimaList::value_type locMin; MinimaList::value_type locMin;
locMin.Y = E->Bot.Y; locMin.Y = E->Bot.Y;
locMin.LeftBound = 0; locMin.LeftBound = 0;
locMin.RightBound = E; locMin.RightBound = E;
locMin.RightBound->Side = esRight; locMin.RightBound->Side = esRight;
locMin.RightBound->WindDelta = 0; locMin.RightBound->WindDelta = 0;
while (E->Next->OutIdx != Skip) for (;;)
{ {
E->NextInLML = E->Next;
if (E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); if (E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E);
if (E->Next->OutIdx == Skip) break;
E->NextInLML = E->Next;
E = E->Next; E = E->Next;
} }
m_MinimaList.push_back(locMin); m_MinimaList.push_back(locMin);
@ -1371,6 +1354,7 @@ void Clipper::Reset()
{ {
ClipperBase::Reset(); ClipperBase::Reset();
m_Scanbeam = ScanbeamList(); m_Scanbeam = ScanbeamList();
m_Maxima = MaximaList();
m_ActiveEdges = 0; m_ActiveEdges = 0;
m_SortedEdges = 0; m_SortedEdges = 0;
for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end(); ++lm) for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end(); ++lm)
@ -1378,12 +1362,24 @@ void Clipper::Reset()
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
bool Clipper::Execute(ClipType clipType, Paths &solution, PolyFillType fillType)
{
return Execute(clipType, solution, fillType, fillType);
}
//------------------------------------------------------------------------------
bool Clipper::Execute(ClipType clipType, PolyTree &polytree, PolyFillType fillType)
{
return Execute(clipType, polytree, fillType, fillType);
}
//------------------------------------------------------------------------------
bool Clipper::Execute(ClipType clipType, Paths &solution, bool Clipper::Execute(ClipType clipType, Paths &solution,
PolyFillType subjFillType, PolyFillType clipFillType) PolyFillType subjFillType, PolyFillType clipFillType)
{ {
if( m_ExecuteLocked ) return false; if( m_ExecuteLocked ) return false;
if (m_HasOpenPaths) if (m_HasOpenPaths)
throw clipperException("Error: PolyTree struct is need for open path clipping."); throw clipperException("Error: PolyTree struct is needed for open path clipping.");
m_ExecuteLocked = true; m_ExecuteLocked = true;
solution.resize(0); solution.resize(0);
m_SubjFillType = subjFillType; m_SubjFillType = subjFillType;
@ -1439,9 +1435,9 @@ bool Clipper::ExecuteInternal()
cInt botY = PopScanbeam(); cInt botY = PopScanbeam();
do { do {
InsertLocalMinimaIntoAEL(botY); InsertLocalMinimaIntoAEL(botY);
ClearGhostJoins(); ProcessHorizontals();
ProcessHorizontals(false); ClearGhostJoins();
if (m_Scanbeam.empty()) break; if (m_Scanbeam.empty()) break;
cInt topY = PopScanbeam(); cInt topY = PopScanbeam();
succeeded = ProcessIntersections(topY); succeeded = ProcessIntersections(topY);
if (!succeeded) break; if (!succeeded) break;
@ -1471,7 +1467,10 @@ bool Clipper::ExecuteInternal()
for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
{ {
OutRec *outRec = m_PolyOuts[i]; OutRec *outRec = m_PolyOuts[i];
if (outRec->Pts && !outRec->IsOpen) if (!outRec->Pts) continue;
if (outRec->IsOpen)
FixupOutPolyline(*outRec);
else
FixupOutPolygon(*outRec); FixupOutPolygon(*outRec);
} }
@ -1486,17 +1485,16 @@ bool Clipper::ExecuteInternal()
void Clipper::InsertScanbeam(const cInt Y) void Clipper::InsertScanbeam(const cInt Y)
{ {
//if (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) return;// avoid duplicates. m_Scanbeam.push(Y);
m_Scanbeam.push(Y);
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
cInt Clipper::PopScanbeam() cInt Clipper::PopScanbeam()
{ {
const cInt Y = m_Scanbeam.top(); const cInt Y = m_Scanbeam.top();
m_Scanbeam.pop(); m_Scanbeam.pop();
while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) { m_Scanbeam.pop(); } // Pop duplicates. while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) { m_Scanbeam.pop(); } // Pop duplicates.
return Y; return Y;
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -2356,7 +2354,6 @@ OutRec* Clipper::CreateOutRec()
OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt) OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt)
{ {
bool ToFront = (e->Side == esLeft);
if( e->OutIdx < 0 ) if( e->OutIdx < 0 )
{ {
OutRec *outRec = CreateOutRec(); OutRec *outRec = CreateOutRec();
@ -2377,7 +2374,8 @@ OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt)
//OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most' //OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most'
OutPt* op = outRec->Pts; OutPt* op = outRec->Pts;
if (ToFront && (pt == op->Pt)) return op; bool ToFront = (e->Side == esLeft);
if (ToFront && (pt == op->Pt)) return op;
else if (!ToFront && (pt == op->Prev->Pt)) return op->Prev; else if (!ToFront && (pt == op->Prev->Pt)) return op->Prev;
OutPt* newOp = new OutPt; OutPt* newOp = new OutPt;
@ -2393,13 +2391,23 @@ OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt)
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void Clipper::ProcessHorizontals(bool IsTopOfScanbeam) OutPt* Clipper::GetLastOutPt(TEdge *e)
{
OutRec *outRec = m_PolyOuts[e->OutIdx];
if (e->Side == esLeft)
return outRec->Pts;
else
return outRec->Pts->Prev;
}
//------------------------------------------------------------------------------
void Clipper::ProcessHorizontals()
{ {
TEdge* horzEdge = m_SortedEdges; TEdge* horzEdge = m_SortedEdges;
while(horzEdge) while(horzEdge)
{ {
DeleteFromSEL(horzEdge); DeleteFromSEL(horzEdge);
ProcessHorizontal(horzEdge, IsTopOfScanbeam); ProcessHorizontal(horzEdge);
horzEdge = m_SortedEdges; horzEdge = m_SortedEdges;
} }
} }
@ -2564,10 +2572,11 @@ void GetHorzDirection(TEdge& HorzEdge, Direction& Dir, cInt& Left, cInt& Right)
* the AEL. These 'promoted' edges may in turn intersect [%] with other HEs. * * the AEL. These 'promoted' edges may in turn intersect [%] with other HEs. *
*******************************************************************************/ *******************************************************************************/
void Clipper::ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam) void Clipper::ProcessHorizontal(TEdge *horzEdge)
{ {
Direction dir; Direction dir;
cInt horzLeft, horzRight; cInt horzLeft, horzRight;
bool IsOpen = (horzEdge->OutIdx >= 0 && m_PolyOuts[horzEdge->OutIdx]->IsOpen);
GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); GetHorzDirection(*horzEdge, dir, horzLeft, horzRight);
@ -2577,50 +2586,100 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam)
if (!eLastHorz->NextInLML) if (!eLastHorz->NextInLML)
eMaxPair = GetMaximaPair(eLastHorz); eMaxPair = GetMaximaPair(eLastHorz);
for (;;) MaximaList::const_iterator maxIt;
MaximaList::const_reverse_iterator maxRit;
if (m_Maxima.size() > 0)
{ {
//get the first maxima in range (X) ...
if (dir == dLeftToRight)
{
maxIt = m_Maxima.begin();
while (maxIt != m_Maxima.end() && *maxIt <= horzEdge->Bot.X) maxIt++;
if (maxIt != m_Maxima.end() && *maxIt >= eLastHorz->Top.X)
maxIt = m_Maxima.end();
}
else
{
maxRit = m_Maxima.rbegin();
while (maxRit != m_Maxima.rend() && *maxRit > horzEdge->Bot.X) maxRit++;
if (maxRit != m_Maxima.rend() && *maxRit <= eLastHorz->Top.X)
maxRit = m_Maxima.rend();
}
}
OutPt* op1 = 0;
for (;;) //loop through consec. horizontal edges
{
bool IsLastHorz = (horzEdge == eLastHorz); bool IsLastHorz = (horzEdge == eLastHorz);
TEdge* e = GetNextInAEL(horzEdge, dir); TEdge* e = GetNextInAEL(horzEdge, dir);
while(e) while(e)
{ {
//Break if we've got to the end of an intermediate horizontal edge ...
//nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal.
if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML &&
e->Dx < horzEdge->NextInLML->Dx) break;
TEdge* eNext = GetNextInAEL(e, dir); //saves eNext for later //this code block inserts extra coords into horizontal edges (in output
//polygons) whereever maxima touch these horizontal edges. This helps
//'simplifying' polygons (ie if the Simplify property is set).
if (m_Maxima.size() > 0)
{
if (dir == dLeftToRight)
{
while (maxIt != m_Maxima.end() && *maxIt < e->Curr.X)
{
if (horzEdge->OutIdx >= 0 && !IsOpen)
AddOutPt(horzEdge, IntPoint(*maxIt, horzEdge->Bot.Y));
maxIt++;
}
}
else
{
while (maxRit != m_Maxima.rend() && *maxRit > e->Curr.X)
{
if (horzEdge->OutIdx >= 0 && !IsOpen)
AddOutPt(horzEdge, IntPoint(*maxRit, horzEdge->Bot.Y));
maxRit++;
}
}
};
if ((dir == dLeftToRight && e->Curr.X <= horzRight) || if ((dir == dLeftToRight && e->Curr.X > horzRight) ||
(dir == dRightToLeft && e->Curr.X >= horzLeft)) (dir == dRightToLeft && e->Curr.X < horzLeft)) break;
{
//so far we're still in range of the horizontal Edge but make sure //Also break if we've got to the end of an intermediate horizontal edge ...
//nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal.
if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML &&
e->Dx < horzEdge->NextInLML->Dx) break;
if (horzEdge->OutIdx >= 0 && !IsOpen) //note: may be done multiple times
{
op1 = AddOutPt(horzEdge, e->Curr);
TEdge* eNextHorz = m_SortedEdges;
while (eNextHorz)
{
if (eNextHorz->OutIdx >= 0 &&
HorzSegmentsOverlap(horzEdge->Bot.X,
horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X))
{
OutPt* op2 = GetLastOutPt(eNextHorz);
AddJoin(op2, op1, eNextHorz->Top);
}
eNextHorz = eNextHorz->NextInSEL;
}
AddGhostJoin(op1, horzEdge->Bot);
}
//OK, so far we're still in range of the horizontal Edge but make sure
//we're at the last of consec. horizontals when matching with eMaxPair //we're at the last of consec. horizontals when matching with eMaxPair
if(e == eMaxPair && IsLastHorz) if(e == eMaxPair && IsLastHorz)
{ {
if (horzEdge->OutIdx >= 0) if (horzEdge->OutIdx >= 0)
{
OutPt* op1 = AddOutPt(horzEdge, horzEdge->Top);
TEdge* eNextHorz = m_SortedEdges;
while (eNextHorz)
{
if (eNextHorz->OutIdx >= 0 &&
HorzSegmentsOverlap(horzEdge->Bot.X,
horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X))
{
OutPt* op2 = AddOutPt(eNextHorz, eNextHorz->Bot);
AddJoin(op2, op1, eNextHorz->Top);
}
eNextHorz = eNextHorz->NextInSEL;
}
AddGhostJoin(op1, horzEdge->Bot);
AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge->Top); AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge->Top);
}
DeleteFromAEL(horzEdge); DeleteFromAEL(horzEdge);
DeleteFromAEL(eMaxPair); DeleteFromAEL(eMaxPair);
return; return;
} }
else if(dir == dLeftToRight)
if(dir == dLeftToRight)
{ {
IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y);
IntersectEdges(horzEdge, e, Pt); IntersectEdges(horzEdge, e, Pt);
@ -2630,28 +2689,43 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam)
IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y);
IntersectEdges( e, horzEdge, Pt); IntersectEdges( e, horzEdge, Pt);
} }
TEdge* eNext = GetNextInAEL(e, dir);
SwapPositionsInAEL( horzEdge, e ); SwapPositionsInAEL( horzEdge, e );
} e = eNext;
else if( (dir == dLeftToRight && e->Curr.X >= horzRight) || } //end while(e)
(dir == dRightToLeft && e->Curr.X <= horzLeft) ) break;
e = eNext; //Break out of loop if HorzEdge.NextInLML is not also horizontal ...
} //end while if (!horzEdge->NextInLML || !IsHorizontal(*horzEdge->NextInLML)) break;
UpdateEdgeIntoAEL(horzEdge);
if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Bot);
GetHorzDirection(*horzEdge, dir, horzLeft, horzRight);
if (horzEdge->NextInLML && IsHorizontal(*horzEdge->NextInLML))
{
UpdateEdgeIntoAEL(horzEdge);
if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Bot);
GetHorzDirection(*horzEdge, dir, horzLeft, horzRight);
} else
break;
} //end for (;;) } //end for (;;)
if(horzEdge->NextInLML) if (horzEdge->OutIdx >= 0 && !op1)
{
op1 = GetLastOutPt(horzEdge);
TEdge* eNextHorz = m_SortedEdges;
while (eNextHorz)
{
if (eNextHorz->OutIdx >= 0 &&
HorzSegmentsOverlap(horzEdge->Bot.X,
horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X))
{
OutPt* op2 = GetLastOutPt(eNextHorz);
AddJoin(op2, op1, eNextHorz->Top);
}
eNextHorz = eNextHorz->NextInSEL;
}
AddGhostJoin(op1, horzEdge->Top);
}
if (horzEdge->NextInLML)
{ {
if(horzEdge->OutIdx >= 0) if(horzEdge->OutIdx >= 0)
{ {
OutPt* op1 = AddOutPt( horzEdge, horzEdge->Top); op1 = AddOutPt( horzEdge, horzEdge->Top);
if (isTopOfScanbeam) AddGhostJoin(op1, horzEdge->Bot);
UpdateEdgeIntoAEL(horzEdge); UpdateEdgeIntoAEL(horzEdge);
if (horzEdge->WindDelta == 0) return; if (horzEdge->WindDelta == 0) return;
//nb: HorzEdge is no longer horizontal here //nb: HorzEdge is no longer horizontal here
@ -2906,6 +2980,7 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY)
if(IsMaximaEdge) if(IsMaximaEdge)
{ {
if (m_StrictSimple) m_Maxima.push_back(e->Top.X);
TEdge* ePrev = e->PrevInAEL; TEdge* ePrev = e->PrevInAEL;
DoMaxima(e); DoMaxima(e);
if( !ePrev ) e = m_ActiveEdges; if( !ePrev ) e = m_ActiveEdges;
@ -2927,6 +3002,8 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY)
e->Curr.Y = topY; e->Curr.Y = topY;
} }
//When StrictlySimple and 'e' is being touched by another edge, then
//make sure both edges have a vertex here ...
if (m_StrictSimple) if (m_StrictSimple)
{ {
TEdge* ePrev = e->PrevInAEL; TEdge* ePrev = e->PrevInAEL;
@ -2948,7 +3025,9 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY)
} }
//3. Process horizontals at the Top of the scanbeam ... //3. Process horizontals at the Top of the scanbeam ...
ProcessHorizontals(true); m_Maxima.sort();
ProcessHorizontals();
m_Maxima.clear();
//4. Promote intermediate vertices ... //4. Promote intermediate vertices ...
e = m_ActiveEdges; e = m_ActiveEdges;
@ -2988,44 +3067,71 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY)
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void Clipper::FixupOutPolygon(OutRec &outrec) void Clipper::FixupOutPolyline(OutRec &outrec)
{ {
//FixupOutPolygon() - removes duplicate points and simplifies consecutive
//parallel edges by removing the middle vertex.
OutPt *lastOK = 0;
outrec.BottomPt = 0;
OutPt *pp = outrec.Pts; OutPt *pp = outrec.Pts;
OutPt *lastPP = pp->Prev;
for (;;) while (pp != lastPP)
{ {
if (pp->Prev == pp || pp->Prev == pp->Next ) pp = pp->Next;
if (pp->Pt == pp->Prev->Pt)
{ {
DisposeOutPts(pp); if (pp == lastPP) lastPP = pp->Prev;
outrec.Pts = 0; OutPt *tmpPP = pp->Prev;
return; tmpPP->Next = pp->Next;
} pp->Next->Prev = tmpPP;
delete pp;
//test for duplicate points and collinear edges ... pp = tmpPP;
if ((pp->Pt == pp->Next->Pt) || (pp->Pt == pp->Prev->Pt) ||
(SlopesEqual(pp->Prev->Pt, pp->Pt, pp->Next->Pt, m_UseFullRange) &&
(!m_PreserveCollinear ||
!Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt))))
{
lastOK = 0;
OutPt *tmp = pp;
pp->Prev->Next = pp->Next;
pp->Next->Prev = pp->Prev;
pp = pp->Prev;
delete tmp;
}
else if (pp == lastOK) break;
else
{
if (!lastOK) lastOK = pp;
pp = pp->Next;
} }
} }
outrec.Pts = pp;
if (pp == pp->Prev)
{
DisposeOutPts(pp);
outrec.Pts = 0;
return;
}
}
//------------------------------------------------------------------------------
void Clipper::FixupOutPolygon(OutRec &outrec)
{
//FixupOutPolygon() - removes duplicate points and simplifies consecutive
//parallel edges by removing the middle vertex.
OutPt *lastOK = 0;
outrec.BottomPt = 0;
OutPt *pp = outrec.Pts;
bool preserveCol = m_PreserveCollinear || m_StrictSimple;
for (;;)
{
if (pp->Prev == pp || pp->Prev == pp->Next)
{
DisposeOutPts(pp);
outrec.Pts = 0;
return;
}
//test for duplicate points and collinear edges ...
if ((pp->Pt == pp->Next->Pt) || (pp->Pt == pp->Prev->Pt) ||
(SlopesEqual(pp->Prev->Pt, pp->Pt, pp->Next->Pt, m_UseFullRange) &&
(!preserveCol || !Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt))))
{
lastOK = 0;
OutPt *tmp = pp;
pp->Prev->Next = pp->Next;
pp->Next->Prev = pp->Prev;
pp = pp->Prev;
delete tmp;
}
else if (pp == lastOK) break;
else
{
if (!lastOK) lastOK = pp;
pp = pp->Next;
}
}
outrec.Pts = pp;
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -3309,7 +3415,7 @@ bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2)
OutPt *op2 = j->OutPt2, *op2b; OutPt *op2 = j->OutPt2, *op2b;
//There are 3 kinds of joins for output polygons ... //There are 3 kinds of joins for output polygons ...
//1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are a vertices anywhere //1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are vertices anywhere
//along (horizontal) collinear edges (& Join.OffPt is on the same horizontal). //along (horizontal) collinear edges (& Join.OffPt is on the same horizontal).
//2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same //2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same
//location at the Bottom of the overlapping segment (& Join.OffPt is above). //location at the Bottom of the overlapping segment (& Join.OffPt is above).
@ -3508,6 +3614,7 @@ void Clipper::JoinCommonEdges()
OutRec *outRec2 = GetOutRec(join->OutPt2->Idx); OutRec *outRec2 = GetOutRec(join->OutPt2->Idx);
if (!outRec1->Pts || !outRec2->Pts) continue; if (!outRec1->Pts || !outRec2->Pts) continue;
if (outRec1->IsOpen || outRec2->IsOpen) continue;
//get the polygon fragment with the correct hole state (FirstLeft) //get the polygon fragment with the correct hole state (FirstLeft)
//before calling JoinPoints() ... //before calling JoinPoints() ...
@ -4355,7 +4462,7 @@ void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool p
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void TranslatePath(const Path& input, Path& output, IntPoint delta) void TranslatePath(const Path& input, Path& output, const IntPoint delta)
{ {
//precondition: input != output //precondition: input != output
output.resize(input.size()); output.resize(input.size());

View file

@ -1,10 +1,10 @@
/******************************************************************************* /*******************************************************************************
* * * *
* Author : Angus Johnson * * Author : Angus Johnson *
* Version : 6.2.1 * * Version : 6.2.9 *
* Date : 31 October 2014 * * Date : 16 February 2015 *
* Website : http://www.angusj.com * * Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2014 * * Copyright : Angus Johnson 2010-2015 *
* * * *
* License: * * License: *
* Use, modification & distribution is subject to Boost Software License Ver 1. * * Use, modification & distribution is subject to Boost Software License Ver 1. *
@ -34,7 +34,7 @@
#ifndef clipper_hpp #ifndef clipper_hpp
#define clipper_hpp #define clipper_hpp
#define CLIPPER_VERSION "6.2.0" #define CLIPPER_VERSION "6.2.6"
//use_int32: When enabled 32bit ints are used instead of 64bit ints. This //use_int32: When enabled 32bit ints are used instead of 64bit ints. This
//improve performance but coordinate values are limited to the range +/- 46340 //improve performance but coordinate values are limited to the range +/- 46340
@ -50,6 +50,7 @@
//#define use_deprecated //#define use_deprecated
#include <vector> #include <vector>
#include <list>
#include <set> #include <set>
#include <stdexcept> #include <stdexcept>
#include <cstring> #include <cstring>
@ -200,7 +201,6 @@ enum EdgeSide { esLeft = 1, esRight = 2};
struct TEdge; struct TEdge;
struct IntersectNode; struct IntersectNode;
struct LocalMinimum; struct LocalMinimum;
struct Scanbeam;
struct OutPt; struct OutPt;
struct OutRec; struct OutRec;
struct Join; struct Join;
@ -232,7 +232,6 @@ protected:
void PopLocalMinima(); void PopLocalMinima();
virtual void Reset(); virtual void Reset();
TEdge* ProcessBound(TEdge* E, bool IsClockwise); TEdge* ProcessBound(TEdge* E, bool IsClockwise);
void DoMinimaLML(TEdge* E1, TEdge* E2, bool IsClosed);
TEdge* DescendToMin(TEdge *&E); TEdge* DescendToMin(TEdge *&E);
void AscendToMax(TEdge *&E, bool Appending, bool IsClosed); void AscendToMax(TEdge *&E, bool Appending, bool IsClosed);
@ -253,14 +252,20 @@ public:
Clipper(int initOptions = 0); Clipper(int initOptions = 0);
~Clipper(); ~Clipper();
bool Execute(ClipType clipType, bool Execute(ClipType clipType,
Paths &solution, Paths &solution,
PolyFillType subjFillType = pftEvenOdd, PolyFillType fillType = pftEvenOdd);
PolyFillType clipFillType = pftEvenOdd);
bool Execute(ClipType clipType, bool Execute(ClipType clipType,
PolyTree &polytree, Paths &solution,
PolyFillType subjFillType = pftEvenOdd, PolyFillType subjFillType,
PolyFillType clipFillType = pftEvenOdd); PolyFillType clipFillType);
bool ReverseSolution() {return m_ReverseOutput;}; bool Execute(ClipType clipType,
PolyTree &polytree,
PolyFillType fillType = pftEvenOdd);
bool Execute(ClipType clipType,
PolyTree &polytree,
PolyFillType subjFillType,
PolyFillType clipFillType);
bool ReverseSolution() { return m_ReverseOutput; };
void ReverseSolution(bool value) {m_ReverseOutput = value;}; void ReverseSolution(bool value) {m_ReverseOutput = value;};
bool StrictlySimple() {return m_StrictSimple;}; bool StrictlySimple() {return m_StrictSimple;};
void StrictlySimple(bool value) {m_StrictSimple = value;}; void StrictlySimple(bool value) {m_StrictSimple = value;};
@ -272,13 +277,15 @@ protected:
void Reset(); void Reset();
virtual bool ExecuteInternal(); virtual bool ExecuteInternal();
private: private:
PolyOutList m_PolyOuts; PolyOutList m_PolyOuts;
JoinList m_Joins; JoinList m_Joins;
JoinList m_GhostJoins; JoinList m_GhostJoins;
IntersectList m_IntersectList; IntersectList m_IntersectList;
ClipType m_ClipType; ClipType m_ClipType;
typedef std::priority_queue<cInt> ScanbeamList; typedef std::priority_queue<cInt> ScanbeamList;
ScanbeamList m_Scanbeam; ScanbeamList m_Scanbeam;
typedef std::list<cInt> MaximaList;
MaximaList m_Maxima;
TEdge *m_ActiveEdges; TEdge *m_ActiveEdges;
TEdge *m_SortedEdges; TEdge *m_SortedEdges;
bool m_ExecuteLocked; bool m_ExecuteLocked;
@ -307,8 +314,8 @@ private:
bool IsTopHorz(const cInt XPos); bool IsTopHorz(const cInt XPos);
void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2);
void DoMaxima(TEdge *e); void DoMaxima(TEdge *e);
void ProcessHorizontals(bool IsTopOfScanbeam); void ProcessHorizontals();
void ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam); void ProcessHorizontal(TEdge *horzEdge);
void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
OutRec* GetOutRec(int idx); OutRec* GetOutRec(int idx);
@ -316,6 +323,7 @@ private:
void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt); void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt);
OutRec* CreateOutRec(); OutRec* CreateOutRec();
OutPt* AddOutPt(TEdge *e, const IntPoint &pt); OutPt* AddOutPt(TEdge *e, const IntPoint &pt);
OutPt* GetLastOutPt(TEdge *e);
void DisposeAllOutRecs(); void DisposeAllOutRecs();
void DisposeOutRec(PolyOutList::size_type index); void DisposeOutRec(PolyOutList::size_type index);
bool ProcessIntersections(const cInt topY); bool ProcessIntersections(const cInt topY);
@ -328,6 +336,7 @@ private:
void DisposeIntersectNodes(); void DisposeIntersectNodes();
bool FixupIntersectionOrder(); bool FixupIntersectionOrder();
void FixupOutPolygon(OutRec &outrec); void FixupOutPolygon(OutRec &outrec);
void FixupOutPolyline(OutRec &outrec);
bool IsHole(TEdge *e); bool IsHole(TEdge *e);
bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl); bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl);
void FixHoleLinkage(OutRec &outrec); void FixHoleLinkage(OutRec &outrec);

View file

@ -37,14 +37,10 @@ BridgeDetector::BridgeDetector(const ExPolygon &_expolygon, const ExPolygonColle
Polygons grown; Polygons grown;
offset((Polygons)this->expolygon, &grown, this->extrusion_width); offset((Polygons)this->expolygon, &grown, this->extrusion_width);
// detect what edges lie on lower slices // detect what edges lie on lower slices by turning bridge contour and holes
for (ExPolygons::const_iterator lower = this->lower_slices.expolygons.begin(); // into polylines and then clipping them with each lower slice's contour
lower != this->lower_slices.expolygons.end(); intersection(grown, this->lower_slices.contours(), &this->_edges);
++lower) {
/* turn bridge contour and holes into polylines and then clip them
with each lower slice's contour */
intersection(grown, lower->contour, &this->_edges);
}
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG
printf(" bridge has %zu support(s)\n", this->_edges.size()); printf(" bridge has %zu support(s)\n", this->_edges.size());
#endif #endif

View file

@ -112,6 +112,16 @@ ExPolygonCollection::lines() const
return lines; return lines;
} }
Polygons
ExPolygonCollection::contours() const
{
Polygons contours;
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) {
contours.push_back(it->contour);
}
return contours;
}
#ifdef SLIC3RXS #ifdef SLIC3RXS
REGISTER_CLASS(ExPolygonCollection, "ExPolygon::Collection"); REGISTER_CLASS(ExPolygonCollection, "ExPolygon::Collection");
#endif #endif

View file

@ -30,6 +30,7 @@ class ExPolygonCollection
void simplify(double tolerance); void simplify(double tolerance);
Polygon convex_hull() const; Polygon convex_hull() const;
Lines lines() const; Lines lines() const;
Polygons contours() const;
}; };
} }

View file

@ -66,6 +66,15 @@ GCodeWriter::preamble()
return gcode.str(); return gcode.str();
} }
std::string
GCodeWriter::postamble()
{
std::ostringstream gcode;
if (FLAVOR_IS(gcfMachinekit))
gcode << "M2 ; end of program\n";
return gcode.str();
}
std::string std::string
GCodeWriter::set_temperature(unsigned int temperature, bool wait, int tool) GCodeWriter::set_temperature(unsigned int temperature, bool wait, int tool)
{ {
@ -83,7 +92,7 @@ GCodeWriter::set_temperature(unsigned int temperature, bool wait, int tool)
std::ostringstream gcode; std::ostringstream gcode;
gcode << code << " "; gcode << code << " ";
if (FLAVOR_IS(gcfMach3)) { if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) {
gcode << "P"; gcode << "P";
} else { } else {
gcode << "S"; gcode << "S";
@ -118,7 +127,7 @@ GCodeWriter::set_bed_temperature(unsigned int temperature, bool wait)
std::ostringstream gcode; std::ostringstream gcode;
gcode << code << " "; gcode << code << " ";
if (FLAVOR_IS(gcfMach3)) { if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) {
gcode << "P"; gcode << "P";
} else { } else {
gcode << "S"; gcode << "S";
@ -153,7 +162,7 @@ GCodeWriter::set_fan(unsigned int speed, bool dont_save)
gcode << "M126"; gcode << "M126";
} else { } else {
gcode << "M106 "; gcode << "M106 ";
if (FLAVOR_IS(gcfMach3)) { if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) {
gcode << "P"; gcode << "P";
} else { } else {
gcode << "S"; gcode << "S";
@ -434,7 +443,10 @@ GCodeWriter::_retract(double length, double restart_extra, const std::string &co
double dE = this->_extruder->retract(length, restart_extra); double dE = this->_extruder->retract(length, restart_extra);
if (dE != 0) { if (dE != 0) {
if (this->config.use_firmware_retraction) { if (this->config.use_firmware_retraction) {
gcode << "G10 ; retract\n"; if (FLAVOR_IS(gcfMachinekit))
gcode << "G22 ; retract\n";
else
gcode << "G10 ; retract\n";
} else { } else {
gcode << "G1 " << this->_extrusion_axis << E_NUM(this->_extruder->E) gcode << "G1 " << this->_extrusion_axis << E_NUM(this->_extruder->E)
<< " F" << this->_extruder->retract_speed_mm_min; << " F" << this->_extruder->retract_speed_mm_min;
@ -460,7 +472,10 @@ GCodeWriter::unretract()
double dE = this->_extruder->unretract(); double dE = this->_extruder->unretract();
if (dE != 0) { if (dE != 0) {
if (this->config.use_firmware_retraction) { if (this->config.use_firmware_retraction) {
gcode << "G11 ; unretract\n"; if (FLAVOR_IS(gcfMachinekit))
gcode << "G23 ; unretract\n";
else
gcode << "G11 ; unretract\n";
gcode << this->reset_e(); gcode << this->reset_e();
} else { } else {
// use G1 instead of G0 because G0 will blend the restart with the previous travel move // use G1 instead of G0 because G0 will blend the restart with the previous travel move
@ -509,4 +524,4 @@ GCodeWriter::get_position() const
REGISTER_CLASS(GCodeWriter, "GCode::Writer"); REGISTER_CLASS(GCodeWriter, "GCode::Writer");
#endif #endif
} }

View file

@ -24,6 +24,7 @@ class GCodeWriter {
void apply_print_config(const PrintConfig &print_config); void apply_print_config(const PrintConfig &print_config);
void set_extruders(const std::vector<unsigned int> &extruder_ids); void set_extruders(const std::vector<unsigned int> &extruder_ids);
std::string preamble(); std::string preamble();
std::string postamble();
std::string set_temperature(unsigned int temperature, bool wait = false, int tool = -1); std::string set_temperature(unsigned int temperature, bool wait = false, int tool = -1);
std::string set_bed_temperature(unsigned int temperature, bool wait = false); std::string set_bed_temperature(unsigned int temperature, bool wait = false);
std::string set_fan(unsigned int speed, bool dont_save = false); std::string set_fan(unsigned int speed, bool dont_save = false);

View file

@ -61,13 +61,18 @@ LayerRegion::prepare_fill_surfaces()
the only meaningful information returned by psPerimeters. */ the only meaningful information returned by psPerimeters. */
// if no solid layers are requested, turn top/bottom surfaces to internal // if no solid layers are requested, turn top/bottom surfaces to internal
if (this->_region->config.top_solid_layers == 0) { if (this->region()->config.top_solid_layers == 0) {
for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) { for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) {
if (surface->surface_type == stTop) if (surface->surface_type == stTop) {
surface->surface_type = stInternal; if (this->layer()->object()->config.infill_only_where_needed) {
surface->surface_type = stInternalVoid;
} else {
surface->surface_type = stInternal;
}
}
} }
} }
if (this->_region->config.bottom_solid_layers == 0) { if (this->region()->config.bottom_solid_layers == 0) {
for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) { for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) {
if (surface->surface_type == stBottom || surface->surface_type == stBottomBridge) if (surface->surface_type == stBottom || surface->surface_type == stBottomBridge)
surface->surface_type = stInternal; surface->surface_type = stInternal;
@ -75,9 +80,9 @@ LayerRegion::prepare_fill_surfaces()
} }
// turn too small internal regions into solid regions according to the user setting // turn too small internal regions into solid regions according to the user setting
if (this->_region->config.fill_density.value > 0) { if (this->region()->config.fill_density.value > 0) {
// scaling an area requires two calls! // scaling an area requires two calls!
double min_area = scale_(scale_(this->_region->config.solid_infill_below_area.value)); double min_area = scale_(scale_(this->region()->config.solid_infill_below_area.value));
for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) { for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) {
if (surface->surface_type == stInternal && surface->area() <= min_area) if (surface->surface_type == stInternal && surface->area() <= min_area)
surface->surface_type = stInternalSolid; surface->surface_type = stInternalSolid;

View file

@ -166,13 +166,14 @@ Print::invalidate_state_by_config_options(const std::vector<t_config_option_key>
if (*opt_key == "skirts" if (*opt_key == "skirts"
|| *opt_key == "skirt_height" || *opt_key == "skirt_height"
|| *opt_key == "skirt_distance" || *opt_key == "skirt_distance"
|| *opt_key == "min_skirt_length") { || *opt_key == "min_skirt_length"
|| *opt_key == "ooze_prevention") {
steps.insert(psSkirt); steps.insert(psSkirt);
} else if (*opt_key == "brim_width") { } else if (*opt_key == "brim_width") {
steps.insert(psBrim); steps.insert(psBrim);
steps.insert(psSkirt); steps.insert(psSkirt);
} else if (*opt_key == "nozzle_diameter") { } else if (*opt_key == "nozzle_diameter") {
steps.insert(psInitExtruders); osteps.insert(posSlice);
} else if (*opt_key == "avoid_crossing_perimeters" } else if (*opt_key == "avoid_crossing_perimeters"
|| *opt_key == "bed_shape" || *opt_key == "bed_shape"
|| *opt_key == "bed_temperature" || *opt_key == "bed_temperature"
@ -266,11 +267,6 @@ Print::invalidate_step(PrintStep step)
// propagate to dependent steps // propagate to dependent steps
if (step == psSkirt) { if (step == psSkirt) {
this->invalidate_step(psBrim); this->invalidate_step(psBrim);
} else if (step == psInitExtruders) {
FOREACH_OBJECT(this, object) {
(*object)->invalidate_step(posPerimeters);
(*object)->invalidate_step(posSupportMaterial);
}
} }
return invalidated; return invalidated;
@ -309,13 +305,22 @@ Print::extruders() const
std::set<size_t> extruders; std::set<size_t> extruders;
FOREACH_REGION(this, region) { FOREACH_REGION(this, region) {
extruders.insert((*region)->config.perimeter_extruder - 1); // these checks reflect the same logic used in the GUI for enabling/disabling
extruders.insert((*region)->config.infill_extruder - 1); // extruder selection fields
extruders.insert((*region)->config.solid_infill_extruder - 1); if ((*region)->config.perimeters.value > 0 || this->config.brim_width.value > 0)
extruders.insert((*region)->config.perimeter_extruder - 1);
if ((*region)->config.fill_density.value > 0)
extruders.insert((*region)->config.infill_extruder - 1);
if ((*region)->config.top_solid_layers.value > 0 || (*region)->config.bottom_solid_layers.value > 0)
extruders.insert((*region)->config.solid_infill_extruder - 1);
} }
FOREACH_OBJECT(this, object) { FOREACH_OBJECT(this, object) {
extruders.insert((*object)->config.support_material_extruder - 1); if ((*object)->has_support_material()) {
extruders.insert((*object)->config.support_material_interface_extruder - 1); extruders.insert((*object)->config.support_material_extruder - 1);
extruders.insert((*object)->config.support_material_interface_extruder - 1);
}
} }
return extruders; return extruders;
@ -534,20 +539,16 @@ Print::apply_config(DynamicPrintConfig config)
return invalidated; return invalidated;
} }
void bool Print::has_infinite_skirt() const
Print::init_extruders()
{ {
if (this->state.is_done(psInitExtruders)) return; return (this->config.skirt_height == -1 && this->config.skirts > 0)
this->state.set_done(psInitExtruders); || (this->config.ooze_prevention && this->extruders().size() > 1);
}
// enforce tall skirt if using ooze_prevention
// FIXME: this is not idempotent (i.e. switching ooze_prevention off will not revert skirt settings) bool Print::has_skirt() const
if (this->config.ooze_prevention && this->extruders().size() > 1) { {
this->config.skirt_height.value = -1; return (this->config.skirt_height > 0 && this->config.skirts > 0)
if (this->config.skirts == 0) this->config.skirts.value = 1; || this->has_infinite_skirt();
}
this->state.set_done(psInitExtruders);
} }
void void
@ -625,17 +626,37 @@ Print::validate() const
} }
{ {
std::vector<double> layer_heights; // find the smallest nozzle diameter
std::set<size_t> extruders = this->extruders();
if (extruders.empty())
throw PrintValidationException("The supplied settings will cause an empty print.");
std::set<double> nozzle_diameters;
for (std::set<size_t>::iterator it = extruders.begin(); it != extruders.end(); ++it)
nozzle_diameters.insert(this->config.nozzle_diameter.get_at(*it));
double min_nozzle_diameter = *std::min_element(nozzle_diameters.begin(), nozzle_diameters.end());
FOREACH_OBJECT(this, i_object) { FOREACH_OBJECT(this, i_object) {
PrintObject* object = *i_object; PrintObject* object = *i_object;
layer_heights.push_back(object->config.layer_height);
layer_heights.push_back(object->config.get_abs_value("first_layer_height")); // validate first_layer_height
} double first_layer_height = object->config.get_abs_value("first_layer_height");
double max_layer_height = *std::max_element(layer_heights.begin(), layer_heights.end()); double first_layer_min_nozzle_diameter;
if (object->config.raft_layers > 0) {
std::set<size_t> extruders = this->extruders(); // if we have raft layers, only support material extruder is used on first layer
for (std::set<size_t>::iterator it = extruders.begin(); it != extruders.end(); ++it) { size_t first_layer_extruder = object->config.raft_layers == 1
if (max_layer_height > this->config.nozzle_diameter.get_at(*it)) ? object->config.support_material_interface_extruder-1
: object->config.support_material_extruder-1;
first_layer_min_nozzle_diameter = this->config.nozzle_diameter.get_at(first_layer_extruder);
} else {
// if we don't have raft layers, any nozzle diameter is potentially used in first layer
first_layer_min_nozzle_diameter = min_nozzle_diameter;
}
if (first_layer_height > first_layer_min_nozzle_diameter)
throw PrintValidationException("First layer height can't be greater than nozzle diameter");
// validate layer_height
if (object->config.layer_height.value > min_nozzle_diameter)
throw PrintValidationException("Layer height can't be greater than nozzle diameter"); throw PrintValidationException("Layer height can't be greater than nozzle diameter");
} }
} }
@ -682,13 +703,15 @@ Print::total_bounding_box() const
Flow brim_flow = this->brim_flow(); Flow brim_flow = this->brim_flow();
extra = std::max(extra, this->config.brim_width.value + brim_flow.width/2); extra = std::max(extra, this->config.brim_width.value + brim_flow.width/2);
} }
if (this->config.skirts.value > 0) { if (this->has_skirt()) {
int skirts = this->config.skirts.value;
if (skirts == 0 && this->has_infinite_skirt()) skirts = 1;
Flow skirt_flow = this->skirt_flow(); Flow skirt_flow = this->skirt_flow();
extra = std::max( extra = std::max(
extra, extra,
this->config.brim_width.value this->config.brim_width.value
+ this->config.skirt_distance.value + this->config.skirt_distance.value
+ this->config.skirts.value * skirt_flow.spacing() + skirts * skirt_flow.spacing()
+ skirt_flow.width/2 + skirt_flow.width/2
); );
} }
@ -773,9 +796,7 @@ bool
Print::has_support_material() const Print::has_support_material() const
{ {
FOREACH_OBJECT(this, object) { FOREACH_OBJECT(this, object) {
PrintObjectConfig &config = (*object)->config; if ((*object)->has_support_material()) return true;
if (config.support_material || config.raft_layers > 0 || config.support_material_enforce_layers > 0)
return true;
} }
return false; return false;
} }

View file

@ -22,7 +22,7 @@ class ModelObject;
enum PrintStep { enum PrintStep {
psInitExtruders, psSkirt, psBrim, psSkirt, psBrim,
}; };
enum PrintObjectStep { enum PrintObjectStep {
posSlice, posPerimeters, posPrepareInfill, posSlice, posPerimeters, posPrepareInfill,
@ -134,6 +134,7 @@ class PrintObject
bool invalidate_step(PrintObjectStep step); bool invalidate_step(PrintObjectStep step);
bool invalidate_all_steps(); bool invalidate_all_steps();
bool has_support_material() const;
void bridge_over_infill(); void bridge_over_infill();
private: private:
@ -189,7 +190,8 @@ class Print
void add_model_object(ModelObject* model_object, int idx = -1); void add_model_object(ModelObject* model_object, int idx = -1);
bool apply_config(DynamicPrintConfig config); bool apply_config(DynamicPrintConfig config);
void init_extruders(); bool has_infinite_skirt() const;
bool has_skirt() const;
void validate() const; void validate() const;
BoundingBox bounding_box() const; BoundingBox bounding_box() const;
BoundingBox total_bounding_box() const; BoundingBox total_bounding_box() const;

View file

@ -380,12 +380,14 @@ PrintConfigDef::build_def() {
Options["gcode_flavor"].enum_values.push_back("makerware"); Options["gcode_flavor"].enum_values.push_back("makerware");
Options["gcode_flavor"].enum_values.push_back("sailfish"); Options["gcode_flavor"].enum_values.push_back("sailfish");
Options["gcode_flavor"].enum_values.push_back("mach3"); Options["gcode_flavor"].enum_values.push_back("mach3");
Options["gcode_flavor"].enum_values.push_back("machinekit");
Options["gcode_flavor"].enum_values.push_back("no-extrusion"); Options["gcode_flavor"].enum_values.push_back("no-extrusion");
Options["gcode_flavor"].enum_labels.push_back("RepRap (Marlin/Sprinter/Repetier)"); Options["gcode_flavor"].enum_labels.push_back("RepRap (Marlin/Sprinter/Repetier)");
Options["gcode_flavor"].enum_labels.push_back("Teacup"); Options["gcode_flavor"].enum_labels.push_back("Teacup");
Options["gcode_flavor"].enum_labels.push_back("MakerWare (MakerBot)"); Options["gcode_flavor"].enum_labels.push_back("MakerWare (MakerBot)");
Options["gcode_flavor"].enum_labels.push_back("Sailfish (MakerBot)"); Options["gcode_flavor"].enum_labels.push_back("Sailfish (MakerBot)");
Options["gcode_flavor"].enum_labels.push_back("Mach3/LinuxCNC"); Options["gcode_flavor"].enum_labels.push_back("Mach3/LinuxCNC");
Options["gcode_flavor"].enum_labels.push_back("Machinekit");
Options["gcode_flavor"].enum_labels.push_back("No extrusion"); Options["gcode_flavor"].enum_labels.push_back("No extrusion");
Options["infill_acceleration"].type = coFloat; Options["infill_acceleration"].type = coFloat;
@ -426,7 +428,7 @@ PrintConfigDef::build_def() {
Options["infill_only_where_needed"].type = coBool; Options["infill_only_where_needed"].type = coBool;
Options["infill_only_where_needed"].label = "Only infill where needed"; Options["infill_only_where_needed"].label = "Only infill where needed";
Options["infill_only_where_needed"].category = "Infill"; Options["infill_only_where_needed"].category = "Infill";
Options["infill_only_where_needed"].tooltip = "This option will limit infill to the areas actually needed for supporting ceilings (it will act as internal support material)."; Options["infill_only_where_needed"].tooltip = "This option will limit infill to the areas actually needed for supporting ceilings (it will act as internal support material). If enabled, slows down the G-code generation due to the multiple checks involved.";
Options["infill_only_where_needed"].cli = "infill-only-where-needed!"; Options["infill_only_where_needed"].cli = "infill-only-where-needed!";
Options["infill_overlap"].type = coFloatOrPercent; Options["infill_overlap"].type = coFloatOrPercent;
@ -1028,6 +1030,40 @@ PrintConfigDef::build_def() {
t_optiondef_map PrintConfigDef::def = PrintConfigDef::build_def(); t_optiondef_map PrintConfigDef::def = PrintConfigDef::build_def();
void
DynamicPrintConfig::normalize() {
if (this->has("extruder")) {
int extruder = this->option("extruder")->getInt();
this->erase("extruder");
if (extruder != 0) {
if (!this->has("infill_extruder"))
this->option("infill_extruder", true)->setInt(extruder);
if (!this->has("perimeter_extruder"))
this->option("perimeter_extruder", true)->setInt(extruder);
if (!this->has("support_material_extruder"))
this->option("support_material_extruder", true)->setInt(extruder);
if (!this->has("support_material_interface_extruder"))
this->option("support_material_interface_extruder", true)->setInt(extruder);
}
}
if (!this->has("solid_infill_extruder") && this->has("infill_extruder"))
this->option("solid_infill_extruder", true)->setInt(this->option("infill_extruder")->getInt());
if (this->has("spiral_vase") && this->opt<ConfigOptionBool>("spiral_vase", true)->value) {
{
// this should be actually done only on the spiral layers instead of all
ConfigOptionBools* opt = this->opt<ConfigOptionBools>("retract_layer_change", true);
opt->values.assign(opt->values.size(), false); // set all values to false
}
{
this->opt<ConfigOptionInt>("perimeters", true)->value = 1;
this->opt<ConfigOptionInt>("top_solid_layers", true)->value = 0;
this->opt<ConfigOptionPercent>("fill_density", true)->value = 0;
}
}
}
#ifdef SLIC3RXS #ifdef SLIC3RXS
REGISTER_CLASS(DynamicPrintConfig, "Config"); REGISTER_CLASS(DynamicPrintConfig, "Config");
REGISTER_CLASS(PrintObjectConfig, "Config::PrintObject"); REGISTER_CLASS(PrintObjectConfig, "Config::PrintObject");

View file

@ -6,7 +6,7 @@
namespace Slic3r { namespace Slic3r {
enum GCodeFlavor { enum GCodeFlavor {
gcfRepRap, gcfTeacup, gcfMakerWare, gcfSailfish, gcfMach3, gcfNoExtrusion, gcfRepRap, gcfTeacup, gcfMakerWare, gcfSailfish, gcfMach3, gcfMachinekit, gcfNoExtrusion,
}; };
enum InfillPattern { enum InfillPattern {
@ -29,6 +29,7 @@ template<> inline t_config_enum_values ConfigOptionEnum<GCodeFlavor>::get_enum_v
keys_map["makerware"] = gcfMakerWare; keys_map["makerware"] = gcfMakerWare;
keys_map["sailfish"] = gcfSailfish; keys_map["sailfish"] = gcfSailfish;
keys_map["mach3"] = gcfMach3; keys_map["mach3"] = gcfMach3;
keys_map["machinekit"] = gcfMachinekit;
keys_map["no-extrusion"] = gcfNoExtrusion; keys_map["no-extrusion"] = gcfNoExtrusion;
return keys_map; return keys_map;
} }
@ -78,38 +79,7 @@ class DynamicPrintConfig : public DynamicConfig
this->def = &PrintConfigDef::def; this->def = &PrintConfigDef::def;
}; };
void normalize() { void normalize();
if (this->has("extruder")) {
int extruder = this->option("extruder")->getInt();
this->erase("extruder");
if (extruder != 0) {
if (!this->has("infill_extruder"))
this->option("infill_extruder", true)->setInt(extruder);
if (!this->has("perimeter_extruder"))
this->option("perimeter_extruder", true)->setInt(extruder);
if (!this->has("support_material_extruder"))
this->option("support_material_extruder", true)->setInt(extruder);
if (!this->has("support_material_interface_extruder"))
this->option("support_material_interface_extruder", true)->setInt(extruder);
}
}
if (!this->has("solid_infill_extruder") && this->has("infill_extruder"))
this->option("solid_infill_extruder", true)->setInt(this->option("infill_extruder")->getInt());
if (this->has("spiral_vase") && this->opt<ConfigOptionBool>("spiral_vase", true)->value) {
{
// this should be actually done only on the spiral layers instead of all
ConfigOptionBools* opt = this->opt<ConfigOptionBools>("retract_layer_change", true);
opt->values.assign(opt->values.size(), false); // set all values to false
}
{
this->opt<ConfigOptionInt>("perimeters", true)->value = 1;
this->opt<ConfigOptionInt>("top_solid_layers", true)->value = 0;
this->opt<ConfigOptionPercent>("fill_density", true)->value = 0;
}
}
};
}; };
class StaticPrintConfig : public virtual StaticConfig class StaticPrintConfig : public virtual StaticConfig
@ -410,7 +380,7 @@ class GCodeConfig : public virtual StaticPrintConfig
std::string get_extrusion_axis() const std::string get_extrusion_axis() const
{ {
if (this->gcode_flavor.value == gcfMach3) { if ((this->gcode_flavor.value == gcfMach3) || (this->gcode_flavor.value == gcfMachinekit)) {
return "A"; return "A";
} else if (this->gcode_flavor.value == gcfNoExtrusion) { } else if (this->gcode_flavor.value == gcfNoExtrusion) {
return ""; return "";

View file

@ -333,6 +333,14 @@ PrintObject::invalidate_all_steps()
return invalidated; return invalidated;
} }
bool
PrintObject::has_support_material() const
{
return this->config.support_material
|| this->config.raft_layers > 0
|| this->config.support_material_enforce_layers > 0;
}
void void
PrintObject::bridge_over_infill() PrintObject::bridge_over_infill()
{ {
@ -340,7 +348,7 @@ PrintObject::bridge_over_infill()
size_t region_id = region - this->_print->regions.begin(); size_t region_id = region - this->_print->regions.begin();
double fill_density = (*region)->config.fill_density.value; double fill_density = (*region)->config.fill_density.value;
if (fill_density == 100 || fill_density == 0) continue; if (fill_density == 100) continue;
FOREACH_LAYER(this, layer_it) { FOREACH_LAYER(this, layer_it) {
if (layer_it == this->layers.begin()) continue; if (layer_it == this->layers.begin()) continue;

View file

@ -6,7 +6,7 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#define SLIC3R_VERSION "1.2.6-dev" #define SLIC3R_VERSION "1.2.7-dev"
#define EPSILON 1e-4 #define EPSILON 1e-4
#define SCALING_FACTOR 0.000001 #define SCALING_FACTOR 0.000001

View file

@ -3,8 +3,9 @@
use strict; use strict;
use warnings; use warnings;
use List::Util qw(sum);
use Slic3r::XS; use Slic3r::XS;
use Test::More tests => 19; use Test::More tests => 23;
my $square = Slic3r::Polygon->new( # ccw my $square = Slic3r::Polygon->new( # ccw
[200, 100], [200, 100],
@ -178,8 +179,8 @@ if (0) { # Clipper does not preserve polyline orientation
is scalar(@$result), 1, 'intersection_ppl - result is not empty'; is scalar(@$result), 1, 'intersection_ppl - result is not empty';
} }
if (0) { {
# Disabled until Clipper bug #122 is fixed # Clipper bug #122
my $subject = [ my $subject = [
Slic3r::Polyline->new([1975,1975],[25,1975],[25,25],[1975,25],[1975,1975]), Slic3r::Polyline->new([1975,1975],[25,1975],[25,25],[1975,25],[1975,1975]),
]; ];
@ -188,9 +189,51 @@ if (0) {
Slic3r::Polygon->new([525,525],[525,1475],[1475,1475],[1475,525]), Slic3r::Polygon->new([525,525],[525,1475],[1475,1475],[1475,525]),
]; ];
my $result = Slic3r::Geometry::Clipper::intersection_pl($subject, $clip); my $result = Slic3r::Geometry::Clipper::intersection_pl($subject, $clip);
###use XXX; YYY $subject->[0]->wkt, [map $_->wkt, @$clip], $result->[0]->wkt;
is scalar(@$result), 1, 'intersection_pl - result is not empty'; is scalar(@$result), 1, 'intersection_pl - result is not empty';
is scalar(@{$result->[0]}), 5, 'intersection_pl - result is not empty'; is scalar(@{$result->[0]}), 5, 'intersection_pl - result is not empty';
} }
{
# Clipper bug #126
my $subject = Slic3r::Polyline->new(
[200000,19799999],[200000,200000],[24304692,200000],[15102879,17506106],[13883200,19799999],[200000,19799999],
);
my $clip = [
Slic3r::Polygon->new([15257205,18493894],[14350057,20200000],[-200000,20200000],[-200000,-200000],[25196917,-200000]),
];
my $result = Slic3r::Geometry::Clipper::intersection_pl([$subject], $clip);
is scalar(@$result), 1, 'intersection_pl - result is not empty';
is $result->[0]->length, $subject->length, 'intersection_pl - result has same length as subject polyline';
}
if (0) {
# Disabled until Clipper bug #127 is fixed
my $subject = [
Slic3r::Polyline->new([-90000000,-100000000],[-90000000,100000000]), # vertical
Slic3r::Polyline->new([-100000000,-10000000],[100000000,-10000000]), # horizontal
Slic3r::Polyline->new([-100000000,0],[100000000,0]), # horizontal
Slic3r::Polyline->new([-100000000,10000000],[100000000,10000000]), # horizontal
];
my $clip = Slic3r::Polygon->new( # a circular, convex, polygon
[99452190,10452846],[97814760,20791169],[95105652,30901699],[91354546,40673664],[86602540,50000000],
[80901699,58778525],[74314483,66913061],[66913061,74314483],[58778525,80901699],[50000000,86602540],
[40673664,91354546],[30901699,95105652],[20791169,97814760],[10452846,99452190],[0,100000000],
[-10452846,99452190],[-20791169,97814760],[-30901699,95105652],[-40673664,91354546],
[-50000000,86602540],[-58778525,80901699],[-66913061,74314483],[-74314483,66913061],
[-80901699,58778525],[-86602540,50000000],[-91354546,40673664],[-95105652,30901699],
[-97814760,20791169],[-99452190,10452846],[-100000000,0],[-99452190,-10452846],
[-97814760,-20791169],[-95105652,-30901699],[-91354546,-40673664],[-86602540,-50000000],
[-80901699,-58778525],[-74314483,-66913061],[-66913061,-74314483],[-58778525,-80901699],
[-50000000,-86602540],[-40673664,-91354546],[-30901699,-95105652],[-20791169,-97814760],
[-10452846,-99452190],[0,-100000000],[10452846,-99452190],[20791169,-97814760],
[30901699,-95105652],[40673664,-91354546],[50000000,-86602540],[58778525,-80901699],
[66913061,-74314483],[74314483,-66913061],[80901699,-58778525],[86602540,-50000000],
[91354546,-40673664],[95105652,-30901699],[97814760,-20791169],[99452190,-10452846],[100000000,0]
);
my $result = Slic3r::Geometry::Clipper::intersection_pl($subject, [$clip]);
is scalar(@$result), scalar(@$subject), 'intersection_pl - expected number of polylines';
is sum(map scalar(@$_), @$result), scalar(@$subject)*2,
'intersection_pl - expected number of points in polylines';
}
__END__ __END__

View file

@ -4,7 +4,7 @@ use strict;
use warnings; use warnings;
use Slic3r::XS; use Slic3r::XS;
use Test::More tests => 108; use Test::More tests => 110;
foreach my $config (Slic3r::Config->new, Slic3r::Config::Full->new) { foreach my $config (Slic3r::Config->new, Slic3r::Config::Full->new) {
$config->set('layer_height', 0.3); $config->set('layer_height', 0.3);
@ -51,6 +51,8 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Full->new) {
is $config->serialize('gcode_flavor'), 'teacup', 'serialize enum'; is $config->serialize('gcode_flavor'), 'teacup', 'serialize enum';
$config->set_deserialize('gcode_flavor', 'mach3'); $config->set_deserialize('gcode_flavor', 'mach3');
is $config->get('gcode_flavor'), 'mach3', 'deserialize enum (gcode_flavor)'; is $config->get('gcode_flavor'), 'mach3', 'deserialize enum (gcode_flavor)';
$config->set_deserialize('gcode_flavor', 'machinekit');
is $config->get('gcode_flavor'), 'machinekit', 'deserialize enum (gcode_flavor)';
$config->set_deserialize('fill_pattern', 'line'); $config->set_deserialize('fill_pattern', 'line');
is $config->get('fill_pattern'), 'line', 'deserialize enum (fill_pattern)'; is $config->get('fill_pattern'), 'line', 'deserialize enum (fill_pattern)';

View file

@ -19,6 +19,7 @@
%code{% THIS->apply_print_config(*print_config); %}; %code{% THIS->apply_print_config(*print_config); %};
void set_extruders(std::vector<unsigned int> extruder_ids); void set_extruders(std::vector<unsigned int> extruder_ids);
std::string preamble(); std::string preamble();
std::string postamble();
std::string set_temperature(unsigned int temperature, bool wait = false, int tool = -1); std::string set_temperature(unsigned int temperature, bool wait = false, int tool = -1);
std::string set_bed_temperature(unsigned int temperature, bool wait = false); std::string set_bed_temperature(unsigned int temperature, bool wait = false);
std::string set_fan(unsigned int speed, bool dont_save = false); std::string set_fan(unsigned int speed, bool dont_save = false);

View file

@ -12,7 +12,6 @@
IV IV
_constant() _constant()
ALIAS: ALIAS:
STEP_INIT_EXTRUDERS = psInitExtruders
STEP_SLICE = posSlice STEP_SLICE = posSlice
STEP_PERIMETERS = posPerimeters STEP_PERIMETERS = posPerimeters
STEP_PREPARE_INFILL = posPrepareInfill STEP_PREPARE_INFILL = posPrepareInfill
@ -199,7 +198,8 @@ _constant()
void add_model_object(ModelObject* model_object, int idx = -1); void add_model_object(ModelObject* model_object, int idx = -1);
bool apply_config(DynamicPrintConfig* config) bool apply_config(DynamicPrintConfig* config)
%code%{ RETVAL = THIS->apply_config(*config); %}; %code%{ RETVAL = THIS->apply_config(*config); %};
void init_extruders(); bool has_infinite_skirt();
bool has_skirt();
void validate() void validate()
%code%{ %code%{
try { try {