From 7de8b20bc4edca6d9be7202564e380e45e297012 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 17 Mar 2013 02:22:50 +0100 Subject: [PATCH 001/179] New wipe feature --- README.markdown | 1 + lib/Slic3r/Config.pm | 11 ++++++++++- lib/Slic3r/Extruder.pm | 10 ++++++++-- lib/Slic3r/GCode.pm | 26 ++++++++++++++++++++++++-- lib/Slic3r/Polyline.pm | 25 +++++++++++++++++++++++++ lib/Slic3r/Print.pm | 3 ++- slic3r.pl | 1 + 7 files changed, 71 insertions(+), 6 deletions(-) diff --git a/README.markdown b/README.markdown index 95c565c9..dabd97a8 100644 --- a/README.markdown +++ b/README.markdown @@ -239,6 +239,7 @@ The author of the Silk icon set is Mark James. --retract-lift Lift Z by the given distance in mm when retracting (default: 0) --retract-layer-change Enforce a retraction before each Z move (default: yes) + --wipe Wipe the nozzle while doing a retraction (default: no) Retraction options for multi-extruder setups: --retract-length-toolchange diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 3e147a44..dc0763be 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -794,6 +794,15 @@ END deserialize => $deserialize_comma, default => [1], }, + 'wipe' => { + label => 'Wipe before retract', + tooltip => 'This flag will move the nozzle while retracting to minimize the possible blob on leaky extruders.', + cli => 'wipe!', + type => 'bool', + serialize => $serialize_comma, + deserialize => $deserialize_comma, + default => [0], + }, 'retract_length_toolchange' => { label => 'Length', tooltip => 'When retraction is triggered before changing tool, filament is pulled back by the specified amount (the length is measured on raw filament, before it enters the extruder).', @@ -1065,7 +1074,7 @@ sub new_from_cli { } $args{$_} = $Options->{$_}{deserialize}->($args{$_}) - for grep exists $args{$_}, qw(print_center bed_size duplicate_grid extruder_offset retract_layer_change); + for grep exists $args{$_}, qw(print_center bed_size duplicate_grid extruder_offset retract_layer_change wipe); return $class->new(%args); } diff --git a/lib/Slic3r/Extruder.pm b/lib/Slic3r/Extruder.pm index bc124dad..526ac504 100644 --- a/lib/Slic3r/Extruder.pm +++ b/lib/Slic3r/Extruder.pm @@ -1,13 +1,13 @@ package Slic3r::Extruder; use Moo; -use Slic3r::Geometry qw(PI); +use Slic3r::Geometry qw(PI scale); use constant OPTIONS => [qw( extruder_offset nozzle_diameter filament_diameter extrusion_multiplier temperature first_layer_temperature retract_length retract_lift retract_speed retract_restart_extra retract_before_travel - retract_layer_change retract_length_toolchange retract_restart_extra_toolchange + retract_layer_change retract_length_toolchange retract_restart_extra_toolchange wipe )]; has 'id' => (is => 'rw', required => 1); @@ -18,6 +18,7 @@ has 'retracted' => (is => 'rw', default => sub {0} ); has 'restart_extra' => (is => 'rw', default => sub {0} ); has 'e_per_mm3' => (is => 'lazy'); has 'retract_speed_mm_min' => (is => 'lazy'); +has 'scaled_wipe_distance' => (is => 'lazy'); # scaled mm has '_mm3_per_mm_cache' => (is => 'ro', default => sub {{}}); sub _build_bridge_flow { @@ -35,6 +36,11 @@ sub _build_retract_speed_mm_min { return $self->retract_speed * 60; } +sub _build_scaled_wipe_distance { + my $self = shift; + return scale $self->retract_length / $self->retract_speed * $Slic3r::Config->travel_speed; +} + sub make_flow { my $self = shift; return Slic3r::Flow->new(nozzle_diameter => $self->nozzle_diameter, @_); diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 52efe6b3..247ad9fa 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -28,6 +28,7 @@ has 'last_pos' => (is => 'rw', default => sub { Slic3r::Point->new(0,0 has 'last_speed' => (is => 'rw', default => sub {""}); has 'last_f' => (is => 'rw', default => sub {""}); has 'last_fan_speed' => (is => 'rw', default => sub {0}); +has 'wipe_path' => (is => 'rw'); has 'dec' => (is => 'ro', default => sub { 3 } ); # used for vibration limit: @@ -150,6 +151,7 @@ sub extrude_loop { # extrude along the path my $gcode = $self->extrude_path($extrusion_path, $description); + $self->wipe_path($extrusion_path->polyline); # make a little move inwards before leaving loop if ($loop->role == EXTR_ROLE_EXTERNAL_PERIMETER) { @@ -236,12 +238,15 @@ sub extrude_path { $path_length = unscale $path->length; $gcode .= $self->G2_G3($path->points->[-1], $path->orientation, $path->center, $e * unscale $path_length, $description); + $self->wipe_path(undef); } else { foreach my $line ($path->lines) { my $line_length = unscale $line->length; $path_length += $line_length; $gcode .= $self->G1($line->[B], undef, $e * $line_length, $description); } + $self->wipe_path([ reverse @{$path->points}[0..$#{$path->points}] ]) + if $Slic3r::Config->wipe; } if ($Slic3r::Config->cooling) { @@ -347,6 +352,14 @@ sub retract { # if we already retracted, reduce the required amount of retraction $length -= $self->extruder->retracted; return "" unless $length > 0; + my $gcode = ""; + + # wipe + my $wipe_path = (); + if ($Slic3r::Config->wipe && $self->wipe_path) { + $wipe_path = Slic3r::Polyline->new([ $self->last_pos, @{$self->wipe_path} ]) + ->clip_start($self->extruder->scaled_wipe_distance); + } # prepare moves $self->speed('retract'); @@ -355,7 +368,6 @@ sub retract { ? undef : [undef, $self->z + $self->extruder->retract_lift, 0, 'lift plate during travel']; - my $gcode = ""; if (($Slic3r::Config->g0 || $Slic3r::Config->gcode_flavor eq 'mach3') && $params{travel_to}) { if ($lift) { # combine lift and retract @@ -371,7 +383,17 @@ sub retract { my $travel = [undef, $params{move_z}, $retract->[2], "change layer and $comment"]; $gcode .= $self->G0(@$travel); } else { - $gcode .= $self->G1(@$retract); + if ($wipe_path) { + $self->speed('travel'); + # subdivide the retraction + my $total_wipe_length = $wipe_path->length; + for (1 .. $#$wipe_path) { + my $segment_length = $wipe_path->[$_-1]->distance_to($wipe_path->[$_]); + $gcode .= $self->G1($wipe_path->[$_], undef, $retract->[2] * ($segment_length / $total_wipe_length), $retract->[3] . ";_WIPE"); + } + } else { + $gcode .= $self->G1(@$retract); + } if (!$self->lifted) { if (defined $params{move_z} && $self->extruder->retract_lift > 0) { my $travel = [undef, $params{move_z} + $self->extruder->retract_lift, 0, 'move to next layer (' . $self->layer->id . ') and lift']; diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm index 2578e505..8d5c9c13 100644 --- a/lib/Slic3r/Polyline.pm +++ b/lib/Slic3r/Polyline.pm @@ -164,6 +164,7 @@ sub scale { return $self; } +# removes the given distance from the end of the polyline sub clip_end { my $self = shift; my ($distance) = @_; @@ -184,6 +185,30 @@ sub clip_end { } } +# only keeps the given distance at the beginning of the polyline +sub clip_start { + my $self = shift; + my ($distance) = @_; + + my $points = []; + + for (my $i = 1; $distance > 0 && $i <= $#$self; $i++) { + my $point = $self->[$i]; + my $segment_length = $point->distance_to($self->[$i-1]); + if ($segment_length <= $distance) { + $distance -= $segment_length; + push @$points, $point; + next; + } + + my $new_point = Slic3r::Geometry::point_along_segment($self->[$i-1], $point, $distance); + push @$points, Slic3r::Point->new($new_point); + $distance = 0; + } + + return (ref $self)->new($points); +} + package Slic3r::Polyline::Collection; use Moo; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index e374a6cf..7c664861 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -957,7 +957,7 @@ sub write_gcode { Slic3r::debugf " fan = %d%%, speed = %d%%\n", $fan_speed, $speed_factor * 100; if ($speed_factor < 1) { - $gcode =~ s/^(?=.*? [XY])(?=.*? E)(?set_fan($Slic3r::Config->bridge_fan_speed, 1) /gmex; $gcode =~ s/^;_BRIDGE_FAN_END\n/ $gcodegen->set_fan($fan_speed, 1) /gmex; } + $gcode =~ s/;_WIPE//g; return $gcode; }; diff --git a/slic3r.pl b/slic3r.pl index 5c5c80b1..9850a991 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -292,6 +292,7 @@ $j --retract-lift Lift Z by the given distance in mm when retracting (default: $config->{retract_lift}[0]) --retract-layer-change Enforce a retraction before each Z move (default: yes) + --wipe Wipe the nozzle while doing a retraction (default: no) Retraction options for multi-extruder setups: --retract-length-toolchange From ec5f0635f7cc0411f54d36f0b9ff935bf60d5422 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 18 Mar 2013 21:20:32 +0100 Subject: [PATCH 002/179] Add wipe in GUI --- lib/Slic3r/GUI/Tab.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 8fdacf6f..f83f2aa5 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -686,7 +686,7 @@ sub build { $self->_build_extruder_pages; } -sub _extruder_options { qw(nozzle_diameter extruder_offset retract_length retract_lift retract_speed retract_restart_extra retract_before_travel +sub _extruder_options { qw(nozzle_diameter extruder_offset retract_length retract_lift retract_speed retract_restart_extra retract_before_travel wipe retract_layer_change retract_length_toolchange retract_restart_extra_toolchange) } sub config { @@ -720,7 +720,7 @@ sub _build_extruder_pages { title => 'Retraction', options => [ map "${_}#${extruder_idx}", - qw(retract_length retract_lift retract_speed retract_restart_extra retract_before_travel retract_layer_change) + qw(retract_length retract_lift retract_speed retract_restart_extra retract_before_travel retract_layer_change wipe) ], }, { From b7cd3628209ec11906d77ab9788fd76caba8847d Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 19 Mar 2013 19:15:53 +0100 Subject: [PATCH 003/179] Remove scale from G-code comments --- lib/Slic3r/Print.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 7fa1db9f..4332fbc6 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -693,7 +693,7 @@ sub write_gcode { print $fh "; $_\n" foreach split /\R/, $Slic3r::Config->notes; print $fh "\n" if $Slic3r::Config->notes; - for (qw(layer_height perimeters top_solid_layers bottom_solid_layers fill_density perimeter_speed infill_speed travel_speed scale)) { + for (qw(layer_height perimeters top_solid_layers bottom_solid_layers fill_density perimeter_speed infill_speed travel_speed)) { printf $fh "; %s = %s\n", $_, $Slic3r::Config->$_; } for (qw(nozzle_diameter filament_diameter extrusion_multiplier)) { From 6bc5de0b5d50108a98e319de50adaebdf570ac32 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 24 Mar 2013 15:26:55 +0100 Subject: [PATCH 004/179] Allow custom ranges with layer_height = 0 --- lib/Slic3r/GUI/Plater/ObjectDialog.pm | 4 +- lib/Slic3r/Print/Object.pm | 70 ++++++++++++++++++--------- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/lib/Slic3r/GUI/Plater/ObjectDialog.pm b/lib/Slic3r/GUI/Plater/ObjectDialog.pm index 77a34685..6543d50d 100644 --- a/lib/Slic3r/GUI/Plater/ObjectDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectDialog.pm @@ -98,7 +98,7 @@ sub new { my $sizer = Wx::BoxSizer->new(wxVERTICAL); { - my $label = Wx::StaticText->new($self, -1, "You can use this section to override the default layer height for parts of this object.", + my $label = Wx::StaticText->new($self, -1, "You can use this section to override the default layer height for parts of this object. Set layer height to zero to skip portions of the input file.", wxDefaultPosition, [-1, 25]); $label->SetFont(Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); $sizer->Add($label, 0, wxEXPAND | wxALL, 10); @@ -162,7 +162,7 @@ sub CanClose { Slic3r::GUI::show_error($self, "Invalid Z range $min-$max."); return 0; } - if ($height <= 0) { + if ($height < 0) { Slic3r::GUI::show_error($self, "Invalid layer height $height."); return 0; } diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index ffac1e1e..a14f6202 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -18,26 +18,50 @@ has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_m sub BUILD { my $self = shift; - # make layers - my $print_z = my $slice_z = my $raft_z = 0; - while (!@{$self->layers} || $self->layers->[-1]->slice_z < $self->size->[Z]) { - my $id = $#{$self->layers} + 1; - my $height = $Slic3r::Config->layer_height; - $height = $Slic3r::Config->get_value('first_layer_height') if $id == 0; - if (my $range = first { $_->[0] <= ($print_z + $_->[2]) && $_->[1] >= ($print_z + $_->[2]) } @{$self->layer_height_ranges}) { - $height = $range->[2]; - } + # make layers taking custom heights into account + my $print_z = my $slice_z = my $height = 0; + + # add raft layers + for my $id (0 .. $Slic3r::Config->raft_layers-1) { + $height = ($id == 0) + ? $Slic3r::Config->get_value('first_layer_height') + : $Slic3r::Config->layer_height; $print_z += $height; - if ($id < $Slic3r::Config->raft_layers) { - # this is a raft layer - $raft_z += $height; - $slice_z = -1; - } else { - $slice_z = $print_z - ($height/2) - $raft_z; + push @{$self->layers}, Slic3r::Layer->new( + object => $self, + id => $id, + height => $height, + print_z => scale $print_z, + slice_z => -1, + ); + } + + # loop until we have at least one layer and the max slice_z reaches the object height + my $max_z = unscale $self->size->[Z]; + while (!@{$self->layers} || ($slice_z - $height) <= $max_z) { + my $id = $#{$self->layers} + 1; + + # assign the default height to the layer according to the general settings + $height = ($id == 0) + ? $Slic3r::Config->get_value('first_layer_height') + : $Slic3r::Config->layer_height; + + # look for an applicable custom range + if (my $range = first { $_->[0] <= $slice_z && $_->[1] > $slice_z } @{$self->layer_height_ranges}) { + $height = $range->[2]; + + # if user set custom height to zero we should just skip the range and resume slicing over it + if ($height == 0) { + $slice_z += $range->[1] - $range->[0]; + next; + } } + $print_z += $height; + $slice_z += $height/2; + ### Slic3r::debugf "Layer %d: height = %s; slice_z = %s; print_z = %s\n", $id, $height, $slice_z, $print_z; push @{$self->layers}, Slic3r::Layer->new( @@ -47,6 +71,8 @@ sub BUILD { print_z => scale $print_z, slice_z => scale $slice_z, ); + + $slice_z += $height/2; # add the other half layer } } @@ -131,9 +157,8 @@ sub slice { # free memory $self->meshes(undef); - # remove last layer if empty - # (we might have created it because of the $max_layer = ... + 1 code in TriangleMesh) - pop @{$self->layers} if !map @{$_->lines}, @{$self->layers->[-1]->regions}; + # remove last layer(s) if empty + pop @{$self->layers} while !map @{$_->lines}, @{$self->layers->[-1]->regions}; foreach my $layer (@{ $self->layers }) { # make sure all layers contain layer region objects for all regions @@ -806,8 +831,7 @@ sub generate_support_material { [ map @$_, @current_layer_offsetted_slices ], ); $layers_contact_areas{$i} = [ - map $_->simplify($flow->scaled_spacing), - @{collapse_ex([ map @$_, @{$layers_contact_areas{$i}} ], $flow->scaled_width)}, + @{collapse_ex([ map @$_, @{$layers_contact_areas{$i}} ], $flow->scaled_width)}, ]; # to define interface regions of this layer we consider the overhangs of all the upper layers @@ -820,8 +844,7 @@ sub generate_support_material { ], ); $layers_interfaces{$i} = [ - map $_->simplify($flow->scaled_spacing), - @{collapse_ex([ map @$_, @{$layers_interfaces{$i}} ], $flow->scaled_width)}, + @{collapse_ex([ map @$_, @{$layers_interfaces{$i}} ], $flow->scaled_width)}, ]; # generate support material in current layer (for upper layers) @@ -842,8 +865,7 @@ sub generate_support_material { ], ); $layers{$i} = [ - map $_->simplify($flow->scaled_spacing), - @{collapse_ex([ map @$_, @{$layers{$i}} ], $flow->scaled_width)}, + @{collapse_ex([ map @$_, @{$layers{$i}} ], $flow->scaled_width)}, ]; # get layer overhangs and put them into queue for adding support inside lower layers; From 4cb36fcbe2f5688da65006d5ca4f32482ba3996e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 24 Mar 2013 21:28:16 +0100 Subject: [PATCH 005/179] Remove cooling checkbox from simple mode --- lib/Slic3r/GUI/SimpleTab.pm | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/Slic3r/GUI/SimpleTab.pm b/lib/Slic3r/GUI/SimpleTab.pm index 29044e4a..0463a3d4 100644 --- a/lib/Slic3r/GUI/SimpleTab.pm +++ b/lib/Slic3r/GUI/SimpleTab.pm @@ -172,11 +172,6 @@ sub build { }, ], ); - - $self->append_optgroup( - title => 'Cooling', - options => [qw(cooling)], - ); } package Slic3r::GUI::SimpleTab::Printer; From 843a700e960d7745de002c5dbdfe7113b2c0ecd7 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 25 Mar 2013 23:06:18 +0100 Subject: [PATCH 006/179] Fixes for wipe --- lib/Slic3r/GCode.pm | 11 +++++++---- lib/Slic3r/Polyline.pm | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 247ad9fa..92190cf7 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -65,10 +65,12 @@ sub set_shift { my @shift = @_; # if shift increases (goes towards right), last_pos decreases because it goes towards left - $self->last_pos->translate( + my @translate = ( scale ($self->shift_x - $shift[X]), scale ($self->shift_y - $shift[Y]), ); + $self->last_pos->translate(@translate); + $self->wipe_path->translate(@translate) if $self->wipe_path; $self->shift_x($shift[X]); $self->shift_y($shift[Y]); @@ -245,7 +247,7 @@ sub extrude_path { $path_length += $line_length; $gcode .= $self->G1($line->[B], undef, $e * $line_length, $description); } - $self->wipe_path([ reverse @{$path->points}[0..$#{$path->points}] ]) + $self->wipe_path(Slic3r::Polyline->new([ reverse @{$path->points} ])) if $Slic3r::Config->wipe; } @@ -355,9 +357,9 @@ sub retract { my $gcode = ""; # wipe - my $wipe_path = (); + my $wipe_path; if ($Slic3r::Config->wipe && $self->wipe_path) { - $wipe_path = Slic3r::Polyline->new([ $self->last_pos, @{$self->wipe_path} ]) + $wipe_path = Slic3r::Polyline->new([ $self->last_pos, @{$self->wipe_path}[1..$#{$self->wipe_path}] ]) ->clip_start($self->extruder->scaled_wipe_distance); } @@ -387,6 +389,7 @@ sub retract { $self->speed('travel'); # subdivide the retraction my $total_wipe_length = $wipe_path->length; + for (1 .. $#$wipe_path) { my $segment_length = $wipe_path->[$_-1]->distance_to($wipe_path->[$_]); $gcode .= $self->G1($wipe_path->[$_], undef, $retract->[2] * ($segment_length / $total_wipe_length), $retract->[3] . ";_WIPE"); diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm index 8d5c9c13..16a72886 100644 --- a/lib/Slic3r/Polyline.pm +++ b/lib/Slic3r/Polyline.pm @@ -190,7 +190,7 @@ sub clip_start { my $self = shift; my ($distance) = @_; - my $points = []; + my $points = [ $self->[0] ]; for (my $i = 1; $distance > 0 && $i <= $#$self; $i++) { my $point = $self->[$i]; From 769ec0cb030b0705cd50003ec12d02ef7ac03c1e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 26 Mar 2013 13:04:57 +0100 Subject: [PATCH 007/179] Better clipping of honeycomb paths --- lib/Slic3r/Fill/Honeycomb.pm | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/Fill/Honeycomb.pm b/lib/Slic3r/Fill/Honeycomb.pm index 9e4ff8a2..a2386d60 100644 --- a/lib/Slic3r/Fill/Honeycomb.pm +++ b/lib/Slic3r/Fill/Honeycomb.pm @@ -78,13 +78,14 @@ sub fill_surface { $self->cache->{$cache_id} = [@polygons]; } - # build polylines from polygons without re-appending the initial point: + # consider polygons as polylines without re-appending the initial point: # this cuts the last segment on purpose, so that the jump to the next # path is more straight - my @paths = map Slic3r::Polyline->new(@$_), map @$_, @{intersection_ex( - $self->cache->{$cache_id}, - $expolygon, - )}; + my @paths = map Slic3r::Polyline->new($_), + @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection( + $expolygon, + $self->cache->{$cache_id}, + ) }; return { flow_spacing => $params{flow_spacing} }, Slic3r::Polyline::Collection->new(polylines => \@paths)->chained_path; From 4c41f6c4629eb9df09794897e697fa5ca8f988ac Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 26 Mar 2013 13:57:37 +0100 Subject: [PATCH 008/179] Safer safety_offset() for ExPolygons --- lib/Slic3r/ExPolygon.pm | 10 +--------- lib/Slic3r/Geometry/Clipper.pm | 7 ++++++- lib/Slic3r/Polygon.pm | 5 ----- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index 558f9fe2..f3742d3c 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -85,15 +85,7 @@ sub offset_ex { sub safety_offset { my $self = shift; - - # we're offsetting contour and holes separately - # because Clipper doesn't return polygons in the same order as - # we feed them to it - - return (ref $self)->new( - $self->contour->safety_offset, - @{ Slic3r::Geometry::Clipper::safety_offset([$self->holes]) }, - ); + return Slic3r::Geometry::Clipper::safety_offset_ex($self, @_); } sub noncollapsing_offset_ex { diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index 0196460f..68e3d7c0 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -4,7 +4,7 @@ use warnings; require Exporter; our @ISA = qw(Exporter); -our @EXPORT_OK = qw(safety_offset offset offset_ex collapse_ex +our @EXPORT_OK = qw(safety_offset safety_offset_ex offset offset_ex collapse_ex diff_ex diff union_ex intersection_ex xor_ex PFT_EVENODD JT_MITER JT_ROUND JT_SQUARE is_counter_clockwise); @@ -17,6 +17,11 @@ sub safety_offset { return Math::Clipper::offset($polygons, $factor // (scale 1e-05), 100000, JT_MITER, 2); } +sub safety_offset_ex { + # offset polygons and then apply holes to the right contours + return @{ union_ex([ safety_offset(@_) ]) }; +} + sub offset { my ($polygons, $distance, $scale, $joinType, $miterLimit) = @_; $scale ||= 100000; diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm index e9bfcf81..07f36d4f 100644 --- a/lib/Slic3r/Polygon.pm +++ b/lib/Slic3r/Polygon.pm @@ -95,11 +95,6 @@ sub area { return Slic3r::Geometry::Clipper::area($self); } -sub safety_offset { - my $self = shift; - return (ref $self)->new(Slic3r::Geometry::Clipper::safety_offset([$self])->[0]); -} - sub offset { my $self = shift; return map Slic3r::Polygon->new($_), Slic3r::Geometry::Clipper::offset([$self], @_); From 2f046799f26fdf923ab25d38f536758437a76316 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 26 Mar 2013 14:03:24 +0100 Subject: [PATCH 009/179] Removed offset() method from Slic3r::Polygon because it only works with ccw polygons --- lib/Slic3r/GCode/MotionPlanner.pm | 5 +++-- lib/Slic3r/GUI/Plater.pm | 6 +++--- lib/Slic3r/Polygon.pm | 7 +------ lib/Slic3r/Polyline.pm | 5 ++++- lib/Slic3r/Print.pm | 2 +- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/lib/Slic3r/GCode/MotionPlanner.pm b/lib/Slic3r/GCode/MotionPlanner.pm index 114f6a2b..0cde4e1a 100644 --- a/lib/Slic3r/GCode/MotionPlanner.pm +++ b/lib/Slic3r/GCode/MotionPlanner.pm @@ -13,7 +13,7 @@ has '_crossing_edges' => (is => 'rw', default => sub { {} }); # edge_idx => boo use List::Util qw(first); use Slic3r::Geometry qw(A B scale epsilon nearest_point); -use Slic3r::Geometry::Clipper qw(diff_ex JT_MITER); +use Slic3r::Geometry::Clipper qw(diff_ex offset JT_MITER); # clearance (in mm) from the perimeters has '_inner_margin' => (is => 'ro', default => sub { scale 0.5 }); @@ -68,7 +68,8 @@ sub BUILD { : [ $self->islands->[$i]->offset_ex(-$self->_inner_margin) ]; # offset the island outwards to make the boundaries for external movements - $self->_outer->[$i] = [ $self->islands->[$i]->contour->offset($self->_outer_margin) ]; + $self->_outer->[$i] = [ offset([ $self->islands->[$i]->contour], $self->_outer_margin) ]; + bless $_, 'Slic3r::Polygon' for @{ $self->_outer->[$i] }; # if internal motion is enabled, build a set of utility expolygons representing # the outer boundaries (as contours) and the inner boundaries (as holes). whenever diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index c0e30012..ba527324 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -7,7 +7,7 @@ use File::Basename qw(basename dirname); use List::Util qw(max sum first); use Math::ConvexHull::MonotoneChain qw(convex_hull); use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX); -use Slic3r::Geometry::Clipper qw(JT_ROUND); +use Slic3r::Geometry::Clipper qw(offset JT_ROUND); use threads::shared qw(shared_clone); use Wx qw(:bitmap :brush :button :cursor :dialog :filedialog :font :keycode :icon :id :listctrl :misc :panel :pen :sizer :toolbar :window); use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED EVT_LIST_ITEM_DESELECTED EVT_LIST_ITEM_SELECTED EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL EVT_CHOICE); @@ -881,7 +881,7 @@ sub repaint { # if sequential printing is enabled and we have more than one object if ($parent->{config}->complete_objects && (map @{$_->instances}, @{$parent->{objects}}) > 1) { my $convex_hull = Slic3r::Polygon->new(convex_hull([ map @{$_->contour}, @{$parent->{object_previews}->[-1][2]->expolygons} ])); - my $clearance = +($convex_hull->offset($parent->{config}->extruder_clearance_radius / 2 * $parent->{scaling_factor}, 1, JT_ROUND))[0]; + my ($clearance) = offset([$convex_hull], $parent->{config}->extruder_clearance_radius / 2 * $parent->{scaling_factor}, 1, JT_ROUND); $dc->SetPen($parent->{clearance_pen}); $dc->SetBrush($parent->{transparent_brush}); $dc->DrawPolygon($parent->_y($clearance), 0, 0); @@ -892,7 +892,7 @@ sub repaint { # draw skirt if (@{$parent->{object_previews}} && $parent->{config}->skirts) { my $convex_hull = Slic3r::Polygon->new(convex_hull([ map @{$_->contour}, map @{$_->[2]->expolygons}, @{$parent->{object_previews}} ])); - $convex_hull = +($convex_hull->offset($parent->{config}->skirt_distance * $parent->{scaling_factor}, 1, JT_ROUND))[0]; + ($convex_hull) = offset([$convex_hull], $parent->{config}->skirt_distance * $parent->{scaling_factor}, 1, JT_ROUND); $dc->SetPen($parent->{skirt_pen}); $dc->SetBrush($parent->{transparent_brush}); $dc->DrawPolygon($parent->_y($convex_hull), 0, 0) if $convex_hull; diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm index 07f36d4f..a89cc079 100644 --- a/lib/Slic3r/Polygon.pm +++ b/lib/Slic3r/Polygon.pm @@ -95,11 +95,6 @@ sub area { return Slic3r::Geometry::Clipper::area($self); } -sub offset { - my $self = shift; - return map Slic3r::Polygon->new($_), Slic3r::Geometry::Clipper::offset([$self], @_); -} - sub grow { my $self = shift; return $self->split_at_first_point->grow(@_); @@ -147,7 +142,7 @@ sub is_printable { # detect them and we would be discarding them. my $p = $self->clone; $p->make_counter_clockwise; - return $p->offset(-$width / 2) ? 1 : 0; + return Slic3r::Geometry::Clipper::offset([$p], -$width / 2) ? 1 : 0; } sub is_valid { diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm index 2578e505..4d7fbe90 100644 --- a/lib/Slic3r/Polyline.pm +++ b/lib/Slic3r/Polyline.pm @@ -86,7 +86,10 @@ sub length { sub grow { my $self = shift; - return Slic3r::Polygon->new(@$self, CORE::reverse @$self[1..($#$self-1)])->offset(@_); + return Slic3r::Geometry::Clipper::offset( + [ Slic3r::Polygon->new(@$self, CORE::reverse @$self[1..($#$self-1)]) ], + @_, + ); } sub nearest_point_to { diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 4332fbc6..6df75f2f 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -151,7 +151,7 @@ sub validate { { my @points = map [ @$_[X,Y] ], map @{$_->vertices}, @{$self->objects->[$obj_idx]->meshes}; my $convex_hull = Slic3r::Polygon->new(convex_hull(\@points)); - $clearance = +($convex_hull->offset(scale $Slic3r::Config->extruder_clearance_radius / 2, 1, JT_ROUND))[0]; + ($clearance) = offset([$convex_hull], scale $Slic3r::Config->extruder_clearance_radius / 2, 1, JT_ROUND); } for my $copy (@{$self->objects->[$obj_idx]->copies}) { my $copy_clearance = $clearance->clone; From aae9625a13eac2e7a1166133b666c6cf2980cc33 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 26 Mar 2013 15:47:40 +0100 Subject: [PATCH 010/179] Bugfix: solid-infill-below-area wasn't ignored when fill_density = 0, causing solid layers here and there. #1049 --- lib/Slic3r/Layer/Region.pm | 2 +- t/fill.t | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index c6f10e87..6aec785a 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -462,7 +462,7 @@ sub prepare_fill_surfaces { } # turn too small internal regions into solid regions according to the user setting - { + if ($Slic3r::Config->fill_density > 0) { my $min_area = scale scale $Slic3r::Config->solid_infill_below_area; # scaling an area requires two calls! my @small = grep $_->surface_type == S_TYPE_INTERNAL && $_->expolygon->contour->area <= $min_area, @{$self->fill_surfaces}; $_->surface_type(S_TYPE_INTERNALSOLID) for @small; diff --git a/t/fill.t b/t/fill.t index b5ebd5dc..cbffd7da 100644 --- a/t/fill.t +++ b/t/fill.t @@ -95,4 +95,22 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } ok Slic3r::Test::gcode($print), 'successful hilbertcurve infill generation'; } +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('skirts', 0); + $config->set('perimeters', 0); + $config->set('fill_density', 0); + $config->set('top_solid_layers', 0); + $config->set('bottom_solid_layers', 0); + $config->set('solid_infill_below_area', 20000000); + + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + Slic3r::Test::GCodeReader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { + my ($self, $cmd, $args, $info) = @_; + + fail "solid_infill_below_area should be ignored when fill_density is 0" + if $info->{extruding}; + }); +} + __END__ From e563c62094f6cbca5834cf1e4245dceefc755d16 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 29 Mar 2013 18:56:34 +0100 Subject: [PATCH 011/179] Replace SetValue with ChangeValue to avoid wxWidgets to fire useless events --- lib/Slic3r/GUI/OptionsGroup.pm | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/Slic3r/GUI/OptionsGroup.pm b/lib/Slic3r/GUI/OptionsGroup.pm index 3cbd52f3..8734ff6f 100644 --- a/lib/Slic3r/GUI/OptionsGroup.pm +++ b/lib/Slic3r/GUI/OptionsGroup.pm @@ -167,12 +167,15 @@ sub _build_field { ? Wx::SpinCtrl->new($self->parent, -1, $opt->{default}, wxDefaultPosition, $size, $style, $opt->{min} || 0, $opt->{max} || 2147483647, $opt->{default}) : Wx::TextCtrl->new($self->parent, -1, $opt->{default}, wxDefaultPosition, $size, $style); $field->Disable if $opt->{readonly}; - $self->_setters->{$opt_key} = sub { $field->SetValue($_[0]) }; my $on_change = sub { $self->_on_change($opt_key, $field->GetValue) }; - $opt->{type} eq 'i' - ? EVT_SPINCTRL ($self->parent, $field, $on_change) - : EVT_TEXT ($self->parent, $field, $on_change); + if ($opt->{type} eq 'i') { + $self->_setters->{$opt_key} = sub { $field->SetValue($_[0]) }; + EVT_SPINCTRL ($self->parent, $field, $on_change); + } else { + $self->_setters->{$opt_key} = sub { $field->ChangeValue($_[0]) }; + EVT_TEXT ($self->parent, $field, $on_change); + } $tooltip .= " (default: " . $opt->{default} . ")" if ($opt->{default}); } elsif ($opt->{type} eq 'bool') { $field = Wx::CheckBox->new($self->parent, -1, ""); From 1b79b1cb2063a920751e32a6c24df2d6cc8a1bc7 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 29 Mar 2013 19:18:06 +0100 Subject: [PATCH 012/179] Refactoring: use the clone() method for inflating Surface objects --- lib/Slic3r/Fill.pm | 8 +------- lib/Slic3r/Layer/Region.pm | 5 +---- lib/Slic3r/Print/Object.pm | 15 ++++++--------- lib/Slic3r/Surface.pm | 25 +++++++++++++------------ 4 files changed, 21 insertions(+), 32 deletions(-) diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index e08bc2e7..de1d52ef 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -89,13 +89,7 @@ sub make_fill { 1, ); - push @surfaces, map Slic3r::Surface->new( - expolygon => $_, - surface_type => $group->[0]->surface_type, - bridge_angle => $group->[0]->bridge_angle, - thickness => $group->[0]->thickness, - thickness_layers => $group->[0]->thickness_layers, - ), @$union; + push @surfaces, map $group->[0]->clone(expolygon => $_), @$union; } } diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 6aec785a..1f1b8df2 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -514,10 +514,7 @@ sub process_external_surfaces { # subtract the new top surfaces from the other non-top surfaces and re-add them my @other = grep $_->surface_type != S_TYPE_TOP && $_->surface_type != S_TYPE_BOTTOM, @{$self->fill_surfaces}; foreach my $group (Slic3r::Surface->group(@other)) { - push @new_surfaces, map Slic3r::Surface->new( - expolygon => $_, - surface_type => $group->[0]->surface_type, - ), @{diff_ex( + push @new_surfaces, map $group->[0]->clone(expolygon => $_), @{diff_ex( [ map $_->p, @$group ], [ map $_->p, @new_surfaces ], )}; diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index a14f6202..a1193217 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -543,13 +543,11 @@ sub bridge_over_infill { my @new_surfaces = (); # subtract the area from all types of surfaces foreach my $group (Slic3r::Surface->group(@{$lower_layerm->fill_surfaces})) { - push @new_surfaces, map Slic3r::Surface->new( - expolygon => $_, - surface_type => $group->[0]->surface_type, - ), @{diff_ex( - [ map $_->p, @$group ], - [ map @$_, @$to_bridge ], - )}; + push @new_surfaces, map $group->[0]->clone(expolygon => $_), + @{diff_ex( + [ map $_->p, @$group ], + [ map @$_, @$to_bridge ], + )}; push @new_surfaces, map Slic3r::Surface->new( expolygon => $_, surface_type => S_TYPE_INTERNALVOID, @@ -669,8 +667,7 @@ sub discover_horizontal_shells { [ map @$_, @$internal_solid, @$internal ], 1, ); - push @$neighbor_fill_surfaces, Slic3r::Surface->new - (expolygon => $_, surface_type => $s->[0]->surface_type, bridge_angle => $s->[0]->bridge_angle) + push @$neighbor_fill_surfaces, $s->[0]->clone(expolygon => $_) for @$solid_surfaces; } } diff --git a/lib/Slic3r/Surface.pm b/lib/Slic3r/Surface.pm index 966dcde4..b18d2e46 100644 --- a/lib/Slic3r/Surface.pm +++ b/lib/Slic3r/Surface.pm @@ -34,6 +34,17 @@ sub new { $self; } +sub clone { + my $self = shift; + my %p = @_; + + return (ref $self)->new( + (map { $_ => $self->$_ } qw(surface_type thickness thickness_layers bridge_angle)), + expolygon => ($p{expolygon} ? delete $p{expolygon} : $self->expolygon->clone), + %p, + ); +} + sub expolygon { $_[0][S_EXPOLYGON] } sub surface_type { $_[0][S_SURFACE_TYPE] = $_[1] if defined $_[1]; $_[0][S_SURFACE_TYPE] } sub thickness { $_[0][S_THICKNESS] } @@ -85,22 +96,12 @@ sub group { sub offset { my $self = shift; - return map $self->_inflate_expolygon($_), $self->expolygon->offset_ex(@_); + return map $self->clone(expolygon => $_), $self->expolygon->offset_ex(@_); } sub simplify { my $self = shift; - return map $self->_inflate_expolygon($_), $self->expolygon->simplify(@_); -} - -sub _inflate_expolygon { - my $self = shift; - my ($expolygon) = @_; - - return (ref $self)->new( - expolygon => $expolygon, - map { $_ => $self->$_ } qw(surface_type thickness thickness_layers bridge_angle), - ); + return map $self->clone(expolygon => $_), $self->expolygon->simplify(@_); } sub p { From 5beb2187d3f9231f4c5cc20d8821a0f3b199c244 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 29 Mar 2013 19:24:08 +0100 Subject: [PATCH 013/179] Bugfix: infill direction wasn't correctly alternated when infilling every 2 layers. #1068 --- lib/Slic3r/Surface.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Surface.pm b/lib/Slic3r/Surface.pm index b18d2e46..06c887de 100644 --- a/lib/Slic3r/Surface.pm +++ b/lib/Slic3r/Surface.pm @@ -28,7 +28,7 @@ sub new { my $self = [ map delete $args{$_}, qw(expolygon surface_type thickness thickness_layers bridge_angle extra_perimeters), ]; - $self->[S_THICKNESS_LAYERS] = 1; + $self->[S_THICKNESS_LAYERS] //= 1; bless $self, $class; $self; From d4119b0eb1e5d8d79ba9f781850466bf360f9e21 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 29 Mar 2013 23:49:58 +0100 Subject: [PATCH 014/179] Better bridge direction detection. #1068 --- lib/Slic3r/Layer/Region.pm | 62 ++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 1f1b8df2..c13b3276 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -1,8 +1,9 @@ package Slic3r::Layer::Region; use Moo; +use List::Util qw(sum first); use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Geometry qw(PI scale chained_path_items points_coincide); +use Slic3r::Geometry qw(PI X1 X2 Y1 Y2 A B scale chained_path_items points_coincide); use Slic3r::Geometry::Clipper qw(safety_offset union_ex diff_ex intersection_ex); use Slic3r::Surface ':types'; @@ -556,9 +557,9 @@ sub process_external_surfaces { if (0) { require "Slic3r/SVG.pm"; - Slic3r::SVG::output("bridge.svg", - polygons => [ $surface->p ], - red_polygons => [ map @$_, @lower ], + Slic3r::SVG::output("bridge_$surface.svg", + expolygons => [ $surface->expolygon ], + red_expolygons => [ @lower ], polylines => [ @edges ], ); } @@ -579,16 +580,51 @@ sub process_external_surfaces { $bridge_angle = Slic3r::Geometry::rad2deg_dir($line->direction); } } elsif (@edges) { - my $center = Slic3r::Geometry::bounding_box_center([ map @$_, @edges ]); - my $x = my $y = 0; - foreach my $point (map @$_, @edges) { - my $line = Slic3r::Line->new($center, $point); - my $dir = $line->direction; - my $len = $line->length; - $x += cos($dir) * $len; - $y += sin($dir) * $len; + # inset the bridge expolygon; we'll use this one to clip our test lines + my $inset = [ $surface->expolygon->offset_ex($self->infill_flow->scaled_width) ]; + + # detect anchors as intersection between our bridge expolygon and the lower slices + my $anchors = intersection_ex( + [ $surface->p ], + [ map @$_, @lower ], + ); + + # we'll now try several directions using a rudimentary visibility check: + # bridge in several directions and then sum the length of lines having both + # endpoints within anchors + my %directions = (); # angle => score + my $angle_increment = PI/36; # 5° + my $line_increment = $self->infill_flow->scaled_width; + for (my $angle = 0; $angle <= PI; $angle += $angle_increment) { + # rotate everything - the center point doesn't matter + $_->rotate($angle, [0,0]) for @$inset, @$anchors; + + # generate lines in this direction + my $bounding_box = [ Slic3r::Geometry::bounding_box([ map @$_, map @$_, @$anchors ]) ]; + my @lines = (); + for (my $x = $bounding_box->[X1]; $x <= $bounding_box->[X2]; $x += $line_increment) { + push @lines, [ [$x, $bounding_box->[Y1]], [$x, $bounding_box->[Y2]] ]; + } + + # TODO: use a multi_polygon_multi_linestring_intersection() call + my @clipped_lines = map @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection($_, \@lines) }, @$inset; + + # remove any line not having both endpoints within anchors + @clipped_lines = grep { + my $line = $_; + !(first { $_->encloses_point_quick($line->[A]) } @$anchors) + && !(first { $_->encloses_point_quick($line->[B]) } @$anchors); + } @clipped_lines; + + # sum length of bridged lines + $directions{-$angle} = sum(map Slic3r::Geometry::line_length($_), @clipped_lines) // 0; } - $bridge_angle = Slic3r::Geometry::rad2deg_dir(atan2($y, $x)); + + # this could be slightly optimized with a max search instead of the sort + my @sorted_directions = sort { $directions{$a} <=> $directions{$b} } keys %directions; + + # the best direction is the one causing most lines to be bridged + $bridge_angle = Slic3r::Geometry::rad2deg_dir($sorted_directions[-1]); } Slic3r::debugf " Optimal infill angle of bridge on layer %d is %d degrees\n", From f5e48a306d58ac17212a200287cbd2f049eb45ea Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 30 Mar 2013 00:21:09 +0100 Subject: [PATCH 015/179] Bugfix: support material and brim didn't work correctly. Includes unit test. #1074 --- MANIFEST | 1 + lib/Slic3r/Polyline.pm | 9 +++++---- t/support.t | 21 +++++++++++++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 t/support.t diff --git a/MANIFEST b/MANIFEST index 4081339c..6e59ce2e 100644 --- a/MANIFEST +++ b/MANIFEST @@ -72,6 +72,7 @@ t/retraction.t t/serialize.t t/shells.t t/slice.t +t/support.t t/vibrationlimit.t utils/amf-to-stl.pl utils/file_info.pl diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm index 4d7fbe90..1df68e80 100644 --- a/lib/Slic3r/Polyline.pm +++ b/lib/Slic3r/Polyline.pm @@ -86,10 +86,11 @@ sub length { sub grow { my $self = shift; - return Slic3r::Geometry::Clipper::offset( - [ Slic3r::Polygon->new(@$self, CORE::reverse @$self[1..($#$self-1)]) ], - @_, - ); + return map Slic3r::Polygon->new($_), + Slic3r::Geometry::Clipper::offset( + [ Slic3r::Polygon->new(@$self, CORE::reverse @$self[1..($#$self-1)]) ], + @_, + ); } sub nearest_point_to { diff --git a/t/support.t b/t/support.t new file mode 100644 index 00000000..1e9a24a6 --- /dev/null +++ b/t/support.t @@ -0,0 +1,21 @@ +use Test::More tests => 1; +use strict; +use warnings; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; +} + +use Slic3r; +use Slic3r::Test; + +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('raft_layers', 3); + $config->set('brim_width', 6); + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + ok Slic3r::Test::gcode($print), 'no conflict between raft/support and brim'; +} + +__END__ From 7b7c9f6330a49f8fa6a090d3782b67dd2250a189 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 30 Mar 2013 00:30:21 +0100 Subject: [PATCH 016/179] Launch even if config is corrupted. #1067 --- lib/Slic3r/GUI/Tab.pm | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 8fdacf6f..9a956ce8 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -197,16 +197,21 @@ sub on_select_preset { $self->{config}->set($opt_key, $preset_config->get($opt_key)) if $preset_config->has($opt_key); } + ($preset->{default} || $preset->{external}) + ? $self->{btn_delete_preset}->Disable + : $self->{btn_delete_preset}->Enable; + + $self->on_preset_loaded; + $self->reload_values; + $self->set_dirty(0); + $Slic3r::GUI::Settings->{presets}{$self->name} = $preset->{file} ? basename($preset->{file}) : ''; }; - Slic3r::GUI::catch_error($self); - ($preset->{default} || $preset->{external}) - ? $self->{btn_delete_preset}->Disable - : $self->{btn_delete_preset}->Enable; + if ($@) { + $@ = "I was unable to load the selected config file: $@"; + Slic3r::GUI::catch_error($self); + $self->select_default_preset; + } - $self->on_preset_loaded; - $self->reload_values; - $self->set_dirty(0); - $Slic3r::GUI::Settings->{presets}{$self->name} = $preset->{file} ? basename($preset->{file}) : ''; Slic3r::GUI->save_settings; } From ac2356f66a02a557e8c4a1dc97f87de039e529b6 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 30 Mar 2013 00:36:14 +0100 Subject: [PATCH 017/179] Don't crash at the end of empty prints. #1042 --- lib/Slic3r/Print.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 6df75f2f..e19ec273 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -1021,7 +1021,7 @@ sub write_gcode { $self->total_extrusion_length($gcodegen->total_extrusion_length); # write end commands to file - print $fh $gcodegen->retract; + print $fh $gcodegen->retract if $gcodegen->extruder; # empty prints don't even set an extruder print $fh $gcodegen->set_fan(0); printf $fh "%s\n", $Slic3r::Config->replace_options($Slic3r::Config->end_gcode); From 33a4ec660d51162b1f7bd25cbe346dfc606a92f1 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 30 Mar 2013 00:47:13 +0100 Subject: [PATCH 018/179] better handling of too-small and too-short models #929. These changes handle small models better in the plater preview. Extremely small thumbnails are preserved instead of culled, allowing the model to be scaled up. The scale range is extended to 100000% to allow mm-scale objects expressed in meters to be opened and scaled to mm. Original coding by mesheldrake --- lib/Slic3r/GUI/Plater.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index ba527324..bd1fa296 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -437,7 +437,7 @@ sub changescale { my ($obj_idx, $object) = $self->selected_object; # max scale factor should be above 2540 to allow importing files exported in inches - my $scale = Wx::GetNumberFromUser("", "Enter the scale % for the selected object:", "Scale", $object->scale*100, 0, 5000, $self); + my $scale = Wx::GetNumberFromUser("", "Enter the scale % for the selected object:", "Scale", $object->scale*100, 0, 100000, $self); return if !$scale || $scale == -1; $self->{list}->SetItem($obj_idx, 2, "$scale%"); @@ -861,7 +861,7 @@ sub repaint { @{$parent->{object_previews}} = (); for my $obj_idx (0 .. $#{$parent->{objects}}) { my $object = $parent->{objects}[$obj_idx]; - next unless $object->thumbnail; + next unless $object->thumbnail && @{$object->thumbnail->expolygons}; for my $instance_idx (0 .. $#{$object->instances}) { my $instance = $object->instances->[$instance_idx]; push @{$parent->{object_previews}}, [ $obj_idx, $instance_idx, $object->thumbnail->clone ]; From 7f23e72a1067e647f19f60ba1e8a0fc013ed515a Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 30 Mar 2013 00:52:45 +0100 Subject: [PATCH 019/179] Display preview for small files too. #929 --- lib/Slic3r/GUI/Plater.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index bd1fa296..331aecaa 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1137,7 +1137,8 @@ sub make_thumbnail { for (map @$_, map @$_, @{$thumbnail->expolygons}) { @$_ = map $_ * $self->thumbnail_scaling_factor, @$_; } - @{$thumbnail->expolygons} = map $_->simplify(0.5), grep $_->area >= 1, @{$thumbnail->expolygons}; + # only simplify expolygons larger than the threshold + @{$thumbnail->expolygons} = map { ($_->area >= 1) ? $_->simplify(0.5) : $_ } @{$thumbnail->expolygons}; foreach my $expolygon (@{$thumbnail->expolygons}) { $expolygon->rotate(Slic3r::Geometry::deg2rad($self->rotate)); $expolygon->scale($self->scale); From 04d2231901e0068e7979cdcddbec637e4bab74af Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 30 Mar 2013 11:22:12 +0100 Subject: [PATCH 020/179] Bugfix: is_printable() wasn't discarding narrow ring-shaped top/bottom surfaces because it was only considering the contour. This caused extra shell material even in hollow prints. #1049 --- lib/Slic3r/ExPolygon.pm | 11 +++++++++++ lib/Slic3r/ExtrusionPath.pm | 2 -- lib/Slic3r/Polygon.pm | 2 +- lib/Slic3r/Print/Object.pm | 4 ++-- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index f3742d3c..ac1d0fb3 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -62,6 +62,17 @@ sub is_valid { && (!first { $_->is_counter_clockwise } $self->holes); } +# returns false if the expolygon is too tight to be printed +sub is_printable { + my $self = shift; + my ($width) = @_; + + # try to get an inwards offset + # for a distance equal to half of the extrusion width; + # if no offset is possible, then expolygon is not printable. + return Slic3r::Geometry::Clipper::offset($self, -$width / 2) ? 1 : 0; +} + sub boost_polygon { my $self = shift; return Boost::Geometry::Utils::polygon(@$self); diff --git a/lib/Slic3r/ExtrusionPath.pm b/lib/Slic3r/ExtrusionPath.pm index a150a9c7..87f6d3a1 100644 --- a/lib/Slic3r/ExtrusionPath.pm +++ b/lib/Slic3r/ExtrusionPath.pm @@ -97,8 +97,6 @@ sub first_point { return $self->polyline->[0]; } -sub is_printable { 1 } - sub is_perimeter { my $self = shift; return $self->role == EXTR_ROLE_PERIMETER diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm index a89cc079..b3cf77d0 100644 --- a/lib/Slic3r/Polygon.pm +++ b/lib/Slic3r/Polygon.pm @@ -128,7 +128,7 @@ sub subdivide { } } -# returns false if the polyline is too tight to be printed +# returns false if the polygon is too tight to be printed sub is_printable { my $self = shift; my ($width) = @_; diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index a1193217..7968ac61 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -360,8 +360,8 @@ sub detect_surfaces_type { [ map @$_, @$clip_surfaces ], 1, ); - return grep $_->contour->is_printable($layerm->perimeter_flow->scaled_width), - map Slic3r::Surface->new(expolygon => $_, surface_type => $result_type), + return map Slic3r::Surface->new(expolygon => $_, surface_type => $result_type), + grep $_->is_printable($layerm->perimeter_flow->scaled_width), @$expolygons; }; From 01e86c26159c5ff0570613831b82f638daf74450 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 30 Mar 2013 15:59:17 +0100 Subject: [PATCH 021/179] Releasing 0.9.9 --- lib/Slic3r.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index c5bb81bf..064c85fe 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -7,7 +7,7 @@ use strict; use warnings; require v5.10; -our $VERSION = "0.9.9-dev"; +our $VERSION = "0.9.9"; our $debug = 0; sub debugf { From 0e6d80d33c2dd05a76368d0ead8c729a440d2a64 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 31 Mar 2013 11:24:33 +0200 Subject: [PATCH 022/179] Bump version number --- lib/Slic3r.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 064c85fe..478165bc 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -7,7 +7,7 @@ use strict; use warnings; require v5.10; -our $VERSION = "0.9.9"; +our $VERSION = "0.9.10-dev"; our $debug = 0; sub debugf { From 1187f4846da7b8cc94de744a2e5af28011cc59c8 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 31 Mar 2013 19:40:25 +0200 Subject: [PATCH 023/179] Some Clipper optimizations --- lib/Slic3r/GUI/Plater.pm | 2 +- lib/Slic3r/Geometry/Clipper.pm | 34 +++++++++++++++++++++++----------- lib/Slic3r/Layer/Region.pm | 16 ++++------------ 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 331aecaa..be001cae 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -5,9 +5,9 @@ use utf8; use File::Basename qw(basename dirname); use List::Util qw(max sum first); +use Math::Clipper qw(offset JT_ROUND); use Math::ConvexHull::MonotoneChain qw(convex_hull); use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX); -use Slic3r::Geometry::Clipper qw(offset JT_ROUND); use threads::shared qw(shared_clone); use Wx qw(:bitmap :brush :button :cursor :dialog :filedialog :font :keycode :icon :id :listctrl :misc :panel :pen :sizer :toolbar :window); use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED EVT_LIST_ITEM_DESELECTED EVT_LIST_ITEM_SELECTED EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL EVT_CHOICE); diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index 68e3d7c0..bef2713d 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -14,12 +14,13 @@ our $clipper = Math::Clipper->new; sub safety_offset { my ($polygons, $factor) = @_; - return Math::Clipper::offset($polygons, $factor // (scale 1e-05), 100000, JT_MITER, 2); + return Math::Clipper::int_offset($polygons, $factor // (scale 1e-05), 100000, JT_MITER, 2); } sub safety_offset_ex { - # offset polygons and then apply holes to the right contours - return @{ union_ex([ safety_offset(@_) ]) }; + my ($polygons, $factor) = @_; + return map Slic3r::ExPolygon->new($_), + @{Math::Clipper::ex_int_offset($polygons, $factor // (scale 1e-05), 100000, JT_MITER, 2)}; } sub offset { @@ -28,13 +29,18 @@ sub offset { $joinType //= JT_MITER; $miterLimit //= 3; - my $offsets = Math::Clipper::offset($polygons, $distance, $scale, $joinType, $miterLimit); + my $offsets = Math::Clipper::int_offset($polygons, $distance, $scale, $joinType, $miterLimit); return @$offsets; } sub offset_ex { - # offset polygons and then apply holes to the right contours - return @{ union_ex([ offset(@_) ]) }; + my ($polygons, $distance, $scale, $joinType, $miterLimit) = @_; + $scale ||= 100000; + $joinType //= JT_MITER; + $miterLimit //= 3; + + my $offsets = Math::Clipper::ex_int_offset($polygons, $distance, $scale, $joinType, $miterLimit); + return map Slic3r::ExPolygon->new($_), @$offsets; } sub diff_ex { @@ -96,13 +102,19 @@ sub xor_ex { ]; } +sub ex_int_offset2 { + my ($polygons, $delta1, $delta2, $scale, $joinType, $miterLimit) = @_; + $scale ||= 100000; + $joinType //= JT_MITER; + $miterLimit //= 3; + + my $offsets = Math::Clipper::ex_int_offset2($polygons, $delta1, $delta2, $scale, $joinType, $miterLimit); + return map Slic3r::ExPolygon->new($_), @$offsets; +} + sub collapse_ex { my ($polygons, $width) = @_; - my @result = offset( - [ offset($polygons, -$width/2,) ], - +$width/2, - ); - return union_ex([@result]); + return ex_int_offset2($polygons, -$width/2, +$width/2); } sub simplify_polygon { diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index c13b3276..e4d8ee47 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -96,12 +96,9 @@ sub make_surfaces { # detect thin walls by offsetting slices by half extrusion inwards { my $width = $self->perimeter_flow->scaled_width; - my $outgrown = union_ex([ - Slic3r::Geometry::Clipper::offset( - [Slic3r::Geometry::Clipper::offset([ map @$_, map $_->expolygon, @{$self->slices} ], -$width)], - +$width, - ), - ]); + my $outgrown = [ + Slic3r::Geometry::Clipper::ex_int_offset2([ map @$_, map $_->expolygon, @{$self->slices} ], -$width, +$width), + ]; my $diff = diff_ex( [ map $_->p, @{$self->slices} ], [ map @$_, @$outgrown ], @@ -230,12 +227,7 @@ sub make_perimeters { # offsetting a polygon can result in one or many offset polygons my @new_offsets = (); foreach my $expolygon (@last_offsets) { - my @offsets = @{union_ex([ - Slic3r::Geometry::Clipper::offset( - [Slic3r::Geometry::Clipper::offset($expolygon, -1.5*$spacing)], - +0.5*$spacing, - ), - ])}; + my @offsets = Slic3r::Geometry::Clipper::ex_int_offset2($expolygon, -1.5*$spacing, +0.5*$spacing); push @new_offsets, @offsets; # where the above check collapses the expolygon, then there's no room for an inner loop From bf809d1fd041bae6f4eca12bb6c85fb428cdbc95 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 31 Mar 2013 19:50:22 +0200 Subject: [PATCH 024/179] Use linestring_length() from Boost --- lib/Slic3r/ExPolygon.pm | 4 ++-- lib/Slic3r/Line.pm | 2 +- lib/Slic3r/Polyline.pm | 4 +--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index ac1d0fb3..2fa46fe4 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -7,7 +7,7 @@ use warnings; use Boost::Geometry::Utils; use List::Util qw(first); use Math::Geometry::Voronoi; -use Slic3r::Geometry qw(X Y A B point_in_polygon same_line line_length epsilon); +use Slic3r::Geometry qw(X Y A B point_in_polygon same_line epsilon); use Slic3r::Geometry::Clipper qw(union_ex JT_MITER); # the constructor accepts an array of polygons @@ -131,7 +131,7 @@ sub encloses_line { # optimization return @$clip == 1 && same_line($clip->[0], $line); } else { - return @$clip == 1 && abs(line_length($clip->[0]) - $line->length) < $tolerance; + return @$clip == 1 && abs(Boost::Geometry::Utils::linestring_length($clip->[0]) - $line->length) < $tolerance; } } diff --git a/lib/Slic3r/Line.pm b/lib/Slic3r/Line.pm index d0d0a3da..81ca1d48 100644 --- a/lib/Slic3r/Line.pm +++ b/lib/Slic3r/Line.pm @@ -29,7 +29,7 @@ sub coincides_with { sub length { my $self = shift; - return Slic3r::Geometry::line_length($self); + return Boost::Geometry::Utils::linestring_length($self); } sub vector { diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm index 12fe0cb9..5883cf00 100644 --- a/lib/Slic3r/Polyline.pm +++ b/lib/Slic3r/Polyline.pm @@ -79,9 +79,7 @@ sub reverse { sub length { my $self = shift; - my $length = 0; - $length += $_->length for $self->lines; - return $length; + return Boost::Geometry::Utils::linestring_length($self); } sub grow { From e602aad998a6f77487b0eec4a5d87031c5d01f4a Mon Sep 17 00:00:00 2001 From: Devin Grady Date: Mon, 1 Apr 2013 22:12:51 -0500 Subject: [PATCH 025/179] inspired by f5e48a3, a seems to work to fix 1076 --- lib/Slic3r/Print.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 2cf12bf6..a0ac953b 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -151,7 +151,9 @@ sub validate { { my @points = map [ @$_[X,Y] ], map @{$_->vertices}, @{$self->objects->[$obj_idx]->meshes}; my $convex_hull = Slic3r::Polygon->new(convex_hull(\@points)); - ($clearance) = offset([$convex_hull], scale $Slic3r::Config->extruder_clearance_radius / 2, 1, JT_ROUND); + ($clearance) = map Slic3r::Polygon->new($_), + Slic3r::Geometry::Clipper::offset( + [$convex_hull], scale $Slic3r::Config->extruder_clearance_radius / 2, 1, JT_ROUND); } for my $copy (@{$self->objects->[$obj_idx]->copies}) { my $copy_clearance = $clearance->clone; From d089d2b2d4064425280d791939568b3ab2d91eac Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 3 Apr 2013 19:06:33 +0200 Subject: [PATCH 026/179] Bugfix: crash introduced by wipe --- lib/Slic3r/GCode.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 92190cf7..6728b5fd 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -385,11 +385,11 @@ sub retract { my $travel = [undef, $params{move_z}, $retract->[2], "change layer and $comment"]; $gcode .= $self->G0(@$travel); } else { - if ($wipe_path) { + # check that we have a positive wipe length + if ($wipe_path && (my $total_wipe_length = $wipe_path->length)) { $self->speed('travel'); - # subdivide the retraction - my $total_wipe_length = $wipe_path->length; + # subdivide the retraction for (1 .. $#$wipe_path) { my $segment_length = $wipe_path->[$_-1]->distance_to($wipe_path->[$_]); $gcode .= $self->G1($wipe_path->[$_], undef, $retract->[2] * ($segment_length / $total_wipe_length), $retract->[3] . ";_WIPE"); From 88e70a59c72d0c06ccaaf0b45499249b12d27890 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 3 Apr 2013 19:08:12 +0200 Subject: [PATCH 027/179] Don't wipe if option is disabled --- lib/Slic3r/GCode.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 6728b5fd..72744eba 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -248,7 +248,7 @@ sub extrude_path { $gcode .= $self->G1($line->[B], undef, $e * $line_length, $description); } $self->wipe_path(Slic3r::Polyline->new([ reverse @{$path->points} ])) - if $Slic3r::Config->wipe; + if $self->extruder->wipe; } if ($Slic3r::Config->cooling) { @@ -358,7 +358,7 @@ sub retract { # wipe my $wipe_path; - if ($Slic3r::Config->wipe && $self->wipe_path) { + if ($self->extruder->wipe && $self->wipe_path) { $wipe_path = Slic3r::Polyline->new([ $self->last_pos, @{$self->wipe_path}[1..$#{$self->wipe_path}] ]) ->clip_start($self->extruder->scaled_wipe_distance); } From b725847a5131f5dc7e82a7eeda1783e9287580ea Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 3 Apr 2013 19:26:59 +0200 Subject: [PATCH 028/179] Bugfix: configuration wizard led to crash with simple mode. #1077 --- lib/Slic3r/GUI/SkeinPanel.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index ef740335..1bcaa4d9 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -243,7 +243,9 @@ sub config_wizard { return unless $self->check_unsaved_changes; if (my $config = Slic3r::GUI::ConfigWizard->new($self)->run) { - $_->select_default_preset for values %{$self->{options_tabs}}; + if ($self->{mode} eq 'expert') { + $_->select_default_preset for values %{$self->{options_tabs}}; + } $self->load_config($config); } } From 3afeb5c7b5896db30f0f673bbf228cedf5fe3110 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 4 Apr 2013 00:52:11 +0200 Subject: [PATCH 029/179] Second layer commands were written multiple times --- lib/Slic3r/Print.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 2cf12bf6..bbccf7ed 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -794,6 +794,7 @@ sub write_gcode { } $gcode .= $gcodegen->set_bed_temperature($Slic3r::Config->bed_temperature) if $Slic3r::Config->bed_temperature && $Slic3r::Config->bed_temperature != $Slic3r::Config->first_layer_bed_temperature; + $second_layer_things_done = 1; } # set new layer, but don't move Z as support material contact areas may need an intermediate one From b4be61b703a33c1b3f579c143a520680caaaed38 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 4 Apr 2013 01:17:44 +0200 Subject: [PATCH 030/179] Bugfix: time estimates for cooling were computed for each object separately instead of the whole actual layer. #1071 --- MANIFEST | 1 + lib/Slic3r.pm | 1 + lib/Slic3r/GCode/CoolingBuffer.pm | 77 +++++++++++++++++++++++++++++++ lib/Slic3r/Print.pm | 57 ++++++----------------- 4 files changed, 93 insertions(+), 43 deletions(-) create mode 100644 lib/Slic3r/GCode/CoolingBuffer.pm diff --git a/MANIFEST b/MANIFEST index 6e59ce2e..e7498665 100644 --- a/MANIFEST +++ b/MANIFEST @@ -24,6 +24,7 @@ lib/Slic3r/Format/AMF/Parser.pm lib/Slic3r/Format/OBJ.pm lib/Slic3r/Format/STL.pm lib/Slic3r/GCode.pm +lib/Slic3r/GCode/CoolingBuffer.pm lib/Slic3r/GCode/MotionPlanner.pm lib/Slic3r/Geometry.pm lib/Slic3r/Geometry/Clipper.pm diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 478165bc..eddc2939 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -45,6 +45,7 @@ use Slic3r::Format::AMF; use Slic3r::Format::OBJ; use Slic3r::Format::STL; use Slic3r::GCode; +use Slic3r::GCode::CoolingBuffer; use Slic3r::GCode::MotionPlanner; use Slic3r::Geometry qw(PI); use Slic3r::Layer; diff --git a/lib/Slic3r/GCode/CoolingBuffer.pm b/lib/Slic3r/GCode/CoolingBuffer.pm new file mode 100644 index 00000000..d88e6c2e --- /dev/null +++ b/lib/Slic3r/GCode/CoolingBuffer.pm @@ -0,0 +1,77 @@ +package Slic3r::GCode::CoolingBuffer; +use Moo; + +has 'config' => (is => 'ro', required => 1); +has 'gcodegen' => (is => 'ro', required => 1); +has 'gcode' => (is => 'rw', default => sub {""}); +has 'layer_id' => (is => 'rw'); +has 'last_z' => (is => 'rw'); +has 'min_print_speed' => (is => 'lazy'); + +sub _build_min_print_speed { + my $self = shift; + return 60 * $self->config->min_print_speed; +} + +sub append { + my $self = shift; + my ($gcode, $layer) = @_; + + my $return = ""; + if (defined $self->last_z && $self->last_z != $layer->print_z) { + $return = $self->flush; + $self->gcodegen->elapsed_time(0); + } + + $self->layer_id($layer->id); + $self->last_z($layer->print_z); + $self->gcode($self->gcode . $gcode); + + return $return; +} + +sub flush { + my $self = shift; + + my $gcode = $self->gcode; + $self->gcode(""); + + my $fan_speed = $self->config->fan_always_on ? $self->config->min_fan_speed : 0; + my $speed_factor = 1; + if ($self->config->cooling) { + my $layer_time = $self->gcodegen->elapsed_time; + Slic3r::debugf "Layer %d estimated printing time: %d seconds\n", $self->layer_id, $layer_time; + if ($layer_time < $self->config->slowdown_below_layer_time) { + $fan_speed = $self->config->max_fan_speed; + $speed_factor = $layer_time / $self->config->slowdown_below_layer_time; + } elsif ($layer_time < $self->config->fan_below_layer_time) { + $fan_speed = $self->config->max_fan_speed - ($self->config->max_fan_speed - $self->config->min_fan_speed) + * ($layer_time - $self->config->slowdown_below_layer_time) + / ($self->config->fan_below_layer_time - $self->config->slowdown_below_layer_time); #/ + } + Slic3r::debugf " fan = %d%%, speed = %d%%\n", $fan_speed, $speed_factor * 100; + + if ($speed_factor < 1) { + my $dec = $self->gcodegen->dec; + $gcode =~ s/^(?=.*? [XY])(?=.*? E)(?!;_WIPE)(?min_print_speed ? $self->min_print_speed : $new_speed) + /gexm; + } + $fan_speed = 0 if $self->layer_id < $self->config->disable_fan_first_layers; + } + $gcode = $self->gcodegen->set_fan($fan_speed) . $gcode; + + # bridge fan speed + if (!$self->config->cooling || $self->config->bridge_fan_speed == 0 || $self->layer_id < $self->config->disable_fan_first_layers) { + $gcode =~ s/^;_BRIDGE_FAN_(?:START|END)\n//gm; + } else { + $gcode =~ s/^;_BRIDGE_FAN_START\n/ $self->gcodegen->set_fan($self->config->bridge_fan_speed, 1) /gmex; + $gcode =~ s/^;_BRIDGE_FAN_END\n/ $self->gcodegen->set_fan($fan_speed, 1) /gmex; + } + $gcode =~ s/;_WIPE//g; + + return $gcode; +} + +1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 64747ca6..189b665b 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -716,8 +716,6 @@ sub write_gcode { multiple_extruders => (@{$self->extruders} > 1), layer_count => $self->layer_count, ); - my $min_print_speed = 60 * $Slic3r::Config->min_print_speed; - my $dec = $gcodegen->dec; print $fh "G21 ; set units to millimeters\n"; print $fh $gcodegen->set_fan(0, 1) if $Slic3r::Config->cooling && $Slic3r::Config->disable_fan_first_layers; @@ -801,7 +799,6 @@ sub write_gcode { # set new layer, but don't move Z as support material contact areas may need an intermediate one $gcode .= $gcodegen->change_layer($layer); - $gcodegen->elapsed_time(0); # prepare callback to call as soon as a Z command is generated $gcodegen->move_z_callback(sub { @@ -943,42 +940,6 @@ sub write_gcode { } } } - return if !$gcode; - - my $fan_speed = $Slic3r::Config->fan_always_on ? $Slic3r::Config->min_fan_speed : 0; - my $speed_factor = 1; - if ($Slic3r::Config->cooling) { - my $layer_time = $gcodegen->elapsed_time; - Slic3r::debugf "Layer %d estimated printing time: %d seconds\n", $layer->id, $layer_time; - if ($layer_time < $Slic3r::Config->slowdown_below_layer_time) { - $fan_speed = $Slic3r::Config->max_fan_speed; - $speed_factor = $layer_time / $Slic3r::Config->slowdown_below_layer_time; - } elsif ($layer_time < $Slic3r::Config->fan_below_layer_time) { - $fan_speed = $Slic3r::Config->max_fan_speed - ($Slic3r::Config->max_fan_speed - $Slic3r::Config->min_fan_speed) - * ($layer_time - $Slic3r::Config->slowdown_below_layer_time) - / ($Slic3r::Config->fan_below_layer_time - $Slic3r::Config->slowdown_below_layer_time); #/ - } - Slic3r::debugf " fan = %d%%, speed = %d%%\n", $fan_speed, $speed_factor * 100; - - if ($speed_factor < 1) { - $gcode =~ s/^(?=.*? [XY])(?=.*? E)(?!;_WIPE)(?id < $Slic3r::Config->disable_fan_first_layers; - } - $gcode = $gcodegen->set_fan($fan_speed) . $gcode; - - # bridge fan speed - if (!$Slic3r::Config->cooling || $Slic3r::Config->bridge_fan_speed == 0 || $layer->id < $Slic3r::Config->disable_fan_first_layers) { - $gcode =~ s/^;_BRIDGE_FAN_(?:START|END)\n//gm; - } else { - $gcode =~ s/^;_BRIDGE_FAN_START\n/ $gcodegen->set_fan($Slic3r::Config->bridge_fan_speed, 1) /gmex; - $gcode =~ s/^;_BRIDGE_FAN_END\n/ $gcodegen->set_fan($fan_speed, 1) /gmex; - } - $gcode =~ s/;_WIPE//g; - return $gcode; }; @@ -1001,6 +962,11 @@ sub write_gcode { print $fh $gcodegen->G0(Slic3r::Point->new(0,0), undef, 0, 'move to origin position for next object'); } + my $buffer = Slic3r::GCode::CoolingBuffer->new( + config => $Slic3r::Config, + gcodegen => $gcodegen, + ); + for my $layer (@{$self->objects->[$obj_idx]->layers}) { # if we are printing the bottom layer of an object, and we have already finished # another one, set first layer temperatures. this happens before the Z move @@ -1010,15 +976,20 @@ sub write_gcode { if $Slic3r::Config->first_layer_bed_temperature; $print_first_layer_temperature->(); } - print $fh $extrude_layer->($layer, [$copy]); + print $fh $buffer->append($extrude_layer->($layer, [$copy]), $layer); } + print $fh $buffer->flush; $finished_objects++; } } } else { - print $fh $extrude_layer->($_, $_->object->copies) - for sort { $a->print_z <=> $b->print_z } - map @{$_->layers}, @{$self->objects}; + my $buffer = Slic3r::GCode::CoolingBuffer->new( + config => $Slic3r::Config, + gcodegen => $gcodegen, + ); + print $fh $buffer->append($extrude_layer->($_, $_->object->copies), $_) + for sort { $a->print_z <=> $b->print_z } map @{$_->layers}, @{$self->objects}; + print $fh $buffer->flush; } # save statistic data From 7e51cbcf69f58da1a3c8ca9addbcf7f6c5b2e764 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 4 Apr 2013 01:24:40 +0200 Subject: [PATCH 031/179] Allow 0.1mm slots. #959 #1056 --- lib/Slic3r/Layer/Region.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index c13b3276..f04b3713 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -139,7 +139,7 @@ sub _merge_loops { # winding order. # TODO: find a faster algorithm for this. my @loops = sort { $a->encloses_point($b->[0]) ? 0 : 1 } @$loops; # outer first - $safety_offset //= scale 0.1; + $safety_offset //= scale 0.0499; @loops = @{ safety_offset(\@loops, $safety_offset) }; my $expolygons = []; while (my $loop = shift @loops) { From 060d2da7fe0eaef472da9adcc0aed1871026c86c Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Sun, 7 Apr 2013 18:01:15 -0400 Subject: [PATCH 032/179] Small optimization on an incredibly hot codepath. --- lib/Slic3r/Geometry.pm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index 0d2d9df4..dc30b347 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -116,8 +116,7 @@ sub distance_between_points { } sub comparable_distance_between_points { - my ($p1, $p2) = @_; - return (($p1->[X] - $p2->[X])**2) + (($p1->[Y] - $p2->[Y])**2); + return (($_[0]->[X] - $_[1]->[X])**2) + (($_[0]->[Y] - $_[1]->[Y])**2); } sub point_line_distance { From 3e8c5804fe03296de07054294bccb187f8c39be3 Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Sun, 7 Apr 2013 18:13:40 -0400 Subject: [PATCH 033/179] Inline comparable_distance_between_points It was called on an incredibly hot codepath from a single place. At 12313276 calls on my test .stl, the sub call overhead alone was a significant perf hit. --- lib/Slic3r/Geometry.pm | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index dc30b347..c6744a20 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -7,7 +7,7 @@ our @ISA = qw(Exporter); our @EXPORT_OK = qw( PI X Y Z A B X1 Y1 X2 Y2 MIN MAX epsilon slope line_atan lines_parallel line_point_belongs_to_segment points_coincide distance_between_points - comparable_distance_between_points chained_path_items chained_path_points + chained_path_items chained_path_points line_length midpoint point_in_polygon point_in_segment segment_in_segment point_is_on_left_of_segment polyline_lines polygon_lines nearest_point point_along_segment polygon_segment_having_point polygon_has_subsegment @@ -115,10 +115,6 @@ sub distance_between_points { return sqrt((($p1->[X] - $p2->[X])**2) + ($p1->[Y] - $p2->[Y])**2); } -sub comparable_distance_between_points { - return (($_[0]->[X] - $_[1]->[X])**2) + (($_[0]->[Y] - $_[1]->[Y])**2); -} - sub point_line_distance { my ($point, $line) = @_; return distance_between_points($point, $line->[A]) @@ -248,7 +244,7 @@ sub nearest_point_index { my ($nearest_point_index, $distance) = (); for my $i (0..$#$points) { - my $d = comparable_distance_between_points($point, $points->[$i]); + my $d = (($point->[X] - $points->[$i]->[X])**2) + (($point->[Y] - $points->[$i]->[Y])**2); if (!defined $distance || $d < $distance) { $nearest_point_index = $i; $distance = $d; From da0e67a891c76b1eccd090e01089c52394fdbbab Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Sun, 7 Apr 2013 18:28:08 -0400 Subject: [PATCH 034/179] Only look up $point's X and Y once, rather than once on every pass through the loop. (Those lookups are expensive) --- lib/Slic3r/Geometry.pm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index c6744a20..d21cff33 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -243,8 +243,12 @@ sub nearest_point_index { my ($point, $points) = @_; my ($nearest_point_index, $distance) = (); + + my $point_x = $point->[X]; + my $point_y = $point->[Y]; + for my $i (0..$#$points) { - my $d = (($point->[X] - $points->[$i]->[X])**2) + (($point->[Y] - $points->[$i]->[Y])**2); + my $d = (($point_x - $points->[$i]->[X])**2) + (($point_y - $points->[$i]->[Y])**2); if (!defined $distance || $d < $distance) { $nearest_point_index = $i; $distance = $d; From e8ca1e59a6bd9df0407c151e24f5d3273625a958 Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Sun, 7 Apr 2013 19:44:32 -0400 Subject: [PATCH 035/179] no functional change. only return from one place for clarity --- lib/Slic3r/Geometry.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index d21cff33..e1a59102 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -252,7 +252,7 @@ sub nearest_point_index { if (!defined $distance || $d < $distance) { $nearest_point_index = $i; $distance = $d; - return $i if $distance < epsilon; + last if $distance < epsilon; } } return $nearest_point_index; From 7ec63321413f10f68f389140c7488b2bb7d14fac Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Sun, 7 Apr 2013 19:53:15 -0400 Subject: [PATCH 036/179] split apart the math in nearest_point_index and short-circuit if we know the candidate is no good --- lib/Slic3r/Geometry.pm | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index e1a59102..9f0a631f 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -248,13 +248,22 @@ sub nearest_point_index { my $point_y = $point->[Y]; for my $i (0..$#$points) { - my $d = (($point_x - $points->[$i]->[X])**2) + (($point_y - $points->[$i]->[Y])**2); - if (!defined $distance || $d < $distance) { - $nearest_point_index = $i; - $distance = $d; - last if $distance < epsilon; - } + my $d = ($point_x - $points->[$i]->[X])**2; + # If the X distance of the candidate is > than the total distance of the + # best previous candidate, we know we don't want it + next if (defined $distance && $d > $distance); + + # If the total distance of the candidate is > than the total distance of the + # best previous candidate, we know we don't want it + $d += ($point_y - $points->[$i]->[Y])**2; + next if (defined $distance && $d > $distance); + + $nearest_point_index = $i; + $distance = $d; + + last if $distance < epsilon; } + return $nearest_point_index; } From 94ed6cd2397c495f2d38b57dac3086e2cda8bdce Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 8 Apr 2013 22:23:51 +0200 Subject: [PATCH 037/179] Always move Z at travel speed. #1093 --- lib/Slic3r/GCode.pm | 5 ++++- t/fill.t | 5 +++++ t/retraction.t | 2 ++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 72744eba..d2a667ef 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -364,13 +364,13 @@ sub retract { } # prepare moves - $self->speed('retract'); my $retract = [undef, undef, -$length, $comment]; my $lift = ($self->extruder->retract_lift == 0 || defined $params{move_z}) && !$self->lifted ? undef : [undef, $self->z + $self->extruder->retract_lift, 0, 'lift plate during travel']; if (($Slic3r::Config->g0 || $Slic3r::Config->gcode_flavor eq 'mach3') && $params{travel_to}) { + $self->speed('travel'); if ($lift) { # combine lift and retract $lift->[2] = $retract->[2]; @@ -382,6 +382,7 @@ sub retract { } } elsif (($Slic3r::Config->g0 || $Slic3r::Config->gcode_flavor eq 'mach3') && defined $params{move_z}) { # combine Z change and retraction + $self->speed('travel'); my $travel = [undef, $params{move_z}, $retract->[2], "change layer and $comment"]; $gcode .= $self->G0(@$travel); } else { @@ -395,9 +396,11 @@ sub retract { $gcode .= $self->G1($wipe_path->[$_], undef, $retract->[2] * ($segment_length / $total_wipe_length), $retract->[3] . ";_WIPE"); } } else { + $self->speed('retract'); $gcode .= $self->G1(@$retract); } if (!$self->lifted) { + $self->speed('travel'); if (defined $params{move_z} && $self->extruder->retract_lift > 0) { my $travel = [undef, $params{move_z} + $self->extruder->retract_lift, 0, 'move to next layer (' . $self->layer->id . ') and lift']; $gcode .= $self->G0(@$travel); diff --git a/t/fill.t b/t/fill.t index cbffd7da..7cc1e5d3 100644 --- a/t/fill.t +++ b/t/fill.t @@ -39,6 +39,11 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } $surface->expolygon->rotate(Slic3r::Geometry::deg2rad($angle), [0,0]); my ($params, @paths) = $filler->fill_surface($surface, flow_spacing => 0.69, density => 0.4); is scalar @paths, 1, 'one continuous path'; + use Slic3r::SVG; + Slic3r::SVG::output("fill.svg", + expolygons => [$surface->expolygon], + polylines => [@paths], + );exit; } } diff --git a/t/retraction.t b/t/retraction.t index a23e6f81..1c028893 100644 --- a/t/retraction.t +++ b/t/retraction.t @@ -52,6 +52,7 @@ my $test = sub { if !_eq($info->{dist_Z}, -$print->extruders->[$tool]->retract_lift); $lifted = 0; } + fail 'move Z at travel speed' if ($args->{F} // $self->F) != $conf->travel_speed * 60; } if ($info->{retracting}) { $retracted[$tool] = 1; @@ -89,6 +90,7 @@ my $test = sub { }; $config->set('first_layer_height', $config->layer_height); +$config->set('first_layer_speed', '100%'); $config->set('start_gcode', ''); # to avoid dealing with the nozzle lift in start G-code $config->set('retract_length', [1.5]); $config->set('retract_before_travel', [3]); From 8030eaaa04a98ad1a3d5711a4567b7106d3113db Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 9 Apr 2013 14:02:49 +0200 Subject: [PATCH 038/179] Remove debugging statements --- t/fill.t | 5 ----- 1 file changed, 5 deletions(-) diff --git a/t/fill.t b/t/fill.t index 7cc1e5d3..cbffd7da 100644 --- a/t/fill.t +++ b/t/fill.t @@ -39,11 +39,6 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } $surface->expolygon->rotate(Slic3r::Geometry::deg2rad($angle), [0,0]); my ($params, @paths) = $filler->fill_surface($surface, flow_spacing => 0.69, density => 0.4); is scalar @paths, 1, 'one continuous path'; - use Slic3r::SVG; - Slic3r::SVG::output("fill.svg", - expolygons => [$surface->expolygon], - polylines => [@paths], - );exit; } } From c2aa119c09350d1ea3d3cb1adb017d2dd2eddddd Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 9 Apr 2013 14:03:24 +0200 Subject: [PATCH 039/179] Require Boost::Geometry::Utils 0.08 --- Build.PL | 2 +- lib/Slic3r.pm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Build.PL b/Build.PL index b8921a8c..c44c5084 100644 --- a/Build.PL +++ b/Build.PL @@ -7,7 +7,7 @@ my $build = Module::Build->new( dist_version => '0.1', license => 'perl', requires => { - 'Boost::Geometry::Utils' => '0.06', + 'Boost::Geometry::Utils' => '0.08', 'Encode::Locale' => '0', 'File::Basename' => '0', 'File::Spec' => '0', diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index eddc2939..df04dc96 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -29,7 +29,7 @@ our $var = "$FindBin::Bin/var"; use Encode; use Encode::Locale; -use Boost::Geometry::Utils 0.06; +use Boost::Geometry::Utils 0.08; use Moo 0.091009; use Slic3r::Config; From a0a54ea70697418ae6fb7939490dcf3c29ecd845 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 11 Apr 2013 19:36:49 +0200 Subject: [PATCH 040/179] Align rectilinear and line infill across layers. #712 --- lib/Slic3r/Fill/Rectilinear.pm | 50 ++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm index e1dcab0e..a455b4aa 100644 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ b/lib/Slic3r/Fill/Rectilinear.pm @@ -3,6 +3,9 @@ use Moo; extends 'Slic3r::Fill::Base'; +has 'bounding_box' => (is => 'rw'); +has 'cache' => (is => 'rw', default => sub {{}}); + use Slic3r::Geometry qw(X1 Y1 X2 Y2 A B X Y scale unscale scaled_epsilon); sub fill_surface { @@ -18,37 +21,44 @@ sub fill_surface { return {} if !$expolygon_off; # skip some very small polygons (which shouldn't arrive here) my $bounding_box = [ $expolygon->bounding_box ]; + my $flow_spacing = $params{flow_spacing}; my $min_spacing = scale $params{flow_spacing}; my $distance_between_lines = $min_spacing / $params{density}; my $line_oscillation = $distance_between_lines - $min_spacing; - - my $flow_spacing = $params{flow_spacing}; - if ($params{density} == 1 && !$params{dont_adjust}) { - $distance_between_lines = $self->adjust_solid_spacing( - width => $bounding_box->[X2] - $bounding_box->[X1], - distance => $distance_between_lines, - ); - $flow_spacing = unscale $distance_between_lines; - } - - my $x = $bounding_box->[X1]; my $is_line_pattern = $self->isa('Slic3r::Fill::Line'); - my @vertical_lines = (); - for (my $i = 0; $x <= $bounding_box->[X2] + scaled_epsilon; $i++) { - my $vertical_line = Slic3r::Line->new([$x, $bounding_box->[Y2]], [$x, $bounding_box->[Y1]]); - if ($is_line_pattern && $i % 2) { - $vertical_line->[A][X] += $line_oscillation; - $vertical_line->[B][X] -= $line_oscillation; + + my $cache_id = sprintf "d%s_s%s_a%s", + $params{density}, $params{flow_spacing}, $rotate_vector->[0][0]; + + if (!$self->cache->{$cache_id} || !defined $self->bounding_box) { + if ($params{density} == 1 && !$params{dont_adjust}) { + $distance_between_lines = $self->adjust_solid_spacing( + width => $bounding_box->[X2] - $bounding_box->[X1], + distance => $distance_between_lines, + ); + $flow_spacing = unscale $distance_between_lines; } - push @vertical_lines, $vertical_line; - $x += $distance_between_lines; + + my $x = $bounding_box->[X1]; + my @vertical_lines = (); + for (my $i = 0; $x <= $bounding_box->[X2] + scaled_epsilon; $i++) { + my $vertical_line = Slic3r::Line->new([$x, $bounding_box->[Y2]], [$x, $bounding_box->[Y1]]); + if ($is_line_pattern && $i % 2) { + $vertical_line->[A][X] += $line_oscillation; + $vertical_line->[B][X] -= $line_oscillation; + } + push @vertical_lines, $vertical_line; + $x += $distance_between_lines; + } + + $self->cache->{$cache_id} = [@vertical_lines]; } # clip paths against a slightly offsetted expolygon, so that the first and last paths # are kept even if the expolygon has vertical sides my @paths = @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection( +($expolygon->offset_ex(scaled_epsilon))[0], # TODO: we should use all the resulting expolygons and clip the linestrings to a multipolygon object - [ @vertical_lines ], + [ @{ $self->cache->{$cache_id} } ], ) }; # connect lines From bd3384525ec62ae8a512035a9adc4db46d652e5c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 15 Apr 2013 11:33:24 +0200 Subject: [PATCH 041/179] Revert "Align rectilinear and line infill across layers. #712" This reverts commit a0a54ea70697418ae6fb7939490dcf3c29ecd845. --- lib/Slic3r/Fill/Rectilinear.pm | 50 ++++++++++++++-------------------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm index a455b4aa..e1dcab0e 100644 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ b/lib/Slic3r/Fill/Rectilinear.pm @@ -3,9 +3,6 @@ use Moo; extends 'Slic3r::Fill::Base'; -has 'bounding_box' => (is => 'rw'); -has 'cache' => (is => 'rw', default => sub {{}}); - use Slic3r::Geometry qw(X1 Y1 X2 Y2 A B X Y scale unscale scaled_epsilon); sub fill_surface { @@ -21,44 +18,37 @@ sub fill_surface { return {} if !$expolygon_off; # skip some very small polygons (which shouldn't arrive here) my $bounding_box = [ $expolygon->bounding_box ]; - my $flow_spacing = $params{flow_spacing}; my $min_spacing = scale $params{flow_spacing}; my $distance_between_lines = $min_spacing / $params{density}; my $line_oscillation = $distance_between_lines - $min_spacing; + + my $flow_spacing = $params{flow_spacing}; + if ($params{density} == 1 && !$params{dont_adjust}) { + $distance_between_lines = $self->adjust_solid_spacing( + width => $bounding_box->[X2] - $bounding_box->[X1], + distance => $distance_between_lines, + ); + $flow_spacing = unscale $distance_between_lines; + } + + my $x = $bounding_box->[X1]; my $is_line_pattern = $self->isa('Slic3r::Fill::Line'); - - my $cache_id = sprintf "d%s_s%s_a%s", - $params{density}, $params{flow_spacing}, $rotate_vector->[0][0]; - - if (!$self->cache->{$cache_id} || !defined $self->bounding_box) { - if ($params{density} == 1 && !$params{dont_adjust}) { - $distance_between_lines = $self->adjust_solid_spacing( - width => $bounding_box->[X2] - $bounding_box->[X1], - distance => $distance_between_lines, - ); - $flow_spacing = unscale $distance_between_lines; + my @vertical_lines = (); + for (my $i = 0; $x <= $bounding_box->[X2] + scaled_epsilon; $i++) { + my $vertical_line = Slic3r::Line->new([$x, $bounding_box->[Y2]], [$x, $bounding_box->[Y1]]); + if ($is_line_pattern && $i % 2) { + $vertical_line->[A][X] += $line_oscillation; + $vertical_line->[B][X] -= $line_oscillation; } - - my $x = $bounding_box->[X1]; - my @vertical_lines = (); - for (my $i = 0; $x <= $bounding_box->[X2] + scaled_epsilon; $i++) { - my $vertical_line = Slic3r::Line->new([$x, $bounding_box->[Y2]], [$x, $bounding_box->[Y1]]); - if ($is_line_pattern && $i % 2) { - $vertical_line->[A][X] += $line_oscillation; - $vertical_line->[B][X] -= $line_oscillation; - } - push @vertical_lines, $vertical_line; - $x += $distance_between_lines; - } - - $self->cache->{$cache_id} = [@vertical_lines]; + push @vertical_lines, $vertical_line; + $x += $distance_between_lines; } # clip paths against a slightly offsetted expolygon, so that the first and last paths # are kept even if the expolygon has vertical sides my @paths = @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection( +($expolygon->offset_ex(scaled_epsilon))[0], # TODO: we should use all the resulting expolygons and clip the linestrings to a multipolygon object - [ @{ $self->cache->{$cache_id} } ], + [ @vertical_lines ], ) }; # connect lines From c723c07f8c9537a7402bb67106d9353225f947bf Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 15 Apr 2013 19:03:02 +0200 Subject: [PATCH 042/179] Update unit test after recent conversion of Boost::Geometry::Utils to double coordinates --- t/polyclip.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/polyclip.t b/t/polyclip.t index 8877d422..c25ad0fb 100644 --- a/t/polyclip.t +++ b/t/polyclip.t @@ -144,8 +144,8 @@ is_deeply $intersection, [ [120, 120], [180, 160] ], 'internal lines are preserv my $intersections = $expolygon->clip_line($line); is_deeply $intersections, [ - [ [152, 287], [152, 214], ], - [ [152, 107], [152, 35] ], + [ [152.742, 288.086660915295], [152.742, 215.178843238354], ], + [ [152.742, 108.087506777797], [152.742, 35.1664774739315] ], ], 'line is clipped to square with hole'; } From 430c82591808cfa361d5d2a2cba5fc771282a8d0 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 18 Apr 2013 17:34:21 +0200 Subject: [PATCH 043/179] Align rectilinear and line infill across layers (new implementation). #712 --- lib/Slic3r/ExPolygon.pm | 5 --- lib/Slic3r/Fill.pm | 7 ++-- lib/Slic3r/Fill/Base.pm | 17 ++++------ lib/Slic3r/Fill/Honeycomb.pm | 5 ++- lib/Slic3r/Fill/Rectilinear.pm | 60 ++++++++++++++++++++++------------ lib/Slic3r/Geometry.pm | 6 ++-- lib/Slic3r/Print/Object.pm | 8 ++--- 7 files changed, 57 insertions(+), 51 deletions(-) diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index ac1d0fb3..fe275952 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -161,11 +161,6 @@ sub bounding_box_polygon { ]); } -sub bounding_box_center { - my $self = shift; - return Slic3r::Geometry::bounding_box_center($self->contour); -} - sub clip_line { my $self = shift; my ($line) = @_; # line must be a Slic3r::Line object diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index de1d52ef..7db23412 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -39,10 +39,9 @@ sub filler { return $FillTypes{$filler}->new; } - if (!$self->fillers->{$filler}) { - my $f = $self->fillers->{$filler} = $FillTypes{$filler}->new; - $f->bounding_box([ $self->print->bounding_box ]) if $f->can('bounding_box'); - } + $self->fillers->{$filler} ||= $FillTypes{$filler}->new( + bounding_box => [ $self->print->bounding_box ], + ); return $self->fillers->{$filler}; } diff --git a/lib/Slic3r/Fill/Base.pm b/lib/Slic3r/Fill/Base.pm index 9c6996be..c9f801f5 100644 --- a/lib/Slic3r/Fill/Base.pm +++ b/lib/Slic3r/Fill/Base.pm @@ -5,6 +5,7 @@ use Slic3r::Geometry qw(PI); has 'layer_id' => (is => 'rw'); has 'angle' => (is => 'rw', default => sub { $Slic3r::Config->fill_angle }); +has 'bounding_box' => (is => 'ro', required => 1); sub angles () { [0, PI/2] } @@ -15,7 +16,7 @@ sub infill_direction { # set infill angle my (@rotate, @shift); $rotate[0] = Slic3r::Geometry::deg2rad($self->angle); - $rotate[1] = $surface->expolygon->bounding_box_center; + $rotate[1] = Slic3r::Geometry::bounding_box_center($self->bounding_box); @shift = @{$rotate[1]}; if (defined $self->layer_id) { @@ -41,11 +42,9 @@ sub rotate_points { my @rotate = @{$rotate_vector->[0]}; my @shift = @{$rotate_vector->[1]}; - # rotate points as needed - if ($rotate[0]) { - $expolygon->rotate(@rotate); - $expolygon->translate(@shift); - } + # rotate points + $expolygon->rotate(@rotate); + $expolygon->translate(@shift); } sub rotate_points_back { @@ -54,10 +53,8 @@ sub rotate_points_back { my @rotate = @{$rotate_vector->[0]}; my @shift = @{$rotate_vector->[1]}; - if ($rotate[0]) { - @$paths = map [ Slic3r::Geometry::rotate_points(-$rotate[0], $rotate[1], @$_) ], - map [ Slic3r::Geometry::move_points([map -$_, @shift], @$_) ], @$paths; - } + @$paths = map [ Slic3r::Geometry::rotate_points(-$rotate[0], $rotate[1], @$_) ], + map [ Slic3r::Geometry::move_points([map -$_, @shift], @$_) ], @$paths; } sub adjust_solid_spacing { diff --git a/lib/Slic3r/Fill/Honeycomb.pm b/lib/Slic3r/Fill/Honeycomb.pm index 9e4ff8a2..1985ddb9 100644 --- a/lib/Slic3r/Fill/Honeycomb.pm +++ b/lib/Slic3r/Fill/Honeycomb.pm @@ -3,7 +3,6 @@ use Moo; extends 'Slic3r::Fill::Base'; -has 'bounding_box' => (is => 'rw'); has 'cache' => (is => 'rw', default => sub {{}}); use Slic3r::Geometry qw(PI X1 Y1 X2 Y2 X Y scale); @@ -25,7 +24,7 @@ sub fill_surface { my $cache_id = sprintf "d%s_s%s_a%s", $params{density}, $params{flow_spacing}, $rotate_vector->[0][0]; - if (!$self->cache->{$cache_id} || !defined $self->bounding_box) { + if (!$self->cache->{$cache_id}) { # hexagons math my $hex_side = $distance / (sqrt(3)/2); @@ -39,7 +38,7 @@ sub fill_surface { # adjust actual bounding box to the nearest multiple of our hex pattern # and align it so that it matches across layers - my $bounding_box = [ $self->bounding_box ? @{$self->bounding_box} : $expolygon->bounding_box ]; + my $bounding_box = [ @{$self->bounding_box} ]; # clone $bounding_box->[$_] = 0 for X1, Y1; { my $bb_polygon = Slic3r::Polygon->new_from_bounding_box($bounding_box); diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm index e1dcab0e..d5191a47 100644 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ b/lib/Slic3r/Fill/Rectilinear.pm @@ -3,6 +3,8 @@ use Moo; extends 'Slic3r::Fill::Base'; +has 'cache' => (is => 'rw', default => sub {{}}); + use Slic3r::Geometry qw(X1 Y1 X2 Y2 A B X Y scale unscale scaled_epsilon); sub fill_surface { @@ -16,39 +18,55 @@ sub fill_surface { my ($expolygon_off) = $expolygon->offset_ex(scale $params{flow_spacing}/2); return {} if !$expolygon_off; # skip some very small polygons (which shouldn't arrive here) - my $bounding_box = [ $expolygon->bounding_box ]; + my $flow_spacing = $params{flow_spacing}; my $min_spacing = scale $params{flow_spacing}; my $distance_between_lines = $min_spacing / $params{density}; my $line_oscillation = $distance_between_lines - $min_spacing; - - my $flow_spacing = $params{flow_spacing}; - if ($params{density} == 1 && !$params{dont_adjust}) { - $distance_between_lines = $self->adjust_solid_spacing( - width => $bounding_box->[X2] - $bounding_box->[X1], - distance => $distance_between_lines, - ); - $flow_spacing = unscale $distance_between_lines; - } - - my $x = $bounding_box->[X1]; my $is_line_pattern = $self->isa('Slic3r::Fill::Line'); - my @vertical_lines = (); - for (my $i = 0; $x <= $bounding_box->[X2] + scaled_epsilon; $i++) { - my $vertical_line = Slic3r::Line->new([$x, $bounding_box->[Y2]], [$x, $bounding_box->[Y1]]); - if ($is_line_pattern && $i % 2) { - $vertical_line->[A][X] += $line_oscillation; - $vertical_line->[B][X] -= $line_oscillation; + + my $cache_id = sprintf "d%s_s%s_a%s", + $params{density}, $params{flow_spacing}, $rotate_vector->[0][0]; + + if (!$self->cache->{$cache_id}) { + # compute bounding box + my $bounding_box = $self->bounding_box; + { + my $bb_expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_from_bounding_box($bounding_box)); + $self->rotate_points($bb_expolygon, $rotate_vector); + $bounding_box = [ $bb_expolygon->bounding_box ]; } - push @vertical_lines, $vertical_line; - $x += $distance_between_lines; + + # define flow spacing according to requested density + if ($params{density} == 1 && !$params{dont_adjust}) { + $distance_between_lines = $self->adjust_solid_spacing( + width => $bounding_box->[X2] - $bounding_box->[X1], + distance => $distance_between_lines, + ); + $flow_spacing = unscale $distance_between_lines; + } + + # generate the basic pattern + my $x = $bounding_box->[X1]; + my @vertical_lines = (); + for (my $i = 0; $x <= $bounding_box->[X2] + scaled_epsilon; $i++) { + my $vertical_line = Slic3r::Line->new([$x, $bounding_box->[Y2]], [$x, $bounding_box->[Y1]]); + if ($is_line_pattern && $i % 2) { + $vertical_line->[A][X] += $line_oscillation; + $vertical_line->[B][X] -= $line_oscillation; + } + push @vertical_lines, $vertical_line; + $x += $distance_between_lines; + } + + $self->cache->{$cache_id} = [@vertical_lines]; } # clip paths against a slightly offsetted expolygon, so that the first and last paths # are kept even if the expolygon has vertical sides my @paths = @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection( +($expolygon->offset_ex(scaled_epsilon))[0], # TODO: we should use all the resulting expolygons and clip the linestrings to a multipolygon object - [ @vertical_lines ], + [ @{ $self->cache->{$cache_id} } ], ) }; # connect lines diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index 9f0a631f..4c1e23b9 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -679,10 +679,10 @@ sub bounding_box { } sub bounding_box_center { - my @bounding_box = bounding_box(@_); + my ($bounding_box) = @_; return Slic3r::Point->new( - ($bounding_box[X2] + $bounding_box[X1]) / 2, - ($bounding_box[Y2] + $bounding_box[Y1]) / 2, + ($bounding_box->[X2] + $bounding_box->[X1]) / 2, + ($bounding_box->[Y2] + $bounding_box->[Y1]) / 2, ); } diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 7968ac61..9fbe353e 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -894,6 +894,7 @@ sub generate_support_material { # generate paths for the pattern that we're going to use Slic3r::debugf "Generating patterns\n"; + my $fill = Slic3r::Fill->new(print => $self->print); my $support_patterns = []; my $support_interface_patterns = []; { @@ -908,10 +909,7 @@ sub generate_support_material { push @angles, $angles[0] + 90; } - my $filler = Slic3r::Fill->filler($pattern); - $filler->bounding_box([ Slic3r::Geometry::bounding_box([ map @$_, map @$_, @areas ]) ]) - if $filler->can('bounding_box'); - + my $filler = $fill->filler($pattern); my $make_pattern = sub { my ($expolygon, $density) = @_; @@ -996,7 +994,7 @@ sub generate_support_material { # make a solid base on bottom layer if ($layer_id == 0) { - my $filler = Slic3r::Fill->filler('rectilinear'); + my $filler = $fill->filler('rectilinear'); $filler->angle($Slic3r::Config->support_material_angle + 90); foreach my $expolygon (@$islands) { my @paths = $filler->fill_surface( From 850690cf790eec5282844b6d8521f610a62d40ef Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 18 Apr 2013 17:36:06 +0200 Subject: [PATCH 044/179] Minor optimization --- lib/Slic3r/Fill/Base.pm | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/Slic3r/Fill/Base.pm b/lib/Slic3r/Fill/Base.pm index c9f801f5..adc1e74c 100644 --- a/lib/Slic3r/Fill/Base.pm +++ b/lib/Slic3r/Fill/Base.pm @@ -39,22 +39,20 @@ sub infill_direction { sub rotate_points { my $self = shift; my ($expolygon, $rotate_vector) = @_; - my @rotate = @{$rotate_vector->[0]}; - my @shift = @{$rotate_vector->[1]}; # rotate points - $expolygon->rotate(@rotate); - $expolygon->translate(@shift); + $expolygon->rotate(@{$rotate_vector->[0]}); + $expolygon->translate(@{$rotate_vector->[1]}); } sub rotate_points_back { my $self = shift; my ($paths, $rotate_vector) = @_; - my @rotate = @{$rotate_vector->[0]}; - my @shift = @{$rotate_vector->[1]}; + my @rotate = (-$rotate_vector->[0][0], $rotate_vector->[0][1]); + my $shift = [ map -$_, @{$rotate_vector->[1]} ]; - @$paths = map [ Slic3r::Geometry::rotate_points(-$rotate[0], $rotate[1], @$_) ], - map [ Slic3r::Geometry::move_points([map -$_, @shift], @$_) ], @$paths; + @$paths = map [ Slic3r::Geometry::rotate_points(@rotate, @$_) ], + map [ Slic3r::Geometry::move_points($shift, @$_) ], @$paths; } sub adjust_solid_spacing { From 01bd9040f54aa05bff04df36f2c4c4e6a3460603 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 18 Apr 2013 17:46:13 +0200 Subject: [PATCH 045/179] Fix syntax for ex_int_offset2() --- lib/Slic3r/Geometry/Clipper.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index bef2713d..0c2cbfe3 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -113,8 +113,8 @@ sub ex_int_offset2 { } sub collapse_ex { - my ($polygons, $width) = @_; - return ex_int_offset2($polygons, -$width/2, +$width/2); + my ($polygons, $width) = @_;use XXX; YYY + return [ ex_int_offset2($polygons, -$width/2, +$width/2) ]; } sub simplify_polygon { From 312bb285a07166d89b4c59670f63003d12807428 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 18 Apr 2013 18:37:46 +0200 Subject: [PATCH 046/179] Fix initialization of Rectilinear filler --- lib/Slic3r/Layer/Region.pm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index fa8b3ad3..b06904c3 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -269,7 +269,10 @@ sub make_perimeters { # fill gaps if ($Slic3r::Config->gap_fill_speed > 0 && $Slic3r::Config->fill_density > 0 && @gaps) { - my $filler = Slic3r::Fill::Rectilinear->new(layer_id => $self->layer->id); + my $filler = Slic3r::Fill::Rectilinear->new( + bounding_box => [ $self->layer->object->print->bounding_box ], + layer_id => $self->layer->id, + ); # we should probably use this code to handle thin walls and remove that logic from # make_surfaces(), but we need to enable dynamic extrusion width before as we can't From e088d9b3f7a0dbf811956690c43916acb5328c81 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 18 Apr 2013 18:40:59 +0200 Subject: [PATCH 047/179] Cache Slic3r::Fill object --- lib/Slic3r/Layer/Region.pm | 6 ++---- lib/Slic3r/Print.pm | 8 +++++++- lib/Slic3r/Print/Object.pm | 5 ++--- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index b06904c3..e58b77b0 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -269,10 +269,8 @@ sub make_perimeters { # fill gaps if ($Slic3r::Config->gap_fill_speed > 0 && $Slic3r::Config->fill_density > 0 && @gaps) { - my $filler = Slic3r::Fill::Rectilinear->new( - bounding_box => [ $self->layer->object->print->bounding_box ], - layer_id => $self->layer->id, - ); + my $filler = $self->layer->object->print->fill_maker->filler('rectilinear'); + $filler->layer_id($self->layer->id); # we should probably use this code to handle thin walls and remove that logic from # make_surfaces(), but we need to enable dynamic extrusion width before as we can't diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 189b665b..634421a2 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -20,6 +20,7 @@ has 'regions' => (is => 'rw', default => sub {[]}); has 'support_material_flow' => (is => 'rw'); has 'first_layer_support_material_flow' => (is => 'rw'); has 'has_support_material' => (is => 'lazy'); +has 'fill_maker' => (is => 'lazy'); # ordered collection of extrusion paths to build skirt loops has 'skirt' => ( @@ -70,6 +71,11 @@ sub _build_has_support_material { || $self->config->support_material_enforce_layers > 0; } +sub _build_fill_maker { + my $self = shift; + return Slic3r::Fill->new(print => $self); +} + sub add_model { my $self = shift; my ($model) = @_; @@ -389,7 +395,7 @@ sub export_gcode { # this will generate extrusion paths for each layer $status_cb->(80, "Infilling layers"); { - my $fill_maker = Slic3r::Fill->new('print' => $self); + my $fill_maker = $self->fill_maker; Slic3r::parallelize( items => sub { my @items = (); # [obj_idx, layer_id] diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 9fbe353e..d7dd3f70 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -894,7 +894,6 @@ sub generate_support_material { # generate paths for the pattern that we're going to use Slic3r::debugf "Generating patterns\n"; - my $fill = Slic3r::Fill->new(print => $self->print); my $support_patterns = []; my $support_interface_patterns = []; { @@ -909,7 +908,7 @@ sub generate_support_material { push @angles, $angles[0] + 90; } - my $filler = $fill->filler($pattern); + my $filler = $self->print->fill_maker->filler($pattern); my $make_pattern = sub { my ($expolygon, $density) = @_; @@ -994,7 +993,7 @@ sub generate_support_material { # make a solid base on bottom layer if ($layer_id == 0) { - my $filler = $fill->filler('rectilinear'); + my $filler = $self->print->fill_maker->filler('rectilinear'); $filler->angle($Slic3r::Config->support_material_angle + 90); foreach my $expolygon (@$islands) { my @paths = $filler->fill_surface( From 17f76c4951f98106257646be7c1fbcc3befd425c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 18 Apr 2013 18:43:46 +0200 Subject: [PATCH 048/179] Move bridge detection to its own method --- lib/Slic3r/Layer/Region.pm | 212 +++++++++++++++++++------------------ 1 file changed, 108 insertions(+), 104 deletions(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index e58b77b0..bdabbd09 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -517,114 +517,118 @@ sub process_external_surfaces { } # detect bridge direction (skip bottom layer) - if ($self->id > 0) { - my @bottom = grep $_->surface_type == S_TYPE_BOTTOM, @{$self->fill_surfaces}; # surfaces - my @lower = @{$self->layer->object->layers->[ $self->id - 1 ]->slices}; # expolygons - - foreach my $surface (@bottom) { - # detect what edges lie on lower slices - my @edges = (); # polylines - foreach my $lower (@lower) { - # turn bridge contour and holes into polylines and then clip them - # with each lower slice's contour - my @clipped = map $_->split_at_first_point->clip_with_polygon($lower->contour), @{$surface->expolygon}; - if (@clipped == 2) { - # If the split_at_first_point() call above happens to split the polygon inside the clipping area - # we would get two consecutive polylines instead of a single one, so we use this ugly hack to - # recombine them back into a single one in order to trigger the @edges == 2 logic below. - # This needs to be replaced with something way better. - if (points_coincide($clipped[0][0], $clipped[-1][-1])) { - @clipped = (Slic3r::Polyline->new(@{$clipped[-1]}, @{$clipped[0]})); - } - if (points_coincide($clipped[-1][0], $clipped[0][-1])) { - @clipped = (Slic3r::Polyline->new(@{$clipped[0]}, @{$clipped[1]})); - } + $self->_detect_bridges if $self->id > 0; +} + +sub _detect_bridges { + my $self = shift; + + my @bottom = grep $_->surface_type == S_TYPE_BOTTOM, @{$self->fill_surfaces}; # surfaces + my @lower = @{$self->layer->object->layers->[ $self->id - 1 ]->slices}; # expolygons + + foreach my $surface (@bottom) { + # detect what edges lie on lower slices + my @edges = (); # polylines + foreach my $lower (@lower) { + # turn bridge contour and holes into polylines and then clip them + # with each lower slice's contour + my @clipped = map $_->split_at_first_point->clip_with_polygon($lower->contour), @{$surface->expolygon}; + if (@clipped == 2) { + # If the split_at_first_point() call above happens to split the polygon inside the clipping area + # we would get two consecutive polylines instead of a single one, so we use this ugly hack to + # recombine them back into a single one in order to trigger the @edges == 2 logic below. + # This needs to be replaced with something way better. + if (points_coincide($clipped[0][0], $clipped[-1][-1])) { + @clipped = (Slic3r::Polyline->new(@{$clipped[-1]}, @{$clipped[0]})); } - push @edges, @clipped; - } - - Slic3r::debugf "Found bridge on layer %d with %d support(s)\n", $self->id, scalar(@edges); - next if !@edges; - - my $bridge_angle = undef; - - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output("bridge_$surface.svg", - expolygons => [ $surface->expolygon ], - red_expolygons => [ @lower ], - polylines => [ @edges ], - ); - } - - if (@edges == 2) { - my @chords = map Slic3r::Line->new($_->[0], $_->[-1]), @edges; - my @midpoints = map $_->midpoint, @chords; - my $line_between_midpoints = Slic3r::Line->new(@midpoints); - $bridge_angle = Slic3r::Geometry::rad2deg_dir($line_between_midpoints->direction); - } elsif (@edges == 1) { - # TODO: this case includes both U-shaped bridges and plain overhangs; - # we need a trapezoidation algorithm to detect the actual bridged area - # and separate it from the overhang area. - # in the mean time, we're treating as overhangs all cases where - # our supporting edge is a straight line - if (@{$edges[0]} > 2) { - my $line = Slic3r::Line->new($edges[0]->[0], $edges[0]->[-1]); - $bridge_angle = Slic3r::Geometry::rad2deg_dir($line->direction); + if (points_coincide($clipped[-1][0], $clipped[0][-1])) { + @clipped = (Slic3r::Polyline->new(@{$clipped[0]}, @{$clipped[1]})); } - } elsif (@edges) { - # inset the bridge expolygon; we'll use this one to clip our test lines - my $inset = [ $surface->expolygon->offset_ex($self->infill_flow->scaled_width) ]; - - # detect anchors as intersection between our bridge expolygon and the lower slices - my $anchors = intersection_ex( - [ $surface->p ], - [ map @$_, @lower ], - ); - - # we'll now try several directions using a rudimentary visibility check: - # bridge in several directions and then sum the length of lines having both - # endpoints within anchors - my %directions = (); # angle => score - my $angle_increment = PI/36; # 5° - my $line_increment = $self->infill_flow->scaled_width; - for (my $angle = 0; $angle <= PI; $angle += $angle_increment) { - # rotate everything - the center point doesn't matter - $_->rotate($angle, [0,0]) for @$inset, @$anchors; - - # generate lines in this direction - my $bounding_box = [ Slic3r::Geometry::bounding_box([ map @$_, map @$_, @$anchors ]) ]; - my @lines = (); - for (my $x = $bounding_box->[X1]; $x <= $bounding_box->[X2]; $x += $line_increment) { - push @lines, [ [$x, $bounding_box->[Y1]], [$x, $bounding_box->[Y2]] ]; - } - - # TODO: use a multi_polygon_multi_linestring_intersection() call - my @clipped_lines = map @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection($_, \@lines) }, @$inset; - - # remove any line not having both endpoints within anchors - @clipped_lines = grep { - my $line = $_; - !(first { $_->encloses_point_quick($line->[A]) } @$anchors) - && !(first { $_->encloses_point_quick($line->[B]) } @$anchors); - } @clipped_lines; - - # sum length of bridged lines - $directions{-$angle} = sum(map Slic3r::Geometry::line_length($_), @clipped_lines) // 0; - } - - # this could be slightly optimized with a max search instead of the sort - my @sorted_directions = sort { $directions{$a} <=> $directions{$b} } keys %directions; - - # the best direction is the one causing most lines to be bridged - $bridge_angle = Slic3r::Geometry::rad2deg_dir($sorted_directions[-1]); } - - Slic3r::debugf " Optimal infill angle of bridge on layer %d is %d degrees\n", - $self->id, $bridge_angle if defined $bridge_angle; - - $surface->bridge_angle($bridge_angle); + push @edges, @clipped; } + + Slic3r::debugf "Found bridge on layer %d with %d support(s)\n", $self->id, scalar(@edges); + next if !@edges; + + my $bridge_angle = undef; + + if (0) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output("bridge_$surface.svg", + expolygons => [ $surface->expolygon ], + red_expolygons => [ @lower ], + polylines => [ @edges ], + ); + } + + if (@edges == 2) { + my @chords = map Slic3r::Line->new($_->[0], $_->[-1]), @edges; + my @midpoints = map $_->midpoint, @chords; + my $line_between_midpoints = Slic3r::Line->new(@midpoints); + $bridge_angle = Slic3r::Geometry::rad2deg_dir($line_between_midpoints->direction); + } elsif (@edges == 1) { + # TODO: this case includes both U-shaped bridges and plain overhangs; + # we need a trapezoidation algorithm to detect the actual bridged area + # and separate it from the overhang area. + # in the mean time, we're treating as overhangs all cases where + # our supporting edge is a straight line + if (@{$edges[0]} > 2) { + my $line = Slic3r::Line->new($edges[0]->[0], $edges[0]->[-1]); + $bridge_angle = Slic3r::Geometry::rad2deg_dir($line->direction); + } + } elsif (@edges) { + # inset the bridge expolygon; we'll use this one to clip our test lines + my $inset = [ $surface->expolygon->offset_ex($self->infill_flow->scaled_width) ]; + + # detect anchors as intersection between our bridge expolygon and the lower slices + my $anchors = intersection_ex( + [ $surface->p ], + [ map @$_, @lower ], + ); + + # we'll now try several directions using a rudimentary visibility check: + # bridge in several directions and then sum the length of lines having both + # endpoints within anchors + my %directions = (); # angle => score + my $angle_increment = PI/36; # 5° + my $line_increment = $self->infill_flow->scaled_width; + for (my $angle = 0; $angle <= PI; $angle += $angle_increment) { + # rotate everything - the center point doesn't matter + $_->rotate($angle, [0,0]) for @$inset, @$anchors; + + # generate lines in this direction + my $bounding_box = [ Slic3r::Geometry::bounding_box([ map @$_, map @$_, @$anchors ]) ]; + my @lines = (); + for (my $x = $bounding_box->[X1]; $x <= $bounding_box->[X2]; $x += $line_increment) { + push @lines, [ [$x, $bounding_box->[Y1]], [$x, $bounding_box->[Y2]] ]; + } + + # TODO: use a multi_polygon_multi_linestring_intersection() call + my @clipped_lines = map @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection($_, \@lines) }, @$inset; + + # remove any line not having both endpoints within anchors + @clipped_lines = grep { + my $line = $_; + !(first { $_->encloses_point_quick($line->[A]) } @$anchors) + && !(first { $_->encloses_point_quick($line->[B]) } @$anchors); + } @clipped_lines; + + # sum length of bridged lines + $directions{-$angle} = sum(map Slic3r::Geometry::line_length($_), @clipped_lines) // 0; + } + + # this could be slightly optimized with a max search instead of the sort + my @sorted_directions = sort { $directions{$a} <=> $directions{$b} } keys %directions; + + # the best direction is the one causing most lines to be bridged + $bridge_angle = Slic3r::Geometry::rad2deg_dir($sorted_directions[-1]); + } + + Slic3r::debugf " Optimal infill angle of bridge on layer %d is %d degrees\n", + $self->id, $bridge_angle if defined $bridge_angle; + + $surface->bridge_angle($bridge_angle); } } From 4e6c58f7491cae69cc906ae43c511d0c2da18216 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 18 Apr 2013 19:49:02 +0200 Subject: [PATCH 049/179] TYpo --- lib/Slic3r/Geometry/Clipper.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index 0c2cbfe3..b1981a3b 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -113,7 +113,7 @@ sub ex_int_offset2 { } sub collapse_ex { - my ($polygons, $width) = @_;use XXX; YYY + my ($polygons, $width) = @_; return [ ex_int_offset2($polygons, -$width/2, +$width/2) ]; } From 51d140d7d772e88c6b84677095627cc83559cb87 Mon Sep 17 00:00:00 2001 From: Mark Hindess Date: Thu, 18 Apr 2013 18:56:55 +0100 Subject: [PATCH 050/179] Fix dependencies to require Math::Clipper 1.18. To avoid error "Undefined subroutine &Math::Clipper::int_offset ...". --- Build.PL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Build.PL b/Build.PL index c44c5084..4b8ad7ef 100644 --- a/Build.PL +++ b/Build.PL @@ -12,7 +12,7 @@ my $build = Module::Build->new( 'File::Basename' => '0', 'File::Spec' => '0', 'Getopt::Long' => '0', - 'Math::Clipper' => '1.17', + 'Math::Clipper' => '1.18', 'Math::ConvexHull::MonotoneChain' => '0.01', 'Math::Geometry::Voronoi' => '1.3', 'Math::PlanePath' => '53', From 407de02fe4a6ce93808d694271fee636bc0ab80e Mon Sep 17 00:00:00 2001 From: Mark Hindess Date: Thu, 18 Apr 2013 19:29:05 +0100 Subject: [PATCH 051/179] Use binary search for get_layer_range. Gives a 13% speed up on Teethy_Tiki.stl (thing:49334) with default settings on my laptop. When $self->layers->[0]->slice_z == $min_z the returned $min_layer value is 0 rather than the (incorrect?) -1 returned by the old code. --- lib/Slic3r/Print/Object.pm | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index d7dd3f70..d4052fcc 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -95,18 +95,33 @@ sub get_layer_range { # $min_layer is the uppermost layer having slice_z <= $min_z # $max_layer is the lowermost layer having slice_z >= $max_z - my ($min_layer, $max_layer) = (0, undef); - for my $i (0 .. $#{$self->layers}) { - if ($self->layers->[$i]->slice_z >= $min_z) { - $min_layer = $i - 1; - for my $k ($i .. $#{$self->layers}) { - if ($self->layers->[$k]->slice_z >= $max_z) { - $max_layer = $k - 1; - last; - } - } + my ($min_layer, $max_layer); + + my ($bottom, $top) = (0, $#{$self->layers}); + while (1) { + my $mid = $bottom+int(($top - $bottom)/2); + if ($mid == $top || $mid == $bottom) { + $min_layer = $mid; last; } + if ($self->layers->[$mid]->slice_z >= $min_z) { + $top = $mid; + } else { + $bottom = $mid; + } + } + $top = $#{$self->layers}; + while (1) { + my $mid = $bottom+int(($top - $bottom)/2); + if ($mid == $top || $mid == $bottom) { + $max_layer = $mid; + last; + } + if ($self->layers->[$mid]->slice_z < $max_z) { + $bottom = $mid; + } else { + $top = $mid; + } } return ($min_layer, $max_layer); } From aac2c45450320c8a63825aba0c1e44d46c727a79 Mon Sep 17 00:00:00 2001 From: Mark Hindess Date: Thu, 18 Apr 2013 19:50:05 +0100 Subject: [PATCH 052/179] Avoid merging meshes when there is only one mesh to merge. When slicing yoda.stl it reduces peak memory usage by 5% and speeds up slicing by 13%. Further optimization using a simple proxy to implement the two methods (size/extends) needed on the merge meshes is also possible and undoubtedly worthwhile. --- lib/Slic3r/Print.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 634421a2..70e60875 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -118,7 +118,8 @@ sub add_model { $mesh->scale($Slic3r::Config->scale / &Slic3r::SCALING_FACTOR); } - my $complete_mesh = Slic3r::TriangleMesh->merge(grep defined $_, @meshes); + my @defined_meshes = grep defined $_, @meshes; + my $complete_mesh = @defined_meshes == 1 ? $defined_meshes[0] : Slic3r::TriangleMesh->merge(@defined_meshes); # initialize print object my $print_object = Slic3r::Print::Object->new( From 8c26f0da30619a373cede31803db6e3e31175cdb Mon Sep 17 00:00:00 2001 From: Mark Hindess Date: Thu, 18 Apr 2013 22:49:43 +0100 Subject: [PATCH 053/179] Avoid memory leaks by breaking reference cycle: Print -> Fill -> Print --- lib/Slic3r/Fill.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index 7db23412..17742005 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -17,7 +17,7 @@ use Slic3r::Geometry::Clipper qw(union_ex diff diff_ex intersection_ex offset); use Slic3r::Surface ':types'; -has 'print' => (is => 'ro', required => 1); +has 'print' => (is => 'ro', required => 1, weak_ref => 1); has 'fillers' => (is => 'rw', default => sub { {} }); our %FillTypes = ( From 55c413627f8545fb5d2b75efb157d30689985d53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Tue, 23 Apr 2013 16:17:43 +0300 Subject: [PATCH 054/179] Bugfix: configuration wizard crash 2 #1077 --- lib/Slic3r/GUI/SkeinPanel.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index 1bcaa4d9..1a822a05 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -234,7 +234,9 @@ sub load_config { my ($config) = @_; foreach my $tab (values %{$self->{options_tabs}}) { - $tab->set_value($_, $config->$_) for keys %$config; + if ($self->{mode} eq 'expert') { + $tab->set_value($_, $config->$_) for keys %$config; + } } } From 24fd152ce433b8841d4fb0196679a834a971bc2c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 24 Apr 2013 11:19:08 +0200 Subject: [PATCH 055/179] Bugfix: cooling buffer was broken. #1149 --- lib/Slic3r/GCode/CoolingBuffer.pm | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/Slic3r/GCode/CoolingBuffer.pm b/lib/Slic3r/GCode/CoolingBuffer.pm index d88e6c2e..4847ef75 100644 --- a/lib/Slic3r/GCode/CoolingBuffer.pm +++ b/lib/Slic3r/GCode/CoolingBuffer.pm @@ -4,6 +4,7 @@ use Moo; has 'config' => (is => 'ro', required => 1); has 'gcodegen' => (is => 'ro', required => 1); has 'gcode' => (is => 'rw', default => sub {""}); +has 'elapsed_time' => (is => 'rw', default => sub {0}); has 'layer_id' => (is => 'rw'); has 'last_z' => (is => 'rw'); has 'min_print_speed' => (is => 'lazy'); @@ -20,12 +21,13 @@ sub append { my $return = ""; if (defined $self->last_z && $self->last_z != $layer->print_z) { $return = $self->flush; - $self->gcodegen->elapsed_time(0); } $self->layer_id($layer->id); $self->last_z($layer->print_z); $self->gcode($self->gcode . $gcode); + $self->elapsed_time($self->elapsed_time + $self->gcodegen->elapsed_time); + $self->gcodegen->elapsed_time(0); return $return; } @@ -34,19 +36,20 @@ sub flush { my $self = shift; my $gcode = $self->gcode; + my $elapsed = $self->elapsed_time; $self->gcode(""); + $self->elapsed_time(0); my $fan_speed = $self->config->fan_always_on ? $self->config->min_fan_speed : 0; my $speed_factor = 1; if ($self->config->cooling) { - my $layer_time = $self->gcodegen->elapsed_time; - Slic3r::debugf "Layer %d estimated printing time: %d seconds\n", $self->layer_id, $layer_time; - if ($layer_time < $self->config->slowdown_below_layer_time) { + Slic3r::debugf "Layer %d estimated printing time: %d seconds\n", $self->layer_id, $elapsed; + if ($elapsed < $self->config->slowdown_below_layer_time) { $fan_speed = $self->config->max_fan_speed; - $speed_factor = $layer_time / $self->config->slowdown_below_layer_time; - } elsif ($layer_time < $self->config->fan_below_layer_time) { + $speed_factor = $elapsed / $self->config->slowdown_below_layer_time; + } elsif ($elapsed < $self->config->fan_below_layer_time) { $fan_speed = $self->config->max_fan_speed - ($self->config->max_fan_speed - $self->config->min_fan_speed) - * ($layer_time - $self->config->slowdown_below_layer_time) + * ($elapsed - $self->config->slowdown_below_layer_time) / ($self->config->fan_below_layer_time - $self->config->slowdown_below_layer_time); #/ } Slic3r::debugf " fan = %d%%, speed = %d%%\n", $fan_speed, $speed_factor * 100; From 0118c7257f1e57ad1b2bebe468aee1a8a9fd1d57 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 25 Apr 2013 12:01:40 +0200 Subject: [PATCH 056/179] Update test according to the new Fill/Base.pm syntax --- t/fill.t | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/t/fill.t b/t/fill.t index cbffd7da..d239199e 100644 --- a/t/fill.t +++ b/t/fill.t @@ -19,7 +19,10 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } { my $print = Slic3r::Print->new; $print->init_extruders; - my $filler = Slic3r::Fill::Rectilinear->new(print => $print); + my $filler = Slic3r::Fill::Rectilinear->new( + print => $print, + bounding_box => [ 0, 0, 10, 10 ], + ); my $surface_width = 250; my $distance = $filler->adjust_solid_spacing( width => $surface_width, @@ -30,10 +33,13 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } } { - my $filler = Slic3r::Fill::Rectilinear->new; + my $expolygon = Slic3r::ExPolygon->new([ scale_points [0,0], [50,0], [50,50], [0,50] ]); + my $filler = Slic3r::Fill::Rectilinear->new( + bounding_box => [ $expolygon->bounding_box ], + ); my $surface = Slic3r::Surface->new( surface_type => S_TYPE_TOP, - expolygon => Slic3r::ExPolygon->new([ scale_points [0,0], [50,0], [50,50], [0,50] ]), + expolygon => $expolygon, ); foreach my $angle (0, 45) { $surface->expolygon->rotate(Slic3r::Geometry::deg2rad($angle), [0,0]); From 6842114d3d5bc707b14f5d9c2365e7875b28baee Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 26 Apr 2013 17:14:30 +0200 Subject: [PATCH 057/179] Fix for rectilinear infill failing in plates with new caching --- lib/Slic3r/Fill/Rectilinear.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm index d5191a47..a65bcbc0 100644 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ b/lib/Slic3r/Fill/Rectilinear.pm @@ -30,7 +30,8 @@ sub fill_surface { if (!$self->cache->{$cache_id}) { # compute bounding box - my $bounding_box = $self->bounding_box; + my $bounding_box = [ @{$self->bounding_box} ]; # clone + $bounding_box->[$_] = 0 for X1, Y1; { my $bb_expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_from_bounding_box($bounding_box)); $self->rotate_points($bb_expolygon, $rotate_vector); From bc9ff47d3f819c93574aed58f237aaa8f5d2fdad Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 27 Apr 2013 15:02:13 +0200 Subject: [PATCH 058/179] New module to output SVG vertical sections of extrusion paths with real width and height. The commit includes a bugfix to the Polyline->grow() method and some simplification in the Line class which is now child of Polyline --- MANIFEST | 1 + lib/Slic3r/ExPolygon.pm | 5 -- lib/Slic3r/Line.pm | 27 +------- lib/Slic3r/Polygon.pm | 10 --- lib/Slic3r/Polyline.pm | 11 ++- lib/Slic3r/Print.pm | 4 ++ lib/Slic3r/Test/SectionCut.pm | 125 ++++++++++++++++++++++++++++++++++ 7 files changed, 138 insertions(+), 45 deletions(-) create mode 100644 lib/Slic3r/Test/SectionCut.pm diff --git a/MANIFEST b/MANIFEST index e7498665..2bf6f4f9 100644 --- a/MANIFEST +++ b/MANIFEST @@ -51,6 +51,7 @@ lib/Slic3r/Print/Object.pm lib/Slic3r/Surface.pm lib/Slic3r/SVG.pm lib/Slic3r/Test.pm +lib/Slic3r/Test/SectionCut.pm lib/Slic3r/TriangleMesh.pm MANIFEST This list of files README.markdown diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index 551e464c..45b59ace 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -73,11 +73,6 @@ sub is_printable { return Slic3r::Geometry::Clipper::offset($self, -$width / 2) ? 1 : 0; } -sub boost_polygon { - my $self = shift; - return Boost::Geometry::Utils::polygon(@$self); -} - sub wkt { my $self = shift; return sprintf "POLYGON(%s)", diff --git a/lib/Slic3r/Line.pm b/lib/Slic3r/Line.pm index 81ca1d48..968ec1b6 100644 --- a/lib/Slic3r/Line.pm +++ b/lib/Slic3r/Line.pm @@ -2,7 +2,9 @@ package Slic3r::Line; use strict; use warnings; -use Boost::Geometry::Utils; +# a line is a two-points line +use parent 'Slic3r::Polyline'; + use Slic3r::Geometry qw(A B X Y); sub new { @@ -14,11 +16,6 @@ sub new { return $self; } -sub boost_linestring { - my $self = shift; - return Boost::Geometry::Utils::linestring($self); -} - sub coincides_with { my $self = shift; my ($line) = @_; @@ -27,11 +24,6 @@ sub coincides_with { || ($self->a->coincides_with($line->b) && $self->b->coincides_with($line->a)); } -sub length { - my $self = shift; - return Boost::Geometry::Utils::linestring_length($self); -} - sub vector { my $self = shift; return (ref $self)->new([0,0], [map $self->[B][$_] - $self->[A][$_], X,Y]); @@ -67,17 +59,4 @@ sub midpoint { ); } -sub reverse { - my $self = shift; - @$self = reverse @$self; -} - -sub translate { - my $self = shift; - my ($x, $y) = @_; - @$self = Slic3r::Geometry::move_points([$x, $y], @$self); - bless $_, 'Slic3r::Point' for @$self; - return $self; -} - 1; diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm index b3cf77d0..6e460f40 100644 --- a/lib/Slic3r/Polygon.pm +++ b/lib/Slic3r/Polygon.pm @@ -27,16 +27,6 @@ sub lines { return polygon_lines($self); } -sub boost_polygon { - my $self = shift; - return Boost::Geometry::Utils::polygon($self); -} - -sub boost_linestring { - my $self = shift; - return Boost::Geometry::Utils::linestring([@$self, $self->[0]]); -} - sub wkt { my $self = shift; return sprintf "POLYGON((%s))", join ',', map "$_->[0] $_->[1]", @$self; diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm index 5883cf00..6646d1aa 100644 --- a/lib/Slic3r/Polyline.pm +++ b/lib/Slic3r/Polyline.pm @@ -5,6 +5,7 @@ use warnings; use Scalar::Util qw(reftype); use Slic3r::Geometry qw(A B X Y X1 X2 Y1 Y2 polyline_remove_parallel_continuous_edges polyline_remove_acute_vertices polyline_lines move_points same_point); +use Slic3r::Geometry::Clipper qw(JT_SQUARE); # the constructor accepts an array(ref) of points sub new { @@ -44,11 +45,6 @@ sub lines { return polyline_lines($self); } -sub boost_linestring { - my $self = shift; - return Boost::Geometry::Utils::linestring($self); -} - sub wkt { my $self = shift; return sprintf "LINESTRING((%s))", join ',', map "$_->[0] $_->[1]", @$self; @@ -84,10 +80,13 @@ sub length { sub grow { my $self = shift; + my ($distance, $scale, $joinType, $miterLimit) = @_; + $joinType //= JT_SQUARE; + return map Slic3r::Polygon->new($_), Slic3r::Geometry::Clipper::offset( [ Slic3r::Polygon->new(@$self, CORE::reverse @$self[1..($#$self-1)]) ], - @_, + $distance, $scale, $joinType, $miterLimit, ); } diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 70e60875..5ddfd2ed 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -466,6 +466,10 @@ sub export_gcode { printf " fills = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->fills), map @{$_->regions}, map @{$_->layers}, @{$self->objects})/1024/1024; printf " print object = %.1fMb\n", Devel::Size::total_size($self)/1024/1024; } + if (0) { + eval "use Slic3r::Test::SectionCut"; + Slic3r::Test::SectionCut->new(print => $self)->export_svg("section_cut.svg"); + } # output everything to a G-code file my $output_file = $self->expanded_output_filepath($params{output_file}); diff --git a/lib/Slic3r/Test/SectionCut.pm b/lib/Slic3r/Test/SectionCut.pm new file mode 100644 index 00000000..70f88968 --- /dev/null +++ b/lib/Slic3r/Test/SectionCut.pm @@ -0,0 +1,125 @@ +package Slic3r::Test::SectionCut; +use Moo; + +use Slic3r::Geometry qw(X Y A B X1 Y1 X2 Y2 unscale); +use Slic3r::Geometry::Clipper qw(union_ex); +use SVG; + +has 'scale' => (is => 'ro', default => sub {30}); +has 'print' => (is => 'ro', required => 1); +has 'y_percent' => (is => 'ro', required => 1, default => sub {0.5}); +has 'line' => (is => 'lazy'); +has 'height' => (is => 'rw'); + +sub _build_line { + my $self = shift; + + my @bb = $self->print->bounding_box; + my $y = ($bb[Y2]-$bb[Y1]) * $self->y_percent; + return [ [ $bb[X1], $y ], [ $bb[X2], $y ] ] +} + +sub export_svg { + my $self = shift; + my ($filename) = @_; + + my $print_size = $self->print->size; + $self->height(unscale($print_size->[X])); + my $svg = SVG->new( + width => $self->scale * unscale($print_size->[X]), + height => $self->scale * $self->height, + ); + + my $group = sub { + my %p = @_; + my $g = $svg->group(style => $p{style}); + $g->rectangle(%$_) for $self->_get_rectangles($p{filter}); + }; + + $group->( + filter => sub { map @{$_->perimeters}, @{$_[0]->regions} }, + style => { + 'stroke-width' => 1, + 'stroke' => 'grey', + 'fill' => 'red', + }, + ); + + $group->( + filter => sub { map @{$_->fills}, @{$_[0]->regions} }, + style => { + 'stroke-width' => 1, + 'stroke' => '#444444', + 'fill' => 'grey', + }, + ); + + $group->( + filter => sub { $_[0]->support_fills, $_[0]->support_contact_fills }, + style => { + 'stroke-width' => 1, + 'stroke' => '#444444', + 'fill' => '#22FF00', + }, + ); + + Slic3r::open(\my $fh, '>', $filename); + print $fh $svg->xmlify; + close $fh; + printf "Section cut SVG written to %s\n", $filename; +} + +sub _get_rectangles { + my $self = shift; + my ($filter) = @_; + + my @rectangles = (); + + foreach my $object (@{$self->print->objects}) { + foreach my $copy (@{$object->copies}) { + foreach my $layer (@{$object->layers}) { + # get all ExtrusionPath objects + my @paths = + map { $_->polyline->translate(@$copy); $_ } + map { $_->isa('Slic3r::ExtrusionLoop') ? $_->split_at_first_point : $_ } + map { ref($_) =~ /::Packed$/ ? $_->unpack : $_ } + map { $_->isa('Slic3r::ExtrusionPath::Collection') ? @{$_->paths} : $_ } + grep defined $_, + $filter->($layer); + + foreach my $path (@paths) { + foreach my $line ($path->lines) { + my @intersections = @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection( + Slic3r::ExPolygon->new($line->grow(Slic3r::Geometry::scale $path->flow_spacing/2)), + [ $self->line ], + ) }; + + push @rectangles, map { + die "Intersection has more than two points!\n" if @$_ > 2; + my $height = $path->height // $layer->height; + { + 'x' => $self->scale * unscale $_->[A][X], + 'y' => $self->scale * $self->_y(unscale($layer->print_z)), + 'width' => $self->scale * unscale(abs($_->[B][X] - $_->[A][X])), + 'height' => $self->scale * $height, + 'rx' => $self->scale * $height * 0.35, + 'ry' => $self->scale * $height * 0.35, + }; + } @intersections; + } + } + } + } + } + + return @rectangles; +} + +sub _y { + my $self = shift; + my ($y) = @_; + + return $self->height - $y; +} + +1; From f5bda326b8ae2e1f1205985fbe5ee569210ca782 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 27 Apr 2013 20:55:43 +0200 Subject: [PATCH 059/179] Version check. #1006 --- lib/Slic3r.pm | 2 +- lib/Slic3r/GUI.pm | 47 ++++++++++++++++++++++++++++++++++ lib/Slic3r/GUI/OptionsGroup.pm | 1 + lib/Slic3r/GUI/Preferences.pm | 8 ++++++ 4 files changed, 57 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index df04dc96..47442f64 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -60,7 +60,7 @@ use Slic3r::Print::Object; use Slic3r::Print::Region; use Slic3r::Surface; use Slic3r::TriangleMesh; -eval "use Slic3r::Build"; +our $build = eval "use Slic3r::Build; 1"; use constant SCALING_FACTOR => 0.000001; use constant RESOLUTION => 0.0125; diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index ceb6adbb..0f4957ca 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -37,6 +37,7 @@ use constant MI_TAB_PRINTER => &Wx::NewId; use constant MI_CONF_WIZARD => &Wx::NewId; use constant MI_WEBSITE => &Wx::NewId; +use constant MI_VERSIONCHECK => &Wx::NewId; use constant MI_DOCUMENTATION => &Wx::NewId; our $datadir; @@ -46,6 +47,7 @@ our $mode; our $Settings = { _ => { mode => 'simple', + version_check => 1, }, }; @@ -155,11 +157,14 @@ sub OnInit { $helpMenu->Append(MI_CONF_WIZARD, "&Configuration $Slic3r::GUI::ConfigWizard::wizard…", "Run Configuration $Slic3r::GUI::ConfigWizard::wizard"); $helpMenu->AppendSeparator(); $helpMenu->Append(MI_WEBSITE, "Slic3r &Website", 'Open the Slic3r website in your browser'); + my $versioncheck = $helpMenu->Append(MI_VERSIONCHECK, "Check for &Updates...", 'Check for new Slic3r versions'); + $versioncheck->Enable(Slic3r::GUI->have_version_check); $helpMenu->Append(MI_DOCUMENTATION, "&Documentation", 'Open the Slic3r documentation in your browser'); $helpMenu->AppendSeparator(); $helpMenu->Append(wxID_ABOUT, "&About Slic3r", 'Show about dialog'); EVT_MENU($frame, MI_CONF_WIZARD, sub { $self->{skeinpanel}->config_wizard }); EVT_MENU($frame, MI_WEBSITE, sub { Wx::LaunchDefaultBrowser('http://slic3r.org/') }); + EVT_MENU($frame, MI_VERSIONCHECK, sub { Slic3r::GUI->check_version(manual => 1) }); EVT_MENU($frame, MI_DOCUMENTATION, sub { Wx::LaunchDefaultBrowser('https://github.com/alexrj/Slic3r/wiki/Documentation') }); EVT_MENU($frame, wxID_ABOUT, \&about); } @@ -192,6 +197,11 @@ sub OnInit { $self->{skeinpanel}->config_wizard if $run_wizard; + Slic3r::GUI->check_version + if Slic3r::GUI->have_version_check + && ($Settings->{_}{version_check} // 1) + && (!$Settings->{_}{last_version_check} || (time - $Settings->{_}{last_version_check}) >= 86400); + return 1; } @@ -222,6 +232,12 @@ sub show_error { Wx::MessageDialog->new($self, $message, 'Error', wxOK | wxICON_ERROR)->ShowModal; } +sub show_info { + my $self = shift; + my ($message, $title) = @_; + Wx::MessageDialog->new($self, $message, $title || 'Notice', wxOK | wxICON_INFORMATION)->ShowModal; +} + sub fatal_error { my $self = shift; $self->show_error(@_); @@ -257,6 +273,37 @@ sub save_settings { Slic3r::Config->write_ini("$datadir/slic3r.ini", $Settings); } +sub have_version_check { + my $class = shift; + + return $Slic3r::have_threads && $Slic3r::build && eval "use LWP::UserAgent; 1"; +} + +sub check_version { + my $class = shift; + my %p = @_; + Slic3r::debugf "Checking for updates...\n"; + + threads->create(sub { + 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(); + Slic3r::GUI->save_settings; + } else { + Slic3r::GUI::show_error(undef, "Failed to check for updates. Try later.") if $p{manual}; + } + })->detach; +} + package Slic3r::GUI::ProgressStatusBar; use Wx qw(:gauge :misc); use base 'Wx::StatusBar'; diff --git a/lib/Slic3r/GUI/OptionsGroup.pm b/lib/Slic3r/GUI/OptionsGroup.pm index 8734ff6f..b228ea59 100644 --- a/lib/Slic3r/GUI/OptionsGroup.pm +++ b/lib/Slic3r/GUI/OptionsGroup.pm @@ -180,6 +180,7 @@ sub _build_field { } elsif ($opt->{type} eq 'bool') { $field = Wx::CheckBox->new($self->parent, -1, ""); $field->SetValue($opt->{default}); + $field->Disable if $opt->{readonly}; EVT_CHECKBOX($self->parent, $field, sub { $self->_on_change($opt_key, $field->GetValue); }); $self->_setters->{$opt_key} = sub { $field->SetValue($_[0]) }; $tooltip .= " (default: " . ($opt->{default} ? 'yes' : 'no') . ")" if defined($opt->{default}); diff --git a/lib/Slic3r/GUI/Preferences.pm b/lib/Slic3r/GUI/Preferences.pm index 25ad5621..2ad5bd24 100644 --- a/lib/Slic3r/GUI/Preferences.pm +++ b/lib/Slic3r/GUI/Preferences.pm @@ -22,6 +22,14 @@ sub new { values => ['simple','expert'], default => $Slic3r::GUI::Settings->{_}{mode}, }, + { + opt_key => 'version_check', + type => 'bool', + label => 'Check for updates', + tooltip => 'If this is enabled, Slic3r will check for updates daily and display a reminder if a newer version is available.', + default => $Slic3r::GUI::Settings->{_}{version_check} // 1, + readonly => !Slic3r::GUI->have_version_check, + }, ], on_change => sub { $self->{values}{$_[0]} = $_[1] }, label_width => 100, From 6db8afe36ddad06699ae3c88f2d6fe39dc956371 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 27 Apr 2013 21:07:30 +0200 Subject: [PATCH 060/179] New option to remember last output directory. #698 --- lib/Slic3r/GUI.pm | 9 +++++++++ lib/Slic3r/GUI/Plater.pm | 4 +++- lib/Slic3r/GUI/Preferences.pm | 7 +++++++ lib/Slic3r/GUI/SkeinPanel.pm | 5 ++++- 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 0f4957ca..4e3321a1 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -304,6 +304,15 @@ sub check_version { })->detach; } +sub output_path { + my $class = shift; + my ($dir) = @_; + + return ($Settings->{_}{last_output_path} && $Settings->{_}{remember_output_path}) + ? $Settings->{_}{last_output_path} + : $dir; +} + package Slic3r::GUI::ProgressStatusBar; use Wx qw(:gauge :misc); use base 'Wx::StatusBar'; diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index be001cae..2f73bb75 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -531,12 +531,14 @@ sub export_gcode { $self->{output_file} = $main::opt{output}; { $self->{output_file} = $print->expanded_output_filepath($self->{output_file}, $self->{objects}[0]->input_file); - my $dlg = Wx::FileDialog->new($self, 'Save G-code file as:', dirname($self->{output_file}), + my $dlg = Wx::FileDialog->new($self, 'Save G-code file as:', Slic3r::GUI->output_path(dirname($self->{output_file})), basename($self->{output_file}), &Slic3r::GUI::SkeinPanel::FILE_WILDCARDS->{gcode}, wxFD_SAVE); if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; return; } + $Slic3r::GUI::Settings->{_}{last_output_path} = dirname($dlg->GetPath); + Slic3r::GUI->save_settings; $self->{output_file} = $Slic3r::GUI::SkeinPanel::last_output_file = $dlg->GetPath; $dlg->Destroy; } diff --git a/lib/Slic3r/GUI/Preferences.pm b/lib/Slic3r/GUI/Preferences.pm index 2ad5bd24..2630745b 100644 --- a/lib/Slic3r/GUI/Preferences.pm +++ b/lib/Slic3r/GUI/Preferences.pm @@ -30,6 +30,13 @@ sub new { default => $Slic3r::GUI::Settings->{_}{version_check} // 1, readonly => !Slic3r::GUI->have_version_check, }, + { + opt_key => 'remember_output_path', + type => 'bool', + label => 'Remember output directory', + tooltip => 'If this is enabled, Slic3r will prompt the last output directory instead of the one containing the input files.', + default => $Slic3r::GUI::Settings->{_}{remember_output_path}, + }, ], on_change => sub { $self->{values}{$_[0]} = $_[1] }, label_width => 100, diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index 1bcaa4d9..ea07c3b0 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -133,7 +133,8 @@ sub do_slice { } elsif ($params{save_as}) { $output_file = $print->expanded_output_filepath($output_file); $output_file =~ s/\.gcode$/.svg/i if $params{export_svg}; - my $dlg = Wx::FileDialog->new($self, 'Save ' . ($params{export_svg} ? 'SVG' : 'G-code') . ' file as:', dirname($output_file), + my $dlg = Wx::FileDialog->new($self, 'Save ' . ($params{export_svg} ? 'SVG' : 'G-code') . ' file as:', + Slic3r::GUI->output_path(dirname($output_file)), basename($output_file), $params{export_svg} ? FILE_WILDCARDS->{svg} : FILE_WILDCARDS->{gcode}, wxFD_SAVE); if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; @@ -141,6 +142,8 @@ sub do_slice { } $output_file = $dlg->GetPath; $last_output_file = $output_file unless $params{export_svg}; + $Slic3r::GUI::Settings->{_}{last_output_path} = dirname($output_file); + Slic3r::GUI->save_settings; $dlg->Destroy; } From bde730a61139c0cec4a0db2bdc62f6ec1a179501 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 27 Apr 2013 22:18:57 +0200 Subject: [PATCH 061/179] Little fix in SectionCut.pm --- lib/Slic3r/Test/SectionCut.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Test/SectionCut.pm b/lib/Slic3r/Test/SectionCut.pm index 70f88968..bab54241 100644 --- a/lib/Slic3r/Test/SectionCut.pm +++ b/lib/Slic3r/Test/SectionCut.pm @@ -7,7 +7,7 @@ use SVG; has 'scale' => (is => 'ro', default => sub {30}); has 'print' => (is => 'ro', required => 1); -has 'y_percent' => (is => 'ro', required => 1, default => sub {0.5}); +has 'y_percent' => (is => 'ro', default => sub {0.5}); has 'line' => (is => 'lazy'); has 'height' => (is => 'rw'); From f7153d67d0a561eee220cd75f8710bfc8525263a Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 28 Apr 2013 23:26:50 +0200 Subject: [PATCH 062/179] Make preset placeholders available in Quick Slice too. #921 --- lib/Slic3r/GUI/Plater.pm | 19 ++----------------- lib/Slic3r/GUI/SkeinPanel.pm | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 2f73bb75..36e0186e 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -525,7 +525,7 @@ sub export_gcode { } # get config before spawning the thread because ->config needs GetParent and it's not available there - my $print = $self->_init_print; + my $print = $self->skeinpanel->init_print; # select output file $self->{output_file} = $main::opt{output}; @@ -582,21 +582,6 @@ sub export_gcode { } } -sub _init_print { - my $self = shift; - - my %extra_variables = (); - if ($self->skeinpanel->{mode} eq 'expert') { - $extra_variables{"${_}_preset"} = $self->skeinpanel->{options_tabs}{$_}->current_preset->{name} - for qw(print filament printer); - } - - return Slic3r::Print->new( - config => $self->skeinpanel->config, - extra_variables => { %extra_variables }, - ); -} - sub export_gcode2 { my $self = shift; my ($print, $output_file, %params) = @_; @@ -687,7 +672,7 @@ sub _get_export_file { my $output_file = $main::opt{output}; { - $output_file = $self->_init_print->expanded_output_filepath($output_file, $self->{objects}[0]->input_file); + $output_file = $self->skeinpanel->init_print->expanded_output_filepath($output_file, $self->{objects}[0]->input_file); $output_file =~ s/\.gcode$/$suffix/i; my $dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file), basename($output_file), &Slic3r::GUI::SkeinPanel::MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index ea07c3b0..95129b40 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -122,7 +122,7 @@ sub do_slice { $Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_file); Slic3r::GUI->save_settings; - my $print = Slic3r::Print->new(config => $config); + my $print = $self->init_print; $print->add_model(Slic3r::Model->read_from_file($input_file)); $print->validate; @@ -189,6 +189,21 @@ sub do_slice { Slic3r::GUI::catch_error($self, sub { $process_dialog->Destroy if $process_dialog }); } +sub init_print { + my $self = shift; + + my %extra_variables = (); + if ($self->{mode} eq 'expert') { + $extra_variables{"${_}_preset"} = $self->{options_tabs}{$_}->current_preset->{name} + for qw(print filament printer); + } + + return Slic3r::Print->new( + config => $self->config, + extra_variables => { %extra_variables }, + ); +} + sub export_config { my $self = shift; From c2301c5796713ee50025c565a0b5a44f69d9f97d Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 29 Apr 2013 15:53:15 +0200 Subject: [PATCH 063/179] Don't extend solid layers when fill density is 0 --- lib/Slic3r/Layer/Region.pm | 10 ++++++++-- lib/Slic3r/Print/Object.pm | 7 ++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index bdabbd09..551c3ac6 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -473,17 +473,23 @@ sub process_external_surfaces { my @top = grep $_->surface_type == S_TYPE_TOP, @{$self->fill_surfaces}; my @bottom = grep $_->surface_type == S_TYPE_BOTTOM, @{$self->fill_surfaces}; + # if we're slicing with no infill, we can't extend external surfaces + # over non-existent infill + my @fill_boundaries = $Slic3r::Config->fill_density > 0 + ? @{$self->fill_surfaces} + : grep $_->surface_type != S_TYPE_INTERNAL, @{$self->fill_surfaces}; + # offset them and intersect the results with the actual fill boundaries my $margin = scale 3; # TODO: ensure this is greater than the total thickness of the perimeters @top = @{intersection_ex( [ Slic3r::Geometry::Clipper::offset([ map $_->p, @top ], +$margin) ], - [ map $_->p, @{$self->fill_surfaces} ], + [ map $_->p, @fill_boundaries ], undef, 1, # to ensure adjacent expolygons are unified )}; @bottom = @{intersection_ex( [ Slic3r::Geometry::Clipper::offset([ map $_->p, @bottom ], +$margin) ], - [ map $_->p, @{$self->fill_surfaces} ], + [ map $_->p, @fill_boundaries ], undef, 1, # to ensure adjacent expolygons are unified )}; diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index d4052fcc..7b21db87 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -637,10 +637,15 @@ sub discover_horizontal_shells { # if some parts are going to collapse, let's grow them and add the extra area to the neighbor layer # as well as to our original surfaces so that we support this additional area in the next shell too if (@$too_narrow) { + # consider the actual fill area + my @fill_boundaries = $Slic3r::Config->fill_density > 0 + ? @neighbor_fill_surfaces + : grep $_->surface_type != S_TYPE_INTERNAL, @neighbor_fill_surfaces; + # make sure our grown surfaces don't exceed the fill area my @grown = map @$_, @{intersection_ex( [ offset([ map @$_, @$too_narrow ], +$margin) ], - [ map $_->p, @neighbor_fill_surfaces ], + [ map $_->p, @fill_boundaries ], )}; $new_internal_solid = union_ex([ @grown, (map @$_, @$new_internal_solid) ]); $solid = union_ex([ @grown, (map @$_, @$solid) ]); From dde3e6e870976d677d10d20cd7c140090309d18e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 29 Apr 2013 15:57:07 +0200 Subject: [PATCH 064/179] Disable Net::DBus. #1015 --- Build.PL | 1 - lib/Slic3r/GUI.pm | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Build.PL b/Build.PL index 4b8ad7ef..0f756ed2 100644 --- a/Build.PL +++ b/Build.PL @@ -28,7 +28,6 @@ my $build = Module::Build->new( recommends => { 'Class::XSAccessor' => '0', 'Growl::GNTP' => '0.15', - 'Net::DBus' => '0', 'XML::SAX::ExpatXS' => '0', 'Wx' => '0.9901', }, diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 4e3321a1..b825603b 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -480,7 +480,8 @@ sub notify { $self->{growler}->notify(Event => 'SKEIN_DONE', Title => $title, Message => $message) if $self->{growler}; }; - if (eval 'use Net::DBus; 1') { + # Net::DBus is broken in multithreaded environment + if (0 && eval 'use Net::DBus; 1') { eval { my $session = Net::DBus->session; my $serv = $session->get_service('org.freedesktop.Notifications'); From 855ba9033262416de2ae9e27f55a21738ef5d70f Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 2 May 2013 11:42:51 +0200 Subject: [PATCH 065/179] New script to generate vertical section cuts from any given G-code file --- MANIFEST | 1 + lib/Slic3r/SVG.pm | 2 +- lib/Slic3r/Test.pm | 15 +++-- utils/gcode_sectioncut.pl | 133 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 143 insertions(+), 8 deletions(-) create mode 100644 utils/gcode_sectioncut.pl diff --git a/MANIFEST b/MANIFEST index 2bf6f4f9..2a6d0b81 100644 --- a/MANIFEST +++ b/MANIFEST @@ -78,6 +78,7 @@ t/support.t t/vibrationlimit.t utils/amf-to-stl.pl utils/file_info.pl +utils/gcode_sectioncut.pl utils/post-processing/filament-weight.pl utils/post-processing/prowl-notification.pl utils/post-processing/z-every-line.pl diff --git a/lib/Slic3r/SVG.pm b/lib/Slic3r/SVG.pm index 0ac28639..a7909485 100644 --- a/lib/Slic3r/SVG.pm +++ b/lib/Slic3r/SVG.pm @@ -9,7 +9,7 @@ use constant Y => 1; our $filltype = 'evenodd'; -sub factor { +sub factor {return 30; return &Slic3r::SCALING_FACTOR * 10; } diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index 0884fd30..a059313f 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -94,7 +94,7 @@ sub parse { my $self = shift; my ($cb) = @_; - foreach my $line (split /\n/, $self->gcode) { + foreach my $line (split /\R+/, $self->gcode) { print "$line\n" if $Verbose || $ENV{SLIC3R_TESTS_GCODE}; $line =~ s/\s*;(.*)//; # strip comment next if $line eq ''; @@ -107,15 +107,14 @@ sub parse { # check retraction if ($command =~ /^G[01]$/) { - if (!exists $args{E}) { - $info{travel} = 1; - } foreach my $axis (@AXES) { - if (!exists $args{$axis}) { + if (exists $args{$axis}) { + $info{"dist_$axis"} = $args{$axis} - $self->$axis; + $info{"new_$axis"} = $args{$axis}; + } else { $info{"dist_$axis"} = 0; - next; + $info{"new_$axis"} = $self->$axis; } - $info{"dist_$axis"} = $args{$axis} - $self->$axis; } $info{dist_XY} = Slic3r::Line->new([0,0], [@info{qw(dist_X dist_Y)}])->length; if (exists $args{E}) { @@ -124,6 +123,8 @@ sub parse { } elsif ($info{dist_E} < 0) { $info{retracting} = 1 } + } else { + $info{travel} = 1; } } diff --git a/utils/gcode_sectioncut.pl b/utils/gcode_sectioncut.pl new file mode 100644 index 00000000..39d94112 --- /dev/null +++ b/utils/gcode_sectioncut.pl @@ -0,0 +1,133 @@ +#!/usr/bin/perl +# This script generates section cuts from a given G-Code file + +use strict; +use warnings; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; +} + +use Getopt::Long qw(:config no_auto_abbrev); +use IO::All; +use List::Util qw(max); +use Slic3r; +use Slic3r::Geometry qw(X Y A B X1 Y1 X2 Y2); +use Slic3r::Geometry::Clipper qw(JT_SQUARE); +use Slic3r::Test; +use SVG; + +my %opt = ( + layer_height => 0.2, + extrusion_width => 0.5, + scale => 30, +); +{ + my %options = ( + 'help' => sub { usage() }, + 'layer-height|h=f' => \$opt{layer_height}, + 'extrusion-width|w=f' => \$opt{extrusion_width}, + 'scale|s=i' => \$opt{scale}, + ); + GetOptions(%options) or usage(1); + $ARGV[0] or usage(1); +} + +{ + my $input_file = $ARGV[0]; + my $output_file = $input_file; + $output_file =~ s/\.(?:gcode|gco|ngc|g)$/.svg/; + + # read paths + my %paths = (); # z => [ path, path ... ] + Slic3r::Test::GCodeReader->new(gcode => io($input_file)->all)->parse(sub { + my ($self, $cmd, $args, $info) = @_; + + if ($cmd eq 'G1' && $info->{extruding}) { + $paths{ $self->Z } ||= []; + push @{ $paths{ $self->Z } }, Slic3r::Line->new( + [ $self->X, $self->Y ], + [ $info->{new_X}, $info->{new_Y} ], + ); + } + }); + + # calculate print extents + my @bounding_box = Slic3r::Geometry::bounding_box([ map @$_, map @$_, values %paths ]); + my @size = ( + ($bounding_box[X2] - $bounding_box[X1]), + ($bounding_box[Y2] - $bounding_box[Y1]), + ); + + # calculate section line + my $section_y = ($bounding_box[Y2] + $bounding_box[Y1]) / 2; + my $section_line = [ + [ $bounding_box[X1], $section_y ], + [ $bounding_box[X2], $section_y ], + ]; + + # initialize output + my $max_z = max(keys %paths); + my $svg = SVG->new( + width => $opt{scale} * $size[X], + height => $opt{scale} * $max_z, + ); + + # put everything into a group + my $g = $svg->group(style => { + 'stroke-width' => 1, + 'stroke' => '#444444', + 'fill' => 'grey', + }); + + # draw paths + foreach my $z (sort keys %paths) { + foreach my $line (@{ $paths{$z} }) { + my @intersections = @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection( + Slic3r::ExPolygon->new(_grow($line, $opt{extrusion_width}/2)), + [ $section_line ], + ) }; + + $g->rectangle( + 'x' => $opt{scale} * ($_->[A][X] - $bounding_box[X1]), + 'y' => $opt{scale} * ($max_z - $z), + 'width' => $opt{scale} * abs($_->[B][X] - $_->[A][X]), + 'height' => $opt{scale} * $opt{layer_height}, + 'rx' => $opt{scale} * $opt{layer_height} * 0.35, + 'ry' => $opt{scale} * $opt{layer_height} * 0.35, + ) for @intersections; + } + } + + # write output + Slic3r::open(\my $fh, '>', $output_file); + print $fh $svg->xmlify; + close $fh; + printf "Section cut SVG written to %s\n", $output_file; +} + +# replace built-in Line->grow method which relies on int_offset() +sub _grow { + my ($line, $distance) = @_; + + my $polygon = [ @$line, CORE::reverse @$line[1..($#$line-1)] ]; + return @{Math::Clipper::offset([$polygon], $distance, 100000, JT_SQUARE, 2)}; +} + +sub usage { + my ($exit_code) = @_; + + print <<"EOF"; +Usage: gcode_sectioncut.pl [ OPTIONS ] file.gcode + + --help Output this usage screen and exit + --layer-height, -h Use the specified layer height + --extrusion-width, -w Use the specified extrusion width + --scale Factor for converting G-code units to SVG units + +EOF + exit ($exit_code || 0); +} + +__END__ From 4aad2f6243e1a3d0503a048536365492f3bc2e14 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 9 May 2013 14:52:56 +0200 Subject: [PATCH 066/179] Smarter ordering of brim loops. #687 --- lib/Slic3r/Geometry/Clipper.pm | 10 ++++++++- lib/Slic3r/Print.pm | 37 +++++++++++++++++++++++++++------- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index b1981a3b..fb0fa242 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -6,7 +6,7 @@ require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(safety_offset safety_offset_ex offset offset_ex collapse_ex diff_ex diff union_ex intersection_ex xor_ex PFT_EVENODD JT_MITER JT_ROUND - JT_SQUARE is_counter_clockwise); + JT_SQUARE is_counter_clockwise union_pt); use Math::Clipper 1.17 qw(:cliptypes :polyfilltypes :jointypes is_counter_clockwise area); use Slic3r::Geometry qw(scale); @@ -78,6 +78,14 @@ sub union_ex { ]; } +sub union_pt { + my ($polygons, $jointype, $safety_offset) = @_; + $jointype = PFT_NONZERO unless defined $jointype; + $clipper->clear; + $clipper->add_subject_polygons($safety_offset ? safety_offset($polygons) : $polygons); + return $clipper->pt_execute(CT_UNION, $jointype, $jointype); +} + sub intersection_ex { my ($subject, $clip, $jointype, $safety_offset) = @_; $jointype = PFT_NONZERO unless defined $jointype; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 5ddfd2ed..c04e3b28 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -6,8 +6,8 @@ use File::Spec; use List::Util qw(max first); use Math::ConvexHull::MonotoneChain qw(convex_hull); use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN PI scale unscale move_points nearest_point); -use Slic3r::Geometry::Clipper qw(diff_ex union_ex intersection_ex offset JT_ROUND JT_SQUARE); +use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN PI scale unscale move_points nearest_point chained_path_items); +use Slic3r::Geometry::Clipper qw(diff_ex union_ex union_pt intersection_ex offset JT_ROUND JT_SQUARE PFT_EVENODD); use Time::HiRes qw(gettimeofday tv_interval); has 'config' => (is => 'rw', default => sub { Slic3r::Config->new_from_defaults }, trigger => 1); @@ -673,16 +673,39 @@ sub make_brim { push @islands, map $_->unpack->split_at_first_point->polyline->grow($grow_distance), @{$self->skirt}; } + my @loops = (); my $num_loops = sprintf "%.0f", $Slic3r::Config->brim_width / $flow->width; for my $i (reverse 1 .. $num_loops) { # JT_SQUARE ensures no vertex is outside the given offset distance - push @{$self->brim}, Slic3r::ExtrusionLoop->pack( - polygon => Slic3r::Polygon->new($_), - role => EXTR_ROLE_SKIRT, - flow_spacing => $flow->spacing, - ) for Slic3r::Geometry::Clipper::offset(\@islands, ($i - 0.5) * $flow->scaled_spacing, undef, JT_SQUARE); # -0.5 because islands are not represented by their centerlines + # -0.5 because islands are not represented by their centerlines # TODO: we need the offset inwards/offset outwards logic to avoid overlapping extrusions + push @loops, offset(\@islands, ($i - 0.5) * $flow->scaled_spacing, undef, JT_SQUARE); } + + # prepare a subroutine to traverse the tree and return inner perimeters first + my $traverse; + $traverse = sub { + my ($loops) = @_; + + # use a nearest neighbor search to order these children + # TODO: supply second argument to chained_path_items() too? + @$loops = @{chained_path_items( + [ map [ ($_->{outer} ? $_->{outer}[0] : $_->{hole}[0]), $_ ], @$loops ], + )}; + + my @polygons = (); + foreach my $loop (@$loops) { + push @polygons, $traverse->($loop->{children}); + push @polygons, Slic3r::ExtrusionLoop->pack( + polygon => Slic3r::Polygon->new($loop->{outer} // [ reverse @{$loop->{hole}} ]), + role => EXTR_ROLE_SKIRT, + flow_spacing => $flow->spacing, + ); + } + return @polygons; + }; + + @{$self->brim} = reverse $traverse->( union_pt(\@loops, PFT_EVENODD) ); } sub write_gcode { From 5bf3f5f5e495db46c59fe9e9ac1c680f2823f3ff Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 10 May 2013 13:47:40 +0200 Subject: [PATCH 067/179] Make sure brim paths don't overlap --- lib/Slic3r/Geometry/Clipper.pm | 12 +++++++++++- lib/Slic3r/Print.pm | 5 +++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index fb0fa242..d454b3b1 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -6,7 +6,7 @@ require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(safety_offset safety_offset_ex offset offset_ex collapse_ex diff_ex diff union_ex intersection_ex xor_ex PFT_EVENODD JT_MITER JT_ROUND - JT_SQUARE is_counter_clockwise union_pt); + JT_SQUARE is_counter_clockwise union_pt ex_int_offset2 offset2); use Math::Clipper 1.17 qw(:cliptypes :polyfilltypes :jointypes is_counter_clockwise area); use Slic3r::Geometry qw(scale); @@ -33,6 +33,16 @@ sub offset { return @$offsets; } +sub offset2 { + my ($polygons, $distance1, $distance2, $scale, $joinType, $miterLimit) = @_; + $scale ||= 100000; + $joinType //= JT_MITER; + $miterLimit //= 3; + + my $offsets = Math::Clipper::int_offset2($polygons, $distance1, $distance2, $scale, $joinType, $miterLimit); + return @$offsets; +} + sub offset_ex { my ($polygons, $distance, $scale, $joinType, $miterLimit) = @_; $scale ||= 100000; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index c04e3b28..69d84475 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -7,7 +7,8 @@ use List::Util qw(max first); use Math::ConvexHull::MonotoneChain qw(convex_hull); use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN PI scale unscale move_points nearest_point chained_path_items); -use Slic3r::Geometry::Clipper qw(diff_ex union_ex union_pt intersection_ex offset JT_ROUND JT_SQUARE PFT_EVENODD); +use Slic3r::Geometry::Clipper qw(diff_ex union_ex union_pt intersection_ex offset + ex_int_offset2 offset2 JT_ROUND JT_SQUARE PFT_EVENODD); use Time::HiRes qw(gettimeofday tv_interval); has 'config' => (is => 'rw', default => sub { Slic3r::Config->new_from_defaults }, trigger => 1); @@ -679,7 +680,7 @@ sub make_brim { # JT_SQUARE ensures no vertex is outside the given offset distance # -0.5 because islands are not represented by their centerlines # TODO: we need the offset inwards/offset outwards logic to avoid overlapping extrusions - push @loops, offset(\@islands, ($i - 0.5) * $flow->scaled_spacing, undef, JT_SQUARE); + push @loops, offset2(\@islands, ($i - 2) * $flow->scaled_spacing, ($i + 1.5) * $flow->scaled_spacing, undef, JT_SQUARE); } # prepare a subroutine to traverse the tree and return inner perimeters first From 993fcd918288669fb447ae5eefdc798f12143251 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 10 May 2013 13:51:28 +0200 Subject: [PATCH 068/179] Revert "Smarter ordering of brim loops. #687" until new Math::Clipper is out This reverts commit 4aad2f6243e1a3d0503a048536365492f3bc2e14. --- lib/Slic3r/Geometry/Clipper.pm | 10 +-------- lib/Slic3r/Print.pm | 37 +++++++--------------------------- 2 files changed, 8 insertions(+), 39 deletions(-) diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index fb0fa242..b1981a3b 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -6,7 +6,7 @@ require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(safety_offset safety_offset_ex offset offset_ex collapse_ex diff_ex diff union_ex intersection_ex xor_ex PFT_EVENODD JT_MITER JT_ROUND - JT_SQUARE is_counter_clockwise union_pt); + JT_SQUARE is_counter_clockwise); use Math::Clipper 1.17 qw(:cliptypes :polyfilltypes :jointypes is_counter_clockwise area); use Slic3r::Geometry qw(scale); @@ -78,14 +78,6 @@ sub union_ex { ]; } -sub union_pt { - my ($polygons, $jointype, $safety_offset) = @_; - $jointype = PFT_NONZERO unless defined $jointype; - $clipper->clear; - $clipper->add_subject_polygons($safety_offset ? safety_offset($polygons) : $polygons); - return $clipper->pt_execute(CT_UNION, $jointype, $jointype); -} - sub intersection_ex { my ($subject, $clip, $jointype, $safety_offset) = @_; $jointype = PFT_NONZERO unless defined $jointype; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index c04e3b28..5ddfd2ed 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -6,8 +6,8 @@ use File::Spec; use List::Util qw(max first); use Math::ConvexHull::MonotoneChain qw(convex_hull); use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN PI scale unscale move_points nearest_point chained_path_items); -use Slic3r::Geometry::Clipper qw(diff_ex union_ex union_pt intersection_ex offset JT_ROUND JT_SQUARE PFT_EVENODD); +use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN PI scale unscale move_points nearest_point); +use Slic3r::Geometry::Clipper qw(diff_ex union_ex intersection_ex offset JT_ROUND JT_SQUARE); use Time::HiRes qw(gettimeofday tv_interval); has 'config' => (is => 'rw', default => sub { Slic3r::Config->new_from_defaults }, trigger => 1); @@ -673,39 +673,16 @@ sub make_brim { push @islands, map $_->unpack->split_at_first_point->polyline->grow($grow_distance), @{$self->skirt}; } - my @loops = (); my $num_loops = sprintf "%.0f", $Slic3r::Config->brim_width / $flow->width; for my $i (reverse 1 .. $num_loops) { # JT_SQUARE ensures no vertex is outside the given offset distance - # -0.5 because islands are not represented by their centerlines + push @{$self->brim}, Slic3r::ExtrusionLoop->pack( + polygon => Slic3r::Polygon->new($_), + role => EXTR_ROLE_SKIRT, + flow_spacing => $flow->spacing, + ) for Slic3r::Geometry::Clipper::offset(\@islands, ($i - 0.5) * $flow->scaled_spacing, undef, JT_SQUARE); # -0.5 because islands are not represented by their centerlines # TODO: we need the offset inwards/offset outwards logic to avoid overlapping extrusions - push @loops, offset(\@islands, ($i - 0.5) * $flow->scaled_spacing, undef, JT_SQUARE); } - - # prepare a subroutine to traverse the tree and return inner perimeters first - my $traverse; - $traverse = sub { - my ($loops) = @_; - - # use a nearest neighbor search to order these children - # TODO: supply second argument to chained_path_items() too? - @$loops = @{chained_path_items( - [ map [ ($_->{outer} ? $_->{outer}[0] : $_->{hole}[0]), $_ ], @$loops ], - )}; - - my @polygons = (); - foreach my $loop (@$loops) { - push @polygons, $traverse->($loop->{children}); - push @polygons, Slic3r::ExtrusionLoop->pack( - polygon => Slic3r::Polygon->new($loop->{outer} // [ reverse @{$loop->{hole}} ]), - role => EXTR_ROLE_SKIRT, - flow_spacing => $flow->spacing, - ); - } - return @polygons; - }; - - @{$self->brim} = reverse $traverse->( union_pt(\@loops, PFT_EVENODD) ); } sub write_gcode { From 6b8894cf9aab685c471c412545e5ce57f47d468a Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 10 May 2013 13:56:29 +0200 Subject: [PATCH 069/179] Rename ex_int_offset2 to offset2_ex --- lib/Slic3r/Geometry/Clipper.pm | 24 ++++++++++++------------ lib/Slic3r/Layer/Region.pm | 6 +++--- lib/Slic3r/Print.pm | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index d454b3b1..5bd2a74e 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -6,7 +6,7 @@ require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(safety_offset safety_offset_ex offset offset_ex collapse_ex diff_ex diff union_ex intersection_ex xor_ex PFT_EVENODD JT_MITER JT_ROUND - JT_SQUARE is_counter_clockwise union_pt ex_int_offset2 offset2); + JT_SQUARE is_counter_clockwise union_pt offset2 offset2_ex); use Math::Clipper 1.17 qw(:cliptypes :polyfilltypes :jointypes is_counter_clockwise area); use Slic3r::Geometry qw(scale); @@ -53,6 +53,16 @@ sub offset_ex { return map Slic3r::ExPolygon->new($_), @$offsets; } +sub offset2_ex { + my ($polygons, $delta1, $delta2, $scale, $joinType, $miterLimit) = @_; + $scale ||= 100000; + $joinType //= JT_MITER; + $miterLimit //= 3; + + my $offsets = Math::Clipper::ex_int_offset2($polygons, $delta1, $delta2, $scale, $joinType, $miterLimit); + return map Slic3r::ExPolygon->new($_), @$offsets; +} + sub diff_ex { my ($subject, $clip, $safety_offset) = @_; @@ -120,19 +130,9 @@ sub xor_ex { ]; } -sub ex_int_offset2 { - my ($polygons, $delta1, $delta2, $scale, $joinType, $miterLimit) = @_; - $scale ||= 100000; - $joinType //= JT_MITER; - $miterLimit //= 3; - - my $offsets = Math::Clipper::ex_int_offset2($polygons, $delta1, $delta2, $scale, $joinType, $miterLimit); - return map Slic3r::ExPolygon->new($_), @$offsets; -} - sub collapse_ex { my ($polygons, $width) = @_; - return [ ex_int_offset2($polygons, -$width/2, +$width/2) ]; + return [ offset2_ex($polygons, -$width/2, +$width/2) ]; } sub simplify_polygon { diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 551c3ac6..6974c8f2 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -4,7 +4,7 @@ use Moo; use List::Util qw(sum first); use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(PI X1 X2 Y1 Y2 A B scale chained_path_items points_coincide); -use Slic3r::Geometry::Clipper qw(safety_offset union_ex diff_ex intersection_ex); +use Slic3r::Geometry::Clipper qw(safety_offset union_ex diff_ex intersection_ex offset2_ex); use Slic3r::Surface ':types'; has 'layer' => ( @@ -97,7 +97,7 @@ sub make_surfaces { { my $width = $self->perimeter_flow->scaled_width; my $outgrown = [ - Slic3r::Geometry::Clipper::ex_int_offset2([ map @$_, map $_->expolygon, @{$self->slices} ], -$width, +$width), + offset2_ex([ map @$_, map $_->expolygon, @{$self->slices} ], -$width, +$width), ]; my $diff = diff_ex( [ map $_->p, @{$self->slices} ], @@ -227,7 +227,7 @@ sub make_perimeters { # offsetting a polygon can result in one or many offset polygons my @new_offsets = (); foreach my $expolygon (@last_offsets) { - my @offsets = Slic3r::Geometry::Clipper::ex_int_offset2($expolygon, -1.5*$spacing, +0.5*$spacing); + my @offsets = offset2_ex($expolygon, -1.5*$spacing, +0.5*$spacing); push @new_offsets, @offsets; # where the above check collapses the expolygon, then there's no room for an inner loop diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 69d84475..d5589227 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -8,7 +8,7 @@ use Math::ConvexHull::MonotoneChain qw(convex_hull); use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN PI scale unscale move_points nearest_point chained_path_items); use Slic3r::Geometry::Clipper qw(diff_ex union_ex union_pt intersection_ex offset - ex_int_offset2 offset2 JT_ROUND JT_SQUARE PFT_EVENODD); + offset2 JT_ROUND JT_SQUARE PFT_EVENODD); use Time::HiRes qw(gettimeofday tv_interval); has 'config' => (is => 'rw', default => sub { Slic3r::Config->new_from_defaults }, trigger => 1); From bac5093de49a2ad6f043cd37e7ae42b20a2a07ed Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 10 May 2013 15:09:27 +0200 Subject: [PATCH 070/179] Alwyas apply disable_fan_first_layers, even if cooling is disabled, since fan_always_on is applied in that case as well. Update descriptive text and have it appear in case of disabled auto cooling. #1166 --- lib/Slic3r/Config.pm | 4 ++-- lib/Slic3r/GCode/CoolingBuffer.pm | 2 +- lib/Slic3r/GUI/Tab.pm | 17 ++++++++++++----- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index dc0763be..4705552b 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -826,8 +826,8 @@ END # cooling options 'cooling' => { - label => 'Enable cooling', - tooltip => 'This flag enables all the cooling features.', + label => 'Enable auto cooling', + tooltip => 'This flag enables the automatic cooling logic that adjusts print speed and fan speed according to layer printing time.', cli => 'cooling!', type => 'bool', default => 1, diff --git a/lib/Slic3r/GCode/CoolingBuffer.pm b/lib/Slic3r/GCode/CoolingBuffer.pm index 4847ef75..a04554f5 100644 --- a/lib/Slic3r/GCode/CoolingBuffer.pm +++ b/lib/Slic3r/GCode/CoolingBuffer.pm @@ -61,8 +61,8 @@ sub flush { $1 . sprintf("%.${dec}f", $new_speed < $self->min_print_speed ? $self->min_print_speed : $new_speed) /gexm; } - $fan_speed = 0 if $self->layer_id < $self->config->disable_fan_first_layers; } + $fan_speed = 0 if $self->layer_id < $self->config->disable_fan_first_layers; $gcode = $self->gcodegen->set_fan($fan_speed) . $gcode; # bridge fan speed diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 235c841b..dec4c864 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -600,6 +600,15 @@ sub _update_description { my $config = $self->config; my $msg = ""; + my $fan_other_layers = $config->fan_always_on + ? sprintf "will always run at %d%%%s.", $config->min_fan_speed, + ($config->disable_fan_first_layers > 1 + ? " except for the first " . $config->disable_fan_first_layers . " layers" + : $config->disable_fan_first_layers == 1 + ? " except for the first layer" + : "") + : "will be turned off."; + if ($config->cooling) { $msg = sprintf "If estimated layer time is below ~%ds, fan will run at 100%% and print speed will be reduced so that no less than %ds are spent on that layer (however, speed will never be reduced below %dmm/s).", $config->slowdown_below_layer_time, $config->slowdown_below_layer_time, $config->min_print_speed; @@ -607,11 +616,9 @@ sub _update_description { $msg .= sprintf "\nIf estimated layer time is greater, but still below ~%ds, fan will run at a proportionally decreasing speed between %d%% and %d%%.", $config->fan_below_layer_time, $config->max_fan_speed, $config->min_fan_speed; } - if ($config->fan_always_on) { - $msg .= sprintf "\nDuring the other layers, fan will always run at %d%%.", $config->min_fan_speed; - } else { - $msg .= "\nDuring the other layers, fan will be turned off." - } + $msg .= "\nDuring the other layers, fan $fan_other_layers" + } else { + $msg = "Fan $fan_other_layers"; } $self->{description_line}->SetText($msg); } From 6e216e2fd947d96782253da0d44a4ce20899e679 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 11 May 2013 09:24:48 +0200 Subject: [PATCH 071/179] New traverse_pt() --- lib/Slic3r/Geometry/Clipper.pm | 19 ++++++++++++++++++- lib/Slic3r/Print.pm | 33 +++++++-------------------------- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index 801f2646..b6d0eb52 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -6,7 +6,7 @@ require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(safety_offset safety_offset_ex offset offset_ex collapse_ex diff_ex diff union_ex intersection_ex xor_ex PFT_EVENODD JT_MITER JT_ROUND - JT_SQUARE is_counter_clockwise union_pt offset2 offset2_ex); + JT_SQUARE is_counter_clockwise union_pt offset2 offset2_ex traverse_pt); use Math::Clipper 1.21 qw(:cliptypes :polyfilltypes :jointypes is_counter_clockwise area); use Slic3r::Geometry qw(scale); @@ -145,4 +145,21 @@ sub simplify_polygons { return @{ Math::Clipper::simplify_polygons($polygons, $pft // PFT_NONZERO) }; } +sub traverse_pt { + my ($polynodes) = @_; + + # use a nearest neighbor search to order these children + # TODO: supply second argument to chained_path_items() too? + my @nodes = @{Slic3r::Geometry::chained_path_items( + [ map [ ($_->{outer} ? $_->{outer}[0] : $_->{hole}[0]), $_ ], @$polynodes ], + )}; + + my @polygons = (); + foreach my $polynode (@$polynodes) { + push @polygons, traverse_pt($polynode->{children}); + push @polygons, $polynode->{outer} // [ reverse @{$polynode->{hole}} ] + } + return @polygons; +} + 1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index d5589227..9841e4fd 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -6,9 +6,9 @@ use File::Spec; use List::Util qw(max first); use Math::ConvexHull::MonotoneChain qw(convex_hull); use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN PI scale unscale move_points nearest_point chained_path_items); +use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN PI scale unscale move_points nearest_point); use Slic3r::Geometry::Clipper qw(diff_ex union_ex union_pt intersection_ex offset - offset2 JT_ROUND JT_SQUARE PFT_EVENODD); + offset2 traverse_pt JT_ROUND JT_SQUARE PFT_EVENODD); use Time::HiRes qw(gettimeofday tv_interval); has 'config' => (is => 'rw', default => sub { Slic3r::Config->new_from_defaults }, trigger => 1); @@ -683,30 +683,11 @@ sub make_brim { push @loops, offset2(\@islands, ($i - 2) * $flow->scaled_spacing, ($i + 1.5) * $flow->scaled_spacing, undef, JT_SQUARE); } - # prepare a subroutine to traverse the tree and return inner perimeters first - my $traverse; - $traverse = sub { - my ($loops) = @_; - - # use a nearest neighbor search to order these children - # TODO: supply second argument to chained_path_items() too? - @$loops = @{chained_path_items( - [ map [ ($_->{outer} ? $_->{outer}[0] : $_->{hole}[0]), $_ ], @$loops ], - )}; - - my @polygons = (); - foreach my $loop (@$loops) { - push @polygons, $traverse->($loop->{children}); - push @polygons, Slic3r::ExtrusionLoop->pack( - polygon => Slic3r::Polygon->new($loop->{outer} // [ reverse @{$loop->{hole}} ]), - role => EXTR_ROLE_SKIRT, - flow_spacing => $flow->spacing, - ); - } - return @polygons; - }; - - @{$self->brim} = reverse $traverse->( union_pt(\@loops, PFT_EVENODD) ); + @{$self->brim} = map Slic3r::ExtrusionLoop->pack( + polygon => Slic3r::Polygon->new($_), + role => EXTR_ROLE_SKIRT, + flow_spacing => $flow->spacing, + ), reverse traverse_pt( union_pt(\@loops, PFT_EVENODD) ); } sub write_gcode { From 0e100827f8c8cacbe42fba95aaaede578d788e0d Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 11 May 2013 09:35:33 +0200 Subject: [PATCH 072/179] Avoid useless looping and usage of ExPolygons --- lib/Slic3r/Layer/Region.pm | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 6974c8f2..aef13925 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -4,7 +4,8 @@ use Moo; use List::Util qw(sum first); use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(PI X1 X2 Y1 Y2 A B scale chained_path_items points_coincide); -use Slic3r::Geometry::Clipper qw(safety_offset union_ex diff_ex intersection_ex offset2_ex); +use Slic3r::Geometry::Clipper qw(safety_offset union_ex diff_ex intersection_ex + offset offset2_ex); use Slic3r::Surface ':types'; has 'layer' => ( @@ -225,18 +226,16 @@ sub make_perimeters { $spacing /= 2 if $loop == 0; # offsetting a polygon can result in one or many offset polygons - my @new_offsets = (); - foreach my $expolygon (@last_offsets) { - my @offsets = offset2_ex($expolygon, -1.5*$spacing, +0.5*$spacing); - push @new_offsets, @offsets; - - # where the above check collapses the expolygon, then there's no room for an inner loop - # and we can extract the gap for later processing + my @new_offsets = offset2_ex([ map @$_, @last_offsets ], -1.5*$spacing, +0.5*$spacing); + + # where the above check collapses the expolygon, then there's no room for an inner loop + # and we can extract the gap for later processing + { my $diff = diff_ex( - [ map @$_, $expolygon->offset_ex(-0.5*$spacing) ], + [ offset([ map @$_, @last_offsets ], -0.5*$spacing) ], # +2 on the offset here makes sure that Clipper float truncation # won't shrink the clip polygon to be smaller than intended. - [ Slic3r::Geometry::Clipper::offset([map @$_, @offsets], +0.5*$spacing + 2) ], + [ offset([ map @$_, @new_offsets ], +0.5*$spacing + 2) ], ); push @gaps, grep $_->area >= $gap_area_threshold, @$diff; } From df62c25c8f54f8ca88104d930984bdb29af3c594 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 11 May 2013 09:39:38 +0200 Subject: [PATCH 073/179] One more offset_ex() optimization --- lib/Slic3r/Layer/Region.pm | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index aef13925..d8ab156d 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -258,12 +258,11 @@ sub make_perimeters { # non-collapsing regions push @{ $self->fill_surfaces }, map $_->simplify(&Slic3r::SCALED_RESOLUTION), - @{union_ex([ - Slic3r::Geometry::Clipper::offset( - [Slic3r::Geometry::Clipper::offset([ map @$_, @last_offsets ], -($perimeter_spacing/2 + $infill_spacing))], + offset2_ex( + [ map @$_, @last_offsets ], + -($perimeter_spacing/2 + $infill_spacing), +$infill_spacing, - ), - ])}; + ); } # fill gaps From 67b24efd49c944e0f641acdc702e32dc084000b9 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 11 May 2013 21:05:29 +0200 Subject: [PATCH 074/179] Incomplete work - still wrong order --- lib/Slic3r/Geometry/Clipper.pm | 10 +- lib/Slic3r/Layer/Region.pm | 193 ++++++++++----------------------- 2 files changed, 68 insertions(+), 135 deletions(-) diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index b6d0eb52..1eff266e 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -146,7 +146,7 @@ sub simplify_polygons { } sub traverse_pt { - my ($polynodes) = @_; + my ($polynodes, $min_depth, $max_depth) = @_; # use a nearest neighbor search to order these children # TODO: supply second argument to chained_path_items() too? @@ -156,8 +156,14 @@ sub traverse_pt { my @polygons = (); foreach my $polynode (@$polynodes) { - push @polygons, traverse_pt($polynode->{children}); + # traverse the next depth + push @polygons, traverse_pt( + $polynode->{children}, + (defined $min_depth ? $min_depth-1 : undef), + (defined $max_depth ? $max_depth-1 : undef), + ) if !defined $max_depth || $max_depth >= 1; push @polygons, $polynode->{outer} // [ reverse @{$polynode->{hole}} ] + if !defined $min_depth || $min_depth <= 0; } return @polygons; } diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index d8ab156d..c09f4354 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -5,7 +5,7 @@ use List::Util qw(sum first); use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(PI X1 X2 Y1 Y2 A B scale chained_path_items points_coincide); use Slic3r::Geometry::Clipper qw(safety_offset union_ex diff_ex intersection_ex - offset offset2_ex); + offset offset2_ex PFT_EVENODD union_pt traverse_pt); use Slic3r::Surface ':types'; has 'layer' => ( @@ -161,37 +161,22 @@ sub make_perimeters { my $perimeter_spacing = $self->perimeter_flow->scaled_spacing; my $infill_spacing = $self->solid_infill_flow->scaled_spacing; - my $gap_area_threshold = $self->perimeter_flow->scaled_width ** 2; - - # this array will hold one arrayref per original surface (island); - # each item of this arrayref is an arrayref representing a depth (from outer - # perimeters to inner); each item of this arrayref is an ExPolygon: - # @perimeters = ( - # [ # first island - # [ Slic3r::ExPolygon, Slic3r::ExPolygon... ], #depth 0: outer loop - # [ Slic3r::ExPolygon, Slic3r::ExPolygon... ], #depth 1: inner loop - # ], - # [ # second island - # ... - # ] - # ) - my @perimeters = (); # one item per depth; each item - - # organize islands using a nearest-neighbor search - my @surfaces = @{chained_path_items([ - map [ $_->contour->[0], $_ ], @{$self->slices}, - ])}; + my $gap_area_threshold = $self->perimeter_flow->scaled_width ** 2; $self->perimeters([]); $self->fill_surfaces([]); $self->thin_fills([]); + my @contours = (); # array of Polygons with ccw orientation + my @holes = (); # array of Polygons with cw orientation + my @gaps = (); # array of ExPolygons + # for each island: - foreach my $surface (@surfaces) { - my @last_offsets = ($surface->expolygon); + foreach my $surface (@{$self->slices}) { # experimental hole compensation (see ArcCompensation in the RepRap wiki) if (0) { + my @last_offsets = (); # dumb instantiation foreach my $hole ($last_offsets[0]->holes) { my $circumference = abs($hole->length); next unless $circumference <= &Slic3r::SMALL_PERIMETER_LENGTH; @@ -213,42 +198,40 @@ sub make_perimeters { } } - my @gaps = (); - - # generate perimeters inwards (loop 0 is the external one) + # detect how many perimeters must be generated for this island my $loop_number = $Slic3r::Config->perimeters + ($surface->extra_perimeters || 0); - push @perimeters, [] if $loop_number > 0; - # do one more loop (<= instead of <) so that we can detect gaps even after the desired - # number of perimeters has been generated - for (my $loop = 0; $loop <= $loop_number; $loop++) { - my $spacing = $perimeter_spacing; - $spacing /= 2 if $loop == 0; + # generate loops + # (one more than necessary so that we can detect gaps even after the desired + # number of perimeters has been generated) + my @last = @{$surface->expolygon}; + for my $i (0 .. $loop_number) { + # external loop only needs half inset distance + my $spacing = ($i == 0) + ? $perimeter_spacing / 2 + : $perimeter_spacing; - # offsetting a polygon can result in one or many offset polygons - my @new_offsets = offset2_ex([ map @$_, @last_offsets ], -1.5*$spacing, +0.5*$spacing); + my @offsets = offset2_ex(\@last, -1.5*$spacing, +0.5*$spacing); + my @contours_offsets = map $_->contour, @offsets; + my @holes_offsets = map $_->holes, @offsets; + @offsets = (@contours_offsets, @holes_offsets); # turn @offsets from ExPolygons to Polygons - # where the above check collapses the expolygon, then there's no room for an inner loop + # where offset2() collapses the expolygon, then there's no room for an inner loop # and we can extract the gap for later processing { my $diff = diff_ex( - [ offset([ map @$_, @last_offsets ], -0.5*$spacing) ], + [ offset(\@last, -0.5*$spacing) ], # +2 on the offset here makes sure that Clipper float truncation # won't shrink the clip polygon to be smaller than intended. - [ offset([ map @$_, @new_offsets ], +0.5*$spacing + 2) ], + [ offset(\@offsets, +0.5*$spacing + 2) ], ); push @gaps, grep $_->area >= $gap_area_threshold, @$diff; } - last if !@new_offsets || $loop == $loop_number; - @last_offsets = @new_offsets; - - # sort loops before storing them - @last_offsets = @{chained_path_items([ - map [ $_->contour->[0], $_ ], @last_offsets, - ])}; - - push @{ $perimeters[-1] }, [@last_offsets]; + last if !@offsets || $i == $loop_number; + push @contours, @contours_offsets; + push @holes, @holes_offsets; + @last = @offsets; } # create one more offset to be used as boundary for fill @@ -259,7 +242,7 @@ sub make_perimeters { push @{ $self->fill_surfaces }, map $_->simplify(&Slic3r::SCALED_RESOLUTION), offset2_ex( - [ map @$_, @last_offsets ], + \@last, -($perimeter_spacing/2 + $infill_spacing), +$infill_spacing, ); @@ -344,99 +327,43 @@ sub make_perimeters { } } - # process one island (original surface) at time - # islands are already sorted with a nearest-neighbor search - foreach my $island (@perimeters) { - # do holes starting from innermost one - my @holes = (); - my %is_external = (); - - # each item of @$island contains the expolygons having the same depth; - # for each depth we build an arrayref containing all the holes - my @hole_depths = map [ map $_->holes, @$_ ], @$island; - - # organize the outermost hole loops using a nearest-neighbor search - @{$hole_depths[0]} = @{chained_path_items([ - map [ $_->[0], $_ ], @{$hole_depths[0]}, - ])}; - - # loop while we have spare holes - CYCLE: while (map @$_, @hole_depths) { - # remove first depth container if it contains no holes anymore - shift @hole_depths while !@{$hole_depths[0]}; - - # take first available hole - push @holes, shift @{$hole_depths[0]}; - $is_external{$#holes} = 1; - - my $current_depth = 0; - while (1) { - $current_depth++; - - # look for the hole containing this one if any - next CYCLE if !$hole_depths[$current_depth]; - my $parent_hole; - for (@{$hole_depths[$current_depth]}) { - if ($_->encloses_point($holes[-1]->[0])) { - $parent_hole = $_; - last; - } - } - next CYCLE if !$parent_hole; - - # look for other holes contained in such parent - for (@{$hole_depths[$current_depth-1]}) { - if ($parent_hole->encloses_point($_->[0])) { - # we have a sibling, so let's move onto next iteration - next CYCLE; - } - } - - push @holes, $parent_hole; - @{$hole_depths[$current_depth]} = grep $_ ne $parent_hole, @{$hole_depths[$current_depth]}; - } - } - - # first do holes - $self->_add_perimeter($holes[$_], $is_external{$_} ? EXTR_ROLE_EXTERNAL_PERIMETER : undef) - for reverse 0 .. $#holes; - - # then do contours according to the user settings - my @contour_order = 0 .. $#$island; - @contour_order = reverse @contour_order if !$Slic3r::Config->external_perimeters_first; - for my $depth (@contour_order) { - my $role = $depth == $#$island ? EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER - : $depth == 0 ? EXTR_ROLE_EXTERNAL_PERIMETER - : EXTR_ROLE_PERIMETER; - $self->_add_perimeter($_, $role) for map $_->contour, @{$island->[$depth]}; - } - } + # TODO: can these be removed? + @contours = grep $_->is_printable($self->perimeter_flow->scaled_width), @contours; + @holes = grep $_->is_printable($self->perimeter_flow->scaled_width), @holes; - # if brim will be printed, reverse the order of perimeters so that - # we continue inwards after having finished the brim - if ($self->layer->id == 0 && $Slic3r::Config->brim_width > 0) { - @{$self->perimeters} = reverse @{$self->perimeters}; - } + # find nesting hierarchies separately for contours and holes + my $contours_pt = union_pt(\@contours, PFT_EVENODD); + my $holes_pt = union_pt(\@holes, PFT_EVENODD); - # add thin walls as perimeters - push @{ $self->perimeters }, Slic3r::ExtrusionPath::Collection->new(paths => [ - map { - Slic3r::ExtrusionPath->pack( - polyline => ($_->isa('Slic3r::Polygon') ? $_->split_at_first_point : $_), - role => EXTR_ROLE_EXTERNAL_PERIMETER, - flow_spacing => $self->perimeter_flow->spacing, - ); - } @{ $self->thin_walls } - ])->chained_path; + # find external perimeters + my $other_contours_pt = [ ]; + + # external contours are root items of $contours_pt + # internal contours are the ones next to external + my @external_contours = map $self->_perimeter($_, EXTR_ROLE_EXTERNAL_PERIMETER), traverse_pt($contours_pt, 0, 0); + my @internal_contours = map $self->_perimeter($_, EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER), traverse_pt($contours_pt, 1, 1); + my @other_contours = map $self->_perimeter($_), traverse_pt($contours_pt, 2); + my @external_holes = map $self->_perimeter($_, EXTR_ROLE_EXTERNAL_PERIMETER), traverse_pt($holes_pt, 0, 0); + my @other_holes = map $self->_perimeter($_), traverse_pt($holes_pt, 1); + + my @loops = ( + @other_holes, + @external_holes, + @other_contours, + @internal_contours, + @external_contours, + ); + @loops = reverse @loops if $Slic3r::Config->external_perimeters_first; + + push @{ $self->perimeters }, @loops; } -sub _add_perimeter { +sub _perimeter { my $self = shift; my ($polygon, $role) = @_; - return unless $polygon->is_printable($self->perimeter_flow->scaled_width); - push @{ $self->perimeters }, Slic3r::ExtrusionLoop->pack( - polygon => $polygon, + return Slic3r::ExtrusionLoop->pack( + polygon => Slic3r::Polygon->new($polygon), role => ($role // EXTR_ROLE_PERIMETER), flow_spacing => $self->perimeter_flow->spacing, ); From ca549cd2fe3724a6a8599efb579cd986b0d6f2e9 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 11 May 2013 21:30:26 +0200 Subject: [PATCH 075/179] Perimeters refactoring complete --- lib/Slic3r/Geometry/Clipper.pm | 11 ++--- lib/Slic3r/Layer/Region.pm | 78 ++++++++++++++++++++++------------ 2 files changed, 54 insertions(+), 35 deletions(-) diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index 1eff266e..498ccbcf 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -146,7 +146,7 @@ sub simplify_polygons { } sub traverse_pt { - my ($polynodes, $min_depth, $max_depth) = @_; + my ($polynodes) = @_; # use a nearest neighbor search to order these children # TODO: supply second argument to chained_path_items() too? @@ -157,13 +157,8 @@ sub traverse_pt { my @polygons = (); foreach my $polynode (@$polynodes) { # traverse the next depth - push @polygons, traverse_pt( - $polynode->{children}, - (defined $min_depth ? $min_depth-1 : undef), - (defined $max_depth ? $max_depth-1 : undef), - ) if !defined $max_depth || $max_depth >= 1; - push @polygons, $polynode->{outer} // [ reverse @{$polynode->{hole}} ] - if !defined $min_depth || $min_depth <= 0; + push @polygons, traverse_pt($polynode->{children}); + push @polygons, $polynode->{outer} // [ reverse @{$polynode->{hole}} ]; } return @polygons; } diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index c09f4354..7776d084 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -335,38 +335,62 @@ sub make_perimeters { my $contours_pt = union_pt(\@contours, PFT_EVENODD); my $holes_pt = union_pt(\@holes, PFT_EVENODD); - # find external perimeters - my $other_contours_pt = [ ]; - + # prepare a coderef for traversing the PolyTree object # external contours are root items of $contours_pt # internal contours are the ones next to external - my @external_contours = map $self->_perimeter($_, EXTR_ROLE_EXTERNAL_PERIMETER), traverse_pt($contours_pt, 0, 0); - my @internal_contours = map $self->_perimeter($_, EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER), traverse_pt($contours_pt, 1, 1); - my @other_contours = map $self->_perimeter($_), traverse_pt($contours_pt, 2); - my @external_holes = map $self->_perimeter($_, EXTR_ROLE_EXTERNAL_PERIMETER), traverse_pt($holes_pt, 0, 0); - my @other_holes = map $self->_perimeter($_), traverse_pt($holes_pt, 1); - - my @loops = ( - @other_holes, - @external_holes, - @other_contours, - @internal_contours, - @external_contours, - ); - @loops = reverse @loops if $Slic3r::Config->external_perimeters_first; - - push @{ $self->perimeters }, @loops; -} + my $traverse; + $traverse = sub { + my ($polynodes, $depth, $is_contour) = @_; + + # use a nearest neighbor search to order these children + # TODO: supply second argument to chained_path_items() too? + my @nodes = @{Slic3r::Geometry::chained_path_items( + [ map [ ($_->{outer} ? $_->{outer}[0] : $_->{hole}[0]), $_ ], @$polynodes ], + )}; + + my @loops = (); + foreach my $polynode (@$polynodes) { + push @loops, $traverse->($polynode->{children}, $depth+1, $is_contour); -sub _perimeter { - my $self = shift; - my ($polygon, $role) = @_; + my $role = EXTR_ROLE_PERIMETER; + if ($depth == 0) { + $role = EXTR_ROLE_EXTERNAL_PERIMETER; + } elsif ($depth == 1 && $is_contour) { + $role = EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER; + } + push @loops, Slic3r::ExtrusionLoop->pack( + polygon => Slic3r::Polygon->new($polynode->{outer} // [ reverse @{$polynode->{hole}} ]), + role => $role, + flow_spacing => $self->perimeter_flow->spacing, + ); + } + return @loops; + }; - return Slic3r::ExtrusionLoop->pack( - polygon => Slic3r::Polygon->new($polygon), - role => ($role // EXTR_ROLE_PERIMETER), - flow_spacing => $self->perimeter_flow->spacing, + # order loops from inner to outer (in terms of object slices) + my @loops = ( + (reverse $traverse->($holes_pt, 0)), + $traverse->($contours_pt, 0, 1), ); + + # if brim will be printed, reverse the order of perimeters so that + # we continue inwards after having finished the brim + # TODO: add test for perimeter order + @loops = reverse @loops + if $Slic3r::Config->external_perimeters_first + || $self->layer->id == 0 && $Slic3r::Config->brim_width > 0; + push @{ $self->perimeters }, @loops; + + # add thin walls as perimeters + push @{ $self->perimeters }, Slic3r::ExtrusionPath::Collection->new(paths => [ + map { + Slic3r::ExtrusionPath->pack( + polyline => ($_->isa('Slic3r::Polygon') ? $_->split_at_first_point : $_), + role => EXTR_ROLE_EXTERNAL_PERIMETER, + flow_spacing => $self->perimeter_flow->spacing, + ); + } @{ $self->thin_walls } + ])->chained_path; } sub prepare_fill_surfaces { From 1d94618c5e46a2f640a25d7d4982053bf2a74351 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 13 May 2013 12:52:57 +0200 Subject: [PATCH 076/179] Correctly detect external hole perimeters --- lib/Slic3r/Layer/Region.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 7776d084..19a30d87 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -353,7 +353,9 @@ sub make_perimeters { push @loops, $traverse->($polynode->{children}, $depth+1, $is_contour); my $role = EXTR_ROLE_PERIMETER; - if ($depth == 0) { + if ($is_contour ? $depth == 0 : !@{ $polynode->{children} }) { + # external perimeters are root level in case of contours + # and items with no children in case of holes $role = EXTR_ROLE_EXTERNAL_PERIMETER; } elsif ($depth == 1 && $is_contour) { $role = EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER; From 5efa409c7c208e0151a2910eba9e66aafe659daf Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 13 May 2013 13:07:22 +0200 Subject: [PATCH 077/179] Move gap fill to its own method --- lib/Slic3r/Layer/Region.pm | 162 +++++++++++++++++++------------------ 1 file changed, 84 insertions(+), 78 deletions(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 19a30d87..ee0b4226 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -247,86 +247,10 @@ sub make_perimeters { +$infill_spacing, ); } - - # fill gaps - if ($Slic3r::Config->gap_fill_speed > 0 && $Slic3r::Config->fill_density > 0 && @gaps) { - my $filler = $self->layer->object->print->fill_maker->filler('rectilinear'); - $filler->layer_id($self->layer->id); - - # we should probably use this code to handle thin walls and remove that logic from - # make_surfaces(), but we need to enable dynamic extrusion width before as we can't - # use zigzag for thin walls. - # in the mean time we subtract thin walls from the detected gaps so that we don't - # reprocess them, causing overlapping thin walls and zigzag. - @gaps = @{diff_ex( - [ map @$_, @gaps ], - [ map $_->grow($self->perimeter_flow->scaled_width), @{$self->{thin_walls}} ], - 1, - )}; - - my $w = $self->perimeter_flow->width; - my @widths = (1.5 * $w, $w, 0.4 * $w); # worth trying 0.2 too? - foreach my $width (@widths) { - my $flow = $self->perimeter_flow->clone(width => $width); - - # extract the gaps having this width - my @this_width = map $_->offset_ex(+0.5*$flow->scaled_width), - map $_->noncollapsing_offset_ex(-0.5*$flow->scaled_width), - @gaps; - - if (0) { # remember to re-enable t/dynamic.t - # fill gaps using dynamic extrusion width, by treating them like thin polygons, - # thus generating the skeleton and using it to fill them - my %path_args = ( - role => EXTR_ROLE_SOLIDFILL, - flow_spacing => $flow->spacing, - ); - push @{ $self->thin_fills }, map { - $_->isa('Slic3r::Polygon') - ? (map $_->pack, Slic3r::ExtrusionLoop->new(polygon => $_, %path_args)->split_at_first_point) # we should keep these as loops - : Slic3r::ExtrusionPath->pack(polyline => $_, %path_args), - } map $_->medial_axis($flow->scaled_width), @this_width; - - Slic3r::debugf " %d gaps filled with extrusion width = %s\n", scalar @this_width, $width - if @{ $self->thin_fills }; - - } else { - # fill gaps using zigzag infill - - # since this is infill, we have to offset by half-extrusion width inwards - my @infill = map $_->offset_ex(-0.5*$flow->scaled_width), @this_width; - - foreach my $expolygon (@infill) { - my @paths = $filler->fill_surface( - Slic3r::Surface->new(expolygon => $expolygon), - density => 1, - flow_spacing => $flow->spacing, - ); - my $params = shift @paths; - - push @{ $self->thin_fills }, - map { - $_->polyline->simplify($flow->scaled_width / 3); - $_->pack; - } - map Slic3r::ExtrusionPath->new( - polyline => Slic3r::Polyline->new(@$_), - role => EXTR_ROLE_GAPFILL, - height => $self->height, - flow_spacing => $params->{flow_spacing}, - ), @paths; - } - } - - # check what's left - @gaps = @{diff_ex( - [ map @$_, @gaps ], - [ map @$_, @this_width ], - )}; - } - } } + $self->_fill_gaps(\@gaps); + # TODO: can these be removed? @contours = grep $_->is_printable($self->perimeter_flow->scaled_width), @contours; @holes = grep $_->is_printable($self->perimeter_flow->scaled_width), @holes; @@ -395,6 +319,88 @@ sub make_perimeters { ])->chained_path; } +sub _fill_gaps { + my $self = shift; + my ($gaps) = @_; + + return unless $Slic3r::Config->gap_fill_speed > 0 && $Slic3r::Config->fill_density > 0 && @$gaps; + + my $filler = $self->layer->object->print->fill_maker->filler('rectilinear'); + $filler->layer_id($self->layer->id); + + # we should probably use this code to handle thin walls and remove that logic from + # make_surfaces(), but we need to enable dynamic extrusion width before as we can't + # use zigzag for thin walls. + # in the mean time we subtract thin walls from the detected gaps so that we don't + # reprocess them, causing overlapping thin walls and zigzag. + @$gaps = @{diff_ex( + [ map @$_, @$gaps ], + [ map $_->grow($self->perimeter_flow->scaled_width), @{$self->{thin_walls}} ], + 1, + )}; + + my $w = $self->perimeter_flow->width; + my @widths = (1.5 * $w, $w, 0.4 * $w); # worth trying 0.2 too? + foreach my $width (@widths) { + my $flow = $self->perimeter_flow->clone(width => $width); + + # extract the gaps having this width + my @this_width = map $_->offset_ex(+0.5*$flow->scaled_width), + map $_->noncollapsing_offset_ex(-0.5*$flow->scaled_width), + @$gaps; + + if (0) { # remember to re-enable t/dynamic.t + # fill gaps using dynamic extrusion width, by treating them like thin polygons, + # thus generating the skeleton and using it to fill them + my %path_args = ( + role => EXTR_ROLE_SOLIDFILL, + flow_spacing => $flow->spacing, + ); + push @{ $self->thin_fills }, map { + $_->isa('Slic3r::Polygon') + ? (map $_->pack, Slic3r::ExtrusionLoop->new(polygon => $_, %path_args)->split_at_first_point) # we should keep these as loops + : Slic3r::ExtrusionPath->pack(polyline => $_, %path_args), + } map $_->medial_axis($flow->scaled_width), @this_width; + + Slic3r::debugf " %d gaps filled with extrusion width = %s\n", scalar @this_width, $width + if @{ $self->thin_fills }; + + } else { + # fill gaps using zigzag infill + + # since this is infill, we have to offset by half-extrusion width inwards + my @infill = map $_->offset_ex(-0.5*$flow->scaled_width), @this_width; + + foreach my $expolygon (@infill) { + my @paths = $filler->fill_surface( + Slic3r::Surface->new(expolygon => $expolygon), + density => 1, + flow_spacing => $flow->spacing, + ); + my $params = shift @paths; + + push @{ $self->thin_fills }, + map { + $_->polyline->simplify($flow->scaled_width / 3); + $_->pack; + } + map Slic3r::ExtrusionPath->new( + polyline => Slic3r::Polyline->new(@$_), + role => EXTR_ROLE_GAPFILL, + height => $self->height, + flow_spacing => $params->{flow_spacing}, + ), @paths; + } + } + + # check what's left + @$gaps = @{diff_ex( + [ map @$_, @$gaps ], + [ map @$_, @this_width ], + )}; + } +} + sub prepare_fill_surfaces { my $self = shift; From d2425748a39d94919784b54734cf51ad026e64d6 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 13 May 2013 13:18:58 +0200 Subject: [PATCH 078/179] Code cleanup and remove arc compensation code --- lib/Slic3r/Layer/Region.pm | 54 +++++++++++--------------------------- 1 file changed, 15 insertions(+), 39 deletions(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index ee0b4226..65ee99ae 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -171,33 +171,9 @@ sub make_perimeters { my @holes = (); # array of Polygons with cw orientation my @gaps = (); # array of ExPolygons - # for each island: + # we need to process each island separately because we might have different + # extra perimeters for each one foreach my $surface (@{$self->slices}) { - - # experimental hole compensation (see ArcCompensation in the RepRap wiki) - if (0) { - my @last_offsets = (); # dumb instantiation - foreach my $hole ($last_offsets[0]->holes) { - my $circumference = abs($hole->length); - next unless $circumference <= &Slic3r::SMALL_PERIMETER_LENGTH; - # this compensation only works for circular holes, while it would - # overcompensate for hexagons and other shapes having straight edges. - # so we require a minimum number of vertices. - next unless $circumference / @$hole >= 3 * $self->perimeter_flow->scaled_width; - - # revert the compensation done in make_surfaces() and get the actual radius - # of the hole - my $radius = ($circumference / PI / 2) - $self->perimeter_flow->scaled_spacing/2; - my $new_radius = ($self->perimeter_flow->scaled_width + sqrt(($self->perimeter_flow->scaled_width ** 2) + (4*($radius**2)))) / 2; - # holes are always turned to contours, so reverse point order before and after - $hole->reverse; - my @offsetted = $hole->offset(+ ($new_radius - $radius)); - # skip arc compensation when hole is not round (thus leads to multiple offsets) - @$hole = map Slic3r::Point->new($_), @{ $offsetted[0] } if @offsetted == 1; - $hole->reverse; - } - } - # detect how many perimeters must be generated for this island my $loop_number = $Slic3r::Config->perimeters + ($surface->extra_perimeters || 0); @@ -235,18 +211,16 @@ sub make_perimeters { } # create one more offset to be used as boundary for fill - { - # we offset by half the perimeter spacing (to get to the actual infill boundary) - # and then we offset back and forth by the infill spacing to only consider the - # non-collapsing regions - push @{ $self->fill_surfaces }, - map $_->simplify(&Slic3r::SCALED_RESOLUTION), - offset2_ex( - \@last, - -($perimeter_spacing/2 + $infill_spacing), - +$infill_spacing, - ); - } + # we offset by half the perimeter spacing (to get to the actual infill boundary) + # and then we offset back and forth by the infill spacing to only consider the + # non-collapsing regions + push @{ $self->fill_surfaces }, + map $_->simplify(&Slic3r::SCALED_RESOLUTION), + offset2_ex( + \@last, + -($perimeter_spacing/2 + $infill_spacing), + +$infill_spacing, + ); } $self->_fill_gaps(\@gaps); @@ -304,7 +278,9 @@ sub make_perimeters { # TODO: add test for perimeter order @loops = reverse @loops if $Slic3r::Config->external_perimeters_first - || $self->layer->id == 0 && $Slic3r::Config->brim_width > 0; + || ($self->layer->id == 0 && $Slic3r::Config->brim_width > 0); + + # append perimeters push @{ $self->perimeters }, @loops; # add thin walls as perimeters From f6dc7121581f4563952e27a6ecf80ea63f6d26ed Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 13 May 2013 18:22:47 +0200 Subject: [PATCH 079/179] Bugfix: most of the File menu items didn't work with --no-plater. #1112 --- lib/Slic3r/GUI/SkeinPanel.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index 95129b40..5defd274 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -331,7 +331,7 @@ sub config { # retrieve filament presets and build a single config object for them my $filament_config; - if ($self->{plater}->filament_presets == 1 || $self->{mode} eq 'simple') { + if (!$self->{plater} || $self->{plater}->filament_presets == 1 || $self->{mode} eq 'simple') { $filament_config = $self->{options_tabs}{filament}->config; } else { # TODO: handle dirty presets. From 1aae311148eeb42ece0d77d9c35e1896799630ff Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 13 May 2013 18:30:12 +0200 Subject: [PATCH 080/179] Fix menus for --no-plater. #1112 --- lib/Slic3r/GUI.pm | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index b825603b..1c60f857 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -128,8 +128,9 @@ sub OnInit { } # Plater menu - my $platerMenu = Wx::Menu->new; - { + my $platerMenu; + unless ($no_plater) { + $platerMenu = Wx::Menu->new; $platerMenu->Append(MI_PLATER_EXPORT_GCODE, "Export G-code...", 'Export current plate as G-code'); $platerMenu->Append(MI_PLATER_EXPORT_STL, "Export STL...", 'Export current plate as STL'); $platerMenu->Append(MI_PLATER_EXPORT_AMF, "Export AMF...", 'Export current plate as AMF'); @@ -141,14 +142,15 @@ sub OnInit { # Window menu my $windowMenu = Wx::Menu->new; { - $windowMenu->Append(MI_TAB_PLATER, "Select &Plater Tab\tCtrl+1", 'Show the plater'); + my $tab_count = $no_plater ? 3 : 4; + $windowMenu->Append(MI_TAB_PLATER, "Select &Plater Tab\tCtrl+1", 'Show the plater') unless $no_plater; $windowMenu->Append(MI_TAB_PRINT, "Select P&rint Settings Tab\tCtrl+2", 'Show the print settings'); $windowMenu->Append(MI_TAB_FILAMENT, "Select &Filament Settings Tab\tCtrl+3", 'Show the filament settings'); $windowMenu->Append(MI_TAB_PRINTER, "Select Print&er Settings Tab\tCtrl+4", 'Show the printer settings'); - EVT_MENU($frame, MI_TAB_PLATER, sub { $self->{skeinpanel}->select_tab(0) }); - EVT_MENU($frame, MI_TAB_PRINT, sub { $self->{skeinpanel}->select_tab(1) }); - EVT_MENU($frame, MI_TAB_FILAMENT, sub { $self->{skeinpanel}->select_tab(2) }); - EVT_MENU($frame, MI_TAB_PRINTER, sub { $self->{skeinpanel}->select_tab(3) }); + EVT_MENU($frame, MI_TAB_PLATER, sub { $self->{skeinpanel}->select_tab(0) }) unless $no_plater; + EVT_MENU($frame, MI_TAB_PRINT, sub { $self->{skeinpanel}->select_tab($tab_count-3) }); + EVT_MENU($frame, MI_TAB_FILAMENT, sub { $self->{skeinpanel}->select_tab($tab_count-2) }); + EVT_MENU($frame, MI_TAB_PRINTER, sub { $self->{skeinpanel}->select_tab($tab_count-1) }); } # Help menu @@ -175,7 +177,7 @@ sub OnInit { { my $menubar = Wx::MenuBar->new; $menubar->Append($fileMenu, "&File"); - $menubar->Append($platerMenu, "&Plater"); + $menubar->Append($platerMenu, "&Plater") if $platerMenu; $menubar->Append($windowMenu, "&Window"); $menubar->Append($helpMenu, "&Help"); $frame->SetMenuBar($menubar); From 7c894766029a0262e0e9d529fcf85292219581de Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 13 May 2013 18:33:44 +0200 Subject: [PATCH 081/179] Move fan_always_on in the Enable options group. #1166 --- lib/Slic3r/GUI/Tab.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index dec4c864..1dac68ca 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -564,8 +564,9 @@ sub build { $self->add_options_page('Cooling', 'hourglass.png', optgroups => [ { title => 'Enable', - options => [qw(cooling)], + options => [qw(fan_always_on cooling)], lines => [ + Slic3r::GUI::OptionsGroup->single_option_line('fan_always_on'), Slic3r::GUI::OptionsGroup->single_option_line('cooling'), { label => '', @@ -575,7 +576,7 @@ sub build { }, { title => 'Fan settings', - options => [qw(min_fan_speed max_fan_speed bridge_fan_speed disable_fan_first_layers fan_always_on)], + options => [qw(min_fan_speed max_fan_speed bridge_fan_speed disable_fan_first_layers)], lines => [ { label => 'Fan speed', @@ -583,7 +584,6 @@ sub build { }, Slic3r::GUI::OptionsGroup->single_option_line('bridge_fan_speed'), Slic3r::GUI::OptionsGroup->single_option_line('disable_fan_first_layers'), - Slic3r::GUI::OptionsGroup->single_option_line('fan_always_on'), ], }, { From ece6dd8a0b4246c933a4d268e03b86c8d2288999 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 13 May 2013 19:35:42 +0200 Subject: [PATCH 082/179] Only make the inwards move when we have more than one perimeter --- lib/Slic3r/GCode.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index d2a667ef..7cd3d0ef 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -156,7 +156,7 @@ sub extrude_loop { $self->wipe_path($extrusion_path->polyline); # make a little move inwards before leaving loop - if ($loop->role == EXTR_ROLE_EXTERNAL_PERIMETER) { + if ($loop->role == EXTR_ROLE_EXTERNAL_PERIMETER && $Slic3r::Config->perimeters > 1) { # detect angle between last and first segment # the side depends on the original winding order of the polygon (left for contours, right for holes) my @points = $was_clockwise ? (-2, 1) : (1, -2); From baa1a8c736e71c5c426437a8050108e88985c4dc Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 13 May 2013 20:14:33 +0200 Subject: [PATCH 083/179] Move Slic3r::Test::GCodeReader to Slic3r::GCode::Reader --- MANIFEST | 1 + lib/Slic3r.pm | 1 + lib/Slic3r/GCode/Reader.pm | 66 ++++++++++++++++++++++++++++++++++++++ lib/Slic3r/Test.pm | 65 ------------------------------------- t/custom_gcode.t | 2 +- t/fill.t | 2 +- t/layers.t | 2 +- t/retraction.t | 2 +- t/shells.t | 2 +- t/vibrationlimit.t | 2 +- utils/gcode_sectioncut.pl | 2 +- 11 files changed, 75 insertions(+), 72 deletions(-) create mode 100644 lib/Slic3r/GCode/Reader.pm diff --git a/MANIFEST b/MANIFEST index 2a6d0b81..8dacceb3 100644 --- a/MANIFEST +++ b/MANIFEST @@ -26,6 +26,7 @@ lib/Slic3r/Format/STL.pm lib/Slic3r/GCode.pm lib/Slic3r/GCode/CoolingBuffer.pm lib/Slic3r/GCode/MotionPlanner.pm +lib/Slic3r/GCode/Reader.pm lib/Slic3r/Geometry.pm lib/Slic3r/Geometry/Clipper.pm lib/Slic3r/GUI.pm diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 47442f64..48adf8e7 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -47,6 +47,7 @@ use Slic3r::Format::STL; use Slic3r::GCode; use Slic3r::GCode::CoolingBuffer; use Slic3r::GCode::MotionPlanner; +use Slic3r::GCode::Reader; use Slic3r::Geometry qw(PI); use Slic3r::Layer; use Slic3r::Layer::Region; diff --git a/lib/Slic3r/GCode/Reader.pm b/lib/Slic3r/GCode/Reader.pm new file mode 100644 index 00000000..235a0ad7 --- /dev/null +++ b/lib/Slic3r/GCode/Reader.pm @@ -0,0 +1,66 @@ +package Slic3r::GCode::Reader; +use Moo; + +has 'gcode' => (is => 'ro', required => 1); +has 'X' => (is => 'rw', default => sub {0}); +has 'Y' => (is => 'rw', default => sub {0}); +has 'Z' => (is => 'rw', default => sub {0}); +has 'E' => (is => 'rw', default => sub {0}); +has 'F' => (is => 'rw', default => sub {0}); + +our $Verbose = 0; +my @AXES = qw(X Y Z E); + +sub parse { + my $self = shift; + my ($cb) = @_; + + foreach my $raw_line (split /\R+/, $self->gcode) { + print "$raw_line\n" if $Verbose || $ENV{SLIC3R_TESTS_GCODE}; + my $line = $raw_line; + $line =~ s/\s*;(.*)//; # strip comment + next if $line eq ''; + my %info = (comment => $1, raw => $raw_line); + + # parse command + my ($command, @args) = split /\s+/, $line; + my %args = map { /([A-Z])(.*)/; ($1 => $2) } @args; + + # check retraction + if ($command =~ /^G[01]$/) { + foreach my $axis (@AXES) { + if (exists $args{$axis}) { + $info{"dist_$axis"} = $args{$axis} - $self->$axis; + $info{"new_$axis"} = $args{$axis}; + } else { + $info{"dist_$axis"} = 0; + $info{"new_$axis"} = $self->$axis; + } + } + $info{dist_XY} = Slic3r::Line->new([0,0], [@info{qw(dist_X dist_Y)}])->length; + if (exists $args{E}) { + if ($info{dist_E} > 0) { + $info{extruding} = 1; + } elsif ($info{dist_E} < 0) { + $info{retracting} = 1 + } + } else { + $info{travel} = 1; + } + } + + # run callback + $cb->($self, $command, \%args, \%info); + + # update coordinates + if ($command =~ /^(?:G[01]|G92)$/) { + for (@AXES, 'F') { + $self->$_($args{$_}) if exists $args{$_}; + } + } + + # TODO: update temperatures + } +} + +1; diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index a059313f..d6a10f37 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -77,69 +77,4 @@ sub add_facet { } } -package Slic3r::Test::GCodeReader; -use Moo; - -has 'gcode' => (is => 'ro', required => 1); -has 'X' => (is => 'rw', default => sub {0}); -has 'Y' => (is => 'rw', default => sub {0}); -has 'Z' => (is => 'rw', default => sub {0}); -has 'E' => (is => 'rw', default => sub {0}); -has 'F' => (is => 'rw', default => sub {0}); - -our $Verbose = 0; -my @AXES = qw(X Y Z E); - -sub parse { - my $self = shift; - my ($cb) = @_; - - foreach my $line (split /\R+/, $self->gcode) { - print "$line\n" if $Verbose || $ENV{SLIC3R_TESTS_GCODE}; - $line =~ s/\s*;(.*)//; # strip comment - next if $line eq ''; - my $comment = $1; - - # parse command - my ($command, @args) = split /\s+/, $line; - my %args = map { /([A-Z])(.*)/; ($1 => $2) } @args; - my %info = (); - - # check retraction - if ($command =~ /^G[01]$/) { - foreach my $axis (@AXES) { - if (exists $args{$axis}) { - $info{"dist_$axis"} = $args{$axis} - $self->$axis; - $info{"new_$axis"} = $args{$axis}; - } else { - $info{"dist_$axis"} = 0; - $info{"new_$axis"} = $self->$axis; - } - } - $info{dist_XY} = Slic3r::Line->new([0,0], [@info{qw(dist_X dist_Y)}])->length; - if (exists $args{E}) { - if ($info{dist_E} > 0) { - $info{extruding} = 1; - } elsif ($info{dist_E} < 0) { - $info{retracting} = 1 - } - } else { - $info{travel} = 1; - } - } - - # run callback - $cb->($self, $command, \%args, \%info); - - # update coordinates - if ($command =~ /^(?:G[01]|G92)$/) { - for (@AXES, 'F') { - $self->$_($args{$_}) if exists $args{$_}; - } - } - - # TODO: update temperatures - } -} - 1; diff --git a/t/custom_gcode.t b/t/custom_gcode.t index 6c1e1818..9c5fbdcc 100644 --- a/t/custom_gcode.t +++ b/t/custom_gcode.t @@ -20,7 +20,7 @@ use Slic3r::Test; my $print = Slic3r::Test::init_print('2x20x10', config => $conf); my $last_move_was_z_change = 0; - Slic3r::Test::GCodeReader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { + Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { my ($self, $cmd, $args, $info) = @_; if ($last_move_was_z_change && $cmd ne $config->layer_gcode) { diff --git a/t/fill.t b/t/fill.t index d239199e..e0fdd3cc 100644 --- a/t/fill.t +++ b/t/fill.t @@ -111,7 +111,7 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } $config->set('solid_infill_below_area', 20000000); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - Slic3r::Test::GCodeReader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { + Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { my ($self, $cmd, $args, $info) = @_; fail "solid_infill_below_area should be ignored when fill_density is 0" diff --git a/t/layers.t b/t/layers.t index d49938f4..e4fdcb9f 100644 --- a/t/layers.t +++ b/t/layers.t @@ -21,7 +21,7 @@ my $test = sub { my @z = (); my @increments = (); - Slic3r::Test::GCodeReader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { + Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { my ($self, $cmd, $args, $info) = @_; if ($info->{dist_Z}) { diff --git a/t/retraction.t b/t/retraction.t index 1c028893..cb136677 100644 --- a/t/retraction.t +++ b/t/retraction.t @@ -25,7 +25,7 @@ my $test = sub { my $lifted = 0; my $changed_tool = 0; my $wait_for_toolchange = 0; - Slic3r::Test::GCodeReader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { + Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { my ($self, $cmd, $args, $info) = @_; if ($cmd =~ /^T(\d+)/) { diff --git a/t/shells.t b/t/shells.t index a631ff9c..7977f491 100644 --- a/t/shells.t +++ b/t/shells.t @@ -23,7 +23,7 @@ use Slic3r::Test; my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my %layers_with_shells = (); # Z => $count - Slic3r::Test::GCodeReader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { + Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { my ($self, $cmd, $args, $info) = @_; if ($self->Z > 0) { diff --git a/t/vibrationlimit.t b/t/vibrationlimit.t index b4540956..a903b6b8 100644 --- a/t/vibrationlimit.t +++ b/t/vibrationlimit.t @@ -28,7 +28,7 @@ my $test = sub { my %dir_time = (X => 0, Y => 0); my %dir_sleep_time = (X => 0, Y => 0); my $last_cmd_pause = 0; - Slic3r::Test::GCodeReader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { + Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { my ($self, $cmd, $args, $info) = @_; if ($cmd !~ /^G[01]$/) { diff --git a/utils/gcode_sectioncut.pl b/utils/gcode_sectioncut.pl index 39d94112..971d49c3 100644 --- a/utils/gcode_sectioncut.pl +++ b/utils/gcode_sectioncut.pl @@ -41,7 +41,7 @@ my %opt = ( # read paths my %paths = (); # z => [ path, path ... ] - Slic3r::Test::GCodeReader->new(gcode => io($input_file)->all)->parse(sub { + Slic3r::GCode::Reader->new(gcode => io($input_file)->all)->parse(sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'G1' && $info->{extruding}) { From ccdb29ddc9eb1e383b98832a1a7ab225cebc016b Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 13 May 2013 20:15:45 +0200 Subject: [PATCH 084/179] New --spiral-vase option to continously raise Z when printing single-walled vases. #997 --- MANIFEST | 1 + README.markdown | 2 ++ lib/Slic3r.pm | 1 + lib/Slic3r/Config.pm | 7 ++++++ lib/Slic3r/GCode/SpiralVase.pm | 40 ++++++++++++++++++++++++++++++++++ lib/Slic3r/GUI/Tab.pm | 2 +- lib/Slic3r/Print.pm | 27 +++++++++++++++++++++++ slic3r.pl | 2 ++ 8 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 lib/Slic3r/GCode/SpiralVase.pm diff --git a/MANIFEST b/MANIFEST index 8dacceb3..bc338a5c 100644 --- a/MANIFEST +++ b/MANIFEST @@ -27,6 +27,7 @@ lib/Slic3r/GCode.pm lib/Slic3r/GCode/CoolingBuffer.pm lib/Slic3r/GCode/MotionPlanner.pm lib/Slic3r/GCode/Reader.pm +lib/Slic3r/GCode/SpiralVase.pm lib/Slic3r/Geometry.pm lib/Slic3r/Geometry/Clipper.pm lib/Slic3r/GUI.pm diff --git a/README.markdown b/README.markdown index dabd97a8..01c43d22 100644 --- a/README.markdown +++ b/README.markdown @@ -198,6 +198,8 @@ The author of the Silk icon set is Mark James. --randomize-start Randomize starting point across layers (default: yes) --avoid-crossing-perimeters Optimize travel moves so that no perimeters are crossed (default: no) --external-perimeters-first Reverse perimeter order. (default: no) + --spiral-vase Experimental option to raise Z gradually when printing single-walled vases + (default: no) --only-retract-when-crossing-perimeters Disable retraction when travelling between infill paths inside the same island. (default: yes) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 48adf8e7..9214022f 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -48,6 +48,7 @@ use Slic3r::GCode; use Slic3r::GCode::CoolingBuffer; use Slic3r::GCode::MotionPlanner; use Slic3r::GCode::Reader; +use Slic3r::GCode::SpiralVase; use Slic3r::Geometry qw(PI); use Slic3r::Layer; use Slic3r::Layer::Region; diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 4705552b..3fc8c283 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -586,6 +586,13 @@ our $Options = { type => 'bool', default => 0, }, + 'spiral_vase' => { + label => 'Spiral vase', + tooltip => 'This experimental feature will raise Z gradually while printing a single-walled object in order to remove any visible seam. By enabling this option other settings will be overridden to enforce a single perimeter, no infill, no top solid layers, no support material. You can still set any number of bottom solid layers as well as skirt/brim loops. It won\'t work when printing more than an object.', + cli => 'spiral-vase!', + type => 'bool', + default => 0, + }, 'only_retract_when_crossing_perimeters' => { label => 'Only retract when crossing perimeters', tooltip => 'Disables retraction when travelling between infill paths inside the same island.', diff --git a/lib/Slic3r/GCode/SpiralVase.pm b/lib/Slic3r/GCode/SpiralVase.pm new file mode 100644 index 00000000..5c0a50fd --- /dev/null +++ b/lib/Slic3r/GCode/SpiralVase.pm @@ -0,0 +1,40 @@ +package Slic3r::GCode::SpiralVase; +use Moo; + +use Slic3r::Geometry qw(unscale); + +sub process_layer { + my $self = shift; + my ($gcode, $layer) = @_; + + my $total_layer_length = 0; + Slic3r::GCode::Reader->new(gcode => $gcode)->parse(sub { + my ($reader, $cmd, $args, $info) = @_; + $total_layer_length += $info->{dist_XY} + if $cmd eq 'G1' && $info->{extruding}; + }); + + my $new_gcode = ""; + my $layer_height = $layer->height; + my $z = unscale($layer->print_z) - $layer_height; + Slic3r::GCode::Reader->new(gcode => $gcode)->parse(sub { + my ($reader, $cmd, $args, $info) = @_; + + if ($cmd eq 'G1' && exists $args->{Z}) { + my $line = $info->{raw}; + $line =~ s/Z([^ ]+)/Z$z/; + $new_gcode .= "$line\n"; + } elsif ($cmd eq 'G1' && !exists $args->{Z} && $info->{extruding} && $info->{dist_XY}) { + $z += $info->{dist_XY} * $layer_height / $total_layer_length; + my $line = $info->{raw}; + $line =~ s/^G1 /sprintf 'G1 Z%.3f ', $z/e; + $new_gcode .= "$line\n"; + } else { + $new_gcode .= "$info->{raw}\n"; + } + }); + + return $new_gcode; +} + +1; diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 1dac68ca..c985e93c 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -412,7 +412,7 @@ sub build { }, { title => 'Advanced', - options => [qw(avoid_crossing_perimeters external_perimeters_first)], + options => [qw(avoid_crossing_perimeters external_perimeters_first spiral_vase)], }, ]); diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 9841e4fd..1d84ad64 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -63,6 +63,14 @@ sub _trigger_config { # G-code flavors $self->config->set('extrusion_axis', 'A') if $self->config->gcode_flavor eq 'mach3'; $self->config->set('extrusion_axis', '') if $self->config->gcode_flavor eq 'no-extrusion'; + + # enforce some settings when spiral_vase is set + if ($self->config->spiral_vase) { + $self->config->set('perimeters', 1); + $self->config->set('fill_density', 0); + $self->config->set('top_solid_layers', 0); + $self->config->set('support_material', 0); + } } sub _build_has_support_material { @@ -184,6 +192,12 @@ sub validate { } } } + + if ($Slic3r::Config->spiral_vase) { + if ((map @{$_->copies}, @{$self->objects}) > 1) { + die "The Spiral Vase option can only be used when printing a single object.\n"; + } + } } sub init_extruders { @@ -794,6 +808,11 @@ sub write_gcode { )); } + # prepare the SpiralVase processor if it's possible + my $spiralvase = $Slic3r::Config->spiral_vase + ? Slic3r::GCode::SpiralVase->new + : undef; + # prepare the logic to print one layer my $skirt_done = 0; # count of skirt layers done my $brim_done = 0; @@ -956,6 +975,14 @@ sub write_gcode { } } } + + # apply spiral vase post-processing if this layer contains suitable geometry + $gcode = $spiralvase->process_layer($gcode, $layer) + if defined $spiralvase + && ($layer->id > 0 || $Slic3r::Config->brim_width == 0) + && ($layer->id >= $Slic3r::Config->skirt_height) + && ($layer->id >= $Slic3r::Config->bottom_solid_layers); + return $gcode; }; diff --git a/slic3r.pl b/slic3r.pl index 9850a991..b3dec496 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -251,6 +251,8 @@ $j --randomize-start Randomize starting point across layers (default: yes) --avoid-crossing-perimeters Optimize travel moves so that no perimeters are crossed (default: no) --external-perimeters-first Reverse perimeter order. (default: no) + --spiral-vase Experimental option to raise Z gradually when printing single-walled vases + (default: no) --only-retract-when-crossing-perimeters Disable retraction when travelling between infill paths inside the same island. (default: no) From a73020c10e586b35db54d52103fdbed5ce79c044 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 13 May 2013 21:22:57 +0200 Subject: [PATCH 085/179] Faster implementation of concentric infill, with loop order reversed so that smaller loops will be printed at the end. #898 --- lib/Slic3r/ExtrusionPath/Collection.pm | 3 ++ lib/Slic3r/Fill.pm | 1 + lib/Slic3r/Fill/Concentric.pm | 54 +++++++------------------- 3 files changed, 18 insertions(+), 40 deletions(-) diff --git a/lib/Slic3r/ExtrusionPath/Collection.pm b/lib/Slic3r/ExtrusionPath/Collection.pm index f523a307..5a4af96d 100644 --- a/lib/Slic3r/ExtrusionPath/Collection.pm +++ b/lib/Slic3r/ExtrusionPath/Collection.pm @@ -2,6 +2,7 @@ package Slic3r::ExtrusionPath::Collection; use Moo; has 'paths' => (is => 'rw', default => sub { [] }); +has 'no_sort' => (is => 'rw'); # no-op sub unpack { $_[0] } @@ -15,6 +16,8 @@ sub chained_path { my $self = shift; my ($start_near) = @_; + return @{$self->paths} if $self->no_sort; + # make sure we pass the same path objects to the Collection constructor # and the ->chained_path() method because the latter will reverse the # paths in-place when needed and we need to return them that way diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index 17742005..68b677c9 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -176,6 +176,7 @@ sub make_fill { # save into layer next unless @paths; push @fills, Slic3r::ExtrusionPath::Collection->new( + no_sort => $params->{no_sort}, paths => [ map Slic3r::ExtrusionPath->pack( polyline => Slic3r::Polyline->new(@$_), diff --git a/lib/Slic3r/Fill/Concentric.pm b/lib/Slic3r/Fill/Concentric.pm index 0f1cce8d..0d848809 100644 --- a/lib/Slic3r/Fill/Concentric.pm +++ b/lib/Slic3r/Fill/Concentric.pm @@ -3,8 +3,8 @@ use Moo; extends 'Slic3r::Fill::Base'; -use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Geometry qw(scale unscale X1 Y1 X2 Y2); +use Slic3r::Geometry qw(scale unscale X1 X2); +use Slic3r::Geometry::Clipper qw(offset2 union_pt traverse_pt PFT_EVENODD); sub fill_surface { my $self = shift; @@ -27,48 +27,22 @@ sub fill_surface { $flow_spacing = unscale $distance; } - my @contour_loops = (); - my @hole_loops = (); - my @last_offsets = ($expolygon->offset_ex($distance)); - while (@last_offsets) { - my @new_offsets = (); - foreach my $last_expolygon (@last_offsets) { - my @offsets = $last_expolygon->offset_ex(-$distance); - foreach my $offset (@offsets) { - push @new_offsets, $offset; - push @contour_loops, $offset->contour; - push @hole_loops, $offset->holes; - } - } - @last_offsets = @new_offsets; + my @loops = my @last = @$expolygon; + while (@last) { + push @loops, @last = offset2(\@last, -1.5*$distance, +0.5*$distance); } - my @loops = (@contour_loops, reverse @hole_loops); + # generate paths from the outermost to the innermost, to avoid + # adhesion problems of the first central tiny loops + my @paths = map Slic3r::Polygon->new(@$_)->split_at_first_point, + reverse traverse_pt( union_pt(\@loops, PFT_EVENODD) ); - # make paths - my @paths = (); - my $cur_pos = Slic3r::Point->new( - ($bounding_box->[X1] + $bounding_box->[X2]) / 2, - ($bounding_box->[Y1] + $bounding_box->[Y2]) / 2, - ); - foreach my $loop (@loops) { - # extrude all loops ccw - $loop->make_counter_clockwise; - - # find the point of the loop that is closest to the current extruder position - my $index = $loop->nearest_point_index_to($cur_pos); - $cur_pos = $loop->[0]; - - # split the loop at the starting point and make a path - my $path = $loop->split_at_index($index); - - # clip the path to avoid the extruder to get exactly on the first point of the loop - $path->clip_end(scale $flow_spacing * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_SPACING); - - push @paths, $path if @$path; - } + # clip the paths to avoid the extruder to get exactly on the first point of the loop + my $clip_length = scale $flow_spacing * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_SPACING; + $_->clip_end($clip_length) for @paths; - return { flow_spacing => $flow_spacing }, @paths; + # TODO: return ExtrusionLoop objects to get better chained paths + return { flow_spacing => $flow_spacing, no_sort => 1 }, @paths; } 1; From a86c48d85c71e45c6f710d2cf06bec0152b29bf0 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 13 May 2013 21:55:34 +0200 Subject: [PATCH 086/179] Disable retract on layer change when doing spiral vase; also check that we're printing a single-material object --- lib/Slic3r/Print.pm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 1d84ad64..77ee41e0 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -70,6 +70,7 @@ sub _trigger_config { $self->config->set('fill_density', 0); $self->config->set('top_solid_layers', 0); $self->config->set('support_material', 0); + $self->config->set('retract_layer_change', [0]); # TODO: only apply this to the spiral layers } } @@ -197,6 +198,9 @@ sub validate { if ((map @{$_->copies}, @{$self->objects}) > 1) { die "The Spiral Vase option can only be used when printing a single object.\n"; } + if (@{$self->regions} > 1) { + die "The Spiral Vase option can only be used when printing single material objects.\n"; + } } } From 7979dd9e9c5fc99648fdb86d9ef4323e9479f468 Mon Sep 17 00:00:00 2001 From: Mark Hindess Date: Mon, 13 May 2013 21:24:49 +0100 Subject: [PATCH 087/179] Avoid expensive TriangleMesh BUILD method during clone. Gives 6% speedup and uses less memory when slicing yoda.stl and should give significantly bigger speedup on more complex models and plates. --- lib/Slic3r/TriangleMesh.pm | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm index 74efc39d..39bfef4c 100644 --- a/lib/Slic3r/TriangleMesh.pm +++ b/lib/Slic3r/TriangleMesh.pm @@ -3,6 +3,7 @@ use Moo; use Slic3r::Geometry qw(X Y Z A B unscale same_point); use Slic3r::Geometry::Clipper qw(union_ex); +use Storable; # public has 'vertices' => (is => 'ro', required => 1); # id => [$x,$y,$z] @@ -92,11 +93,7 @@ sub merge { } sub clone { - my $self = shift; - return (ref $self)->new( - vertices => [ map [ @$_ ], @{$self->vertices} ], - facets => [ map [ @$_ ], @{$self->facets} ], - ); + Storable::dclone($_[0]) } sub _facet_edges { From 4121a7ece596daabef32a40f01905c8530989bb8 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 14 May 2013 12:42:48 +0200 Subject: [PATCH 088/179] Set support material enforce layers to 0 when spiral printing --- lib/Slic3r/Print.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 77ee41e0..a581df4e 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -70,6 +70,7 @@ sub _trigger_config { $self->config->set('fill_density', 0); $self->config->set('top_solid_layers', 0); $self->config->set('support_material', 0); + $self->config->set('support_material_enforce_layers', 0); $self->config->set('retract_layer_change', [0]); # TODO: only apply this to the spiral layers } } From 25bafe7ff11601e95627fc6e81af905b33e52ea7 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 14 May 2013 13:52:29 +0200 Subject: [PATCH 089/179] Memory (and maybe speed?) optimization in STL reader --- lib/Slic3r/Format/STL.pm | 52 +++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/lib/Slic3r/Format/STL.pm b/lib/Slic3r/Format/STL.pm index 5c992230..65956809 100644 --- a/lib/Slic3r/Format/STL.pm +++ b/lib/Slic3r/Format/STL.pm @@ -33,26 +33,11 @@ sub read_file { } my $facets = []; - $mode eq 'ascii' - ? _read_ascii($fh, $facets) - : _read_binary($fh, $facets); - close $fh; - my $vertices = []; - { - my %vertices_map = (); # given a vertex's coordinates, what's its index? - for (my $f = 0; $f <= $#$facets; $f++) { - for (-3..-1) { - my $point_id = join ',', @{$facets->[$f][$_]}; - if (exists $vertices_map{$point_id}) { - $facets->[$f][$_] = $vertices_map{$point_id}; - } else { - push @$vertices, $facets->[$f][$_]; - $facets->[$f][$_] = $vertices_map{$point_id} = $#$vertices; - } - } - } - } + $mode eq 'ascii' + ? _read_ascii($fh, $facets, $vertices) + : _read_binary($fh, $facets, $vertices); + close $fh; my $model = Slic3r::Model->new; my $object = $model->add_object(vertices => $vertices); @@ -61,11 +46,12 @@ sub read_file { } sub _read_ascii { - my ($fh, $facets) = @_; + my ($fh, $facets, $vertices) = @_; my $point_re = qr/([^ ]+)\s+([^ ]+)\s+([^ ]+)/; my $facet; + my %vertices_map = (); seek $fh, 0, 0; while (my $_ = <$fh>) { if (!$facet) { @@ -77,7 +63,15 @@ sub _read_ascii { undef $facet; } else { /^\s*vertex\s+$point_re/o or next; - push @$facet, [map $_ * 1, $1, $2, $3]; + my $vertex_id = join ',', $1, $2, $3; + my $vertex_idx; + if (exists $vertices_map{$vertex_id}) { + $vertex_idx = $vertices_map{$vertex_id}; + } else { + push @$vertices, [map $_ * 1, $1, $2, $3]; + $vertex_idx = $vertices_map{$vertex_id} = $#$vertices; + } + push @$facet, $vertex_idx; } } } @@ -87,15 +81,25 @@ sub _read_ascii { } sub _read_binary { - my ($fh, $facets) = @_; + my ($fh, $facets, $vertices) = @_; die "bigfloat" unless length(pack "f", 1) == 4; + my %vertices_map = (); binmode $fh; seek $fh, 80 + 4, 0; while (read $fh, my $_, 4*4*3+2) { - my @v = unpack '(f<3)4'; - push @$facets, [ [@v[3..5]], [@v[6..8]], [@v[9..11]] ]; # ignore normal: [@v[0..2]] + push @$facets, my $facet = []; + for (unpack 'x[f3](a[f3])3') { # ignore normal + my $vertex_idx; + if (exists $vertices_map{$_}) { + $vertex_idx = $vertices_map{$_}; + } else { + push @$vertices, [ unpack 'f<3', $_ ]; + $vertex_idx = $vertices_map{$_} = $#$vertices; + } + push @$facet, $vertex_idx; + } } } From 745cea8e6c9aee1757aae6c5014bd926b3766131 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 14 May 2013 14:12:32 +0200 Subject: [PATCH 090/179] One more little speed optimization in STL.pm --- lib/Slic3r/Format/STL.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/Format/STL.pm b/lib/Slic3r/Format/STL.pm index 65956809..6b103cbd 100644 --- a/lib/Slic3r/Format/STL.pm +++ b/lib/Slic3r/Format/STL.pm @@ -55,8 +55,8 @@ sub _read_ascii { seek $fh, 0, 0; while (my $_ = <$fh>) { if (!$facet) { - /^\s*facet\s+normal\s+$point_re/ or next; - $facet = []; # ignore normal: [$1, $2, $3] + /^\s*facet\s+normal\s+/ or next; + $facet = []; # ignore normal } else { if (/^\s*endfacet/) { push @$facets, $facet; From e51dbb994d7c9c395aa67bffb6e146819a9210bc Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 14 May 2013 14:17:46 +0200 Subject: [PATCH 091/179] One more speed optimization in STL.pm --- lib/Slic3r/Format/STL.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/Format/STL.pm b/lib/Slic3r/Format/STL.pm index 6b103cbd..0d0331c8 100644 --- a/lib/Slic3r/Format/STL.pm +++ b/lib/Slic3r/Format/STL.pm @@ -48,7 +48,7 @@ sub read_file { sub _read_ascii { my ($fh, $facets, $vertices) = @_; - my $point_re = qr/([^ ]+)\s+([^ ]+)\s+([^ ]+)/; + my $point_re = qr/(([^ ]+)\s+([^ ]+)\s+([^ ]+))/; my $facet; my %vertices_map = (); @@ -63,12 +63,12 @@ sub _read_ascii { undef $facet; } else { /^\s*vertex\s+$point_re/o or next; - my $vertex_id = join ',', $1, $2, $3; + my $vertex_id = $1; my $vertex_idx; if (exists $vertices_map{$vertex_id}) { $vertex_idx = $vertices_map{$vertex_id}; } else { - push @$vertices, [map $_ * 1, $1, $2, $3]; + push @$vertices, [map $_ * 1, $2, $3, $4]; $vertex_idx = $vertices_map{$vertex_id} = $#$vertices; } push @$facet, $vertex_idx; From 7eff002e210c537d880d4c17eaf884bb1024d314 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 14 May 2013 14:47:00 +0200 Subject: [PATCH 092/179] Memory and speed optimization: only study meshes when needed --- lib/Slic3r/TriangleMesh.pm | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm index 39bfef4c..99af2552 100644 --- a/lib/Slic3r/TriangleMesh.pm +++ b/lib/Slic3r/TriangleMesh.pm @@ -29,8 +29,8 @@ use constant I_FACET_EDGE => 6; use constant FE_TOP => 0; use constant FE_BOTTOM => 1; -# always make sure BUILD is idempotent -sub BUILD { +# always make sure this method is idempotent +sub analyze { my $self = shift; @{$self->edges} = (); @@ -145,6 +145,8 @@ sub clean { sub check_manifoldness { my $self = shift; + $self->analyze; + # look for any edges not connected to exactly two facets my ($first_bad_edge_id) = grep { @{ $self->edges_facets->[$_] } != 2 } 0..$#{$self->edges_facets}; @@ -154,6 +156,10 @@ sub check_manifoldness { map @{$self->vertices->[$_]}, @{$self->edges->[$first_bad_edge_id]}; return 0; } + + # empty the edges array as we don't really need it anymore + @{$self->edges} = (); + return 1; } From 06ad6b70f8bbfa045766e2588fca857553505190 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 14 May 2013 16:31:50 +0200 Subject: [PATCH 093/179] Bugfix: scaling object from the plater applied scale factor over the factor used in the previous slicing job. #1075 --- Build.PL | 1 + lib/Slic3r/GUI/Plater.pm | 8 +++++++- lib/Slic3r/Model.pm | 3 +++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Build.PL b/Build.PL index b09e9e87..d7d4d8e8 100644 --- a/Build.PL +++ b/Build.PL @@ -19,6 +19,7 @@ my $build = Module::Build->new( 'Moo' => '0.091009', 'perl' => '5.10.0', 'Scalar::Util' => '0', + 'Storable' => '0', 'Time::HiRes' => '0', }, build_requires => { diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 36e0186e..ae66ba8e 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -692,6 +692,12 @@ sub make_model { my $model = Slic3r::Model->new; foreach my $plater_object (@{$self->{objects}}) { my $model_object = $plater_object->get_model_object; + + # if we need to alter the mesh, clone it first + if ($plater_object->scale != 1) { + $model_object = $model_object->clone; + } + my $new_model_object = $model->add_object( vertices => $model_object->vertices, input_file => $plater_object->input_file, @@ -1091,7 +1097,7 @@ sub free_model_object { my $self = shift; # only delete mesh from memory if we can retrieve it from the original file - return unless $self->input_file && $self->input_file_object_id; + return unless $self->input_file && defined $self->input_file_object_id; $self->model_object(undef); } diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index fef1f834..58473fa9 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -75,6 +75,7 @@ use Moo; use List::Util qw(first); use Slic3r::Geometry qw(X Y Z); +use Storable qw(dclone); has 'input_file' => (is => 'rw'); has 'model' => (is => 'ro', weak_ref => 1, required => 1); @@ -145,6 +146,8 @@ sub check_manifoldness { return (first { !$_->mesh->check_manifoldness } @{$self->volumes}) ? 0 : 1; } +sub clone { dclone($_[0]) } + package Slic3r::Model::Volume; use Moo; From 632652d92424051b5610e52414e9ef69d4f645af Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 10 May 2013 20:53:49 +0200 Subject: [PATCH 094/179] Unfinished work for displaying bridge paths in section cuts --- lib/Slic3r/ExtrusionPath.pm | 6 ++++ lib/Slic3r/Test/SectionCut.pm | 64 +++++++++++++++++++++++++---------- 2 files changed, 53 insertions(+), 17 deletions(-) diff --git a/lib/Slic3r/ExtrusionPath.pm b/lib/Slic3r/ExtrusionPath.pm index 87f6d3a1..30be3651 100644 --- a/lib/Slic3r/ExtrusionPath.pm +++ b/lib/Slic3r/ExtrusionPath.pm @@ -111,6 +111,12 @@ sub is_fill { || $self->role == EXTR_ROLE_TOPSOLIDFILL; } +sub is_bridge { + my $self = shift; + return $self->role == EXTR_ROLE_BRIDGE + || $self->role == EXTR_ROLE_INTERNALBRIDGE; +} + sub split_at_acute_angles { my $self = shift; diff --git a/lib/Slic3r/Test/SectionCut.pm b/lib/Slic3r/Test/SectionCut.pm index bab54241..d0ada7f4 100644 --- a/lib/Slic3r/Test/SectionCut.pm +++ b/lib/Slic3r/Test/SectionCut.pm @@ -1,6 +1,7 @@ package Slic3r::Test::SectionCut; use Moo; +use List::Util qw(first max); use Slic3r::Geometry qw(X Y A B X1 Y1 X2 Y2 unscale); use Slic3r::Geometry::Clipper qw(union_ex); use SVG; @@ -24,7 +25,7 @@ sub export_svg { my ($filename) = @_; my $print_size = $self->print->size; - $self->height(unscale($print_size->[X])); + $self->height(unscale max(map $_->print_z, map @{$_->layers}, @{$self->print->objects})); my $svg = SVG->new( width => $self->scale * unscale($print_size->[X]), height => $self->scale * $self->height, @@ -33,7 +34,9 @@ sub export_svg { my $group = sub { my %p = @_; my $g = $svg->group(style => $p{style}); - $g->rectangle(%$_) for $self->_get_rectangles($p{filter}); + my $items = $self->_plot($p{filter}); + $g->rectangle(%$_) for @{ $items->{rectangles} }; + $g->circle(%$_) for @{ $items->{circles} }; }; $group->( @@ -69,11 +72,11 @@ sub export_svg { printf "Section cut SVG written to %s\n", $filename; } -sub _get_rectangles { +sub _plot { my $self = shift; my ($filter) = @_; - my @rectangles = (); + my (@rectangles, @circles) = (); foreach my $object (@{$self->print->objects}) { foreach my $copy (@{$object->copies}) { @@ -93,26 +96,53 @@ sub _get_rectangles { Slic3r::ExPolygon->new($line->grow(Slic3r::Geometry::scale $path->flow_spacing/2)), [ $self->line ], ) }; + die "Intersection has more than two points!\n" if first { @$_ > 2 } @intersections; - push @rectangles, map { - die "Intersection has more than two points!\n" if @$_ > 2; - my $height = $path->height // $layer->height; - { - 'x' => $self->scale * unscale $_->[A][X], - 'y' => $self->scale * $self->_y(unscale($layer->print_z)), - 'width' => $self->scale * unscale(abs($_->[B][X] - $_->[A][X])), - 'height' => $self->scale * $height, - 'rx' => $self->scale * $height * 0.35, - 'ry' => $self->scale * $height * 0.35, - }; - } @intersections; + if ($path->is_bridge) { + foreach my $line (@intersections) { + my $radius = $path->flow_spacing / 2; + my $width = abs($line->[B][X] - $line->[A][X]); + if ((10 * Slic3r::Geometry::scale $radius) < $width) { + # we're cutting the path in the longitudinal direction, so we've got a rectangle + push @rectangles, { + 'x' => $self->scale * unscale $line->[A][X], + 'y' => $self->scale * $self->_y(unscale($layer->print_z)), + 'width' => $self->scale * unscale(abs($line->[B][X] - $line->[A][X])), + 'height' => $self->scale * $radius * 2, + 'rx' => $self->scale * $radius * 0.35, + 'ry' => $self->scale * $radius * 0.35, + }; + } else { + push @circles, { + 'cx' => $self->scale * unscale($line->[A][X] + $radius), + 'cy' => $self->scale * $self->_y(unscale($layer->print_z - $radius)), + 'r' => $self->scale * unscale $radius, + }; + } + } + } else { + push @rectangles, map { + my $height = $path->height // $layer->height; + { + 'x' => $self->scale * unscale $_->[A][X], + 'y' => $self->scale * $self->_y(unscale($layer->print_z)), + 'width' => $self->scale * unscale(abs($_->[B][X] - $_->[A][X])), + 'height' => $self->scale * $height, + 'rx' => $self->scale * $height * 0.35, + 'ry' => $self->scale * $height * 0.35, + }; + } @intersections; + } } } } } } - return @rectangles; + return { + rectangles => \@rectangles, + circles => \@circles, + }; } sub _y { From 967d567db0a7bc483b5b8320c1e6a730be0e1722 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 13 May 2013 15:21:26 +0200 Subject: [PATCH 095/179] Fix bridge rendering in SectionCut --- lib/Slic3r/Test/SectionCut.pm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/Test/SectionCut.pm b/lib/Slic3r/Test/SectionCut.pm index d0ada7f4..2ca30de4 100644 --- a/lib/Slic3r/Test/SectionCut.pm +++ b/lib/Slic3r/Test/SectionCut.pm @@ -101,22 +101,22 @@ sub _plot { if ($path->is_bridge) { foreach my $line (@intersections) { my $radius = $path->flow_spacing / 2; - my $width = abs($line->[B][X] - $line->[A][X]); + my $width = unscale abs($line->[B][X] - $line->[A][X]); if ((10 * Slic3r::Geometry::scale $radius) < $width) { # we're cutting the path in the longitudinal direction, so we've got a rectangle push @rectangles, { 'x' => $self->scale * unscale $line->[A][X], 'y' => $self->scale * $self->_y(unscale($layer->print_z)), - 'width' => $self->scale * unscale(abs($line->[B][X] - $line->[A][X])), + 'width' => $self->scale * $width, 'height' => $self->scale * $radius * 2, 'rx' => $self->scale * $radius * 0.35, 'ry' => $self->scale * $radius * 0.35, }; } else { push @circles, { - 'cx' => $self->scale * unscale($line->[A][X] + $radius), - 'cy' => $self->scale * $self->_y(unscale($layer->print_z - $radius)), - 'r' => $self->scale * unscale $radius, + 'cx' => $self->scale * unscale($line->[A][X]) + $radius, + 'cy' => $self->scale * $self->_y(unscale($layer->print_z) - $radius), + 'r' => $self->scale * $radius, }; } } From 90280fe63b61139078c7ec37255fff7251d641d9 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 14 May 2013 20:11:05 +0200 Subject: [PATCH 096/179] Little fix for bridge rendering in SectionCut --- lib/Slic3r/Test/SectionCut.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Test/SectionCut.pm b/lib/Slic3r/Test/SectionCut.pm index 2ca30de4..515ddd86 100644 --- a/lib/Slic3r/Test/SectionCut.pm +++ b/lib/Slic3r/Test/SectionCut.pm @@ -114,7 +114,7 @@ sub _plot { }; } else { push @circles, { - 'cx' => $self->scale * unscale($line->[A][X]) + $radius, + 'cx' => $self->scale * (unscale($line->[A][X]) + $radius), 'cy' => $self->scale * $self->_y(unscale($layer->print_z) - $radius), 'r' => $self->scale * $radius, }; From 575127151b5178eeb194610a13fb032a3c1c0ff7 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 14 May 2013 20:19:42 +0200 Subject: [PATCH 097/179] New algorithm for overhang detection --- lib/Slic3r/ExtrusionPath.pm | 9 ++++++--- lib/Slic3r/Flow.pm | 4 +++- lib/Slic3r/GCode.pm | 5 +++-- lib/Slic3r/Geometry/Clipper.pm | 15 ++++++++++++++- lib/Slic3r/Layer/Region.pm | 22 +++++++++++++++++++--- 5 files changed, 45 insertions(+), 10 deletions(-) diff --git a/lib/Slic3r/ExtrusionPath.pm b/lib/Slic3r/ExtrusionPath.pm index 30be3651..7e759fc9 100644 --- a/lib/Slic3r/ExtrusionPath.pm +++ b/lib/Slic3r/ExtrusionPath.pm @@ -4,7 +4,7 @@ use Moo; require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(EXTR_ROLE_PERIMETER EXTR_ROLE_EXTERNAL_PERIMETER - EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER + EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER EXTR_ROLE_OVERHANG_PERIMETER EXTR_ROLE_FILL EXTR_ROLE_SOLIDFILL EXTR_ROLE_TOPSOLIDFILL EXTR_ROLE_BRIDGE EXTR_ROLE_INTERNALBRIDGE EXTR_ROLE_SKIRT EXTR_ROLE_SUPPORTMATERIAL EXTR_ROLE_GAPFILL); our %EXPORT_TAGS = (roles => \@EXPORT_OK); @@ -24,7 +24,8 @@ has 'flow_spacing' => (is => 'rw'); has 'role' => (is => 'rw', required => 1); use constant EXTR_ROLE_PERIMETER => 0; -use constant EXTR_ROLE_EXTERNAL_PERIMETER => 2; +use constant EXTR_ROLE_EXTERNAL_PERIMETER => 1; +use constant EXTR_ROLE_OVERHANG_PERIMETER => 2; use constant EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER => 3; use constant EXTR_ROLE_FILL => 4; use constant EXTR_ROLE_SOLIDFILL => 5; @@ -101,6 +102,7 @@ sub is_perimeter { my $self = shift; return $self->role == EXTR_ROLE_PERIMETER || $self->role == EXTR_ROLE_EXTERNAL_PERIMETER + || $self->role == EXTR_ROLE_OVERHANG_PERIMETER || $self->role == EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER; } @@ -114,7 +116,8 @@ sub is_fill { sub is_bridge { my $self = shift; return $self->role == EXTR_ROLE_BRIDGE - || $self->role == EXTR_ROLE_INTERNALBRIDGE; + || $self->role == EXTR_ROLE_INTERNALBRIDGE + || $self->role == EXTR_ROLE_OVERHANG_PERIMETER; } sub split_at_acute_angles { diff --git a/lib/Slic3r/Flow.pm b/lib/Slic3r/Flow.pm index b17b1991..eca1ff55 100644 --- a/lib/Slic3r/Flow.pm +++ b/lib/Slic3r/Flow.pm @@ -38,7 +38,9 @@ sub _build_width { my $min = $self->nozzle_diameter * 1.05; my $max; - if ($self->role ne 'infill') { + if ($self->role eq 'perimeter') { + $min = $max = $self->nozzle_diameter; + } elsif ($self->role ne 'infill') { # do not limit width for sparse infill so that we use full native flow for it $max = $self->nozzle_diameter * 1.7; } diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 7cd3d0ef..48d32e78 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -49,6 +49,7 @@ has 'speeds' => ( my %role_speeds = ( &EXTR_ROLE_PERIMETER => 'perimeter', &EXTR_ROLE_EXTERNAL_PERIMETER => 'external_perimeter', + &EXTR_ROLE_OVERHANG_PERIMETER => 'external_perimeter', &EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER => 'perimeter', &EXTR_ROLE_FILL => 'infill', &EXTR_ROLE_SOLIDFILL => 'solid_infill', @@ -208,13 +209,13 @@ sub extrude_path { $acceleration = $Slic3r::Config->perimeter_acceleration; } elsif ($Slic3r::Config->infill_acceleration && $path->is_fill) { $acceleration = $Slic3r::Config->infill_acceleration; - } elsif ($Slic3r::Config->infill_acceleration && ($path->role == EXTR_ROLE_BRIDGE || $path->role == EXTR_ROLE_INTERNALBRIDGE)) { + } elsif ($Slic3r::Config->infill_acceleration && $path->is_bridge) { $acceleration = $Slic3r::Config->bridge_acceleration; } $gcode .= $self->set_acceleration($acceleration) if $acceleration; my $area; # mm^3 of extrudate per mm of tool movement - if ($path->role == EXTR_ROLE_BRIDGE || $path->role == EXTR_ROLE_INTERNALBRIDGE) { + if ($path->is_bridge) { my $s = $path->flow_spacing; $area = ($s**2) * PI/4; } else { diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index 498ccbcf..7e5e4b48 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -6,7 +6,8 @@ require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(safety_offset safety_offset_ex offset offset_ex collapse_ex diff_ex diff union_ex intersection_ex xor_ex PFT_EVENODD JT_MITER JT_ROUND - JT_SQUARE is_counter_clockwise union_pt offset2 offset2_ex traverse_pt); + JT_SQUARE is_counter_clockwise union_pt offset2 offset2_ex traverse_pt + intersection); use Math::Clipper 1.21 qw(:cliptypes :polyfilltypes :jointypes is_counter_clockwise area); use Slic3r::Geometry qw(scale); @@ -118,6 +119,18 @@ sub intersection_ex { ]; } +sub intersection { + my ($subject, $clip, $jointype, $safety_offset) = @_; + $jointype = PFT_NONZERO unless defined $jointype; + $clipper->clear; + $clipper->add_subject_polygons($subject); + $clipper->add_clip_polygons($safety_offset ? safety_offset($clip) : $clip); + return [ + map Slic3r::Polygon->new($_), + @{ $clipper->execute(CT_INTERSECTION, $jointype, $jointype) }, + ]; +} + sub xor_ex { my ($subject, $clip, $jointype) = @_; $jointype = PFT_NONZERO unless defined $jointype; diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 65ee99ae..e00d58c4 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -5,7 +5,7 @@ use List::Util qw(sum first); use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(PI X1 X2 Y1 Y2 A B scale chained_path_items points_coincide); use Slic3r::Geometry::Clipper qw(safety_offset union_ex diff_ex intersection_ex - offset offset2_ex PFT_EVENODD union_pt traverse_pt); + offset offset2_ex PFT_EVENODD union_pt traverse_pt diff intersection); use Slic3r::Surface ':types'; has 'layer' => ( @@ -233,6 +233,11 @@ sub make_perimeters { my $contours_pt = union_pt(\@contours, PFT_EVENODD); my $holes_pt = union_pt(\@holes, PFT_EVENODD); + # get lower layer slices for overhang check + my @lower_slices = $self->id == 0 + ? () + : map @$_, @{$self->layer->object->layers->[$self->id-1]->slices}; + # prepare a coderef for traversing the PolyTree object # external contours are root items of $contours_pt # internal contours are the ones next to external @@ -249,7 +254,9 @@ sub make_perimeters { my @loops = (); foreach my $polynode (@$polynodes) { push @loops, $traverse->($polynode->{children}, $depth+1, $is_contour); - + + my $polygon = Slic3r::Polygon->new($polynode->{outer} // [ reverse @{$polynode->{hole}} ]); + my $role = EXTR_ROLE_PERIMETER; if ($is_contour ? $depth == 0 : !@{ $polynode->{children} }) { # external perimeters are root level in case of contours @@ -258,8 +265,17 @@ sub make_perimeters { } elsif ($depth == 1 && $is_contour) { $role = EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER; } + + if ($self->id > 0) { + my $is_overhang = $is_contour + ? @{diff([$polygon], \@lower_slices)} + : !@{intersection([$polygon], \@lower_slices)}; + + $role = EXTR_ROLE_OVERHANG_PERIMETER if $is_overhang; + } + push @loops, Slic3r::ExtrusionLoop->pack( - polygon => Slic3r::Polygon->new($polynode->{outer} // [ reverse @{$polynode->{hole}} ]), + polygon => $polygon, role => $role, flow_spacing => $self->perimeter_flow->spacing, ); From a94d26b1ce4186f323d0f1c5b99d7e8d2f40f58e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 15 May 2013 10:50:38 +0200 Subject: [PATCH 098/179] Fix overhang detection for contours --- lib/Slic3r/Layer/Region.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index e00d58c4..89975317 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -268,7 +268,7 @@ sub make_perimeters { if ($self->id > 0) { my $is_overhang = $is_contour - ? @{diff([$polygon], \@lower_slices)} + ? @{diff([$polygon], [ @lower_slices, offset([$polygon], -$self->perimeter_flow->scaled_width) ])} : !@{intersection([$polygon], \@lower_slices)}; $role = EXTR_ROLE_OVERHANG_PERIMETER if $is_overhang; From bfba5b3d789921302791b77acd484702bec2d579 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 15 May 2013 17:38:50 +0200 Subject: [PATCH 099/179] Update tests after recent TriangleMesh optimizations --- t/loops.t | 1 + t/slice.t | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/t/loops.t b/t/loops.t index d0340b20..2495f7c6 100644 --- a/t/loops.t +++ b/t/loops.t @@ -40,6 +40,7 @@ use Slic3r::Test; [ [5,5,0], [5,15,10], [5,5,10] ]; my $mesh = Slic3r::TriangleMesh->new(vertices => \@vertices, facets => \@facets); + $mesh->analyze; my @lines = map $mesh->intersect_facet($_, 10), 0..$#facets; my $loops = Slic3r::TriangleMesh::make_loops(\@lines); is scalar(@$loops), 3, 'correct number of loops detected'; diff --git a/t/slice.t b/t/slice.t index 39ae3f71..cfa78a60 100644 --- a/t/slice.t +++ b/t/slice.t @@ -111,7 +111,7 @@ sub vertices { sub add_facet { push @{$mesh->facets}, [ [0,0,0], @{vertices(@_)} ]; - $mesh->BUILD; + $mesh->analyze; } sub intersect { From bff31d7002bfc45f02a5f709fccd915c80dd0d17 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 16 May 2013 11:25:15 +0200 Subject: [PATCH 100/179] Fix split_mesh() not working anymore since analyze() is lazy --- lib/Slic3r/TriangleMesh.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm index 99af2552..f093a3fe 100644 --- a/lib/Slic3r/TriangleMesh.pm +++ b/lib/Slic3r/TriangleMesh.pm @@ -543,6 +543,8 @@ sub get_connected_facets { sub split_mesh { my $self = shift; + $self->analyze; + my @meshes = (); # loop while we have remaining facets From fc1a23f5b0447c9d43e7060b49e278fad81ee7f4 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 16 May 2013 12:01:38 +0200 Subject: [PATCH 101/179] Quick and dirty OpenGL mockup --- MANIFEST | 1 + lib/Slic3r/GUI.pm | 1 + lib/Slic3r/GUI/Plater/ObjectDialog.pm | 19 +++ lib/Slic3r/GUI/PreviewCanvas.pm | 182 ++++++++++++++++++++++++++ 4 files changed, 203 insertions(+) create mode 100644 lib/Slic3r/GUI/PreviewCanvas.pm diff --git a/MANIFEST b/MANIFEST index bc338a5c..24253e6b 100644 --- a/MANIFEST +++ b/MANIFEST @@ -37,6 +37,7 @@ lib/Slic3r/GUI/OptionsGroup.pm lib/Slic3r/GUI/Plater.pm lib/Slic3r/GUI/Plater/ObjectDialog.pm lib/Slic3r/GUI/Preferences.pm +lib/Slic3r/GUI/PreviewCanvas.pm lib/Slic3r/GUI/SkeinPanel.pm lib/Slic3r/GUI/SimpleTab.pm lib/Slic3r/GUI/Tab.pm diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 1c60f857..399b978b 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -10,6 +10,7 @@ use Slic3r::GUI::Plater; use Slic3r::GUI::Plater::ObjectDialog; use Slic3r::GUI::Preferences; use Slic3r::GUI::OptionsGroup; +use Slic3r::GUI::PreviewCanvas; use Slic3r::GUI::SkeinPanel; use Slic3r::GUI::SimpleTab; use Slic3r::GUI::Tab; diff --git a/lib/Slic3r/GUI/Plater/ObjectDialog.pm b/lib/Slic3r/GUI/Plater/ObjectDialog.pm index 6543d50d..c1723632 100644 --- a/lib/Slic3r/GUI/Plater/ObjectDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectDialog.pm @@ -14,6 +14,7 @@ sub new { $self->{object} = $params{object}; $self->{tabpanel} = Wx::Notebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL); + $self->{tabpanel}->AddPage($self->{preview} = Slic3r::GUI::Plater::ObjectDialog::PreviewTab->new($self->{tabpanel}, object => $self->{object}), "Preview"); $self->{tabpanel}->AddPage($self->{info} = Slic3r::GUI::Plater::ObjectDialog::InfoTab->new($self->{tabpanel}, object => $self->{object}), "Info"); $self->{tabpanel}->AddPage($self->{layers} = Slic3r::GUI::Plater::ObjectDialog::LayersTab->new($self->{tabpanel}, object => $self->{object}), "Layers"); @@ -83,6 +84,24 @@ sub get_properties { ]; } +package Slic3r::GUI::Plater::ObjectDialog::PreviewTab; +use Wx qw(:dialog :id :misc :sizer :systemsettings); +use base 'Wx::Panel'; + +sub new { + my $class = shift; + my ($parent, %params) = @_; + my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize); + $self->{object} = $params{object}; + + my $sizer = Wx::BoxSizer->new(wxVERTICAL); + $sizer->Add(Slic3r::GUI::PreviewCanvas::Cube->new($self, $self->{object}->get_model_object->mesh), 1, wxEXPAND, 0); + $self->SetSizer($sizer); + $sizer->SetSizeHints($self); + + return $self; +} + package Slic3r::GUI::Plater::ObjectDialog::LayersTab; use Wx qw(:dialog :id :misc :sizer :systemsettings); use Wx::Grid; diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm new file mode 100644 index 00000000..122df54b --- /dev/null +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -0,0 +1,182 @@ +package Slic3r::GUI::PreviewCanvas; + +use strict; + +use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_TIMER); +# must load OpenGL *before* Wx::GLCanvas +use OpenGL qw(:glconstants :glfunctions); +use base qw(Wx::GLCanvas Class::Accessor::Fast); +use Wx::GLCanvas qw(:all); + +__PACKAGE__->mk_accessors( qw(timer x_rot y_rot dirty init mesh) ); + +sub new { + my( $class, $parent, $mesh ) = @_; + my $self = $class->SUPER::new($parent); + $self->mesh($mesh); + + my $timer = $self->timer( Wx::Timer->new( $self ) ); + $timer->Start( 50 ); + + $self->x_rot( 0 ); + $self->y_rot( 0 ); + + EVT_PAINT( $self, + sub { + my $dc = Wx::PaintDC->new( $self ); + $self->Render( $dc ); + } ); + EVT_SIZE( $self, sub { $self->dirty( 1 ) } ); + EVT_IDLE( $self, sub { + return unless $self->dirty; + $self->Resize( $self->GetSizeWH ); + $self->Refresh; + } ); + EVT_TIMER( $self, -1, sub { + my( $self, $e ) = @_; + + $self->x_rot( $self->x_rot - 1 ); + $self->y_rot( $self->y_rot + 2 ); + + $self->dirty( 1 ); + Wx::WakeUpIdle; + } ); + + return $self; +} + +sub GetContext { + my( $self ) = @_; + + if( Wx::wxVERSION >= 2.009 ) { + return $self->{context} ||= Wx::GLContext->new( $self ); + } else { + return $self->SUPER::GetContext; + } +} + +sub SetCurrent { + my( $self, $context ) = @_; + + if( Wx::wxVERSION >= 2.009 ) { + return $self->SUPER::SetCurrent( $context ); + } else { + return $self->SUPER::SetCurrent; + } +} + +sub Resize { + my( $self, $x, $y ) = @_; + + return unless $self->GetContext; + $self->dirty( 0 ); + + $self->SetCurrent( $self->GetContext ); + glViewport( 0, 0, $x, $y ); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + my_gluPerspective( 45, $x/$y, .5, 100 ); + + glMatrixMode(GL_MODELVIEW); +} + +use Math::Trig; + +sub my_gluPerspective { + my( $fov, $ratio, $near, $far ) = @_; + + my $top = tan(deg2rad($fov)*0.5) * $near; + my $bottom = -$top; + my $left = $ratio * $bottom; + my $right = $ratio * $top; + + glFrustum( $left, $right, $bottom, $top, $near, $far ); +} + +sub DESTROY { + my( $self ) = @_; + + $self->timer->Stop; + $self->timer( undef ); +} + +package Slic3r::GUI::PreviewCanvas::Cube; + +# must load OpenGL *before* Wx::GLCanvas +use OpenGL qw(:glconstants :glfunctions); +use base qw(Slic3r::GUI::PreviewCanvas); +use Slic3r::Geometry qw(X Y Z MIN MAX); + +sub cube { + my( @v ) = ( [ 1, 1, 1 ], [ -1, 1, 1 ], + [ -1, -1, 1 ], [ 1, -1, 1 ], + [ 1, 1, -1 ], [ -1, 1, -1 ], + [ -1, -1, -1 ], [ 1, -1, -1 ] ); + my( @c ) = ( [ 1, 1, 0 ], [ 1, 0, 1 ], + [ 0, 1, 1 ], [ 1, 1, 1 ], + [ 0, 0, 1 ], [ 0, 1, 0 ], + [ 1, 0, 1 ], [ 1, 1, 0 ] ); + my( @s ) = ( [ 0, 1, 2, 3 ], [ 4, 5, 6, 7 ], + [ 0, 1, 5, 4 ], [ 2, 3, 7, 6 ], + [ 1, 2, 6, 5 ], [ 0, 3, 7, 4 ] ); + + for my $i ( 0 .. 5 ) { + my $s = $s[$i]; + glBegin(GL_QUADS); + foreach my $j ( @$s ) { + glColor3f( @{$c[$j]} ); + glVertex3f( @{$v[$j]} ); + } + glEnd(); + } +} + +sub draw_mesh { + my $self = shift; + + my $mesh = $self->mesh; + $mesh->align_to_origin; + glBegin(GL_TRIANGLES); + for my $facet (@{$mesh->facets}) { + glVertex3f( map 0.1 * $_, @{ $mesh->vertices->[$_] } ) for @$facet; + } + glEnd(); +} + +sub InitGL { + my $self = shift; + + return if $self->init; + return unless $self->GetContext; + $self->init( 1 ); + + glDisable( GL_LIGHTING ); + glDepthFunc( GL_LESS ); + glEnable( GL_DEPTH_TEST ); +} + +sub Render { + my( $self, $dc ) = @_; + + return unless $self->GetContext; + $self->SetCurrent( $self->GetContext ); + $self->InitGL; + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glPushMatrix(); + glTranslatef( 0, 0, -5 ); + glRotatef( $self->x_rot, 1, 0, 0 ); + glRotatef( $self->y_rot, 0, 0, 1 ); + + #cube(); + $self->draw_mesh; + + glPopMatrix(); + glFlush(); + + $self->SwapBuffers(); +} + +1; \ No newline at end of file From 55071e544f73205cbd26920c52e06f73ed9f65a0 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 16 May 2013 12:29:46 +0200 Subject: [PATCH 102/179] Fix brim spacing. #1174 --- lib/Slic3r/Print.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index a581df4e..b5482f7a 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -699,7 +699,7 @@ sub make_brim { # JT_SQUARE ensures no vertex is outside the given offset distance # -0.5 because islands are not represented by their centerlines # TODO: we need the offset inwards/offset outwards logic to avoid overlapping extrusions - push @loops, offset2(\@islands, ($i - 2) * $flow->scaled_spacing, ($i + 1.5) * $flow->scaled_spacing, undef, JT_SQUARE); + push @loops, offset2(\@islands, ($i - 1.5) * $flow->scaled_spacing, +1.0 * $flow->scaled_spacing, undef, JT_SQUARE); } @{$self->brim} = map Slic3r::ExtrusionLoop->pack( From 521d668712590fe5c76e109a0646a09b5f98ce87 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 16 May 2013 12:34:24 +0200 Subject: [PATCH 103/179] Reduce overlap for bridges. #1090 --- lib/Slic3r.pm | 1 + lib/Slic3r/Flow.pm | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 9214022f..1dfaf885 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -68,6 +68,7 @@ use constant SCALING_FACTOR => 0.000001; use constant RESOLUTION => 0.0125; use constant SCALED_RESOLUTION => RESOLUTION / SCALING_FACTOR; use constant OVERLAP_FACTOR => 1; +use constant BRIDGE_OVERLAP_FACTOR => 0.2; use constant SMALL_PERIMETER_LENGTH => (6.5 / SCALING_FACTOR) * 2 * PI; use constant LOOP_CLIPPING_LENGTH_OVER_SPACING => 0.15; use constant INFILL_OVERLAP_OVER_SPACING => 0.45; diff --git a/lib/Slic3r/Flow.pm b/lib/Slic3r/Flow.pm index b17b1991..50d7cb7f 100644 --- a/lib/Slic3r/Flow.pm +++ b/lib/Slic3r/Flow.pm @@ -97,7 +97,7 @@ sub _build_width { sub _build_spacing { my $self = shift; my $width = $self->width; - return $width + &Slic3r::OVERLAP_FACTOR * ($width * PI / 4 - $width); + return $width - (&Slic3r::BRIDGE_OVERLAP_FACTOR * $width); } 1; From 09989ab8bd49e0037ddae26fd273a38f94800053 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 16 May 2013 12:54:38 +0200 Subject: [PATCH 104/179] Fix regression in --gcode-arcs --- lib/Slic3r/ExtrusionPath.pm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/ExtrusionPath.pm b/lib/Slic3r/ExtrusionPath.pm index 30be3651..b6a2205b 100644 --- a/lib/Slic3r/ExtrusionPath.pm +++ b/lib/Slic3r/ExtrusionPath.pm @@ -20,7 +20,7 @@ has 'polyline' => ( # height is the vertical thickness of the extrusion expressed in mm has 'height' => (is => 'rw'); -has 'flow_spacing' => (is => 'rw'); +has 'flow_spacing' => (is => 'rw', required => 1); has 'role' => (is => 'rw', required => 1); use constant EXTR_ROLE_PERIMETER => 0; @@ -241,6 +241,7 @@ sub detect_arcs { my $arc = Slic3r::ExtrusionPath::Arc->new( polyline => Slic3r::Polyline->new(\@arc_points), role => $self->role, + flow_spacing => $self->flow_spacing, orientation => $orientation, center => $arc_center, radius => $arc_center->distance_to($points[$i]), @@ -250,6 +251,7 @@ sub detect_arcs { push @paths, (ref $self)->new( polyline => Slic3r::Polyline->new(@points[0..$i]), role => $self->role, + flow_spacing => $self->flow_spacing, height => $self->height, ) if $i > 0; @@ -269,6 +271,7 @@ sub detect_arcs { push @paths, (ref $self)->new( polyline => Slic3r::Polyline->new(\@points), role => $self->role, + flow_spacing => $self->flow_spacing, height => $self->height, ) if @points > 1; From 228c84ddc1bbac6c544a40138723a2d089700356 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 16 May 2013 13:42:19 +0200 Subject: [PATCH 105/179] Use glDrawArrays() --- lib/Slic3r/GUI/PreviewCanvas.pm | 55 +++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index 122df54b..2c776e85 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -136,12 +136,32 @@ sub draw_mesh { my $self = shift; my $mesh = $self->mesh; - $mesh->align_to_origin; - glBegin(GL_TRIANGLES); - for my $facet (@{$mesh->facets}) { - glVertex3f( map 0.1 * $_, @{ $mesh->vertices->[$_] } ) for @$facet; - } - glEnd(); + + #glEnable(GL_CULL_FACE); + glEnableClientState(GL_VERTEX_ARRAY); + #glEnableClientState(GL_NORMAL_ARRAY); + + my @verts = map 0.1 * $_, map @{ $mesh->vertices->[$_] }, map @$_, @{$mesh->facets}; + my $verts = OpenGL::Array->new_list(GL_FLOAT, @verts); + + #my @norms = map @$_, map {my $f = $_; Slic3r::Geometry::triangle_normal(map $mesh->vertices->[$_], @$f) } @{$mesh->facets}; + #my $norms = OpenGL::Array->new_list(GL_FLOAT, @norms); + + #my @inv_norms = map @$_, map {my $f = $_; Slic3r::Geometry::triangle_normal(reverse map $mesh->vertices->[$_], @$f) } @{$mesh->facets}; + #my $inv_norms = OpenGL::Array->new_list(GL_FLOAT, @inv_norms); + + glVertexPointer_p(3, $verts); + + #glCullFace(GL_BACK); + #glNormalPointer_p($norms); + glDrawArrays(GL_TRIANGLES, 0, scalar @verts); + + #glCullFace(GL_FRONT); + #glNormalPointer_p($inv_norms); + #glDrawArrays(GL_TRIANGLES, 0, scalar @verts); + + #glDisableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); } sub InitGL { @@ -151,9 +171,32 @@ sub InitGL { return unless $self->GetContext; $self->init( 1 ); + $self->mesh->align_to_origin; + glDisable( GL_LIGHTING ); glDepthFunc( GL_LESS ); glEnable( GL_DEPTH_TEST ); + + if (0) { + # Settings for our light. + my @Light_Ambient = ( 0.1, 0.1, 0.1, 1.0 ); + my @Light_Diffuse = ( 1.2, 1.2, 1.2, 1.0 ); + my @Light_Position = ( 2.0, 2.0, 0.0, 1.0 ); + + + # Enables Smooth Color Shading; try GL_FLAT for (lack of) fun. + glShadeModel(GL_SMOOTH); + + # Set up a light, turn it on. + glLightfv_p(GL_LIGHT1, GL_POSITION, @Light_Position); + glLightfv_p(GL_LIGHT1, GL_AMBIENT, @Light_Ambient); + glLightfv_p(GL_LIGHT1, GL_DIFFUSE, @Light_Diffuse); + glEnable(GL_LIGHT1); + + # A handy trick -- have surface material mirror the color. + glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE); + glEnable(GL_COLOR_MATERIAL); + } } sub Render { From 5c74fd095b93151fc3956d158bbbdfd3ccb2dd96 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 17 May 2013 14:14:33 +0200 Subject: [PATCH 106/179] Very basic implementation of 3D preview - install Wx::GLCanvas to get it working --- MANIFEST | 1 + lib/Slic3r/GUI.pm | 3 +- lib/Slic3r/GUI/Plater/ObjectDialog.pm | 8 +- lib/Slic3r/GUI/PreviewCanvas.pm | 263 ++++++++++++-------------- lib/Slic3r/Geometry.pm | 15 +- lib/Slic3r/TriangleMesh.pm | 13 ++ utils/view-mesh.pl | 67 +++++++ 7 files changed, 221 insertions(+), 149 deletions(-) create mode 100644 utils/view-mesh.pl diff --git a/MANIFEST b/MANIFEST index 24253e6b..22766599 100644 --- a/MANIFEST +++ b/MANIFEST @@ -89,6 +89,7 @@ utils/post-processing/decimate.pl utils/post-processing/flowrate.pl utils/split_stl.pl utils/stl-to-amf.pl +utils/view-mesh.pl utils/zsh/functions/_slic3r utils/zsh/README.markdown var/add.png diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 399b978b..09aa80a9 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -10,11 +10,12 @@ use Slic3r::GUI::Plater; use Slic3r::GUI::Plater::ObjectDialog; use Slic3r::GUI::Preferences; use Slic3r::GUI::OptionsGroup; -use Slic3r::GUI::PreviewCanvas; use Slic3r::GUI::SkeinPanel; use Slic3r::GUI::SimpleTab; use Slic3r::GUI::Tab; +our $have_OpenGL = eval "use Slic3r::GUI::PreviewCanvas; 1"; + use Wx 0.9901 qw(:bitmap :dialog :frame :icon :id :misc :systemsettings :toplevelwindow); use Wx::Event qw(EVT_CLOSE EVT_MENU); use base 'Wx::App'; diff --git a/lib/Slic3r/GUI/Plater/ObjectDialog.pm b/lib/Slic3r/GUI/Plater/ObjectDialog.pm index c1723632..3cb725e6 100644 --- a/lib/Slic3r/GUI/Plater/ObjectDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectDialog.pm @@ -10,11 +10,12 @@ use base 'Wx::Dialog'; sub new { my $class = shift; my ($parent, %params) = @_; - my $self = $class->SUPER::new($parent, -1, "Object", wxDefaultPosition, [500,350]); + my $self = $class->SUPER::new($parent, -1, "Object", wxDefaultPosition, [500,350], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); $self->{object} = $params{object}; $self->{tabpanel} = Wx::Notebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL); - $self->{tabpanel}->AddPage($self->{preview} = Slic3r::GUI::Plater::ObjectDialog::PreviewTab->new($self->{tabpanel}, object => $self->{object}), "Preview"); + $self->{tabpanel}->AddPage($self->{preview} = Slic3r::GUI::Plater::ObjectDialog::PreviewTab->new($self->{tabpanel}, object => $self->{object}), "Preview") + if $Slic3r::GUI::have_OpenGL; $self->{tabpanel}->AddPage($self->{info} = Slic3r::GUI::Plater::ObjectDialog::InfoTab->new($self->{tabpanel}, object => $self->{object}), "Info"); $self->{tabpanel}->AddPage($self->{layers} = Slic3r::GUI::Plater::ObjectDialog::LayersTab->new($self->{tabpanel}, object => $self->{object}), "Layers"); @@ -34,6 +35,7 @@ sub new { $sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); $self->SetSizer($sizer); + $self->SetMinSize($self->GetSize); return $self; } @@ -95,7 +97,7 @@ sub new { $self->{object} = $params{object}; my $sizer = Wx::BoxSizer->new(wxVERTICAL); - $sizer->Add(Slic3r::GUI::PreviewCanvas::Cube->new($self, $self->{object}->get_model_object->mesh), 1, wxEXPAND, 0); + $sizer->Add(Slic3r::GUI::PreviewCanvas->new($self, $self->{object}->get_model_object->mesh), 1, wxEXPAND, 0); $self->SetSizer($sizer); $sizer->SetSizeHints($self); diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index 2c776e85..f70a0bba 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -1,90 +1,111 @@ package Slic3r::GUI::PreviewCanvas; - use strict; +use warnings; -use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_TIMER); +use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_TIMER EVT_MOUSEWHEEL); # must load OpenGL *before* Wx::GLCanvas use OpenGL qw(:glconstants :glfunctions); -use base qw(Wx::GLCanvas Class::Accessor::Fast); +use base qw(Wx::GLCanvas Class::Accessor); +use Slic3r::Geometry qw(X Y Z MIN MAX triangle_normal normalize deg2rad tan); use Wx::GLCanvas qw(:all); -__PACKAGE__->mk_accessors( qw(timer x_rot y_rot dirty init mesh) ); +__PACKAGE__->mk_accessors( qw(timer x_rot y_rot dirty init mesh_center zoom + verts norms) ); sub new { - my( $class, $parent, $mesh ) = @_; + my ($class, $parent, $mesh) = @_; my $self = $class->SUPER::new($parent); - $self->mesh($mesh); - my $timer = $self->timer( Wx::Timer->new( $self ) ); - $timer->Start( 50 ); - - $self->x_rot( 0 ); - $self->y_rot( 0 ); - - EVT_PAINT( $self, - sub { - my $dc = Wx::PaintDC->new( $self ); - $self->Render( $dc ); - } ); - EVT_SIZE( $self, sub { $self->dirty( 1 ) } ); - EVT_IDLE( $self, sub { - return unless $self->dirty; - $self->Resize( $self->GetSizeWH ); - $self->Refresh; - } ); - EVT_TIMER( $self, -1, sub { - my( $self, $e ) = @_; - - $self->x_rot( $self->x_rot - 1 ); - $self->y_rot( $self->y_rot + 2 ); - - $self->dirty( 1 ); - Wx::WakeUpIdle; - } ); - + # prepare mesh + { + $self->mesh_center($mesh->center); + $self->zoom(0.1); + + my @verts = map $self->zoom * $_, map @{ $mesh->vertices->[$_] }, map @$_, @{$mesh->facets}; + $self->verts(OpenGL::Array->new_list(GL_FLOAT, @verts)); + + my @norms = map { @$_, @$_, @$_ } map normalize(triangle_normal(map $mesh->vertices->[$_], @$_)), @{$mesh->facets}; + $self->norms(OpenGL::Array->new_list(GL_FLOAT, @norms)); + } + + my $timer = $self->timer( Wx::Timer->new($self) ); + $timer->Start(50); + + $self->x_rot(0); + $self->y_rot(0); + + EVT_PAINT($self, sub { + my $dc = Wx::PaintDC->new($self); + $self->Render($dc); + }); + EVT_SIZE($self, sub { $self->dirty(1) }); + EVT_IDLE($self, sub { + return unless $self->dirty; + return if !$self->IsShownOnScreen; + $self->Resize( $self->GetSizeWH ); + $self->Refresh; + }); + EVT_TIMER($self, -1, sub { + my ($self, $e) = @_; + + $self->x_rot( $self->x_rot - 1 ); + $self->y_rot( $self->y_rot + 2 ); + + $self->dirty(1); + Wx::WakeUpIdle; + }); + EVT_MOUSEWHEEL($self, sub { + my ($self, $e) = @_; + + my $zoom = $self->zoom * (1.0 - $e->GetWheelRotation() / $e->GetWheelDelta() / 10); + $zoom = 0.001 if $zoom < 0.001; + $zoom = 0.1 if $zoom > 0.1; + $self->zoom($zoom); + + $self->Refresh; + }); + return $self; } sub GetContext { - my( $self ) = @_; - - if( Wx::wxVERSION >= 2.009 ) { - return $self->{context} ||= Wx::GLContext->new( $self ); + my ($self) = @_; + + if (Wx::wxVERSION >= 2.009) { + return $self->{context} ||= Wx::GLContext->new($self); } else { return $self->SUPER::GetContext; } } sub SetCurrent { - my( $self, $context ) = @_; - - if( Wx::wxVERSION >= 2.009 ) { - return $self->SUPER::SetCurrent( $context ); + my ($self, $context) = @_; + + if (Wx::wxVERSION >= 2.009) { + return $self->SUPER::SetCurrent($context); } else { return $self->SUPER::SetCurrent; } } sub Resize { - my( $self, $x, $y ) = @_; + my ($self, $x, $y) = @_; return unless $self->GetContext; - $self->dirty( 0 ); + $self->dirty(0); - $self->SetCurrent( $self->GetContext ); - glViewport( 0, 0, $x, $y ); + $self->SetCurrent($self->GetContext); + glViewport(0, 0, $x, $y); glMatrixMode(GL_PROJECTION); glLoadIdentity(); - my_gluPerspective( 45, $x/$y, .5, 100 ); + my_gluPerspective(45, $x/$y, .5, 100); glMatrixMode(GL_MODELVIEW); } -use Math::Trig; - sub my_gluPerspective { - my( $fov, $ratio, $near, $far ) = @_; + my ($fov, $ratio, $near, $far) = @_; my $top = tan(deg2rad($fov)*0.5) * $near; my $bottom = -$top; @@ -95,73 +116,10 @@ sub my_gluPerspective { } sub DESTROY { - my( $self ) = @_; + my $self = shift; $self->timer->Stop; - $self->timer( undef ); -} - -package Slic3r::GUI::PreviewCanvas::Cube; - -# must load OpenGL *before* Wx::GLCanvas -use OpenGL qw(:glconstants :glfunctions); -use base qw(Slic3r::GUI::PreviewCanvas); -use Slic3r::Geometry qw(X Y Z MIN MAX); - -sub cube { - my( @v ) = ( [ 1, 1, 1 ], [ -1, 1, 1 ], - [ -1, -1, 1 ], [ 1, -1, 1 ], - [ 1, 1, -1 ], [ -1, 1, -1 ], - [ -1, -1, -1 ], [ 1, -1, -1 ] ); - my( @c ) = ( [ 1, 1, 0 ], [ 1, 0, 1 ], - [ 0, 1, 1 ], [ 1, 1, 1 ], - [ 0, 0, 1 ], [ 0, 1, 0 ], - [ 1, 0, 1 ], [ 1, 1, 0 ] ); - my( @s ) = ( [ 0, 1, 2, 3 ], [ 4, 5, 6, 7 ], - [ 0, 1, 5, 4 ], [ 2, 3, 7, 6 ], - [ 1, 2, 6, 5 ], [ 0, 3, 7, 4 ] ); - - for my $i ( 0 .. 5 ) { - my $s = $s[$i]; - glBegin(GL_QUADS); - foreach my $j ( @$s ) { - glColor3f( @{$c[$j]} ); - glVertex3f( @{$v[$j]} ); - } - glEnd(); - } -} - -sub draw_mesh { - my $self = shift; - - my $mesh = $self->mesh; - - #glEnable(GL_CULL_FACE); - glEnableClientState(GL_VERTEX_ARRAY); - #glEnableClientState(GL_NORMAL_ARRAY); - - my @verts = map 0.1 * $_, map @{ $mesh->vertices->[$_] }, map @$_, @{$mesh->facets}; - my $verts = OpenGL::Array->new_list(GL_FLOAT, @verts); - - #my @norms = map @$_, map {my $f = $_; Slic3r::Geometry::triangle_normal(map $mesh->vertices->[$_], @$f) } @{$mesh->facets}; - #my $norms = OpenGL::Array->new_list(GL_FLOAT, @norms); - - #my @inv_norms = map @$_, map {my $f = $_; Slic3r::Geometry::triangle_normal(reverse map $mesh->vertices->[$_], @$f) } @{$mesh->facets}; - #my $inv_norms = OpenGL::Array->new_list(GL_FLOAT, @inv_norms); - - glVertexPointer_p(3, $verts); - - #glCullFace(GL_BACK); - #glNormalPointer_p($norms); - glDrawArrays(GL_TRIANGLES, 0, scalar @verts); - - #glCullFace(GL_FRONT); - #glNormalPointer_p($inv_norms); - #glDrawArrays(GL_TRIANGLES, 0, scalar @verts); - - #glDisableClientState(GL_NORMAL_ARRAY); - glDisableClientState(GL_VERTEX_ARRAY); + $self->timer(undef); } sub InitGL { @@ -169,51 +127,51 @@ sub InitGL { return if $self->init; return unless $self->GetContext; - $self->init( 1 ); - - $self->mesh->align_to_origin; - - glDisable( GL_LIGHTING ); - glDepthFunc( GL_LESS ); - glEnable( GL_DEPTH_TEST ); + $self->init(1); - if (0) { - # Settings for our light. - my @Light_Ambient = ( 0.1, 0.1, 0.1, 1.0 ); - my @Light_Diffuse = ( 1.2, 1.2, 1.2, 1.0 ); - my @Light_Position = ( 2.0, 2.0, 0.0, 1.0 ); + glEnable(GL_NORMALIZE); + glEnable(GL_LIGHTING); + glDepthFunc(GL_LESS); + glEnable(GL_DEPTH_TEST); - - # Enables Smooth Color Shading; try GL_FLAT for (lack of) fun. - glShadeModel(GL_SMOOTH); - - # Set up a light, turn it on. - glLightfv_p(GL_LIGHT1, GL_POSITION, @Light_Position); - glLightfv_p(GL_LIGHT1, GL_AMBIENT, @Light_Ambient); - glLightfv_p(GL_LIGHT1, GL_DIFFUSE, @Light_Diffuse); - glEnable(GL_LIGHT1); - - # A handy trick -- have surface material mirror the color. - glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE); - glEnable(GL_COLOR_MATERIAL); - } + # Settings for our light. + my @LightPos = (0, 0, 2, 1.0); + my @LightAmbient = (0.1, 0.1, 0.1, 1.0); + my @LightDiffuse = (0.7, 0.5, 0.5, 1.0); + my @LightSpecular = (0.1, 0.1, 0.1, 0.1); + + # Enables Smooth Color Shading; try GL_FLAT for (lack of) fun. + glShadeModel(GL_SMOOTH); + + # Set up a light, turn it on. + glLightfv_p(GL_LIGHT1, GL_POSITION, @LightPos); + glLightfv_p(GL_LIGHT1, GL_AMBIENT, @LightAmbient); + glLightfv_p(GL_LIGHT1, GL_DIFFUSE, @LightDiffuse); + glLightfv_p(GL_LIGHT1, GL_SPECULAR, @LightSpecular); + glEnable(GL_LIGHT1); + + # A handy trick -- have surface material mirror the color. + glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); + glEnable(GL_COLOR_MATERIAL); } sub Render { - my( $self, $dc ) = @_; + my ($self, $dc) = @_; return unless $self->GetContext; - $self->SetCurrent( $self->GetContext ); + $self->SetCurrent($self->GetContext); $self->InitGL; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix(); glTranslatef( 0, 0, -5 ); + + # this needs to get a lot better... glRotatef( $self->x_rot, 1, 0, 0 ); glRotatef( $self->y_rot, 0, 0, 1 ); + glTranslatef(map -$_ * $self->zoom, @{ $self->mesh_center }); - #cube(); $self->draw_mesh; glPopMatrix(); @@ -222,4 +180,21 @@ sub Render { $self->SwapBuffers(); } -1; \ No newline at end of file +sub draw_mesh { + my $self = shift; + + glEnable(GL_CULL_FACE); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + + glVertexPointer_p(3, $self->verts); + + glCullFace(GL_BACK); + glNormalPointer_p($self->norms); + glDrawArrays(GL_TRIANGLES, 0, $self->verts->elements / 3); + + glDisableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); +} + +1; diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index 4c1e23b9..6eaf7fac 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -7,7 +7,7 @@ our @ISA = qw(Exporter); our @EXPORT_OK = qw( PI X Y Z A B X1 Y1 X2 Y2 MIN MAX epsilon slope line_atan lines_parallel line_point_belongs_to_segment points_coincide distance_between_points - chained_path_items chained_path_points + chained_path_items chained_path_points normalize tan line_length midpoint point_in_polygon point_in_segment segment_in_segment point_is_on_left_of_segment polyline_lines polygon_lines nearest_point point_along_segment polygon_segment_having_point polygon_has_subsegment @@ -45,6 +45,11 @@ sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR } sub scale ($) { $_[0] / &Slic3r::SCALING_FACTOR } sub unscale ($) { $_[0] * &Slic3r::SCALING_FACTOR } +sub tan { + my ($angle) = @_; + return (sin $angle) / (cos $angle); +} + sub slope { my ($line) = @_; return undef if abs($line->[B][X] - $line->[A][X]) < epsilon; # line is vertical @@ -461,6 +466,14 @@ sub triangle_normal { return normal($u, $v); } +sub normalize { + my ($line) = @_; + + my $len = sqrt( ($line->[X]**2) + ($line->[Y]**2) + ($line->[Z]**2) ) + or return [0, 0, 0]; # to avoid illegal division by zero + return [ map $_ / $len, @$line ]; +} + # 2D dot product sub dot { my ($u, $v) = @_; diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm index f093a3fe..9ffe542f 100644 --- a/lib/Slic3r/TriangleMesh.pm +++ b/lib/Slic3r/TriangleMesh.pm @@ -365,6 +365,19 @@ sub align_to_origin { $self->move(map -$extents[$_][MIN], X,Y,Z); } +sub center_around_origin { + my $self = shift; + + $self->move(map -$_, @{ $self->center }); +} + +sub center { + my $self = shift; + + my @extents = $self->extents; + return [ map +($extents[$_][MAX] + $extents[$_][MIN])/2, X,Y,Z ]; +} + sub duplicate { my $self = shift; my (@shifts) = @_; diff --git a/utils/view-mesh.pl b/utils/view-mesh.pl new file mode 100644 index 00000000..5d69525d --- /dev/null +++ b/utils/view-mesh.pl @@ -0,0 +1,67 @@ +#!/usr/bin/perl +# This script displays 3D preview of a mesh + +use strict; +use warnings; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; +} + +use Getopt::Long qw(:config no_auto_abbrev); +use Slic3r; +use Slic3r::GUI; +$|++; + +my %opt = (); +{ + my %options = ( + 'help' => sub { usage() }, + ); + GetOptions(%options) or usage(1); + $ARGV[0] or usage(1); +} + +{ + my $model = Slic3r::Model->read_from_file($ARGV[0]); + + $Slic3r::ViewMesh::mesh = $model->mesh; + my $app = Slic3r::ViewMesh->new; + $app->MainLoop; +} + + +sub usage { + my ($exit_code) = @_; + + print <<"EOF"; +Usage: view-mesh.pl [ OPTIONS ] file.stl + + --help Output this usage screen and exit + +EOF + exit ($exit_code || 0); +} + +package Slic3r::ViewMesh; +use Wx qw(:sizer); +use base qw(Wx::App); + +our $mesh; + +sub OnInit { + my $self = shift; + + my $frame = Wx::Frame->new(undef, -1, 'Mesh Viewer', [-1, -1], [500, 400]); + my $panel = Wx::Panel->new($frame, -1); + + my $sizer = Wx::BoxSizer->new(wxVERTICAL); + $sizer->Add(Slic3r::GUI::PreviewCanvas->new($panel, $mesh), 1, wxEXPAND, 0); + $panel->SetSizer($sizer); + $sizer->SetSizeHints($panel); + + $frame->Show(1); +} + +__END__ From 8f77d3b9456c68e026ddc7680b18d8a4da43f4c1 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 17 May 2013 15:03:42 +0200 Subject: [PATCH 107/179] Increase spacing for bridge traces. #1090 --- lib/Slic3r.pm | 1 - lib/Slic3r/Flow.pm | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 1dfaf885..9214022f 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -68,7 +68,6 @@ use constant SCALING_FACTOR => 0.000001; use constant RESOLUTION => 0.0125; use constant SCALED_RESOLUTION => RESOLUTION / SCALING_FACTOR; use constant OVERLAP_FACTOR => 1; -use constant BRIDGE_OVERLAP_FACTOR => 0.2; use constant SMALL_PERIMETER_LENGTH => (6.5 / SCALING_FACTOR) * 2 * PI; use constant LOOP_CLIPPING_LENGTH_OVER_SPACING => 0.15; use constant INFILL_OVERLAP_OVER_SPACING => 0.45; diff --git a/lib/Slic3r/Flow.pm b/lib/Slic3r/Flow.pm index 50d7cb7f..d6188564 100644 --- a/lib/Slic3r/Flow.pm +++ b/lib/Slic3r/Flow.pm @@ -96,8 +96,7 @@ sub _build_width { sub _build_spacing { my $self = shift; - my $width = $self->width; - return $width - (&Slic3r::BRIDGE_OVERLAP_FACTOR * $width); + return $self->width + 0.05; } 1; From 4bfbaddb59ced40d809f0a738cc13188c778a1aa Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 17 May 2013 15:07:01 +0200 Subject: [PATCH 108/179] Update t/arcs.t and add one more test --- t/arcs.t | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/t/arcs.t b/t/arcs.t index 97262040..3b14903c 100644 --- a/t/arcs.t +++ b/t/arcs.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 12; +plan tests => 13; BEGIN { use FindBin; @@ -20,7 +20,7 @@ use Slic3r::Geometry qw(scaled_epsilon scale X Y); [306517.1,219034.23], [286979.42,248012.49], [258001.16,267550.17], [222515.14,274714.47], [187029.11,267550.17], [158050.85,248012.49], [138513.17,219034.23], [131348.87,183548.2], [86948.77,175149.09], [119825.35,100585], - ), role => EXTR_ROLE_FILL); + ), role => EXTR_ROLE_FILL, flow_spacing => 0.5); my $collection = Slic3r::ExtrusionPath::Collection->new(paths => [$path]); $collection->detect_arcs(30); @@ -42,10 +42,12 @@ use Slic3r::Geometry qw(scaled_epsilon scale X Y); my $path1 = Slic3r::ExtrusionPath->new( polyline => Slic3r::Polyline->new(@points), role => EXTR_ROLE_FILL, + flow_spacing => 0.5, ); my $path2 = Slic3r::ExtrusionPath->new( polyline => Slic3r::Polyline->new(reverse @points), role => EXTR_ROLE_FILL, + flow_spacing => 0.5, ); my $collection1 = Slic3r::ExtrusionPath::Collection->new(paths => [$path1]); @@ -66,6 +68,7 @@ use Slic3r::Geometry qw(scaled_epsilon scale X Y); is $collection1->paths->[0]->orientation, 'cw', 'cw orientation was correctly detected'; is $collection2->paths->[0]->orientation, 'ccw', 'ccw orientation was correctly detected'; + is $collection1->paths->[0]->flow_spacing, $path1->flow_spacing, 'flow spacing was correctly preserved'; my $center1 = [ map sprintf('%.0f', $_), @{ $collection1->paths->[0]->center } ]; ok abs($center1->[X] - scale 10) < scaled_epsilon && abs($center1->[Y] - scale 10) < scaled_epsilon, 'center was correctly detected'; From ba433822b673a99bedec619c91dbf5ab74070c4f Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 17 May 2013 15:44:23 +0200 Subject: [PATCH 109/179] Disable OpenGL code in master branch for now --- lib/Slic3r/GUI.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 09aa80a9..0f9057ed 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -14,7 +14,7 @@ use Slic3r::GUI::SkeinPanel; use Slic3r::GUI::SimpleTab; use Slic3r::GUI::Tab; -our $have_OpenGL = eval "use Slic3r::GUI::PreviewCanvas; 1"; +our $have_OpenGL = 0 && eval "use Slic3r::GUI::PreviewCanvas; 1"; use Wx 0.9901 qw(:bitmap :dialog :frame :icon :id :misc :systemsettings :toplevelwindow); use Wx::Event qw(EVT_CLOSE EVT_MENU); From e33ca54943c4579ac950f8e06561ac544e20f3b7 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 17 May 2013 20:03:38 +0200 Subject: [PATCH 110/179] Some code cleanup in MotionPlanner --- lib/Slic3r/GCode/MotionPlanner.pm | 58 ++++++++++++++++--------------- lib/Slic3r/Polygon.pm | 6 ++++ 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/lib/Slic3r/GCode/MotionPlanner.pm b/lib/Slic3r/GCode/MotionPlanner.pm index 0cde4e1a..6eff6c3b 100644 --- a/lib/Slic3r/GCode/MotionPlanner.pm +++ b/lib/Slic3r/GCode/MotionPlanner.pm @@ -10,6 +10,7 @@ has '_contours_ex' => (is => 'rw', default => sub { [] }); # arrayref of array has '_pointmap' => (is => 'rw', default => sub { {} }); # { id => $point } has '_edges' => (is => 'rw', default => sub { {} }); # node_idx => { node_idx => distance, ... } has '_crossing_edges' => (is => 'rw', default => sub { {} }); # edge_idx => bool +has '_tolerance' => (is => 'lazy'); use List::Util qw(first); use Slic3r::Geometry qw(A B scale epsilon nearest_point); @@ -30,31 +31,14 @@ use constant CROSSING_FACTOR => 20; use constant INFINITY => 'inf'; +sub _build__tolerance { scale epsilon } + # setup our configuration space sub BUILD { my $self = shift; my $edges = $self->_edges; my $crossing_edges = $self->_crossing_edges; - my $tolerance = scale epsilon; - - # given an expolygon, this subroutine connects all its visible points - my $add_expolygon = sub { - my ($expolygon, $crosses_perimeter) = @_; - my @points = map @$_, @$expolygon; - for my $i (0 .. $#points) { - for my $j (($i+1) .. $#points) { - my $line = Slic3r::Line->new($points[$i], $points[$j]); - if ($expolygon->encloses_line($line, $tolerance)) { - my $dist = $line->length * ($crosses_perimeter ? CROSSING_FACTOR : 1); - $edges->{$points[$i]}{$points[$j]} = $dist; - $edges->{$points[$j]}{$points[$i]} = $dist; - $crossing_edges->{$points[$i]}{$points[$j]} = 1; - $crossing_edges->{$points[$j]}{$points[$i]} = 1; - } - } - } - }; # simplify islands @{$self->islands} = map $_->simplify($self->_inner_margin), @{$self->islands}; @@ -81,19 +65,14 @@ sub BUILD { ); # lines enclosed in inner expolygons are visible - $add_expolygon->($_) for @{ $self->_inner->[$i] }; + $self->_add_expolygon($_) for @{ $self->_inner->[$i] }; # lines enclosed in expolygons covering perimeters are visible # (but discouraged) - $add_expolygon->($_, 1) for @{ $self->_contours_ex->[$i] }; + $self->_add_expolygon($_, 1) for @{ $self->_contours_ex->[$i] }; } } - my $intersects = sub { - my ($polygon, $line) = @_; - @{Boost::Geometry::Utils::polygon_multi_linestring_intersection([$polygon], [$line])} > 0; - }; - { my @outer = (map @$_, @{$self->_outer}); @@ -112,7 +91,7 @@ sub BUILD { for my $m (0 .. $#{$outer[$i]}) { for my $n (0 .. $#{$outer[$j]}) { my $line = Slic3r::Line->new($outer[$i][$m], $outer[$j][$n]); - if (!first { $intersects->($_, $line) } @outer) { + if (!first { $_->intersects_line($line) } @outer) { # this line does not cross any polygon my $dist = $line->length; $edges->{$outer[$i][$m]}{$outer[$j][$n]} = $dist; @@ -132,7 +111,7 @@ sub BUILD { for my $m (0 .. $#{$inner[$i]}) { for my $n (0 .. $#{$inner[$j]}) { my $line = Slic3r::Line->new($inner[$i][$m], $inner[$j][$n]); - if (!first { $intersects->($_, $line) } @inner) { + if (!first { $_->intersects_line($line) } @inner) { # this line does not cross any polygon my $dist = $line->length * CROSSING_FACTOR; $edges->{$inner[$i][$m]}{$inner[$j][$n]} = $dist; @@ -183,6 +162,29 @@ sub BUILD { } } +# given an expolygon, this subroutine connects all its visible points +sub _add_expolygon { + my $self = shift; + my ($expolygon, $crosses_perimeter) = @_; + + my $edges = $self->_edges; + my $crossing_edges = $self->_crossing_edges; + + my @points = map @$_, @$expolygon; + for my $i (0 .. $#points) { + for my $j (($i+1) .. $#points) { + my $line = Slic3r::Line->new($points[$i], $points[$j]); + if ($expolygon->encloses_line($line, $self->_tolerance)) { + my $dist = $line->length * ($crosses_perimeter ? CROSSING_FACTOR : 1); + $edges->{$points[$i]}{$points[$j]} = $dist; + $edges->{$points[$j]}{$points[$i]} = $dist; + $crossing_edges->{$points[$i]}{$points[$j]} = 1; + $crossing_edges->{$points[$j]}{$points[$i]} = 1; + } + } + } +} + sub find_node { my $self = shift; my ($point, $near_to) = @_; diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm index 6e460f40..f8df6915 100644 --- a/lib/Slic3r/Polygon.pm +++ b/lib/Slic3r/Polygon.pm @@ -172,4 +172,10 @@ sub split_at_first_point { return $self->split_at_index(0); } +sub intersects_line { + my $self = shift; + my ($line) = @_; + return @{Boost::Geometry::Utils::polygon_multi_linestring_intersection([$self], [$line])} > 0; +} + 1; \ No newline at end of file From 08a0bbd7f09ef9f2f48572ba75e878567ad90690 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 18 May 2013 16:48:26 +0200 Subject: [PATCH 111/179] Optimization: split meshes automatically when avoid_crossing_perimeters is enabled, so that we reduce the complexity of the MotionPlanner graphs. This commit includes a very large refactoring of the Model class which is now responsible for duplication and arrangement --- lib/Slic3r/Fill/Base.pm | 1 + lib/Slic3r/Fill/Rectilinear.pm | 12 +- lib/Slic3r/GUI/Plater.pm | 1 + lib/Slic3r/Geometry.pm | 11 +- lib/Slic3r/Model.pm | 229 ++++++++++++++++++++++++++++++++- lib/Slic3r/Print.pm | 90 +++---------- lib/Slic3r/Print/Object.pm | 5 +- lib/Slic3r/SVG.pm | 2 +- lib/Slic3r/Test.pm | 4 +- lib/Slic3r/TriangleMesh.pm | 14 +- slic3r.pl | 16 ++- t/slice.t | 24 ++-- 12 files changed, 300 insertions(+), 109 deletions(-) diff --git a/lib/Slic3r/Fill/Base.pm b/lib/Slic3r/Fill/Base.pm index adc1e74c..76330823 100644 --- a/lib/Slic3r/Fill/Base.pm +++ b/lib/Slic3r/Fill/Base.pm @@ -36,6 +36,7 @@ sub infill_direction { return [\@rotate, \@shift]; } +# this method accepts any object that implements rotate() and translate() sub rotate_points { my $self = shift; my ($expolygon, $rotate_vector) = @_; diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm index a65bcbc0..4ad3ff78 100644 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ b/lib/Slic3r/Fill/Rectilinear.pm @@ -25,17 +25,17 @@ sub fill_surface { my $line_oscillation = $distance_between_lines - $min_spacing; my $is_line_pattern = $self->isa('Slic3r::Fill::Line'); - my $cache_id = sprintf "d%s_s%s_a%s", + my $cache_id = sprintf "d%s_s%.2f_a%.2f", $params{density}, $params{flow_spacing}, $rotate_vector->[0][0]; if (!$self->cache->{$cache_id}) { # compute bounding box - my $bounding_box = [ @{$self->bounding_box} ]; # clone - $bounding_box->[$_] = 0 for X1, Y1; + my $bounding_box; { - my $bb_expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_from_bounding_box($bounding_box)); - $self->rotate_points($bb_expolygon, $rotate_vector); - $bounding_box = [ $bb_expolygon->bounding_box ]; + my $bb_polygon = Slic3r::Polygon->new_from_bounding_box($self->bounding_box); + $bb_polygon->scale(sqrt 2); + $self->rotate_points($bb_polygon, $rotate_vector); + $bounding_box = [ $bb_polygon->bounding_box ]; } # define flow spacing according to requested density diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index ae66ba8e..4f63271d 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -715,6 +715,7 @@ sub make_model { rotation => $plater_object->rotate, offset => [ @$_ ], ) for @{$plater_object->instances}; + $new_model_object->align_to_origin; } return $model; diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index 6eaf7fac..e0848aa8 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -7,7 +7,7 @@ our @ISA = qw(Exporter); our @EXPORT_OK = qw( PI X Y Z A B X1 Y1 X2 Y2 MIN MAX epsilon slope line_atan lines_parallel line_point_belongs_to_segment points_coincide distance_between_points - chained_path_items chained_path_points normalize tan + chained_path_items chained_path_points normalize tan move_points_3D line_length midpoint point_in_polygon point_in_segment segment_in_segment point_is_on_left_of_segment polyline_lines polygon_lines nearest_point point_along_segment polygon_segment_having_point polygon_has_subsegment @@ -388,6 +388,15 @@ sub move_points { return map Slic3r::Point->new($shift->[X] + $_->[X], $shift->[Y] + $_->[Y]), @points; } +sub move_points_3D { + my ($shift, @points) = @_; + return map [ + $shift->[X] + $_->[X], + $shift->[Y] + $_->[Y], + $shift->[Z] + $_->[Z], + ], @points; +} + # implementation of Liang-Barsky algorithm # polygon must be convex and ccw sub clip_segment_polygon { diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index 58473fa9..cb740ab7 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -1,7 +1,8 @@ package Slic3r::Model; use Moo; -use Slic3r::Geometry qw(X Y Z); +use List::Util qw(first max); +use Slic3r::Geometry qw(X Y Z MIN move_points); has 'materials' => (is => 'ro', default => sub { {} }); has 'objects' => (is => 'ro', default => sub { [] }); @@ -19,6 +20,38 @@ sub read_from_file { return $model; } +sub merge { + my $class = shift; + my @models = @_; + + my $new_model = $class->new; + foreach my $model (@models) { + # merge material attributes (should we rename them in case of duplicates?) + $new_model->set_material($_, { %{$model->materials->{$_}}, %{$model->materials->{$_} || {}} }) + for keys %{$model->materials}; + + foreach my $object (@{$model->objects}) { + my $new_object = $new_model->add_object( + input_file => $object->input_file, + vertices => $object->vertices, + layer_height_ranges => $object->layer_height_ranges, + ); + + $new_object->add_volume( + material_id => $_->material_id, + facets => $_->facets, + ) for @{$object->volumes}; + + $new_object->add_instance( + offset => $_->offset, + rotation => $_->rotation, + ) for @{ $object->instances // [] }; + } + } + + return $new_model; +} + sub add_object { my $self = shift; @@ -39,10 +72,122 @@ sub set_material { sub scale { my $self = shift; - $_->scale(@_) for @{$self->objects}; } +sub arrange_objects { + my $self = shift; + my ($config) = @_; + + # do we have objects with no position? + if (first { !defined $_->instances } @{$self->objects}) { + # we shall redefine positions for all objects + + my ($copies, @positions) = $self->_arrange( + config => $config, + items => $self->objects, + ); + + # apply positions to objects + foreach my $object (@{$self->objects}) { + $object->align_to_origin; + + $object->instances([]); + $object->add_instance( + offset => $_, + rotation => 0, + ) for splice @positions, 0, $copies; + } + + } else { + # we only have objects with defined position + + # align the whole model to origin as it is + $self->align_to_origin; + + # arrange this model as a whole + my ($copies, @positions) = $self->_arrange( + config => $config, + items => [$self], + ); + + # apply positions to objects by translating the current positions + foreach my $object (@{$self->objects}) { + my @old_instances = @{$object->instances}; + $object->instances([]); + foreach my $instance (@old_instances) { + $object->add_instance( + offset => $_, + rotation => $instance->rotation, + ) for move_points($instance->offset, @positions); + } + } + } +} + +sub _arrange { + my $self = shift; + my %params = @_; + + my $config = $params{config}; + my @items = @{$params{items}}; # can be Model or Object objects, they have to implement size() + + if ($config->duplicate_grid->[X] > 1 || $config->duplicate_grid->[Y] > 1) { + if (@items > 1) { + die "Grid duplication is not supported with multiple objects\n"; + } + my @positions = (); + my $size = $items[0]->size; + my $dist = $config->duplicate_distance; + for my $x_copy (1..$config->duplicate_grid->[X]) { + for my $y_copy (1..$config->duplicate_grid->[Y]) { + push @positions, [ + ($size->[X] + $dist) * ($x_copy-1), + ($size->[Y] + $dist) * ($y_copy-1), + ]; + } + } + return ($config->duplicate_grid->[X] * $config->duplicate_grid->[Y]), @positions; + } else { + my $total_parts = $config->duplicate * @items; + my $partx = max(map $_->size->[X], @items); + my $party = max(map $_->size->[Y], @items); + return $config->duplicate, + Slic3r::Geometry::arrange + ($total_parts, $partx, $party, (map $_, @{$config->bed_size}), + $config->min_object_distance, $config); + } +} + +sub vertices { + my $self = shift; + return [ map @{$_->vertices}, @{$self->objects} ]; +} + +sub size { + my $self = shift; + return [ Slic3r::Geometry::size_3D($self->vertices) ]; +} + +sub extents { + my $self = shift; + return Slic3r::Geometry::bounding_box_3D($self->vertices); +} + +sub align_to_origin { + my $self = shift; + + # calculate the displacements needed to + # have lowest value for each axis at coordinate 0 + my @extents = $self->extents; + $self->move(map -$extents[$_][MIN], X,Y,Z); +} + +sub move { + my $self = shift; + $_->move(@_) for @{$self->objects}; +} + # flattens everything to a single mesh sub mesh { my $self = shift; @@ -64,6 +209,47 @@ sub mesh { return Slic3r::TriangleMesh->merge(@meshes); } +# this method splits objects into multiple distinct objects by walking their meshes +sub split_meshes { + my $self = shift; + + my @objects = @{$self->objects}; + @{$self->objects} = (); + + foreach my $object (@objects) { + if (@{$object->volumes} > 1) { + # We can't split meshes if there's more than one material, because + # we can't group the resulting meshes by object afterwards + push @{$self->objects}, $object; + next; + } + + my $volume = $object->volumes->[0]; + foreach my $mesh ($volume->mesh->split_mesh) { + my $new_object = $self->add_object( + input_file => $object->input_file, + layer_height_ranges => $object->layer_height_ranges, + ); + $new_object->add_volume( + vertices => $mesh->vertices, + facets => $mesh->facets, + material_id => $volume->material_id, + ); + + # let's now align the new object to the origin and put its displacement + # (extents) in the instances info + my @extents = $mesh->extents; + $new_object->align_to_origin; + + # add one instance per original instance applying the displacement + $new_object->add_instance( + offset => [ $_->offset->[X] + $extents[X][MIN], $_->offset->[Y] + $extents[Y][MIN] ], + rotation => $_->rotation, + ) for @{ $object->instances // [] }; + } + } +} + package Slic3r::Model::Region; use Moo; @@ -74,7 +260,7 @@ package Slic3r::Model::Object; use Moo; use List::Util qw(first); -use Slic3r::Geometry qw(X Y Z); +use Slic3r::Geometry qw(X Y Z MIN move_points_3D); use Storable qw(dclone); has 'input_file' => (is => 'rw'); @@ -123,6 +309,30 @@ sub mesh { ); } +sub size { + my $self = shift; + return [ Slic3r::Geometry::size_3D($self->vertices) ]; +} + +sub extents { + my $self = shift; + return Slic3r::Geometry::bounding_box_3D($self->vertices); +} + +sub align_to_origin { + my $self = shift; + + # calculate the displacements needed to + # have lowest value for each axis at coordinate 0 + my @extents = $self->extents; + $self->move(map -$extents[$_][MIN], X,Y,Z); +} + +sub move { + my $self = shift; + @{$self->vertices} = move_points_3D([ @_ ], @{$self->vertices}); +} + sub scale { my $self = shift; my ($factor) = @_; @@ -134,6 +344,19 @@ sub scale { } } +sub rotate { + my $self = shift; + my ($deg) = @_; + return if $deg == 0; + + my $rad = Slic3r::Geometry::deg2rad($deg); + + # transform vertex coordinates + foreach my $vertex (@{$self->vertices}) { + @$vertex = (@{ +(Slic3r::Geometry::rotate_points($rad, undef, [ $vertex->[X], $vertex->[Y] ]))[0] }, $vertex->[Z]); + } +} + sub materials_count { my $self = shift; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index b5482f7a..7a380fa5 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -87,6 +87,8 @@ sub _build_fill_maker { return Slic3r::Fill->new(print => $self); } +# caller is responsible for supplying models whose objects don't collide +# and have explicit instance positions sub add_model { my $self = shift; my ($model) = @_; @@ -103,13 +105,18 @@ sub add_model { } } + # optimization: if avoid_crossing_perimeters is enabled, split + # this mesh into distinct objects so that we reduce the complexity + # of the graphs + $model->split_meshes if $Slic3r::Config->avoid_crossing_perimeters && !$Slic3r::Config->complete_objects; + foreach my $object (@{ $model->objects }) { + # extract meshes by material my @meshes = (); # by region_id - foreach my $volume (@{$object->volumes}) { - # should the object contain multiple volumes of the same material, merge them my $region_id = defined $volume->material_id ? $materials{$volume->material_id} : 0; my $mesh = $volume->mesh->clone; + # should the object contain multiple volumes of the same material, merge them $meshes[$region_id] = $meshes[$region_id] ? Slic3r::TriangleMesh->merge($meshes[$region_id], $mesh) : $mesh; @@ -119,41 +126,22 @@ sub add_model { next unless $mesh; $mesh->check_manifoldness; - if ($object->instances) { - # we ignore the per-instance rotation currently and only - # consider the first one - $mesh->rotate($object->instances->[0]->rotation); - } + # we ignore the per-instance rotation currently and only + # consider the first one + $mesh->rotate($object->instances->[0]->rotation); - $mesh->rotate($Slic3r::Config->rotate); - $mesh->scale($Slic3r::Config->scale / &Slic3r::SCALING_FACTOR); + $mesh->scale(1 / &Slic3r::SCALING_FACTOR); } - my @defined_meshes = grep defined $_, @meshes; - my $complete_mesh = @defined_meshes == 1 ? $defined_meshes[0] : Slic3r::TriangleMesh->merge(@defined_meshes); - # initialize print object - my $print_object = Slic3r::Print::Object->new( + push @{$self->objects}, Slic3r::Print::Object->new( print => $self, meshes => [ @meshes ], - size => [ $complete_mesh->size ], + copies => [ map [ scale $_->offset->[X], scale $_->offset->[Y] ], @{$object->instances} ], + size => [ map scale $_, @{ $object->size } ], input_file => $object->input_file, layer_height_ranges => $object->layer_height_ranges, ); - push @{$self->objects}, $print_object; - - # align object to origin - { - my @extents = $complete_mesh->extents; - foreach my $mesh (grep defined $_, @meshes) { - $mesh->move(map -$extents[$_][MIN], X,Y,Z); - } - } - - if ($object->instances) { - # replace the default [0,0] instance with the custom ones - $print_object->copies([ map [ scale $_->offset->[X], scale $_->offset->[Y] ], @{$object->instances} ]); - } } } @@ -282,54 +270,12 @@ sub regions_count { return scalar @{$self->regions}; } -sub duplicate { - my $self = shift; - - if ($Slic3r::Config->duplicate_grid->[X] > 1 || $Slic3r::Config->duplicate_grid->[Y] > 1) { - if (@{$self->objects} > 1) { - die "Grid duplication is not supported with multiple objects\n"; - } - my $object = $self->objects->[0]; - - # generate offsets for copies - my $dist = scale $Slic3r::Config->duplicate_distance; - @{$self->objects->[0]->copies} = (); - for my $x_copy (1..$Slic3r::Config->duplicate_grid->[X]) { - for my $y_copy (1..$Slic3r::Config->duplicate_grid->[Y]) { - push @{$self->objects->[0]->copies}, [ - ($object->size->[X] + $dist) * ($x_copy-1), - ($object->size->[Y] + $dist) * ($y_copy-1), - ]; - } - } - } elsif ($Slic3r::Config->duplicate > 1) { - foreach my $object (@{$self->objects}) { - @{$object->copies} = map [0,0], 1..$Slic3r::Config->duplicate; - } - $self->arrange_objects; - } -} - -sub arrange_objects { - my $self = shift; - - my $total_parts = scalar map @{$_->copies}, @{$self->objects}; - my $partx = max(map $_->size->[X], @{$self->objects}); - my $party = max(map $_->size->[Y], @{$self->objects}); - - my @positions = Slic3r::Geometry::arrange - ($total_parts, $partx, $party, (map scale $_, @{$Slic3r::Config->bed_size}), scale $Slic3r::Config->min_object_distance, $self->config); - - @{$_->copies} = splice @positions, 0, scalar @{$_->copies} for @{$self->objects}; -} - sub bounding_box { my $self = shift; my @points = (); - foreach my $obj_idx (0 .. $#{$self->objects}) { - my $object = $self->objects->[$obj_idx]; - foreach my $copy (@{$self->objects->[$obj_idx]->copies}) { + foreach my $object (@{$self->objects}) { + foreach my $copy (@{$object->copies}) { push @points, [ $copy->[X], $copy->[Y] ], [ $copy->[X] + $object->size->[X], $copy->[Y] ], diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 7b21db87..39913d59 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -11,7 +11,7 @@ has 'print' => (is => 'ro', weak_ref => 1, required => 1); has 'input_file' => (is => 'rw', required => 0); has 'meshes' => (is => 'rw', default => sub { [] }); # by region_id has 'size' => (is => 'rw', required => 1); -has 'copies' => (is => 'rw', default => sub {[ [0,0] ]}, trigger => 1); +has 'copies' => (is => 'rw', trigger => 1); # in scaled coordinates has 'layers' => (is => 'rw', default => sub { [] }); has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ] @@ -76,6 +76,7 @@ sub BUILD { } } +# This should be probably moved in Print.pm at the point where we sort Layer objects sub _trigger_copies { my $self = shift; return unless @{$self->copies} > 1; @@ -166,6 +167,8 @@ sub slice { } }, ); + + $self->meshes->[$region_id] = undef; # free memory } die "Invalid input file\n" if !@{$self->layers}; diff --git a/lib/Slic3r/SVG.pm b/lib/Slic3r/SVG.pm index a7909485..0ac28639 100644 --- a/lib/Slic3r/SVG.pm +++ b/lib/Slic3r/SVG.pm @@ -9,7 +9,7 @@ use constant Y => 1; our $filltype = 'evenodd'; -sub factor {return 30; +sub factor { return &Slic3r::SCALING_FACTOR * 10; } diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index d6a10f37..63ee7ac6 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -30,7 +30,9 @@ sub model { } my $model = Slic3r::Model->new; - $model->add_object(vertices => $vertices)->add_volume(facets => $facets); + my $object = $model->add_object(vertices => $vertices); + $object->add_volume(facets => $facets); + $object->add_instance(offset => [0,0]); return $model; } diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm index 9ffe542f..0968f735 100644 --- a/lib/Slic3r/TriangleMesh.pm +++ b/lib/Slic3r/TriangleMesh.pm @@ -10,9 +10,9 @@ has 'vertices' => (is => 'ro', required => 1); # id => [$x,$y,$z] has 'facets' => (is => 'ro', required => 1); # id => [ $v1_id, $v2_id, $v3_id ] # private -has 'edges' => (is => 'ro', default => sub { [] }); # id => [ $v1_id, $v2_id ] -has 'facets_edges' => (is => 'ro', default => sub { [] }); # id => [ $e1_id, $e2_id, $e3_id ] -has 'edges_facets' => (is => 'ro', default => sub { [] }); # id => [ $f1_id, $f2_id, (...) ] +has 'edges' => (is => 'rw'); # id => [ $v1_id, $v2_id ] +has 'facets_edges' => (is => 'rw'); # id => [ $e1_id, $e2_id, $e3_id ] +has 'edges_facets' => (is => 'rw'); # id => [ $f1_id, $f2_id, (...) ] use constant MIN => 0; use constant MAX => 1; @@ -29,13 +29,13 @@ use constant I_FACET_EDGE => 6; use constant FE_TOP => 0; use constant FE_BOTTOM => 1; -# always make sure this method is idempotent sub analyze { my $self = shift; - @{$self->edges} = (); - @{$self->facets_edges} = (); - @{$self->edges_facets} = (); + return if defined $self->edges; + $self->edges([]); + $self->facets_edges([]); + $self->edges_facets([]); my %table = (); # edge_coordinates => edge_id for (my $facet_id = 0; $facet_id <= $#{$self->facets}; $facet_id++) { diff --git a/slic3r.pl b/slic3r.pl index b3dec496..1d09441d 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -91,13 +91,19 @@ if (@ARGV) { # slicing from command line $config->validate; while (my $input_file = shift @ARGV) { - my $print = Slic3r::Print->new(config => $config); - $print->add_model(Slic3r::Model->read_from_file($input_file)); + my $model; if ($opt{merge}) { - $print->add_model(Slic3r::Model->read_from_file($_)) for splice @ARGV, 0; + my @models = map Slic3r::Model->read_from_file($_), $input_file, (splice @ARGV, 0); + $model = Slic3r::Model->merge(@models); + } else { + $model = Slic3r::Model->read_from_file($input_file); } - $print->duplicate; - $print->arrange_objects if @{$print->objects} > 1; + $_->scale($config->scale) for @{$model->objects}; + $_->rotate($config->rotate) for @{$model->objects}; + $model->arrange_objects($config); + + my $print = Slic3r::Print->new(config => $config); + $print->add_model($model); $print->validate; my %params = ( output_file => $opt{output}, diff --git a/t/slice.t b/t/slice.t index cfa78a60..e47929f0 100644 --- a/t/slice.t +++ b/t/slice.t @@ -16,8 +16,6 @@ my @lines; my $z = 20; my @points = ([3, 4], [8, 5], [1, 9]); # XY coordinates of the facet vertices -my $mesh = Slic3r::TriangleMesh->new(facets => [], vertices => []); - # NOTE: # the first point of the intersection lines is replaced by -1 because TriangleMesh.pm # is saving memory and doesn't store point A anymore since it's not actually needed. @@ -104,21 +102,23 @@ my @upper = intersect(20, 20, 10); is $lower[0][Slic3r::TriangleMesh::I_FACET_EDGE], Slic3r::TriangleMesh::FE_BOTTOM, 'bottom edge on layer'; is $upper[0][Slic3r::TriangleMesh::I_FACET_EDGE], Slic3r::TriangleMesh::FE_TOP, 'upper edge on layer'; +my $mesh; + +sub intersect { + $mesh = Slic3r::TriangleMesh->new( + facets => [], + vertices => [], + ); + push @{$mesh->facets}, [ [0,0,0], @{vertices(@_)} ]; + $mesh->analyze; + return map Slic3r::TriangleMesh::unpack_line($_), $mesh->intersect_facet($#{$mesh->facets}, $z); +} + sub vertices { push @{$mesh->vertices}, map [ @{$points[$_]}, $_[$_] ], 0..2; [ ($#{$mesh->vertices}-2) .. $#{$mesh->vertices} ] } -sub add_facet { - push @{$mesh->facets}, [ [0,0,0], @{vertices(@_)} ]; - $mesh->analyze; -} - -sub intersect { - add_facet(@_); - return map Slic3r::TriangleMesh::unpack_line($_), $mesh->intersect_facet($#{$mesh->facets}, $z); -} - sub lines { my @lines = intersect(@_); #$_->a->[X] = sprintf('%.0f', $_->a->[X]) for @lines; From f599ed00c605ccd478323cea984ed39a93370f85 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 18 May 2013 16:57:44 +0200 Subject: [PATCH 112/179] Avoid closures, move planning code to _plan() and layer G-code generation to Slic3r::GCode::Layer --- MANIFEST | 1 + lib/Slic3r.pm | 1 + lib/Slic3r/GCode.pm | 61 ++++++------ lib/Slic3r/GCode/Layer.pm | 202 ++++++++++++++++++++++++++++++++++++++ lib/Slic3r/Print.pm | 191 +++-------------------------------- 5 files changed, 246 insertions(+), 210 deletions(-) create mode 100644 lib/Slic3r/GCode/Layer.pm diff --git a/MANIFEST b/MANIFEST index 22766599..a9180a69 100644 --- a/MANIFEST +++ b/MANIFEST @@ -25,6 +25,7 @@ lib/Slic3r/Format/OBJ.pm lib/Slic3r/Format/STL.pm lib/Slic3r/GCode.pm lib/Slic3r/GCode/CoolingBuffer.pm +lib/Slic3r/GCode/Layer.pm lib/Slic3r/GCode/MotionPlanner.pm lib/Slic3r/GCode/Reader.pm lib/Slic3r/GCode/SpiralVase.pm diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 9214022f..1d2849f1 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -46,6 +46,7 @@ use Slic3r::Format::OBJ; use Slic3r::Format::STL; use Slic3r::GCode; use Slic3r::GCode::CoolingBuffer; +use Slic3r::GCode::Layer; use Slic3r::GCode::MotionPlanner; use Slic3r::GCode::Reader; use Slic3r::GCode::SpiralVase; diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 7cd3d0ef..1180b76f 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -294,34 +294,6 @@ sub travel_to { $self->speed('travel'); $gcode .= $self->G0($point, undef, 0, $comment || ""); } else { - my $plan = sub { - my $mp = shift; - - my $gcode = ""; - my @travel = $mp->shortest_path($self->last_pos, $point)->lines; - - # if the path is not contained in a single island we need to retract - my $need_retract = !$Slic3r::Config->only_retract_when_crossing_perimeters; - if (!$need_retract) { - $need_retract = 1; - foreach my $slice (@{$self->layer->slices}) { - # discard the island if at any line is not enclosed in it - next if first { !$slice->encloses_line($_, scaled_epsilon) } @travel; - # okay, this island encloses the full travel path - $need_retract = 0; - last; - } - } - - # do the retract (the travel_to argument is broken) - $gcode .= $self->retract(travel_to => $point) if $need_retract; - - # append the actual path and return - $self->speed('travel'); - $gcode .= join '', map $self->G0($_->[B], undef, 0, $comment || ""), @travel; - return $gcode; - }; - if ($self->new_object) { $self->new_object(0); @@ -332,16 +304,45 @@ sub travel_to { # calculate path (external_mp uses G-code coordinates so we temporary need a null shift) $self->set_shift(0,0); - $gcode .= $plan->($self->external_mp); + $gcode .= $self->_plan($self->external_mp, $point, $comment); $self->set_shift(@shift); } else { - $gcode .= $plan->($self->layer_mp); + $gcode .= $self->_plan($self->layer_mp, $point, $comment); } } return $gcode; } +sub _plan { + my $self = shift; + my ($mp, $point, $comment) = @_; + + my $gcode = ""; + my @travel = $mp->shortest_path($self->last_pos, $point)->lines; + + # if the path is not contained in a single island we need to retract + my $need_retract = !$Slic3r::Config->only_retract_when_crossing_perimeters; + if (!$need_retract) { + $need_retract = 1; + foreach my $slice (@{$self->layer->slices}) { + # discard the island if at any line is not enclosed in it + next if first { !$slice->encloses_line($_, scaled_epsilon) } @travel; + # okay, this island encloses the full travel path + $need_retract = 0; + last; + } + } + + # do the retract (the travel_to argument is broken) + $gcode .= $self->retract(travel_to => $point) if $need_retract; + + # append the actual path and return + $self->speed('travel'); + $gcode .= join '', map $self->G0($_->[B], undef, 0, $comment || ""), @travel; + return $gcode; +} + sub retract { my $self = shift; my %params = @_; diff --git a/lib/Slic3r/GCode/Layer.pm b/lib/Slic3r/GCode/Layer.pm new file mode 100644 index 00000000..4a6eb366 --- /dev/null +++ b/lib/Slic3r/GCode/Layer.pm @@ -0,0 +1,202 @@ +package Slic3r::GCode::Layer; +use Moo; + +use Slic3r::Geometry qw(X Y unscale); + +has 'print' => (is => 'ro', required => 1, handles => [qw(extruders)]); +has 'gcodegen' => (is => 'ro', required => 1); +has 'shift' => (is => 'ro', required => 1); + +has 'spiralvase' => (is => 'lazy'); +has 'skirt_done' => (is => 'rw', default => sub {0}); # count of skirt layers done +has 'brim_done' => (is => 'rw'); +has 'second_layer_things_done' => (is => 'rw'); +has '_last_obj_copy' => (is => 'rw'); + +sub _build_spiralvase { + my $self = shift; + + return $Slic3r::Config->spiral_vase + ? Slic3r::GCode::SpiralVase->new + : undef; +} + +sub process_layer { + my $self = shift; + my ($layer, $object_copies) = @_; + my $gcode = ""; + + if (!$self->second_layer_things_done && $layer->id == 1) { + for my $t (grep $self->extruders->[$_], 0 .. $#{$Slic3r::Config->temperature}) { + $gcode .= $self->gcodegen->set_temperature($self->extruders->[$t]->temperature, 0, $t) + if $self->print->extruders->[$t]->temperature && $self->extruders->[$t]->temperature != $self->extruders->[$t]->first_layer_temperature; + } + $gcode .= $self->gcodegen->set_bed_temperature($Slic3r::Config->bed_temperature) + if $Slic3r::Config->bed_temperature && $Slic3r::Config->bed_temperature != $Slic3r::Config->first_layer_bed_temperature; + $self->second_layer_things_done(1); + } + + # set new layer, but don't move Z as support material contact areas may need an intermediate one + $gcode .= $self->gcodegen->change_layer($layer); + + # prepare callback to call as soon as a Z command is generated + $self->gcodegen->move_z_callback(sub { + $self->gcodegen->move_z_callback(undef); # circular ref or not? + return "" if !$Slic3r::Config->layer_gcode; + return $Slic3r::Config->replace_options($Slic3r::Config->layer_gcode) . "\n"; + }); + + # extrude skirt + if ($self->skirt_done < $Slic3r::Config->skirt_height) { + $self->gcodegen->set_shift(@{$self->shift}); + $gcode .= $self->gcodegen->set_extruder($self->extruders->[0]); # move_z requires extruder + $gcode .= $self->gcodegen->move_z($self->gcodegen->layer->print_z); + # skip skirt if we have a large brim + if ($layer->id < $Slic3r::Config->skirt_height) { + # distribute skirt loops across all extruders + for my $i (0 .. $#{$self->print->skirt}) { + # when printing layers > 0 ignore 'min_skirt_length' and + # just use the 'skirts' setting; also just use the current extruder + last if ($layer->id > 0) && ($i >= $Slic3r::Config->skirts); + $gcode .= $self->gcodegen->set_extruder($self->extruders->[ ($i/@{$self->extruders}) % @{$self->extruders} ]) + if $layer->id == 0; + $gcode .= $self->gcodegen->extrude_loop($self->print->skirt->[$i], 'skirt'); + } + } + $self->skirt_done($self->skirt_done + 1); + $self->gcodegen->straight_once(1); + } + + # extrude brim + if (!$self->brim_done) { + $gcode .= $self->gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]); # move_z requires extruder + $gcode .= $self->gcodegen->move_z($self->gcodegen->layer->print_z); + $self->gcodegen->set_shift(@{$self->shift}); + $gcode .= $self->gcodegen->extrude_loop($_, 'brim') for @{$self->print->brim}; + $self->brim_done(1); + $self->gcodegen->straight_once(1); + } + + for my $copy (@$object_copies) { + $self->gcodegen->new_object(1) if ($self->_last_obj_copy // '') ne "$copy"; + $self->_last_obj_copy("$copy"); + + $self->gcodegen->set_shift(map $self->shift->[$_] + unscale $copy->[$_], X,Y); + + # extrude support material before other things because it might use a lower Z + # and also because we avoid travelling on other things when printing it + if ($self->print->has_support_material) { + $gcode .= $self->gcodegen->move_z($layer->support_material_contact_z) + if ($layer->support_contact_fills && @{ $layer->support_contact_fills->paths }); + $gcode .= $self->gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]); + if ($layer->support_contact_fills) { + $gcode .= $self->gcodegen->extrude_path($_, 'support material contact area') + for $layer->support_contact_fills->chained_path($self->gcodegen->last_pos); + } + + $gcode .= $self->gcodegen->move_z($layer->print_z); + if ($layer->support_fills) { + $gcode .= $self->gcodegen->extrude_path($_, 'support material') + for $layer->support_fills->chained_path($self->gcodegen->last_pos); + } + } + + # set actual Z - this will force a retraction + $gcode .= $self->gcodegen->move_z($layer->print_z); + + # tweak region ordering to save toolchanges + my @region_ids = 0 .. ($self->print->regions_count-1); + if ($self->gcodegen->multiple_extruders) { + my $last_extruder = $self->gcodegen->extruder; + my $best_region_id = first { $self->print->regions->[$_]->extruders->{perimeter} eq $last_extruder } @region_ids; + @region_ids = ($best_region_id, grep $_ != $best_region_id, @region_ids) if $best_region_id; + } + + foreach my $region_id (@region_ids) { + my $layerm = $layer->regions->[$region_id]; + my $region = $self->print->regions->[$region_id]; + + my @islands = (); + if ($Slic3r::Config->avoid_crossing_perimeters) { + push @islands, map +{ perimeters => [], fills => [] }, @{$layer->slices}; + PERIMETER: foreach my $perimeter (@{$layerm->perimeters}) { + my $p = $perimeter->unpack; + for my $i (0 .. $#{$layer->slices}-1) { + if ($layer->slices->[$i]->contour->encloses_point($p->first_point)) { + push @{ $islands[$i]{perimeters} }, $p; + next PERIMETER; + } + } + push @{ $islands[-1]{perimeters} }, $p; # optimization + } + FILL: foreach my $fill (@{$layerm->fills}) { + my $f = $fill->unpack; + for my $i (0 .. $#{$layer->slices}-1) { + if ($layer->slices->[$i]->contour->encloses_point($f->first_point)) { + push @{ $islands[$i]{fills} }, $f; + next FILL; + } + } + push @{ $islands[-1]{fills} }, $f; # optimization + } + } else { + push @islands, { + perimeters => $layerm->perimeters, + fills => $layerm->fills, + }; + } + + foreach my $island (@islands) { + # give priority to infill if we were already using its extruder and it wouldn't + # be good for perimeters + if ($Slic3r::Config->infill_first + || ($self->gcodegen->multiple_extruders && $region->extruders->{infill} eq $self->gcodegen->extruder) && $region->extruders->{infill} ne $region->extruders->{perimeter}) { + $gcode .= $self->_extrude_infill($island, $region); + $gcode .= $self->_extrude_perimeters($island, $region); + } else { + $gcode .= $self->_extrude_perimeters($island, $region); + $gcode .= $self->_extrude_infill($island, $region); + } + } + } + } + + # apply spiral vase post-processing if this layer contains suitable geometry + $gcode = $self->spiralvase->process_layer($gcode, $layer) + if defined $self->spiralvase + && ($layer->id > 0 || $Slic3r::Config->brim_width == 0) + && ($layer->id >= $Slic3r::Config->skirt_height) + && ($layer->id >= $Slic3r::Config->bottom_solid_layers); + + return $gcode; +} + +sub _extrude_perimeters { + my $self = shift; + my ($island, $region) = @_; + + return "" if !@{ $island->{perimeters} }; + return $self->gcodegen->set_extruder($region->extruders->{perimeter}) + . $self->gcodegen->extrude($_, 'perimeter') for @{ $island->{perimeters} }; +} + +sub _extrude_infill { + my $self = shift; + my ($island, $region) = @_; + + return "" if !@{ $island->{fills} }; + + my $gcode = ""; + $gcode .= $self->gcodegen->set_extruder($region->extruders->{infill}); + for my $fill (@{ $island->{fills} }) { + if ($fill->isa('Slic3r::ExtrusionPath::Collection')) { + $gcode .= $self->gcodegen->extrude($_, 'fill') + for $fill->chained_path($self->gcodegen->last_pos); + } else { + $gcode .= $self->gcodegen->extrude($fill, 'fill') ; + } + } + return $gcode; +} + +1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 7a380fa5..c5e341cb 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -759,183 +759,12 @@ sub write_gcode { )); } - # prepare the SpiralVase processor if it's possible - my $spiralvase = $Slic3r::Config->spiral_vase - ? Slic3r::GCode::SpiralVase->new - : undef; - - # prepare the logic to print one layer - my $skirt_done = 0; # count of skirt layers done - my $brim_done = 0; - my $second_layer_things_done = 0; - my $last_obj_copy = ""; - my $extrude_layer = sub { - my ($layer, $object_copies) = @_; - my $gcode = ""; - - if (!$second_layer_things_done && $layer->id == 1) { - for my $t (grep $self->extruders->[$_], 0 .. $#{$Slic3r::Config->temperature}) { - $gcode .= $gcodegen->set_temperature($self->extruders->[$t]->temperature, 0, $t) - if $self->extruders->[$t]->temperature && $self->extruders->[$t]->temperature != $self->extruders->[$t]->first_layer_temperature; - } - $gcode .= $gcodegen->set_bed_temperature($Slic3r::Config->bed_temperature) - if $Slic3r::Config->bed_temperature && $Slic3r::Config->bed_temperature != $Slic3r::Config->first_layer_bed_temperature; - $second_layer_things_done = 1; - } - - # set new layer, but don't move Z as support material contact areas may need an intermediate one - $gcode .= $gcodegen->change_layer($layer); - - # prepare callback to call as soon as a Z command is generated - $gcodegen->move_z_callback(sub { - $gcodegen->move_z_callback(undef); # circular ref or not? - return "" if !$Slic3r::Config->layer_gcode; - return $Slic3r::Config->replace_options($Slic3r::Config->layer_gcode) . "\n"; - }); - - # extrude skirt - if ($skirt_done < $Slic3r::Config->skirt_height) { - $gcodegen->set_shift(@shift); - $gcode .= $gcodegen->set_extruder($self->extruders->[0]); # move_z requires extruder - $gcode .= $gcodegen->move_z($gcodegen->layer->print_z); - # skip skirt if we have a large brim - if ($layer->id < $Slic3r::Config->skirt_height) { - # distribute skirt loops across all extruders - for my $i (0 .. $#{$self->skirt}) { - # when printing layers > 0 ignore 'min_skirt_length' and - # just use the 'skirts' setting; also just use the current extruder - last if ($layer->id > 0) && ($i >= $Slic3r::Config->skirts); - $gcode .= $gcodegen->set_extruder($self->extruders->[ ($i/@{$self->extruders}) % @{$self->extruders} ]) - if $layer->id == 0; - $gcode .= $gcodegen->extrude_loop($self->skirt->[$i], 'skirt'); - } - } - $skirt_done++; - $gcodegen->straight_once(1); - } - - # extrude brim - if (!$brim_done) { - $gcode .= $gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]); # move_z requires extruder - $gcode .= $gcodegen->move_z($gcodegen->layer->print_z); - $gcodegen->set_shift(@shift); - $gcode .= $gcodegen->extrude_loop($_, 'brim') for @{$self->brim}; - $brim_done = 1; - $gcodegen->straight_once(1); - } - - for my $copy (@$object_copies) { - $gcodegen->new_object(1) if $last_obj_copy && $last_obj_copy ne "$copy"; - $last_obj_copy = "$copy"; - - $gcodegen->set_shift(map $shift[$_] + unscale $copy->[$_], X,Y); - - # extrude support material before other things because it might use a lower Z - # and also because we avoid travelling on other things when printing it - if ($self->has_support_material) { - $gcode .= $gcodegen->move_z($layer->support_material_contact_z) - if ($layer->support_contact_fills && @{ $layer->support_contact_fills->paths }); - $gcode .= $gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]); - if ($layer->support_contact_fills) { - $gcode .= $gcodegen->extrude_path($_, 'support material contact area') - for $layer->support_contact_fills->chained_path($gcodegen->last_pos); - } - - $gcode .= $gcodegen->move_z($layer->print_z); - if ($layer->support_fills) { - $gcode .= $gcodegen->extrude_path($_, 'support material') - for $layer->support_fills->chained_path($gcodegen->last_pos); - } - } - - # set actual Z - this will force a retraction - $gcode .= $gcodegen->move_z($layer->print_z); - - # tweak region ordering to save toolchanges - my @region_ids = 0 .. ($self->regions_count-1); - if ($gcodegen->multiple_extruders) { - my $last_extruder = $gcodegen->extruder; - my $best_region_id = first { $self->regions->[$_]->extruders->{perimeter} eq $last_extruder } @region_ids; - @region_ids = ($best_region_id, grep $_ != $best_region_id, @region_ids) if $best_region_id; - } - - foreach my $region_id (@region_ids) { - my $layerm = $layer->regions->[$region_id]; - my $region = $self->regions->[$region_id]; - - my @islands = (); - if ($Slic3r::Config->avoid_crossing_perimeters) { - push @islands, map +{ perimeters => [], fills => [] }, @{$layer->slices}; - PERIMETER: foreach my $perimeter (@{$layerm->perimeters}) { - my $p = $perimeter->unpack; - for my $i (0 .. $#{$layer->slices}-1) { - if ($layer->slices->[$i]->contour->encloses_point($p->first_point)) { - push @{ $islands[$i]{perimeters} }, $p; - next PERIMETER; - } - } - push @{ $islands[-1]{perimeters} }, $p; # optimization - } - FILL: foreach my $fill (@{$layerm->fills}) { - my $f = $fill->unpack; - for my $i (0 .. $#{$layer->slices}-1) { - if ($layer->slices->[$i]->contour->encloses_point($f->first_point)) { - push @{ $islands[$i]{fills} }, $f; - next FILL; - } - } - push @{ $islands[-1]{fills} }, $f; # optimization - } - } else { - push @islands, { - perimeters => $layerm->perimeters, - fills => $layerm->fills, - }; - } - - foreach my $island (@islands) { - my $extrude_perimeters = sub { - return if !@{ $island->{perimeters} }; - $gcode .= $gcodegen->set_extruder($region->extruders->{perimeter}); - $gcode .= $gcodegen->extrude($_, 'perimeter') for @{ $island->{perimeters} }; - }; - - my $extrude_fills = sub { - return if !@{ $island->{fills} }; - $gcode .= $gcodegen->set_extruder($region->extruders->{infill}); - for my $fill (@{ $island->{fills} }) { - if ($fill->isa('Slic3r::ExtrusionPath::Collection')) { - $gcode .= $gcodegen->extrude($_, 'fill') - for $fill->chained_path($gcodegen->last_pos); - } else { - $gcode .= $gcodegen->extrude($fill, 'fill') ; - } - } - }; - - # give priority to infill if we were already using its extruder and it wouldn't - # be good for perimeters - if ($Slic3r::Config->infill_first - || ($gcodegen->multiple_extruders && $region->extruders->{infill} eq $gcodegen->extruder) && $region->extruders->{infill} ne $region->extruders->{perimeter}) { - $extrude_fills->(); - $extrude_perimeters->(); - } else { - $extrude_perimeters->(); - $extrude_fills->(); - } - } - } - } - - # apply spiral vase post-processing if this layer contains suitable geometry - $gcode = $spiralvase->process_layer($gcode, $layer) - if defined $spiralvase - && ($layer->id > 0 || $Slic3r::Config->brim_width == 0) - && ($layer->id >= $Slic3r::Config->skirt_height) - && ($layer->id >= $Slic3r::Config->bottom_solid_layers); - - return $gcode; - }; + # prepare the layer processor + my $layer_gcode = Slic3r::GCode::Layer->new( + print => $self, + gcodegen => $gcodegen, + shift => \@shift, + ); # do all objects for each layer if ($Slic3r::Config->complete_objects) { @@ -970,7 +799,7 @@ sub write_gcode { if $Slic3r::Config->first_layer_bed_temperature; $print_first_layer_temperature->(); } - print $fh $buffer->append($extrude_layer->($layer, [$copy]), $layer); + print $fh $buffer->append($layer_gcode->process_layer($layer, [$copy]), $layer); } print $fh $buffer->flush; $finished_objects++; @@ -981,8 +810,10 @@ sub write_gcode { config => $Slic3r::Config, gcodegen => $gcodegen, ); - print $fh $buffer->append($extrude_layer->($_, $_->object->copies), $_) - for sort { $a->print_z <=> $b->print_z } map @{$_->layers}, @{$self->objects}; + my @layers = sort { $a->print_z <=> $b->print_z } map @{$_->layers}, @{$self->objects}; + foreach my $layer (@layers) { + print $fh $buffer->append($layer_gcode->process_layer($layer, $layer->object->copies), $layer); + } print $fh $buffer->flush; } From 7180e7cb30938e996f86b4329f77bf5c1a8ef1b0 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 19 May 2013 10:35:11 +0200 Subject: [PATCH 113/179] Speed optimization in avoid_crossing_perimeters --- lib/Slic3r/GCode/MotionPlanner.pm | 6 ++++-- lib/Slic3r/Polygon.pm | 6 ------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/Slic3r/GCode/MotionPlanner.pm b/lib/Slic3r/GCode/MotionPlanner.pm index 6eff6c3b..02648535 100644 --- a/lib/Slic3r/GCode/MotionPlanner.pm +++ b/lib/Slic3r/GCode/MotionPlanner.pm @@ -75,6 +75,7 @@ sub BUILD { { my @outer = (map @$_, @{$self->_outer}); + my @outer_ex = map [$_], @outer; # as ExPolygons # lines of outer polygons connect visible points for my $i (0 .. $#outer) { @@ -91,7 +92,7 @@ sub BUILD { for my $m (0 .. $#{$outer[$i]}) { for my $n (0 .. $#{$outer[$j]}) { my $line = Slic3r::Line->new($outer[$i][$m], $outer[$j][$n]); - if (!first { $_->intersects_line($line) } @outer) { + if (!@{Boost::Geometry::Utils::multi_polygon_multi_linestring_intersection(\@outer_ex, [$line])}) { # this line does not cross any polygon my $dist = $line->length; $edges->{$outer[$i][$m]}{$outer[$j][$n]} = $dist; @@ -106,12 +107,13 @@ sub BUILD { # lines connecting inner polygons contours are visible but discouraged if (!$self->no_internal) { my @inner = (map $_->contour, map @$_, @{$self->_inner}); + my @inner_ex = map [$_], @inner; # as ExPolygons for my $i (0 .. $#inner) { for my $j (($i+1) .. $#inner) { for my $m (0 .. $#{$inner[$i]}) { for my $n (0 .. $#{$inner[$j]}) { my $line = Slic3r::Line->new($inner[$i][$m], $inner[$j][$n]); - if (!first { $_->intersects_line($line) } @inner) { + if (!@{Boost::Geometry::Utils::multi_polygon_multi_linestring_intersection(\@inner_ex, [$line])}) { # this line does not cross any polygon my $dist = $line->length * CROSSING_FACTOR; $edges->{$inner[$i][$m]}{$inner[$j][$n]} = $dist; diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm index f8df6915..6e460f40 100644 --- a/lib/Slic3r/Polygon.pm +++ b/lib/Slic3r/Polygon.pm @@ -172,10 +172,4 @@ sub split_at_first_point { return $self->split_at_index(0); } -sub intersects_line { - my $self = shift; - my ($line) = @_; - return @{Boost::Geometry::Utils::polygon_multi_linestring_intersection([$self], [$line])} > 0; -} - 1; \ No newline at end of file From b5b0c2cc8944375d9361086236d8f332517a4dea Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 19 May 2013 10:47:00 +0200 Subject: [PATCH 114/179] Bad copy and paste in commit from yesterday causing loss of perimeters. #1178 --- lib/Slic3r/GCode/Layer.pm | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GCode/Layer.pm b/lib/Slic3r/GCode/Layer.pm index 4a6eb366..1cb0b90b 100644 --- a/lib/Slic3r/GCode/Layer.pm +++ b/lib/Slic3r/GCode/Layer.pm @@ -176,8 +176,11 @@ sub _extrude_perimeters { my ($island, $region) = @_; return "" if !@{ $island->{perimeters} }; - return $self->gcodegen->set_extruder($region->extruders->{perimeter}) - . $self->gcodegen->extrude($_, 'perimeter') for @{ $island->{perimeters} }; + + my $gcode = ""; + $gcode .= $self->gcodegen->set_extruder($region->extruders->{perimeter}); + $gcode .= $self->gcodegen->extrude($_, 'perimeter') for @{ $island->{perimeters} }; + return $gcode; } sub _extrude_infill { From 627debf2843d28465d2a7a61225b5d95839af08f Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 19 May 2013 11:25:18 +0200 Subject: [PATCH 115/179] Scale bounding_box by sqrt(2) in honeycomb too to ensure it will cover the object even after rotation, like we already did for Rectilinear --- lib/Slic3r/Fill/Honeycomb.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Slic3r/Fill/Honeycomb.pm b/lib/Slic3r/Fill/Honeycomb.pm index 1985ddb9..4a44b445 100644 --- a/lib/Slic3r/Fill/Honeycomb.pm +++ b/lib/Slic3r/Fill/Honeycomb.pm @@ -38,10 +38,12 @@ sub fill_surface { # adjust actual bounding box to the nearest multiple of our hex pattern # and align it so that it matches across layers + my $bounding_box = [ @{$self->bounding_box} ]; # clone $bounding_box->[$_] = 0 for X1, Y1; { my $bb_polygon = Slic3r::Polygon->new_from_bounding_box($bounding_box); + $bb_polygon->scale(sqrt 2); $bb_polygon->rotate($rotate_vector->[0][0], $hex_center); $bounding_box = [ Slic3r::Geometry::bounding_box($bb_polygon) ]; # $bounding_box->[X1] and [Y1] represent the displacement between new bounding box offset and old one From bbb190dc68e06f8666403e6df54fac69f55de81c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 19 May 2013 11:35:41 +0200 Subject: [PATCH 116/179] Generate infill using each object's bounding_box instead of full print. #1177 --- lib/Slic3r/Fill.pm | 4 ++-- lib/Slic3r/Layer/Region.pm | 2 +- lib/Slic3r/Print.pm | 12 +++--------- lib/Slic3r/Print/Object.pm | 17 +++++++++++++++-- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index 68b677c9..ecd0c024 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -17,7 +17,7 @@ use Slic3r::Geometry::Clipper qw(union_ex diff diff_ex intersection_ex offset); use Slic3r::Surface ':types'; -has 'print' => (is => 'ro', required => 1, weak_ref => 1); +has 'object' => (is => 'ro', required => 1, weak_ref => 1); has 'fillers' => (is => 'rw', default => sub { {} }); our %FillTypes = ( @@ -40,7 +40,7 @@ sub filler { } $self->fillers->{$filler} ||= $FillTypes{$filler}->new( - bounding_box => [ $self->print->bounding_box ], + bounding_box => [ $self->object->bounding_box ], ); return $self->fillers->{$filler}; } diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 65ee99ae..1e32aff9 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -301,7 +301,7 @@ sub _fill_gaps { return unless $Slic3r::Config->gap_fill_speed > 0 && $Slic3r::Config->fill_density > 0 && @$gaps; - my $filler = $self->layer->object->print->fill_maker->filler('rectilinear'); + my $filler = $self->layer->object->fill_maker->filler('rectilinear'); $filler->layer_id($self->layer->id); # we should probably use this code to handle thin walls and remove that logic from diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index c5e341cb..9acbe2cf 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -21,7 +21,6 @@ has 'regions' => (is => 'rw', default => sub {[]}); has 'support_material_flow' => (is => 'rw'); has 'first_layer_support_material_flow' => (is => 'rw'); has 'has_support_material' => (is => 'lazy'); -has 'fill_maker' => (is => 'lazy'); # ordered collection of extrusion paths to build skirt loops has 'skirt' => ( @@ -82,11 +81,6 @@ sub _build_has_support_material { || $self->config->support_material_enforce_layers > 0; } -sub _build_fill_maker { - my $self = shift; - return Slic3r::Fill->new(print => $self); -} - # caller is responsible for supplying models whose objects don't collide # and have explicit instance positions sub add_model { @@ -362,7 +356,6 @@ sub export_gcode { # this will generate extrusion paths for each layer $status_cb->(80, "Infilling layers"); { - my $fill_maker = $self->fill_maker; Slic3r::parallelize( items => sub { my @items = (); # [obj_idx, layer_id] @@ -379,10 +372,11 @@ sub export_gcode { my $fills = {}; while (defined (my $obj_layer = $q->dequeue)) { my ($obj_idx, $layer_id, $region_id) = @$obj_layer; + my $object = $self->objects->[$obj_idx]; $fills->{$obj_idx} ||= {}; $fills->{$obj_idx}{$layer_id} ||= {}; $fills->{$obj_idx}{$layer_id}{$region_id} = [ - $fill_maker->make_fill($self->objects->[$obj_idx]->layers->[$layer_id]->regions->[$region_id]), + $object->fill_maker->make_fill($object->layers->[$layer_id]->regions->[$region_id]), ]; } return $fills; @@ -401,7 +395,7 @@ sub export_gcode { }, no_threads_cb => sub { foreach my $layerm (map @{$_->regions}, map @{$_->layers}, @{$self->objects}) { - $layerm->fills([ $fill_maker->make_fill($layerm) ]); + $layerm->fills([ $layerm->layer->object->fill_maker->make_fill($layerm) ]); } }, ); diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 39913d59..816b9865 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -14,6 +14,7 @@ has 'size' => (is => 'rw', required => 1); has 'copies' => (is => 'rw', trigger => 1); # in scaled coordinates has 'layers' => (is => 'rw', default => sub { [] }); has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ] +has 'fill_maker' => (is => 'lazy'); sub BUILD { my $self = shift; @@ -76,6 +77,11 @@ sub BUILD { } } +sub _build_fill_maker { + my $self = shift; + return Slic3r::Fill->new(object => $self); +} + # This should be probably moved in Print.pm at the point where we sort Layer objects sub _trigger_copies { my $self = shift; @@ -127,6 +133,13 @@ sub get_layer_range { return ($min_layer, $max_layer); } +sub bounding_box { + my $self = shift; + + # since the object is aligned to origin, bounding box coincides with size + return Slic3r::Geometry::bounding_box([ [0,0], $self->size ]); +} + sub slice { my $self = shift; my %params = @_; @@ -931,7 +944,7 @@ sub generate_support_material { push @angles, $angles[0] + 90; } - my $filler = $self->print->fill_maker->filler($pattern); + my $filler = $self->fill_maker->filler($pattern); my $make_pattern = sub { my ($expolygon, $density) = @_; @@ -1016,7 +1029,7 @@ sub generate_support_material { # make a solid base on bottom layer if ($layer_id == 0) { - my $filler = $self->print->fill_maker->filler('rectilinear'); + my $filler = $self->fill_maker->filler('rectilinear'); $filler->angle($Slic3r::Config->support_material_angle + 90); foreach my $expolygon (@$islands) { my @paths = $filler->fill_surface( From 48e37f97b48839fb3f071920e5bccbeb22a61936 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 19 May 2013 15:33:54 +0200 Subject: [PATCH 117/179] Fixed bad copy and paste causing multiple extruders to throw an error --- lib/Slic3r/GCode/Layer.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Slic3r/GCode/Layer.pm b/lib/Slic3r/GCode/Layer.pm index 1cb0b90b..bb74dcd2 100644 --- a/lib/Slic3r/GCode/Layer.pm +++ b/lib/Slic3r/GCode/Layer.pm @@ -1,6 +1,7 @@ package Slic3r::GCode::Layer; use Moo; +use List::Util qw(first); use Slic3r::Geometry qw(X Y unscale); has 'print' => (is => 'ro', required => 1, handles => [qw(extruders)]); From 5494f4f38558129283cbec8a2ba421aff3ec07b2 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 19 May 2013 17:34:33 +0200 Subject: [PATCH 118/179] Free @_ before spawning a new thread (known old Perl bug) as an attempt to fix the unref scalars error --- lib/Slic3r.pm | 1 + lib/Slic3r/GUI.pm | 1 + lib/Slic3r/GUI/Plater.pm | 2 ++ 3 files changed, 4 insertions(+) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 1d2849f1..bd5215c4 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -84,6 +84,7 @@ sub parallelize { $q->enqueue(@items, (map undef, 1..$Config->threads)); my $thread_cb = sub { $params{thread_cb}->($q) }; + @_ = (); foreach my $th (map threads->create($thread_cb), 1..$Config->threads) { $params{collect_cb}->($th->join); } diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 0f9057ed..a799a570 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -288,6 +288,7 @@ sub check_version { my %p = @_; Slic3r::debugf "Checking for updates...\n"; + @_ = (); threads->create(sub { my $ua = LWP::UserAgent->new; $ua->timeout(10); diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 4f63271d..1144e507 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -545,6 +545,7 @@ sub export_gcode { $self->statusbar->StartBusy; if ($Slic3r::have_threads) { + @_ = (); $self->{export_thread} = threads->create(sub { $self->export_gcode2( $print, @@ -739,6 +740,7 @@ sub make_thumbnail { } }; + @_ = (); $Slic3r::have_threads ? threads->create($cb)->detach : $cb->(); } From f13d4e4e660f4a6fdbad8df089b8ac0fc15e2622 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 19 May 2013 18:03:18 +0200 Subject: [PATCH 119/179] Restore skirt preview and extrude clearance after recent usage of int_offset() --- lib/Slic3r/GUI/Plater.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 1144e507..933dbcb2 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -877,7 +877,7 @@ sub repaint { # if sequential printing is enabled and we have more than one object if ($parent->{config}->complete_objects && (map @{$_->instances}, @{$parent->{objects}}) > 1) { my $convex_hull = Slic3r::Polygon->new(convex_hull([ map @{$_->contour}, @{$parent->{object_previews}->[-1][2]->expolygons} ])); - my ($clearance) = offset([$convex_hull], $parent->{config}->extruder_clearance_radius / 2 * $parent->{scaling_factor}, 1, JT_ROUND); + my ($clearance) = @{offset([$convex_hull], $parent->{config}->extruder_clearance_radius / 2 * $parent->{scaling_factor}, 100, JT_ROUND)}; $dc->SetPen($parent->{clearance_pen}); $dc->SetBrush($parent->{transparent_brush}); $dc->DrawPolygon($parent->_y($clearance), 0, 0); @@ -888,7 +888,7 @@ sub repaint { # draw skirt if (@{$parent->{object_previews}} && $parent->{config}->skirts) { my $convex_hull = Slic3r::Polygon->new(convex_hull([ map @{$_->contour}, map @{$_->[2]->expolygons}, @{$parent->{object_previews}} ])); - ($convex_hull) = offset([$convex_hull], $parent->{config}->skirt_distance * $parent->{scaling_factor}, 1, JT_ROUND); + ($convex_hull) = @{offset([$convex_hull], $parent->{config}->skirt_distance * $parent->{scaling_factor}, 100, JT_ROUND)}; $dc->SetPen($parent->{skirt_pen}); $dc->SetBrush($parent->{transparent_brush}); $dc->DrawPolygon($parent->_y($convex_hull), 0, 0) if $convex_hull; From c43ef450646bf03dee96419abb08599f6651e309 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 20 May 2013 09:56:55 +0200 Subject: [PATCH 120/179] Require the newest Math::Clipper and Boost::Geometry::Utils to avoid the huge memory leaks present in previous versions --- Build.PL | 4 ++-- lib/Slic3r.pm | 2 +- lib/Slic3r/Geometry/Clipper.pm | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Build.PL b/Build.PL index d7d4d8e8..522cf228 100644 --- a/Build.PL +++ b/Build.PL @@ -7,12 +7,12 @@ my $build = Module::Build->new( dist_version => '0.1', license => 'perl', requires => { - 'Boost::Geometry::Utils' => '0.08', + 'Boost::Geometry::Utils' => '0.12', 'Encode::Locale' => '0', 'File::Basename' => '0', 'File::Spec' => '0', 'Getopt::Long' => '0', - 'Math::Clipper' => '1.21', + 'Math::Clipper' => '1.22', 'Math::ConvexHull::MonotoneChain' => '0.01', 'Math::Geometry::Voronoi' => '1.3', 'Math::PlanePath' => '53', diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index bd5215c4..1338ce06 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -29,7 +29,7 @@ our $var = "$FindBin::Bin/var"; use Encode; use Encode::Locale; -use Boost::Geometry::Utils 0.08; +use Boost::Geometry::Utils 0.12; use Moo 0.091009; use Slic3r::Config; diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index 498ccbcf..e15f31a5 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -8,7 +8,7 @@ our @EXPORT_OK = qw(safety_offset safety_offset_ex offset offset_ex collapse_ex diff_ex diff union_ex intersection_ex xor_ex PFT_EVENODD JT_MITER JT_ROUND JT_SQUARE is_counter_clockwise union_pt offset2 offset2_ex traverse_pt); -use Math::Clipper 1.21 qw(:cliptypes :polyfilltypes :jointypes is_counter_clockwise area); +use Math::Clipper 1.22 qw(:cliptypes :polyfilltypes :jointypes is_counter_clockwise area); use Slic3r::Geometry qw(scale); our $clipper = Math::Clipper->new; From 71d6f428ca3d1ceda95bb100fbe07338458aff09 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 20 May 2013 10:57:27 +0200 Subject: [PATCH 121/179] Apparent fix for "Attempt to free unreferenced scalar" --- lib/Slic3r/GUI/Plater.pm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 933dbcb2..e70414a3 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -544,6 +544,11 @@ sub export_gcode { } $self->statusbar->StartBusy; + + # It looks like declaring a local $SIG{__WARN__} prevents the ugly + # "Attempt to free unreferenced scalar" warning... + local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self); + if ($Slic3r::have_threads) { @_ = (); $self->{export_thread} = threads->create(sub { From 8f5a9589485fff618921685e7d70da67826587bd Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 20 May 2013 11:02:12 +0200 Subject: [PATCH 122/179] Rename do_slice() to quick_slice() --- lib/Slic3r/GUI.pm | 8 ++++---- lib/Slic3r/GUI/SkeinPanel.pm | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index a799a570..b08ae251 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -118,12 +118,12 @@ sub OnInit { $fileMenu->Append(wxID_EXIT, "&Quit", 'Quit Slic3r'); EVT_MENU($frame, MI_LOAD_CONF, sub { $self->{skeinpanel}->load_config_file }); EVT_MENU($frame, MI_EXPORT_CONF, sub { $self->{skeinpanel}->export_config }); - EVT_MENU($frame, MI_QUICK_SLICE, sub { $self->{skeinpanel}->do_slice; + EVT_MENU($frame, MI_QUICK_SLICE, sub { $self->{skeinpanel}->quick_slice; $repeat->Enable(defined $Slic3r::GUI::SkeinPanel::last_input_file) }); - EVT_MENU($frame, MI_REPEAT_QUICK, sub { $self->{skeinpanel}->do_slice(reslice => 1) }); - EVT_MENU($frame, MI_QUICK_SAVE_AS, sub { $self->{skeinpanel}->do_slice(save_as => 1); + EVT_MENU($frame, MI_REPEAT_QUICK, sub { $self->{skeinpanel}->quick_slice(reslice => 1) }); + EVT_MENU($frame, MI_QUICK_SAVE_AS, sub { $self->{skeinpanel}->quick_slice(save_as => 1); $repeat->Enable(defined $Slic3r::GUI::SkeinPanel::last_input_file) }); - EVT_MENU($frame, MI_SLICE_SVG, sub { $self->{skeinpanel}->do_slice(save_as => 1, export_svg => 1) }); + EVT_MENU($frame, MI_SLICE_SVG, sub { $self->{skeinpanel}->quick_slice(save_as => 1, export_svg => 1) }); EVT_MENU($frame, MI_COMBINE_STLS, sub { $self->{skeinpanel}->combine_stls }); EVT_MENU($frame, wxID_PREFERENCES, sub { Slic3r::GUI::Preferences->new($frame)->ShowModal }); EVT_MENU($frame, wxID_EXIT, sub {$_[0]->Close(0)}); diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index 5defd274..6f30da47 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -73,7 +73,7 @@ sub new { return $self; } -sub do_slice { +sub quick_slice { my $self = shift; my %params = @_; From 7baaf6bf5d3cdf68a877a16d5f27e76f0fbc6e35 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 30 May 2013 20:06:05 +0200 Subject: [PATCH 123/179] Fixed regression causing skirt_height to he honored incorrectly when printing more than one object. Includes regression test #1200 --- MANIFEST | 1 + lib/Slic3r/GCode/Layer.pm | 10 ++++----- lib/Slic3r/GCode/Reader.pm | 2 +- lib/Slic3r/Test.pm | 4 +++- t/skirt_brim.t | 46 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 t/skirt_brim.t diff --git a/MANIFEST b/MANIFEST index a9180a69..45d52d6e 100644 --- a/MANIFEST +++ b/MANIFEST @@ -78,6 +78,7 @@ t/retraction.t t/serialize.t t/shells.t t/slice.t +t/skirt_brim.t t/support.t t/vibrationlimit.t utils/amf-to-stl.pl diff --git a/lib/Slic3r/GCode/Layer.pm b/lib/Slic3r/GCode/Layer.pm index bb74dcd2..d1716c40 100644 --- a/lib/Slic3r/GCode/Layer.pm +++ b/lib/Slic3r/GCode/Layer.pm @@ -9,7 +9,7 @@ has 'gcodegen' => (is => 'ro', required => 1); has 'shift' => (is => 'ro', required => 1); has 'spiralvase' => (is => 'lazy'); -has 'skirt_done' => (is => 'rw', default => sub {0}); # count of skirt layers done +has 'skirt_done' => (is => 'rw', default => sub { {} }); # print_z => 1 has 'brim_done' => (is => 'rw'); has 'second_layer_things_done' => (is => 'rw'); has '_last_obj_copy' => (is => 'rw'); @@ -48,10 +48,10 @@ sub process_layer { }); # extrude skirt - if ($self->skirt_done < $Slic3r::Config->skirt_height) { + if ((values %{$self->skirt_done}) < $Slic3r::Config->skirt_height && !$self->skirt_done->{$layer->print_z}) { $self->gcodegen->set_shift(@{$self->shift}); $gcode .= $self->gcodegen->set_extruder($self->extruders->[0]); # move_z requires extruder - $gcode .= $self->gcodegen->move_z($self->gcodegen->layer->print_z); + $gcode .= $self->gcodegen->move_z($layer->print_z); # skip skirt if we have a large brim if ($layer->id < $Slic3r::Config->skirt_height) { # distribute skirt loops across all extruders @@ -64,14 +64,14 @@ sub process_layer { $gcode .= $self->gcodegen->extrude_loop($self->print->skirt->[$i], 'skirt'); } } - $self->skirt_done($self->skirt_done + 1); + $self->skirt_done->{$layer->print_z} = 1; $self->gcodegen->straight_once(1); } # extrude brim if (!$self->brim_done) { $gcode .= $self->gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]); # move_z requires extruder - $gcode .= $self->gcodegen->move_z($self->gcodegen->layer->print_z); + $gcode .= $self->gcodegen->move_z($layer->print_z); $self->gcodegen->set_shift(@{$self->shift}); $gcode .= $self->gcodegen->extrude_loop($_, 'brim') for @{$self->print->brim}; $self->brim_done(1); diff --git a/lib/Slic3r/GCode/Reader.pm b/lib/Slic3r/GCode/Reader.pm index 235a0ad7..20a1313b 100644 --- a/lib/Slic3r/GCode/Reader.pm +++ b/lib/Slic3r/GCode/Reader.pm @@ -26,7 +26,7 @@ sub parse { my ($command, @args) = split /\s+/, $line; my %args = map { /([A-Z])(.*)/; ($1 => $2) } @args; - # check retraction + # check motion if ($command =~ /^G[01]$/) { foreach my $axis (@AXES) { if (exists $args{$axis}) { diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index 63ee7ac6..46443cc6 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -44,7 +44,9 @@ sub init_print { $config->set('gcode_comments', 1) if $ENV{SLIC3R_TESTS_GCODE}; my $print = Slic3r::Print->new(config => $config); - $print->add_model(model($model_name)); + + $model_name = [$model_name] if ref($model_name) ne 'ARRAY'; + $print->add_model(model($_)) for @$model_name; $print->validate; return $print; diff --git a/t/skirt_brim.t b/t/skirt_brim.t new file mode 100644 index 00000000..c78bf41e --- /dev/null +++ b/t/skirt_brim.t @@ -0,0 +1,46 @@ +use Test::More tests => 1; +use strict; +use warnings; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; +} + +use List::Util qw(first); +use Slic3r; +use Slic3r::Test; + +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('skirts', 1); + $config->set('skirt_height', 2); + $config->set('perimeters', 0); + $config->set('perimeter_speed', 99); + $config->set('cooling', 0); # to prevent speeds to be altered + $config->set('first_layer_speed', '100%'); # to prevent speeds to be altered + + my $test = sub { + my ($conf) = @_; + $conf ||= $config; + + my $print = Slic3r::Test::init_print(['20mm_cube','20mm_cube'], config => $config); + + my %layers_with_skirt = (); # Z => $count + Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { + my ($self, $cmd, $args, $info) = @_; + + if (defined $self->Z) { + $layers_with_skirt{$self->Z} //= 0; + $layers_with_skirt{$self->Z} = 1 + if $info->{extruding} && ($args->{F} // $self->F) == $config->perimeter_speed*60; + } + }); + fail "wrong number of layers with skirt" + unless (grep $_, values %layers_with_skirt) == $config->skirt_height; + }; + + ok $test->(), "skirt_height is honored when printing multiple objects too"; +} + +__END__ From da7649698809a1e5afc5460fdc022897ad1bb152 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 31 May 2013 11:19:36 +0200 Subject: [PATCH 124/179] Store config in Slic3r::GCode object --- lib/Slic3r/GCode.pm | 133 ++++++++++++++++++++++---------------------- lib/Slic3r/Print.pm | 1 + t/gcode.t | 6 +- 3 files changed, 72 insertions(+), 68 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 1180b76f..4519d933 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -6,6 +6,7 @@ use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(scale unscale scaled_epsilon points_coincide PI X Y B); use Slic3r::Geometry::Clipper qw(union_ex); +has 'config' => (is => 'ro', required => 1); has 'multiple_extruders' => (is => 'ro', default => sub {0} ); has 'layer_count' => (is => 'ro', required => 1 ); has 'layer' => (is => 'rw'); @@ -15,6 +16,7 @@ has 'shift_y' => (is => 'rw', default => sub {0} ); has 'z' => (is => 'rw'); has 'speed' => (is => 'rw'); +has 'speeds' => (is => 'lazy'); # mm/min has 'external_mp' => (is => 'rw'); has 'layer_mp' => (is => 'rw'); has 'new_object' => (is => 'rw', default => sub {0}); @@ -35,15 +37,14 @@ has 'dec' => (is => 'ro', default => sub { 3 } ); has 'last_dir' => (is => 'ro', default => sub { [0,0] }); has 'dir_time' => (is => 'ro', default => sub { [0,0] }); -# calculate speeds (mm/min) -has 'speeds' => ( - is => 'ro', - default => sub {+{ - map { $_ => 60 * $Slic3r::Config->get_value("${_}_speed") } +sub _build_speeds { + my $self = shift; + return { + map { $_ => 60 * $self->config->get_value("${_}_speed") } qw(travel perimeter small_perimeter external_perimeter infill solid_infill top_solid_infill support_material bridge gap_fill retract), - }}, -); + }; +} # assign speeds to roles my %role_speeds = ( @@ -81,17 +82,17 @@ sub change_layer { my ($layer) = @_; $self->layer($layer); - if ($Slic3r::Config->avoid_crossing_perimeters) { + if ($self->config->avoid_crossing_perimeters) { $self->layer_mp(Slic3r::GCode::MotionPlanner->new( islands => union_ex([ map @$_, @{$layer->slices} ], undef, 1), )); } my $gcode = ""; - if ($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) { + if ($self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) { $gcode .= sprintf "M73 P%s%s\n", int(99 * ($layer->id / ($self->layer_count - 1))), - ($Slic3r::Config->gcode_comments ? ' ; update progress' : ''); + ($self->config->gcode_comments ? ' ; update progress' : ''); } return $gcode; } @@ -102,7 +103,7 @@ sub move_z { my ($z, $comment) = @_; $z *= &Slic3r::SCALING_FACTOR; - $z += $Slic3r::Config->z_offset; + $z += $self->config->z_offset; my $gcode = ""; my $current_z = $self->z; @@ -136,9 +137,9 @@ sub extrude_loop { # find the point of the loop that is closest to the current extruder position # or randomize if requested my $last_pos = $self->last_pos; - if ($Slic3r::Config->randomize_start && $loop->role == EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER) { - $last_pos = Slic3r::Point->new(scale $Slic3r::Config->print_center->[X], scale $Slic3r::Config->bed_size->[Y]); - $last_pos->rotate(rand(2*PI), $Slic3r::Config->print_center); + if ($self->config->randomize_start && $loop->role == EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER) { + $last_pos = Slic3r::Point->new(scale $self->config->print_center->[X], scale $self->config->bed_size->[Y]); + $last_pos->rotate(rand(2*PI), $self->config->print_center); } my $start_index = $loop->nearest_point_index_to($last_pos); @@ -156,7 +157,7 @@ sub extrude_loop { $self->wipe_path($extrusion_path->polyline); # make a little move inwards before leaving loop - if ($loop->role == EXTR_ROLE_EXTERNAL_PERIMETER && $Slic3r::Config->perimeters > 1) { + if ($loop->role == EXTR_ROLE_EXTERNAL_PERIMETER && $self->config->perimeters > 1) { # detect angle between last and first segment # the side depends on the original winding order of the polygon (left for contours, right for holes) my @points = $was_clockwise ? (-2, 1) : (1, -2); @@ -187,7 +188,7 @@ sub extrude_path { $path->simplify(&Slic3r::SCALED_RESOLUTION); # detect arcs - if ($Slic3r::Config->gcode_arcs && !$recursive) { + if ($self->config->gcode_arcs && !$recursive) { my $gcode = ""; foreach my $arc_path ($path->detect_arcs) { $gcode .= $self->extrude_path($arc_path, $description, 1); @@ -204,12 +205,12 @@ sub extrude_path { # adjust acceleration my $acceleration; - if ($Slic3r::Config->perimeter_acceleration && $path->is_perimeter) { - $acceleration = $Slic3r::Config->perimeter_acceleration; - } elsif ($Slic3r::Config->infill_acceleration && $path->is_fill) { - $acceleration = $Slic3r::Config->infill_acceleration; - } elsif ($Slic3r::Config->infill_acceleration && ($path->role == EXTR_ROLE_BRIDGE || $path->role == EXTR_ROLE_INTERNALBRIDGE)) { - $acceleration = $Slic3r::Config->bridge_acceleration; + if ($self->config->perimeter_acceleration && $path->is_perimeter) { + $acceleration = $self->config->perimeter_acceleration; + } elsif ($self->config->infill_acceleration && $path->is_fill) { + $acceleration = $self->config->infill_acceleration; + } elsif ($self->config->infill_acceleration && ($path->role == EXTR_ROLE_BRIDGE || $path->role == EXTR_ROLE_INTERNALBRIDGE)) { + $acceleration = $self->config->bridge_acceleration; } $gcode .= $self->set_acceleration($acceleration) if $acceleration; @@ -251,19 +252,19 @@ sub extrude_path { if $self->extruder->wipe; } - if ($Slic3r::Config->cooling) { + if ($self->config->cooling) { my $path_time = $path_length / $self->speeds->{$self->last_speed} * 60; if ($self->layer->id == 0) { - $path_time = $Slic3r::Config->first_layer_speed =~ /^(\d+(?:\.\d+)?)%$/ + $path_time = $self->config->first_layer_speed =~ /^(\d+(?:\.\d+)?)%$/ ? $path_time / ($1/100) - : $path_length / $Slic3r::Config->first_layer_speed * 60; + : $path_length / $self->config->first_layer_speed * 60; } $self->elapsed_time($self->elapsed_time + $path_time); } # reset acceleration - $gcode .= $self->set_acceleration($Slic3r::Config->default_acceleration) - if $acceleration && $Slic3r::Config->default_acceleration; + $gcode .= $self->set_acceleration($self->config->default_acceleration) + if $acceleration && $self->config->default_acceleration; return $gcode; } @@ -282,13 +283,13 @@ sub travel_to { $travel->translate(-$self->shift_x, -$self->shift_y); if ($travel->length < scale $self->extruder->retract_before_travel - || ($Slic3r::Config->only_retract_when_crossing_perimeters && first { $_->encloses_line($travel, scaled_epsilon) } @{$self->layer->slices}) + || ($self->config->only_retract_when_crossing_perimeters && first { $_->encloses_line($travel, scaled_epsilon) } @{$self->layer->slices}) || ($role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands_enclose_line($travel)) ) { $self->straight_once(0); $self->speed('travel'); $gcode .= $self->G0($point, undef, 0, $comment || ""); - } elsif (!$Slic3r::Config->avoid_crossing_perimeters || $self->straight_once) { + } elsif (!$self->config->avoid_crossing_perimeters || $self->straight_once) { $self->straight_once(0); $gcode .= $self->retract(travel_to => $point); $self->speed('travel'); @@ -322,7 +323,7 @@ sub _plan { my @travel = $mp->shortest_path($self->last_pos, $point)->lines; # if the path is not contained in a single island we need to retract - my $need_retract = !$Slic3r::Config->only_retract_when_crossing_perimeters; + my $need_retract = !$self->config->only_retract_when_crossing_perimeters; if (!$need_retract) { $need_retract = 1; foreach my $slice (@{$self->layer->slices}) { @@ -370,7 +371,7 @@ sub retract { ? undef : [undef, $self->z + $self->extruder->retract_lift, 0, 'lift plate during travel']; - if (($Slic3r::Config->g0 || $Slic3r::Config->gcode_flavor eq 'mach3') && $params{travel_to}) { + if (($self->config->g0 || $self->config->gcode_flavor eq 'mach3') && $params{travel_to}) { $self->speed('travel'); if ($lift) { # combine lift and retract @@ -381,7 +382,7 @@ sub retract { my $travel = [$params{travel_to}, undef, $retract->[2], "travel and $comment"]; $gcode .= $self->G0(@$travel); } - } elsif (($Slic3r::Config->g0 || $Slic3r::Config->gcode_flavor eq 'mach3') && defined $params{move_z}) { + } elsif (($self->config->g0 || $self->config->gcode_flavor eq 'mach3') && defined $params{move_z}) { # combine Z change and retraction $self->speed('travel'); my $travel = [undef, $params{move_z}, $retract->[2], "change layer and $comment"]; @@ -417,7 +418,7 @@ sub retract { # reset extrusion distance during retracts # this makes sure we leave sufficient precision in the firmware - $gcode .= $self->reset_e if $Slic3r::Config->gcode_flavor !~ /^(?:mach3|makerbot)$/; + $gcode .= $self->reset_e if $self->config->gcode_flavor !~ /^(?:mach3|makerbot)$/; return $gcode; } @@ -448,8 +449,8 @@ sub reset_e { my $self = shift; $self->extrusion_distance(0); - return sprintf "G92 %s0%s\n", $Slic3r::Config->extrusion_axis, ($Slic3r::Config->gcode_comments ? ' ; reset extrusion distance' : '') - if $Slic3r::Config->extrusion_axis && !$Slic3r::Config->use_relative_e_distances; + return sprintf "G92 %s0%s\n", $self->config->extrusion_axis, ($self->config->gcode_comments ? ' ; reset extrusion distance' : '') + if $self->config->extrusion_axis && !$self->config->use_relative_e_distances; } sub set_acceleration { @@ -458,12 +459,12 @@ sub set_acceleration { return "" if !$acceleration; return sprintf "M204 S%s%s\n", - $acceleration, ($Slic3r::Config->gcode_comments ? ' ; adjust acceleration' : ''); + $acceleration, ($self->config->gcode_comments ? ' ; adjust acceleration' : ''); } sub G0 { my $self = shift; - return $self->G1(@_) if !($Slic3r::Config->g0 || $Slic3r::Config->gcode_flavor eq 'mach3'); + return $self->G1(@_) if !($self->config->g0 || $self->config->gcode_flavor eq 'mach3'); return $self->_G0_G1("G0", @_); } @@ -533,9 +534,9 @@ sub _Gx { ? ($self->extruder->retract_speed_mm_min) : $self->speeds->{$self->speed} // $self->speed; if ($e && $self->layer && $self->layer->id == 0 && $comment !~ /retract/) { - $F = $Slic3r::Config->first_layer_speed =~ /^(\d+(?:\.\d+)?)%$/ + $F = $self->config->first_layer_speed =~ /^(\d+(?:\.\d+)?)%$/ ? ($F * $1/100) - : $Slic3r::Config->first_layer_speed * 60; + : $self->config->first_layer_speed * 60; } $self->last_speed($self->speed); $self->last_f($F); @@ -543,14 +544,14 @@ sub _Gx { $gcode .= sprintf " F%.${dec}f", $F if defined $F; # output extrusion distance - if ($e && $Slic3r::Config->extrusion_axis) { - $self->extrusion_distance(0) if $Slic3r::Config->use_relative_e_distances; + if ($e && $self->config->extrusion_axis) { + $self->extrusion_distance(0) if $self->config->use_relative_e_distances; $self->extrusion_distance($self->extrusion_distance + $e); $self->total_extrusion_length($self->total_extrusion_length + $e); - $gcode .= sprintf " %s%.5f", $Slic3r::Config->extrusion_axis, $self->extrusion_distance; + $gcode .= sprintf " %s%.5f", $self->config->extrusion_axis, $self->extrusion_distance; } - $gcode .= sprintf " ; %s", $comment if $comment && $Slic3r::Config->gcode_comments; + $gcode .= sprintf " ; %s", $comment if $comment && $self->config->gcode_comments; if ($append_bridge_off) { $gcode .= "\n;_BRIDGE_FAN_END"; } @@ -575,8 +576,8 @@ sub set_extruder { $gcode .= $self->retract(toolchange => 1) if defined $self->extruder; # append custom toolchange G-code - if (defined $self->extruder && $Slic3r::Config->toolchange_gcode) { - $gcode .= sprintf "%s\n", $Slic3r::Config->replace_options($Slic3r::Config->toolchange_gcode, { + if (defined $self->extruder && $self->config->toolchange_gcode) { + $gcode .= sprintf "%s\n", $self->config->replace_options($self->config->toolchange_gcode, { previous_extruder => $self->extruder->id, next_extruder => $extruder->id, }); @@ -585,11 +586,11 @@ sub set_extruder { # set the new extruder $self->extruder($extruder); my $toolchange_gcode = sprintf "%s%d%s\n", - ($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ ? 'M108 T' : 'T'), + ($self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ ? 'M108 T' : 'T'), $extruder->id, - ($Slic3r::Config->gcode_comments ? ' ; change extruder' : ''); + ($self->config->gcode_comments ? ' ; change extruder' : ''); - if ($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) { + if ($self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) { $gcode .= $self->reset_e; $gcode .= $toolchange_gcode; } else { @@ -607,18 +608,18 @@ sub set_fan { if ($self->last_fan_speed != $speed || $dont_save) { $self->last_fan_speed($speed) if !$dont_save; if ($speed == 0) { - my $code = $Slic3r::Config->gcode_flavor eq 'teacup' + my $code = $self->config->gcode_flavor eq 'teacup' ? 'M106 S0' - : $Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ + : $self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ ? 'M127' : 'M107'; - return sprintf "$code%s\n", ($Slic3r::Config->gcode_comments ? ' ; disable fan' : ''); + return sprintf "$code%s\n", ($self->config->gcode_comments ? ' ; disable fan' : ''); } else { - if ($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) { - return sprintf "M126%s\n", ($Slic3r::Config->gcode_comments ? ' ; enable fan' : ''); + if ($self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) { + return sprintf "M126%s\n", ($self->config->gcode_comments ? ' ; enable fan' : ''); } else { - return sprintf "M106 %s%d%s\n", ($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'), - (255 * $speed / 100), ($Slic3r::Config->gcode_comments ? ' ; enable fan' : ''); + return sprintf "M106 %s%d%s\n", ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), + (255 * $speed / 100), ($self->config->gcode_comments ? ' ; enable fan' : ''); } } } @@ -629,17 +630,17 @@ sub set_temperature { my $self = shift; my ($temperature, $wait, $tool) = @_; - return "" if $wait && $Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/; + return "" if $wait && $self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/; - my ($code, $comment) = ($wait && $Slic3r::Config->gcode_flavor ne 'teacup') + my ($code, $comment) = ($wait && $self->config->gcode_flavor ne 'teacup') ? ('M109', 'wait for temperature to be reached') : ('M104', 'set temperature'); my $gcode = sprintf "$code %s%d %s; $comment\n", - ($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature, - (defined $tool && ($self->multiple_extruders || $Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/)) ? "T$tool " : ""; + ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature, + (defined $tool && ($self->multiple_extruders || $self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/)) ? "T$tool " : ""; $gcode .= "M116 ; wait for temperature to be reached\n" - if $Slic3r::Config->gcode_flavor eq 'teacup' && $wait; + if $self->config->gcode_flavor eq 'teacup' && $wait; return $gcode; } @@ -648,14 +649,14 @@ sub set_bed_temperature { my $self = shift; my ($temperature, $wait) = @_; - my ($code, $comment) = ($wait && $Slic3r::Config->gcode_flavor ne 'teacup') - ? (($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ ? 'M109' : 'M190'), 'wait for bed temperature to be reached') + my ($code, $comment) = ($wait && $self->config->gcode_flavor ne 'teacup') + ? (($self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ ? 'M109' : 'M190'), 'wait for bed temperature to be reached') : ('M140', 'set bed temperature'); my $gcode = sprintf "$code %s%d ; $comment\n", - ($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature; + ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature; $gcode .= "M116 ; wait for bed temperature to be reached\n" - if $Slic3r::Config->gcode_flavor eq 'teacup' && $wait; + if $self->config->gcode_flavor eq 'teacup' && $wait; return $gcode; } @@ -665,8 +666,8 @@ sub _limit_frequency { my $self = shift; my ($point) = @_; - return '' if $Slic3r::Config->vibration_limit == 0; - my $min_time = 1 / ($Slic3r::Config->vibration_limit * 60); # in minutes + return '' if $self->config->vibration_limit == 0; + my $min_time = 1 / ($self->config->vibration_limit * 60); # in minutes # calculate the move vector and move direction my $vector = Slic3r::Line->new($self->last_pos, $point)->vector; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 9acbe2cf..04fc20ed 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -688,6 +688,7 @@ sub write_gcode { # set up our extruder object my $gcodegen = Slic3r::GCode->new( + config => $self->config, multiple_extruders => (@{$self->extruders} > 1), layer_count => $self->layer_count, ); diff --git a/t/gcode.t b/t/gcode.t index c41307d4..61c940fa 100644 --- a/t/gcode.t +++ b/t/gcode.t @@ -11,8 +11,10 @@ use Slic3r; use Slic3r::Geometry qw(scale); { - local $Slic3r::Config = Slic3r::Config->new_from_defaults; - my $gcodegen = Slic3r::GCode->new(layer_count => 1); + my $gcodegen = Slic3r::GCode->new( + config => Slic3r::Config->new_from_defaults, + layer_count => 1, + ); $gcodegen->set_shift(10, 10); is_deeply $gcodegen->last_pos, [scale -10, scale -10], 'last_pos is shifted correctly'; } From 5c7dd2cf78310b2511aa21a29329f24ca6d91026 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 31 May 2013 12:18:33 +0200 Subject: [PATCH 125/179] Some fixes to the cooling logic and new test suite for cooling --- MANIFEST | 1 + lib/Slic3r/GCode/CoolingBuffer.pm | 13 +++-- lib/Slic3r/Print.pm | 14 ++++- t/cooling.t | 89 +++++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 t/cooling.t diff --git a/MANIFEST b/MANIFEST index 45d52d6e..c319935b 100644 --- a/MANIFEST +++ b/MANIFEST @@ -66,6 +66,7 @@ t/clean_polylines.t t/clipper.t t/collinear.t t/combineinfill.t +t/cooling.t t/custom_gcode.t t/dynamic.t t/fill.t diff --git a/lib/Slic3r/GCode/CoolingBuffer.pm b/lib/Slic3r/GCode/CoolingBuffer.pm index a04554f5..a4f44fe2 100644 --- a/lib/Slic3r/GCode/CoolingBuffer.pm +++ b/lib/Slic3r/GCode/CoolingBuffer.pm @@ -6,7 +6,7 @@ has 'gcodegen' => (is => 'ro', required => 1); has 'gcode' => (is => 'rw', default => sub {""}); has 'elapsed_time' => (is => 'rw', default => sub {0}); has 'layer_id' => (is => 'rw'); -has 'last_z' => (is => 'rw'); +has 'last_z' => (is => 'rw', default => sub { {} }); # obj_id => z (basically a 'last seen' table) has 'min_print_speed' => (is => 'lazy'); sub _build_min_print_speed { @@ -16,15 +16,17 @@ sub _build_min_print_speed { sub append { my $self = shift; - my ($gcode, $layer) = @_; + my ($gcode, $obj_id, $layer_id, $print_z) = @_; + + # TODO: differentiate $obj_id between normal layers and support layers my $return = ""; - if (defined $self->last_z && $self->last_z != $layer->print_z) { + if (exists $self->last_z->{$obj_id} && $self->last_z->{$obj_id} != $print_z) { $return = $self->flush; } - $self->layer_id($layer->id); - $self->last_z($layer->print_z); + $self->layer_id($layer_id); + $self->last_z->{$obj_id} = $print_z; $self->gcode($self->gcode . $gcode); $self->elapsed_time($self->elapsed_time + $self->gcodegen->elapsed_time); $self->gcodegen->elapsed_time(0); @@ -39,6 +41,7 @@ sub flush { my $elapsed = $self->elapsed_time; $self->gcode(""); $self->elapsed_time(0); + $self->last_z({}); # reset the whole table otherwise we would compute overlapping times my $fan_speed = $self->config->fan_always_on ? $self->config->min_fan_speed : 0; my $speed_factor = 1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 04fc20ed..0e4182da 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -794,7 +794,12 @@ sub write_gcode { if $Slic3r::Config->first_layer_bed_temperature; $print_first_layer_temperature->(); } - print $fh $buffer->append($layer_gcode->process_layer($layer, [$copy]), $layer); + print $fh $buffer->append( + $layer_gcode->process_layer($layer, [$copy]), + $layer->object."", + $layer->id, + $layer->print_z, + ); } print $fh $buffer->flush; $finished_objects++; @@ -807,7 +812,12 @@ sub write_gcode { ); my @layers = sort { $a->print_z <=> $b->print_z } map @{$_->layers}, @{$self->objects}; foreach my $layer (@layers) { - print $fh $buffer->append($layer_gcode->process_layer($layer, $layer->object->copies), $layer); + print $fh $buffer->append( + $layer_gcode->process_layer($layer, $layer->object->copies), + $layer->object."", + $layer->id, + $layer->print_z, + ); } print $fh $buffer->flush; } diff --git a/t/cooling.t b/t/cooling.t new file mode 100644 index 00000000..27cd59b7 --- /dev/null +++ b/t/cooling.t @@ -0,0 +1,89 @@ +use Test::More; +use strict; +use warnings; + +plan tests => 8; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; +} + +use Slic3r; + +sub buffer { + my $config = shift || Slic3r::Config->new_from_defaults; + my $buffer = Slic3r::GCode::CoolingBuffer->new( + config => $config, + gcodegen => Slic3r::GCode->new(config => $config, layer_count => 10), + ); + return $buffer; +} + +my $config = Slic3r::Config->new_from_defaults; +$config->set('disable_fan_first_layers', 0); + +{ + my $buffer = buffer($config); + $buffer->gcodegen->elapsed_time($buffer->config->slowdown_below_layer_time + 1); + my $gcode = $buffer->append('G1 X100 E1 F3000', 0, 0, 0.4) . $buffer->flush; + like $gcode, qr/F3000/, 'speed is not altered when elapsed time is greater than slowdown threshold'; +} + +{ + my $buffer = buffer($config); + $buffer->gcodegen->elapsed_time($buffer->config->slowdown_below_layer_time - 1); + my $gcode = $buffer->append("G1 X50 F2500\nG1 X100 E1 F3000\nG1 E4 F400", 0, 0, 0.4) . $buffer->flush; + unlike $gcode, qr/F3000/, 'speed is altered when elapsed time is lower than slowdown threshold'; + like $gcode, qr/F2500/, 'speed is not altered for travel moves'; + like $gcode, qr/F400/, 'speed is not altered for extruder-only moves'; +} + +{ + my $buffer = buffer($config); + $buffer->gcodegen->elapsed_time($buffer->config->fan_below_layer_time + 1); + my $gcode = $buffer->append('G1 X100 E1 F3000', 0, 0, 0.4) . $buffer->flush; + unlike $gcode, qr/M106/, 'fan is not activated when elapsed time is greater than fan threshold'; +} + +{ + my $buffer = buffer($config); + my $gcode = ""; + for my $obj_id (0 .. 1) { + # use an elapsed time which is < the slowdown threshold but greater than it when summed twice + $buffer->gcodegen->elapsed_time($buffer->config->slowdown_below_layer_time - 1); + $gcode .= $buffer->append("G1 X100 E1 F3000\n", $obj_id, 0, 0.4); + } + $gcode .= $buffer->flush; + like $gcode, qr/F3000/, 'slowdown is computed on all objects printing at same Z'; +} + +{ + my $buffer = buffer($config); + my $gcode = ""; + for my $layer_id (0 .. 1) { + for my $obj_id (0 .. 1) { + # use an elapsed time which is < the threshold but greater than it when summed twice + $buffer->gcodegen->elapsed_time($buffer->config->fan_below_layer_time - 1); + $gcode .= $buffer->append("G1 X100 E1 F3000\n", $obj_id, $layer_id, 0.4 + 0.4*$layer_id + 0.1*$obj_id); # print same layer at distinct heights + } + } + $gcode .= $buffer->flush; + unlike $gcode, qr/M106/, 'fan activation is computed on all objects printing at different Z'; +} + +{ + my $buffer = buffer($config); + my $gcode = ""; + for my $layer_id (0 .. 1) { + for my $obj_id (0 .. 1) { + # use an elapsed time which is < the threshold even when summed twice + $buffer->gcodegen->elapsed_time($buffer->config->fan_below_layer_time/2 - 1); + $gcode .= $buffer->append("G1 X100 E1 F3000\n", $obj_id, $layer_id, 0.4 + 0.4*$layer_id + 0.1*$obj_id); # print same layer at distinct heights + } + } + $gcode .= $buffer->flush; + like $gcode, qr/M106/, 'fan activation is computed on all objects printing at different Z'; +} + +__END__ From 5f06cea821d1cdcff77dfc5912a683dcc55b18d2 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 31 May 2013 12:45:18 +0200 Subject: [PATCH 126/179] Prevent --extrusion-multiplier <= 0. #1183 --- lib/Slic3r/Config.pm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 3fc8c283..257b90b4 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -1328,6 +1328,10 @@ sub validate { if $self->extruder_clearance_radius <= 0; die "Invalid value for --extruder-clearance-height\n" if $self->extruder_clearance_height <= 0; + + # --extrusion-multiplier + die "Invalid value for --extrusion-multiplier\n" + if defined first { $_ <= 0 } @{$self->extrusion_multiplier}; } sub replace_options { From 917915d68e5932df327a797e47a7c8a6fcfe9ca4 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 31 May 2013 14:23:42 +0200 Subject: [PATCH 127/179] Bugfix: superfluous extra perimeters were generated. #1170 --- lib/Slic3r/Print/Object.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 816b9865..b166a0e3 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -313,6 +313,7 @@ sub make_perimeters { [ offset([ map @$_, @$diff ], -$perimeter_spacing) ], +$perimeter_spacing ) ], + 1, ); next if !@$diff; # diff contains the collapsed area From c62b49d1af7a6cbecfa3f97f28b388fadab40fe0 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 31 May 2013 14:30:07 +0200 Subject: [PATCH 128/179] Optimization of extra perimeters detection --- lib/Slic3r/Geometry/Clipper.pm | 15 ++++++++++++++- lib/Slic3r/Print/Object.pm | 22 ++++++++-------------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index e15f31a5..276a300b 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -6,7 +6,8 @@ require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(safety_offset safety_offset_ex offset offset_ex collapse_ex diff_ex diff union_ex intersection_ex xor_ex PFT_EVENODD JT_MITER JT_ROUND - JT_SQUARE is_counter_clockwise union_pt offset2 offset2_ex traverse_pt); + JT_SQUARE is_counter_clockwise union_pt offset2 offset2_ex traverse_pt + intersection); use Math::Clipper 1.22 qw(:cliptypes :polyfilltypes :jointypes is_counter_clockwise area); use Slic3r::Geometry qw(scale); @@ -118,6 +119,18 @@ sub intersection_ex { ]; } +sub intersection { + my ($subject, $clip, $jointype, $safety_offset) = @_; + $jointype = PFT_NONZERO unless defined $jointype; + $clipper->clear; + $clipper->add_subject_polygons($subject); + $clipper->add_clip_polygons($safety_offset ? safety_offset($clip) : $clip); + return [ + map Slic3r::Polygon->new($_), + @{ $clipper->execute(CT_INTERSECTION, $jointype, $jointype) }, + ]; +} + sub xor_ex { my ($subject, $clip, $jointype) = @_; $jointype = PFT_NONZERO unless defined $jointype; diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index b166a0e3..11339955 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -4,7 +4,8 @@ use Moo; use List::Util qw(min sum first); use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(Z PI scale unscale deg2rad rad2deg scaled_epsilon chained_path_points); -use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex union_ex offset collapse_ex); +use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex union_ex offset collapse_ex + offset2 diff intersection); use Slic3r::Surface ':types'; has 'print' => (is => 'ro', weak_ref => 1, required => 1); @@ -300,19 +301,16 @@ sub make_perimeters { my $overlap = $perimeter_spacing; # one perimeter - my $diff = diff_ex( + my $diff = diff( [ offset([ map @{$_->expolygon}, @{$layerm->slices} ], -($Slic3r::Config->perimeters * $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 - $diff = diff_ex( - [ map @$_, @$diff ], - [ offset( - [ offset([ map @$_, @$diff ], -$perimeter_spacing) ], - +$perimeter_spacing - ) ], + $diff = diff( + $diff, + [ offset2($diff, -$perimeter_spacing, +$perimeter_spacing) ], 1, ); next if !@$diff; @@ -324,18 +322,14 @@ sub make_perimeters { # compute polygons representing the thickness of the hypotetical new internal perimeter # of our slice $extra_perimeters++; - my $hypothetical_perimeter = diff_ex( + my $hypothetical_perimeter = diff( [ offset($slice->expolygon, -($perimeter_spacing * ($Slic3r::Config->perimeters + $extra_perimeters-1))) ], [ offset($slice->expolygon, -($perimeter_spacing * ($Slic3r::Config->perimeters + $extra_perimeters))) ], ); last CYCLE if !@$hypothetical_perimeter; # no extra perimeter is possible # only add the perimeter if there's an intersection with the collapsed area - my $intersection = intersection_ex( - [ map @$_, @$diff ], - [ map @$_, @$hypothetical_perimeter ], - ); - last CYCLE if !@$intersection; + last CYCLE if !@{ intersection($diff, $hypothetical_perimeter) }; Slic3r::debugf " adding one more perimeter at layer %d\n", $layer_id; $slice->extra_perimeters($extra_perimeters); } From 49531f6f7834d5b00a27fd91cfeccc57286e87cd Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 31 May 2013 19:41:31 +0200 Subject: [PATCH 129/179] Fixed regression causing rotation from plater to also translate the resulting G-code. #1191 --- lib/Slic3r/GUI/Plater.pm | 3 +-- lib/Slic3r/Model.pm | 2 +- lib/Slic3r/Print.pm | 2 +- lib/Slic3r/TriangleMesh.pm | 4 ++-- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index e70414a3..1e229a78 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -718,7 +718,7 @@ sub make_model { } $new_model_object->scale($plater_object->scale); $new_model_object->add_instance( - rotation => $plater_object->rotate, + rotation => $plater_object->rotate, # around center point offset => [ @$_ ], ) for @{$plater_object->instances}; $new_model_object->align_to_origin; @@ -1128,7 +1128,6 @@ sub instances_count { sub make_thumbnail { my $self = shift; - my @points = map [ @$_[X,Y] ], @{$self->model_object->mesh->vertices}; my $mesh = $self->model_object->mesh; my $thumbnail = Slic3r::ExPolygon::Collection->new( expolygons => (@{$mesh->facets} <= 5000) diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index cb740ab7..70824726 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -390,7 +390,7 @@ package Slic3r::Model::Instance; use Moo; has 'object' => (is => 'ro', weak_ref => 1, required => 1); -has 'rotation' => (is => 'rw', default => sub { 0 }); +has 'rotation' => (is => 'rw', default => sub { 0 }); # around mesh center point has 'offset' => (is => 'rw'); 1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 0e4182da..ae299248 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -122,7 +122,7 @@ sub add_model { # we ignore the per-instance rotation currently and only # consider the first one - $mesh->rotate($object->instances->[0]->rotation); + $mesh->rotate($object->instances->[0]->rotation, $mesh->center); $mesh->scale(1 / &Slic3r::SCALING_FACTOR); } diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm index 0968f735..f6146d54 100644 --- a/lib/Slic3r/TriangleMesh.pm +++ b/lib/Slic3r/TriangleMesh.pm @@ -324,14 +324,14 @@ sub make_loops { sub rotate { my $self = shift; - my ($deg) = @_; + my ($deg, $center) = @_; return if $deg == 0; my $rad = Slic3r::Geometry::deg2rad($deg); # transform vertex coordinates foreach my $vertex (@{$self->vertices}) { - @$vertex = (@{ +(Slic3r::Geometry::rotate_points($rad, undef, [ $vertex->[X], $vertex->[Y] ]))[0] }, $vertex->[Z]); + @$vertex = (@{ +(Slic3r::Geometry::rotate_points($rad, $center, [ $vertex->[X], $vertex->[Y] ]))[0] }, $vertex->[Z]); } } From d801876ee93de5dd1bea2c311a7d7cf3497eda74 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 1 Jun 2013 18:54:07 +0200 Subject: [PATCH 130/179] Revert "Bugfix: configuration wizard crash 2 #1077" This reverts commit 55c413627f8545fb5d2b75efb157d30689985d53. --- lib/Slic3r/GUI/SkeinPanel.pm | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index 7e61cd4a..6f30da47 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -252,9 +252,7 @@ sub load_config { my ($config) = @_; foreach my $tab (values %{$self->{options_tabs}}) { - if ($self->{mode} eq 'expert') { - $tab->set_value($_, $config->$_) for keys %$config; - } + $tab->set_value($_, $config->$_) for keys %$config; } } From 30ce7dc745039301913456c7a2619828b2f65e10 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 1 Jun 2013 18:56:23 +0200 Subject: [PATCH 131/179] Bugfix: wizard crashing in Simple Mode. #1077 #1122 --- lib/Slic3r/GUI/SimpleTab.pm | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/Slic3r/GUI/SimpleTab.pm b/lib/Slic3r/GUI/SimpleTab.pm index 0463a3d4..c1fee543 100644 --- a/lib/Slic3r/GUI/SimpleTab.pm +++ b/lib/Slic3r/GUI/SimpleTab.pm @@ -78,6 +78,17 @@ sub load_config { } } +sub set_value { + my $self = shift; + my ($opt_key, $value) = @_; + + my $changed = 0; + foreach my $optgroup (@{$self->{optgroups}}) { + $changed = 1 if $optgroup->set_value($opt_key, $value); + } + return $changed; +} + sub is_dirty { 0 } sub config { $_[0]->{config}->clone } From da36df65a44d340983fa01fc25ad370c2f85cd56 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 2 Jun 2013 11:15:57 +0200 Subject: [PATCH 132/179] Warning removed when exporting config --- lib/Slic3r/Config.pm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 257b90b4..41b5fc4f 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -9,6 +9,7 @@ use List::Util qw(first); our @Ignore = qw(duplicate_x duplicate_y multiply_x multiply_y support_material_tool acceleration); my $serialize_comma = sub { join ',', @{$_[0]} }; +my $serialize_comma_bool = sub { join ',', map $_ // 0, @{$_[0]} }; my $deserialize_comma = sub { [ split /,/, $_[0] ] }; our $Options = { @@ -797,7 +798,7 @@ END tooltip => 'This flag enforces a retraction whenever a Z move is done.', cli => 'retract-layer-change!', type => 'bool', - serialize => $serialize_comma, + serialize => $serialize_comma_bool, deserialize => $deserialize_comma, default => [1], }, @@ -806,7 +807,7 @@ END tooltip => 'This flag will move the nozzle while retracting to minimize the possible blob on leaky extruders.', cli => 'wipe!', type => 'bool', - serialize => $serialize_comma, + serialize => $serialize_comma_bool, deserialize => $deserialize_comma, default => [0], }, From 655d528d929af4daacd63bb7796c623e3347bfba Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 2 Jun 2013 16:56:08 +0200 Subject: [PATCH 133/179] Fixed recent regression causing a spike when avoid_crossing_perimeters was used on split objects --- lib/Slic3r/GUI/Plater.pm | 4 ++-- lib/Slic3r/Model.pm | 35 +++++++++++++++++++++++++++-------- lib/Slic3r/Print.pm | 19 ++++++++++++++----- lib/Slic3r/TriangleMesh.pm | 9 +++++++-- 4 files changed, 50 insertions(+), 17 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 1e229a78..0213f6c4 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -719,11 +719,11 @@ sub make_model { $new_model_object->scale($plater_object->scale); $new_model_object->add_instance( rotation => $plater_object->rotate, # around center point - offset => [ @$_ ], + offset => Slic3r::Point->new($_), ) for @{$plater_object->instances}; - $new_model_object->align_to_origin; } + $model->align_to_origin; return $model; } diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index 70824726..b7ff6f7a 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -164,14 +164,19 @@ sub vertices { return [ map @{$_->vertices}, @{$self->objects} ]; } +sub used_vertices { + my $self = shift; + return [ map @{$_->used_vertices}, @{$self->objects} ]; +} + sub size { my $self = shift; - return [ Slic3r::Geometry::size_3D($self->vertices) ]; + return [ Slic3r::Geometry::size_3D($self->used_vertices) ]; } sub extents { my $self = shift; - return Slic3r::Geometry::bounding_box_3D($self->vertices); + return Slic3r::Geometry::bounding_box_3D($self->used_vertices); } sub align_to_origin { @@ -179,8 +184,17 @@ sub align_to_origin { # calculate the displacements needed to # have lowest value for each axis at coordinate 0 - my @extents = $self->extents; - $self->move(map -$extents[$_][MIN], X,Y,Z); + { + my @extents = $self->extents; + $self->move(map -$extents[$_][MIN], X,Y,Z); + } + + # align all instances to 0,0 as well + { + my @instances = map @{$_->instances}, @{$self->objects}; + my @extents = Slic3r::Geometry::bounding_box_3D([ map $_->offset, @instances ]); + $_->offset->translate(-$extents[X][MIN], -$extents[Y][MIN]) for @instances; + } } sub move { @@ -260,7 +274,7 @@ package Slic3r::Model::Object; use Moo; use List::Util qw(first); -use Slic3r::Geometry qw(X Y Z MIN move_points_3D); +use Slic3r::Geometry qw(X Y Z MIN move_points move_points_3D); use Storable qw(dclone); has 'input_file' => (is => 'rw'); @@ -309,14 +323,19 @@ sub mesh { ); } +sub used_vertices { + my $self = shift; + return [ map $self->vertices->[$_], map @$_, map @{$_->facets}, @{$self->volumes} ]; +} + sub size { my $self = shift; - return [ Slic3r::Geometry::size_3D($self->vertices) ]; + return [ Slic3r::Geometry::size_3D($self->used_vertices) ]; } sub extents { my $self = shift; - return Slic3r::Geometry::bounding_box_3D($self->vertices); + return Slic3r::Geometry::bounding_box_3D($self->used_vertices); } sub align_to_origin { @@ -391,6 +410,6 @@ use Moo; has 'object' => (is => 'ro', weak_ref => 1, required => 1); has 'rotation' => (is => 'rw', default => sub { 0 }); # around mesh center point -has 'offset' => (is => 'rw'); +has 'offset' => (is => 'rw'); # must be Slic3r::Point object 1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index ae299248..84a208ea 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -6,7 +6,7 @@ use File::Spec; use List::Util qw(max first); use Math::ConvexHull::MonotoneChain qw(convex_hull); use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN PI scale unscale move_points nearest_point); +use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX PI scale unscale move_points nearest_point); use Slic3r::Geometry::Clipper qw(diff_ex union_ex union_pt intersection_ex offset offset2 traverse_pt JT_ROUND JT_SQUARE PFT_EVENODD); use Time::HiRes qw(gettimeofday tv_interval); @@ -116,8 +116,7 @@ sub add_model { : $mesh; } - foreach my $mesh (@meshes) { - next unless $mesh; + foreach my $mesh (grep $_, @meshes) { $mesh->check_manifoldness; # we ignore the per-instance rotation currently and only @@ -127,12 +126,22 @@ sub add_model { $mesh->scale(1 / &Slic3r::SCALING_FACTOR); } + # align the object to origin; not sure this is required by the toolpath generation + # algorithms, but it's good practice to avoid negative coordinates; it probably + # provides also some better performance in infill generation + my @extents = Slic3r::Geometry::bounding_box_3D([ map @{$_->used_vertices}, grep $_, @meshes ]); + foreach my $mesh (grep $_, @meshes) { + $mesh->move(map -$extents[$_][MIN], X,Y,Z); + } + # initialize print object push @{$self->objects}, Slic3r::Print::Object->new( print => $self, meshes => [ @meshes ], - copies => [ map [ scale $_->offset->[X], scale $_->offset->[Y] ], @{$object->instances} ], - size => [ map scale $_, @{ $object->size } ], + copies => [ + map [ (scale $_->offset->[X]) + $extents[X][MIN], (scale $_->offset->[Y]) + $extents[Y][MIN] ], @{$object->instances}, + ], + size => [ map $extents[$_][MAX] - $extents[$_][MIN], (X,Y,Z) ], input_file => $object->input_file, layer_height_ranges => $object->layer_height_ranges, ); diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm index f6146d54..3506d893 100644 --- a/lib/Slic3r/TriangleMesh.pm +++ b/lib/Slic3r/TriangleMesh.pm @@ -398,14 +398,19 @@ sub duplicate { $self->BUILD; } +sub used_vertices { + my $self = shift; + return [ map $self->vertices->[$_], map @$_, @{$self->facets} ]; +} + sub extents { my $self = shift; - return Slic3r::Geometry::bounding_box_3D($self->vertices); + return Slic3r::Geometry::bounding_box_3D($self->used_vertices); } sub size { my $self = shift; - return Slic3r::Geometry::size_3D($self->vertices); + return Slic3r::Geometry::size_3D($self->used_vertices); } sub slice_facet { From 28a01e7c5e170aa50dcb01d420495d8ff9ddd5a7 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 2 Jun 2013 16:58:23 +0200 Subject: [PATCH 134/179] Fix past participle --- lib/Slic3r/GUI/Plater.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 0213f6c4..0dbca23b 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -476,7 +476,7 @@ sub split_object { my $model_object = $current_object->get_model_object; if (@{$model_object->volumes} > 1) { - Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be splitted because it contains more than one volume/material."); + Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be split because it contains more than one volume/material."); return; } @@ -485,7 +485,7 @@ sub split_object { my @new_meshes = $mesh->split_mesh; if (@new_meshes == 1) { - Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be splitted because it already contains a single part."); + Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be split because it already contains a single part."); return; } From 086ec4af9c4925c7bf5cac9f91b84fd46938ce11 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 2 Jun 2013 19:32:53 +0200 Subject: [PATCH 135/179] Fixed yet one more regression in Quick Slice caused by recent Model refactoring. #1208 --- lib/Slic3r/Print.pm | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 84a208ea..64289141 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -121,7 +121,8 @@ sub add_model { # we ignore the per-instance rotation currently and only # consider the first one - $mesh->rotate($object->instances->[0]->rotation, $mesh->center); + $mesh->rotate($object->instances->[0]->rotation, $mesh->center) + if @{ $object->instances // [] }; $mesh->scale(1 / &Slic3r::SCALING_FACTOR); } @@ -139,7 +140,9 @@ sub add_model { print => $self, meshes => [ @meshes ], copies => [ - map [ (scale $_->offset->[X]) + $extents[X][MIN], (scale $_->offset->[Y]) + $extents[Y][MIN] ], @{$object->instances}, + $object->instances + ? (map [ (scale $_->offset->[X]) + $extents[X][MIN], (scale $_->offset->[Y]) + $extents[Y][MIN] ], @{$object->instances}) + : [0,0], ], size => [ map $extents[$_][MAX] - $extents[$_][MIN], (X,Y,Z) ], input_file => $object->input_file, From 8a01cb9e96f2c3552c466956b739d2cd5b46176a Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 2 Jun 2013 19:44:59 +0200 Subject: [PATCH 136/179] Generate a better error when input file is too thin and no layers could be generated. #1127 --- lib/Slic3r/Print/Object.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 11339955..6e806738 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -184,13 +184,13 @@ sub slice { $self->meshes->[$region_id] = undef; # free memory } - die "Invalid input file\n" if !@{$self->layers}; # free memory $self->meshes(undef); # remove last layer(s) if empty - pop @{$self->layers} while !map @{$_->lines}, @{$self->layers->[-1]->regions}; + pop @{$self->layers} while @{$self->layers} && (!map @{$_->lines}, @{$self->layers->[-1]->regions}); + die "Invalid or too thin input file: no layers could be generated\n" if !@{$self->layers}; foreach my $layer (@{ $self->layers }) { # make sure all layers contain layer region objects for all regions From 6bb2e593a7dadd2871ca100dd31c8eb072bab030 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 2 Jun 2013 19:49:21 +0200 Subject: [PATCH 137/179] Avoid potential fatal error when processing models with empty layers. #1127 --- lib/Slic3r/GCode/Layer.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GCode/Layer.pm b/lib/Slic3r/GCode/Layer.pm index d1716c40..5d398f46 100644 --- a/lib/Slic3r/GCode/Layer.pm +++ b/lib/Slic3r/GCode/Layer.pm @@ -119,7 +119,8 @@ sub process_layer { my @islands = (); if ($Slic3r::Config->avoid_crossing_perimeters) { - push @islands, map +{ perimeters => [], fills => [] }, @{$layer->slices}; + push @islands, { perimeters => [], fills => [] } + for 1 .. (@{$layer->slices} || 1); # make sure we have at least one island hash to avoid failure of the -1 subscript below PERIMETER: foreach my $perimeter (@{$layerm->perimeters}) { my $p = $perimeter->unpack; for my $i (0 .. $#{$layer->slices}-1) { From 895e0bbfcd22bb364bcadc062f86eb9d1ed18a49 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 2 Jun 2013 19:58:29 +0200 Subject: [PATCH 138/179] Include M82/M83 for Teacup too. #1206 --- lib/Slic3r/Print.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 64289141..c3fbb7b3 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -725,7 +725,7 @@ sub write_gcode { print $fh "G90 ; use absolute coordinates\n"; if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|teacup)$/) { printf $fh $gcodegen->reset_e; - if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|makerbot|sailfish)$/) { + if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|teacup|makerbot|sailfish)$/) { if ($Slic3r::Config->use_relative_e_distances) { print $fh "M83 ; use relative distances for extrusion\n"; } else { From 8c74d2f41eb12bf098245dfe2769b1ac73bada70 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 2 Jun 2013 20:03:22 +0200 Subject: [PATCH 139/179] New --autosave option for better toolchain integration. #837 --- README.markdown | 1 + lib/Slic3r/GUI.pm | 1 + lib/Slic3r/GUI/SkeinPanel.pm | 9 ++++++--- slic3r.pl | 3 +++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/README.markdown b/README.markdown index 01c43d22..0a4e2b95 100644 --- a/README.markdown +++ b/README.markdown @@ -93,6 +93,7 @@ The author of the Silk icon set is Mark James. GUI options: --no-plater Disable the plater tab --gui-mode Overrides the configured mode (simple/expert) + --autosave Automatically export current configuration to the specified file Output options: --output-filename-format diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index b08ae251..fd46cc5e 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -45,6 +45,7 @@ use constant MI_DOCUMENTATION => &Wx::NewId; our $datadir; our $no_plater; our $mode; +our $autosave; our $Settings = { _ => { diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index 6f30da47..6c4842ea 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -49,9 +49,12 @@ sub new { $self->{tabpanel}, on_value_change => sub { $self->{plater}->on_config_change(@_) if $self->{plater}; # propagate config change events to the plater - if ($self->{mode} eq 'simple' && $init) { # don't save while loading for the first time - # save config - $self->config->save("$Slic3r::GUI::datadir/simple.ini"); + if ($init) { # don't save while loading for the first time + if ($self->{mode} eq 'simple') { + # save config + $self->config->save("$Slic3r::GUI::datadir/simple.ini"); + } + $self->config->save($Slic3r::GUI::autosave) if $Slic3r::GUI::autosave; } }, on_presets_changed => sub { diff --git a/slic3r.pl b/slic3r.pl index 1d09441d..e792548e 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -26,6 +26,7 @@ my %cli_options = (); 'save=s' => \$opt{save}, 'load=s@' => \$opt{load}, + 'autosave=s' => \$opt{autosave}, 'ignore-nonexistent-config' => \$opt{ignore_nonexistent_config}, 'no-plater' => \$opt{no_plater}, 'gui-mode=s' => \$opt{gui_mode}, @@ -78,6 +79,7 @@ if (!@ARGV && !$opt{save} && eval "require Slic3r::GUI; 1") { $Slic3r::GUI::datadir = $opt{datadir}; $Slic3r::GUI::no_plater = $opt{no_plater}; $Slic3r::GUI::mode = $opt{gui_mode}; + $Slic3r::GUI::autosave = $opt{autosave}; } $gui = Slic3r::GUI->new; $gui->{skeinpanel}->load_config_file($_) for @{$opt{load}}; @@ -152,6 +154,7 @@ $j GUI options: --no-plater Disable the plater tab --gui-mode Overrides the configured mode (simple/expert) + --autosave Automatically export current configuration to the specified file Output options: --output-filename-format From 7134df4638e6fee0ba1ae1f480c9d1c0574a0502 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Jun 2013 11:39:23 +0200 Subject: [PATCH 140/179] Only skip objects with no layers without stopping the whole job --- lib/Slic3r/Print.pm | 7 +++++++ lib/Slic3r/Print/Object.pm | 4 ---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index c3fbb7b3..0d583061 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -322,6 +322,13 @@ sub export_gcode { $status_cb->(10, "Processing triangulated mesh"); $_->slice for @{$self->objects}; + # remove empty layers and abort if there are no more + # as some algorithms assume all objects have at least one layer + # note: this will change object indexes + @{$self->objects} = grep @{$_->layers}, @{$self->objects}; + die "No layers were detected. You might want to repair your STL file(s) or check their size and retry.\n" + if !@{$self->objects}; + if ($Slic3r::Config->resolution) { $status_cb->(15, "Simplifying input"); $self->_simplify_slices(scale $Slic3r::Config->resolution); diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 6e806738..dc5428dc 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -190,7 +190,6 @@ sub slice { # remove last layer(s) if empty pop @{$self->layers} while @{$self->layers} && (!map @{$_->lines}, @{$self->layers->[-1]->regions}); - die "Invalid or too thin input file: no layers could be generated\n" if !@{$self->layers}; foreach my $layer (@{ $self->layers }) { # make sure all layers contain layer region objects for all regions @@ -277,9 +276,6 @@ sub slice { $self->layers->[$i]->id($i); } } - - warn "No layers were detected. You might want to repair your STL file and retry.\n" - if !@{$self->layers}; } sub make_perimeters { From 81bae56e928a075642d598059d9ab326804ef9b8 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Jun 2013 12:25:32 +0200 Subject: [PATCH 141/179] Limit only_retract_when_crossing_perimeters to travel moves that are completely enclosed in the upper layer's slices so that we avoid visible traces on top layers. #1091 --- lib/Slic3r/GCode.pm | 4 ++-- lib/Slic3r/Layer.pm | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 4519d933..da685521 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -283,7 +283,7 @@ sub travel_to { $travel->translate(-$self->shift_x, -$self->shift_y); if ($travel->length < scale $self->extruder->retract_before_travel - || ($self->config->only_retract_when_crossing_perimeters && first { $_->encloses_line($travel, scaled_epsilon) } @{$self->layer->slices}) + || ($self->config->only_retract_when_crossing_perimeters && first { $_->encloses_line($travel, scaled_epsilon) } @{$self->layer->upper_layer_slices}) || ($role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands_enclose_line($travel)) ) { $self->straight_once(0); @@ -326,7 +326,7 @@ sub _plan { my $need_retract = !$self->config->only_retract_when_crossing_perimeters; if (!$need_retract) { $need_retract = 1; - foreach my $slice (@{$self->layer->slices}) { + foreach my $slice (@{$self->layer->upper_layer_slices}) { # discard the island if at any line is not enclosed in it next if first { !$slice->encloses_line($_, scaled_epsilon) } @travel; # okay, this island encloses the full travel path diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm index 074baef5..ac6d8d29 100644 --- a/lib/Slic3r/Layer.pm +++ b/lib/Slic3r/Layer.pm @@ -54,6 +54,12 @@ sub support_material_contact_z { return $self->print_z - ($self->height - $self->support_material_contact_height) / &Slic3r::SCALING_FACTOR; } +sub upper_layer_slices { + my $self = shift; + + my $upper_layer = $self->object->layers->[ $self->id + 1 ] or return []; + return $upper_layer->slices; + sub region { my $self = shift; my ($region_id) = @_; From 48d5d6de586c7c96ccc2046c84efadbffe552401 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Jun 2013 12:29:45 +0200 Subject: [PATCH 142/179] Typo --- lib/Slic3r/Layer.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm index ac6d8d29..9803ce2b 100644 --- a/lib/Slic3r/Layer.pm +++ b/lib/Slic3r/Layer.pm @@ -59,6 +59,7 @@ sub upper_layer_slices { my $upper_layer = $self->object->layers->[ $self->id + 1 ] or return []; return $upper_layer->slices; +} sub region { my $self = shift; From 71608e799ebebf0f9c1efd9174ac9466dda245dc Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Jun 2013 15:27:58 +0200 Subject: [PATCH 143/179] Keep per-extruder E absolute value for Makerbot flavour to avoid any G92 E0. #950 --- lib/Slic3r/Extruder.pm | 1 + lib/Slic3r/GCode.pm | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/Slic3r/Extruder.pm b/lib/Slic3r/Extruder.pm index 526ac504..925c44fc 100644 --- a/lib/Slic3r/Extruder.pm +++ b/lib/Slic3r/Extruder.pm @@ -14,6 +14,7 @@ has 'id' => (is => 'rw', required => 1); has $_ => (is => 'ro', required => 1) for @{&OPTIONS}; has 'bridge_flow' => (is => 'lazy'); +has 'e' => (is => 'rw', default => sub {0} ); has 'retracted' => (is => 'rw', default => sub {0} ); has 'restart_extra' => (is => 'rw', default => sub {0} ); has 'e_per_mm3' => (is => 'lazy'); diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index da685521..409609d4 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -22,7 +22,6 @@ has 'layer_mp' => (is => 'rw'); has 'new_object' => (is => 'rw', default => sub {0}); has 'straight_once' => (is => 'rw', default => sub {1}); has 'extruder' => (is => 'rw'); -has 'extrusion_distance' => (is => 'rw', default => sub {0} ); has 'elapsed_time' => (is => 'rw', default => sub {0} ); # seconds has 'total_extrusion_length' => (is => 'rw', default => sub {0} ); has 'lifted' => (is => 'rw', default => sub {0} ); @@ -418,7 +417,7 @@ sub retract { # reset extrusion distance during retracts # this makes sure we leave sufficient precision in the firmware - $gcode .= $self->reset_e if $self->config->gcode_flavor !~ /^(?:mach3|makerbot)$/; + $gcode .= $self->reset_e; return $gcode; } @@ -447,8 +446,9 @@ sub unretract { sub reset_e { my $self = shift; + return "" if $self->config->gcode_flavor =~ /^(?:mach3|makerbot)$/; - $self->extrusion_distance(0); + $self->extruder->e(0) if $self->extruder; return sprintf "G92 %s0%s\n", $self->config->extrusion_axis, ($self->config->gcode_comments ? ' ; reset extrusion distance' : '') if $self->config->extrusion_axis && !$self->config->use_relative_e_distances; } @@ -545,10 +545,10 @@ sub _Gx { # output extrusion distance if ($e && $self->config->extrusion_axis) { - $self->extrusion_distance(0) if $self->config->use_relative_e_distances; - $self->extrusion_distance($self->extrusion_distance + $e); + $self->extruder->e(0) if $self->config->use_relative_e_distances; + $self->extruder->e($self->extruder->e + $e); $self->total_extrusion_length($self->total_extrusion_length + $e); - $gcode .= sprintf " %s%.5f", $self->config->extrusion_axis, $self->extrusion_distance; + $gcode .= sprintf " %s%.5f", $self->config->extrusion_axis, $self->extruder->e; } $gcode .= sprintf " ; %s", $comment if $comment && $self->config->gcode_comments; From 974379c95558773e6f5ce3f79a5f4c77a28f962a Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Jun 2013 15:37:32 +0200 Subject: [PATCH 144/179] Add M103 after retraction and M101 before restart when Makerbot G-code flavor is selected. #1119 --- lib/Slic3r/GCode.pm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 409609d4..706ec8d4 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -419,6 +419,8 @@ sub retract { # this makes sure we leave sufficient precision in the firmware $gcode .= $self->reset_e; + $gcode .= "M103 ; extruder off\n" if $self->config->gcode_flavor eq 'makerbot'; + return $gcode; } @@ -426,6 +428,7 @@ sub unretract { my $self = shift; my $gcode = ""; + $gcode .= "M101 ; extruder on\n" if $self->config->gcode_flavor eq 'makerbot'; if ($self->lifted) { $self->speed('travel'); From 167ec7a4e7a99efb10cef9d6834152d9f051a38c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Jun 2013 16:21:22 +0200 Subject: [PATCH 145/179] Use M135 for makerbot toolchange; also disable any G90 and G21 command. #1034 --- lib/Slic3r/GCode.pm | 16 +++++++--------- lib/Slic3r/Print.pm | 4 ++-- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 706ec8d4..7f5ed876 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -588,18 +588,16 @@ sub set_extruder { # set the new extruder $self->extruder($extruder); - my $toolchange_gcode = sprintf "%s%d%s\n", - ($self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ ? 'M108 T' : 'T'), + $gcode .= sprintf "%s%d%s\n", + ($self->config->gcode_flavor eq 'makerbot' + ? 'M135 T' + : $self->config->gcode_flavor eq 'sailfish' + ? 'M108 T' + : 'T'), $extruder->id, ($self->config->gcode_comments ? ' ; change extruder' : ''); - if ($self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) { - $gcode .= $self->reset_e; - $gcode .= $toolchange_gcode; - } else { - $gcode .= $toolchange_gcode; - $gcode .= $self->reset_e; - } + $gcode .= $self->reset_e; return $gcode; } diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 0d583061..47c2bbad 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -711,7 +711,7 @@ sub write_gcode { multiple_extruders => (@{$self->extruders} > 1), layer_count => $self->layer_count, ); - print $fh "G21 ; set units to millimeters\n"; + print $fh "G21 ; set units to millimeters\n" if $Slic3r::Config->gcode_flavor ne 'makerbot'; print $fh $gcodegen->set_fan(0, 1) if $Slic3r::Config->cooling && $Slic3r::Config->disable_fan_first_layers; # write start commands to file @@ -729,7 +729,7 @@ sub write_gcode { printf $fh $gcodegen->set_temperature($self->extruders->[$t]->first_layer_temperature, 1, $t) if $self->extruders->[$t]->first_layer_temperature && $Slic3r::Config->start_gcode !~ /M(?:109|104)/i; } - print $fh "G90 ; use absolute coordinates\n"; + print $fh "G90 ; use absolute coordinates\n" if $Slic3r::Config->gcode_flavor ne 'makerbot'; if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|teacup)$/) { printf $fh $gcodegen->reset_e; if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|teacup|makerbot|sailfish)$/) { From c95245f143d22409ed617e3f2480b9a6d0a5a422 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Jun 2013 18:01:14 +0200 Subject: [PATCH 146/179] Some G-code flavor logic simplification --- lib/Slic3r/Print.pm | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 47c2bbad..87d83b29 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -732,12 +732,10 @@ sub write_gcode { print $fh "G90 ; use absolute coordinates\n" if $Slic3r::Config->gcode_flavor ne 'makerbot'; if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|teacup)$/) { printf $fh $gcodegen->reset_e; - if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|teacup|makerbot|sailfish)$/) { - if ($Slic3r::Config->use_relative_e_distances) { - print $fh "M83 ; use relative distances for extrusion\n"; - } else { - print $fh "M82 ; use absolute distances for extrusion\n"; - } + if ($Slic3r::Config->use_relative_e_distances) { + print $fh "M83 ; use relative distances for extrusion\n"; + } else { + print $fh "M82 ; use absolute distances for extrusion\n"; } } From 09b81ad02792802bc6cc2a3396ae8cc817940bb7 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Jun 2013 21:27:09 +0200 Subject: [PATCH 147/179] Fix typo causing regression in island ordering. #1211 --- lib/Slic3r/Layer/Region.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 1e32aff9..472c2da5 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -247,7 +247,7 @@ sub make_perimeters { )}; my @loops = (); - foreach my $polynode (@$polynodes) { + foreach my $polynode (@nodes) { push @loops, $traverse->($polynode->{children}, $depth+1, $is_contour); my $role = EXTR_ROLE_PERIMETER; From 3ffe98f0c39978057911d908b58c978b993551b8 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Jun 2013 21:40:13 +0200 Subject: [PATCH 148/179] Use actual Z to order objects in sequential printing rather than layer count, as layer heights might be different --- lib/Slic3r/Print.pm | 2 +- lib/Slic3r/Print/Object.pm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 87d83b29..eb81df84 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -783,7 +783,7 @@ sub write_gcode { # print objects from the smallest to the tallest to avoid collisions # when moving onto next object starting point - my @obj_idx = sort { $self->objects->[$a]->layer_count <=> $self->objects->[$b]->layer_count } 0..$#{$self->objects}; + my @obj_idx = sort { $self->objects->[$a]->size->[Z] <=> $self->objects->[$b]->size->[Z] } 0..$#{$self->objects}; my $finished_objects = 0; for my $obj_idx (@obj_idx) { diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index dc5428dc..bfc46ce6 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -11,7 +11,7 @@ use Slic3r::Surface ':types'; has 'print' => (is => 'ro', weak_ref => 1, required => 1); has 'input_file' => (is => 'rw', required => 0); has 'meshes' => (is => 'rw', default => sub { [] }); # by region_id -has 'size' => (is => 'rw', required => 1); +has 'size' => (is => 'rw', required => 1); # XYZ in scaled coordinates has 'copies' => (is => 'rw', trigger => 1); # in scaled coordinates has 'layers' => (is => 'rw', default => sub { [] }); has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ] From 230367b38353766f241863f5e2807f0a61bb8d58 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Jun 2013 21:54:55 +0200 Subject: [PATCH 149/179] Order objects using a nearest neighbor search instead of relying on the order in plater. #1184 --- lib/Slic3r/Print.pm | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index eb81df84..c2b59a20 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -6,7 +6,8 @@ use File::Spec; use List::Util qw(max first); use Math::ConvexHull::MonotoneChain qw(convex_hull); use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX PI scale unscale move_points nearest_point); +use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX PI scale unscale move_points + nearest_point chained_path); use Slic3r::Geometry::Clipper qw(diff_ex union_ex union_pt intersection_ex offset offset2 traverse_pt JT_ROUND JT_SQUARE PFT_EVENODD); use Time::HiRes qw(gettimeofday tv_interval); @@ -823,18 +824,32 @@ sub write_gcode { } } } else { + # order objects using a nearest neighbor search + my @obj_idx = chained_path([ map $_->copies->[0], @{$self->objects} ]); + + # sort layers by Z + my %layers = (); # print_z => [ layer, layer, layer ] by obj_idx + foreach my $obj_idx (0 .. $#{$self->objects}) { + foreach my $layer (@{$self->objects->[$obj_idx]->layers}) { + $layers{ $layer->print_z } ||= []; + $layers{ $layer->print_z }[$obj_idx] = $layer; # turn this into [$layer] when merging support layers + } + } + my $buffer = Slic3r::GCode::CoolingBuffer->new( config => $Slic3r::Config, gcodegen => $gcodegen, ); - my @layers = sort { $a->print_z <=> $b->print_z } map @{$_->layers}, @{$self->objects}; - foreach my $layer (@layers) { - print $fh $buffer->append( - $layer_gcode->process_layer($layer, $layer->object->copies), - $layer->object."", - $layer->id, - $layer->print_z, - ); + foreach my $print_z (sort { $a <=> $b } keys %layers) { + foreach my $obj_idx (@obj_idx) { + next unless my $layer = $layers{$print_z}[$obj_idx]; + print $fh $buffer->append( + $layer_gcode->process_layer($layer, $layer->object->copies), + $layer->object."", + $layer->id, + $layer->print_z, + ); + } } print $fh $buffer->flush; } From 8b3aa32a7e49994d3e27415fdf4a33975faac857 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Jun 2013 22:40:29 +0200 Subject: [PATCH 150/179] Update tooltip for only_retract_when_crossing_perimeters --- lib/Slic3r/Config.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 41b5fc4f..22edd0ff 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -596,7 +596,7 @@ our $Options = { }, 'only_retract_when_crossing_perimeters' => { label => 'Only retract when crossing perimeters', - tooltip => 'Disables retraction when travelling between infill paths inside the same island.', + tooltip => 'Disables retraction when the travel path does not exceed the upper layer\'s perimeters (and thus any ooze will be probably invisible).', cli => 'only-retract-when-crossing-perimeters!', type => 'bool', default => 1, From b3f1795cb4ad82fbf2168dd8faf6c00bd09d414e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Jun 2013 22:49:47 +0200 Subject: [PATCH 151/179] Reduce wipe feedrate a bit --- lib/Slic3r/Extruder.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/Extruder.pm b/lib/Slic3r/Extruder.pm index 925c44fc..91c6314c 100644 --- a/lib/Slic3r/Extruder.pm +++ b/lib/Slic3r/Extruder.pm @@ -39,7 +39,9 @@ sub _build_retract_speed_mm_min { sub _build_scaled_wipe_distance { my $self = shift; - return scale $self->retract_length / $self->retract_speed * $Slic3r::Config->travel_speed; + # reduce feedrate a bit; travel speed is often too high to move on existing material + # too fast = ripping of existing material; too slow = short wipe path, thus more blob + return scale($self->retract_length / $self->retract_speed * $Slic3r::Config->travel_speed * 0.8); } sub make_flow { From 532ae53d0b87d05d4114848ee1a7d3b419e1df9e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 5 Jun 2013 16:58:30 +0200 Subject: [PATCH 152/179] Use G1 instead of G0 for restart after retraction to avoid blending with the previous travel move. #1212 --- lib/Slic3r/GCode.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 7f5ed876..b4a8d6b2 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -439,7 +439,8 @@ sub unretract { my $to_unretract = $self->extruder->retracted + $self->extruder->restart_extra; if ($to_unretract) { $self->speed('retract'); - $gcode .= $self->G0(undef, undef, $to_unretract, "compensate retraction"); + # use G1 instead of G0 because G0 will blend the restart with the previous travel move + $gcode .= $self->G1(undef, undef, $to_unretract, "compensate retraction"); $self->extruder->retracted(0); $self->extruder->restart_extra(0); } From 8c40cefe1ed8dbaecafc02282ba40326d9171dbc Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 5 Jun 2013 17:12:34 +0200 Subject: [PATCH 153/179] Also use G1 when traveling with avoid_crossing_perimeters enabled --- lib/Slic3r/GCode.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index b4a8d6b2..b7f855e8 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -339,7 +339,8 @@ sub _plan { # append the actual path and return $self->speed('travel'); - $gcode .= join '', map $self->G0($_->[B], undef, 0, $comment || ""), @travel; + # use G1 because we rely on paths being straight (G0 may make round paths) + $gcode .= join '', map $self->G1($_->[B], undef, 0, $comment || ""), @travel; return $gcode; } From 43814e99f6c8bfc1a2d8cc0eae4a44d01a08b2af Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 6 Jun 2013 10:46:58 +0200 Subject: [PATCH 154/179] Remove smart match and lexical $_ for compatibility with perl 5.18. #1216 --- lib/Slic3r/Config.pm | 4 ++-- lib/Slic3r/Format/OBJ.pm | 2 +- lib/Slic3r/Format/STL.pm | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 22edd0ff..8b9fb1ca 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -1141,7 +1141,7 @@ sub set { my ($opt_key, $value, $deserialize) = @_; # handle legacy options - return if $opt_key ~~ @Ignore; + return if first { $_ eq $opt_key } @Ignore; if ($opt_key =~ /^(extrusion_width|bottom_layer_speed|first_layer_height)_ratio$/) { $opt_key = $1; $opt_key =~ s/^bottom_layer_speed$/first_layer_speed/; @@ -1413,7 +1413,7 @@ sub read_ini { my $ini = { _ => {} }; my $category = '_'; - while (my $_ = <$fh>) { + while (<$fh>) { s/\R+$//; next if /^\s+/; next if /^$/; diff --git a/lib/Slic3r/Format/OBJ.pm b/lib/Slic3r/Format/OBJ.pm index c5cc0855..05a141db 100644 --- a/lib/Slic3r/Format/OBJ.pm +++ b/lib/Slic3r/Format/OBJ.pm @@ -8,7 +8,7 @@ sub read_file { Slic3r::open(\my $fh, '<', $file) or die "Failed to open $file\n"; my $vertices = []; my $facets = []; - while (my $_ = <$fh>) { + while (<$fh>) { if (/^v ([^ ]+)\s+([^ ]+)\s+([^ ]+)/) { push @$vertices, [$1, $2, $3]; } elsif (/^f (\d+).*? (\d+).*? (\d+).*?/) { diff --git a/lib/Slic3r/Format/STL.pm b/lib/Slic3r/Format/STL.pm index 0d0331c8..cf07b1ca 100644 --- a/lib/Slic3r/Format/STL.pm +++ b/lib/Slic3r/Format/STL.pm @@ -53,7 +53,7 @@ sub _read_ascii { my $facet; my %vertices_map = (); seek $fh, 0, 0; - while (my $_ = <$fh>) { + while (<$fh>) { if (!$facet) { /^\s*facet\s+normal\s+/ or next; $facet = []; # ignore normal @@ -88,7 +88,7 @@ sub _read_binary { my %vertices_map = (); binmode $fh; seek $fh, 80 + 4, 0; - while (read $fh, my $_, 4*4*3+2) { + while (read $fh, $_, 4*4*3+2) { push @$facets, my $facet = []; for (unpack 'x[f3](a[f3])3') { # ignore normal my $vertex_idx; From fcc442882c8a372ec6780ea068f9fa910857d80f Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 6 Jun 2013 20:53:56 +0200 Subject: [PATCH 155/179] Fix plater defect causing misalignment --- lib/Slic3r/GUI/Plater.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 0dbca23b..286a524e 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -717,6 +717,7 @@ sub make_model { $model->set_material($volume->material_id || 0, {}); } $new_model_object->scale($plater_object->scale); + $new_model_object->align_to_origin; $new_model_object->add_instance( rotation => $plater_object->rotate, # around center point offset => Slic3r::Point->new($_), From f991e3bc106ec1402bd68c7ad555ff2f65dffbe1 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 7 Jun 2013 12:00:03 +0200 Subject: [PATCH 156/179] Fixed SVG export and added regression test --- MANIFEST | 1 + lib/Slic3r/Print.pm | 14 ++++++++------ t/svg.t | 23 +++++++++++++++++++++++ 3 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 t/svg.t diff --git a/MANIFEST b/MANIFEST index c319935b..36389aef 100644 --- a/MANIFEST +++ b/MANIFEST @@ -81,6 +81,7 @@ t/shells.t t/slice.t t/skirt_brim.t t/support.t +t/svg.t t/vibrationlimit.t utils/amf-to-stl.pl utils/file_info.pl diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index c2b59a20..d1777431 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -488,13 +488,15 @@ sub export_svg { $self->init_extruders; $_->slice for @{$self->objects}; - $self->arrange_objects; - my $output_file = $self->expanded_output_filepath($params{output_file}); - $output_file =~ s/\.gcode$/.svg/i; + my $fh = $params{output_fh}; + if ($params{output_file}) { + my $output_file = $self->expanded_output_filepath($params{output_file}); + $output_file =~ s/\.gcode$/.svg/i; + Slic3r::open(\$fh, ">", $output_file) or die "Failed to open $output_file for writing\n"; + print "Exporting to $output_file..." unless $params{quiet}; + } - Slic3r::open(\my $fh, ">", $output_file) or die "Failed to open $output_file for writing\n"; - print "Exporting to $output_file..."; my $print_size = $self->size; print $fh sprintf <<"EOF", unscale($print_size->[X]), unscale($print_size->[Y]); @@ -563,7 +565,7 @@ EOF print $fh "\n"; close $fh; - print "Done.\n"; + print "Done.\n" unless $params{quiet}; } sub make_skirt { diff --git a/t/svg.t b/t/svg.t new file mode 100644 index 00000000..9e2a8dcb --- /dev/null +++ b/t/svg.t @@ -0,0 +1,23 @@ +use Test::More tests => 1; +use strict; +use warnings; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; +} + +use Slic3r; +use Slic3r::Test; + +{ + my $print = Slic3r::Test::init_print('20mm_cube'); + eval { + my $fh = IO::Scalar->new(\my $gcode); + $print->export_svg(output_fh => $fh, quiet => 1); + $fh->close; + }; + ok !$@, 'successful SVG export'; +} + +__END__ From 7a8e1e778a1871bd031aa27e5328ca727d5254c6 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 7 Jun 2013 13:38:29 +0200 Subject: [PATCH 157/179] New test to ensure brim and raft are extruded with the support material extruder. #123 --- t/support.t | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/t/support.t b/t/support.t index 1e9a24a6..bee1987f 100644 --- a/t/support.t +++ b/t/support.t @@ -14,8 +14,24 @@ use Slic3r::Test; my $config = Slic3r::Config->new_from_defaults; $config->set('raft_layers', 3); $config->set('brim_width', 6); + $config->set('skirts', 0); + $config->set('support_material_extruder', 2); + $config->set('layer_height', 0.4); + $config->set('first_layer_height', '100%'); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - ok Slic3r::Test::gcode($print), 'no conflict between raft/support and brim'; + ok my $gcode = Slic3r::Test::gcode($print), 'no conflict between raft/support and brim'; + + my $tool = 0; + Slic3r::GCode::Reader->new(gcode => $gcode)->parse(sub { + my ($self, $cmd, $args, $info) = @_; + + if ($cmd =~ /^T(\d+)/) { + $tool = $1; + } elsif ($info->{extruding} && $self->Z <= ($config->raft_layers * $config->layer_height)) { + fail 'not extruding raft/brim with support material extruder' + if $tool != ($config->support_material_extruder-1); + } + }); } __END__ From 9ea55497c255930b92984e1457ea9e1caa8f3a65 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 7 Jun 2013 13:54:40 +0200 Subject: [PATCH 158/179] Prevent crash when user deleted object from plater before thumbnail was generated in the other thread. #1207 --- lib/Slic3r/GUI.pm | 1 + lib/Slic3r/GUI/Plater.pm | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index fd46cc5e..00e56f4f 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -253,6 +253,7 @@ sub warning_catcher { my ($self, $message_dialog) = @_; return sub { my $message = shift; + return if $message =~ /GLUquadricObjPtr|Attempt to free unreferenced scalar/; my @params = ($message, 'Warning', wxOK | wxICON_WARNING); $message_dialog ? $message_dialog->(@params) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 286a524e..dc17bdcf 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -159,6 +159,7 @@ sub new { EVT_COMMAND($self, -1, $THUMBNAIL_DONE_EVENT, sub { my ($self, $event) = @_; my ($obj_idx, $thumbnail) = @{$event->GetData}; + return if !$self->{objects}[$obj_idx]; # object was deleted before thumbnail generation completed $self->{objects}[$obj_idx]->thumbnail($thumbnail->clone); $self->on_thumbnail_made($obj_idx); }); From 510c2092dff31a17201e4de20d160a0b643a8e32 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 7 Jun 2013 23:16:02 +0200 Subject: [PATCH 159/179] Fix rotation and scaling in plater producing mispositioned objects in G-code after recent changes. Includes a large refactoring and the new Slic3r::Geometry::BoundingBox class. #1171 #1191 --- MANIFEST | 1 + lib/Slic3r.pm | 2 + lib/Slic3r/ExPolygon.pm | 14 +++++ lib/Slic3r/GUI/Plater.pm | 74 ++++++++++++++----------- lib/Slic3r/GUI/Plater/ObjectDialog.pm | 2 +- lib/Slic3r/Geometry/BoundingBox.pm | 78 +++++++++++++++++++++++++++ lib/Slic3r/Point.pm | 7 +++ lib/Slic3r/TriangleMesh.pm | 5 ++ 8 files changed, 151 insertions(+), 32 deletions(-) create mode 100644 lib/Slic3r/Geometry/BoundingBox.pm diff --git a/MANIFEST b/MANIFEST index 36389aef..b2fe05ed 100644 --- a/MANIFEST +++ b/MANIFEST @@ -30,6 +30,7 @@ lib/Slic3r/GCode/MotionPlanner.pm lib/Slic3r/GCode/Reader.pm lib/Slic3r/GCode/SpiralVase.pm lib/Slic3r/Geometry.pm +lib/Slic3r/Geometry/BoundingBox.pm lib/Slic3r/Geometry/Clipper.pm lib/Slic3r/GUI.pm lib/Slic3r/GUI/AboutDialog.pm diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 1338ce06..67314e21 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -51,6 +51,8 @@ use Slic3r::GCode::MotionPlanner; use Slic3r::GCode::Reader; use Slic3r::GCode::SpiralVase; use Slic3r::Geometry qw(PI); +use Slic3r::Geometry::BoundingBox; +use Slic3r::Geometry::Clipper; use Slic3r::Layer; use Slic3r::Layer::Region; use Slic3r::Line; diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index 45b59ace..5190357f 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -335,11 +335,25 @@ sub align_to_origin { my @bb = Slic3r::Geometry::bounding_box([ map @$_, map @$_, @{$self->expolygons} ]); $_->translate(-$bb[X1], -$bb[Y1]) for @{$self->expolygons}; + $self; +} + +sub scale { + my $self = shift; + $_->scale(@_) for @{$self->expolygons}; + $self; } sub rotate { my $self = shift; $_->rotate(@_) for @{$self->expolygons}; + $self; +} + +sub translate { + my $self = shift; + $_->translate(@_) for @{$self->expolygons}; + $self; } sub size { diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index dc17bdcf..7b5dde5b 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -452,7 +452,7 @@ sub arrange { my $total_parts = sum(map $_->instances_count, @{$self->{objects}}) or return; my @size = (); for my $a (X,Y) { - $size[$a] = max(map $_->size->[$a], @{$self->{objects}}); + $size[$a] = max(map $_->transformed_size->[$a], @{$self->{objects}}); } eval { @@ -769,10 +769,9 @@ sub recenter { my @print_bb = Slic3r::Geometry::bounding_box([ map { my $obj = $_; - map { - my $instance = $_; - $instance, [ map $instance->[$_] + $obj->size->[$_], X,Y ]; - } @{$obj->instances}; + my $bb = $obj->transformed_bounding_box; + my @points = ($bb->min_point, $bb->max_point); + map Slic3r::Geometry::move_points($_, @points), @{$obj->instances}; } @{$self->{objects}}, ]); $self->{shift} = [ @@ -867,9 +866,12 @@ sub repaint { next unless $object->thumbnail && @{$object->thumbnail->expolygons}; for my $instance_idx (0 .. $#{$object->instances}) { my $instance = $object->instances->[$instance_idx]; - push @{$parent->{object_previews}}, [ $obj_idx, $instance_idx, $object->thumbnail->clone ]; - $_->translate(map $parent->to_pixel($instance->[$_]) + $parent->{shift}[$_], (X,Y)) - for @{$parent->{object_previews}->[-1][2]->expolygons}; + + my $thumbnail = $object->thumbnail + ->clone + ->translate(map $parent->to_pixel($instance->[$_]) + $parent->{shift}[$_], (X,Y)); + + push @{$parent->{object_previews}}, [ $obj_idx, $instance_idx, $thumbnail ]; my $drag_object = $self->{drag_object}; if (defined $drag_object && $obj_idx == $drag_object->[0] && $instance_idx == $drag_object->[1]) { @@ -1065,15 +1067,15 @@ package Slic3r::GUI::Plater::Object; use Moo; use Math::ConvexHull::MonotoneChain qw(convex_hull); -use Slic3r::Geometry qw(X Y Z); +use Slic3r::Geometry qw(X Y Z MIN MAX deg2rad); has 'name' => (is => 'rw', required => 1); has 'input_file' => (is => 'rw', required => 1); has 'input_file_object_id' => (is => 'rw'); # undef means keep model object has 'model_object' => (is => 'rw', required => 1, trigger => 1); -has 'size' => (is => 'rw'); +has 'bounding_box' => (is => 'rw'); # 3D bb of original object (aligned to origin) with no rotation or scaling has 'scale' => (is => 'rw', default => sub { 1 }); -has 'rotate' => (is => 'rw', default => sub { 0 }); +has 'rotate' => (is => 'rw', default => sub { 0 }); # around object center point has 'instances' => (is => 'rw', default => sub { [] }); # upward Y axis has 'thumbnail' => (is => 'rw'); has 'thumbnail_scaling_factor' => (is => 'rw'); @@ -1088,8 +1090,9 @@ has 'is_manifold' => (is => 'rw'); sub _trigger_model_object { my $self = shift; if ($self->model_object) { + $self->model_object->align_to_origin; my $mesh = $self->model_object->mesh; - $self->size([$mesh->size]); + $self->bounding_box($mesh->bounding_box); $self->facets(scalar @{$mesh->facets}); $self->vertices(scalar @{$mesh->vertices}); $self->materials($self->model_object->materials_count); @@ -1131,22 +1134,22 @@ sub make_thumbnail { my $self = shift; my $mesh = $self->model_object->mesh; + $mesh->align_to_origin; my $thumbnail = Slic3r::ExPolygon::Collection->new( expolygons => (@{$mesh->facets} <= 5000) ? $mesh->horizontal_projection : [ Slic3r::ExPolygon->new(convex_hull($mesh->vertices)) ], ); - for (map @$_, map @$_, @{$thumbnail->expolygons}) { - @$_ = map $_ * $self->thumbnail_scaling_factor, @$_; - } + $thumbnail->scale($self->thumbnail_scaling_factor); + # only simplify expolygons larger than the threshold - @{$thumbnail->expolygons} = map { ($_->area >= 1) ? $_->simplify(0.5) : $_ } @{$thumbnail->expolygons}; - foreach my $expolygon (@{$thumbnail->expolygons}) { - $expolygon->rotate(Slic3r::Geometry::deg2rad($self->rotate)); - $expolygon->scale($self->scale); - } - @{$thumbnail->expolygons} = grep @$_, @{$thumbnail->expolygons}; - $thumbnail->align_to_origin; + @{$thumbnail->expolygons} = grep @$_, + map { ($_->area >= 1) ? $_->simplify(0.5) : $_ } + @{$thumbnail->expolygons}; + + $thumbnail->rotate(deg2rad($self->rotate)); # TODO: around center + $thumbnail->scale($self->scale); + $self->thumbnail($thumbnail); # ignored in multi-threaded environments $self->free_model_object; @@ -1158,10 +1161,8 @@ sub set_rotation { my ($angle) = @_; if ($self->thumbnail) { - $self->thumbnail->rotate(Slic3r::Geometry::deg2rad($angle - $self->rotate)); - $self->thumbnail->align_to_origin; - my $z_size = $self->size->[Z]; - $self->size([ (map $_ / $self->thumbnail_scaling_factor, @{$self->thumbnail->size}), $z_size ]); + # rotate around object centerpoint + $self->thumbnail->rotate(deg2rad($angle - $self->rotate), $self->bounding_box->center_2D->clone->scale($self->thumbnail_scaling_factor)); } $self->rotate($angle); } @@ -1170,14 +1171,25 @@ sub set_scale { my $self = shift; my ($scale) = @_; - my $factor = $scale / $self->scale; - return if $factor == 1; - $self->size->[$_] *= $factor for X,Y,Z; if ($self->thumbnail) { - $_->scale($factor) for @{$self->thumbnail->expolygons}; - $self->thumbnail->align_to_origin; + $self->thumbnail->scale($scale / $self->scale); } $self->scale($scale); } +# bounding box with applied rotation and scaling +sub transformed_bounding_box { + my $self = shift; + + return $self->bounding_box + ->clone + ->rotate(deg2rad($self->rotate), $self->bounding_box->center) + ->scale($self->scale); +} + +sub transformed_size { + my $self = shift; + return $self->transformed_bounding_box->size; +} + 1; diff --git a/lib/Slic3r/GUI/Plater/ObjectDialog.pm b/lib/Slic3r/GUI/Plater/ObjectDialog.pm index 3cb725e6..4fc83884 100644 --- a/lib/Slic3r/GUI/Plater/ObjectDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectDialog.pm @@ -78,7 +78,7 @@ sub get_properties { my $object = $self->{object}; return [ ['Name' => $object->name], - ['Size' => sprintf "%.2f x %.2f x %.2f", @{$object->size}], + ['Size' => sprintf "%.2f x %.2f x %.2f", @{$object->transformed_size}], ['Facets' => $object->facets], ['Vertices' => $object->vertices], ['Materials' => $object->materials], diff --git a/lib/Slic3r/Geometry/BoundingBox.pm b/lib/Slic3r/Geometry/BoundingBox.pm new file mode 100644 index 00000000..cbcec824 --- /dev/null +++ b/lib/Slic3r/Geometry/BoundingBox.pm @@ -0,0 +1,78 @@ +package Slic3r::Geometry::BoundingBox; +use Moo; +use Slic3r::Geometry qw(X Y Z MIN MAX X1 Y1 X2 Y2); +use Storable qw(); + +has 'extents' => (is => 'ro', required => 1); + +sub clone { Storable::dclone($_[0]) } + +# four-arguments 2D bb +sub bb { + my $self = shift; + my $extents = $self->extents; + return [ $extents->[X][MIN], $extents->[Y][MIN], $extents->[X][MAX], $extents->[Y][MAX] ]; +} + +sub polygon { + my $self = shift; + return Slic3r::Polygon->new_from_bounding_box($self->bb); +} + +sub rotate { + my $self = shift; + my ($angle, $center) = @_; + + # rotate the 2D bounding box polygon and leave Z unaltered + my $bb_p = $self->polygon; + $bb_p->rotate($angle, $center); + my @bb = $bb_p->bounding_box; + $self->extents->[X][MIN] = $bb[X1]; + $self->extents->[Y][MIN] = $bb[Y1]; + $self->extents->[X][MAX] = $bb[X2]; + $self->extents->[Y][MAX] = $bb[Y2]; + + $self; +} + +sub scale { + my $self = shift; + my ($factor) = @_; + + $_ *= $factor + for map @$_[MIN,MAX], + grep $_, @{$self->extents}[X,Y,Z]; + + $self; +} + +sub size { + my $self = shift; + + my $extents = $self->extents; + return [ map $extents->[$_][MAX] - $extents->[$_][MIN], grep $extents->[$_], (X,Y,Z) ]; +} + +sub center { + my $self = shift; + + my $extents = $self->extents; + return [ map +($extents->[$_][MAX] + $extents->[$_][MIN])/2, grep $extents->[$_], (X,Y,Z) ]; +} + +sub center_2D { + my $self = shift; + return Slic3r::Point->new(@{$self->center}[X,Y]); +} + +sub min_point { + my $self = shift; + return Slic3r::Point->new($self->extents->[X][MIN], $self->extents->[Y][MIN]); +} + +sub max_point { + my $self = shift; + return Slic3r::Point->new($self->extents->[X][MAX], $self->extents->[Y][MAX]); +} + +1; diff --git a/lib/Slic3r/Point.pm b/lib/Slic3r/Point.pm index 26e79045..b820a1e8 100644 --- a/lib/Slic3r/Point.pm +++ b/lib/Slic3r/Point.pm @@ -35,6 +35,13 @@ sub distance_to { return Slic3r::Geometry::distance_between_points($self, $point); } +sub scale { + my $self = shift; + my ($factor) = @_; + $_ *= $factor for @$self; + $self; +} + sub rotate { my $self = shift; my ($angle, $center) = @_; diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm index 3506d893..a58f3c0e 100644 --- a/lib/Slic3r/TriangleMesh.pm +++ b/lib/Slic3r/TriangleMesh.pm @@ -408,6 +408,11 @@ sub extents { return Slic3r::Geometry::bounding_box_3D($self->used_vertices); } +sub bounding_box { + my $self = shift; + return Slic3r::Geometry::BoundingBox->new(extents => [ $self->extents ]); +} + sub size { my $self = shift; return Slic3r::Geometry::size_3D($self->used_vertices); From b12a09ed71b5264e5e0123ff0e38b12df07a7657 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 7 Jun 2013 23:24:53 +0200 Subject: [PATCH 160/179] Rename makerbot G-code flavor to makerware; also mention Repetier among RepRap firmwares. #1034 --- README.markdown | 4 ++-- lib/Slic3r/Config.pm | 11 +++++++++-- lib/Slic3r/GCode.pm | 20 ++++++++++---------- lib/Slic3r/Print.pm | 4 ++-- slic3r.pl | 2 +- utils/zsh/functions/_slic3r | 2 +- 6 files changed, 25 insertions(+), 18 deletions(-) diff --git a/README.markdown b/README.markdown index 0a4e2b95..4fe41b87 100644 --- a/README.markdown +++ b/README.markdown @@ -7,7 +7,7 @@ A: Yes. ## What's it? Slic3r is a G-code generator for 3D printers. It's compatible with RepRaps, -Makerbots, Ultimakers and many more machines. +makerwares, Ultimakers and many more machines. See the [project homepage](http://slic3r.org/) at slic3r.org and the [documentation](https://github.com/alexrj/Slic3r/wiki/Documentation) on the Slic3r wiki for more information. @@ -112,7 +112,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/makerbot/sailfish/mach3/no-extrusion, + --gcode-flavor The type of G-code to generate (reprap/teacup/makerware/sailfish/mach3/no-extrusion, default: reprap) --use-relative-e-distances Enable this to get relative E values --gcode-arcs Use G2/G3 commands for native arcs (experimental, not supported diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 22edd0ff..d83cc4c5 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -74,8 +74,8 @@ our $Options = { tooltip => 'Some G/M-code commands, including temperature control and others, are not universal. Set this option to your printer\'s firmware to get a compatible output. The "No extrusion" flavor prevents Slic3r from exporting any extrusion value at all.', cli => 'gcode-flavor=s', type => 'select', - values => [qw(reprap teacup makerbot sailfish mach3 no-extrusion)], - labels => ['RepRap (Marlin/Sprinter)', 'Teacup', 'MakerBot', 'Sailfish', 'Mach3/EMC', 'No extrusion'], + values => [qw(reprap teacup makerware sailfish mach3 no-extrusion)], + labels => ['RepRap (Marlin/Sprinter/Repetier)', 'Teacup', 'MakerWare (MakerBot)', 'Sailfish (MakerBot)', 'Mach3/EMC', 'No extrusion'], default => 'reprap', }, 'use_relative_e_distances' => { @@ -1150,6 +1150,9 @@ sub set { if ($opt_key eq 'threads' && !$Slic3r::have_threads) { $value = 1; } + if ($opt_key eq 'gcode_flavor' && $value eq 'makerbot') { + $value = 'makerware'; + } # For historical reasons, the world's full of configs having these very low values; # to avoid unexpected behavior we need to ignore them. Banning these two hard-coded @@ -1271,6 +1274,10 @@ sub validate { die "Invalid value for --top-solid-layers\n" if $self->top_solid_layers < 0; die "Invalid value for --bottom-solid-layers\n" if $self->bottom_solid_layers < 0; + # --gcode-flavor + die "Invalid value for --gcode-flavor\n" + if !first { $_ eq $self->gcode_flavor } @{$Options->{gcode_flavor}{values}}; + # --print-center die "Invalid value for --print-center\n" if !ref $self->print_center diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index b7f855e8..3b05deb9 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -88,7 +88,7 @@ sub change_layer { } my $gcode = ""; - if ($self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) { + if ($self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/) { $gcode .= sprintf "M73 P%s%s\n", int(99 * ($layer->id / ($self->layer_count - 1))), ($self->config->gcode_comments ? ' ; update progress' : ''); @@ -420,7 +420,7 @@ sub retract { # this makes sure we leave sufficient precision in the firmware $gcode .= $self->reset_e; - $gcode .= "M103 ; extruder off\n" if $self->config->gcode_flavor eq 'makerbot'; + $gcode .= "M103 ; extruder off\n" if $self->config->gcode_flavor eq 'makerware'; return $gcode; } @@ -429,7 +429,7 @@ sub unretract { my $self = shift; my $gcode = ""; - $gcode .= "M101 ; extruder on\n" if $self->config->gcode_flavor eq 'makerbot'; + $gcode .= "M101 ; extruder on\n" if $self->config->gcode_flavor eq 'makerware'; if ($self->lifted) { $self->speed('travel'); @@ -451,7 +451,7 @@ sub unretract { sub reset_e { my $self = shift; - return "" if $self->config->gcode_flavor =~ /^(?:mach3|makerbot)$/; + return "" if $self->config->gcode_flavor =~ /^(?:mach3|makerware)$/; $self->extruder->e(0) if $self->extruder; return sprintf "G92 %s0%s\n", $self->config->extrusion_axis, ($self->config->gcode_comments ? ' ; reset extrusion distance' : '') @@ -591,7 +591,7 @@ sub set_extruder { # set the new extruder $self->extruder($extruder); $gcode .= sprintf "%s%d%s\n", - ($self->config->gcode_flavor eq 'makerbot' + ($self->config->gcode_flavor eq 'makerware' ? 'M135 T' : $self->config->gcode_flavor eq 'sailfish' ? 'M108 T' @@ -613,12 +613,12 @@ sub set_fan { if ($speed == 0) { my $code = $self->config->gcode_flavor eq 'teacup' ? 'M106 S0' - : $self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ + : $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/ ? 'M127' : 'M107'; return sprintf "$code%s\n", ($self->config->gcode_comments ? ' ; disable fan' : ''); } else { - if ($self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) { + if ($self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/) { return sprintf "M126%s\n", ($self->config->gcode_comments ? ' ; enable fan' : ''); } else { return sprintf "M106 %s%d%s\n", ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), @@ -633,14 +633,14 @@ sub set_temperature { my $self = shift; my ($temperature, $wait, $tool) = @_; - return "" if $wait && $self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/; + return "" if $wait && $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/; my ($code, $comment) = ($wait && $self->config->gcode_flavor ne 'teacup') ? ('M109', 'wait for temperature to be reached') : ('M104', 'set temperature'); my $gcode = sprintf "$code %s%d %s; $comment\n", ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature, - (defined $tool && ($self->multiple_extruders || $self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/)) ? "T$tool " : ""; + (defined $tool && ($self->multiple_extruders || $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/)) ? "T$tool " : ""; $gcode .= "M116 ; wait for temperature to be reached\n" if $self->config->gcode_flavor eq 'teacup' && $wait; @@ -653,7 +653,7 @@ sub set_bed_temperature { my ($temperature, $wait) = @_; my ($code, $comment) = ($wait && $self->config->gcode_flavor ne 'teacup') - ? (($self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ ? 'M109' : 'M190'), 'wait for bed temperature to be reached') + ? (($self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/ ? 'M109' : 'M190'), 'wait for bed temperature to be reached') : ('M140', 'set bed temperature'); my $gcode = sprintf "$code %s%d ; $comment\n", ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index d1777431..6464ba90 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -714,7 +714,7 @@ sub write_gcode { multiple_extruders => (@{$self->extruders} > 1), layer_count => $self->layer_count, ); - print $fh "G21 ; set units to millimeters\n" if $Slic3r::Config->gcode_flavor ne 'makerbot'; + print $fh "G21 ; set units to millimeters\n" if $Slic3r::Config->gcode_flavor ne 'makerware'; print $fh $gcodegen->set_fan(0, 1) if $Slic3r::Config->cooling && $Slic3r::Config->disable_fan_first_layers; # write start commands to file @@ -732,7 +732,7 @@ sub write_gcode { printf $fh $gcodegen->set_temperature($self->extruders->[$t]->first_layer_temperature, 1, $t) if $self->extruders->[$t]->first_layer_temperature && $Slic3r::Config->start_gcode !~ /M(?:109|104)/i; } - print $fh "G90 ; use absolute coordinates\n" if $Slic3r::Config->gcode_flavor ne 'makerbot'; + print $fh "G90 ; use absolute coordinates\n" if $Slic3r::Config->gcode_flavor ne 'makerware'; if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|teacup)$/) { printf $fh $gcodegen->reset_e; if ($Slic3r::Config->use_relative_e_distances) { diff --git a/slic3r.pl b/slic3r.pl index e792548e..0b05cd04 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -173,7 +173,7 @@ $j (default: $config->{print_center}->[0],$config->{print_center}->[1]) --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/makerbot/sailfish/mach3/no-extrusion, + --gcode-flavor The type of G-code to generate (reprap/teacup/makerware/sailfish/mach3/no-extrusion, default: $config->{gcode_flavor}) --use-relative-e-distances Enable this to get relative E values --gcode-arcs Use G2/G3 commands for native arcs (experimental, not supported diff --git a/utils/zsh/functions/_slic3r b/utils/zsh/functions/_slic3r index 41b2594b..10a0ce05 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 makerbot sailfish mach3 no-extrusion)' \ + '--gcode-flavor[specify the type of G-code to generate]:G-code flavor:(reprap teacup makerware sailfish mach3 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]' \ From d5a9320587ebc77f813aee0be4901c611d2adb70 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 8 Jun 2013 17:48:34 +0200 Subject: [PATCH 161/179] Bugfix: narrow top surfaces didn't generate solid layers. #1161 --- lib/Slic3r/Print/Object.pm | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index bfc46ce6..9f9f4353 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -383,7 +383,6 @@ sub detect_surfaces_type { 1, ); return map Slic3r::Surface->new(expolygon => $_, surface_type => $result_type), - grep $_->is_printable($layerm->perimeter_flow->scaled_width), @$expolygons; }; From 8b2c13cc6f225915bf6df837859b0685979f9e62 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 8 Jun 2013 20:01:26 +0200 Subject: [PATCH 162/179] Regression test for top solid surfaces in V-shaped object. #1161 --- lib/Slic3r/Test.pm | 7 +++++++ t/shells.t | 25 ++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index 46443cc6..1a0f0e2d 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -27,6 +27,13 @@ sub model { $facets = [ [0,1,2], [0,2,3], [4,5,6], [4,6,7], [0,4,7], [0,7,1], [1,7,6], [1,6,2], [2,6,5], [2,5,3], [4,0,3], [4,3,5], ], + } elsif ($model_name eq 'V') { + $vertices = [ + [-14,0,20],[-14,15,20],[0,0,0],[0,15,0],[-4,0,20],[-4,15,20],[5,0,7.14286],[10,0,0],[24,0,20],[14,0,20],[10,15,0],[5,15,7.14286],[14,15,20],[24,15,20] + ]; + $facets = [ + [0,1,2],[2,1,3],[1,0,4],[5,1,4],[4,0,2],[6,4,2],[7,6,2],[8,9,7],[9,6,7],[2,3,7],[7,3,10],[1,5,3],[3,5,11],[11,12,13],[11,13,3],[3,13,10],[5,4,6],[11,5,6],[6,9,11],[11,9,12],[12,9,8],[13,12,8],[8,7,10],[13,8,10] + ], } my $model = Slic3r::Model->new; diff --git a/t/shells.t b/t/shells.t index 7977f491..56a3ca59 100644 --- a/t/shells.t +++ b/t/shells.t @@ -1,4 +1,4 @@ -use Test::More tests => 2; +use Test::More tests => 3; use strict; use warnings; @@ -45,4 +45,27 @@ use Slic3r::Test; ok $test->(), "proper number of shells is applied even when fill density is none"; } +# issue #1161 +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('layer_height', 0.3); + $config->set('first_layer_height', '100%'); + $config->set('bottom_solid_layers', 0); + $config->set('top_solid_layers', 3); + $config->set('cooling', 0); + $config->set('solid_infill_speed', 99); + $config->set('top_solid_infill_speed', 99); + + my $print = Slic3r::Test::init_print('V', config => $config); + my %layers_with_solid_infill = (); # Z => 1 + Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { + my ($self, $cmd, $args, $info) = @_; + + $layers_with_solid_infill{$self->Z} = 1 + if $info->{extruding} && ($args->{F} // $self->F) == $config->solid_infill_speed*60; + }); + is scalar(map $layers_with_solid_infill{$_}, grep $_ <= 7.2, keys %layers_with_solid_infill), 3, + "correct number of top solid shells is generated in V-shaped object"; +} + __END__ From 6ae766600657cac98bf3b129fefd28ea33c8cda1 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 8 Jun 2013 20:02:21 +0200 Subject: [PATCH 163/179] New utility script to dump STL contents in Perl syntax for writing tests --- utils/dump-stl.pl | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 utils/dump-stl.pl diff --git a/utils/dump-stl.pl b/utils/dump-stl.pl new file mode 100644 index 00000000..a5c716b8 --- /dev/null +++ b/utils/dump-stl.pl @@ -0,0 +1,34 @@ +#!/usr/bin/perl +# This script dumps a STL file into Perl syntax for writing tests + +use strict; +use warnings; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; +} + +use Slic3r; +$|++; + +$ARGV[0] or usage(1); + +{ + my $model = Slic3r::Format::STL->read_file($ARGV[0]); + my $mesh = $model->mesh; + printf "VERTICES = %s\n", join ',', map "[$_->[0],$_->[1],$_->[2]]", @{$mesh->vertices}; + printf "FACETS = %s\n", join ',', map "[$_->[0],$_->[1],$_->[2]]", @{$mesh->facets}; +} + + +sub usage { + my ($exit_code) = @_; + + print <<"EOF"; +Usage: dump-stl.pl file.stl +EOF + exit ($exit_code || 0); +} + +__END__ From 8e0e03247d72da806c919db6f1b1505c528f793e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 10 Jun 2013 15:34:45 +0200 Subject: [PATCH 164/179] Bugfix: ineffective scale() method in recently created BoundingBox objects caused wrong positioning for scaled objects in plater. Includes regression test. #1171 --- lib/Slic3r/Geometry/BoundingBox.pm | 26 +++++++++++++++++++++++--- t/geometry.t | 12 ++++++++++-- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/Geometry/BoundingBox.pm b/lib/Slic3r/Geometry/BoundingBox.pm index cbcec824..3cd019f0 100644 --- a/lib/Slic3r/Geometry/BoundingBox.pm +++ b/lib/Slic3r/Geometry/BoundingBox.pm @@ -7,6 +7,26 @@ has 'extents' => (is => 'ro', required => 1); sub clone { Storable::dclone($_[0]) } +# 2D +sub new_from_points { + my $class = shift; + my ($points) = @_; + + my $bb = [ Slic3r::Geometry::bounding_box($points) ]; + return $class->new(extents => [ + [ $bb->[X1], $bb->[X2] ], + [ $bb->[Y1], $bb->[Y2] ], + ]); +} + +# 3D +sub new_from_points_3D { + my $class = shift; + my ($points) = @_; + + return $class->new(extents => [ Slic3r::Geometry::bounding_box_3D($points) ]); +} + # four-arguments 2D bb sub bb { my $self = shift; @@ -39,9 +59,9 @@ sub scale { my $self = shift; my ($factor) = @_; - $_ *= $factor - for map @$_[MIN,MAX], - grep $_, @{$self->extents}[X,Y,Z]; + for (@{$self->extents}) { + $_ *= $factor for @$_[MIN,MAX]; + } $self; } diff --git a/t/geometry.t b/t/geometry.t index c0e1f753..edbe7262 100644 --- a/t/geometry.t +++ b/t/geometry.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 23; +plan tests => 24; BEGIN { use FindBin; @@ -173,4 +173,12 @@ is Slic3r::Geometry::can_connect_points(@$points, $polygons), 0, 'can_connect_po is_deeply $result, [ [10, 0], [5, 5], [0, 0], [10, 0] ], 'split_at_index'; } -#========================================================== \ No newline at end of file +#========================================================== + +{ + my $bb = Slic3r::Geometry::BoundingBox->new_from_points([ [0, 1], [10, 2], [20, 2] ]); + $bb->scale(2); + is_deeply $bb->extents, [ [0,40], [2,4] ], 'bounding box is scaled correctly'; +} + +#========================================================== From aee7b70d79fdc6bad638c282aab010517db42872 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 12 Jun 2013 16:53:19 +0200 Subject: [PATCH 165/179] Bugfix: the order of transformations was not consistent in plater and actual G-code generation. #1171 #1191 --- lib/Slic3r/GUI/Plater.pm | 62 +++++++++++++++++++++++----------------- lib/Slic3r/Print.pm | 3 ++ 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 7b5dde5b..460f10ed 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -326,7 +326,7 @@ sub load_file { $object->check_manifoldness; # we only consider the rotation of the first instance for now - $object->set_rotation($model->objects->[$i]->instances->[0]->rotation) + $object->rotate($model->objects->[$i]->instances->[0]->rotation) if $model->objects->[$i]->instances; push @{ $self->{objects} }, $object; @@ -427,7 +427,7 @@ sub rotate { return if !$angle || $angle == -1; } - $object->set_rotation($object->rotate + $angle); + $object->rotate($object->rotate + $angle); $self->recenter; $self->{canvas}->Refresh; } @@ -437,12 +437,15 @@ sub changescale { my ($obj_idx, $object) = $self->selected_object; + # we need thumbnail to be computed before allowing scaling + return if !$object->thumbnail; + # max scale factor should be above 2540 to allow importing files exported in inches my $scale = Wx::GetNumberFromUser("", "Enter the scale % for the selected object:", "Scale", $object->scale*100, 0, 100000, $self); return if !$scale || $scale == -1; $self->{list}->SetItem($obj_idx, 2, "$scale%"); - $object->set_scale($scale / 100); + $object->scale($scale / 100); $self->arrange; } @@ -867,7 +870,7 @@ sub repaint { for my $instance_idx (0 .. $#{$object->instances}) { my $instance = $object->instances->[$instance_idx]; - my $thumbnail = $object->thumbnail + my $thumbnail = $object->transformed_thumbnail ->clone ->translate(map $parent->to_pixel($instance->[$_]) + $parent->{shift}[$_], (X,Y)); @@ -1074,10 +1077,11 @@ has 'input_file' => (is => 'rw', required => 1); has 'input_file_object_id' => (is => 'rw'); # undef means keep model object has 'model_object' => (is => 'rw', required => 1, trigger => 1); has 'bounding_box' => (is => 'rw'); # 3D bb of original object (aligned to origin) with no rotation or scaling -has 'scale' => (is => 'rw', default => sub { 1 }); -has 'rotate' => (is => 'rw', default => sub { 0 }); # around object center point +has 'scale' => (is => 'rw', default => sub { 1 }, trigger => 1); +has 'rotate' => (is => 'rw', default => sub { 0 }, trigger => 1); # around object center point has 'instances' => (is => 'rw', default => sub { [] }); # upward Y axis -has 'thumbnail' => (is => 'rw'); +has 'thumbnail' => (is => 'rw', trigger => 1); +has 'transformed_thumbnail' => (is => 'rw'); has 'thumbnail_scaling_factor' => (is => 'rw'); has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ] @@ -1099,6 +1103,21 @@ sub _trigger_model_object { } } +sub _trigger_scale { + my $self = shift; + $self->_transform_thumbnail; +} + +sub _trigger_rotate { + my $self = shift; + $self->_transform_thumbnail; +} + +sub _trigger_thumbnail { + my $self = shift; + $self->_transform_thumbnail; +} + sub check_manifoldness { my $self = shift; @@ -1147,34 +1166,23 @@ sub make_thumbnail { map { ($_->area >= 1) ? $_->simplify(0.5) : $_ } @{$thumbnail->expolygons}; - $thumbnail->rotate(deg2rad($self->rotate)); # TODO: around center - $thumbnail->scale($self->scale); - $self->thumbnail($thumbnail); # ignored in multi-threaded environments $self->free_model_object; return $thumbnail; } -sub set_rotation { +sub _transform_thumbnail { my $self = shift; - my ($angle) = @_; - if ($self->thumbnail) { - # rotate around object centerpoint - $self->thumbnail->rotate(deg2rad($angle - $self->rotate), $self->bounding_box->center_2D->clone->scale($self->thumbnail_scaling_factor)); - } - $self->rotate($angle); -} - -sub set_scale { - my $self = shift; - my ($scale) = @_; + # the order of these transformations MUST be the same everywhere, including + # in Slic3r::Print->add_model() + my $t = $self->thumbnail + ->clone + ->rotate(deg2rad($self->rotate), $self->bounding_box->center_2D->clone->scale($self->thumbnail_scaling_factor)) + ->scale($self->scale); - if ($self->thumbnail) { - $self->thumbnail->scale($scale / $self->scale); - } - $self->scale($scale); + $self->transformed_thumbnail($t); } # bounding box with applied rotation and scaling @@ -1183,7 +1191,7 @@ sub transformed_bounding_box { return $self->bounding_box ->clone - ->rotate(deg2rad($self->rotate), $self->bounding_box->center) + ->rotate(deg2rad($self->rotate), $self->bounding_box->center_2D) ->scale($self->scale); } diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 6464ba90..e20d4aca 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -120,6 +120,9 @@ sub add_model { foreach my $mesh (grep $_, @meshes) { $mesh->check_manifoldness; + # the order of these transformations must be the same as the one used in plater + # to make the object positioning consistent with the visual preview + # we ignore the per-instance rotation currently and only # consider the first one $mesh->rotate($object->instances->[0]->rotation, $mesh->center) From 5dcf2775638d3508b543b03d78d61dfccb351d11 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 13 Jun 2013 10:27:47 +0200 Subject: [PATCH 166/179] Rotate cw when using free rotation button to be consistent with icon. #1171 --- lib/Slic3r/GUI/Plater.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 460f10ed..5f55575a 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -425,6 +425,7 @@ sub rotate { if (!defined $angle) { $angle = Wx::GetNumberFromUser("", "Enter the rotation angle:", "Rotate", $object->rotate, -364, 364, $self); return if !$angle || $angle == -1; + $angle = 0 - $angle; # rotate clockwise (be consistent with button icon) } $object->rotate($object->rotate + $angle); From 0a74e45432a764b1b711abd1b23983d461f746c3 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 13 Jun 2013 11:27:15 +0200 Subject: [PATCH 167/179] Scale and recenter thumbnails when bed size and/or print center is changed --- lib/Slic3r/GUI/Plater.pm | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 5f55575a..ffe54c52 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -818,6 +818,8 @@ sub _update_bed_size { my $canvas_side = CANVAS_SIZE->[X]; # when the canvas is not rendered yet, its GetSize() method returns 0,0 my $bed_largest_side = $bed_size->[X] > $bed_size->[Y] ? $bed_size->[X] : $bed_size->[Y]; $self->{scaling_factor} = $canvas_side / $bed_largest_side; + $_->change_thumbnail_scaling_factor($self->{scaling_factor}) for @{ $self->{objects} }; + $self->recenter; } # this is called on the canvas @@ -1078,10 +1080,10 @@ has 'input_file' => (is => 'rw', required => 1); has 'input_file_object_id' => (is => 'rw'); # undef means keep model object has 'model_object' => (is => 'rw', required => 1, trigger => 1); has 'bounding_box' => (is => 'rw'); # 3D bb of original object (aligned to origin) with no rotation or scaling -has 'scale' => (is => 'rw', default => sub { 1 }, trigger => 1); -has 'rotate' => (is => 'rw', default => sub { 0 }, trigger => 1); # around object center point +has 'scale' => (is => 'rw', default => sub { 1 }, trigger => \&_transform_thumbnail); +has 'rotate' => (is => 'rw', default => sub { 0 }, trigger => \&_transform_thumbnail); # around object center point has 'instances' => (is => 'rw', default => sub { [] }); # upward Y axis -has 'thumbnail' => (is => 'rw', trigger => 1); +has 'thumbnail' => (is => 'rw', trigger => \&_transform_thumbnail); has 'transformed_thumbnail' => (is => 'rw'); has 'thumbnail_scaling_factor' => (is => 'rw'); has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ] @@ -1104,21 +1106,6 @@ sub _trigger_model_object { } } -sub _trigger_scale { - my $self = shift; - $self->_transform_thumbnail; -} - -sub _trigger_rotate { - my $self = shift; - $self->_transform_thumbnail; -} - -sub _trigger_thumbnail { - my $self = shift; - $self->_transform_thumbnail; -} - sub check_manifoldness { my $self = shift; @@ -1201,4 +1188,14 @@ sub transformed_size { return $self->transformed_bounding_box->size; } +sub change_thumbnail_scaling_factor { + my $self = shift; + my ($new_factor) = @_; + + return unless $self->thumbnail; + $self->thumbnail->scale($new_factor / $self->thumbnail_scaling_factor); + $self->transformed_thumbnail->scale($new_factor / $self->thumbnail_scaling_factor); + $self->thumbnail_scaling_factor($new_factor); +} + 1; From def013ba91d3e23a94b8d109a1cc4ac47db8754a Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 13 Jun 2013 11:28:27 +0200 Subject: [PATCH 168/179] Use _transform_thumbnail when rescaling thumbnails --- lib/Slic3r/GUI/Plater.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index ffe54c52..8c0020e8 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1194,8 +1194,8 @@ sub change_thumbnail_scaling_factor { return unless $self->thumbnail; $self->thumbnail->scale($new_factor / $self->thumbnail_scaling_factor); - $self->transformed_thumbnail->scale($new_factor / $self->thumbnail_scaling_factor); $self->thumbnail_scaling_factor($new_factor); + $self->_transform_thumbnail; } 1; From 962a51cc800d8912739b25ab8248b087d3131289 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 13 Jun 2013 14:33:10 +0200 Subject: [PATCH 169/179] Some cleanup for the plater code --- lib/Slic3r/GUI/Plater.pm | 28 ++++++++++++---------------- lib/Slic3r/Model.pm | 22 ++++++++++++++++++++-- lib/Slic3r/Print.pm | 23 +++++++++++------------ 3 files changed, 43 insertions(+), 30 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 8c0020e8..3adf65f7 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -704,11 +704,6 @@ sub make_model { foreach my $plater_object (@{$self->{objects}}) { my $model_object = $plater_object->get_model_object; - # if we need to alter the mesh, clone it first - if ($plater_object->scale != 1) { - $model_object = $model_object->clone; - } - my $new_model_object = $model->add_object( vertices => $model_object->vertices, input_file => $plater_object->input_file, @@ -721,10 +716,10 @@ sub make_model { ); $model->set_material($volume->material_id || 0, {}); } - $new_model_object->scale($plater_object->scale); $new_model_object->align_to_origin; $new_model_object->add_instance( rotation => $plater_object->rotate, # around center point + scaling_factor => $plater_object->scale, offset => Slic3r::Point->new($_), ) for @{$plater_object->instances}; } @@ -778,9 +773,12 @@ sub recenter { map Slic3r::Geometry::move_points($_, @points), @{$obj->instances}; } @{$self->{objects}}, ]); + + # $self->{shift} contains the offset in pixels to add to object instances in order to center them + # it is expressed in upwards Y $self->{shift} = [ - ($self->{canvas}->GetSize->GetWidth - $self->to_pixel($print_bb[X2] + $print_bb[X1])) / 2, - ($self->{canvas}->GetSize->GetHeight - $self->to_pixel($print_bb[Y2] + $print_bb[Y1])) / 2, + $self->to_pixel(-$print_bb[X1]) + ($self->{canvas}->GetSize->GetWidth - $self->to_pixel($print_bb[X2] - $print_bb[X1])) / 2, + $self->to_pixel(-$print_bb[Y1]) + ($self->{canvas}->GetSize->GetHeight - $self->to_pixel($print_bb[Y2] - $print_bb[Y1])) / 2, ]; } @@ -814,10 +812,8 @@ sub _update_bed_size { # supposing the preview canvas is square, calculate the scaling factor # to constrain print bed area inside preview - my $bed_size = $self->{config}->bed_size; - my $canvas_side = CANVAS_SIZE->[X]; # when the canvas is not rendered yet, its GetSize() method returns 0,0 - my $bed_largest_side = $bed_size->[X] > $bed_size->[Y] ? $bed_size->[X] : $bed_size->[Y]; - $self->{scaling_factor} = $canvas_side / $bed_largest_side; + # when the canvas is not rendered yet, its GetSize() method returns 0,0 + $self->{scaling_factor} = CANVAS_SIZE->[X] / max(@{ $self->{config}->bed_size }); $_->change_thumbnail_scaling_factor($self->{scaling_factor}) for @{ $self->{objects} }; $self->recenter; } @@ -949,7 +945,6 @@ sub mouse_event { my ($obj_idx, $instance_idx, $thumbnail) = @$preview; my $instance = $parent->{objects}[$obj_idx]->instances->[$instance_idx]; $instance->[$_] = $parent->to_units($pos->[$_] - $self->{drag_start_pos}[$_] - $parent->{shift}[$_]) for X,Y; - $instance = $parent->_y([$instance])->[0]; $parent->Refresh; } } elsif ($event->Moving) { @@ -1098,10 +1093,12 @@ sub _trigger_model_object { my $self = shift; if ($self->model_object) { $self->model_object->align_to_origin; + $self->bounding_box($self->model_object->bounding_box); + my $mesh = $self->model_object->mesh; - $self->bounding_box($mesh->bounding_box); $self->facets(scalar @{$mesh->facets}); $self->vertices(scalar @{$mesh->vertices}); + $self->materials($self->model_object->materials_count); } } @@ -1140,8 +1137,7 @@ sub instances_count { sub make_thumbnail { my $self = shift; - my $mesh = $self->model_object->mesh; - $mesh->align_to_origin; + my $mesh = $self->model_object->mesh; # $self->model_object is already aligned to origin my $thumbnail = Slic3r::ExPolygon::Collection->new( expolygons => (@{$mesh->facets} <= 5000) ? $mesh->horizontal_projection diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index b7ff6f7a..b0f422ea 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -119,6 +119,7 @@ sub arrange_objects { $object->add_instance( offset => $_, rotation => $instance->rotation, + scaling_factor => $instance->scaling_factor, ) for move_points($instance->offset, @positions); } } @@ -213,6 +214,7 @@ sub mesh { my $mesh = $object->mesh->clone; if ($instance) { $mesh->rotate($instance->rotation); + $mesh->scale($instance->scaling_factor); $mesh->align_to_origin; $mesh->move(@{$instance->offset}); } @@ -259,6 +261,7 @@ sub split_meshes { $new_object->add_instance( offset => [ $_->offset->[X] + $extents[X][MIN], $_->offset->[Y] + $extents[Y][MIN] ], rotation => $_->rotation, + scaling_factor => $_->scaling_factor, ) for @{ $object->instances // [] }; } } @@ -274,7 +277,7 @@ package Slic3r::Model::Object; use Moo; use List::Util qw(first); -use Slic3r::Geometry qw(X Y Z MIN move_points move_points_3D); +use Slic3r::Geometry qw(X Y Z MIN MAX move_points move_points_3D); use Storable qw(dclone); has 'input_file' => (is => 'rw'); @@ -338,13 +341,27 @@ sub extents { return Slic3r::Geometry::bounding_box_3D($self->used_vertices); } +sub center { + my $self = shift; + + my @extents = $self->extents; + return [ map +($extents[$_][MAX] + $extents[$_][MIN])/2, X,Y,Z ]; +} + +sub bounding_box { + my $self = shift; + return Slic3r::Geometry::BoundingBox->new(extents => [ $self->extents ]); +} + sub align_to_origin { my $self = shift; # calculate the displacements needed to # have lowest value for each axis at coordinate 0 my @extents = $self->extents; - $self->move(map -$extents[$_][MIN], X,Y,Z); + my @shift = map -$extents[$_][MIN], X,Y,Z; + $self->move(@shift); + return @shift; } sub move { @@ -410,6 +427,7 @@ use Moo; has 'object' => (is => 'ro', weak_ref => 1, required => 1); has 'rotation' => (is => 'rw', default => sub { 0 }); # around mesh center point +has 'scaling_factor' => (is => 'rw', default => sub { 1 }); has 'offset' => (is => 'rw'); # must be Slic3r::Point object 1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index e20d4aca..638ce8d3 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -106,6 +106,8 @@ sub add_model { $model->split_meshes if $Slic3r::Config->avoid_crossing_perimeters && !$Slic3r::Config->complete_objects; foreach my $object (@{ $model->objects }) { + my @align = $object->align_to_origin; + # extract meshes by material my @meshes = (); # by region_id foreach my $volume (@{$object->volumes}) { @@ -123,21 +125,18 @@ sub add_model { # the order of these transformations must be the same as the one used in plater # to make the object positioning consistent with the visual preview - # we ignore the per-instance rotation currently and only + # we ignore the per-instance transformations currently and only # consider the first one - $mesh->rotate($object->instances->[0]->rotation, $mesh->center) - if @{ $object->instances // [] }; + if ($object->instances && @{$object->instances}) { + $mesh->rotate($object->instances->[0]->rotation, $object->center); + $mesh->scale($object->instances->[0]->scaling_factor); + } $mesh->scale(1 / &Slic3r::SCALING_FACTOR); } - # align the object to origin; not sure this is required by the toolpath generation - # algorithms, but it's good practice to avoid negative coordinates; it probably - # provides also some better performance in infill generation - my @extents = Slic3r::Geometry::bounding_box_3D([ map @{$_->used_vertices}, grep $_, @meshes ]); - foreach my $mesh (grep $_, @meshes) { - $mesh->move(map -$extents[$_][MIN], X,Y,Z); - } + # calculate transformed size + my $size = [ Slic3r::Geometry::size_3D([ map @{$_->used_vertices}, grep $_, @meshes ]) ]; # initialize print object push @{$self->objects}, Slic3r::Print::Object->new( @@ -145,10 +144,10 @@ sub add_model { meshes => [ @meshes ], copies => [ $object->instances - ? (map [ (scale $_->offset->[X]) + $extents[X][MIN], (scale $_->offset->[Y]) + $extents[Y][MIN] ], @{$object->instances}) + ? (map [ scale($_->offset->[X] - $align[X]), scale($_->offset->[Y] - $align[Y]) ], @{$object->instances}) : [0,0], ], - size => [ map $extents[$_][MAX] - $extents[$_][MIN], (X,Y,Z) ], + size => $size, input_file => $object->input_file, layer_height_ranges => $object->layer_height_ranges, ); From 145fe0820314c5f807784afd38c22126a1508791 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 13 Jun 2013 20:05:32 +0200 Subject: [PATCH 170/179] More fixes for plater positioning. #1171 --- lib/Slic3r/GUI/Plater.pm | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 3adf65f7..999a1868 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -814,7 +814,7 @@ sub _update_bed_size { # to constrain print bed area inside preview # when the canvas is not rendered yet, its GetSize() method returns 0,0 $self->{scaling_factor} = CANVAS_SIZE->[X] / max(@{ $self->{config}->bed_size }); - $_->change_thumbnail_scaling_factor($self->{scaling_factor}) for @{ $self->{objects} }; + $_->thumbnail_scaling_factor($self->{scaling_factor}) for @{ $self->{objects} }; $self->recenter; } @@ -1080,7 +1080,7 @@ has 'rotate' => (is => 'rw', default => sub { 0 }, trigger => \&_ has 'instances' => (is => 'rw', default => sub { [] }); # upward Y axis has 'thumbnail' => (is => 'rw', trigger => \&_transform_thumbnail); has 'transformed_thumbnail' => (is => 'rw'); -has 'thumbnail_scaling_factor' => (is => 'rw'); +has 'thumbnail_scaling_factor' => (is => 'rw', trigger => \&_transform_thumbnail); has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ] # statistics @@ -1143,7 +1143,6 @@ sub make_thumbnail { ? $mesh->horizontal_projection : [ Slic3r::ExPolygon->new(convex_hull($mesh->vertices)) ], ); - $thumbnail->scale($self->thumbnail_scaling_factor); # only simplify expolygons larger than the threshold @{$thumbnail->expolygons} = grep @$_, @@ -1159,12 +1158,9 @@ sub make_thumbnail { sub _transform_thumbnail { my $self = shift; - # the order of these transformations MUST be the same everywhere, including - # in Slic3r::Print->add_model() - my $t = $self->thumbnail - ->clone - ->rotate(deg2rad($self->rotate), $self->bounding_box->center_2D->clone->scale($self->thumbnail_scaling_factor)) - ->scale($self->scale); + return unless $self->thumbnail; + my $t = $self->_apply_transform($self->thumbnail); + $t->scale($self->thumbnail_scaling_factor); $self->transformed_thumbnail($t); } @@ -1172,8 +1168,16 @@ sub _transform_thumbnail { # bounding box with applied rotation and scaling sub transformed_bounding_box { my $self = shift; + return $self->_apply_transform($self->bounding_box); +} + +sub _apply_transform { + my $self = shift; + my ($entity) = @_; # can be anything that implements ->clone(), ->rotate() and ->scale() - return $self->bounding_box + # the order of these transformations MUST be the same everywhere, including + # in Slic3r::Print->add_model() + return $entity ->clone ->rotate(deg2rad($self->rotate), $self->bounding_box->center_2D) ->scale($self->scale); @@ -1184,14 +1188,4 @@ sub transformed_size { return $self->transformed_bounding_box->size; } -sub change_thumbnail_scaling_factor { - my $self = shift; - my ($new_factor) = @_; - - return unless $self->thumbnail; - $self->thumbnail->scale($new_factor / $self->thumbnail_scaling_factor); - $self->thumbnail_scaling_factor($new_factor); - $self->_transform_thumbnail; -} - 1; From 878d17605c0585dcfb895e9302a99e6611022bf4 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 14 Jun 2013 16:48:24 +0200 Subject: [PATCH 171/179] Ignore solid_infill_every_layers when fill_density is 0. Includes regression test. #1240 --- lib/Slic3r/Print/Object.pm | 3 ++- t/fill.t | 11 +++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index bfc46ce6..88a445f0 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -597,7 +597,8 @@ sub discover_horizontal_shells { for (my $i = 0; $i < $self->layer_count; $i++) { my $layerm = $self->layers->[$i]->regions->[$region_id]; - if ($Slic3r::Config->solid_infill_every_layers && ($i % $Slic3r::Config->solid_infill_every_layers) == 0) { + if ($Slic3r::Config->solid_infill_every_layers && $Slic3r::Config->fill_density > 0 + && ($i % $Slic3r::Config->solid_infill_every_layers) == 0) { $_->surface_type(S_TYPE_INTERNALSOLID) for grep $_->surface_type == S_TYPE_INTERNAL, @{$layerm->fill_surfaces}; } diff --git a/t/fill.t b/t/fill.t index e0fdd3cc..4665a0c4 100644 --- a/t/fill.t +++ b/t/fill.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 9; +plan tests => 10; BEGIN { use FindBin; @@ -109,14 +109,17 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } $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); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my %layers_with_extrusion = (); Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { my ($self, $cmd, $args, $info) = @_; - - fail "solid_infill_below_area should be ignored when fill_density is 0" - if $info->{extruding}; + $layers_with_extrusion{$self->Z} = 1 if $info->{extruding}; }); + + ok !%layers_with_extrusion, + "solid_infill_below_area and solid_infill_every_layers are ignored when fill_density is 0"; } __END__ From 5fa49aad02d646a9d71cc051b0a2c6c8bc471973 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 14 Jun 2013 16:59:20 +0200 Subject: [PATCH 172/179] Releasing 0.9.10 --- lib/Slic3r.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 67314e21..78ac6e5b 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -7,7 +7,7 @@ use strict; use warnings; require v5.10; -our $VERSION = "0.9.10-dev"; +our $VERSION = "0.9.10"; our $debug = 0; sub debugf { From a8981b8b359eedc2294404f72aa9b70182d62d97 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 15 Jun 2013 12:10:57 +0200 Subject: [PATCH 173/179] Bugfix: infill was clipped badly. Includes regression test. #1245 --- MANIFEST | 1 + lib/Slic3r/Print.pm | 12 ++++++++---- lib/Slic3r/Test.pm | 9 ++++++--- t/print.t | 20 ++++++++++++++++++++ 4 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 t/print.t diff --git a/MANIFEST b/MANIFEST index b2fe05ed..86390ed3 100644 --- a/MANIFEST +++ b/MANIFEST @@ -76,6 +76,7 @@ t/geometry.t t/layers.t t/loops.t t/polyclip.t +t/print.t t/retraction.t t/serialize.t t/shells.t diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 638ce8d3..956de699 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -106,6 +106,7 @@ sub add_model { $model->split_meshes if $Slic3r::Config->avoid_crossing_perimeters && !$Slic3r::Config->complete_objects; foreach my $object (@{ $model->objects }) { + # we align object to origin before applying transformations my @align = $object->align_to_origin; # extract meshes by material @@ -135,8 +136,11 @@ sub add_model { $mesh->scale(1 / &Slic3r::SCALING_FACTOR); } - # calculate transformed size - my $size = [ Slic3r::Geometry::size_3D([ map @{$_->used_vertices}, grep $_, @meshes ]) ]; + # we also align object after transformations so that we only work with positive coordinates + # and the assumption that bounding_box === size works + my $bb = Slic3r::Geometry::BoundingBox->new_from_points_3D([ map @{$_->used_vertices}, grep $_, @meshes ]); + my @align2 = map -$bb->extents->[$_][MIN], (X,Y,Z); + $_->move(@align2) for grep $_, @meshes; # initialize print object push @{$self->objects}, Slic3r::Print::Object->new( @@ -144,10 +148,10 @@ sub add_model { meshes => [ @meshes ], copies => [ $object->instances - ? (map [ scale($_->offset->[X] - $align[X]), scale($_->offset->[Y] - $align[Y]) ], @{$object->instances}) + ? (map [ scale($_->offset->[X] - $align[X]) - $align2[X], scale($_->offset->[Y] - $align[Y]) - $align2[Y] ], @{$object->instances}) : [0,0], ], - size => $size, + size => $bb->size, # transformed size input_file => $object->input_file, layer_height_ranges => $object->layer_height_ranges, ); diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index 46443cc6..31921499 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -16,7 +16,7 @@ my %cuboids = ( ); sub model { - my ($model_name) = @_; + my ($model_name, %params) = @_; my ($vertices, $facets); if ($cuboids{$model_name}) { @@ -32,7 +32,10 @@ sub model { my $model = Slic3r::Model->new; my $object = $model->add_object(vertices => $vertices); $object->add_volume(facets => $facets); - $object->add_instance(offset => [0,0]); + $object->add_instance( + offset => [0,0], + rotation => $params{rotation}, + ); return $model; } @@ -46,7 +49,7 @@ sub init_print { my $print = Slic3r::Print->new(config => $config); $model_name = [$model_name] if ref($model_name) ne 'ARRAY'; - $print->add_model(model($_)) for @$model_name; + $print->add_model(model($_, %params)) for @$model_name; $print->validate; return $print; diff --git a/t/print.t b/t/print.t new file mode 100644 index 00000000..485ad12b --- /dev/null +++ b/t/print.t @@ -0,0 +1,20 @@ +use Test::More tests => 1; +use strict; +use warnings; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; +} + +use List::Util qw(first); +use Slic3r; +use Slic3r::Test; + +{ + my $print = Slic3r::Test::init_print('20mm_cube', rotation => 45); + ok !(first { $_ < 0 } map @$_, map @{$_->used_vertices}, grep $_, map @{$_->meshes}, @{$print->objects}), + "object is still in positive coordinate space even after rotation"; +} + +__END__ From 7bf308c08fec43d45649f0a20787372ba046e9a6 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 15 Jun 2013 15:50:02 +0200 Subject: [PATCH 174/179] Fix one more centering problem caused by wrong bounding box implementation --- lib/Slic3r/GUI/Plater.pm | 11 ++++++++--- lib/Slic3r/Geometry/BoundingBox.pm | 15 ++------------- lib/Slic3r/Polyline.pm | 7 ++++--- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 999a1868..b34efa82 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1067,7 +1067,7 @@ sub OnDropFiles { package Slic3r::GUI::Plater::Object; use Moo; -use Math::ConvexHull::MonotoneChain qw(convex_hull); +use Math::ConvexHull::MonotoneChain qw(); use Slic3r::Geometry qw(X Y Z MIN MAX deg2rad); has 'name' => (is => 'rw', required => 1); @@ -1075,6 +1075,7 @@ has 'input_file' => (is => 'rw', required => 1); has 'input_file_object_id' => (is => 'rw'); # undef means keep model object has 'model_object' => (is => 'rw', required => 1, trigger => 1); has 'bounding_box' => (is => 'rw'); # 3D bb of original object (aligned to origin) with no rotation or scaling +has 'convex_hull' => (is => 'rw'); # 2D convex hull of original object (aligned to origin) with no rotation or scaling has 'scale' => (is => 'rw', default => sub { 1 }, trigger => \&_transform_thumbnail); has 'rotate' => (is => 'rw', default => sub { 0 }, trigger => \&_transform_thumbnail); # around object center point has 'instances' => (is => 'rw', default => sub { [] }); # upward Y axis @@ -1138,10 +1139,11 @@ sub make_thumbnail { my $self = shift; my $mesh = $self->model_object->mesh; # $self->model_object is already aligned to origin + $self->convex_hull(Slic3r::Polygon->new(Math::ConvexHull::MonotoneChain::convex_hull($mesh->vertices))); my $thumbnail = Slic3r::ExPolygon::Collection->new( expolygons => (@{$mesh->facets} <= 5000) ? $mesh->horizontal_projection - : [ Slic3r::ExPolygon->new(convex_hull($mesh->vertices)) ], + : [ Slic3r::ExPolygon->new($self->convex_hull) ], ); # only simplify expolygons larger than the threshold @@ -1168,7 +1170,10 @@ sub _transform_thumbnail { # bounding box with applied rotation and scaling sub transformed_bounding_box { my $self = shift; - return $self->_apply_transform($self->bounding_box); + + my $bb = Slic3r::Geometry::BoundingBox->new_from_points($self->_apply_transform($self->convex_hull)); + $bb->extents->[Z] = $self->bounding_box->clone->extents->[Z]; + return $bb; } sub _apply_transform { diff --git a/lib/Slic3r/Geometry/BoundingBox.pm b/lib/Slic3r/Geometry/BoundingBox.pm index 3cd019f0..c2d27a1f 100644 --- a/lib/Slic3r/Geometry/BoundingBox.pm +++ b/lib/Slic3r/Geometry/BoundingBox.pm @@ -39,20 +39,9 @@ sub polygon { return Slic3r::Polygon->new_from_bounding_box($self->bb); } +# note to $self sub rotate { - my $self = shift; - my ($angle, $center) = @_; - - # rotate the 2D bounding box polygon and leave Z unaltered - my $bb_p = $self->polygon; - $bb_p->rotate($angle, $center); - my @bb = $bb_p->bounding_box; - $self->extents->[X][MIN] = $bb[X1]; - $self->extents->[Y][MIN] = $bb[Y1]; - $self->extents->[X][MAX] = $bb[X2]; - $self->extents->[Y][MAX] = $bb[Y2]; - - $self; + die "Rotating an axis-aligned bounding box doesn't make any sense"; } sub scale { diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm index 6646d1aa..2de79893 100644 --- a/lib/Slic3r/Polyline.pm +++ b/lib/Slic3r/Polyline.pm @@ -156,11 +156,12 @@ sub translate { sub scale { my $self = shift; my ($factor) = @_; - return if $factor == 1; # transform point coordinates - foreach my $point (@$self) { - $point->[$_] *= $factor for X,Y; + if ($factor != 1) { + foreach my $point (@$self) { + $point->[$_] *= $factor for X,Y; + } } return $self; } From 8b6afb61d911b028b87ee133ad1b2248f1811468 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 15 Jun 2013 17:17:48 +0200 Subject: [PATCH 175/179] Fix little error in tests --- lib/Slic3r/Test.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index 31921499..f4942d0c 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -34,7 +34,7 @@ sub model { $object->add_volume(facets => $facets); $object->add_instance( offset => [0,0], - rotation => $params{rotation}, + rotation => $params{rotation} // 0, ); return $model; } From cb75b1e47bc81656a5e4818be615c27e4cc15ace Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 15 Jun 2013 19:15:24 +0200 Subject: [PATCH 176/179] Fix plater crash on threaded perls --- lib/Slic3r/GUI/Plater.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index b34efa82..8ce65948 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1097,6 +1097,7 @@ sub _trigger_model_object { $self->bounding_box($self->model_object->bounding_box); my $mesh = $self->model_object->mesh; + $self->convex_hull(Slic3r::Polygon->new(Math::ConvexHull::MonotoneChain::convex_hull($mesh->used_vertices))); $self->facets(scalar @{$mesh->facets}); $self->vertices(scalar @{$mesh->vertices}); @@ -1139,7 +1140,6 @@ sub make_thumbnail { my $self = shift; my $mesh = $self->model_object->mesh; # $self->model_object is already aligned to origin - $self->convex_hull(Slic3r::Polygon->new(Math::ConvexHull::MonotoneChain::convex_hull($mesh->vertices))); my $thumbnail = Slic3r::ExPolygon::Collection->new( expolygons => (@{$mesh->facets} <= 5000) ? $mesh->horizontal_projection From 8b48d79563d4a504e6d7ee0c8e4cc93eb8094190 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 15 Jun 2013 21:10:04 +0200 Subject: [PATCH 177/179] Improve zigzag gap fill. #1234 --- lib/Slic3r/Layer/Region.pm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 472c2da5..a9dbc3bd 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -315,8 +315,12 @@ sub _fill_gaps { 1, )}; + # medial axis-based gap fill should benefit from detection of larger gaps too, so + # we could try with 1.5*$w for example, but that doesn't work well for zigzag fill + # because it tends to create very sparse points along the gap when the infill direction + # is not parallel to the gap (1.5*$w thus may only work well with a straight line) my $w = $self->perimeter_flow->width; - my @widths = (1.5 * $w, $w, 0.4 * $w); # worth trying 0.2 too? + my @widths = ($w, 0.4 * $w); # worth trying 0.2 too? foreach my $width (@widths) { my $flow = $self->perimeter_flow->clone(width => $width); From d0eac88ff9586b17dcc1766874f69dbd7e8c534f Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 15 Jun 2013 21:27:36 +0200 Subject: [PATCH 178/179] Reduce vibrations when doing gap fill --- lib/Slic3r/Layer/Region.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index a9dbc3bd..1e6b7c16 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -361,7 +361,7 @@ sub _fill_gaps { push @{ $self->thin_fills }, map { - $_->polyline->simplify($flow->scaled_width / 3); + $_->simplify($flow->scaled_width/3); $_->pack; } map Slic3r::ExtrusionPath->new( From 2993a1659e10f7e2c05abf7ab5e0032f96e5ea7e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 16 Jun 2013 10:22:22 +0200 Subject: [PATCH 179/179] Bump version number --- lib/Slic3r.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 78ac6e5b..d4caf4b4 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -7,7 +7,7 @@ use strict; use warnings; require v5.10; -our $VERSION = "0.9.10"; +our $VERSION = "0.9.11-dev"; our $debug = 0; sub debugf {