-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 55a523e
Showing
12 changed files
with
891 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
_Q: Yet another RepRap slicer?_ | ||
|
||
A: Yes. | ||
|
||
# Slic3r | ||
|
||
## What's it? | ||
|
||
Slic3r is (er, will be) an STL-to-GCODE translator for RepRap 3D printers, | ||
like Enrique's Skeinforge or RevK's E3D. | ||
|
||
## Why another one? Why Perl? | ||
|
||
The goal is to build something more maintainable and flexible than both | ||
Skeinforge and E3D. The code makes extensive use of object-oriented | ||
programming to achieve some level of abstraction instead of working with | ||
raw geometry and low-level data structures. | ||
This should help to maintain code, fix bugs and implement new and better | ||
algorithms in the future. | ||
Of course, Perl's not that fast as C and usage of modules like Moose make | ||
everything quite memory-hungry, but I'm happy with it. My goal is a "rapid | ||
prototyping" architecture for a slicer. | ||
|
||
Also, http://xkcd.com/224/ | ||
|
||
## What's its current status? | ||
|
||
Slic3r can now successfully parse and analyze an STL file by slicing it in | ||
layers and representing internally the following features: | ||
|
||
* holes in surfaces; | ||
* external top/bottom surfaces. | ||
|
||
This kind of abstraction will allow to implement particular logic and allow the | ||
user to specify custom options. | ||
|
||
I need to implement algorithms to produce perimeter outlines and surface fill. | ||
|
||
Future goals include support material, options to control bridges, skirt, cool. | ||
|
||
## Can I help? | ||
|
||
Sure! Send patches and/or drop me a line at [email protected]. You can also | ||
find me in #RepRap on FreeNode with the nickname _Sound_. | ||
|
||
## What's Slic3r license? | ||
|
||
Slic3r is dual-licensed under the _Perl Artistic License_ and the _AGPLv3_. | ||
The author is Alessandro Ranellucci (that's me). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package Slic3r; | ||
|
||
use strict; | ||
use warnings; | ||
|
||
use Slic3r::Layer; | ||
use Slic3r::Line; | ||
use Slic3r::Point; | ||
use Slic3r::Polyline; | ||
use Slic3r::Polyline::Closed; | ||
use Slic3r::Print; | ||
use Slic3r::STL; | ||
use Slic3r::Surface; | ||
|
||
our $layer_height = 0.4; | ||
our $resolution = 0.1; | ||
|
||
1; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,325 @@ | ||
package Slic3r::Layer; | ||
use Moose; | ||
|
||
use XXX; | ||
|
||
has 'id' => ( | ||
is => 'ro', | ||
isa => 'Int', | ||
required => 1, | ||
); | ||
|
||
has 'pointmap' => ( | ||
traits => ['Hash'], | ||
is => 'rw', | ||
isa => 'HashRef[Slic3r::Point]', | ||
default => sub { {} }, | ||
handles => { | ||
points => 'values', | ||
}, | ||
); | ||
|
||
has 'lines' => ( | ||
is => 'rw', | ||
isa => 'ArrayRef[Slic3r::Line]', | ||
default => sub { [] }, | ||
); | ||
|
||
has 'surfaces' => ( | ||
traits => ['Array'], | ||
is => 'rw', | ||
isa => 'ArrayRef[Slic3r::Surface]', | ||
default => sub { [] }, | ||
); | ||
|
||
sub z { | ||
my $self = shift; | ||
return $self->id * $Slic3r::layer_height / $Slic3r::resolution; | ||
} | ||
|
||
sub add_surface { | ||
my $self = shift; | ||
my (@vertices) = @_; | ||
|
||
my @points = map $self->add_point($_), @vertices; | ||
my $polyline = Slic3r::Polyline::Closed->new_from_points(@points); | ||
my @lines = map $self->add_line($_), @{ $polyline->lines }; | ||
|
||
my $surface = Slic3r::Surface->new( | ||
contour => Slic3r::Polyline::Closed->new(lines => \@lines), | ||
); | ||
push @{ $self->surfaces }, $surface; | ||
|
||
return $surface; | ||
} | ||
|
||
sub add_line { | ||
my $self = shift; | ||
my ($a, $b) = @_; | ||
|
||
# we accept either a Line object or a couple of points | ||
my $line; | ||
if ($b) { | ||
($a, $b) = map $self->add_point($_), ($a, $b); | ||
$line = Slic3r::Line->new(a => $a, b => $b); | ||
} elsif (ref $a eq 'Slic3r::Line') { | ||
$line = $a; | ||
} | ||
|
||
# check whether we already have such a line | ||
foreach my $point ($line->a, $line->b) { | ||
foreach my $existing_line (grep $_, @{$point->lines}) { | ||
return $existing_line | ||
if $line->coincides_with($existing_line) && $line ne $existing_line; | ||
} | ||
} | ||
|
||
push @{ $self->lines }, $line; | ||
return $line; | ||
} | ||
|
||
sub add_point { | ||
my $self = shift; | ||
my ($point) = @_; | ||
|
||
# we accept either a Point object or a pair of coordinates | ||
if (ref $point eq 'ARRAY') { | ||
$point = Slic3r::Point->new('x' => $point->[0], 'y' => $point->[1]); | ||
} | ||
|
||
# check whether we already defined this point | ||
if (my $existing_point = $self->pointmap_get($point->x, $point->y)) { #) | ||
return $existing_point; | ||
} | ||
|
||
# define the new point | ||
$self->pointmap->{ $point->id } = $point; #}} | ||
|
||
return $point; | ||
} | ||
|
||
sub pointmap_get { | ||
my $self = shift; | ||
my ($x, $y) = @_; | ||
|
||
return $self->pointmap->{"$x,$y"}; | ||
} | ||
|
||
sub remove_point { | ||
my $self = shift; | ||
my ($point) = @_; | ||
|
||
delete $self->pointmap->{ $point->id }; #}} | ||
} | ||
|
||
sub remove_line { | ||
my $self = shift; | ||
my ($line) = @_; | ||
@{ $self->lines } = grep $_ ne $line, @{ $self->lines }; | ||
} | ||
|
||
sub remove_surface { | ||
my $self = shift; | ||
my ($surface) = @_; | ||
@{ $self->surfaces } = grep $_ ne $surface, @{ $self->surfaces }; | ||
} | ||
|
||
# merge parallel and continuous lines | ||
sub merge_continuous_lines { | ||
my $self = shift; | ||
|
||
my $finished = 0; | ||
CYCLE: while (!$finished) { | ||
foreach my $line (@{ $self->lines }) { | ||
# TODO: we shouldn't skip lines already included in polylines | ||
next if $line->polyline; | ||
my $slope = $line->slope; | ||
|
||
foreach my $point ($line->points) { | ||
# skip points connecting more than two lines | ||
next if @{ $point->lines } > 2; | ||
|
||
foreach my $neighbor_line (@{ $point->lines }) { | ||
next if $neighbor_line eq $line; | ||
|
||
# skip line if it's not parallel to ours | ||
my $neighbor_slope = $neighbor_line->slope; | ||
next if (!defined $neighbor_slope && defined $slope) | ||
|| (defined $neighbor_slope && !defined $slope) | ||
|| (defined $neighbor_slope && defined $slope && $neighbor_slope != $slope); | ||
|
||
# create new line | ||
my ($a, $b) = grep $_ ne $point, $line->points, $neighbor_line->points; | ||
my $new_line = $self->add_line($a, $b); | ||
printf "Merging continuous lines %s and %s into %s\n", | ||
$line->id, $neighbor_line->id, $new_line->id; | ||
|
||
# delete merged lines | ||
$self->remove_line($_) for ($line, $neighbor_line); | ||
|
||
# restart cycle | ||
next CYCLE; | ||
} | ||
} | ||
} | ||
$finished = 1; | ||
} | ||
} | ||
|
||
# build polylines of lines which do not already belong to a surface | ||
sub make_polylines { | ||
my $self = shift; | ||
|
||
# defensive programming: let's check that every point | ||
# connects at least two lines | ||
foreach my $point ($self->points) { | ||
if (grep $_, @{ $point->lines } < 2) { | ||
warn "Found point connecting less than 2 lines:"; | ||
XXX $point; | ||
} | ||
} | ||
|
||
my $polylines = []; | ||
foreach my $line (@{ $self->lines }) { | ||
next if $line->polyline; | ||
|
||
my %points = map {$_ => $_} $line->points; | ||
my %visited_lines = (); | ||
my ($cur_line, $next_line) = ($line, undef); | ||
while (!$next_line || $next_line ne $line) { | ||
$visited_lines{ $cur_line } = $cur_line; | ||
|
||
$next_line = +(grep !$visited_lines{$_}, $cur_line->neighbors)[0] | ||
or last; | ||
|
||
$points{$_} = $_ for grep $_ ne $cur_line->a && $_ ne $cur_line->b, $next_line->points; | ||
$cur_line = $next_line; | ||
} | ||
|
||
printf "Discovered polyline of %d lines (%s)\n", scalar keys %points, | ||
join('-', map $_->id, values %visited_lines); | ||
push @$polylines, Slic3r::Polyline::Closed->new(lines => [values %visited_lines]); | ||
} | ||
|
||
return $polylines; | ||
} | ||
|
||
sub make_surfaces { | ||
my $self = shift; | ||
my ($polylines) = @_; | ||
|
||
# count how many other polylines enclose each polyline | ||
# even = contour; odd = hole | ||
my %enclosing_polylines = (); | ||
my %enclosing_polylines_count = (); | ||
my $max_depth = 0; | ||
foreach my $polyline (@$polylines) { | ||
# a polyline encloses another one if any point of it is enclosed | ||
# in the other | ||
my $point = $polyline->lines->[0]->a; | ||
$enclosing_polylines{$polyline} = | ||
[ grep $_ ne $polyline && $_->encloses_point($point), @$polylines ]; | ||
$enclosing_polylines_count{$polyline} = scalar @{ $enclosing_polylines{$polyline} }; | ||
|
||
$max_depth = $enclosing_polylines_count{$polyline} | ||
if $enclosing_polylines_count{$polyline} > $max_depth; | ||
} | ||
|
||
# start looking at most inner polylines | ||
for (; $max_depth > -1; $max_depth--) { | ||
foreach my $polyline (@$polylines) { | ||
next if $polyline->contour_of or $polyline->hole_of; | ||
next unless $enclosing_polylines_count{$polyline} == $max_depth; | ||
|
||
my $surface; | ||
if ($enclosing_polylines_count{$polyline} % 2 == 0) { | ||
# this is a contour | ||
$surface = Slic3r::Surface->new(contour => $polyline); | ||
} else { | ||
# this is a hole | ||
# find the enclosing polyline having immediately close depth | ||
my ($contour) = grep $enclosing_polylines_count{$_} == ($max_depth-1), | ||
@{ $enclosing_polylines{$polyline} }; | ||
|
||
if ($contour->contour_of) { | ||
$surface = $contour->contour_of; | ||
$surface->add_hole($polyline); | ||
} else { | ||
$surface = Slic3r::Surface->new( | ||
contour => $contour, | ||
holes => [$polyline], | ||
); | ||
} | ||
} | ||
$surface->surface_type('internal'); | ||
push @{ $self->surfaces }, $surface; | ||
|
||
printf "New surface: %s (holes: %s)\n", | ||
$surface->id, join(', ', map $_->id, @{$surface->holes}) || 'none'; | ||
} | ||
} | ||
} | ||
|
||
sub merge_contiguous_surfaces { | ||
my $self = shift; | ||
|
||
my $finished = 0; | ||
CYCLE: while (!$finished) { | ||
foreach my $surface (@{ $self->surfaces }) { | ||
# look for a surface sharing one edge with this one | ||
foreach my $neighbor_surface (@{ $self->surfaces }) { | ||
next if $surface eq $neighbor_surface; | ||
|
||
# find lines shared by the two surfaces (might be 0, 1, 2) | ||
my @common_lines = (); | ||
foreach my $line (@{ $neighbor_surface->contour->lines }) { | ||
next unless grep $_ eq $line, @{ $surface->contour->lines }; | ||
push @common_lines, $line; | ||
} | ||
next if !@common_lines; | ||
|
||
# defensive programming | ||
if (@common_lines > 2) { | ||
printf "Surfaces %s and %s share %d lines! How's it possible?\n", | ||
$surface->id, $neighbor_surface->id, scalar @common_lines; | ||
} | ||
|
||
printf "Surfaces %s and %s share line/lines %s!\n", | ||
$surface->id, $neighbor_surface->id, | ||
join(', ', map $_->id, @common_lines); | ||
|
||
# defensive programming | ||
if ($surface->surface_type ne $neighbor_surface->surface_type) { | ||
die "Surfaces %s and %s are of different types: %s, %s!\n", | ||
$surface->id, $neighbor_surface->id, | ||
$surface->surface_type, $neighbor_surface->surface_type; | ||
} | ||
|
||
# build new contour taking all lines of the surfaces' contours | ||
# and removing the ones that matched | ||
my @new_lines = map @{$_->contour->lines}, $surface, $neighbor_surface; | ||
foreach my $line (@common_lines) { | ||
@new_lines = grep $_ ne $line, @new_lines; | ||
} | ||
my $new_contour = Slic3r::Polyline::Closed->new( | ||
lines => [ @new_lines ], | ||
); | ||
|
||
# build new surface by combining all holes in the two surfaces | ||
my $new_surface = Slic3r::Surface->new( | ||
contour => $new_contour, | ||
holes => [ map @{$_->holes}, $surface, $neighbor_surface ], | ||
surface_type => $surface->surface_type, | ||
); | ||
|
||
printf " merging into new surface %s\n", $new_surface->id; | ||
push @{ $self->surfaces }, $surface; | ||
|
||
$self->remove_surface($_) for ($surface, $neighbor_surface); | ||
} | ||
} | ||
$finished = 1; | ||
} | ||
} | ||
|
||
1; |
Oops, something went wrong.