mirror of https://github.com/vitalif/Slic3r
More work on PathsToGCode
parent
dcc89126cf
commit
42a2ea31ec
|
@ -1,6 +1,7 @@
|
||||||
package Slic3r::PathsToGCode;
|
package Slic3r::PathsToGCode;
|
||||||
use Moo;
|
use Moo;
|
||||||
|
|
||||||
|
use Slic3r::Geometry qw(X Y unscale);
|
||||||
use XML::SAX;
|
use XML::SAX;
|
||||||
|
|
||||||
has 'start_gcode' => (is => 'ro', default => sub {<<"EOF"});
|
has 'start_gcode' => (is => 'ro', default => sub {<<"EOF"});
|
||||||
|
@ -8,18 +9,19 @@ G21 ; set units to millimeters
|
||||||
G90 ; use absolute coordinates
|
G90 ; use absolute coordinates
|
||||||
EOF
|
EOF
|
||||||
has 'end_gcode' => (is => 'ro', default => sub {''});
|
has 'end_gcode' => (is => 'ro', default => sub {''});
|
||||||
has 'path_speed' => (is => 'ro'); # mm/sec
|
has 'before_path_gcode' => (is => 'ro', default => sub {''});
|
||||||
has 'travel_speed' => (is => 'ro'); # mm/sec
|
has 'after_path_gcode' => (is => 'ro', default => sub {''});
|
||||||
has 'placement' => (is => 'ro', default => sub { 'align' }); # center/align
|
has 'placement' => (is => 'ro', default => sub { 'align' }); # center/align
|
||||||
has 'origin' => (is => 'ro', default => sub { Slic3r::Point->new(0,0) }); # scaled Slic3r::Point
|
has 'origin' => (is => 'ro', default => sub { Slic3r::Point->new(0,0) }); # scaled Slic3r::Point
|
||||||
has 'scale' => (is => 'ro', default => sub { 1 }); # factor
|
has 'scale' => (is => 'ro', default => sub { 1 }); # factor
|
||||||
|
has 'reorder' => (is => 'ro', default => sub { 1 }); # bool
|
||||||
|
has 'start_near' => (is => 'ro', default => sub { Slic3r::Point->new(0,0) }); # scaled Slic3r::Point
|
||||||
|
|
||||||
sub process_svg {
|
sub process_svg {
|
||||||
my ($self, $svgfile, $gcodefile) = @_;
|
my ($self, $svgfile, $gcodefile) = @_;
|
||||||
|
|
||||||
open my $fh, '>', $gcodefile or die "Failed to open output file $gcodefile\n";
|
open my $fh, '>', $gcodefile or die "Failed to open output file $gcodefile\n";
|
||||||
print $fh $self->start_gcode;
|
print $fh $self->start_gcode;
|
||||||
printf $fh "G1 F%d\n", $self->speed * 60 if $self->speed;
|
|
||||||
|
|
||||||
my $parser = XML::SAX::ParserFactory->parser(
|
my $parser = XML::SAX::ParserFactory->parser(
|
||||||
Handler => (my $handler = Slic3r::PathsToGCode::SVGParser->new),
|
Handler => (my $handler = Slic3r::PathsToGCode::SVGParser->new),
|
||||||
|
@ -27,9 +29,31 @@ sub process_svg {
|
||||||
$parser->parse_uri($svgfile);
|
$parser->parse_uri($svgfile);
|
||||||
my $collection = Slic3r::Polyline::Collection->new(@{ $handler->{_paths} });
|
my $collection = Slic3r::Polyline::Collection->new(@{ $handler->{_paths} });
|
||||||
|
|
||||||
|
$collection->scale($self->scale);
|
||||||
|
{
|
||||||
my $bb = $collection->bounding_box;
|
my $bb = $collection->bounding_box;
|
||||||
if ($self->placement eq 'align') {
|
$collection->translate(-$bb->x_min + $self->origin->x, -$bb->y_min + $self->origin->y); #))
|
||||||
|
if ($self->placement eq 'center') {
|
||||||
|
my $size = $bb->size;
|
||||||
|
$collection->translate(-$size->[X]/2, -$size->[Y]/2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# reorder paths if requested
|
||||||
|
my @polylines = ();
|
||||||
|
if ($self->reorder) {
|
||||||
|
my $c = $collection->chained_path_from($self->start_near, 0);
|
||||||
|
@polylines = map $_->clone, @$c;
|
||||||
|
} else {
|
||||||
|
@polylines = @$collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach my $polyline (@polylines) {
|
||||||
|
my @points = @$polyline;
|
||||||
|
printf $fh "G1 X%.4f Y%.4f\n", map unscale($_), @$_ for shift @points;
|
||||||
|
print $fh $self->before_path_gcode . "\n" if $self->before_path_gcode;
|
||||||
|
printf $fh "G1 X%.4f Y%.4f\n", map unscale($_), @$_ for @points;
|
||||||
|
print $fh $self->after_path_gcode . "\n" if $self->after_path_gcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
close $fh;
|
close $fh;
|
||||||
|
@ -37,7 +61,8 @@ sub process_svg {
|
||||||
|
|
||||||
package Slic3r::PathsToGCode::SVGParser;
|
package Slic3r::PathsToGCode::SVGParser;
|
||||||
use base qw(XML::SAX::Base);
|
use base qw(XML::SAX::Base);
|
||||||
use Image::SVG::Path qw(extract_path_info);
|
use Image::SVG::Path qw();
|
||||||
|
use Slic3r::Geometry qw(X Y);
|
||||||
|
|
||||||
sub new {
|
sub new {
|
||||||
my $self = shift->SUPER::new(@_);
|
my $self = shift->SUPER::new(@_);
|
||||||
|
@ -55,18 +80,253 @@ sub start_element {
|
||||||
foreach my $event (@path_info) {
|
foreach my $event (@path_info) {
|
||||||
if ($event->{type} eq 'moveto') {
|
if ($event->{type} eq 'moveto') {
|
||||||
push @polylines, Slic3r::Polyline->new;
|
push @polylines, Slic3r::Polyline->new;
|
||||||
$polylines[-1]->append(Slic3r::Point->new_scale(@{ $event->{point} }));
|
$polylines[-1]->append(Slic3r::Point->new_scale($event->{point}[X], -$event->{point}[Y]));
|
||||||
} elsif ($event->{type} =~ /cubic-bezier/ || $event->{type} eq 'line-to') {
|
} elsif ($event->{type} =~ /cubic-bezier/ || $event->{type} eq 'line-to') {
|
||||||
$polylines[-1]->append(Slic3r::Point->new_scale(@{ $event->{end} }));
|
$polylines[-1]->append(Slic3r::Point->new_scale($event->{end}[X], -$event->{end}[Y]));
|
||||||
} elsif ($event->{type} eq 'closepath') {
|
} elsif ($event->{type} eq 'closepath') {
|
||||||
$polylines[-1]->append($polylines[-1]->first_point);
|
$polylines[-1]->append($polylines[-1]->first_point);
|
||||||
} else {
|
} else {
|
||||||
die "Failed to parse path item " . $event->{type};
|
die "Failed to parse path item " . $event->{type};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
push @{$self->{_paths}}, @polylines;
|
push @{$self->{_paths}}, @polylines;
|
||||||
|
} elsif ($el->{LocalName} eq 'polyline') {
|
||||||
|
my @points = map { my @c = split /,/; Slic3r::Point->new_scale($c[0], -$c[1]) }
|
||||||
|
split /\s+/, $el->{Attributes}{'{}points'}{Value};
|
||||||
|
push @{$self->{_paths}}, Slic3r::Polyline->new(@points);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
## The following is temporarily extracted from Image::SVG::Path
|
||||||
|
## waiting for my patch to be merged.
|
||||||
|
|
||||||
|
use Carp;
|
||||||
|
sub position_type
|
||||||
|
{
|
||||||
|
my ($curve_type) = @_;
|
||||||
|
if (lc $curve_type eq $curve_type) {
|
||||||
|
return "relative";
|
||||||
|
}
|
||||||
|
elsif (uc $curve_type eq $curve_type) {
|
||||||
|
return "absolute";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
croak "I don't know what to do with '$curve_type'";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub add_coords
|
||||||
|
{
|
||||||
|
my ($first_ref, $to_add_ref) = @_;
|
||||||
|
$first_ref->[0] += $to_add_ref->[0];
|
||||||
|
$first_ref->[1] += $to_add_ref->[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
# The following regular expression splits the path into pieces
|
||||||
|
|
||||||
|
my $split_re = qr/(?:,|(?=-)|\s+)/;
|
||||||
|
|
||||||
|
sub extract_path_info
|
||||||
|
{
|
||||||
|
my ($path, $options_ref) = @_;
|
||||||
|
my $me = 'extract_path_info';
|
||||||
|
if (! $path) {
|
||||||
|
croak "$me: no input";
|
||||||
|
}
|
||||||
|
# Create an empty options so that we don't have to
|
||||||
|
# keep testing whether the "options" string is defined or not
|
||||||
|
# before trying to read a hash value from it.
|
||||||
|
if ($options_ref) {
|
||||||
|
if (ref $options_ref ne 'HASH') {
|
||||||
|
croak "$me: second argument should be a hash reference";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$options_ref = {};
|
||||||
|
}
|
||||||
|
if (! wantarray) {
|
||||||
|
croak "$me: extract_path_info returns an array of values";
|
||||||
|
}
|
||||||
|
my $verbose = $options_ref->{verbose};
|
||||||
|
if ($verbose) {
|
||||||
|
print "$me: I am trying to split up '$path'.\n";
|
||||||
|
}
|
||||||
|
my @path_info;
|
||||||
|
my $has_moveto = ($path =~ /^([Mm])\s*,?\s*([-0-9.,]+)(.*)$/);
|
||||||
|
if (! $has_moveto) {
|
||||||
|
croak "No moveto at start of path '$path'";
|
||||||
|
}
|
||||||
|
my ($moveto_type, $move_to, $curves) = ($1, $2, $3);
|
||||||
|
if ($verbose) {
|
||||||
|
print "$me: The initial moveto looks like '$moveto_type' '$move_to'.\n";
|
||||||
|
}
|
||||||
|
# Deal with the initial "move to" command.
|
||||||
|
my $position = position_type ($moveto_type);
|
||||||
|
my ($x, $y) = split $split_re, $move_to, 2;
|
||||||
|
push @path_info, {
|
||||||
|
type => 'moveto',
|
||||||
|
position => $position,
|
||||||
|
point => [$x, $y],
|
||||||
|
svg_key => $moveto_type,
|
||||||
|
};
|
||||||
|
# Deal with the rest of the path.
|
||||||
|
my @curves;
|
||||||
|
while ($curves =~ /([cslqtahvz])\s*([-0-9.,\s]*)/gi) {
|
||||||
|
push @curves, [$1, $2];
|
||||||
|
}
|
||||||
|
if (@curves == 0) {
|
||||||
|
croak "No curves found in '$curves'";
|
||||||
|
}
|
||||||
|
for my $curve_data (@curves) {
|
||||||
|
my ($curve_type, $curve) = @$curve_data;
|
||||||
|
$curve =~ s/^,//;
|
||||||
|
my @numbers = split $split_re, $curve;
|
||||||
|
if ($verbose) {
|
||||||
|
print "Extracted numbers: @numbers\n";
|
||||||
|
}
|
||||||
|
if (uc $curve_type eq 'C') {
|
||||||
|
my $expect_numbers = 6;
|
||||||
|
if (@numbers % 6 != 0) {
|
||||||
|
croak "Wrong number of values for a C curve " .
|
||||||
|
scalar @numbers . " in '$path'";
|
||||||
|
}
|
||||||
|
my $position = position_type ($curve_type);
|
||||||
|
for (my $i = 0; $i < @numbers / 6; $i++) {
|
||||||
|
my $offset = 6 * $i;
|
||||||
|
my @control1 = @numbers[$offset + 0, $offset + 1];
|
||||||
|
my @control2 = @numbers[$offset + 2, $offset + 3];
|
||||||
|
my @end = @numbers[$offset + 4, $offset + 5];
|
||||||
|
# Put each of these abbreviated things into the list
|
||||||
|
# as a separate path.
|
||||||
|
push @path_info, {
|
||||||
|
type => 'cubic-bezier',
|
||||||
|
position => $position,
|
||||||
|
control1 => \@control1,
|
||||||
|
control2 => \@control2,
|
||||||
|
end => \@end,
|
||||||
|
svg_key => $curve_type,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elsif (uc $curve_type eq 'S') {
|
||||||
|
my $expect_numbers = 4;
|
||||||
|
if (@numbers % $expect_numbers != 0) {
|
||||||
|
croak "Wrong number of values for an S curve " .
|
||||||
|
scalar @numbers . " in '$path'";
|
||||||
|
}
|
||||||
|
my $position = position_type ($curve_type);
|
||||||
|
for (my $i = 0; $i < @numbers / $expect_numbers; $i++) {
|
||||||
|
my $offset = $expect_numbers * $i;
|
||||||
|
my @control2 = @numbers[$offset + 0, $offset + 1];
|
||||||
|
my @end = @numbers[$offset + 2, $offset + 3];
|
||||||
|
push @path_info, {
|
||||||
|
type => 'shortcut-cubic-bezier',
|
||||||
|
position => $position,
|
||||||
|
control2 => \@control2,
|
||||||
|
end => \@end,
|
||||||
|
svg_key => $curve_type,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elsif (uc $curve_type eq 'L') {
|
||||||
|
my $expect_numbers = 2;
|
||||||
|
if (@numbers % $expect_numbers != 0) {
|
||||||
|
croak "Wrong number of values for an L command " .
|
||||||
|
scalar @numbers . " in '$path'";
|
||||||
|
}
|
||||||
|
my $position = position_type ($curve_type);
|
||||||
|
for (my $i = 0; $i < @numbers / $expect_numbers; $i++) {
|
||||||
|
my $offset = $expect_numbers * $i;
|
||||||
|
push @path_info, {
|
||||||
|
type => 'line-to',
|
||||||
|
position => $position,
|
||||||
|
end => [@numbers[$offset, $offset + 1]],
|
||||||
|
svg_key => $curve_type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elsif (uc $curve_type eq 'Z') {
|
||||||
|
if (@numbers > 0) {
|
||||||
|
croak "Wrong number of values for a Z command " .
|
||||||
|
scalar @numbers . " in '$path'";
|
||||||
|
}
|
||||||
|
my $position = position_type ($curve_type);
|
||||||
|
push @path_info, {
|
||||||
|
type => 'closepath',
|
||||||
|
position => $position,
|
||||||
|
svg_key => $curve_type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
croak "I don't know what to do with a curve type '$curve_type'";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# Now sort it out if the user wants to get rid of the absolute
|
||||||
|
# paths etc.
|
||||||
|
|
||||||
|
my $absolute = $options_ref->{absolute};
|
||||||
|
my $no_shortcuts = $options_ref->{no_shortcuts};
|
||||||
|
if ($absolute) {
|
||||||
|
if ($verbose) {
|
||||||
|
print "Making all coordinates absolute.\n";
|
||||||
|
}
|
||||||
|
my $abs_pos;
|
||||||
|
my $previous;
|
||||||
|
for my $element (@path_info) {
|
||||||
|
if ($element->{type} eq 'moveto') {
|
||||||
|
if ($element->{position} eq 'relative') {
|
||||||
|
my $ip = $options_ref->{initial_position};
|
||||||
|
if ($ip) {
|
||||||
|
if (ref $ip ne 'ARRAY' ||
|
||||||
|
scalar @$ip != 2) {
|
||||||
|
croak "The initial position supplied doesn't look like a pair of coordinates";
|
||||||
|
}
|
||||||
|
add_coords ($element->{point}, $ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$abs_pos = $element->{point};
|
||||||
|
}
|
||||||
|
elsif ($element->{type} eq 'line-to') {
|
||||||
|
if ($element->{position} eq 'relative') {
|
||||||
|
add_coords ($element->{end}, $abs_pos);
|
||||||
|
}
|
||||||
|
$abs_pos = $element->{end};
|
||||||
|
}
|
||||||
|
elsif ($element->{type} eq 'cubic-bezier') {
|
||||||
|
if ($element->{position} eq 'relative') {
|
||||||
|
add_coords ($element->{control1}, $abs_pos);
|
||||||
|
add_coords ($element->{control2}, $abs_pos);
|
||||||
|
add_coords ($element->{end}, $abs_pos);
|
||||||
|
}
|
||||||
|
$abs_pos = $element->{end};
|
||||||
|
}
|
||||||
|
elsif ($element->{type} eq 'shortcut-cubic-bezier') {
|
||||||
|
if ($element->{position} eq 'relative') {
|
||||||
|
add_coords ($element->{control2}, $abs_pos);
|
||||||
|
add_coords ($element->{end}, $abs_pos);
|
||||||
|
}
|
||||||
|
if ($no_shortcuts) {
|
||||||
|
if (!$previous) {
|
||||||
|
die "No previous element";
|
||||||
|
}
|
||||||
|
if ($previous->{type} ne 'cubic-bezier') {
|
||||||
|
die "Bad previous element type $previous->{type}";
|
||||||
|
}
|
||||||
|
$element->{type} = 'cubic-bezier';
|
||||||
|
$element->{svg_key} = 'C';
|
||||||
|
$element->{control1} = [
|
||||||
|
2 * $abs_pos->[0] - $previous->{control2}->[0],
|
||||||
|
2 * $abs_pos->[1] - $previous->{control2}->[1],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$abs_pos = $element->{end};
|
||||||
|
}
|
||||||
|
$element->{position} = 'absolute';
|
||||||
|
$element->{svg_key} = uc $element->{svg_key};
|
||||||
|
$previous = $element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return @path_info;
|
||||||
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|
|
@ -33,4 +33,11 @@ sub align_to_origin {
|
||||||
return $self->translate(-$bb->x_min, -$bb->y_min);
|
return $self->translate(-$bb->x_min, -$bb->y_min);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
package Slic3r::Polyline::Collection;
|
||||||
|
|
||||||
|
sub bounding_box {
|
||||||
|
my $self = shift;
|
||||||
|
return Slic3r::Geometry::BoundingBox->new_from_points([ map @$_, @$self ]);
|
||||||
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
# This script converts a SVG file to GCode
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
use FindBin;
|
||||||
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
}
|
||||||
|
|
||||||
|
use Getopt::Long qw(:config no_auto_abbrev);
|
||||||
|
use Slic3r;
|
||||||
|
use Slic3r::PathsToGCode;
|
||||||
|
$|++;
|
||||||
|
|
||||||
|
my %opt = ();
|
||||||
|
{
|
||||||
|
my %options = (
|
||||||
|
'help' => sub { usage() },
|
||||||
|
'scale' => \$opt{scale},
|
||||||
|
);
|
||||||
|
GetOptions(%options) or usage(1);
|
||||||
|
$ARGV[0] or usage(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
my $input_file = $ARGV[0];
|
||||||
|
my $output_file = $input_file;
|
||||||
|
$output_file =~ s/\.svg$/.gcode/i;
|
||||||
|
|
||||||
|
my $converter = Slic3r::PathsToGCode->new(
|
||||||
|
scale => $opt{scale} // 1,
|
||||||
|
);
|
||||||
|
$converter->process_svg($input_file, $output_file);
|
||||||
|
printf "G-code written to $output_file\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub usage {
|
||||||
|
my ($exit_code) = @_;
|
||||||
|
|
||||||
|
print <<"EOF";
|
||||||
|
Usage: svg-to-gcode.pl [ OPTIONS ] file.svg
|
||||||
|
|
||||||
|
--help Output this usage screen and exit
|
||||||
|
--scale SCALE Scale factor (default: 1)
|
||||||
|
|
||||||
|
EOF
|
||||||
|
exit ($exit_code || 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
__END__
|
|
@ -2,6 +2,20 @@
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
|
void
|
||||||
|
PolylineCollection::scale(double factor)
|
||||||
|
{
|
||||||
|
for (Polylines::iterator it = this->polylines.begin(); it != this->polylines.end(); ++it)
|
||||||
|
it->scale(factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PolylineCollection::translate(double x, double y)
|
||||||
|
{
|
||||||
|
for (Polylines::iterator it = this->polylines.begin(); it != this->polylines.end(); ++it)
|
||||||
|
it->translate(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
PolylineCollection*
|
PolylineCollection*
|
||||||
PolylineCollection::chained_path(bool no_reverse) const
|
PolylineCollection::chained_path(bool no_reverse) const
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,6 +10,8 @@ class PolylineCollection
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Polylines polylines;
|
Polylines polylines;
|
||||||
|
void scale(double factor);
|
||||||
|
void translate(double x, double y);
|
||||||
PolylineCollection* chained_path(bool no_reverse) const;
|
PolylineCollection* chained_path(bool no_reverse) const;
|
||||||
PolylineCollection* chained_path_from(const Point* start_near, bool no_reverse) const;
|
PolylineCollection* chained_path_from(const Point* start_near, bool no_reverse) const;
|
||||||
Point* leftmost_point() const;
|
Point* leftmost_point() const;
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
%code{% const char* CLASS = "Slic3r::Polyline::Collection"; RETVAL = new PolylineCollection(*THIS); %};
|
%code{% const char* CLASS = "Slic3r::Polyline::Collection"; RETVAL = new PolylineCollection(*THIS); %};
|
||||||
void clear()
|
void clear()
|
||||||
%code{% THIS->polylines.clear(); %};
|
%code{% THIS->polylines.clear(); %};
|
||||||
|
void scale(double factor);
|
||||||
|
void translate(double x, double y);
|
||||||
PolylineCollection* chained_path(bool no_reverse)
|
PolylineCollection* chained_path(bool no_reverse)
|
||||||
%code{% const char* CLASS = "Slic3r::Polyline::Collection"; RETVAL = THIS->chained_path(no_reverse); %};
|
%code{% const char* CLASS = "Slic3r::Polyline::Collection"; RETVAL = THIS->chained_path(no_reverse); %};
|
||||||
PolylineCollection* chained_path_from(Point* start_near, bool no_reverse)
|
PolylineCollection* chained_path_from(Point* start_near, bool no_reverse)
|
||||||
|
|
Loading…
Reference in New Issue