Thin walls. #24
This commit is contained in:
parent
62ee79f0c9
commit
1c7564e4a4
1
Build.PL
1
Build.PL
@ -11,6 +11,7 @@ my $build = Module::Build->new(
|
|||||||
'Getopt::Long' => '0',
|
'Getopt::Long' => '0',
|
||||||
'Math::Clipper' => '1.02',
|
'Math::Clipper' => '1.02',
|
||||||
'Math::ConvexHull' => '1.0.4',
|
'Math::ConvexHull' => '1.0.4',
|
||||||
|
'Math::Geometry::Voronoi' => '1.3',
|
||||||
'Math::PlanePath' => '53',
|
'Math::PlanePath' => '53',
|
||||||
'Moo' => '0',
|
'Moo' => '0',
|
||||||
'Time::HiRes' => '0',
|
'Time::HiRes' => '0',
|
||||||
|
@ -55,7 +55,6 @@ Roadmap includes the following goals:
|
|||||||
|
|
||||||
Sure, it's very usable. Remember that:
|
Sure, it's very usable. Remember that:
|
||||||
|
|
||||||
* it doesn't currently support single-walled parts (such as thin calibration objects);
|
|
||||||
* it doesn't generate support material;
|
* it doesn't generate support material;
|
||||||
* it only works well with manifold models (check them with Meshlab or Netfabb or http://cloud.netfabb.com/).
|
* it only works well with manifold models (check them with Meshlab or Netfabb or http://cloud.netfabb.com/).
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ package Slic3r;
|
|||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
|
||||||
our $VERSION = "0.5.8-beta";
|
our $VERSION = "0.6.0-beta";
|
||||||
|
|
||||||
our $debug = 0;
|
our $debug = 0;
|
||||||
sub debugf {
|
sub debugf {
|
||||||
|
@ -4,6 +4,7 @@ use warnings;
|
|||||||
|
|
||||||
# an ExPolygon is a polygon with holes
|
# an ExPolygon is a polygon with holes
|
||||||
|
|
||||||
|
use Math::Geometry::Voronoi;
|
||||||
use Slic3r::Geometry qw(point_in_polygon X Y A B);
|
use Slic3r::Geometry qw(point_in_polygon X Y A B);
|
||||||
use Slic3r::Geometry::Clipper qw(union_ex JT_MITER);
|
use Slic3r::Geometry::Clipper qw(union_ex JT_MITER);
|
||||||
|
|
||||||
@ -24,6 +25,11 @@ sub new {
|
|||||||
$self;
|
$self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub clone {
|
||||||
|
my $self = shift;
|
||||||
|
return (ref $self)->new(map $_->clone, @$self);
|
||||||
|
}
|
||||||
|
|
||||||
sub contour {
|
sub contour {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
return $self->[0];
|
return $self->[0];
|
||||||
@ -147,4 +153,102 @@ sub area {
|
|||||||
return $area;
|
return $area;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# this method only works for expolygons having only a contour or
|
||||||
|
# a contour and a hole, and not being thicker than the supplied
|
||||||
|
# width. it returns a polyline or a polygon
|
||||||
|
sub medial_axis {
|
||||||
|
my $self = shift;
|
||||||
|
my ($width) = @_;
|
||||||
|
|
||||||
|
my @self_lines = map $_->lines, @$self;
|
||||||
|
my $expolygon = $self->clone;
|
||||||
|
my @points = ();
|
||||||
|
foreach my $polygon (@$expolygon) {
|
||||||
|
Slic3r::Geometry::polyline_remove_short_segments($polygon, $width / 2);
|
||||||
|
|
||||||
|
# subdivide polygon segments so that we don't have anyone of them
|
||||||
|
# being longer than $width / 2
|
||||||
|
$polygon->subdivide($width/2);
|
||||||
|
|
||||||
|
push @points, @$polygon;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $voronoi = Math::Geometry::Voronoi->new(points => \@points);
|
||||||
|
$voronoi->compute;
|
||||||
|
|
||||||
|
my @skeleton_lines = ();
|
||||||
|
|
||||||
|
my $vertices = $voronoi->vertices;
|
||||||
|
my $edges = $voronoi->edges;
|
||||||
|
foreach my $edge (@$edges) {
|
||||||
|
# ignore lines going to infinite
|
||||||
|
next if $edge->[1] == -1 || $edge->[2] == -1;
|
||||||
|
|
||||||
|
my ($a, $b);
|
||||||
|
$a = $vertices->[$edge->[1]];
|
||||||
|
$b = $vertices->[$edge->[2]];
|
||||||
|
|
||||||
|
next if !$self->encloses_point($a) || !$self->encloses_point($b);
|
||||||
|
|
||||||
|
push @skeleton_lines, [$edge->[1], $edge->[2]];
|
||||||
|
}
|
||||||
|
|
||||||
|
# remove leafs (lines not connected to other lines at one of their endpoints)
|
||||||
|
{
|
||||||
|
my %pointmap = ();
|
||||||
|
$pointmap{$_}++ for map @$_, @skeleton_lines;
|
||||||
|
@skeleton_lines = grep {
|
||||||
|
$pointmap{$_->[A]} >= 2 && $pointmap{$_->[B]} >= 2
|
||||||
|
} @skeleton_lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
# now build a single polyline
|
||||||
|
my $polyline = [];
|
||||||
|
{
|
||||||
|
my %pointmap = ();
|
||||||
|
foreach my $line (@skeleton_lines) {
|
||||||
|
foreach my $point_id (@$line) {
|
||||||
|
$pointmap{$point_id} ||= [];
|
||||||
|
push @{$pointmap{$point_id}}, $line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# start from a point having only one line
|
||||||
|
foreach my $point_id (keys %pointmap) {
|
||||||
|
if (@{$pointmap{$point_id}} == 1) {
|
||||||
|
push @$polyline, grep $_ ne $point_id, map @$_, shift @{$pointmap{$point_id}};
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# if no such point is found, pick a random one
|
||||||
|
push @$polyline, shift @{ +(values %pointmap)[0][0] } if !@$polyline;
|
||||||
|
|
||||||
|
my %visited_lines = ();
|
||||||
|
while (1) {
|
||||||
|
my $last_point_id = $polyline->[-1];
|
||||||
|
|
||||||
|
shift @{ $pointmap{$last_point_id} }
|
||||||
|
while @{ $pointmap{$last_point_id} } && $visited_lines{$pointmap{$last_point_id}[0]};
|
||||||
|
my $next_line = shift @{ $pointmap{$last_point_id} } or last;
|
||||||
|
$visited_lines{$next_line} = 1;
|
||||||
|
push @$polyline, grep $_ ne $last_point_id, @$next_line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# now replace point indexes with coordinates
|
||||||
|
@$polyline = map $vertices->[$_], @$polyline;
|
||||||
|
|
||||||
|
# cleanup
|
||||||
|
Slic3r::Geometry::polyline_remove_short_segments($polyline, $width / 2);
|
||||||
|
@$polyline = Slic3r::Geometry::Douglas_Peucker($polyline, $width / 100);
|
||||||
|
Slic3r::Geometry::polyline_remove_parallel_continuous_edges($polyline);
|
||||||
|
|
||||||
|
if (Slic3r::Geometry::same_point($polyline->[0], $polyline->[-1])) {
|
||||||
|
return Slic3r::Polygon->new(@$polyline[0..$#$polyline-1]);
|
||||||
|
} else {
|
||||||
|
return Slic3r::Polyline->cast($polyline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
@ -66,6 +66,14 @@ sub change_layer {
|
|||||||
return $gcode;
|
return $gcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub extrude {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
return $_[0]->isa('Slic3r::ExtrusionLoop')
|
||||||
|
? $self->extrude_loop(@_)
|
||||||
|
: $self->extrude_path(@_);
|
||||||
|
}
|
||||||
|
|
||||||
sub extrude_loop {
|
sub extrude_loop {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ($loop, $description) = @_;
|
my ($loop, $description) = @_;
|
||||||
@ -80,10 +88,10 @@ sub extrude_loop {
|
|||||||
$extrusion_path->clip_end(scale $Slic3r::nozzle_diameter / 2);
|
$extrusion_path->clip_end(scale $Slic3r::nozzle_diameter / 2);
|
||||||
|
|
||||||
# extrude along the path
|
# extrude along the path
|
||||||
return $self->extrude($extrusion_path, $description);
|
return $self->extrude_path($extrusion_path, $description);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub extrude {
|
sub extrude_path {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ($path, $description, $recursive) = @_;
|
my ($path, $description, $recursive) = @_;
|
||||||
|
|
||||||
@ -92,7 +100,7 @@ sub extrude {
|
|||||||
# detect arcs
|
# detect arcs
|
||||||
if ($Slic3r::gcode_arcs && !$recursive) {
|
if ($Slic3r::gcode_arcs && !$recursive) {
|
||||||
my $gcode = "";
|
my $gcode = "";
|
||||||
$gcode .= $self->extrude($_, $description, 1) for $path->detect_arcs;
|
$gcode .= $self->extrude_path($_, $description, 1) for $path->detect_arcs;
|
||||||
return $gcode;
|
return $gcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +45,8 @@ sub reverse {
|
|||||||
@{$self->points} = reverse @{$self->points};
|
@{$self->points} = reverse @{$self->points};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub is_printable { 1 }
|
||||||
|
|
||||||
sub split_at_acute_angles {
|
sub split_at_acute_angles {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
|
|
||||||
|
@ -18,7 +18,8 @@ our @EXPORT_OK = qw(
|
|||||||
polyline_remove_parallel_continuous_edges polyline_remove_acute_vertices
|
polyline_remove_parallel_continuous_edges polyline_remove_acute_vertices
|
||||||
polygon_remove_acute_vertices polygon_remove_parallel_continuous_edges
|
polygon_remove_acute_vertices polygon_remove_parallel_continuous_edges
|
||||||
shortest_path collinear scale unscale merge_collinear_lines
|
shortest_path collinear scale unscale merge_collinear_lines
|
||||||
rad2deg_dir bounding_box_center
|
rad2deg_dir bounding_box_center line_intersects_any
|
||||||
|
polyline_remove_short_segments
|
||||||
);
|
);
|
||||||
|
|
||||||
use Slic3r::Geometry::DouglasPeucker qw(Douglas_Peucker);
|
use Slic3r::Geometry::DouglasPeucker qw(Douglas_Peucker);
|
||||||
@ -66,7 +67,7 @@ sub line_direction {
|
|||||||
sub lines_parallel {
|
sub lines_parallel {
|
||||||
my ($line1, $line2) = @_;
|
my ($line1, $line2) = @_;
|
||||||
|
|
||||||
return abs(line_atan($line1) - line_atan($line2)) < $parallel_degrees_limit;
|
return abs(line_direction($line1) - line_direction($line2)) < $parallel_degrees_limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub three_points_aligned {
|
sub three_points_aligned {
|
||||||
@ -438,6 +439,14 @@ sub polygon_points_visibility {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub line_intersects_any {
|
||||||
|
my ($line, $lines) = @_;
|
||||||
|
for (@$lines) {
|
||||||
|
return 1 if line_intersection($line, $_, 1);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
sub line_intersection {
|
sub line_intersection {
|
||||||
my ($line1, $line2, $require_crossing) = @_;
|
my ($line1, $line2, $require_crossing) = @_;
|
||||||
$require_crossing ||= 0;
|
$require_crossing ||= 0;
|
||||||
@ -676,6 +685,17 @@ sub polygon_remove_acute_vertices {
|
|||||||
return polyline_remove_acute_vertices($points, 1);
|
return polyline_remove_acute_vertices($points, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub polyline_remove_short_segments {
|
||||||
|
my ($points, $min_length, $isPolygon) = @_;
|
||||||
|
for (my $i = $isPolygon ? 0 : 1; $i < $#$points; $i++) {
|
||||||
|
if (distance_between_points($points->[$i-1], $points->[$i]) < $min_length) {
|
||||||
|
# we can remove $points->[$i]
|
||||||
|
splice @$points, $i, 1;
|
||||||
|
$i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# accepts an arrayref; each item should be an arrayref whose first
|
# accepts an arrayref; each item should be an arrayref whose first
|
||||||
# item is the point to be used for the shortest path, and the second
|
# item is the point to be used for the shortest path, and the second
|
||||||
# one is the value to be returned in output (if the second item
|
# one is the value to be returned in output (if the second item
|
||||||
|
@ -30,6 +30,10 @@ has 'slices' => (
|
|||||||
default => sub { [] },
|
default => sub { [] },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
# collection of polygons or polylines representing thin walls contained
|
||||||
|
# in the original geometry
|
||||||
|
has 'thin_walls' => (is => 'rw', default => sub { [] });
|
||||||
|
|
||||||
# collection of surfaces generated by offsetting the innermost perimeter(s)
|
# collection of surfaces generated by offsetting the innermost perimeter(s)
|
||||||
# they represent boundaries of areas to fill
|
# they represent boundaries of areas to fill
|
||||||
has 'fill_boundaries' => (
|
has 'fill_boundaries' => (
|
||||||
@ -160,6 +164,19 @@ sub make_surfaces {
|
|||||||
($_, surface_type => 'internal'),
|
($_, surface_type => 'internal'),
|
||||||
$surface->expolygon->offset_ex(-$distance);
|
$surface->expolygon->offset_ex(-$distance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# now detect thin walls by re-outgrowing offsetted surfaces and subtracting
|
||||||
|
# them from the original slices
|
||||||
|
my $outgrown = Math::Clipper::offset([ map $_->p, @{$self->slices} ], $distance);
|
||||||
|
my $diff = diff_ex(
|
||||||
|
[ map $_->p, @surfaces ],
|
||||||
|
$outgrown,
|
||||||
|
);
|
||||||
|
|
||||||
|
push @{$self->thin_walls},
|
||||||
|
grep $_,
|
||||||
|
map $_->medial_axis(scale $Slic3r::flow_width),
|
||||||
|
@$diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (0) {
|
if (0) {
|
||||||
|
@ -79,6 +79,15 @@ sub make_perimeter {
|
|||||||
for (@{ $layer->perimeters }) {
|
for (@{ $layer->perimeters }) {
|
||||||
$_->role('small-perimeter') if $_->polygon->area < $Slic3r::small_perimeter_area;
|
$_->role('small-perimeter') if $_->polygon->area < $Slic3r::small_perimeter_area;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# add thin walls as perimeters
|
||||||
|
for (@{ $layer->thin_walls }) {
|
||||||
|
if ($_->isa('Slic3r::Polygon')) {
|
||||||
|
push @{ $layer->perimeters }, Slic3r::ExtrusionLoop->cast($_, role => 'perimeter');
|
||||||
|
} else {
|
||||||
|
push @{ $layer->perimeters }, Slic3r::ExtrusionPath->cast($_->points, role => 'perimeter');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
@ -20,6 +20,11 @@ sub new {
|
|||||||
return $self;
|
return $self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub clone {
|
||||||
|
my $self = shift;
|
||||||
|
return (ref $self)->new(@$self);
|
||||||
|
}
|
||||||
|
|
||||||
sub cast {
|
sub cast {
|
||||||
my $class = shift;
|
my $class = shift;
|
||||||
if (ref $_[0] eq 'Slic3r::Point') {
|
if (ref $_[0] eq 'Slic3r::Point') {
|
||||||
|
@ -26,6 +26,11 @@ sub new {
|
|||||||
$self;
|
$self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub clone {
|
||||||
|
my $self = shift;
|
||||||
|
return (ref $self)->new(map $_->clone, @$self);
|
||||||
|
}
|
||||||
|
|
||||||
# legacy method, to be removed when we ditch Slic3r::Polyline::Closed
|
# legacy method, to be removed when we ditch Slic3r::Polyline::Closed
|
||||||
sub closed_polyline {
|
sub closed_polyline {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
@ -87,4 +92,26 @@ sub offset {
|
|||||||
return @$offsets;
|
return @$offsets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# this method subdivides the polygon segments to that no one of them
|
||||||
|
# is longer than the length provided
|
||||||
|
sub subdivide {
|
||||||
|
my $self = shift;
|
||||||
|
my ($max_length) = @_;
|
||||||
|
|
||||||
|
for (my $i = 0; $i <= $#$self; $i++) {
|
||||||
|
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);
|
||||||
|
my @new_points = map Slic3r::Geometry::point_along_segment($self->[$i-1], $self->[$i], $spacing * $_),
|
||||||
|
1..$num_points;
|
||||||
|
|
||||||
|
splice @$self, $i, 0, @new_points;
|
||||||
|
$i += @new_points;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
1;
|
1;
|
@ -131,7 +131,7 @@ sub new_from_mesh {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# remove empty layers from bottom
|
# remove empty layers from bottom
|
||||||
while (@{$print->layers} && !@{$print->layers->[0]->slices}) {
|
while (@{$print->layers} && !@{$print->layers->[0]->slices} && !@{$print->layers->[0]->thin_walls}) {
|
||||||
shift @{$print->layers};
|
shift @{$print->layers};
|
||||||
for (my $i = 0; $i <= $#{$print->layers}; $i++) {
|
for (my $i = 0; $i <= $#{$print->layers}; $i++) {
|
||||||
$print->layers->[$i]->id($i);
|
$print->layers->[$i]->id($i);
|
||||||
@ -327,11 +327,10 @@ sub extrude_skirt {
|
|||||||
return unless $Slic3r::skirts > 0;
|
return unless $Slic3r::skirts > 0;
|
||||||
|
|
||||||
# collect points from all layers contained in skirt height
|
# collect points from all layers contained in skirt height
|
||||||
my @points = ();
|
|
||||||
my $skirt_height = $Slic3r::skirt_height;
|
my $skirt_height = $Slic3r::skirt_height;
|
||||||
$skirt_height = $self->layer_count if $skirt_height > $self->layer_count;
|
$skirt_height = $self->layer_count if $skirt_height > $self->layer_count;
|
||||||
my @layers = map $self->layer($_), 0..($skirt_height-1);
|
my @layers = map $self->layer($_), 0..($skirt_height-1);
|
||||||
push @points, map @$_, map $_->p, map @{ $_->slices }, @layers;
|
my @points = map @$_, map $_->p, map @{ $_->slices }, @layers;
|
||||||
return if !@points;
|
return if !@points;
|
||||||
|
|
||||||
# find out convex hull
|
# find out convex hull
|
||||||
@ -484,11 +483,11 @@ sub export_gcode {
|
|||||||
printf $fh $extruder->extrude_loop($_, 'skirt') for @{ $layer->skirts };
|
printf $fh $extruder->extrude_loop($_, 'skirt') for @{ $layer->skirts };
|
||||||
|
|
||||||
# extrude perimeters
|
# extrude perimeters
|
||||||
printf $fh $extruder->extrude_loop($_, 'perimeter') for @{ $layer->perimeters };
|
printf $fh $extruder->extrude($_, 'perimeter') for @{ $layer->perimeters };
|
||||||
|
|
||||||
# extrude fills
|
# extrude fills
|
||||||
for my $fill (@{ $layer->fills }) {
|
for my $fill (@{ $layer->fills }) {
|
||||||
printf $fh $extruder->extrude($_, 'fill')
|
printf $fh $extruder->extrude_path($_, 'fill')
|
||||||
for $fill->shortest_path($extruder->last_pos);
|
for $fill->shortest_path($extruder->last_pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user