New fill types (hilbertcurve, archimedeanchords, octagramspiral) and ability to use different patterns for solid layers. #20
This commit is contained in:
parent
041e9877a3
commit
038caddcda
1
Build.PL
1
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',
|
||||
|
@ -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,6 +125,8 @@ 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)
|
||||
--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
|
||||
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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";
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -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 = (
|
||||
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;
|
||||
}
|
||||
|
7
lib/Slic3r/Fill/ArchimedeanChords.pm
Normal file
7
lib/Slic3r/Fill/ArchimedeanChords.pm
Normal file
@ -0,0 +1,7 @@
|
||||
package Slic3r::Fill::ArchimedeanChords;
|
||||
use Moo;
|
||||
|
||||
extends 'Slic3r::Fill::PlanePath';
|
||||
use Math::PlanePath::ArchimedeanChords;
|
||||
|
||||
1;
|
@ -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 {
|
||||
|
18
lib/Slic3r/Fill/Flowsnake.pm
Normal file
18
lib/Slic3r/Fill/Flowsnake.pm
Normal file
@ -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;
|
7
lib/Slic3r/Fill/HilbertCurve.pm
Normal file
7
lib/Slic3r/Fill/HilbertCurve.pm
Normal file
@ -0,0 +1,7 @@
|
||||
package Slic3r::Fill::HilbertCurve;
|
||||
use Moo;
|
||||
|
||||
extends 'Slic3r::Fill::PlanePath';
|
||||
use Math::PlanePath::HilbertCurve;
|
||||
|
||||
1;
|
9
lib/Slic3r/Fill/OctagramSpiral.pm
Normal file
9
lib/Slic3r/Fill/OctagramSpiral.pm
Normal file
@ -0,0 +1,9 @@
|
||||
package Slic3r::Fill::OctagramSpiral;
|
||||
use Moo;
|
||||
|
||||
extends 'Slic3r::Fill::PlanePath';
|
||||
use Math::PlanePath::OctagramSpiral;
|
||||
|
||||
sub multiplier () { sqrt(2) }
|
||||
|
||||
1;
|
62
lib/Slic3r/Fill/PlanePath.pm
Normal file
62
lib/Slic3r/Fill/PlanePath.pm
Normal file
@ -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;
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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};
|
||||
}
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
|
@ -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] }
|
||||
|
||||
|
@ -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;
|
@ -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;
|
||||
|
@ -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
|
||||
|
59
t/polyclip.t
59
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 ]);
|
||||
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';
|
||||
}
|
||||
|
||||
#==========================================================
|
||||
|
Loading…
Reference in New Issue
Block a user