From 038caddcdae26a44bb0b68c952570319cd0ab42c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 13 Nov 2011 18:14:02 +0100 Subject: [PATCH] New fill types (hilbertcurve, archimedeanchords, octagramspiral) and ability to use different patterns for solid layers. #20 --- Build.PL | 1 + README.markdown | 11 +++- lib/Slic3r.pm | 3 +- lib/Slic3r/Config.pm | 20 ++++++-- lib/Slic3r/ExPolygon.pm | 77 ++++++++++++++++++++++++---- lib/Slic3r/Fill.pm | 20 ++++++-- lib/Slic3r/Fill/ArchimedeanChords.pm | 7 +++ lib/Slic3r/Fill/Base.pm | 11 ++-- lib/Slic3r/Fill/Flowsnake.pm | 18 +++++++ lib/Slic3r/Fill/HilbertCurve.pm | 7 +++ lib/Slic3r/Fill/OctagramSpiral.pm | 9 ++++ lib/Slic3r/Fill/PlanePath.pm | 62 ++++++++++++++++++++++ lib/Slic3r/Fill/Rectilinear.pm | 17 ++---- lib/Slic3r/Fill/Rectilinear2.pm | 10 +--- lib/Slic3r/GUI/OptionsGroup.pm | 10 +++- lib/Slic3r/GUI/SkeinPanel.pm | 2 +- lib/Slic3r/Geometry.pm | 37 +++---------- lib/Slic3r/Line.pm | 12 ++++- lib/Slic3r/Polygon.pm | 29 ++++++++++- lib/Slic3r/Polyline.pm | 49 +++++++++++++++++- slic3r.pl | 5 +- t/polyclip.t | 67 ++++++++++++++++++++---- 22 files changed, 391 insertions(+), 93 deletions(-) create mode 100644 lib/Slic3r/Fill/ArchimedeanChords.pm create mode 100644 lib/Slic3r/Fill/Flowsnake.pm create mode 100644 lib/Slic3r/Fill/HilbertCurve.pm create mode 100644 lib/Slic3r/Fill/OctagramSpiral.pm create mode 100644 lib/Slic3r/Fill/PlanePath.pm diff --git a/Build.PL b/Build.PL index 288e152ff..01314e924 100644 --- a/Build.PL +++ b/Build.PL @@ -11,6 +11,7 @@ my $build = Module::Build->new( 'Getopt::Long' => '0', 'Math::Clipper' => '1.02', 'Math::ConvexHull' => '1.0.4', + 'Math::PlanePath' => '53', 'Moo' => '0', 'Time::HiRes' => '0', 'XXX' => '0', diff --git a/README.markdown b/README.markdown index c1e9a50a5..94972b5f2 100644 --- a/README.markdown +++ b/README.markdown @@ -94,9 +94,11 @@ The author is Alessandro Ranellucci (me). Use relative distances for extrusion in GCODE output --z-offset Additional height in mm to add to vertical coordinates (+/-, default: 0) + --gcode-arcs Use G2/G3 commands for native arcs (experimental, not supported + by all firmwares) Filament options: - --filament-diameter Diameter of your raw filament (default: 3) + --filament-diameter Diameter in mm of your raw filament (default: 3) --filament-packing-density Ratio of the extruded volume over volume pushed into the extruder (default: 1) @@ -123,7 +125,9 @@ The author is Alessandro Ranellucci (me). (range: 1+, default: 3) --fill-density Infill density (range: 0-1, default: 0.4) --fill-angle Infill angle in degrees (range: 0-90, default: 0) - --start-gcode Load initial gcode from the supplied file. This will overwrite + --fill-pattern Pattern to use to fill non-solid layers (default: rectilinear) + --solid-fill-pattern Pattern to use to fill solid layers (default: rectilinear) + --start-gcode Load initial gcode from the supplied file. This will overwrite the default command (home all axes [G28]). --end-gcode Load final gcode from the supplied file. This will overwrite the default commands (turn off temperature [M104 S0], @@ -138,6 +142,7 @@ The author is Alessandro Ranellucci (me). compensating retraction (default: 0) --retract-before-travel Only retract before travel moves of this length (default: 2) + --retract-lift Lift Z by the given distance in mm when retracting (default: 0) Skirt options: --skirts Number of skirts to draw (default: 1) @@ -152,3 +157,5 @@ The author is Alessandro Ranellucci (me). --duplicate-distance Distance in mm between copies (default: 6) + + diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 380da3d94..790e5c5cf 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -60,7 +60,8 @@ our $flow_width; our $perimeter_offsets = 3; our $solid_layers = 3; our $bridge_overlap = 3; # mm -our $fill_type = 'rectilinear'; +our $fill_pattern = 'rectilinear'; +our $solid_fill_pattern = 'rectilinear'; our $fill_density = 0.4; # 1 = 100% our $fill_angle = 0; our $start_gcode = "G28 ; home all axes"; diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 632312bbf..1c20ca59b 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -78,6 +78,16 @@ our $Options = { label => 'Solid layers', type => 'i', }, + 'fill_pattern' => { + label => 'Fill pattern', + type => 'select', + values => [qw(rectilinear hilbertcurve archimedeanchords octagramspiral)], + }, + 'solid_fill_pattern' => { + label => 'Solid fill pattern', + type => 'select', + values => [qw(rectilinear hilbertcurve archimedeanchords octagramspiral)], + }, 'fill_density' => { label => 'Fill density', type => 'f', @@ -266,9 +276,13 @@ sub validate { $Slic3r::print_center = [ split /,/, $Slic3r::print_center ] if !ref $Slic3r::print_center; - # --fill-type - die "Invalid value for --fill-type\n" - if !exists $Slic3r::Fill::FillTypes{$Slic3r::fill_type}; + # --fill-pattern + die "Invalid value for --fill-pattern\n" + if !exists $Slic3r::Fill::FillTypes{$Slic3r::fill_pattern}; + + # --solid-fill-pattern + die "Invalid value for --solid-fill-pattern\n" + if !exists $Slic3r::Fill::FillTypes{$Slic3r::solid_fill_pattern}; # --fill-density die "Invalid value for --fill-density\n" diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index 593925952..7abaf6b48 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -5,6 +5,7 @@ use warnings; # an ExPolygon is a polygon with holes use Math::Clipper qw(CT_UNION PFT_NONZERO JT_MITER); +use Slic3r::Geometry qw(point_in_polygon X Y A B); use Slic3r::Geometry::Clipper qw(union_ex); # the constructor accepts an array of polygons @@ -24,14 +25,6 @@ sub new { $self; } -# this class method accepts an array of polygons and returns -# an array of expolygons with the right holes applied to the -# right contours -sub make { - my $class = shift; - return @{ union_ex(\@_) }; -} - sub contour { my $self = shift; return $self->[0]; @@ -60,7 +53,73 @@ sub offset { my $offsets = Math::Clipper::offset($self, $distance, $scale, $joinType, $miterLimit); # apply holes to the right contours - return (ref $self)->make(@$offsets); + return @{ union_ex($offsets) }; +} + +sub encloses_point { + my $self = shift; + my ($point) = @_; + return $self->contour->encloses_point($point) + && (!grep($_->encloses_point($point), $self->holes) + || grep($_->point_on_segment($point), $self->holes)); +} + +sub point_on_segment { + my $self = shift; + my ($point) = @_; + for (@$self) { + my $line = $_->point_on_segment($point); + return $line if $line; + } + return undef; +} + +sub bounding_box { + my $self = shift; + return Slic3r::Geometry::bounding_box($self->contour); +} + +sub clip_line { + my $self = shift; + my ($line) = @_; + $line = Slic3r::Line->cast($line); + + my @intersections = grep $_, map $_->intersection($line, 1), map $_->lines, @$self; + my @dir = ( + $line->[B][X] <=> $line->[A][X], + $line->[B][Y] <=> $line->[A][Y], + ); + + @intersections = sort { + (($a->[X] <=> $b->[X]) == $dir[X]) && (($a->[Y] <=> $b->[Y]) == $dir[Y]) ? 1 : -1 + } @intersections, @$line; + + shift @intersections if $intersections[0]->coincides_with($intersections[1]); + pop @intersections if $intersections[-1]->coincides_with($intersections[-2]); + + shift @intersections + if !$self->encloses_point($intersections[0]) + && !$self->point_on_segment($intersections[0]); + + my @lines = (); + while (@intersections) { + # skip tangent points + my @points = splice @intersections, 0, 2; + next if !$points[1]; + next if $points[0]->coincides_with($points[1]); + push @lines, [ @points ]; + } + return [@lines]; +} + +sub translate { + my $self = shift; + $_->translate(@_) for @$self; +} + +sub rotate { + my $self = shift; + $_->rotate(@_) for @$self; } 1; diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index 92e4d60af..35bd4e94e 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -1,7 +1,11 @@ package Slic3r::Fill; use Moo; +use Slic3r::Fill::ArchimedeanChords; use Slic3r::Fill::Base; +use Slic3r::Fill::Flowsnake; +use Slic3r::Fill::HilbertCurve; +use Slic3r::Fill::OctagramSpiral; use Slic3r::Fill::Rectilinear; use Slic3r::Fill::Rectilinear2; @@ -11,14 +15,18 @@ has 'print' => (is => 'ro', required => 1); has 'fillers' => (is => 'rw', default => sub { {} }); our %FillTypes = ( - rectilinear => 'Slic3r::Fill::Rectilinear', - rectilinear2 => 'Slic3r::Fill::Rectilinear2', + archimedeanchords => 'Slic3r::Fill::ArchimedeanChords', + rectilinear => 'Slic3r::Fill::Rectilinear', + rectilinear2 => 'Slic3r::Fill::Rectilinear2', + flowsnake => 'Slic3r::Fill::Flowsnake', + octagramspiral => 'Slic3r::Fill::OctagramSpiral', + hilbertcurve => 'Slic3r::Fill::HilbertCurve', ); sub BUILD { my $self = shift; $self->fillers->{$_} ||= $FillTypes{$_}->new(print => $self->print) - for ('rectilinear', $Slic3r::fill_type); + for ('rectilinear', $Slic3r::fill_pattern, $Slic3r::solid_fill_pattern); } sub make_fill { @@ -38,13 +46,15 @@ sub make_fill { SURFACE: foreach my $surface (@$surfaces) { Slic3r::debugf " Processing surface %s:\n", $surface->id; - my $filler = $Slic3r::fill_type; + my $filler = $Slic3r::fill_pattern; my $density = $Slic3r::fill_density; # force 100% density and rectilinear fill for external surfaces if ($surface->surface_type ne 'internal') { $density = 1; - $filler = 'rectilinear'; + $filler = $surface->isa('Slic3r::Surface::Bridge') + ? 'rectilinear' + : $Slic3r::solid_fill_pattern; } else { next SURFACE unless $density > 0; } diff --git a/lib/Slic3r/Fill/ArchimedeanChords.pm b/lib/Slic3r/Fill/ArchimedeanChords.pm new file mode 100644 index 000000000..3c71f0e36 --- /dev/null +++ b/lib/Slic3r/Fill/ArchimedeanChords.pm @@ -0,0 +1,7 @@ +package Slic3r::Fill::ArchimedeanChords; +use Moo; + +extends 'Slic3r::Fill::PlanePath'; +use Math::PlanePath::ArchimedeanChords; + +1; diff --git a/lib/Slic3r/Fill/Base.pm b/lib/Slic3r/Fill/Base.pm index 30b49e6e9..45a8e49cc 100644 --- a/lib/Slic3r/Fill/Base.pm +++ b/lib/Slic3r/Fill/Base.pm @@ -35,14 +35,15 @@ sub infill_direction { sub rotate_points { my $self = shift; - my ($polygons, $rotate_vector) = @_; + my ($expolygon, $rotate_vector) = @_; my @rotate = @{$rotate_vector->[0]}; my @shift = @{$rotate_vector->[1]}; - # rotate surface as needed - @$polygons = map [ Slic3r::Geometry::move_points(\@shift, @$_) ], - map [ Slic3r::Geometry::rotate_points(@rotate, @$_) ], @$polygons if $rotate[0]; - + # rotate points as needed + if ($rotate[0]) { + $expolygon->rotate(@rotate); + $expolygon->translate(@shift); + } } sub rotate_points_back { diff --git a/lib/Slic3r/Fill/Flowsnake.pm b/lib/Slic3r/Fill/Flowsnake.pm new file mode 100644 index 000000000..79d7a1530 --- /dev/null +++ b/lib/Slic3r/Fill/Flowsnake.pm @@ -0,0 +1,18 @@ +package Slic3r::Fill::Flowsnake; +use Moo; + +extends 'Slic3r::Fill::PlanePath'; + +use Math::PlanePath::Flowsnake; +use Slic3r::Geometry qw(X X1 X2); + +# Sorry, this fill is currently broken. + +sub process_polyline { + my $self = shift; + my ($polyline, $bounding_box) = @_; + + $_->[X] += ($bounding_box->[X1] + $bounding_box->[X2]/2) for @{$polyline->points}; +} + +1; diff --git a/lib/Slic3r/Fill/HilbertCurve.pm b/lib/Slic3r/Fill/HilbertCurve.pm new file mode 100644 index 000000000..eff1a44ac --- /dev/null +++ b/lib/Slic3r/Fill/HilbertCurve.pm @@ -0,0 +1,7 @@ +package Slic3r::Fill::HilbertCurve; +use Moo; + +extends 'Slic3r::Fill::PlanePath'; +use Math::PlanePath::HilbertCurve; + +1; diff --git a/lib/Slic3r/Fill/OctagramSpiral.pm b/lib/Slic3r/Fill/OctagramSpiral.pm new file mode 100644 index 000000000..9c1272444 --- /dev/null +++ b/lib/Slic3r/Fill/OctagramSpiral.pm @@ -0,0 +1,9 @@ +package Slic3r::Fill::OctagramSpiral; +use Moo; + +extends 'Slic3r::Fill::PlanePath'; +use Math::PlanePath::OctagramSpiral; + +sub multiplier () { sqrt(2) } + +1; diff --git a/lib/Slic3r/Fill/PlanePath.pm b/lib/Slic3r/Fill/PlanePath.pm new file mode 100644 index 000000000..45a00ceca --- /dev/null +++ b/lib/Slic3r/Fill/PlanePath.pm @@ -0,0 +1,62 @@ +package Slic3r::Fill::PlanePath; +use Moo; + +extends 'Slic3r::Fill::Base'; + +use Slic3r::Geometry qw(bounding_box); +use XXX; + +sub multiplier () { 1 } + +sub get_n { + my $self = shift; + my ($path, $bounding_box) = @_; + + my ($n_lo, $n_hi) = $path->rect_to_n_range(@$bounding_box); + return ($n_lo .. $n_hi); +} + +sub process_polyline {} + +sub fill_surface { + my $self = shift; + my ($surface, %params) = @_; + + # rotate polygons + my $expolygon = $surface->expolygon; + my $rotate_vector = $self->infill_direction($surface); + $self->rotate_points($expolygon, $rotate_vector); + + my $distance_between_lines = $Slic3r::flow_width / $Slic3r::resolution / $params{density} * $self->multiplier; + my $bounding_box = [ bounding_box(map @$_, $expolygon) ]; + + (ref $self) =~ /::([^:]+)$/; + my $path = "Math::PlanePath::$1"->new; + my @n = $self->get_n($path, [map +($_ / $distance_between_lines), @$bounding_box]); + + my $polyline = Slic3r::Polyline->cast([ + map [ map {$_*$distance_between_lines} $path->n_to_xy($_) ], @n, + ]); + return [] if !@{$polyline->points}; + + $self->process_polyline($polyline, $bounding_box); + + my @paths = ($polyline->clip_with_expolygon($expolygon)); + + if (0) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output(undef, "fill.svg", + polygons => $expolygon, + polylines => [map $_->p, @paths], + ); + } + + @paths = map $_->p, @paths; + + # paths must be rotated back + $self->rotate_points_back(\@paths, $rotate_vector); + + return @paths; +} + +1; diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm index ff8b7bba4..28fb884b7 100644 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ b/lib/Slic3r/Fill/Rectilinear.pm @@ -3,11 +3,7 @@ use Moo; extends 'Slic3r::Fill::Base'; -use constant X1 => 0; -use constant Y1 => 1; -use constant X2 => 2; -use constant Y2 => 3; - +use Slic3r::Geometry qw(X1 Y1 X2 Y2); use XXX; sub fill_surface { @@ -15,21 +11,18 @@ sub fill_surface { my ($surface, %params) = @_; # rotate polygons so that we can work with vertical lines here - my $polygons = [ $surface->p ]; + my $expolygon = $surface->expolygon; my $rotate_vector = $self->infill_direction($surface); - $self->rotate_points($polygons, $rotate_vector); - - my $bounding_box = [ Slic3r::Geometry::bounding_box(map @$_, $polygons) ]; - my $surface_width = $bounding_box->[X2] - $bounding_box->[X1]; - my $surface_height = $bounding_box->[Y2] - $bounding_box->[Y1]; + $self->rotate_points($expolygon, $rotate_vector); + my $bounding_box = [ $expolygon->bounding_box ]; my $distance_between_lines = $Slic3r::flow_width / $Slic3r::resolution / $params{density}; my @paths = (); my $x = $bounding_box->[X1]; while ($x < $bounding_box->[X2]) { my $vertical_line = [ [$x, $bounding_box->[Y2]], [$x, $bounding_box->[Y1]] ]; - push @paths, @{ Slic3r::Geometry::clip_segment_complex_polygon($vertical_line, $polygons) }; + push @paths, @{ $expolygon->clip_line($vertical_line) }; $x += int($distance_between_lines); } diff --git a/lib/Slic3r/Fill/Rectilinear2.pm b/lib/Slic3r/Fill/Rectilinear2.pm index aee115a72..f2e210bf7 100644 --- a/lib/Slic3r/Fill/Rectilinear2.pm +++ b/lib/Slic3r/Fill/Rectilinear2.pm @@ -3,15 +3,7 @@ use Moo; extends 'Slic3r::Fill::Base'; -use constant X1 => 0; -use constant Y1 => 1; -use constant X2 => 2; -use constant Y2 => 3; -use constant A => 0; -use constant B => 1; -use constant X => 0; -use constant Y => 1; - +use Slic3r::Geometry qw(X1 Y1 X2 Y2 A B X Y); use XXX; sub fill_surface { diff --git a/lib/Slic3r/GUI/OptionsGroup.pm b/lib/Slic3r/GUI/OptionsGroup.pm index b5ad7797d..98fa53ed9 100644 --- a/lib/Slic3r/GUI/OptionsGroup.pm +++ b/lib/Slic3r/GUI/OptionsGroup.pm @@ -3,7 +3,7 @@ use strict; use warnings; use Wx qw(:sizer); -use Wx::Event qw(EVT_TEXT EVT_CHECKBOX); +use Wx::Event qw(EVT_TEXT EVT_CHECKBOX EVT_CHOICE); use base 'Wx::StaticBoxSizer'; # not very elegant, but this solution is temporary waiting for a better GUI @@ -55,6 +55,14 @@ sub new { $x_field->SetValue($value->[0]); $y_field->SetValue($value->[1]); }; + } elsif ($opt->{type} eq 'select') { + $field = Wx::Choice->new($parent, -1, Wx::wxDefaultPosition, Wx::wxDefaultSize, $opt->{values}); + EVT_CHOICE($parent, $field, sub { Slic3r::Config->set($opt_key, $opt->{values}[$field->GetSelection]) }); + push @reload_callbacks, sub { + my $value = Slic3r::Config->get($opt_key); + $field->SetSelection(grep $opt->{values}[$_] eq $value, 0..$#{$opt->{values}}); + }; + $reload_callbacks[-1]->(); } else { die "Unsupported option type: " . $opt->{type}; } diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index ab338498d..878495da3 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -33,7 +33,7 @@ sub new { ), print => Slic3r::GUI::OptionsGroup->new($self, title => 'Print settings', - options => [qw(perimeter_offsets solid_layers fill_density fill_angle)], + options => [qw(perimeter_offsets solid_layers fill_density fill_angle fill_pattern solid_fill_pattern)], ), retract => Slic3r::GUI::OptionsGroup->new($self, title => 'Retraction', diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index 39fdc0937..98ad804e2 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -5,7 +5,7 @@ use warnings; require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw( - PI X Y Z A B epsilon slope line_atan lines_parallel three_points_aligned + PI X Y Z A B X1 Y1 X2 Y2 epsilon slope line_atan lines_parallel line_point_belongs_to_segment points_coincide distance_between_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 @@ -14,7 +14,7 @@ our @EXPORT_OK = qw( rotate_points move_points remove_coinciding_points clip_segment_polygon sum_vectors multiply_vector subtract_vectors dot perp polygon_points_visibility line_intersection bounding_box bounding_box_intersect - clip_segment_complex_polygon longest_segment angle3points + longest_segment angle3points three_points_aligned polyline_remove_parallel_continuous_edges polyline_remove_acute_vertices polygon_remove_acute_vertices polygon_remove_parallel_continuous_edges shortest_path collinear @@ -29,6 +29,10 @@ use constant B => 1; use constant X => 0; use constant Y => 1; use constant Z => 2; +use constant X1 => 0; +use constant Y1 => 1; +use constant X2 => 2; +use constant Y2 => 3; our $parallel_degrees_limit = abs(deg2rad(3)); our $epsilon = 1E-4; @@ -110,6 +114,7 @@ sub midpoint { return [ ($line->[B][X] + $line->[A][X]) / 2, ($line->[B][Y] + $line->[A][Y]) / 2 ]; } +# this will check whether a point is in a polygon regardless of polygon orientation sub point_in_polygon { my ($point, $polygon) = @_; @@ -311,7 +316,7 @@ sub rotate_points { sub move_points { my ($shift, @points) = @_; - return map [ $shift->[X] + $_->[X], $shift->[Y] + $_->[Y] ], @points; + return map Slic3r::Point->new($shift->[X] + $_->[X], $shift->[Y] + $_->[Y]), @points; } # preserves order @@ -558,32 +563,6 @@ sub bounding_box_intersect { return 1; } -sub clip_segment_complex_polygon { - my ($line, $polygons) = @_; - - my @intersections = grep $_, map line_intersection($line, $_, 1), - map polygon_lines($_), @$polygons or return (); - - # this is not very elegant, however it works - @intersections = sort { sprintf("%020f,%020f", @$a) cmp sprintf("%020f,%020f", @$b) } @intersections; - - shift(@intersections) if !grep(point_in_polygon($intersections[0], $_), @$polygons) - && !grep(polygon_segment_having_point($_, $intersections[0]), @$polygons); - - # defensive programming - ###die "Invalid intersections" if @intersections % 2 != 0; - - my @lines = (); - while (@intersections) { - # skip tangent points - my @points = map shift @intersections, 1..2; - next if !$points[1]; - next if points_coincide(@points); - push @lines, [ @points ]; - } - return [@lines]; -} - sub angle3points { my ($p1, $p2, $p3) = @_; # p1 is the center diff --git a/lib/Slic3r/Line.pm b/lib/Slic3r/Line.pm index 0e192a400..6d395e50b 100644 --- a/lib/Slic3r/Line.pm +++ b/lib/Slic3r/Line.pm @@ -8,18 +8,26 @@ sub new { my $class = shift; my $self; if (@_ == 2) { - $self = [ map Slic3r::Point->new($_), @_ ]; + $self = [ @_ ]; } elsif (ref $_[0] eq 'ARRAY') { - $self = [ map Slic3r::Point->new($_), $_[0][0], $_[0][1] ]; + $self = [ $_[0][0], $_[0][1] ]; } elsif ($_[0]->isa(__PACKAGE__)) { return $_[0]; } else { die "Invalid argument for $class->new"; } bless $self, $class; + bless $_, 'Slic3r::Point' for @$self; return $self; } +sub cast { + my $class = shift; + my ($line) = @_; + return $line if ref $line eq __PACKAGE__; + return $class->new($line); +} + sub a { $_[0][0] } sub b { $_[0][1] } diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm index 734a8c9be..47e8ce51d 100644 --- a/lib/Slic3r/Polygon.pm +++ b/lib/Slic3r/Polygon.pm @@ -7,7 +7,8 @@ use warnings; # as a Slic3r::Polyline::Closed you're right. I plan to # ditch the latter and port everything to this class. -use Slic3r::Geometry qw(polygon_lines polygon_remove_parallel_continuous_edges); +use Slic3r::Geometry qw(polygon_lines polygon_remove_parallel_continuous_edges + polygon_segment_having_point point_in_polygon move_points rotate_points); # the constructor accepts an array(ref) of points sub new { @@ -19,8 +20,8 @@ sub new { $self = [ @_ ]; } - @$self = map Slic3r::Point->cast($_), @$self; bless $self, $class; + bless $_, 'Slic3r::Point' for @$self; $self; } @@ -40,4 +41,28 @@ sub cleanup { polygon_remove_parallel_continuous_edges($self); } +sub point_on_segment { + my $self = shift; + my ($point) = @_; + return polygon_segment_having_point($self, $point); +} + +sub encloses_point { + my $self = shift; + my ($point) = @_; + return point_in_polygon($point, $self); +} + +sub translate { + my $self = shift; + my ($x, $y) = @_; + @$self = move_points([$x, $y], @$self); +} + +sub rotate { + my $self = shift; + my ($angle, $center) = @_; + @$self = rotate_points($angle, $center, @$self); +} + 1; \ No newline at end of file diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm index e7e6dd27b..f85a45ba7 100644 --- a/lib/Slic3r/Polyline.pm +++ b/lib/Slic3r/Polyline.pm @@ -2,8 +2,8 @@ package Slic3r::Polyline; use Moo; use Math::Clipper qw(); -use Slic3r::Geometry qw(polyline_remove_parallel_continuous_edges polyline_remove_acute_vertices - polygon_remove_acute_vertices polygon_remove_parallel_continuous_edges); +use Slic3r::Geometry qw(A B polyline_remove_parallel_continuous_edges polyline_remove_acute_vertices + polygon_remove_acute_vertices polygon_remove_parallel_continuous_edges move_points); use Sub::Quote; use XXX; @@ -118,4 +118,49 @@ sub has_segment { return 0; } +sub clip_with_expolygon { + my $self = shift; + my ($expolygon) = @_; + + my @polylines = (); + my $current_polyline = []; + foreach my $line ($self->lines) { + my ($first_line, @other_lines) = @{ $expolygon->clip_line($line) }; + next unless $first_line; + + if (!@$current_polyline) { + push @$current_polyline, @$first_line; + } elsif ($first_line->[A]->coincides_with($current_polyline->[-1])) { + push @$current_polyline, $first_line->[B]; + } else { + push @polylines, $current_polyline; + $current_polyline = [ @$first_line ]; + } + + foreach my $other_line (@other_lines) { + if (@$current_polyline) { + push @polylines, $current_polyline; + $current_polyline = []; + } + push @polylines, [ @$other_line ]; + } + } + if (@$current_polyline) { + push @polylines, $current_polyline; + } + + return map Slic3r::Polyline->cast($_), @polylines; +} + +sub bounding_box { + my $self = shift; + return Slic3r::Geometry::bounding_box($self->points); +} + +sub translate { + my $self = shift; + my ($x, $y) = @_; + @{$self->points} = move_points([$x, $y], @{$self->points}); +} + 1; diff --git a/slic3r.pl b/slic3r.pl index a711d6f1b..74eb88d46 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -47,7 +47,8 @@ GetOptions( # print options 'perimeters=i' => \$Slic3r::perimeter_offsets, 'solid-layers=i' => \$Slic3r::solid_layers, - 'fill-type=s' => \$Slic3r::fill_type, + 'fill-pattern=s' => \$Slic3r::fill_pattern, + 'solid-fill-pattern=s' => \$Slic3r::solid_fill_pattern, 'fill-density=f' => \$Slic3r::fill_density, 'fill-angle=i' => \$Slic3r::fill_angle, 'start-gcode=s' => \$opt{start_gcode}, @@ -161,6 +162,8 @@ Usage: slic3r.pl [ OPTIONS ] file.stl (range: 1+, default: $Slic3r::solid_layers) --fill-density Infill density (range: 0-1, default: $Slic3r::fill_density) --fill-angle Infill angle in degrees (range: 0-90, default: $Slic3r::fill_angle) + --fill-pattern Pattern to use to fill non-solid layers (default: $Slic3r::fill_pattern) + --solid-fill-pattern Pattern to use to fill solid layers (default: $Slic3r::solid_fill_pattern) --start-gcode Load initial gcode from the supplied file. This will overwrite the default command (home all axes [G28]). --end-gcode Load final gcode from the supplied file. This will overwrite diff --git a/t/polyclip.t b/t/polyclip.t index b7d4d58f6..205f036c8 100644 --- a/t/polyclip.t +++ b/t/polyclip.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 14; +plan tests => 24; BEGIN { use FindBin; @@ -59,11 +59,57 @@ is_deeply $intersection, [ [12, 12], [18, 16] ], 'internal lines are preserved'; [16, 16], [16, 14], ]; - my $intersections = Slic3r::Geometry::clip_segment_complex_polygon($line, [ $square, $hole_in_square ]); - is_deeply $intersections, [ - [ [10, 15], [14, 15] ], - [ [16, 15], [20, 15] ], - ], 'line is clipped to square with hole'; + my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); + is $expolygon->encloses_point([10, 10]), 1, 'corner point is recognized'; + is $expolygon->encloses_point([10, 18]), 1, 'point on contour is recognized'; + is $expolygon->encloses_point([14, 15]), 1, 'point on hole contour is recognized'; + is $expolygon->encloses_point([14, 14]), 1, 'point on hole corner is recognized'; + { + my $intersections = $expolygon->clip_line([ [15,18], [15,15] ]); + is_deeply $intersections, [ + [ [15, 18], [15, 16] ], + ], 'line is clipped to square with hole'; + } + { + my $intersections = $expolygon->clip_line([ [15,15], [15,12] ]); + is_deeply $intersections, [ + [ [15, 14], [15, 12] ], + ], 'line is clipped to square with hole'; + } + { + my $intersections = $expolygon->clip_line([ [12,18], [18,18] ]); + is_deeply $intersections, [ + [ [12,18], [18,18] ], + ], 'line is clipped to square with hole'; + } + { + my $intersections = $expolygon->clip_line($line); + is_deeply $intersections, [ + [ [10, 15], [14, 15] ], + [ [16, 15], [20, 15] ], + ], 'line is clipped to square with hole'; + } + { + my $intersections = $expolygon->clip_line([ reverse @$line ]); + is_deeply $intersections, [ + [ [20, 15], [16, 15] ], + [ [14, 15], [10, 15] ], + ], 'reverse line is clipped to square with hole'; + } + { + my $intersections = $expolygon->clip_line([ [10,18], [20,18] ]); + is_deeply $intersections, [ + [ [10, 18], [20, 18] ], + ], 'tangent line is clipped to square with hole'; + } + { + my $polyline = Slic3r::Polyline->cast([ [5, 18], [25, 18], [25, 15], [15, 15], [15, 12], [12, 12], [12, 5] ]); + is_deeply [ map $_->p, $polyline->clip_with_expolygon($expolygon) ], [ + [ [10, 18], [20, 18] ], + [ [20, 15], [16, 15] ], + [ [15, 14], [15, 12], [12, 12], [12, 10] ], + ], 'polyline is clipped to square with hole'; + } } #========================================================== @@ -93,11 +139,14 @@ is_deeply $intersection, [ [12, 12], [18, 16] ], 'internal lines are preserved'; ]; is is_counter_clockwise($small_circle), 0, "hole is clockwise"; + my $expolygon = Slic3r::ExPolygon->new($large_circle, $small_circle); $line = [ [152.741724,288.086671142818], [152.741724,34.166466971035] ]; - my $intersections = Slic3r::Geometry::clip_segment_complex_polygon($line, [ $large_circle, $small_circle ]); + my $intersections = $expolygon->clip_line($line); is_deeply $intersections, [ - [ [152.741724, 35.166466971035], [152.741724, 108.087543109156] ], - [ [152.741724, 215.178806915206], [152.741724, 288.086671142818] ], + [ [152.741724, 288.086671142818], [152.741724, 215.178806915206], ], + [ [152.741724, 108.087543109156], [152.741724, 35.166466971035] ], ], 'line is clipped to square with hole'; } + +#==========================================================