Refactoring: moved slicing code to new TriangleMesh class, leaving in STL just what's needed to read that particular input format. Slic3r will now warn if model is not manifold. #16

This commit is contained in:
Alessandro Ranellucci 2011-11-27 11:40:03 +01:00
parent 15d060019f
commit d51a37a0ae
16 changed files with 642 additions and 451 deletions

View File

@ -26,9 +26,6 @@ lib/Slic3r/GUI/OptionsGroup.pm
lib/Slic3r/GUI/SkeinPanel.pm
lib/Slic3r/Layer.pm
lib/Slic3r/Line.pm
lib/Slic3r/Line/FacetEdge.pm
lib/Slic3r/Line/FacetEdge/Bottom.pm
lib/Slic3r/Line/FacetEdge/Top.pm
lib/Slic3r/Perimeter.pm
lib/Slic3r/Point.pm
lib/Slic3r/Polygon.pm
@ -39,6 +36,8 @@ lib/Slic3r/Skein.pm
lib/Slic3r/STL.pm
lib/Slic3r/Surface.pm
lib/Slic3r/SVG.pm
lib/Slic3r/TriangleMesh.pm
lib/Slic3r/TriangleMesh/IntersectionLine.pm
MANIFEST This list of files
README.markdown
slic3r.pl

View File

@ -3,7 +3,7 @@ package Slic3r;
use strict;
use warnings;
our $VERSION = "0.5.3";
our $VERSION = "0.5.4beta";
our $debug = 0;
sub debugf {
@ -21,7 +21,6 @@ use Slic3r::Fill;
use Slic3r::Geometry;
use Slic3r::Layer;
use Slic3r::Line;
use Slic3r::Line::FacetEdge;
use Slic3r::Perimeter;
use Slic3r::Point;
use Slic3r::Polygon;
@ -31,6 +30,8 @@ use Slic3r::Print;
use Slic3r::Skein;
use Slic3r::STL;
use Slic3r::Surface;
use Slic3r::TriangleMesh;
use Slic3r::TriangleMesh::IntersectionLine;
# printer options
our $nozzle_diameter = 0.5;
@ -60,7 +61,7 @@ our $layer_height = 0.4;
our $first_layer_height_ratio = 1;
our $infill_every_layers = 1;
our $extrusion_width_ratio = 0;
our $flow_speed_ratio = 1.1;
our $flow_speed_ratio = 1;
our $flow_width;
# print options

View File

@ -4,7 +4,7 @@ use warnings;
use utf8;
use File::Basename qw(basename);
use Wx qw(:sizer :progressdialog wxOK wxICON_INFORMATION wxICON_ERROR wxID_OK wxFD_OPEN
use Wx qw(:sizer :progressdialog wxOK wxICON_INFORMATION wxICON_WARNING wxICON_ERROR wxID_OK wxFD_OPEN
wxFD_SAVE wxDEFAULT wxNORMAL);
use Wx::Event qw(EVT_BUTTON);
use base 'Wx::Panel';
@ -155,7 +155,14 @@ sub do_slice {
}
},
);
{
local $SIG{__WARN__} = sub {
my $message = shift;
Wx::MessageDialog->new($self, $message, 'Non-manifold object',
wxOK | wxICON_WARNING)->ShowModal;
};
$skein->go;
}
$process_dialog->Destroy;
undef $process_dialog;

View File

@ -13,11 +13,11 @@ our @EXPORT_OK = qw(
polygon_has_vertex polyline_length can_connect_points deg2rad rad2deg
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
line_intersection bounding_box bounding_box_intersect same_point
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
shortest_path collinear scale unscale merge_collinear_lines
);
use Slic3r::Geometry::DouglasPeucker qw(Douglas_Peucker);
@ -35,9 +35,12 @@ use constant X2 => 2;
use constant Y2 => 3;
our $parallel_degrees_limit = abs(deg2rad(3));
our $epsilon = 1E-4;
our $epsilon = 1E-6;
sub epsilon () { $epsilon }
sub scale ($) { $_[0] / $Slic3r::resolution }
sub unscale ($) { $_[0] * $Slic3r::resolution }
sub slope {
my ($line) = @_;
return undef if abs($line->[B][X] - $line->[A][X]) < epsilon; # line is vertical
@ -85,6 +88,11 @@ sub points_coincide {
return 0;
}
sub same_point {
my ($p1, $p2) = @_;
return $p1->[X] == $p2->[X] && $p1->[Y] == $p2->[Y];
}
sub distance_between_points {
my ($p1, $p2) = @_;
return sqrt((($p1->[X] - $p2->[X])**2) + ($p1->[Y] - $p2->[Y])**2);
@ -438,6 +446,40 @@ sub collinear {
return 1;
}
sub merge_collinear_lines {
my ($lines) = @_;
my $line_count = @$lines;
for (my $i = 0; $i <= $#$lines-1; $i++) {
for (my $j = $i+1; $j <= $#$lines; $j++) {
# lines are collinear and overlapping?
next unless collinear($lines->[$i], $lines->[$j], 1);
# lines have same orientation?
next unless ($lines->[$i][A][X] <=> $lines->[$i][B][X]) == ($lines->[$j][A][X] <=> $lines->[$j][B][X])
&& ($lines->[$i][A][Y] <=> $lines->[$i][B][Y]) == ($lines->[$j][A][Y] <=> $lines->[$j][B][Y]);
# resulting line
my @x = sort { $a <=> $b } ($lines->[$i][A][X], $lines->[$i][B][X], $lines->[$j][A][X], $lines->[$j][B][X]);
my @y = sort { $a <=> $b } ($lines->[$i][A][Y], $lines->[$i][B][Y], $lines->[$j][A][Y], $lines->[$j][B][Y]);
my $new_line = Slic3r::Line->new([$x[0], $y[0]], [$x[-1], $y[-1]]);
for (X, Y) {
($new_line->[A][$_], $new_line->[B][$_]) = ($new_line->[B][$_], $new_line->[A][$_])
if $lines->[$i][A][$_] > $lines->[$i][B][$_];
}
# save new line and remove found one
$lines->[$i] = $new_line;
splice @$lines, $j, 1;
$j--;
}
}
Slic3r::debugf " merging %d lines resulted in %d lines\n", $line_count, scalar(@$lines);
return $lines;
}
sub _line_intersection {
my ( $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3 );

View File

@ -2,8 +2,7 @@ package Slic3r::Layer;
use Moo;
use Math::Clipper ':all';
use Slic3r::Geometry qw(polygon_lines points_coincide angle3points polyline_lines nearest_point
line_length collinear X Y A B PI);
use Slic3r::Geometry qw(collinear X Y A B PI);
use Slic3r::Geometry::Clipper qw(union_ex diff_ex intersection_ex PFT_EVENODD);
use XXX;
@ -18,7 +17,7 @@ has 'id' => (
# these need to be merged in continuos (closed) polylines
has 'lines' => (
is => 'rw',
#isa => 'ArrayRef[Slic3r::Line]',
#isa => 'ArrayRef[Slic3r::TriangleMesh::IntersectionLine]',
default => sub { [] },
);
@ -98,202 +97,19 @@ sub add_line {
my $self = shift;
my ($line) = @_;
return if $line->a->coincides_with($line->b);
push @{ $self->lines }, $line;
return $line;
}
# merge overlapping lines
sub cleanup_lines {
my $self = shift;
my $lines = $self->lines;
my $line_count = @$lines;
for (my $i = 0; $i <= $#$lines-1; $i++) {
for (my $j = $i+1; $j <= $#$lines; $j++) {
# lines are collinear and overlapping?
next unless collinear($lines->[$i], $lines->[$j], 1);
# lines have same orientation?
next unless ($lines->[$i][A][X] <=> $lines->[$i][B][X]) == ($lines->[$j][A][X] <=> $lines->[$j][B][X])
&& ($lines->[$i][A][Y] <=> $lines->[$i][B][Y]) == ($lines->[$j][A][Y] <=> $lines->[$j][B][Y]);
# resulting line
my @x = sort { $a <=> $b } ($lines->[$i][A][X], $lines->[$i][B][X], $lines->[$j][A][X], $lines->[$j][B][X]);
my @y = sort { $a <=> $b } ($lines->[$i][A][Y], $lines->[$i][B][Y], $lines->[$j][A][Y], $lines->[$j][B][Y]);
my $new_line = Slic3r::Line->new([$x[0], $y[0]], [$x[-1], $y[-1]]);
for (X, Y) {
($new_line->[A][$_], $new_line->[B][$_]) = ($new_line->[B][$_], $new_line->[A][$_])
if $lines->[$i][A][$_] > $lines->[$i][B][$_];
}
# save new line and remove found one
$lines->[$i] = $new_line;
splice @$lines, $j, 1;
$j--;
}
}
Slic3r::debugf " merging %d lines resulted in %d lines\n", $line_count, scalar(@$lines);
}
# build polylines from lines
sub make_surfaces {
my $self = shift;
if (0) {
printf "Layer was sliced at z = %f\n", $self->slice_z * $Slic3r::resolution;
require "Slic3r/SVG.pm";
Slic3r::SVG::output(undef, "lines.svg",
lines => [ grep !$_->isa('Slic3r::Line::FacetEdge'), @{$self->lines} ],
red_lines => [ grep $_->isa('Slic3r::Line::FacetEdge'), @{$self->lines} ],
);
}
my (@polygons, %visited_lines, @discarded_lines, @discarded_polylines) = ();
my $detect = sub {
my @lines = @{$self->lines};
(@polygons, %visited_lines, @discarded_lines, @discarded_polylines) = ();
my $get_point_id = sub { sprintf "%.0f,%.0f", @{$_[0]} };
my (%pointmap, @pointmap_keys) = ();
foreach my $line (@lines) {
my $point_id = $get_point_id->($line->[A]);
if (!exists $pointmap{$point_id}) {
$pointmap{$point_id} = [];
push @pointmap_keys, $line->[A];
}
push @{ $pointmap{$point_id} }, $line;
}
my $n = 0;
while (my $first_line = shift @lines) {
next if $visited_lines{ $first_line->id };
my @points = @$first_line;
my @seen_lines = ($first_line);
my %seen_points = map { $get_point_id->($points[$_]) => $_ } 0..1;
CYCLE: while (1) {
my $next_lines = $pointmap{ $get_point_id->($points[-1]) };
# shouldn't we find the point, let's try with a slower algorithm
# as approximation may make the coordinates differ
if (!$next_lines) {
my $nearest_point = nearest_point($points[-1], \@pointmap_keys);
#printf " we have a nearest point: %f,%f (%s)\n", @$nearest_point, $get_point_id->($nearest_point);
if ($nearest_point) {
local $Slic3r::Geometry::epsilon = 1000000;
$next_lines = $pointmap{$get_point_id->($nearest_point)}
if points_coincide($points[-1], $nearest_point);
}
}
if (0 && !$next_lines) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(undef, "no_lines.svg",
lines => [ grep !$_->isa('Slic3r::Line::FacetEdge'), @{$self->lines} ],
red_lines => [ grep $_->isa('Slic3r::Line::FacetEdge'), @{$self->lines} ],
points => [ $points[-1] ],
no_arrows => 1,
);
}
$next_lines
or die sprintf("No lines start at point %s. This shouldn't happen. Please check the model for manifoldness.\n", $get_point_id->($points[-1]));
last CYCLE if !@$next_lines;
my @ordered_next_lines = sort
{ angle3points($points[-1], $points[-2], $next_lines->[$a][B]) <=> angle3points($points[-1], $points[-2], $next_lines->[$b][B]) }
0..$#$next_lines;
#if (@$next_lines > 1) {
# Slic3r::SVG::output(undef, "next_line.svg",
# lines => $next_lines,
# red_lines => [ polyline_lines([@points]) ],
# green_lines => [ $next_lines->[ $ordered_next_lines[0] ] ],
# );
#}
my ($next_line) = splice @$next_lines, $ordered_next_lines[0], 1;
push @seen_lines, $next_line;
push @points, $next_line->[B];
my $point_id = $get_point_id->($points[-1]);
if ($seen_points{$point_id}) {
splice @points, 0, $seen_points{$point_id};
last CYCLE;
}
$seen_points{$point_id} = $#points;
}
if (@points < 4 || !points_coincide($points[0], $points[-1])) {
# discarding polyline
push @discarded_lines, @seen_lines;
if (@points > 2) {
push @discarded_polylines, [@points];
}
next;
}
$visited_lines{ $_->id } = 1 for @seen_lines;
pop @points;
Slic3r::debugf "Discovered polygon of %d points\n", scalar(@points);
push @polygons, Slic3r::Polygon->new(@points);
$polygons[-1]->cleanup;
}
};
$detect->();
# Now, if we got a clean and manifold model then @polygons would contain everything
# we need to draw our layer. In real life, sadly, things are different and it is likely
# that the above algorithm wasn't able to detect every polygon. This may happen because
# of non-manifoldness or because of many close lines, often overlapping; both situations
# make a head-to-tail search difficult.
# On the other hand, we can safely assume that every polygon we detected is correct, as
# the above algorithm is quite strict. We can take a brute force approach to connect any
# other line.
# So, let's first check what lines were not detected as part of polygons.
if (@discarded_lines) {
Slic3r::debugf " %d lines out of %d were discarded and %d polylines were not closed\n",
scalar(@discarded_lines), scalar(@{$self->lines}), scalar(@discarded_polylines);
print " Warning: errors while parsing this layer (dirty or non-manifold model).\n";
print " Retrying with slower algorithm.\n";
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(undef, "layer" . $self->id . "_detected.svg",
white_polygons => \@polygons,
);
Slic3r::SVG::output(undef, "layer" . $self->id . "_discarded_lines.svg",
red_lines => \@discarded_lines,
);
Slic3r::SVG::output(undef, "layer" . $self->id . "_discarded_polylines.svg",
polylines => \@discarded_polylines,
);
}
$self->cleanup_lines;
eval { $detect->(); };
warn $@ if $@;
if (@discarded_lines) {
print " Warning: even slow detection algorithm threw errors. Review the output before printing.\n";
}
}
my ($loops) = @_;
{
my $expolygons = union_ex([ @polygons ], PFT_EVENODD);
my $expolygons = union_ex($loops, PFT_EVENODD);
Slic3r::debugf " %d surface(s) having %d holes detected from %d polylines\n",
scalar(@$expolygons), scalar(map $_->holes, @$expolygons), scalar(@polygons);
scalar(@$expolygons), scalar(map $_->holes, @$expolygons), scalar(@$loops);
push @{$self->surfaces},
map Slic3r::Surface->cast_from_expolygon($_, surface_type => 'internal'),

View File

@ -1,9 +0,0 @@
package Slic3r::Line::FacetEdge;
use Moo;
extends 'Slic3r::Line';
use Slic3r::Line::FacetEdge::Bottom;
use Slic3r::Line::FacetEdge::Top;
1;

View File

@ -1,6 +0,0 @@
package Slic3r::Line::FacetEdge::Bottom;
use Moo;
extends 'Slic3r::Line::FacetEdge';
1;

View File

@ -1,6 +0,0 @@
package Slic3r::Line::FacetEdge::Top;
use Moo;
extends 'Slic3r::Line::FacetEdge';
1;

View File

@ -2,11 +2,14 @@ package Slic3r::Print;
use Moo;
use Math::ConvexHull 1.0.4 qw(convex_hull);
use Slic3r::Geometry qw(X Y PI);
use Slic3r::Geometry qw(X Y Z PI scale);
use Slic3r::Geometry::Clipper qw(explode_expolygons safety_offset diff_ex intersection_ex
union_ex offset JT_ROUND JT_MITER);
use XXX;
use constant MIN => 0;
use constant MAX => 1;
has 'x_length' => (
is => 'ro',
required => 1,
@ -26,11 +29,59 @@ has 'layers' => (
default => sub { [] },
);
sub new_from_stl {
my $self = shift;
my ($stl_file) = @_;
sub new_from_mesh {
my $class = shift;
my ($mesh) = @_;
my $print = Slic3r::STL->new->parse_file($stl_file);
$mesh->rotate($Slic3r::rotate);
$mesh->scale($Slic3r::scale / $Slic3r::resolution);
# calculate the displacements needed to
# have lowest value for each axis at coordinate 0
{
my @extents = $mesh->bounding_box;
my @shift = map -$extents[$_][MIN], X,Y,Z;
$mesh->move(@shift);
}
# duplicate object
{
my @size = $mesh->size;
my @duplicate_offset = (
($size[X] + scale $Slic3r::duplicate_distance),
($size[Y] + scale $Slic3r::duplicate_distance),
);
for (my $i = 2; $i <= $Slic3r::duplicate_x; $i++) {
$mesh->duplicate($duplicate_offset[X] * ($i-1), 0);
}
for (my $i = 2; $i <= $Slic3r::duplicate_y; $i++) {
$mesh->duplicate(0, $duplicate_offset[Y] * ($i-1));
}
}
# initialize print job
my @size = $mesh->size;
my $print = $class->new(
x_length => $size[X],
y_length => $size[Y],
);
$mesh->make_edge_table;
# process facets
for (my $i = 0; $i <= $#{$mesh->facets}; $i++) {
my $facet = $mesh->facets->[$i];
# transform vertex coordinates
my ($normal, @vertices) = @$facet;
$mesh->_facet($print, $i, $normal, @vertices);
}
die "Invalid input file\n" if !@{$print->layers};
# remove last layer if empty
# (we might have created it because of the $max_layer = ... + 1 code below)
pop @{$print->layers} if !@{$print->layers->[-1]->surfaces} && !@{$print->layers->[-1]->lines};
print "\n==> PROCESSING SLICES:\n";
foreach my $layer (@{ $print->layers }) {
@ -44,7 +95,7 @@ sub new_from_stl {
# inside a closed polyline)
# build surfaces from sparse lines
$layer->make_surfaces;
$layer->make_surfaces($mesh->make_loops($layer));
}
return $print;

View File

@ -5,205 +5,6 @@ use Math::Clipper qw(integerize_coordinate_sets is_counter_clockwise);
use Slic3r::Geometry qw(X Y Z three_points_aligned longest_segment);
use XXX;
use constant MIN => 0;
use constant MAX => 1;
sub parse_file {
my $self = shift;
my ($file) = @_;
# open STL file
my $facets = $self->read_file($file);
if ($Slic3r::rotate > 0) {
my $deg = Slic3r::Geometry::deg2rad($Slic3r::rotate);
foreach my $facet (@$facets) {
my ($normal, @vertices) = @$facet;
foreach my $vertex (@vertices) {
@$vertex = (@{ +(Slic3r::Geometry::rotate_points($deg, undef, [ $vertex->[X], $vertex->[Y] ]))[0] }, $vertex->[Z]);
}
}
}
# we only want to work with positive coordinates, so let's
# find our object extents to calculate coordinate displacements
my @extents = (map [99999999999, -99999999999], X,Y,Z);
foreach my $facet (@$facets) {
my ($normal, @vertices) = @$facet;
foreach my $vertex (@vertices) {
for (X,Y,Z) {
$extents[$_][MIN] = $vertex->[$_] if $vertex->[$_] < $extents[$_][MIN];
$extents[$_][MAX] = $vertex->[$_] if $vertex->[$_] > $extents[$_][MAX];
}
}
}
# scale extents
for (X,Y,Z) {
$extents[$_][MIN] *= $Slic3r::scale;
$extents[$_][MAX] *= $Slic3r::scale;
}
# duplicate object
my @duplicate_offset = (
(($extents[X][MAX] - $extents[X][MIN]) + $Slic3r::duplicate_distance),
(($extents[Y][MAX] - $extents[Y][MIN]) + $Slic3r::duplicate_distance),
);
$extents[X][MAX] += $duplicate_offset[X] * ($Slic3r::duplicate_x-1);
$extents[Y][MAX] += $duplicate_offset[Y] * ($Slic3r::duplicate_y-1);
my @copies = ();
for (my $i = 0; $i < $Slic3r::duplicate_x; $i++) {
for (my $j = 0; $j < $Slic3r::duplicate_y; $j++) {
push @copies, [ $duplicate_offset[X] * $i, $duplicate_offset[Y] * $j ];
}
}
# initialize print job
my $print = Slic3r::Print->new(
x_length => ($extents[X][MAX] - $extents[X][MIN]) / $Slic3r::resolution,
y_length => ($extents[Y][MAX] - $extents[Y][MIN]) / $Slic3r::resolution,
);
# calculate the displacements needed to
# have lowest value for each axis at coordinate 0
my @shift = map sprintf('%.0f', -$extents[$_][MIN] / $Slic3r::resolution), X,Y,Z;
# process facets
foreach my $facet (@$facets) {
# transform vertex coordinates
my ($normal, @vertices) = @$facet;
foreach my $vertex (@vertices) {
$vertex->[$_] = ($Slic3r::scale * $vertex->[$_] / $Slic3r::resolution) + $shift[$_]
for X,Y,Z;
}
foreach my $copy (@copies) {
my @copy_vertices = map [ @$_ ], @vertices; # clone vertices
foreach my $vertex (@copy_vertices) {
$vertex->[$_] += $copy->[$_] / $Slic3r::resolution for X,Y;
}
$self->_facet($print, $normal, @copy_vertices);
}
}
die "Invalid input file\n" if !@{$print->layers};
# remove last layer if empty
# (we might have created it because of the $max_layer = ... + 1 code below)
pop @{$print->layers} if !@{$print->layers->[-1]->surfaces} && !@{$print->layers->[-1]->lines};
return $print;
}
sub _facet {
my $self = shift;
my ($print, $normal, @vertices) = @_;
Slic3r::debugf "\n==> FACET (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n", map @$_, @vertices
if $Slic3r::debug;
# find the vertical extents of the facet
my ($min_z, $max_z) = (99999999999, -99999999999);
foreach my $vertex (@vertices) {
$min_z = $vertex->[Z] if $vertex->[Z] < $min_z;
$max_z = $vertex->[Z] if $vertex->[Z] > $max_z;
}
Slic3r::debugf "z: min = %.0f, max = %.0f\n", $min_z, $max_z;
if ($min_z == $max_z) {
Slic3r::debugf "Facet is horizontal; ignoring\n";
return;
}
# calculate the layer extents
# (the -1 and +1 here are used as a quick and dirty replacement for some
# complex calculation of the first layer height ratio logic)
my $min_layer = int($min_z * $Slic3r::resolution / $Slic3r::layer_height) - 1;
$min_layer = 0 if $min_layer < 0;
my $max_layer = int($max_z * $Slic3r::resolution / $Slic3r::layer_height) + 1;
Slic3r::debugf "layers: min = %s, max = %s\n", $min_layer, $max_layer;
# reorder vertices so that the first one is the one with lowest Z
# this is needed to get all intersection lines in a consistent order
# (external on the right of the line)
{
my @z_order = sort { $vertices[$a][Z] <=> $vertices[$b][Z] } 0..2;
@vertices = (splice(@vertices, $z_order[0]), splice(@vertices, 0, $z_order[0]));
}
for (my $layer_id = $min_layer; $layer_id <= $max_layer; $layer_id++) {
my $layer = $print->layer($layer_id);
$layer->add_line($_) for $self->intersect_facet(\@vertices, $layer->slice_z);
}
}
sub intersect_facet {
my $self = shift;
my ($vertices, $z) = @_;
# build the three segments of the triangle facet
my @edges = (
[ $vertices->[0], $vertices->[1] ],
[ $vertices->[1], $vertices->[2] ],
[ $vertices->[2], $vertices->[0] ],
);
my (@lines, @intersection_points) = ();
foreach my $edge (@edges) {
my ($a, $b) = @$edge;
#printf "Az = %d, Bz = %d, z = %d\n", $a->[Z], $b->[Z], $z;
if ($a->[Z] == $b->[Z] && $a->[Z] == $z) {
# edge is horizontal and belongs to the current layer
my $edge_type = (grep $_->[Z] > $z, @$vertices) ? 'Bottom' : 'Top';
($a, $b) = ($b, $a) if $edge_type eq 'Bottom';
push @lines, "Slic3r::Line::FacetEdge::$edge_type"->new(
[$a->[X], $a->[Y]], [$b->[X], $b->[Y]],
);
#print "Horizontal edge at $z!\n";
} elsif (($a->[Z] < $z && $b->[Z] > $z) || ($b->[Z] < $z && $a->[Z] > $z)) {
# edge intersects the current layer; calculate intersection
push @intersection_points, [
$b->[X] + ($a->[X] - $b->[X]) * ($z - $b->[Z]) / ($a->[Z] - $b->[Z]),
$b->[Y] + ($a->[Y] - $b->[Y]) * ($z - $b->[Z]) / ($a->[Z] - $b->[Z]),
];
#print "Intersects at $z!\n";
} elsif ($a->[Z] == $z) {
#print "A point on plane $z!\n";
push @intersection_points, [ $a->[X], $a->[Y] ];
} elsif ($b->[Z] == $z) {
#print "B point on plane $z!\n";
push @intersection_points, [ $b->[X], $b->[Y] ];
}
}
Slic3r::Geometry::remove_coinciding_points(\@intersection_points);
if (@intersection_points > 1 && !@lines) {
# remove coinciding points
# defensive programming:
die "Facets must intersect each plane 0 or 2 times" if @intersection_points != 2;
# check whether the two points coincide due to resolution rounding
#if ($intersection_points[0]->coincides_with($intersection_points[1])) {
# Slic3r::debugf "Points coincide; removing\n";
# return;
#}
# connect points:
push @lines, Slic3r::Line->new(@intersection_points);
#printf " intersection points = %f,%f - %f,%f\n", map @$_, @intersection_points;
}
return @lines;
}
sub read_file {
my $self = shift;
my ($file) = @_;
@ -238,7 +39,7 @@ sub read_file {
: _read_binary($fh, $facets);
close $fh;
return $facets;
return Slic3r::TriangleMesh->new(facets => $facets);
}
sub _read_ascii {

View File

@ -46,7 +46,7 @@ sub output {
my $g = $svg->group(
style => {
'stroke-width' => 2,
'stroke' => 'black' || $colour || 'black',
'stroke' => $colour || 'black',
'fill' => ($type !~ /polygons/ ? 'none' : ($colour || 'grey')),
},
);

View File

@ -20,7 +20,12 @@ sub go {
# skein the STL into layers
# each layer has surfaces with holes
$self->status_cb->(10, "Processing triangulated mesh...");
my $print = Slic3r::Print->new_from_stl($self->input_file);
my $print;
{
my $mesh = Slic3r::STL->read_file($self->input_file);
$mesh->check_manifoldness;
$print = Slic3r::Print->new_from_mesh($mesh);
}
# make skirt
$self->status_cb->(15, "Generating skirt...");

457
lib/Slic3r/TriangleMesh.pm Normal file
View File

@ -0,0 +1,457 @@
package Slic3r::TriangleMesh;
use Moo;
use Slic3r::Geometry qw(X Y Z A B PI epsilon same_point points_coincide angle3points
merge_collinear_lines);
use XXX;
has 'facets' => (is => 'ro', default => sub { [] });
has 'edges' => (is => 'ro', default => sub { [] });
has 'edge_table' => (is => 'ro', default => sub { {} });
has 'edge_facets' => (is => 'ro', default => sub { {} });
use constant MIN => 0;
use constant MAX => 1;
sub make_edge_table {
my $self = shift;
@{$self->edges} = ();
%{$self->edge_table} = ();
%{$self->edge_facets} = ();
for (my $facet_index = 0; $facet_index <= $#{$self->facets}; $facet_index++) {
my $facet = $self->facets->[$facet_index];
foreach my $edge ($self->facet_edges($facet)) {
my $edge_id = $self->edge_id($edge);
if (!exists $self->edge_table->{$edge_id}) {
push @{$self->edges}, $edge;
$self->edge_table->{$edge_id} = $#{$self->edges};
$self->edge_facets->{$edge_id} = [];
}
my $edge_index = $self->edge_table->{$edge_id};
push @{$self->edge_facets->{$edge_id}}, $facet_index;
}
}
}
sub check_manifoldness {
my $self = shift;
$self->make_edge_table;
if (grep { @$_ != 2 } values %{$self->edge_facets}) {
warn "Warning: The input file is not manifold. You might want to check the "
. "resulting gcode before printing.\n";
}
}
sub make_loops {
my $self = shift;
my ($layer) = @_;
my @lines = @{$layer->lines};
# remove tangent edges
{
for (my $i = 0; $i <= $#lines; $i++) {
next unless defined $lines[$i] && $lines[$i]->facet_edge;
# if the line is a facet edge, find another facet edge
# having the same endpoints but in reverse order
for (my $j = $i+1; $j <= $#lines; $j++) {
next unless defined $lines[$j] && defined $lines[$j]->facet_edge;
next unless $lines[$j]->facet_edge eq $lines[$i]->facet_edge;
if (same_point($lines[$i]->a, $lines[$j]->b) && same_point($lines[$i]->b, $lines[$j]->a)) {
$lines[$j] = undef;
last;
}
}
}
}
my $sparse_lines = [ map $_->line, @lines ];
# detect closed loops
if (0) {
printf "Layer was sliced at z = %f\n", $self->slice_z * $Slic3r::resolution;
require "Slic3r/SVG.pm";
Slic3r::SVG::output(undef, "lines.svg",
lines => [ grep !$_->isa('Slic3r::Line::FacetEdge'), @lines ],
red_lines => [ grep $_->isa('Slic3r::Line::FacetEdge'), @lines ],
);
}
my (@polygons, %visited_lines, @discarded_lines, @discarded_polylines) = ();
my $detect = sub {
my @lines = @$sparse_lines;
(@polygons, %visited_lines, @discarded_lines, @discarded_polylines) = ();
my $get_point_id = sub { sprintf "%.0f,%.0f", @{$_[0]} };
my (%pointmap, @pointmap_keys) = ();
foreach my $line (@lines) {
my $point_id = $get_point_id->($line->[A]);
if (!exists $pointmap{$point_id}) {
$pointmap{$point_id} = [];
push @pointmap_keys, $line->[A];
}
push @{ $pointmap{$point_id} }, $line;
}
my $n = 0;
while (my $first_line = shift @lines) {
next if $visited_lines{ $first_line->id };
my @points = @$first_line;
my @seen_lines = ($first_line);
my %seen_points = map { $get_point_id->($points[$_]) => $_ } 0..1;
CYCLE: while (1) {
my $next_lines = $pointmap{ $get_point_id->($points[-1]) };
# shouldn't we find the point, let's try with a slower algorithm
# as approximation may make the coordinates differ
if (!$next_lines) {
my $nearest_point = nearest_point($points[-1], \@pointmap_keys);
#printf " we have a nearest point: %f,%f (%s)\n", @$nearest_point, $get_point_id->($nearest_point);
if ($nearest_point) {
local $Slic3r::Geometry::epsilon = 1000000;
$next_lines = $pointmap{$get_point_id->($nearest_point)}
if points_coincide($points[-1], $nearest_point);
}
}
if (0 && !$next_lines) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(undef, "no_lines.svg",
lines => [ grep !$_->isa('Slic3r::Line::FacetEdge'), @lines ],
red_lines => [ grep $_->isa('Slic3r::Line::FacetEdge'), @lines ],
points => [ $points[-1] ],
no_arrows => 1,
);
}
$next_lines
or die sprintf("No lines start at point %s. This shouldn't happen. Please check the model for manifoldness.\n", $get_point_id->($points[-1]));
last CYCLE if !@$next_lines;
my @ordered_next_lines = sort
{ angle3points($points[-1], $points[-2], $next_lines->[$a][B]) <=> angle3points($points[-1], $points[-2], $next_lines->[$b][B]) }
0..$#$next_lines;
#if (@$next_lines > 1) {
# Slic3r::SVG::output(undef, "next_line.svg",
# lines => $next_lines,
# red_lines => [ polyline_lines([@points]) ],
# green_lines => [ $next_lines->[ $ordered_next_lines[0] ] ],
# );
#}
my ($next_line) = splice @$next_lines, $ordered_next_lines[0], 1;
push @seen_lines, $next_line;
push @points, $next_line->[B];
my $point_id = $get_point_id->($points[-1]);
if ($seen_points{$point_id}) {
splice @points, 0, $seen_points{$point_id};
last CYCLE;
}
$seen_points{$point_id} = $#points;
}
if (@points < 4 || !points_coincide($points[0], $points[-1])) {
# discarding polyline
push @discarded_lines, @seen_lines;
if (@points > 2) {
push @discarded_polylines, [@points];
}
next;
}
$visited_lines{ $_->id } = 1 for @seen_lines;
pop @points;
Slic3r::debugf "Discovered polygon of %d points\n", scalar(@points);
push @polygons, Slic3r::Polygon->new(@points);
$polygons[-1]->cleanup;
}
};
$detect->();
# Now, if we got a clean and manifold model then @polygons would contain everything
# we need to draw our layer. In real life, sadly, things are different and it is likely
# that the above algorithm wasn't able to detect every polygon. This may happen because
# of non-manifoldness or because of many close lines, often overlapping; both situations
# make a head-to-tail search difficult.
# On the other hand, we can safely assume that every polygon we detected is correct, as
# the above algorithm is quite strict. We can take a brute force approach to connect any
# other line.
# So, let's first check what lines were not detected as part of polygons.
if (@discarded_lines) {
Slic3r::debugf " %d lines out of %d were discarded and %d polylines were not closed\n",
scalar(@discarded_lines), scalar(@lines), scalar(@discarded_polylines);
print " Warning: errors while parsing this layer (dirty or non-manifold model).\n";
print " Retrying with slower algorithm.\n";
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(undef, "layer" . $self->id . "_detected.svg",
white_polygons => \@polygons,
);
Slic3r::SVG::output(undef, "layer" . $self->id . "_discarded_lines.svg",
red_lines => \@discarded_lines,
);
Slic3r::SVG::output(undef, "layer" . $self->id . "_discarded_polylines.svg",
polylines => \@discarded_polylines,
);
}
$sparse_lines = merge_collinear_lines($sparse_lines);
eval { $detect->(); };
warn $@ if $@;
if (@discarded_lines) {
print " Warning: even slow detection algorithm threw errors. Review the output before printing.\n";
}
}
return [@polygons];
}
sub rotate {
my $self = shift;
my ($deg) = @_;
return if $deg == 0;
my $rad = Slic3r::Geometry::deg2rad($deg);
foreach my $facet (@{$self->facets}) {
my ($normal, @vertices) = @$facet;
foreach my $vertex (@vertices) {
@$vertex = (@{ +(Slic3r::Geometry::rotate_points($rad, undef, [ $vertex->[X], $vertex->[Y] ]))[0] }, $vertex->[Z]);
}
}
}
sub scale {
my $self = shift;
my ($factor) = @_;
return if $factor == 1;
foreach my $facet (@{$self->facets}) {
# transform vertex coordinates
my ($normal, @vertices) = @$facet;
foreach my $vertex (@vertices) {
$vertex->[$_] *= $factor for X,Y,Z;
}
}
}
sub move {
my $self = shift;
my (@shift) = @_;
foreach my $facet (@{$self->facets}) {
# transform vertex coordinates
my ($normal, @vertices) = @$facet;
foreach my $vertex (@vertices) {
$vertex->[$_] += $shift[$_] for X,Y,Z;
}
}
}
sub duplicate {
my $self = shift;
my (@shift) = @_;
my @new_facets = ();
foreach my $facet (@{$self->facets}) {
# transform vertex coordinates
my ($normal, @vertices) = @$facet;
push @new_facets, [ $normal ];
foreach my $vertex (@vertices) {
push @{$new_facets[-1]}, [ map $vertex->[$_] + ($shift[$_] || 0), (X,Y,Z) ];
}
}
push @{$self->facets}, @new_facets;
}
sub bounding_box {
my $self = shift;
my @extents = (map [99999999999, -99999999999], X,Y,Z);
foreach my $facet (@{$self->facets}) {
my ($normal, @vertices) = @$facet;
foreach my $vertex (@vertices) {
for (X,Y,Z) {
$extents[$_][MIN] = $vertex->[$_] if $vertex->[$_] < $extents[$_][MIN];
$extents[$_][MAX] = $vertex->[$_] if $vertex->[$_] > $extents[$_][MAX];
}
}
}
return @extents;
}
sub size {
my $self = shift;
my @extents = $self->bounding_box;
return map $extents[$_][MAX] - $extents[$_][MIN], (X,Y,Z);
}
sub _facet {
my $self = shift;
my ($print, $facet_index, $normal, @vertices) = @_;
Slic3r::debugf "\n==> FACET %d (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n",
$facet_index, map @$_, @vertices
if $Slic3r::debug;
# find the vertical extents of the facet
my ($min_z, $max_z) = (99999999999, -99999999999);
foreach my $vertex (@vertices) {
$min_z = $vertex->[Z] if $vertex->[Z] < $min_z;
$max_z = $vertex->[Z] if $vertex->[Z] > $max_z;
}
Slic3r::debugf "z: min = %.0f, max = %.0f\n", $min_z, $max_z;
if ($min_z == $max_z) {
Slic3r::debugf "Facet is horizontal; ignoring\n";
return;
}
# calculate the layer extents
# (the -1 and +1 here are used as a quick and dirty replacement for some
# complex calculation of the first layer height ratio logic)
my $min_layer = int($min_z * $Slic3r::resolution / $Slic3r::layer_height) - 1;
$min_layer = 0 if $min_layer < 0;
my $max_layer = int($max_z * $Slic3r::resolution / $Slic3r::layer_height) + 1;
Slic3r::debugf "layers: min = %s, max = %s\n", $min_layer, $max_layer;
# reorder vertices so that the first one is the one with lowest Z
# this is needed to get all intersection lines in a consistent order
# (external on the right of the line)
{
my @z_order = sort { $vertices[$a][Z] <=> $vertices[$b][Z] } 0..2;
@vertices = (splice(@vertices, $z_order[0]), splice(@vertices, 0, $z_order[0]));
}
for (my $layer_id = $min_layer; $layer_id <= $max_layer; $layer_id++) {
my $layer = $print->layer($layer_id);
my @intersections = $self->intersect_facet($facet_index, \@vertices, $layer->slice_z);
if ($facet_index =~ /^(488)$/ && $layer_id == 14) {
printf "z = %f\n", $layer->slice_z;
YYY \@intersections;
#exit if $facet_index == 488;
}
$layer->add_line($_) for @intersections;
}
}
sub intersect_facet {
my $self = shift;
my ($facet_index, $vertices, $z) = @_;
# build the three segments of the triangle facet
my @edges = $self->facet_edges($vertices);
my (@lines, @points, @intersection_points, @points_on_layer) = ();
foreach my $edge (@edges) {
my ($a, $b) = @$edge;
my $edge_id = $self->edge_id($edge);
#printf "Az = %f, Bz = %f, z = %f\n", $a->[Z], $b->[Z], $z;
if (abs($a->[Z] - $b->[Z]) < epsilon && abs($a->[Z] - $z) < epsilon) {
# edge is horizontal and belongs to the current layer
my $edge_type = (grep $_->[Z] > $z, @$vertices) ? 'bottom' : 'top';
($a, $b) = ($b, $a) if $edge_type eq 'bottom';
push @lines, Slic3r::TriangleMesh::IntersectionLine->new(
a => [$a->[X], $a->[Y]],
b => [$b->[X], $b->[Y]],
a_id => sprintf("%f,%f", @$a[X,Y]),
b_id => sprintf("%f,%f", @$b[X,Y]),
facet_edge => $edge_type,
facet_index => $facet_index,
);
#print "Horizontal edge at $z!\n";
} elsif (abs($a->[Z] - $z) < epsilon) {
#print "A point on plane $z!\n";
push @points, [ $a->[X], $a->[Y], sprintf("%f,%f", @$a[X,Y]) ];
push @points_on_layer, $#points;
} elsif (abs($b->[Z] - $z) < epsilon) {
#print "B point on plane $z!\n";
push @points, [ $b->[X], $b->[Y], sprintf("%f,%f", @$b[X,Y]) ];
push @points_on_layer, $#points;
} elsif (($a->[Z] < ($z - epsilon) && $b->[Z] > ($z + epsilon))
|| ($b->[Z] < ($z - epsilon) && $a->[Z] > ($z + epsilon))) {
# edge intersects the current layer; calculate intersection
push @points, [
$b->[X] + ($a->[X] - $b->[X]) * ($z - $b->[Z]) / ($a->[Z] - $b->[Z]),
$b->[Y] + ($a->[Y] - $b->[Y]) * ($z - $b->[Z]) / ($a->[Z] - $b->[Z]),
$edge_id,
$edge_id,
];
push @intersection_points, $#points;
#print "Intersects at $z!\n";
}
}
return @lines if @lines;
if (@points_on_layer == 2 && @intersection_points == 1) {
$points[ $points_on_layer[1] ] = undef;
@points = grep $_, @points;
}
if (@points_on_layer == 2 && @intersection_points == 0) {
if (same_point(map $points[$_], @points_on_layer)) {
return ();
}
}
if (@points) {
# defensive programming:
die "Facets must intersect each plane 0 or 2 times" if @points != 2;
# connect points:
return Slic3r::TriangleMesh::IntersectionLine->new(
a => [$points[A][X], $points[A][Y]],
b => [$points[B][X], $points[B][Y]],
a_id => $points[A][2],
b_id => $points[B][2],
facet_index => $facet_index,
prev_facet_index => ($points[A][3] ? +(grep $_ != $facet_index, @{$self->edge_facets->{$points[A][3]}})[0] || undef : undef),
next_facet_index => ($points[B][3] ? +(grep $_ != $facet_index, @{$self->edge_facets->{$points[B][3]}})[0] || undef : undef),
);
#printf " intersection points at z = %f: %f,%f - %f,%f\n", $z, map @$_, @intersection_points;
}
return ();
}
sub facet_edges {
my $self = shift;
my ($facet) = @_;
# ignore the normal if provided
my @vertices = @$facet[-3..-1];
return (
[ $vertices[0], $vertices[1] ],
[ $vertices[1], $vertices[2] ],
[ $vertices[2], $vertices[0] ],
)
}
sub edge_id {
my $self = shift;
my ($edge) = @_;
my @point_ids = map sprintf("%f,%f,%f", @$_), @$edge;
return join "-", sort @point_ids;
}
1;

View File

@ -0,0 +1,23 @@
package Slic3r::TriangleMesh::IntersectionLine;
use Moo;
has 'a' => (is => 'ro', required => 1);
has 'b' => (is => 'ro', required => 1);
has 'a_id' => (is => 'ro', required => 1);
has 'b_id' => (is => 'ro', required => 1);
has 'facet_index' => (is => 'ro', required => 1);
has 'prev_facet_index' => (is => 'ro', required => 0);
has 'next_facet_index' => (is => 'ro', required => 0);
has 'facet_edge' => (is => 'ro', default => sub {0});
sub points {
my $self = shift;
return [$self->a, $self->b];
}
sub line {
my $self = shift;
return Slic3r::Line->new($self->a, $self->b);
}
1;

View File

@ -28,6 +28,7 @@ isnt Slic3r::Geometry::line_intersection($line1, $line2, 1), undef, 'line_inters
#==========================================================
{
my $polyline = [
[459190000, 5152739000], [147261000, 4612464000], [147261000, 3487535000], [339887000, 3153898000],
[437497000, 3438430000], [454223000, 3522515000], [523621000, 3626378000], [627484000, 3695776000],
@ -40,9 +41,11 @@ my $polyline = [
# this points belongs to $polyline
my $point = [2797980957.103410,3392691792.513960];
local $Slic3r::Geometry::epsilon = 1E-5;
is_deeply Slic3r::Geometry::polygon_segment_having_point($polyline, $point),
[ [2540810000, 2947261000], [2852739000, 3487535000] ],
'polygon_segment_having_point';
}
#==========================================================

21
t/stl.t
View File

@ -10,8 +10,10 @@ BEGIN {
}
use Slic3r;
use Slic3r::Geometry qw(X Y Z);
use XXX;
my $stl = Slic3r::STL->new;
my $mesh = Slic3r::TriangleMesh->new;
my @lines;
my $z = 20;
@ -30,15 +32,20 @@ is_deeply lines(28, 20, 30), [ ], 'lower vertex on la
is_deeply lines(24, 10, 16), [ [ [4, 4], [2, 6] ] ], 'two edges intersect';
is_deeply lines(24, 10, 20), [ [ [4, 4], [1, 9] ] ], 'one vertex on plane and one edge intersects';
my @lower = $stl->intersect_facet(vertices(22, 20, 20), $z);
my @upper = $stl->intersect_facet(vertices(20, 20, 10), $z);
isa_ok $lower[0], 'Slic3r::Line::FacetEdge::Bottom', 'bottom edge on layer';
isa_ok $upper[0], 'Slic3r::Line::FacetEdge::Top', 'upper edge on layer';
my @lower = $mesh->intersect_facet(0, vertices(22, 20, 20), $z);
my @upper = $mesh->intersect_facet(0, vertices(20, 20, 10), $z);
is $lower[0]->facet_edge, 'bottom', 'bottom edge on layer';
is $upper[0]->facet_edge, 'top', 'upper edge on layer';
sub vertices {
[ map [ @{$points[$_]}, $_[$_] ], 0..2 ]
[ map [ @{$points[$_]}, $_[$_] ], X,Y,Z ]
}
sub lines {
[ map [ map [ map sprintf('%.0f', $_), @$_ ], @$_ ], $stl->intersect_facet(vertices(@_), $z) ];
my @lines = $mesh->intersect_facet(0, vertices(@_), $z);
$_->a->[X] = sprintf('%.0f', $_->a->[X]) for @lines;
$_->a->[Y] = sprintf('%.0f', $_->a->[Y]) for @lines;
$_->b->[X] = sprintf('%.0f', $_->b->[X]) for @lines;
$_->b->[Y] = sprintf('%.0f', $_->b->[Y]) for @lines;
return [ map $_->points, @lines ];
}