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/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..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; @@ -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/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index 2fa46fe4..551e464c 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..adc1e74c 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) { @@ -38,26 +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 as needed - if ($rotate[0]) { - $expolygon->rotate(@rotate); - $expolygon->translate(@shift); - } + # rotate points + $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]} ]; - 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, @$_) ], + map [ Slic3r::Geometry::move_points($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/GCode.pm b/lib/Slic3r/GCode.pm index 92190cf7..d2a667ef 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,19 +358,19 @@ 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); } # 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,22 +382,25 @@ 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 { - 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"); } } 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/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/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/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); } } diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index 0d2d9df4..4c1e23b9 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,11 +115,6 @@ sub distance_between_points { return sqrt((($p1->[X] - $p2->[X])**2) + ($p1->[Y] - $p2->[Y])**2); } -sub comparable_distance_between_points { - my ($p1, $p2) = @_; - return (($p1->[X] - $p2->[X])**2) + (($p1->[Y] - $p2->[Y])**2); -} - sub point_line_distance { my ($point, $line) = @_; return distance_between_points($point, $line->[A]) @@ -248,14 +243,27 @@ 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 = comparable_distance_between_points($point, $points->[$i]); - if (!defined $distance || $d < $distance) { - $nearest_point_index = $i; - $distance = $d; - return $i 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; } @@ -671,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/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index 68e3d7c0..0c2cbfe3 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]); + my ($polygons, $width) = @_;use XXX; YYY + 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..fa8b3ad3 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 ], @@ -139,7 +136,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) { @@ -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 diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 2cf12bf6..189b665b 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; @@ -714,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; @@ -794,11 +794,11 @@ 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 $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 { @@ -940,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; }; @@ -998,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 @@ -1007,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 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( 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'; } 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]);