Faster support generation. Includes a new implementation of the Douglas-Peucker algorithm
This commit is contained in:
parent
94e673e050
commit
eba7c10018
9 changed files with 166 additions and 183 deletions
1
MANIFEST
1
MANIFEST
|
@ -20,7 +20,6 @@ lib/Slic3r/Fill/Rectilinear.pm
|
|||
lib/Slic3r/Fill/Rectilinear2.pm
|
||||
lib/Slic3r/Geometry.pm
|
||||
lib/Slic3r/Geometry/Clipper.pm
|
||||
lib/Slic3r/Geometry/DouglasPeucker.pm
|
||||
lib/Slic3r/GUI.pm
|
||||
lib/Slic3r/GUI/OptionsGroup.pm
|
||||
lib/Slic3r/GUI/SkeinPanel.pm
|
||||
|
|
|
@ -108,6 +108,17 @@ sub bounding_box {
|
|||
return Slic3r::Geometry::bounding_box($self->contour);
|
||||
}
|
||||
|
||||
sub bounding_box_polygon {
|
||||
my $self = shift;
|
||||
my @bb = $self->bounding_box;
|
||||
return Slic3r::Polygon->new([
|
||||
[ $bb[0], $bb[1] ],
|
||||
[ $bb[2], $bb[1] ],
|
||||
[ $bb[2], $bb[3] ],
|
||||
[ $bb[0], $bb[3] ],
|
||||
]);
|
||||
}
|
||||
|
||||
sub clip_line {
|
||||
my $self = shift;
|
||||
my ($line) = @_;
|
||||
|
@ -141,6 +152,11 @@ sub clip_line {
|
|||
return [@lines];
|
||||
}
|
||||
|
||||
sub simplify {
|
||||
my $self = shift;
|
||||
$_->simplify(@_) for @$self;
|
||||
}
|
||||
|
||||
sub translate {
|
||||
my $self = shift;
|
||||
$_->translate(@_) for @$self;
|
||||
|
|
|
@ -45,6 +45,13 @@ sub clip_end {
|
|||
}
|
||||
}
|
||||
|
||||
sub clip_with_polygon {
|
||||
my $self = shift;
|
||||
my ($polygon) = @_;
|
||||
|
||||
return $self->clip_with_expolygon(Slic3r::ExPolygon->new($polygon));
|
||||
}
|
||||
|
||||
sub clip_with_expolygon {
|
||||
my $self = shift;
|
||||
my ($expolygon) = @_;
|
||||
|
|
|
@ -18,11 +18,10 @@ our @EXPORT_OK = qw(
|
|||
polyline_remove_parallel_continuous_edges polyline_remove_acute_vertices
|
||||
polygon_remove_acute_vertices polygon_remove_parallel_continuous_edges
|
||||
shortest_path collinear scale unscale merge_collinear_lines
|
||||
rad2deg_dir bounding_box_center line_intersects_any
|
||||
rad2deg_dir bounding_box_center line_intersects_any douglas_peucker
|
||||
polyline_remove_short_segments normal triangle_normal
|
||||
);
|
||||
|
||||
use Slic3r::Geometry::DouglasPeucker qw(Douglas_Peucker);
|
||||
use XXX;
|
||||
|
||||
use constant PI => 4 * atan2(1, 1);
|
||||
|
@ -110,6 +109,19 @@ sub distance_between_points {
|
|||
return sqrt((($p1->[X] - $p2->[X])**2) + ($p1->[Y] - $p2->[Y])**2);
|
||||
}
|
||||
|
||||
sub point_line_distance {
|
||||
my ($point, $line) = @_;
|
||||
return distance_between_points($point, $line->[A])
|
||||
if same_point($line->[A], $line->[B]);
|
||||
|
||||
my $n = ($line->[B][X] - $line->[A][X]) * ($line->[A][Y] - $point->[Y])
|
||||
- ($line->[A][X] - $point->[X]) * ($line->[B][Y] - $line->[A][Y]);
|
||||
|
||||
my $d = sqrt((($line->[B][X] - $line->[A][X]) ** 2) + (($line->[B][Y] - $line->[A][Y]) ** 2));
|
||||
|
||||
return abs($n) / $d;
|
||||
}
|
||||
|
||||
sub line_length {
|
||||
my ($line) = @_;
|
||||
return distance_between_points(@$line[A, B]);
|
||||
|
@ -740,4 +752,97 @@ sub shortest_path {
|
|||
return $result;
|
||||
}
|
||||
|
||||
sub douglas_peucker {
|
||||
my ($points, $tolerance) = @_;
|
||||
|
||||
my $results = [];
|
||||
my $dmax = 0;
|
||||
my $index = 0;
|
||||
for my $i (1..$#$points) {
|
||||
my $d = point_line_distance($points->[$i], [ $points->[0], $points->[-1] ]);
|
||||
if ($d > $dmax) {
|
||||
$index = $i;
|
||||
$dmax = $d;
|
||||
}
|
||||
}
|
||||
if ($dmax >= $tolerance) {
|
||||
my $dp1 = douglas_peucker([ @$points[0..$index] ], $tolerance);
|
||||
$results = [
|
||||
@$dp1[0..($#$dp1-1)],
|
||||
@{douglas_peucker([ @$points[$index..$#$points] ], $tolerance)},
|
||||
];
|
||||
} else {
|
||||
$results = [ $points->[0], $points->[-1] ];
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
sub douglas_peucker2 {
|
||||
my ($points, $tolerance) = @_;
|
||||
|
||||
my $anchor = 0;
|
||||
my $floater = $#$points;
|
||||
my @stack = ();
|
||||
my %keep = ();
|
||||
|
||||
push @stack, [$anchor, $floater];
|
||||
while (@stack) {
|
||||
($anchor, $floater) = @{pop @stack};
|
||||
|
||||
# initialize line segment
|
||||
my ($anchor_x, $anchor_y, $seg_len);
|
||||
if (grep $points->[$floater][$_] != $points->[$anchor][$_], X, Y) {
|
||||
$anchor_x = $points->[$floater][X] - $points->[$anchor][X];
|
||||
$anchor_y = $points->[$floater][Y] - $points->[$anchor][Y];
|
||||
$seg_len = sqrt(($anchor_x ** 2) + ($anchor_y ** 2));
|
||||
# get the unit vector
|
||||
$anchor_x /= $seg_len;
|
||||
$anchor_y /= $seg_len;
|
||||
} else {
|
||||
$anchor_x = $anchor_y = $seg_len = 0;
|
||||
}
|
||||
|
||||
# inner loop:
|
||||
my $max_dist = 0;
|
||||
my $farthest = $anchor + 1;
|
||||
for my $i (($anchor + 1) .. $floater) {
|
||||
my $dist_to_seg = 0;
|
||||
# compare to anchor
|
||||
my $vecX = $points->[$i][X] - $points->[$anchor][X];
|
||||
my $vecY = $points->[$i][Y] - $points->[$anchor][Y];
|
||||
$seg_len = sqrt(($vecX ** 2) + ($vecY ** 2));
|
||||
# dot product:
|
||||
my $proj = $vecX * $anchor_x + $vecY * $anchor_y;
|
||||
if ($proj < 0) {
|
||||
$dist_to_seg = $seg_len;
|
||||
} else {
|
||||
# compare to floater
|
||||
$vecX = $points->[$i][X] - $points->[$floater][X];
|
||||
$vecY = $points->[$i][Y] - $points->[$floater][Y];
|
||||
$seg_len = sqrt(($vecX ** 2) + ($vecY ** 2));
|
||||
# dot product:
|
||||
$proj = $vecX * (-$anchor_x) + $vecY * (-$anchor_y);
|
||||
if ($proj < 0) {
|
||||
$dist_to_seg = $seg_len
|
||||
} else { # calculate perpendicular distance to line (pythagorean theorem):
|
||||
$dist_to_seg = sqrt(abs(($seg_len ** 2) - ($proj ** 2)));
|
||||
}
|
||||
if ($max_dist < $dist_to_seg) {
|
||||
$max_dist = $dist_to_seg;
|
||||
$farthest = $i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($max_dist <= $tolerance) { # use line segment
|
||||
$keep{$_} = 1 for $anchor, $floater;
|
||||
} else {
|
||||
push @stack, [$anchor, $farthest];
|
||||
push @stack, [$farthest, $floater];
|
||||
}
|
||||
}
|
||||
|
||||
return [ map $points->[$_], sort keys %keep ];
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
@ -1,167 +0,0 @@
|
|||
package Slic3r::Geometry::DouglasPeucker;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
BEGIN {
|
||||
use Exporter ;
|
||||
use vars qw ( $VERSION @ISA @EXPORT) ;
|
||||
$VERSION = 1.0 ;
|
||||
@ISA = qw ( Exporter ) ;
|
||||
@EXPORT = qw (
|
||||
Douglas_Peucker
|
||||
perp_distance
|
||||
haversine_distance_meters
|
||||
angle3points
|
||||
) ;
|
||||
}
|
||||
|
||||
# Call as: @Opoints = &Douglas_Peucker( <reference to input array of points>, <tolerance>) ;
|
||||
# Returns: Array of points
|
||||
# Points Array Format:
|
||||
# ([lat1,lng1],[lat2,lng2],...[latn,lngn])
|
||||
#
|
||||
|
||||
sub Douglas_Peucker
|
||||
{
|
||||
my $href = shift ;
|
||||
my $tolerance = shift ;
|
||||
my @Ipoints = @$href ;
|
||||
my @Opoints = ( ) ;
|
||||
my @stack = ( ) ;
|
||||
my $fIndex = 0 ;
|
||||
my $fPoint = '' ;
|
||||
my $aIndex = 0 ;
|
||||
my $anchor = '' ;
|
||||
my $max = 0 ;
|
||||
my $maxIndex = 0 ;
|
||||
my $point = '' ;
|
||||
my $dist = 0 ;
|
||||
my $polygon = 0 ; # Line Type
|
||||
|
||||
$anchor = $Ipoints[0] ; # save first point
|
||||
|
||||
push( @Opoints, $anchor ) ;
|
||||
|
||||
$aIndex = 0 ; # Anchor Index
|
||||
|
||||
# Check for a polygon: At least 4 points and the first point == last point...
|
||||
|
||||
if ( $#Ipoints >= 4 and $Ipoints[0] == $Ipoints[$#Ipoints] )
|
||||
{
|
||||
$fIndex = $#Ipoints - 1 ; # Start from the next to last point
|
||||
$polygon = 1 ; # It's a polygon
|
||||
|
||||
} else
|
||||
{
|
||||
$fIndex = $#Ipoints ; # It's a path (open polygon)
|
||||
}
|
||||
|
||||
push( @stack, $fIndex ) ;
|
||||
|
||||
# Douglas - Peucker algorithm...
|
||||
|
||||
while(@stack)
|
||||
{
|
||||
$fIndex = $stack[$#stack] ;
|
||||
$fPoint = $Ipoints[$fIndex] ;
|
||||
$max = $tolerance ; # comparison values
|
||||
$maxIndex = 0 ;
|
||||
|
||||
# Process middle points...
|
||||
|
||||
for (($aIndex+1) .. ($fIndex-1))
|
||||
{
|
||||
$point = $Ipoints[$_] ;
|
||||
$dist = &perp_distance($anchor, $fPoint, $point);
|
||||
|
||||
if( $dist >= $max )
|
||||
{
|
||||
$max = $dist ;
|
||||
$maxIndex = $_;
|
||||
}
|
||||
}
|
||||
|
||||
if( $maxIndex > 0 )
|
||||
{
|
||||
push( @stack, $maxIndex ) ;
|
||||
} else
|
||||
{
|
||||
push( @Opoints, $fPoint ) ;
|
||||
$anchor = $Ipoints[(pop @stack)] ;
|
||||
$aIndex = $fIndex ;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $polygon ) # Check for Polygon
|
||||
{
|
||||
push( @Opoints, $Ipoints[$#Ipoints] ) ; # Add the last point
|
||||
|
||||
# Check for collapsed polygons, use original data in that case...
|
||||
|
||||
if( $#Opoints < 4 )
|
||||
{
|
||||
@Opoints = @Ipoints ;
|
||||
}
|
||||
}
|
||||
|
||||
return ( @Opoints ) ;
|
||||
|
||||
}
|
||||
|
||||
# Calculate Perpendicular Distance in meters between a line (two points) and a point...
|
||||
# my $dist = &perp_distance( <line point 1>, <line point 2>, <point> ) ;
|
||||
|
||||
sub perp_distance # Perpendicular distance in meters
|
||||
{
|
||||
my $lp1 = shift ;
|
||||
my $lp2 = shift ;
|
||||
my $p = shift ;
|
||||
my $dist = &haversine_distance_meters( $lp1, $p ) ;
|
||||
my $angle = &angle3points( $lp1, $lp2, $p ) ;
|
||||
|
||||
return ( sprintf("%0.6f", abs($dist * sin($angle)) ) ) ;
|
||||
}
|
||||
|
||||
# Calculate Distance in meters between two points...
|
||||
|
||||
sub haversine_distance_meters
|
||||
{
|
||||
my $p1 = shift ;
|
||||
my $p2 = shift ;
|
||||
|
||||
my $O = 3.141592654/180 ;
|
||||
my $b = $$p1[0] * $O ;
|
||||
my $c = $$p2[0] * $O ;
|
||||
my $d = $b - $c ;
|
||||
my $e = ($$p1[1] * $O) - ($$p2[1] * $O) ;
|
||||
my $f = 2 * &asin( sqrt( (sin($d/2) ** 2) + cos($b) * cos($c) * (sin($e/2) ** 2)));
|
||||
|
||||
return sprintf("%0.4f",$f * 6378137) ; # Return meters
|
||||
|
||||
sub asin
|
||||
{
|
||||
atan2($_[0], sqrt(1 - $_[0] * $_[0])) ;
|
||||
}
|
||||
}
|
||||
|
||||
# Calculate Angle in Radians between three points...
|
||||
|
||||
sub angle3points # Angle between three points in radians
|
||||
{
|
||||
my $p1 = shift ;
|
||||
my $p2 = shift ;
|
||||
my $p3 = shift ;
|
||||
my $m1 = &slope( $p2, $p1 ) ;
|
||||
my $m2 = &slope( $p3, $p1 ) ;
|
||||
|
||||
return ($m2 - $m1) ;
|
||||
|
||||
sub slope # Slope in radians
|
||||
{
|
||||
my $p1 = shift ;
|
||||
my $p2 = shift ;
|
||||
return atan2( (@$p2[1] - @$p1[1]),( @$p2[0] - @$p1[0] ));
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
|
@ -109,7 +109,6 @@ sub subdivide {
|
|||
my $len = Slic3r::Geometry::line_length([ $self->[$i-1], $self->[$i] ]);
|
||||
my $num_points = int($len / $max_length) - 1;
|
||||
$num_points++ if $len % $max_length;
|
||||
next unless $num_points;
|
||||
|
||||
# $num_points is the number of points to add between $i-1 and $i
|
||||
my $spacing = $len / ($num_points + 1);
|
||||
|
|
|
@ -57,7 +57,7 @@ sub simplify {
|
|||
my $self = shift;
|
||||
my $tolerance = shift || 10;
|
||||
|
||||
@$self = Slic3r::Geometry::Douglas_Peucker($self, $tolerance);
|
||||
@$self = @{ Slic3r::Geometry::douglas_peucker($self, $tolerance) };
|
||||
bless $_, 'Slic3r::Point' for @$self;
|
||||
}
|
||||
|
||||
|
@ -110,6 +110,9 @@ sub clip_with_expolygon {
|
|||
my $self = shift;
|
||||
my ($expolygon) = @_;
|
||||
|
||||
#printf "Clipping polyline of %d points to expolygon of %d polygons and %d points\n",
|
||||
# scalar(@$self), scalar(@$expolygon), scalar(map @$_, @$expolygon);
|
||||
|
||||
my @polylines = ();
|
||||
my $current_polyline = [];
|
||||
foreach my $line ($self->lines) {
|
||||
|
|
|
@ -364,8 +364,6 @@ sub infill_every_layers {
|
|||
my $self = shift;
|
||||
return unless $Slic3r::infill_every_layers > 1 && $Slic3r::fill_density > 0;
|
||||
|
||||
printf "==> COMBINING INFILL\n";
|
||||
|
||||
# start from bottom, skip first layer
|
||||
for (my $i = 1; $i < $self->layer_count; $i++) {
|
||||
my $layer = $self->layer($i);
|
||||
|
@ -489,6 +487,14 @@ sub generate_support_material {
|
|||
), @paths;
|
||||
}
|
||||
}
|
||||
$_->polyline->simplify(scale $Slic3r::flow_spacing / 3) for @$support_pattern;
|
||||
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output(undef, "support.svg",
|
||||
polylines => [ map $_->polyline, @$support_pattern ],
|
||||
);
|
||||
}
|
||||
|
||||
# now apply the pattern to layers below unsupported surfaces
|
||||
my (@a, @b) = ();
|
||||
|
@ -502,13 +508,15 @@ sub generate_support_material {
|
|||
)};
|
||||
$layer->support_fills(Slic3r::ExtrusionPath::Collection->new);
|
||||
foreach my $expolygon (@c) {
|
||||
push @{$layer->support_fills->paths}, map $_->clip_with_expolygon($expolygon), @$support_pattern;
|
||||
push @{$layer->support_fills->paths}, map $_->clip_with_expolygon($expolygon),
|
||||
map $_->clip_with_polygon($expolygon->bounding_box_polygon), @$support_pattern;
|
||||
}
|
||||
}
|
||||
@b = @{union_ex([ map @$_, @c, @a ])};
|
||||
@a = map $_->expolygon->offset_ex(scale 2),
|
||||
grep $_->surface_type eq 'bottom' && !defined $_->bridge_angle,
|
||||
@{$layer->slices};
|
||||
$_->simplify(scale $Slic3r::flow_spacing * 3) for @a;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ use Test::More;
|
|||
use strict;
|
||||
use warnings;
|
||||
|
||||
plan tests => 3;
|
||||
plan tests => 6;
|
||||
|
||||
BEGIN {
|
||||
use FindBin;
|
||||
|
@ -20,6 +20,22 @@ use Slic3r;
|
|||
is scalar(@$polygon), 3, 'merge_continuous_lines';
|
||||
}
|
||||
|
||||
{
|
||||
my $polyline = Slic3r::Polyline->new([
|
||||
[0,0],[1,0],[2,0],[2,1],[2,2],[1,2],[0,2],[0,1],[0,0],
|
||||
]);
|
||||
$polyline->simplify(1);
|
||||
is_deeply $polyline, [ [0, 0], [2, 0], [2, 2], [0, 2], [0, 0] ], 'Douglas-Peucker';
|
||||
}
|
||||
|
||||
{
|
||||
my $polyline = Slic3r::Polyline->new([
|
||||
[0,0],[0.5,0.5],[1,0],[1.25,-0.25],[1.5,.5],
|
||||
]);
|
||||
$polyline->simplify(0.25);
|
||||
is_deeply $polyline, [ [0, 0], [0.5, 0.5], [1.25, -0.25], [1.5, 0.5] ], 'Douglas-Peucker';
|
||||
}
|
||||
|
||||
{
|
||||
my $gear = [
|
||||
[144.9694,317.1543], [145.4181,301.5633], [146.3466,296.921], [131.8436,294.1643], [131.7467,294.1464],
|
||||
|
@ -57,13 +73,10 @@ use Slic3r;
|
|||
note sprintf "original points: %d\nnew points: %d", scalar(@$gear), scalar(@$polygon);
|
||||
ok @$polygon < @$gear, 'gear was simplified using merge_continuous_lines';
|
||||
|
||||
# simplify() is not being used, so we don't test it
|
||||
if (0) {
|
||||
my $num_points = scalar @$polygon;
|
||||
$polygon->simplify;
|
||||
note sprintf "original points: %d\nnew points: %d", $num_points, scalar(@$polygon);
|
||||
ok @$polygon < $num_points, 'gear was further simplified using Douglas-Peucker';
|
||||
}
|
||||
my $num_points = scalar @$polygon;
|
||||
$polygon->simplify;
|
||||
note sprintf "original points: %d\nnew points: %d", $num_points, scalar(@$polygon);
|
||||
ok @$polygon < $num_points, 'gear was further simplified using Douglas-Peucker';
|
||||
}
|
||||
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue