diff --git a/.travis.yml b/.travis.yml index 6b5086838..4cbb89cac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,9 @@ language: perl install: true script: perl ./Build.PL perl: - - "5.12" - "5.14" - "5.18" + - "5.20" branches: only: - master diff --git a/README.md b/README.md index ee97e5b65..984931402 100644 --- a/README.md +++ b/README.md @@ -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 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 -with Mach3 and LinuxCNC controllers. +with Mach3, LinuxCNC and Machinekit controllers. See the [project homepage](http://slic3r.org/) at slic3r.org and the [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 * complete **command-line interface** to use it with no GUI * 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** * **multithread** processing * **STL auto-repair** (tolerance for broken models) @@ -109,6 +109,8 @@ The author of the Silk icon set is Mark James. -j, --threads Number of threads to use (1+, default: 2) 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 --gui-mode Overrides the configured mode (simple/expert) --autosave 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) --z-offset Additional height in mm to add to vertical coordinates (+/-, 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) --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) diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index e2739c421..bc0455889 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -243,7 +243,7 @@ sub validate { if !first { $_ eq $self->gcode_flavor } @{$Options->{gcode_flavor}{values}}; 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" if $self->use_firmware_retraction && first {$_} @{$self->wipe}; diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index e3acbe551..09fba7a29 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -49,9 +49,10 @@ sub make_fill { Slic3r::debugf "Filling layer %d:\n", $layerm->id; - my $fill_density = $layerm->config->fill_density; - my $infill_flow = $layerm->flow(FLOW_ROLE_INFILL); - my $solid_infill_flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL); + my $fill_density = $layerm->config->fill_density; + my $infill_flow = $layerm->flow(FLOW_ROLE_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 = (); @@ -75,7 +76,7 @@ sub make_fill { if ($groups[$i][0]->is_solid && (!$groups[$i][0]->is_bridge || $layerm->id == 0)) { $is_solid[$i] = 1; $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; $pattern[$i] = $groups[$i][0]->is_external ? $layerm->config->external_fill_pattern diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 62a8f2ab5..27f5dbe53 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -584,11 +584,14 @@ sub wipe { $gcode .= $gcodegen->writer->extrude_to_xy( $gcodegen->point_to_gcode($line->b), -$dE, - 'retract' . ($gcodegen->enable_cooling_markers ? ';_WIPE' : ''), + 'wipe and retract' . ($gcodegen->enable_cooling_markers ? ';_WIPE' : ''), ); $retracted += $dE; } $gcodegen->writer->extruder->set_retracted($gcodegen->writer->extruder->retracted + $retracted); + + # prevent wiping again on same path + $self->path(undef); } return $gcode; diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index af4646745..88dafd467 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -34,7 +34,7 @@ our $have_LWP = eval "use LWP::UserAgent; 1"; use Wx 0.9901 qw(:bitmap :dialog :icon :id :misc :systemsettings :toplevelwindow :filedialog); -use Wx::Event qw(EVT_IDLE); +use Wx::Event qw(EVT_IDLE EVT_COMMAND); use base 'Wx::App'; use constant FILE_WILDCARDS => { @@ -70,6 +70,8 @@ our $medium_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); $medium_font->SetPointSize(12); our $grey = Wx::Colour->new(200,200,200); +our $VERSION_CHECK_EVENT : shared = Wx::NewEventType; + sub OnInit { 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; } @@ -239,7 +260,7 @@ sub have_version_check { } sub check_version { - my ($self, %p) = @_; + my ($self, $manual_check) = @_; Slic3r::debugf "Checking for updates...\n"; @@ -248,19 +269,9 @@ sub check_version { my $ua = LWP::UserAgent->new; $ua->timeout(10); my $response = $ua->get('http://slic3r.org/updatecheck'); - if ($response->is_success) { - if ($response->decoded_content =~ /^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 $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}; - } + Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $VERSION_CHECK_EVENT, + threads::shared::shared_clone([ $response->is_success, $response->decoded_content, $manual_check ]))); + Slic3r::thread_cleanup(); })->detach; } diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index 2974179ca..794de1fc1 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -16,6 +16,7 @@ __PACKAGE__->mk_accessors( qw(_quat _dirty init enable_cutting enable_picking enable_moving + on_viewport_changed on_hover on_select on_double_click @@ -108,6 +109,7 @@ sub new { -($pos->y - $size->y/2) * ($zoom) / $self->_zoom, 0, ) if 0; + $self->on_viewport_changed->() if $self->on_viewport_changed; $self->_dirty(1); $self->Refresh; }); @@ -207,6 +209,7 @@ sub mouse_event { ); $self->_quat(mulquats($self->_quat, \@quat)); } + $self->on_viewport_changed->() if $self->on_viewport_changed; $self->Refresh; } $self->_drag_start_pos($pos); @@ -220,6 +223,7 @@ sub mouse_event { $self->_camera_target->translate( @{$orig->vector_to($cur_pos)->negative}, ); + $self->on_viewport_changed->() if $self->on_viewport_changed; $self->Refresh; } $self->_drag_start_xy($pos); @@ -256,6 +260,17 @@ sub reset_objects { $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 { my ($self, $bb) = @_; @@ -267,6 +282,8 @@ sub zoom_to_bounding_box { # center view around bounding box center $self->_camera_target($bb->center); + + $self->on_viewport_changed->() if $self->on_viewport_changed; } 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) { - 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) { - 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 # 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 - foreach my $line (map @{$_->lines}, @$expolygon) { - push @lines, $line->as_polyline; - } + push @lines, map @{$_->lines}, @$expolygon; my @points = (); - foreach my $polyline (@lines) { - push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$polyline; #)) + foreach my $line (@lines) { + push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$line; #)) } $self->bed_grid_lines(OpenGL::Array->new_list(GL_FLOAT, @points)); } @@ -572,9 +588,10 @@ sub Resize { glMatrixMode(GL_PROJECTION); glLoadIdentity(); + my $depth = 10 * max(@{ $self->max_bounding_box->size }); glOrtho( -$x/2, $x/2, -$y/2, $y/2, - -200, 10 * max(@{ $self->max_bounding_box->size }), + -$depth, 2*$depth, ); glMatrixMode(GL_MODELVIEW); diff --git a/lib/Slic3r/GUI/BedShapeDialog.pm b/lib/Slic3r/GUI/BedShapeDialog.pm index a055dad24..7b5c3ca92 100644 --- a/lib/Slic3r/GUI/BedShapeDialog.pm +++ b/lib/Slic3r/GUI/BedShapeDialog.pm @@ -307,7 +307,7 @@ sub _repaint_canvas { @polylines = @{intersection_pl(\@polylines, [$bed_polygon])}; $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 diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index fa4419d0f..9882888c0 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -255,7 +255,7 @@ sub _init_menubar { Wx::LaunchDefaultBrowser('http://slic3r.org/'); }); 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); $self->_append_menu_item($helpMenu, "Slic3r &Manual", 'Open the Slic3r manual in your browser', sub { @@ -678,7 +678,7 @@ sub config { } else { my $extruders_count = $self->{options_tabs}{printer}{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; diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 1511010c5..bb344e50e 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -96,6 +96,9 @@ sub new { $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_instances_moved($on_instances_moved); + $self->{canvas3D}->on_viewport_changed(sub { + $self->{preview3D}->canvas->set_viewport_from_scene($self->{canvas3D}); + }); } # Initialize 2D preview canvas @@ -109,6 +112,9 @@ sub new { # Initialize 3D toolpaths preview if ($Slic3r::GUI::have_OpenGL) { $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->{preview3D_page_idx} = $self->{preview_notebook}->GetPageCount-1; } @@ -988,9 +994,13 @@ sub pause_background_process { if ($self->{process_thread} || $self->{export_thread}) { Slic3r::pause_all_threads(); + return 1; } elsif (defined $self->{apply_config_timer} && $self->{apply_config_timer}->IsRunning) { $self->{apply_config_timer}->Stop; + return 1; } + + return 0; } sub resume_background_process { @@ -1312,9 +1322,14 @@ sub update { $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(); - 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; } else { $self->resume_background_process; @@ -1323,12 +1338,6 @@ sub update { $self->refresh_canvases; } -sub on_model_instances_changed { - my ($self) = @_; - - -} - sub on_extruders_change { my ($self, $num_extruders) = @_; diff --git a/lib/Slic3r/GUI/Plater/2D.pm b/lib/Slic3r/GUI/Plater/2D.pm index bf0dcbfba..788892050 100644 --- a/lib/Slic3r/GUI/Plater/2D.pm +++ b/lib/Slic3r/GUI/Plater/2D.pm @@ -276,7 +276,7 @@ sub update_bed_size { push @polylines, Slic3r::Polyline->new([$bb->x_min, $y], [$bb->x_max, $y]); } @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 ]; } } diff --git a/lib/Slic3r/GUI/Plater/2DToolpaths.pm b/lib/Slic3r/GUI/Plater/2DToolpaths.pm index 3e6f09b27..8ab289855 100644 --- a/lib/Slic3r/GUI/Plater/2DToolpaths.pm +++ b/lib/Slic3r/GUI/Plater/2DToolpaths.pm @@ -291,7 +291,7 @@ sub Render { $brim_drawn = 1; } 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) { $self->color([0, 0, 0]); $self->_draw(undef, $print_z, $_) for @{$self->print->skirt}; diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 67c5f57e1..7026ea9fb 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -37,9 +37,9 @@ sub new { # buttons $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), - wxDefaultPosition, [16,16], wxBORDER_NONE); + wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); $self->{btn_save_preset}->SetToolTipString("Save current " . lc($self->title)); $self->{btn_delete_preset}->SetToolTipString("Delete this preset"); $self->{btn_delete_preset}->Disable; @@ -745,11 +745,13 @@ sub _update { perimeter_speed small_perimeter_speed external_perimeter_speed); my $have_infill = $config->fill_density > 0; + # infill_extruder uses the same logic as in Print::extruders() $self->get_field($_)->toggle($have_infill) for qw(fill_pattern infill_every_layers infill_only_where_needed solid_infill_every_layers solid_infill_below_area infill_extruder); 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) for qw(external_fill_pattern infill_first solid_infill_extruder solid_infill_extrusion_width solid_infill_speed); @@ -772,6 +774,7 @@ sub _update { for qw(skirt_distance skirt_height); 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); my $have_support_material = $config->support_material || $config->raft_layers > 0; diff --git a/lib/Slic3r/Layer/PerimeterGenerator.pm b/lib/Slic3r/Layer/PerimeterGenerator.pm index 6dcbe5733..a5b737466 100644 --- a/lib/Slic3r/Layer/PerimeterGenerator.pm +++ b/lib/Slic3r/Layer/PerimeterGenerator.pm @@ -286,7 +286,7 @@ sub process { # where 0.5*$pwidth < thickness < $pwidth, infill with width = 0.5*$pwidth my @gap_sizes = ( [ $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) { 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) # and then we offset back and forth by half the infill spacing to only consider the # 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); $self->fill_surfaces->append($_) for map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL), # use a bogus surface type @{offset2_ex( [ 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, )}; } @@ -384,8 +394,8 @@ sub _traverse_loops { push @paths, Slic3r::ExtrusionPath->new( polyline => $loop->polygon->split_at_first_point, role => $role, - mm3_per_mm => $self->_mm3_per_mm, - width => $self->perimeter_flow->width, + mm3_per_mm => ($is_external ? $self->_ext_mm3_per_mm : $self->_mm3_per_mm), + width => ($is_external ? $self->ext_perimeter_flow->width : $self->perimeter_flow->width), height => $self->layer_height, ); } @@ -441,11 +451,14 @@ sub _traverse_loops { sub _fill_gaps { my ($self, $min, $max, $w, $gaps) = @_; + $min *= (1 - &Slic3r::INSET_OVERLAP_TOLERANCE); + my $this = diff_ex( offset2([ map @$_, @$gaps ], -$min/2, +$min/2), offset2([ map @$_, @$gaps ], -$max/2, +$max/2), 1, ); + my @polylines = map @{$_->medial_axis($max, $min/2)}, @$this; return if !@polylines; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index dd96b0e15..2837e0db7 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -89,7 +89,8 @@ sub export_gcode { $self->config->setenv; for my $script (@{$self->config->post_process}) { 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"; } system($script, $output_file); @@ -101,9 +102,6 @@ sub export_svg { my $self = shift; my %params = @_; - # is this needed? - $self->init_extruders; - $_->slice for @{$self->objects}; my $fh = $params{output_fh}; @@ -208,8 +206,7 @@ sub make_skirt { # checking whether we need to generate them $self->skirt->clear; - if (($self->config->skirts == 0 || $self->config->skirt_height == 0) - && (!$self->config->ooze_prevention || @{$self->extruders} == 1)) { + if (!$self->has_skirt) { $self->set_step_done(STEP_SKIRT); return; } @@ -219,7 +216,7 @@ sub make_skirt { # 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 # 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 # 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 @@ -227,10 +224,9 @@ sub make_skirt { # $skirt_height_z in this case is the highest possible skirt height for safety. my $skirt_height_z = -1; 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}) : min($self->config->skirt_height, scalar(@{$object->layers})); - my $highest_layer = $object->get_layer($skirt_height - 1); $skirt_height_z = max($skirt_height_z, $highest_layer->print_z); } @@ -278,10 +274,13 @@ sub make_skirt { my @extruders_e_per_mm = (); my $extruder_idx = 0; + my $skirts = $self->config->skirts; + $skirts ||= 1 if $self->has_infinite_skirt; + # 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 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; my $loop = offset([$convex_hull], $distance, 1, JT_ROUND, scale(0.1))->[0]; $self->skirt->append(Slic3r::ExtrusionLoop->new_from_paths( @@ -428,10 +427,10 @@ sub expanded_output_filepath { my $filename = my $filename_base = basename($input_file); $filename_base =~ s/\.[^.]+$//; # without suffix - my $extra = { - input_filename => $filename, - input_filename_base => $filename_base, - }; + + # set filename in placeholder parser so that it's available also in custom G-code + $self->placeholder_parser->set(input_filename => $filename); + $self->placeholder_parser->set(input_filename_base => $filename_base); if ($path && -d $path) { # 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 $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 diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index 6b7957a10..d9246ebb4 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -195,12 +195,14 @@ sub export { # no collision happens hopefully. if ($finished_objects > 0) { $gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y)); + $gcodegen->enable_cooling_markers(0); # we're not filtering these moves through CoolingBuffer print $fh $gcodegen->retract; print $fh $gcodegen->travel_to( Slic3r::Point->new(0,0), undef, '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}; @@ -217,6 +219,7 @@ sub export { } $self->flush_filters; $finished_objects++; + $self->_second_layer_things_done(0); } } } else { @@ -249,6 +252,7 @@ sub export { print $fh $gcodegen->writer->set_fan(0); 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->postamble; # get filament stats $self->print->clear_filament_stats; @@ -299,7 +303,7 @@ sub process_layer { if (defined $self->_spiral_vase) { $self->_spiral_vase->enable( ($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 { @{$_->perimeters} > 1 } @{$layer->regions}) && !defined(first { @{$_->fills} > 0 } @{$layer->regions}) @@ -332,14 +336,15 @@ sub process_layer { }) . "\n" if $self->print->config->layer_gcode; # extrude skirt - if (((values %{$self->_skirt_done}) < $self->print->config->skirt_height || $self->print->config->skirt_height == -1) - && !$self->_skirt_done->{$layer->print_z}) { + if (((values %{$self->_skirt_done}) < $self->print->config->skirt_height || $self->print->has_infinite_skirt) + && !$self->_skirt_done->{$layer->print_z} + && !$layer->isa('Slic3r::Layer::Support')) { $self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0)); $self->_gcodegen->avoid_crossing_perimeters->use_external_mp(1); my @extruder_ids = map { $_->id } @{$self->_gcodegen->writer->extruders}; $gcode .= $self->_gcodegen->set_extruder($extruder_ids[0]); # 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; # distribute skirt loops across all extruders diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index dd60a9ec6..aa24eedf3 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -6,7 +6,7 @@ use List::Util qw(min max sum first); use Slic3r::Flow ':roles'; 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 - 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::Surface ':types'; @@ -344,7 +344,6 @@ sub make_perimeters { my $self = shift; # prerequisites - $self->print->init_extruders; $self->slice; return if $self->step_done(STEP_PERIMETERS); @@ -369,51 +368,59 @@ sub make_perimeters { my $region = $self->print->regions->[$region_id]; my $region_perimeters = $region->config->perimeters; - if ($region->config->extra_perimeters && $region_perimeters > 0 && $region->config->fill_density > 0) { - for my $i (0 .. ($self->layer_count - 2)) { - my $layerm = $self->get_layer($i)->regions->[$region_id]; - my $upper_layerm = $self->get_layer($i+1)->regions->[$region_id]; - my $perimeter_spacing = $layerm->flow(FLOW_ROLE_PERIMETER)->scaled_spacing; - my $ext_perimeter_spacing = $layerm->flow(FLOW_ROLE_EXTERNAL_PERIMETER)->scaled_spacing; - - my $overlap = $perimeter_spacing; # one perimeter - - my $diff = diff( - offset([ map @{$_->expolygon}, @{$layerm->slices} ], -($ext_perimeter_spacing + ($region_perimeters-1) * $perimeter_spacing)), - offset([ map @{$_->expolygon}, @{$upper_layerm->slices} ], -$overlap), - ); - next if !@$diff; - # if we need more perimeters, $diff should contain a narrow region that we can collapse - - # we use a higher miterLimit here to handle areas with acute angles - # in those cases, the default miterLimit would cut the corner and we'd - # get a triangle that would trigger a non-needed extra perimeter - $diff = diff( - $diff, - offset2($diff, -$perimeter_spacing, +$perimeter_spacing, CLIPPER_OFFSET_SCALE, JT_MITER, 5), - 1, - ); - next if !@$diff; - # diff contains the collapsed area - - foreach my $slice (@{$layerm->slices}) { - my $extra_perimeters = 0; - CYCLE: while (1) { - # compute polygons representing the thickness of the hypotetical new internal perimeter - # of our slice - $extra_perimeters++; - my $hypothetical_perimeter = diff( - offset($slice->expolygon->arrayref, -($perimeter_spacing * ($region_perimeters + $extra_perimeters-1))), - offset($slice->expolygon->arrayref, -($perimeter_spacing * ($region_perimeters + $extra_perimeters))), + next if !$region->config->extra_perimeters; + next if $region_perimeters == 0; + next if $region->config->fill_density == 0; + + for my $i (0 .. ($self->layer_count - 2)) { + my $layerm = $self->get_layer($i)->get_region($region_id); + my $upper_layerm = $self->get_layer($i+1)->get_region($region_id); + + my $perimeter_spacing = $layerm->flow(FLOW_ROLE_PERIMETER)->scaled_spacing; + my $ext_perimeter_flow = $layerm->flow(FLOW_ROLE_EXTERNAL_PERIMETER); + my $ext_perimeter_width = $ext_perimeter_flow->scaled_width; + my $ext_perimeter_spacing = $ext_perimeter_flow->scaled_spacing; + + foreach my $slice (@{$layerm->slices}) { + while (1) { + # compute the total thickness of perimeters + my $perimeters_thickness = $ext_perimeter_width/2 + $ext_perimeter_spacing/2 + + ($region_perimeters-1 + $slice->extra_perimeters) * $perimeter_spacing; + + # define a critical area where we don't want the upper slice to fall into + # (it should either lay over our perimeters or outside this area) + my $critical_area_depth = $perimeter_spacing*1.5; + my $critical_area = diff( + offset($slice->expolygon->arrayref, -$perimeters_thickness), + offset($slice->expolygon->arrayref, -($perimeters_thickness + $critical_area_depth)), + ); + + # check whether a portion of the upper slices falls inside the critical area + my $intersection = intersection_ppl( + [ map $_->p, @{$upper_layerm->slices} ], + $critical_area, + ); + + # only add an additional loop if at least 30% of the slice loop would benefit from it + my $total_loop_length = sum(map $_->length, map $_->p, @{$upper_layerm->slices}) // 0; + 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; # prerequisites - $self->print->init_extruders; $self->slice; return if $self->step_done(STEP_SUPPORTMATERIAL); @@ -547,7 +553,7 @@ sub _support_material { my ($self) = @_; 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, nozzle_diameter => $self->print->config->nozzle_diameter->[ $self->config->support_material_extruder-1 ] // $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 # 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; } else { $_->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 # internal support material. - my $additional_margin = scale 3*0; - - my $overhangs = []; # arrayref of polygons - for my $layer_id (reverse 0..($self->layer_count - 1)) { - my $layer = $self->get_layer($layer_id); - my @layer_internal = (); # arrayref of Surface objects - my @new_internal = (); # arrayref of Surface objects + # proceed top-down skipping bottom layer + my $upper_internal = []; + for my $layer_id (reverse 1..($self->layer_count - 1)) { + my $layer = $self->get_layer($layer_id); + my $lower_layer = $self->get_layer($layer_id-1); - # clip this layer's internal surfaces to @overhangs - foreach my $layerm (@{$layer->regions}) { + # detect things that we need to support + 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) = (); 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; } else { push @other, $surface; } } - # keep all the original internal surfaces to detect overhangs in this layer - push @layer_internal, @internal; - - push @new_internal, my @new = map Slic3r::Surface->new( + my @new = map Slic3r::Surface->new( expolygon => $_, surface_type => S_TYPE_INTERNAL, ), @{intersection_ex( [ map $_->p, @internal ], - $overhangs, + $new_internal, + 1, )}; - push @new, map Slic3r::Surface->new( + push @other, map Slic3r::Surface->new( expolygon => $_, surface_type => S_TYPE_INTERNALVOID, ), @{diff_ex( [ 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->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 - } } } diff --git a/lib/Slic3r/Print/State.pm b/lib/Slic3r/Print/State.pm index 437f66a70..7220aa818 100644 --- a/lib/Slic3r/Print/State.pm +++ b/lib/Slic3r/Print/State.pm @@ -4,7 +4,7 @@ use warnings; require 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); our %EXPORT_TAGS = (steps => \@EXPORT_OK); diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm index 4d3e05a9d..6bd9fdb76 100644 --- a/lib/Slic3r/Print/SupportMaterial.pm +++ b/lib/Slic3r/Print/SupportMaterial.pm @@ -723,6 +723,11 @@ sub generate_toolpaths { if (@$base) { my $filler = $fillers{support}; $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 $base_flow = $_flow; @@ -737,6 +742,10 @@ sub generate_toolpaths { $filler->angle($self->object_config->support_material_angle + 90); $density = 0.5; $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 { # draw a perimeter all around support infill # TODO: use brim ordering algorithm @@ -753,10 +762,6 @@ sub generate_toolpaths { $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; foreach my $expolygon (@$to_infill) { my @p = $filler->fill_surface( diff --git a/slic3r.pl b/slic3r.pl index 84590b197..ede37dbc0 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -90,7 +90,7 @@ $config->apply($cli_config); # launch 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'; $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'); $gui->{mainframe}->load_config_file($_) for @{$opt{load}}; $gui->{mainframe}->load_config($cli_config); + foreach my $input_file (@ARGV) { + $gui->{mainframe}{plater}->load_file($input_file) unless $opt{no_plater}; + } $gui->MainLoop; exit; } @@ -261,6 +264,8 @@ Usage: slic3r.pl [ OPTIONS ] [ file.stl ] [ file2.stl ] ... $j 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 --gui-mode Overrides the configured mode (simple/expert) --autosave Automatically export current configuration to the specified file @@ -282,7 +287,7 @@ $j (default: 100,100) --z-offset Additional height in mm to add to vertical coordinates (+/-, 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}) --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) diff --git a/t/combineinfill.t b/t/combineinfill.t index 24cd2bb71..6661cdd6c 100644 --- a/t/combineinfill.t +++ b/t/combineinfill.t @@ -124,7 +124,6 @@ if (0) { # copy of Print::export_gcode() up to the point # after fill surfaces are combined - $self->init_extruders; $_->slice for @{$self->objects}; $_->make_perimeters for @{$self->objects}; $_->detect_surfaces_type for @{$self->objects}; diff --git a/t/fill.t b/t/fill.t index 2a7a4521f..6fb05196e 100644 --- a/t/fill.t +++ b/t/fill.t @@ -12,7 +12,7 @@ BEGIN { use List::Util qw(first sum); use Slic3r; 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::Test; @@ -20,7 +20,6 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } { my $print = Slic3r::Print->new; - $print->init_extruders; my $filler = Slic3r::Fill::Rectilinear->new( print => $print, 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; $config->set('skirts', 0); - $config->set('perimeters', 0); + $config->set('perimeters', 1); $config->set('fill_density', 0); $config->set('top_solid_layers', 0); $config->set('bottom_solid_layers', 0); $config->set('solid_infill_below_area', 20000000); $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 %layers_with_extrusion = (); Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { 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, @@ -276,6 +284,7 @@ for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { $config->set('first_layer_height', 0.2); $config->set('nozzle_diameter', [0.35]); $config->set('infill_extruder', 2); + $config->set('solid_infill_extruder', 2); $config->set('infill_extrusion_width', 0.52); $config->set('solid_infill_extrusion_width', 0.52); $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 $layer0_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.2} } ]); 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'; } diff --git a/t/gcode.t b/t/gcode.t index aa15098a0..79ab1a7de 100644 --- a/t/gcode.t +++ b/t/gcode.t @@ -1,4 +1,4 @@ -use Test::More tests => 20; +use Test::More tests => 22; use strict; use warnings; @@ -86,6 +86,7 @@ use Slic3r::Test; # - no hard-coded "E" are generated # - Z moves are correctly generated for both objects # - no travel moves go outside skirt + # - temperatures are set correctly my $config = Slic3r::Config->new_from_defaults; $config->set('gcode_comments', 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('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); ok my $gcode = Slic3r::Test::gcode($print), "complete_objects"; my @z_moves = (); my @travel_moves = (); # array of scaled points my @extrusions = (); # array of scaled points + my @temps = (); Slic3r::GCode::Reader->new->parse($gcode, sub { my ($self, $cmd, $args, $info) = @_; 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}) 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 @@ -120,6 +126,8 @@ use Slic3r::Test; my $convex_hull = convex_hull(\@extrusions); 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__ diff --git a/t/perimeters.t b/t/perimeters.t index e2a5bc344..ed6f6b430 100644 --- a/t/perimeters.t +++ b/t/perimeters.t @@ -172,7 +172,6 @@ use Slic3r::Test; # we just need a pre-filled Print object my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - $print->print->init_extruders; # override a layer's slices my $expolygon = Slic3r::ExPolygon->new([[-71974463,-139999376],[-71731792,-139987456],[-71706544,-139985616],[-71682119,-139982639],[-71441248,-139946912],[-71417487,-139942895],[-71379384,-139933984],[-71141800,-139874480],[-71105247,-139862895],[-70873544,-139779984],[-70838592,-139765856],[-70614943,-139660064],[-70581783,-139643567],[-70368368,-139515680],[-70323751,-139487872],[-70122160,-139338352],[-70082399,-139306639],[-69894800,-139136624],[-69878679,-139121327],[-69707992,-138933008],[-69668575,-138887343],[-69518775,-138685359],[-69484336,-138631632],[-69356423,-138418207],[-69250040,-138193296],[-69220920,-138128976],[-69137992,-137897168],[-69126095,-137860255],[-69066568,-137622608],[-69057104,-137582511],[-69053079,-137558751],[-69017352,-137317872],[-69014392,-137293456],[-69012543,-137268207],[-68999369,-137000000],[-63999999,-137000000],[-63705947,-136985551],[-63654984,-136977984],[-63414731,-136942351],[-63364756,-136929840],[-63129151,-136870815],[-62851950,-136771631],[-62585807,-136645743],[-62377483,-136520895],[-62333291,-136494415],[-62291908,-136463728],[-62096819,-136319023],[-62058644,-136284432],[-61878676,-136121328],[-61680968,-135903184],[-61650275,-135861807],[-61505591,-135666719],[-61354239,-135414191],[-61332211,-135367615],[-61228359,-135148063],[-61129179,-134870847],[-61057639,-134585262],[-61014451,-134294047],[-61000000,-134000000],[-61000000,-107999999],[-61014451,-107705944],[-61057639,-107414736],[-61129179,-107129152],[-61228359,-106851953],[-61354239,-106585808],[-61505591,-106333288],[-61680967,-106096816],[-61878675,-105878680],[-62096820,-105680967],[-62138204,-105650279],[-62333292,-105505591],[-62585808,-105354239],[-62632384,-105332207],[-62851951,-105228360],[-62900463,-105211008],[-63129152,-105129183],[-63414731,-105057640],[-63705947,-105014448],[-63999999,-105000000],[-68999369,-105000000],[-69012543,-104731792],[-69014392,-104706544],[-69017352,-104682119],[-69053079,-104441248],[-69057104,-104417487],[-69066008,-104379383],[-69125528,-104141799],[-69137111,-104105248],[-69220007,-103873544],[-69234136,-103838591],[-69339920,-103614943],[-69356415,-103581784],[-69484328,-103368367],[-69512143,-103323752],[-69661647,-103122160],[-69693352,-103082399],[-69863383,-102894800],[-69878680,-102878679],[-70066999,-102707992],[-70112656,-102668576],[-70314648,-102518775],[-70368367,-102484336],[-70581783,-102356424],[-70806711,-102250040],[-70871040,-102220919],[-71102823,-102137992],[-71139752,-102126095],[-71377383,-102066568],[-71417487,-102057104],[-71441248,-102053079],[-71682119,-102017352],[-71706535,-102014392],[-71731784,-102012543],[-71974456,-102000624],[-71999999,-102000000],[-104000000,-102000000],[-104025536,-102000624],[-104268207,-102012543],[-104293455,-102014392],[-104317880,-102017352],[-104558751,-102053079],[-104582512,-102057104],[-104620616,-102066008],[-104858200,-102125528],[-104894751,-102137111],[-105126455,-102220007],[-105161408,-102234136],[-105385056,-102339920],[-105418215,-102356415],[-105631632,-102484328],[-105676247,-102512143],[-105877839,-102661647],[-105917600,-102693352],[-106105199,-102863383],[-106121320,-102878680],[-106292007,-103066999],[-106331424,-103112656],[-106481224,-103314648],[-106515663,-103368367],[-106643575,-103581783],[-106749959,-103806711],[-106779080,-103871040],[-106862007,-104102823],[-106873904,-104139752],[-106933431,-104377383],[-106942896,-104417487],[-106946920,-104441248],[-106982648,-104682119],[-106985607,-104706535],[-106987456,-104731784],[-107000630,-105000000],[-112000000,-105000000],[-112294056,-105014448],[-112585264,-105057640],[-112870848,-105129184],[-112919359,-105146535],[-113148048,-105228360],[-113194624,-105250392],[-113414191,-105354239],[-113666711,-105505591],[-113708095,-105536279],[-113903183,-105680967],[-114121320,-105878679],[-114319032,-106096816],[-114349720,-106138200],[-114494408,-106333288],[-114645760,-106585808],[-114667792,-106632384],[-114771640,-106851952],[-114788991,-106900463],[-114870815,-107129151],[-114942359,-107414735],[-114985551,-107705943],[-115000000,-107999999],[-115000000,-134000000],[-114985551,-134294048],[-114942359,-134585263],[-114870816,-134870847],[-114853464,-134919359],[-114771639,-135148064],[-114645759,-135414192],[-114494407,-135666720],[-114319031,-135903184],[-114121320,-136121327],[-114083144,-136155919],[-113903184,-136319023],[-113861799,-136349712],[-113666711,-136494416],[-113458383,-136619264],[-113414192,-136645743],[-113148049,-136771631],[-112870848,-136870815],[-112820872,-136883327],[-112585264,-136942351],[-112534303,-136949920],[-112294056,-136985551],[-112000000,-137000000],[-107000630,-137000000],[-106987456,-137268207],[-106985608,-137293440],[-106982647,-137317872],[-106946920,-137558751],[-106942896,-137582511],[-106933991,-137620624],[-106874471,-137858208],[-106862888,-137894751],[-106779992,-138126463],[-106765863,-138161424],[-106660080,-138385055],[-106643584,-138418223],[-106515671,-138631648],[-106487855,-138676256],[-106338352,-138877839],[-106306647,-138917600],[-106136616,-139105199],[-106121320,-139121328],[-105933000,-139291999],[-105887344,-139331407],[-105685351,-139481232],[-105631632,-139515663],[-105418216,-139643567],[-105193288,-139749951],[-105128959,-139779072],[-104897175,-139862016],[-104860247,-139873904],[-104622616,-139933423],[-104582511,-139942896],[-104558751,-139946912],[-104317880,-139982656],[-104293463,-139985616],[-104268216,-139987456],[-104025544,-139999376],[-104000000,-140000000],[-71999999,-140000000]],[[-105000000,-138000000],[-105000000,-104000000],[-71000000,-104000000],[-71000000,-138000000]],[[-69000000,-132000000],[-69000000,-110000000],[-64991180,-110000000],[-64991180,-132000000]],[[-111008824,-132000000],[-111008824,-110000000],[-107000000,-110000000],[-107000000,-132000000]]); @@ -230,19 +229,19 @@ use Slic3r::Test; my $test = sub { my ($print) = @_; - my $has_bridges = 0; + my %z_with_bridges = (); # z => 1 Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{extruding} && $info->{dist_XY} > 0) { - $has_bridges++ if ($args->{F} // $self->F) == $config->bridge_speed*60; + $z_with_bridges{$self->Z} = 1 if ($args->{F} // $self->F) == $config->bridge_speed*60; } }); - return $has_bridges; + return scalar keys %z_with_bridges; }; - ok !$test->(Slic3r::Test::init_print('V', config => $config)), - 'no overhangs printed with bridge speed'; - ok $test->(Slic3r::Test::init_print('V', config => $config, scale_xyz => [3,1,1])), + ok $test->(Slic3r::Test::init_print('V', config => $config)) == 1, + 'no overhangs printed with bridge speed'; # except for the first internal solid layers above void + ok $test->(Slic3r::Test::init_print('V', config => $config, scale_xyz => [3,1,1])) > 1, 'overhangs printed with bridge speed'; } diff --git a/t/shells.t b/t/shells.t index a12858ae0..f3fd62d14 100644 --- a/t/shells.t +++ b/t/shells.t @@ -72,6 +72,9 @@ use Slic3r::Test; $config->set('bottom_solid_layers', 0); 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); ok $test->(), "proper number of shells is applied even when fill density is none"; } diff --git a/t/support.t b/t/support.t index 54d5ef700..2091b4432 100644 --- a/t/support.t +++ b/t/support.t @@ -1,4 +1,4 @@ -use Test::More tests => 20; +use Test::More tests => 25; use strict; use warnings; @@ -20,7 +20,6 @@ use Slic3r::Test; my $test = sub { my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - $print->print->init_extruders; my $flow = $print->print->objects->[0]->support_material_flow; my $support = Slic3r::Print::SupportMaterial->new( object_config => $print->print->objects->[0]->config, @@ -180,4 +179,54 @@ use Slic3r::Test; $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__ diff --git a/utils/zsh/functions/_slic3r b/utils/zsh/functions/_slic3r index 41a36963f..df1cecb03 100644 --- a/utils/zsh/functions/_slic3r +++ b/utils/zsh/functions/_slic3r @@ -22,7 +22,7 @@ _arguments -S \ '*--nozzle-diameter[specify nozzle diameter]:nozzle diameter in mm' \ '--print-center[specify print center coordinates]:print center coordinates in mm,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]' \ '--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]' \ diff --git a/xs/Build.PL b/xs/Build.PL index f90f9b3c8..5dd226f30 100644 --- a/xs/Build.PL +++ b/xs/Build.PL @@ -63,7 +63,7 @@ my $build = Module::Build::WithXSpp->new( dist_abstract => 'XS code for Slic3r', build_requires => {qw( ExtUtils::ParseXS 3.18 - ExtUtils::Typemap 1.00 + ExtUtils::Typemaps 1.00 ExtUtils::Typemaps::Default 1.05 ExtUtils::XSpp 0.17 Module::Build 0.3601 diff --git a/xs/src/admesh/stlinit.c b/xs/src/admesh/stlinit.c index a7ab2b6d2..efc4e11ba 100644 --- a/xs/src/admesh/stlinit.c +++ b/xs/src/admesh/stlinit.c @@ -135,7 +135,21 @@ stl_count_facets(stl_file *stl, char *file) { /* Otherwise, if the .STL file is ASCII, then do the following */ else { /* 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 */ j = 0; diff --git a/xs/src/clipper.cpp b/xs/src/clipper.cpp index f44201a89..264896e0b 100644 --- a/xs/src/clipper.cpp +++ b/xs/src/clipper.cpp @@ -1,10 +1,10 @@ /******************************************************************************* * * * Author : Angus Johnson * -* Version : 6.2.1 * -* Date : 31 October 2014 * +* Version : 6.2.9 * +* Date : 16 February 2015 * * Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2014 * +* Copyright : Angus Johnson 2010-2015 * * * * License: * * Use, modification & distribution is subject to Boost Software License Ver 1. * @@ -381,13 +381,6 @@ Int128 Int128Mul (long64 lhs, long64 rhs) // Miscellaneous global functions //------------------------------------------------------------------------------ -void Swap(cInt& val1, cInt& val2) -{ - cInt tmp = val1; - val1 = val2; - val2 = tmp; -} -//------------------------------------------------------------------------------ bool Orientation(const Path &poly) { 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 - //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; size_t cnt = path.size(); 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 //progression of the bounds - ie so their xbots will align with the //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 - Swap(e.Top.Z, e.Bot.Z); + std::swap(e.Top.Z, e.Bot.Z); #endif } //------------------------------------------------------------------------------ @@ -866,8 +859,8 @@ bool Pt2IsBetweenPt1AndPt3(const IntPoint pt1, bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b) { - if (seg1a > seg1b) Swap(seg1a, seg1b); - if (seg2a > seg2b) Swap(seg2a, seg2b); + if (seg1a > seg1b) std::swap(seg1a, seg1b); + if (seg2a > seg2b) std::swap(seg2a, seg2b); return (seg1a < seg2b) && (seg2a < seg1b); } @@ -976,16 +969,13 @@ TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward) EStart = E->Prev; else 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) ReverseHorizontal(*E); } else if (EStart->Bot.X != E->Bot.X) ReverseHorizontal(*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 Horz = Result; while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev; - if (Horz->Prev->Top.X == Result->Next->Top.X) - { - if (!NextIsForward) Result = Horz->Prev; - } - else if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev; + if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev; } while (E != Result) { @@ -1024,11 +1010,8 @@ TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward) { Horz = Result; while (IsHorizontal(*Horz->Next)) Horz = Horz->Next; - if (Horz->Next->Top.X == Result->Prev->Top.X) - { - if (!NextIsForward) Result = Horz->Next; - } - else if (Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next; + if (Horz->Next->Top.X == Result->Prev->Top.X || + Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next; } while (E != Result) @@ -1155,17 +1138,17 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) return false; } E->Prev->OutIdx = Skip; - if (E->Prev->Bot.X < E->Prev->Top.X) ReverseHorizontal(*E->Prev); MinimaList::value_type locMin; locMin.Y = E->Bot.Y; locMin.LeftBound = 0; locMin.RightBound = E; locMin.RightBound->Side = esRight; 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->Next->OutIdx == Skip) break; + E->NextInLML = E->Next; E = E->Next; } m_MinimaList.push_back(locMin); @@ -1371,6 +1354,7 @@ void Clipper::Reset() { ClipperBase::Reset(); m_Scanbeam = ScanbeamList(); + m_Maxima = MaximaList(); m_ActiveEdges = 0; m_SortedEdges = 0; 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, PolyFillType subjFillType, PolyFillType clipFillType) { if( m_ExecuteLocked ) return false; 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; solution.resize(0); m_SubjFillType = subjFillType; @@ -1439,9 +1435,9 @@ bool Clipper::ExecuteInternal() cInt botY = PopScanbeam(); do { InsertLocalMinimaIntoAEL(botY); - ClearGhostJoins(); - ProcessHorizontals(false); - if (m_Scanbeam.empty()) break; + ProcessHorizontals(); + ClearGhostJoins(); + if (m_Scanbeam.empty()) break; cInt topY = PopScanbeam(); succeeded = ProcessIntersections(topY); if (!succeeded) break; @@ -1471,7 +1467,10 @@ bool Clipper::ExecuteInternal() for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) { OutRec *outRec = m_PolyOuts[i]; - if (outRec->Pts && !outRec->IsOpen) + if (!outRec->Pts) continue; + if (outRec->IsOpen) + FixupOutPolyline(*outRec); + else FixupOutPolygon(*outRec); } @@ -1486,17 +1485,16 @@ bool Clipper::ExecuteInternal() 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() { - const cInt Y = m_Scanbeam.top(); - m_Scanbeam.pop(); - while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) { m_Scanbeam.pop(); } // Pop duplicates. - return Y; + const cInt Y = m_Scanbeam.top(); + m_Scanbeam.pop(); + while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) { m_Scanbeam.pop(); } // Pop duplicates. + return Y; } //------------------------------------------------------------------------------ @@ -2356,7 +2354,6 @@ OutRec* Clipper::CreateOutRec() OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt) { - bool ToFront = (e->Side == esLeft); if( e->OutIdx < 0 ) { 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' 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; 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; while(horzEdge) { DeleteFromSEL(horzEdge); - ProcessHorizontal(horzEdge, IsTopOfScanbeam); + ProcessHorizontal(horzEdge); 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. * *******************************************************************************/ -void Clipper::ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam) +void Clipper::ProcessHorizontal(TEdge *horzEdge) { Direction dir; cInt horzLeft, horzRight; + bool IsOpen = (horzEdge->OutIdx >= 0 && m_PolyOuts[horzEdge->OutIdx]->IsOpen); GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); @@ -2577,50 +2586,100 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam) if (!eLastHorz->NextInLML) 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); TEdge* e = GetNextInAEL(horzEdge, dir); 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) || - (dir == dRightToLeft && e->Curr.X >= horzLeft)) - { - //so far we're still in range of the horizontal Edge but make sure + if ((dir == dLeftToRight && e->Curr.X > horzRight) || + (dir == dRightToLeft && e->Curr.X < horzLeft)) break; + + //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 if(e == eMaxPair && IsLastHorz) { - 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); - } DeleteFromAEL(horzEdge); DeleteFromAEL(eMaxPair); return; } - else if(dir == dLeftToRight) + + if(dir == dLeftToRight) { IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); IntersectEdges(horzEdge, e, Pt); @@ -2630,28 +2689,43 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam) IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); IntersectEdges( e, horzEdge, Pt); } + TEdge* eNext = GetNextInAEL(e, dir); SwapPositionsInAEL( horzEdge, e ); - } - else if( (dir == dLeftToRight && e->Curr.X >= horzRight) || - (dir == dRightToLeft && e->Curr.X <= horzLeft) ) break; - e = eNext; - } //end while + e = eNext; + } //end while(e) + + //Break out of loop if HorzEdge.NextInLML is not also horizontal ... + 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 (;;) - 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) { - OutPt* op1 = AddOutPt( horzEdge, horzEdge->Top); - if (isTopOfScanbeam) AddGhostJoin(op1, horzEdge->Bot); + op1 = AddOutPt( horzEdge, horzEdge->Top); UpdateEdgeIntoAEL(horzEdge); if (horzEdge->WindDelta == 0) return; //nb: HorzEdge is no longer horizontal here @@ -2906,6 +2980,7 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) if(IsMaximaEdge) { + if (m_StrictSimple) m_Maxima.push_back(e->Top.X); TEdge* ePrev = e->PrevInAEL; DoMaxima(e); if( !ePrev ) e = m_ActiveEdges; @@ -2927,6 +3002,8 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt 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) { TEdge* ePrev = e->PrevInAEL; @@ -2948,7 +3025,9 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) } //3. Process horizontals at the Top of the scanbeam ... - ProcessHorizontals(true); + m_Maxima.sort(); + ProcessHorizontals(); + m_Maxima.clear(); //4. Promote intermediate vertices ... 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; - - for (;;) + OutPt *lastPP = pp->Prev; + while (pp != lastPP) { - if (pp->Prev == pp || pp->Prev == pp->Next ) + pp = pp->Next; + if (pp->Pt == pp->Prev->Pt) { - 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) && - (!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; + if (pp == lastPP) lastPP = pp->Prev; + OutPt *tmpPP = pp->Prev; + tmpPP->Next = pp->Next; + pp->Next->Prev = tmpPP; + delete pp; + pp = tmpPP; } } - 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; //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). //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). @@ -3508,6 +3614,7 @@ void Clipper::JoinCommonEdges() OutRec *outRec2 = GetOutRec(join->OutPt2->Idx); if (!outRec1->Pts || !outRec2->Pts) continue; + if (outRec1->IsOpen || outRec2->IsOpen) continue; //get the polygon fragment with the correct hole state (FirstLeft) //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 output.resize(input.size()); diff --git a/xs/src/clipper.hpp b/xs/src/clipper.hpp index 6d841611b..c3151b51b 100644 --- a/xs/src/clipper.hpp +++ b/xs/src/clipper.hpp @@ -1,10 +1,10 @@ /******************************************************************************* * * * Author : Angus Johnson * -* Version : 6.2.1 * -* Date : 31 October 2014 * +* Version : 6.2.9 * +* Date : 16 February 2015 * * Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2014 * +* Copyright : Angus Johnson 2010-2015 * * * * License: * * Use, modification & distribution is subject to Boost Software License Ver 1. * @@ -34,7 +34,7 @@ #ifndef 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 //improve performance but coordinate values are limited to the range +/- 46340 @@ -50,6 +50,7 @@ //#define use_deprecated #include +#include #include #include #include @@ -200,7 +201,6 @@ enum EdgeSide { esLeft = 1, esRight = 2}; struct TEdge; struct IntersectNode; struct LocalMinimum; -struct Scanbeam; struct OutPt; struct OutRec; struct Join; @@ -232,7 +232,6 @@ protected: void PopLocalMinima(); virtual void Reset(); TEdge* ProcessBound(TEdge* E, bool IsClockwise); - void DoMinimaLML(TEdge* E1, TEdge* E2, bool IsClosed); TEdge* DescendToMin(TEdge *&E); void AscendToMax(TEdge *&E, bool Appending, bool IsClosed); @@ -253,14 +252,20 @@ public: Clipper(int initOptions = 0); ~Clipper(); bool Execute(ClipType clipType, - Paths &solution, - PolyFillType subjFillType = pftEvenOdd, - PolyFillType clipFillType = pftEvenOdd); + Paths &solution, + PolyFillType fillType = pftEvenOdd); bool Execute(ClipType clipType, - PolyTree &polytree, - PolyFillType subjFillType = pftEvenOdd, - PolyFillType clipFillType = pftEvenOdd); - bool ReverseSolution() {return m_ReverseOutput;}; + Paths &solution, + PolyFillType subjFillType, + PolyFillType clipFillType); + 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;}; bool StrictlySimple() {return m_StrictSimple;}; void StrictlySimple(bool value) {m_StrictSimple = value;}; @@ -272,13 +277,15 @@ protected: void Reset(); virtual bool ExecuteInternal(); private: - PolyOutList m_PolyOuts; - JoinList m_Joins; - JoinList m_GhostJoins; - IntersectList m_IntersectList; - ClipType m_ClipType; + PolyOutList m_PolyOuts; + JoinList m_Joins; + JoinList m_GhostJoins; + IntersectList m_IntersectList; + ClipType m_ClipType; typedef std::priority_queue ScanbeamList; - ScanbeamList m_Scanbeam; + ScanbeamList m_Scanbeam; + typedef std::list MaximaList; + MaximaList m_Maxima; TEdge *m_ActiveEdges; TEdge *m_SortedEdges; bool m_ExecuteLocked; @@ -307,8 +314,8 @@ private: bool IsTopHorz(const cInt XPos); void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); void DoMaxima(TEdge *e); - void ProcessHorizontals(bool IsTopOfScanbeam); - void ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam); + void ProcessHorizontals(); + void ProcessHorizontal(TEdge *horzEdge); void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); OutRec* GetOutRec(int idx); @@ -316,6 +323,7 @@ private: void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt); OutRec* CreateOutRec(); OutPt* AddOutPt(TEdge *e, const IntPoint &pt); + OutPt* GetLastOutPt(TEdge *e); void DisposeAllOutRecs(); void DisposeOutRec(PolyOutList::size_type index); bool ProcessIntersections(const cInt topY); @@ -328,6 +336,7 @@ private: void DisposeIntersectNodes(); bool FixupIntersectionOrder(); void FixupOutPolygon(OutRec &outrec); + void FixupOutPolyline(OutRec &outrec); bool IsHole(TEdge *e); bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl); void FixHoleLinkage(OutRec &outrec); diff --git a/xs/src/libslic3r/BridgeDetector.cpp b/xs/src/libslic3r/BridgeDetector.cpp index dfc145422..da59a04d8 100644 --- a/xs/src/libslic3r/BridgeDetector.cpp +++ b/xs/src/libslic3r/BridgeDetector.cpp @@ -37,14 +37,10 @@ BridgeDetector::BridgeDetector(const ExPolygon &_expolygon, const ExPolygonColle Polygons grown; offset((Polygons)this->expolygon, &grown, this->extrusion_width); - // detect what edges lie on lower slices - for (ExPolygons::const_iterator lower = this->lower_slices.expolygons.begin(); - lower != this->lower_slices.expolygons.end(); - ++lower) { - /* turn bridge contour and holes into polylines and then clip them - with each lower slice's contour */ - intersection(grown, lower->contour, &this->_edges); - } + // detect what edges lie on lower slices by turning bridge contour and holes + // into polylines and then clipping them with each lower slice's contour + intersection(grown, this->lower_slices.contours(), &this->_edges); + #ifdef SLIC3R_DEBUG printf(" bridge has %zu support(s)\n", this->_edges.size()); #endif diff --git a/xs/src/libslic3r/ExPolygonCollection.cpp b/xs/src/libslic3r/ExPolygonCollection.cpp index 3b47c29b3..9ae7e8907 100644 --- a/xs/src/libslic3r/ExPolygonCollection.cpp +++ b/xs/src/libslic3r/ExPolygonCollection.cpp @@ -112,6 +112,16 @@ ExPolygonCollection::lines() const 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 REGISTER_CLASS(ExPolygonCollection, "ExPolygon::Collection"); #endif diff --git a/xs/src/libslic3r/ExPolygonCollection.hpp b/xs/src/libslic3r/ExPolygonCollection.hpp index 86f77cbcb..4e6c366e2 100644 --- a/xs/src/libslic3r/ExPolygonCollection.hpp +++ b/xs/src/libslic3r/ExPolygonCollection.hpp @@ -30,6 +30,7 @@ class ExPolygonCollection void simplify(double tolerance); Polygon convex_hull() const; Lines lines() const; + Polygons contours() const; }; } diff --git a/xs/src/libslic3r/GCodeWriter.cpp b/xs/src/libslic3r/GCodeWriter.cpp index 9aa7616a6..801e0ebd4 100644 --- a/xs/src/libslic3r/GCodeWriter.cpp +++ b/xs/src/libslic3r/GCodeWriter.cpp @@ -66,6 +66,15 @@ GCodeWriter::preamble() 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 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; gcode << code << " "; - if (FLAVOR_IS(gcfMach3)) { + if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) { gcode << "P"; } else { gcode << "S"; @@ -118,7 +127,7 @@ GCodeWriter::set_bed_temperature(unsigned int temperature, bool wait) std::ostringstream gcode; gcode << code << " "; - if (FLAVOR_IS(gcfMach3)) { + if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) { gcode << "P"; } else { gcode << "S"; @@ -153,7 +162,7 @@ GCodeWriter::set_fan(unsigned int speed, bool dont_save) gcode << "M126"; } else { gcode << "M106 "; - if (FLAVOR_IS(gcfMach3)) { + if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) { gcode << "P"; } else { 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); if (dE != 0) { if (this->config.use_firmware_retraction) { - gcode << "G10 ; retract\n"; + if (FLAVOR_IS(gcfMachinekit)) + gcode << "G22 ; retract\n"; + else + gcode << "G10 ; retract\n"; } else { gcode << "G1 " << this->_extrusion_axis << E_NUM(this->_extruder->E) << " F" << this->_extruder->retract_speed_mm_min; @@ -460,7 +472,10 @@ GCodeWriter::unretract() double dE = this->_extruder->unretract(); if (dE != 0) { 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(); } else { // 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"); #endif -} \ No newline at end of file +} diff --git a/xs/src/libslic3r/GCodeWriter.hpp b/xs/src/libslic3r/GCodeWriter.hpp index 03de197d1..dbedc1766 100644 --- a/xs/src/libslic3r/GCodeWriter.hpp +++ b/xs/src/libslic3r/GCodeWriter.hpp @@ -24,6 +24,7 @@ class GCodeWriter { void apply_print_config(const PrintConfig &print_config); void set_extruders(const std::vector &extruder_ids); std::string preamble(); + std::string postamble(); 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_fan(unsigned int speed, bool dont_save = false); diff --git a/xs/src/libslic3r/LayerRegion.cpp b/xs/src/libslic3r/LayerRegion.cpp index e988566a8..d09ea19c7 100644 --- a/xs/src/libslic3r/LayerRegion.cpp +++ b/xs/src/libslic3r/LayerRegion.cpp @@ -61,13 +61,18 @@ LayerRegion::prepare_fill_surfaces() the only meaningful information returned by psPerimeters. */ // 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) { - if (surface->surface_type == stTop) - surface->surface_type = stInternal; + if (surface->surface_type == stTop) { + 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) { if (surface->surface_type == stBottom || surface->surface_type == stBottomBridge) 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 - if (this->_region->config.fill_density.value > 0) { + if (this->region()->config.fill_density.value > 0) { // 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) { if (surface->surface_type == stInternal && surface->area() <= min_area) surface->surface_type = stInternalSolid; diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index 1944f22d3..d3bb43b3c 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -166,13 +166,14 @@ Print::invalidate_state_by_config_options(const std::vector if (*opt_key == "skirts" || *opt_key == "skirt_height" || *opt_key == "skirt_distance" - || *opt_key == "min_skirt_length") { + || *opt_key == "min_skirt_length" + || *opt_key == "ooze_prevention") { steps.insert(psSkirt); } else if (*opt_key == "brim_width") { steps.insert(psBrim); steps.insert(psSkirt); } else if (*opt_key == "nozzle_diameter") { - steps.insert(psInitExtruders); + osteps.insert(posSlice); } else if (*opt_key == "avoid_crossing_perimeters" || *opt_key == "bed_shape" || *opt_key == "bed_temperature" @@ -266,11 +267,6 @@ Print::invalidate_step(PrintStep step) // propagate to dependent steps if (step == psSkirt) { this->invalidate_step(psBrim); - } else if (step == psInitExtruders) { - FOREACH_OBJECT(this, object) { - (*object)->invalidate_step(posPerimeters); - (*object)->invalidate_step(posSupportMaterial); - } } return invalidated; @@ -309,13 +305,22 @@ Print::extruders() const std::set extruders; FOREACH_REGION(this, region) { - extruders.insert((*region)->config.perimeter_extruder - 1); - extruders.insert((*region)->config.infill_extruder - 1); - extruders.insert((*region)->config.solid_infill_extruder - 1); + // these checks reflect the same logic used in the GUI for enabling/disabling + // extruder selection fields + 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) { - extruders.insert((*object)->config.support_material_extruder - 1); - extruders.insert((*object)->config.support_material_interface_extruder - 1); + if ((*object)->has_support_material()) { + extruders.insert((*object)->config.support_material_extruder - 1); + extruders.insert((*object)->config.support_material_interface_extruder - 1); + } } return extruders; @@ -534,20 +539,16 @@ Print::apply_config(DynamicPrintConfig config) return invalidated; } -void -Print::init_extruders() +bool Print::has_infinite_skirt() const { - if (this->state.is_done(psInitExtruders)) return; - this->state.set_done(psInitExtruders); - - // enforce tall skirt if using ooze_prevention - // FIXME: this is not idempotent (i.e. switching ooze_prevention off will not revert skirt settings) - if (this->config.ooze_prevention && this->extruders().size() > 1) { - this->config.skirt_height.value = -1; - if (this->config.skirts == 0) this->config.skirts.value = 1; - } - - this->state.set_done(psInitExtruders); + return (this->config.skirt_height == -1 && this->config.skirts > 0) + || (this->config.ooze_prevention && this->extruders().size() > 1); +} + +bool Print::has_skirt() const +{ + return (this->config.skirt_height > 0 && this->config.skirts > 0) + || this->has_infinite_skirt(); } void @@ -625,17 +626,37 @@ Print::validate() const } { - std::vector layer_heights; + // find the smallest nozzle diameter + std::set extruders = this->extruders(); + if (extruders.empty()) + throw PrintValidationException("The supplied settings will cause an empty print."); + + std::set nozzle_diameters; + for (std::set::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) { PrintObject* object = *i_object; - layer_heights.push_back(object->config.layer_height); - layer_heights.push_back(object->config.get_abs_value("first_layer_height")); - } - double max_layer_height = *std::max_element(layer_heights.begin(), layer_heights.end()); - - std::set extruders = this->extruders(); - for (std::set::iterator it = extruders.begin(); it != extruders.end(); ++it) { - if (max_layer_height > this->config.nozzle_diameter.get_at(*it)) + + // validate first_layer_height + double first_layer_height = object->config.get_abs_value("first_layer_height"); + double first_layer_min_nozzle_diameter; + if (object->config.raft_layers > 0) { + // if we have raft layers, only support material extruder is used on first layer + size_t first_layer_extruder = object->config.raft_layers == 1 + ? 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"); } } @@ -682,13 +703,15 @@ Print::total_bounding_box() const Flow brim_flow = this->brim_flow(); 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(); extra = std::max( extra, this->config.brim_width.value + this->config.skirt_distance.value - + this->config.skirts.value * skirt_flow.spacing() + + skirts * skirt_flow.spacing() + skirt_flow.width/2 ); } @@ -773,9 +796,7 @@ bool Print::has_support_material() const { FOREACH_OBJECT(this, object) { - PrintObjectConfig &config = (*object)->config; - if (config.support_material || config.raft_layers > 0 || config.support_material_enforce_layers > 0) - return true; + if ((*object)->has_support_material()) return true; } return false; } diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index dbc4ad1b8..47defe770 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -22,7 +22,7 @@ class ModelObject; enum PrintStep { - psInitExtruders, psSkirt, psBrim, + psSkirt, psBrim, }; enum PrintObjectStep { posSlice, posPerimeters, posPrepareInfill, @@ -134,6 +134,7 @@ class PrintObject bool invalidate_step(PrintObjectStep step); bool invalidate_all_steps(); + bool has_support_material() const; void bridge_over_infill(); private: @@ -189,7 +190,8 @@ class Print void add_model_object(ModelObject* model_object, int idx = -1); bool apply_config(DynamicPrintConfig config); - void init_extruders(); + bool has_infinite_skirt() const; + bool has_skirt() const; void validate() const; BoundingBox bounding_box() const; BoundingBox total_bounding_box() const; diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index a6e8b5fd5..4bf981473 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -380,12 +380,14 @@ PrintConfigDef::build_def() { Options["gcode_flavor"].enum_values.push_back("makerware"); Options["gcode_flavor"].enum_values.push_back("sailfish"); 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_labels.push_back("RepRap (Marlin/Sprinter/Repetier)"); Options["gcode_flavor"].enum_labels.push_back("Teacup"); 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("Mach3/LinuxCNC"); + Options["gcode_flavor"].enum_labels.push_back("Machinekit"); Options["gcode_flavor"].enum_labels.push_back("No extrusion"); Options["infill_acceleration"].type = coFloat; @@ -426,7 +428,7 @@ PrintConfigDef::build_def() { Options["infill_only_where_needed"].type = coBool; Options["infill_only_where_needed"].label = "Only infill where needed"; 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_overlap"].type = coFloatOrPercent; @@ -1028,6 +1030,40 @@ 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("spiral_vase", true)->value) { + { + // this should be actually done only on the spiral layers instead of all + ConfigOptionBools* opt = this->opt("retract_layer_change", true); + opt->values.assign(opt->values.size(), false); // set all values to false + } + { + this->opt("perimeters", true)->value = 1; + this->opt("top_solid_layers", true)->value = 0; + this->opt("fill_density", true)->value = 0; + } + } +} + #ifdef SLIC3RXS REGISTER_CLASS(DynamicPrintConfig, "Config"); REGISTER_CLASS(PrintObjectConfig, "Config::PrintObject"); diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index caf52803e..8952afa8d 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -6,7 +6,7 @@ namespace Slic3r { enum GCodeFlavor { - gcfRepRap, gcfTeacup, gcfMakerWare, gcfSailfish, gcfMach3, gcfNoExtrusion, + gcfRepRap, gcfTeacup, gcfMakerWare, gcfSailfish, gcfMach3, gcfMachinekit, gcfNoExtrusion, }; enum InfillPattern { @@ -29,6 +29,7 @@ template<> inline t_config_enum_values ConfigOptionEnum::get_enum_v keys_map["makerware"] = gcfMakerWare; keys_map["sailfish"] = gcfSailfish; keys_map["mach3"] = gcfMach3; + keys_map["machinekit"] = gcfMachinekit; keys_map["no-extrusion"] = gcfNoExtrusion; return keys_map; } @@ -78,38 +79,7 @@ class DynamicPrintConfig : public DynamicConfig this->def = &PrintConfigDef::def; }; - 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("spiral_vase", true)->value) { - { - // this should be actually done only on the spiral layers instead of all - ConfigOptionBools* opt = this->opt("retract_layer_change", true); - opt->values.assign(opt->values.size(), false); // set all values to false - } - { - this->opt("perimeters", true)->value = 1; - this->opt("top_solid_layers", true)->value = 0; - this->opt("fill_density", true)->value = 0; - } - } - }; + void normalize(); }; class StaticPrintConfig : public virtual StaticConfig @@ -410,7 +380,7 @@ class GCodeConfig : public virtual StaticPrintConfig 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"; } else if (this->gcode_flavor.value == gcfNoExtrusion) { return ""; diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index fdf3116b7..cf1ee20e4 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -333,6 +333,14 @@ PrintObject::invalidate_all_steps() 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 PrintObject::bridge_over_infill() { @@ -340,7 +348,7 @@ PrintObject::bridge_over_infill() size_t region_id = region - this->_print->regions.begin(); 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) { if (layer_it == this->layers.begin()) continue; diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index 8cdd13e26..25d3c7f48 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -6,7 +6,7 @@ #include #include -#define SLIC3R_VERSION "1.2.6-dev" +#define SLIC3R_VERSION "1.2.7-dev" #define EPSILON 1e-4 #define SCALING_FACTOR 0.000001 diff --git a/xs/t/11_clipper.t b/xs/t/11_clipper.t index 5c5b66349..8709c0108 100644 --- a/xs/t/11_clipper.t +++ b/xs/t/11_clipper.t @@ -3,8 +3,9 @@ use strict; use warnings; +use List::Util qw(sum); use Slic3r::XS; -use Test::More tests => 19; +use Test::More tests => 23; my $square = Slic3r::Polygon->new( # ccw [200, 100], @@ -178,8 +179,8 @@ if (0) { # Clipper does not preserve polyline orientation is scalar(@$result), 1, 'intersection_ppl - result is not empty'; } -if (0) { - # Disabled until Clipper bug #122 is fixed +{ + # Clipper bug #122 my $subject = [ 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]), ]; 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->[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__ diff --git a/xs/t/15_config.t b/xs/t/15_config.t index e051d9a86..43bfe501c 100644 --- a/xs/t/15_config.t +++ b/xs/t/15_config.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 108; +use Test::More tests => 110; foreach my $config (Slic3r::Config->new, Slic3r::Config::Full->new) { $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'; $config->set_deserialize('gcode_flavor', 'mach3'); 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'); is $config->get('fill_pattern'), 'line', 'deserialize enum (fill_pattern)'; diff --git a/xs/xsp/GCodeWriter.xsp b/xs/xsp/GCodeWriter.xsp index a76c0e55f..18ae7b0e2 100644 --- a/xs/xsp/GCodeWriter.xsp +++ b/xs/xsp/GCodeWriter.xsp @@ -19,6 +19,7 @@ %code{% THIS->apply_print_config(*print_config); %}; void set_extruders(std::vector extruder_ids); std::string preamble(); + std::string postamble(); 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_fan(unsigned int speed, bool dont_save = false); diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 13ce4f1c3..c9b93bba3 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -12,7 +12,6 @@ IV _constant() ALIAS: - STEP_INIT_EXTRUDERS = psInitExtruders STEP_SLICE = posSlice STEP_PERIMETERS = posPerimeters STEP_PREPARE_INFILL = posPrepareInfill @@ -199,7 +198,8 @@ _constant() void add_model_object(ModelObject* model_object, int idx = -1); bool apply_config(DynamicPrintConfig* config) %code%{ RETVAL = THIS->apply_config(*config); %}; - void init_extruders(); + bool has_infinite_skirt(); + bool has_skirt(); void validate() %code%{ try {