Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/prusa3d/PrusaSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlessandro Ranellucci <aar@cpan.org>2012-09-12 18:04:16 +0400
committerAlessandro Ranellucci <aar@cpan.org>2012-09-12 18:04:16 +0400
commitcc8d8bd26fbd9e9f28c0639631b6f6697d7e15ab (patch)
tree90e7308e74018b05039f3ec7c36680f3a1b00b27
parentd061534b83ff2c99b71b4d52872857466a3b072b (diff)
parent2a51cad0c39adeb028b0fc90e0df6fb5092ae68d (diff)
Merge branch 'master' into avoid-crossing-perimeters
Conflicts: README.markdown lib/Slic3r/Config.pm lib/Slic3r/GCode.pm lib/Slic3r/Print.pm slic3r.pl
-rw-r--r--MANIFEST1
-rw-r--r--README.markdown42
-rw-r--r--lib/Slic3r.pm6
-rw-r--r--lib/Slic3r/Config.pm10
-rw-r--r--lib/Slic3r/ExPolygon.pm21
-rw-r--r--lib/Slic3r/Fill.pm2
-rw-r--r--lib/Slic3r/Fill/Concentric.pm2
-rw-r--r--lib/Slic3r/Fill/Honeycomb.pm3
-rw-r--r--lib/Slic3r/Fill/PlanePath.pm2
-rw-r--r--lib/Slic3r/Fill/Rectilinear.pm10
-rw-r--r--lib/Slic3r/Format/AMF.pm82
-rw-r--r--lib/Slic3r/Format/AMF/Parser.pm65
-rw-r--r--lib/Slic3r/Format/OBJ.pm5
-rw-r--r--lib/Slic3r/Format/STL.pm13
-rw-r--r--lib/Slic3r/GCode.pm36
-rw-r--r--lib/Slic3r/GUI.pm18
-rw-r--r--lib/Slic3r/GUI/Plater.pm69
-rw-r--r--lib/Slic3r/GUI/SkeinPanel.pm2
-rw-r--r--lib/Slic3r/GUI/Tab.pm2
-rw-r--r--lib/Slic3r/Geometry.pm2
-rw-r--r--lib/Slic3r/Geometry/Clipper.pm27
-rw-r--r--lib/Slic3r/Layer.pm21
-rw-r--r--lib/Slic3r/Model.pm112
-rw-r--r--lib/Slic3r/Polygon.pm8
-rw-r--r--lib/Slic3r/Polyline.pm2
-rw-r--r--lib/Slic3r/Print.pm102
-rw-r--r--lib/Slic3r/Print/Object.pm3
-rwxr-xr-xslic3r.pl12
-rw-r--r--t/arcs.t10
-rw-r--r--t/fill.t27
-rwxr-xr-xutils/amf-to-stl.pl4
-rwxr-xr-xutils/post-processing/filament-weight.pl24
-rwxr-xr-xutils/split_stl.pl11
-rwxr-xr-xutils/stl-to-amf.pl40
34 files changed, 530 insertions, 266 deletions
diff --git a/MANIFEST b/MANIFEST
index b021f8d85..e4d52f390 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -36,6 +36,7 @@ lib/Slic3r/GUI/SkeinPanel.pm
lib/Slic3r/GUI/Tab.pm
lib/Slic3r/Layer.pm
lib/Slic3r/Line.pm
+lib/Slic3r/Model.pm
lib/Slic3r/Point.pm
lib/Slic3r/Polygon.pm
lib/Slic3r/Polyline.pm
diff --git a/README.markdown b/README.markdown
index 0932110cc..6490dd367 100644
--- a/README.markdown
+++ b/README.markdown
@@ -9,8 +9,8 @@ A: Yes.
Slic3r is a G-code generator for 3D printers. It's compatible with RepRaps,
Makerbots, Ultimakers and many more machines.
-See the [project homepage](http://slic3r.org/) at slic3r.org
-for more information.
+See the [project homepage](http://slic3r.org/) at slic3r.org and the
+[documentation](https://github.com/alexrj/Slic3r/wiki/Documentation) on the Slic3r wiki for more information.
## What language is it written in?
@@ -171,6 +171,9 @@ The author of the Silk icon set is Mark James.
--extra-perimeters Add more perimeters when needed (default: yes)
--randomize-start Randomize starting point across layers (default: yes)
--avoid-crossing-perimeters Optimize travel moves so that no perimeters are crossed (default: no)
+ --only-retract-when-crossing-perimeters
+ Disable retraction when travelling between infill paths inside the same island.
+ (default: no)
--solid-infill-below-area
Force solid infill when a region has a smaller area than this threshold
(mm^2, default: 70)
@@ -273,38 +276,3 @@ If you want to change a preset file, just do
If you want to slice a file overriding an option contained in your preset file:
slic3r.pl --load config.ini --layer-height 0.25 file.stl
-
-## How can I integrate Slic3r with Pronterface?
-
-Put this into *slicecommand*:
-
- slic3r.pl $s --load config.ini --output $o
-
-And this into *sliceoptscommand*:
-
- slic3r.pl --load config.ini --ignore-nonexistent-config
-
-Replace `slic3r.pl` with the full path to the slic3r executable and `config.ini`
-with the full path of your config file (put it in your home directory or where
-you like).
-On Mac, the executable has a path like this:
-
- /Applications/Slic3r.app/Contents/MacOS/slic3r
-
-## How can I specify a custom filename format for output G-code files?
-
-You can specify a filename format by using any of the config options.
-Just enclose them in square brackets, and Slic3r will replace them upon
-exporting.
-The additional `[input_filename]` and `[input_filename_base]` options will
-be replaced by the input file name (in the second case, the .stl extension
-is stripped).
-
-The default format is `[input_filename_base].gcode`, meaning that if you slice
-a *foo.stl* file, the output will be saved to *foo.gcode*.
-
-See below for more complex examples:
-
- [input_filename_base]_h[layer_height]_p[perimeters]_s[solid_layers].gcode
- [input_filename]_center[print_center]_[layer_height]layers.gcode
-
diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm
index 72a088702..d09b66dad 100644
--- a/lib/Slic3r.pm
+++ b/lib/Slic3r.pm
@@ -7,7 +7,7 @@ use strict;
use warnings;
require v5.10;
-our $VERSION = "0.9.2-dev";
+our $VERSION = "0.9.3-dev";
our $debug = 0;
sub debugf {
@@ -21,6 +21,9 @@ BEGIN {
$have_threads = $Config{useithreads} && eval "use threads; use Thread::Queue; 1";
}
+warn "Running Slic3r under Perl >= 5.16 is not supported nor recommended\n"
+ if $^V >= v5.16;
+
use FindBin;
our $var = "$FindBin::Bin/var";
@@ -42,6 +45,7 @@ use Slic3r::GCode::MotionPlanner;
use Slic3r::Geometry qw(PI);
use Slic3r::Layer;
use Slic3r::Line;
+use Slic3r::Model;
use Slic3r::Point;
use Slic3r::Polygon;
use Slic3r::Polyline;
diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm
index 6548edf86..722d1f73c 100644
--- a/lib/Slic3r/Config.pm
+++ b/lib/Slic3r/Config.pm
@@ -178,6 +178,7 @@ our $Options = {
# extruder mapping
'perimeter_extruder' => {
label => 'Perimeter extruder',
+ tooltip => 'The extruder to use when printing perimeters.',
cli => 'perimeter-extruder=i',
type => 'i',
aliases => [qw(perimeters_extruder)],
@@ -185,12 +186,14 @@ our $Options = {
},
'infill_extruder' => {
label => 'Infill extruder',
+ tooltip => 'The extruder to use when printing infill.',
cli => 'infill-extruder=i',
type => 'i',
default => 1,
},
'support_material_extruder' => {
label => 'Support material extruder',
+ tooltip => 'The extruder to use when printing support material. This affects brim too.',
cli => 'support-material-extruder=i',
type => 'i',
default => 1,
@@ -477,6 +480,13 @@ our $Options = {
type => 'bool',
default => 0,
},
+ 'only_retract_when_crossing_perimeters' => {
+ label => 'Only retract when crossing perimeters',
+ tooltip => 'Disables retraction when travelling between infill paths inside the same island.',
+ cli => 'only-retract-when-crossing-perimeters!',
+ type => 'bool',
+ default => 0,
+ },
'support_material' => {
label => 'Generate support material',
tooltip => 'Enable support material generation.',
diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm
index 5d87f720f..8f1541ada 100644
--- a/lib/Slic3r/ExPolygon.pm
+++ b/lib/Slic3r/ExPolygon.pm
@@ -61,13 +61,12 @@ sub boost_polygon {
sub offset {
my $self = shift;
- my ($distance, $scale, $joinType, $miterLimit) = @_;
- $scale ||= &Slic3r::SCALING_FACTOR * 1000000;
- $joinType = JT_MITER if !defined $joinType;
- $miterLimit ||= 2;
-
- my $offsets = Math::Clipper::offset($self, $distance, $scale, $joinType, $miterLimit);
- return @$offsets;
+ return Slic3r::Geometry::Clipper::offset($self, @_);
+}
+
+sub offset_ex {
+ my $self = shift;
+ return Slic3r::Geometry::Clipper::offset_ex($self, @_);
}
sub safety_offset {
@@ -83,14 +82,6 @@ sub safety_offset {
);
}
-sub offset_ex {
- my $self = shift;
- my @offsets = $self->offset(@_);
-
- # apply holes to the right contours
- return @{ union_ex(\@offsets) };
-}
-
sub encloses_point {
my $self = shift;
my ($point) = @_;
diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm
index e30eec65f..7d0556f89 100644
--- a/lib/Slic3r/Fill.pm
+++ b/lib/Slic3r/Fill.pm
@@ -170,7 +170,7 @@ sub make_fill {
? ($surface->surface_type == S_TYPE_TOP ? EXTR_ROLE_TOPSOLIDFILL : EXTR_ROLE_SOLIDFILL)
: EXTR_ROLE_FILL),
depth_layers => $surface->depth_layers,
- flow_spacing => $params->{flow_spacing} || $flow_spacing,
+ flow_spacing => $params->{flow_spacing} || (warn "Warning: no flow_spacing was returned by the infill engine, please report this to the developer\n"),
), @paths,
],
);
diff --git a/lib/Slic3r/Fill/Concentric.pm b/lib/Slic3r/Fill/Concentric.pm
index 94866e2ae..c0d172cfe 100644
--- a/lib/Slic3r/Fill/Concentric.pm
+++ b/lib/Slic3r/Fill/Concentric.pm
@@ -18,7 +18,7 @@ sub fill_surface {
my $min_spacing = scale $params{flow_spacing};
my $distance = $min_spacing / $params{density};
- my $flow_spacing;
+ my $flow_spacing = $params{flow_spacing};
if ($params{density} == 1) {
$distance = $self->adjust_solid_spacing(
width => $bounding_box->[X2] - $bounding_box->[X1],
diff --git a/lib/Slic3r/Fill/Honeycomb.pm b/lib/Slic3r/Fill/Honeycomb.pm
index 229c72568..1e407e2e6 100644
--- a/lib/Slic3r/Fill/Honeycomb.pm
+++ b/lib/Slic3r/Fill/Honeycomb.pm
@@ -94,7 +94,8 @@ sub fill_surface {
paths => [ map Slic3r::ExtrusionPath->pack(polyline => $_, role => -1), @paths ],
);
- return {}, map $_->polyline, $collection->shortest_path;
+ return { flow_spacing => $params{flow_spacing} },
+ map $_->polyline, $collection->shortest_path;
}
1;
diff --git a/lib/Slic3r/Fill/PlanePath.pm b/lib/Slic3r/Fill/PlanePath.pm
index daeb13dea..d33b6e6fc 100644
--- a/lib/Slic3r/Fill/PlanePath.pm
+++ b/lib/Slic3r/Fill/PlanePath.pm
@@ -60,7 +60,7 @@ sub fill_surface {
# paths must be rotated back
$self->rotate_points_back(\@paths, $rotate_vector);
- return {}, @paths;
+ return { flow_spacing => $params{flow_spacing} }, @paths;
}
1;
diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm
index 2de7872a6..121249eaa 100644
--- a/lib/Slic3r/Fill/Rectilinear.pm
+++ b/lib/Slic3r/Fill/Rectilinear.pm
@@ -3,7 +3,7 @@ use Moo;
extends 'Slic3r::Fill::Base';
-use Slic3r::Geometry qw(X1 Y1 X2 Y2 A B X Y scale unscale epsilon);
+use Slic3r::Geometry qw(X1 Y1 X2 Y2 A B X Y scale unscale scaled_epsilon);
sub fill_surface {
my $self = shift;
@@ -22,7 +22,7 @@ sub fill_surface {
my $distance_between_lines = $min_spacing / $params{density};
my $line_oscillation = $distance_between_lines - $min_spacing;
- my $flow_spacing;
+ my $flow_spacing = $params{flow_spacing};
if ($params{density} == 1) {
$distance_between_lines = $self->adjust_solid_spacing(
width => $bounding_box->[X2] - $bounding_box->[X1],
@@ -36,7 +36,7 @@ sub fill_surface {
my $x = $bounding_box->[X1];
my $is_line_pattern = $self->isa('Slic3r::Fill::Line');
my @vertical_lines = ();
- for (my $i = 0; $x <= $bounding_box->[X2] + scale epsilon; $i++) {
+ for (my $i = 0; $x <= $bounding_box->[X2] + scaled_epsilon; $i++) {
my $vertical_line = Slic3r::Line->new([$x, $bounding_box->[Y2]], [$x, $bounding_box->[Y1]]);
if ($is_line_pattern && $i % 2) {
$vertical_line->[A][X] += $line_oscillation;
@@ -49,7 +49,7 @@ sub fill_surface {
# clip paths against a slightly offsetted expolygon, so that the first and last paths
# are kept even if the expolygon has vertical sides
my @paths = @{ Boost::Geometry::Utils::polygon_linestring_intersection(
- +($expolygon->offset_ex(scale epsilon))[0]->boost_polygon, # TODO: we should use all the resulting expolygons and clip the linestrings to a multipolygon object
+ +($expolygon->offset_ex(scaled_epsilon))[0]->boost_polygon, # TODO: we should use all the resulting expolygons and clip the linestrings to a multipolygon object
Boost::Geometry::Utils::linestring(@vertical_lines),
) };
for (@paths) {
@@ -64,7 +64,7 @@ sub fill_surface {
);
@paths = ();
- my $tolerance = 10 * scale epsilon;
+ my $tolerance = 10 * scaled_epsilon;
my $diagonal_distance = $distance_between_lines * 5;
my $can_connect = $is_line_pattern
? sub {
diff --git a/lib/Slic3r/Format/AMF.pm b/lib/Slic3r/Format/AMF.pm
index ce388ec2e..552f1fdbd 100644
--- a/lib/Slic3r/Format/AMF.pm
+++ b/lib/Slic3r/Format/AMF.pm
@@ -12,28 +12,18 @@ sub read_file {
open my $fh, '<', $file or die "Failed to open $file\n";
- my $vertices = [];
- my $materials = {};
- my $meshes_by_material = {};
+ my $model = Slic3r::Model->new;
XML::SAX::PurePerl
- ->new(Handler => Slic3r::Format::AMF::Parser->new(
- _vertices => $vertices,
- _materials => $materials,
- _meshes_by_material => $meshes_by_material,
- ))
+ ->new(Handler => Slic3r::Format::AMF::Parser->new(_model => $model))
->parse_file($fh);
-
close $fh;
- $_ = Slic3r::TriangleMesh->new(vertices => $vertices, facets => $_)
- for values %$meshes_by_material;
-
- return $materials, $meshes_by_material;
+ return $model;
}
sub write_file {
my $self = shift;
- my ($file, $materials, $meshes_by_material) = @_;
+ my ($file, $model, %params) = @_;
my %vertices_offset = ();
@@ -42,20 +32,21 @@ sub write_file {
printf $fh qq{<?xml version="1.0" encoding="UTF-8"?>\n};
printf $fh qq{<amf unit="millimeter">\n};
printf $fh qq{ <metadata type="cad">Slic3r %s</metadata>\n}, $Slic3r::VERSION;
- foreach my $material_id (keys %$materials) {
- printf $fh qq{ <material id="%s">\n}, $material_id;
- for (keys %{$materials->{$material_id}}) {
- printf $fh qq{ <metadata type=\"%s\">%s</metadata>\n}, $_, $materials->{$material_id}{$_};
+ for my $material_id (sort keys %{ $model->materials }) {
+ my $material = $model->materials->{$material_id};
+ printf $fh qq{ <material id="%d">\n}, $material_id;
+ for (keys %$material) {
+ printf $fh qq{ <metadata type=\"%s\">%s</metadata>\n}, $_, $material->{$_};
}
printf $fh qq{ </material>\n};
}
- printf $fh qq{ <object id="0">\n};
- printf $fh qq{ <mesh>\n};
- printf $fh qq{ <vertices>\n};
- my $vertices_count = 0;
- foreach my $mesh (values %$meshes_by_material) {
- $vertices_offset{$mesh} = $vertices_count;
- foreach my $vertex (@{$mesh->vertices}, ) {
+ my $instances = '';
+ for my $object_id (0 .. $#{ $model->objects }) {
+ my $object = $model->objects->[$object_id];
+ printf $fh qq{ <object id="%d">\n}, $object_id;
+ printf $fh qq{ <mesh>\n};
+ printf $fh qq{ <vertices>\n};
+ foreach my $vertex (@{$object->vertices}, ) {
printf $fh qq{ <vertex>\n};
printf $fh qq{ <coordinates>\n};
printf $fh qq{ <x>%s</x>\n}, $vertex->[X];
@@ -63,24 +54,35 @@ sub write_file {
printf $fh qq{ <z>%s</z>\n}, $vertex->[Z];
printf $fh qq{ </coordinates>\n};
printf $fh qq{ </vertex>\n};
- $vertices_count++;
}
- }
- printf $fh qq{ </vertices>\n};
- foreach my $material_id (sort keys %$meshes_by_material) {
- my $mesh = $meshes_by_material->{$material_id};
- printf $fh qq{ <volume%s>\n},
- ($material_id eq '_') ? '' : " materialid=\"$material_id\"";
- foreach my $facet (@{$mesh->facets}) {
- printf $fh qq{ <triangle>\n};
- printf $fh qq{ <v%d>%d</v%d>\n}, $_, $facet->[$_] + $vertices_offset{$mesh}, $_
- for -3..-1;
- printf $fh qq{ </triangle>\n};
+ printf $fh qq{ </vertices>\n};
+ foreach my $volume (@{ $object->volumes }) {
+ printf $fh qq{ <volume%s>\n},
+ (!defined $volume->material_id) ? '' : (sprintf ' materialid="%s"', $volume->material_id);
+ foreach my $facet (@{$volume->facets}) {
+ printf $fh qq{ <triangle>\n};
+ printf $fh qq{ <v%d>%d</v%d>\n}, (4+$_), $facet->[$_], (4+$_) for -3..-1;
+ printf $fh qq{ </triangle>\n};
+ }
+ printf $fh qq{ </volume>\n};
}
- printf $fh qq{ </volume>\n};
+ printf $fh qq{ </mesh>\n};
+ printf $fh qq{ </object>\n};
+ if ($object->instances) {
+ foreach my $instance (@{$object->instances}) {
+ $instances .= sprintf qq{ <instance objectid="%d">\n}, $object_id;
+ $instances .= sprintf qq{ <deltax>%s</deltax>\n}, $instance->offset->[X];
+ $instances .= sprintf qq{ <deltax>%s</deltax>\n}, $instance->offset->[Y];
+ $instances .= sprintf qq{ <rz>%s</rz>\n}, $instance->rotation;
+ $instances .= sprintf qq{ </instance>\n};
+ }
+ }
+ }
+ if ($instances) {
+ printf $fh qq{ <constellation id="1">\n};
+ printf $fh $instances;
+ printf $fh qq{ </constellation>\n};
}
- printf $fh qq{ </mesh>\n};
- printf $fh qq{ </object>\n};
printf $fh qq{</amf>\n};
close $fh;
}
diff --git a/lib/Slic3r/Format/AMF/Parser.pm b/lib/Slic3r/Format/AMF/Parser.pm
index 1d8d4dc39..93b35765e 100644
--- a/lib/Slic3r/Format/AMF/Parser.pm
+++ b/lib/Slic3r/Format/AMF/Parser.pm
@@ -11,6 +11,8 @@ my %xyz_index = (x => 0, y => 1, z => 2); #=
sub new {
my $self = shift->SUPER::new(@_);
$self->{_tree} = [];
+ $self->{_objects_map} = {}; # this hash maps AMF object IDs to object indexes in $model->objects
+ $self->{_instances} = {}; # apply these lazily to make sure all objects have been parsed
$self;
}
@@ -18,23 +20,35 @@ sub start_element {
my $self = shift;
my $data = shift;
- if ($data->{LocalName} eq 'vertex') {
+ if ($data->{LocalName} eq 'object') {
+ $self->{_object} = $self->{_model}->add_object;
+ $self->{_objects_map}{ $self->_get_attribute($data, 'id') } = $#{ $self->{_model}->objects };
+ } elsif ($data->{LocalName} eq 'vertex') {
$self->{_vertex} = ["", "", ""];
} elsif ($self->{_vertex} && $data->{LocalName} =~ /^[xyz]$/ && $self->{_tree}[-1] eq 'coordinates') {
$self->{_coordinate} = $data->{LocalName};
} elsif ($data->{LocalName} eq 'volume') {
- $self->{_volume_materialid} = $self->_get_attribute($data, 'materialid') || '_';
- $self->{_volume} = [];
+ $self->{_volume} = $self->{_object}->add_volume(
+ material_id => $self->_get_attribute($data, 'materialid') || undef,
+ );
} elsif ($data->{LocalName} eq 'triangle') {
$self->{_triangle} = ["", "", ""];
} elsif ($self->{_triangle} && $data->{LocalName} =~ /^v([123])$/ && $self->{_tree}[-1] eq 'triangle') {
$self->{_vertex_idx} = $1-1;
} elsif ($data->{LocalName} eq 'material') {
- $self->{_material_id} = $self->_get_attribute($data, 'id') || '_';
- $self->{_material} = {};
+ my $material_id = $self->_get_attribute($data, 'id') || '_';
+ $self->{_material} = $self->{_model}->materials->{ $material_id } = {};
} elsif ($data->{LocalName} eq 'metadata' && $self->{_tree}[-1] eq 'material') {
$self->{_material_metadata_type} = $self->_get_attribute($data, 'type');
$self->{_material}{ $self->{_material_metadata_type} } = "";
+ } elsif ($data->{LocalName} eq 'constellation') {
+ $self->{_constellation} = 1; # we merge all constellations as we don't support more than one
+ } elsif ($data->{LocalName} eq 'instance' && $self->{_constellation}) {
+ my $object_id = $self->_get_attribute($data, 'objectid');
+ $self->{_instances}{$object_id} ||= [];
+ push @{ $self->{_instances}{$object_id} }, $self->{_instance} = {};
+ } elsif ($data->{LocalName} =~ /^(?:deltax|deltay|rz)$/ && $self->{_instance}) {
+ $self->{_instance_property} = $data->{LocalName};
}
push @{$self->{_tree}}, $data->{LocalName};
@@ -50,6 +64,8 @@ sub characters {
$self->{_triangle}[ $self->{_vertex_idx} ] .= $data->{Data};
} elsif ($self->{_material_metadata_type}) {
$self->{_material}{ $self->{_material_metadata_type} } .= $data->{Data};
+ } elsif ($self->{_instance_property}) {
+ $self->{_instance}{ $self->{_instance_property} } .= $data->{Data};
}
}
@@ -59,26 +75,49 @@ sub end_element {
pop @{$self->{_tree}};
- if ($data->{LocalName} eq 'vertex') {
- push @{$self->{_vertices}}, $self->{_vertex};
+ if ($data->{LocalName} eq 'object') {
+ $self->{_object} = undef;
+ } elsif ($data->{LocalName} eq 'vertex') {
+ push @{$self->{_object}->vertices}, $self->{_vertex};
$self->{_vertex} = undef;
} elsif ($self->{_coordinate} && $data->{LocalName} =~ /^[xyz]$/) {
$self->{_coordinate} = undef;
} elsif ($data->{LocalName} eq 'volume') {
- $self->{_meshes_by_material}{ $self->{_volume_materialid} } ||= [];
- push @{ $self->{_meshes_by_material}{ $self->{_volume_materialid} } }, @{$self->{_volume}};
$self->{_volume} = undef;
} elsif ($data->{LocalName} eq 'triangle') {
- push @{$self->{_volume}}, $self->{_triangle};
+ push @{$self->{_volume}->facets}, $self->{_triangle};
$self->{_triangle} = undef;
- } elsif ($self->{_vertex_idx} && $data->{LocalName} =~ /^v[123]$/) {
+ } elsif (defined $self->{_vertex_idx} && $data->{LocalName} =~ /^v[123]$/) {
$self->{_vertex_idx} = undef;
} elsif ($data->{LocalName} eq 'material') {
- $self->{_materials}{ $self->{_material_id} } = $self->{_material};
- $self->{_material_id} = undef;
$self->{_material} = undef;
} elsif ($data->{LocalName} eq 'metadata' && $self->{_material}) {
$self->{_material_metadata_type} = undef;
+ } elsif ($data->{LocalName} eq 'constellation') {
+ $self->{_constellation} = undef;
+ } elsif ($data->{LocalName} eq 'instance') {
+ $self->{_instance} = undef;
+ } elsif ($data->{LocalName} =~ /^(?:deltax|deltay|rz)$/ && $self->{_instance}) {
+ $self->{_instance_property} = undef;
+ }
+}
+
+sub end_document {
+ my $self = shift;
+
+ foreach my $object_id (keys %{ $self->{_instances} }) {
+ my $new_object_id = $self->{_objects_map}{$object_id};
+ if (!$new_object_id) {
+ warn "Undefined object $object_id referenced in constellation\n";
+ next;
+ }
+
+ foreach my $instance (@{ $self->{_instances}{$object_id} }) {
+ $self->{_model}->objects->[$new_object_id]->add_instance(
+ rotation => $instance->{rz} || 0,
+ offset => [ $instance->{deltax} || 0, $instance->{deltay} ],
+ );
+ }
}
}
diff --git a/lib/Slic3r/Format/OBJ.pm b/lib/Slic3r/Format/OBJ.pm
index 210853025..d663cfb5a 100644
--- a/lib/Slic3r/Format/OBJ.pm
+++ b/lib/Slic3r/Format/OBJ.pm
@@ -17,7 +17,10 @@ sub read_file {
}
close $fh;
- return Slic3r::TriangleMesh->new(vertices => $vertices, facets => $facets);
+ my $model = Slic3r::Model->new;
+ my $object = $model->add_object(vertices => $vertices);
+ my $volume = $object->add_volume(facets => $facets);
+ return $model;
}
1;
diff --git a/lib/Slic3r/Format/STL.pm b/lib/Slic3r/Format/STL.pm
index 9706fd00d..5325a4ea6 100644
--- a/lib/Slic3r/Format/STL.pm
+++ b/lib/Slic3r/Format/STL.pm
@@ -117,7 +117,10 @@ sub read_file {
}
}
- return Slic3r::TriangleMesh->new(vertices => $vertices, facets => $facets);
+ my $model = Slic3r::Model->new;
+ my $object = $model->add_object(vertices => $vertices);
+ my $volume = $object->add_volume(facets => $facets);
+ return $model;
}
sub _read_ascii {
@@ -161,13 +164,13 @@ sub _read_binary {
sub write_file {
my $self = shift;
- my ($file, $mesh, $binary) = @_;
+ my ($file, $model, %params) = @_;
open my $fh, '>', $file;
- $binary
- ? _write_binary($fh, $mesh)
- : _write_ascii($fh, $mesh);
+ $params{binary}
+ ? _write_binary($fh, $model->mesh)
+ : _write_ascii($fh, $model->mesh);
close $fh;
}
diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm
index 842cf2b0f..ae78e3b35 100644
--- a/lib/Slic3r/GCode.pm
+++ b/lib/Slic3r/GCode.pm
@@ -1,8 +1,9 @@
package Slic3r::GCode;
use Moo;
+use List::Util qw(first);
use Slic3r::ExtrusionPath ':roles';
-use Slic3r::Geometry qw(PI X Y scale unscale points_coincide);
+use Slic3r::Geometry qw(scale unscale scaled_epsilon points_coincide PI X Y);
use Slic3r::Geometry::Clipper qw(union_ex);
has 'layer' => (is => 'rw');
@@ -154,15 +155,13 @@ sub extrude_path {
my $gcode = "";
# retract if distance from previous position is greater or equal to the one
- # specified by the user *and* to the maximum distance between infill lines
+ # specified by the user
{
- my $distance_from_last_pos = $self->last_pos->distance_to($path->points->[0]) * &Slic3r::SCALING_FACTOR;
- my $distance_threshold = $self->extruder->retract_before_travel;
- $distance_threshold = 2 * ($self->layer ? $self->layer->flow->width : $Slic3r::flow->width) / $Slic3r::Config->fill_density * sqrt(2)
- if 0 && $Slic3r::Config->fill_density > 0 && $description =~ /fill/;
-
- if ($distance_from_last_pos >= $distance_threshold) {
- $gcode .= $self->retract(travel_to => $path->points->[0]);
+ my $travel = Slic3r::Line->new($self->last_pos, $path->points->[0]);
+ if ($travel->length >= scale $self->extruder->retract_before_travel) {
+ if (!$Slic3r::Config->only_retract_when_crossing_perimeters || $path->role != EXTR_ROLE_FILL || !first { $_->expolygon->encloses_line($travel, scaled_epsilon) } @{$self->layer->slices}) {
+ $gcode .= $self->retract(travel_to => $path->points->[0]);
+ }
}
}
@@ -478,25 +477,34 @@ sub set_temperature {
return "" if $wait && $Slic3r::Config->gcode_flavor eq 'makerbot';
- my ($code, $comment) = $wait
+ my ($code, $comment) = ($wait && $Slic3r::Config->gcode_flavor ne 'teacup')
? ('M109', 'wait for temperature to be reached')
: ('M104', 'set temperature');
- return sprintf "$code %s%d %s; $comment\n",
+ my $gcode = sprintf "$code %s%d %s; $comment\n",
($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature,
(defined $tool && $tool != $self->extruder_idx) ? "T$tool " : "";
+
+ $gcode .= "M116 ; wait for temperature to be reached\n"
+ if $Slic3r::Config->gcode_flavor eq 'teacup' && $wait;
+
+ return $gcode;
}
sub set_bed_temperature {
my $self = shift;
my ($temperature, $wait) = @_;
- my ($code, $comment) = $wait
+ my ($code, $comment) = ($wait && $Slic3r::Config->gcode_flavor ne 'teacup')
? (($Slic3r::Config->gcode_flavor eq 'makerbot' ? 'M109'
- : $Slic3r::Config->gcode_flavor eq 'teacup' ? 'M109 P1'
: 'M190'), 'wait for bed temperature to be reached')
: ('M140', 'set bed temperature');
- return sprintf "$code %s%d ; $comment\n",
+ my $gcode = sprintf "$code %s%d ; $comment\n",
($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature;
+
+ $gcode .= "M116 ; wait for bed temperature to be reached\n"
+ if $Slic3r::Config->gcode_flavor eq 'teacup' && $wait;
+
+ return $gcode;
}
1;
diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm
index 8bd7fa684..887d35320 100644
--- a/lib/Slic3r/GUI.pm
+++ b/lib/Slic3r/GUI.pm
@@ -22,6 +22,10 @@ use constant MI_REPEAT_QUICK => &Wx::NewId;
use constant MI_QUICK_SAVE_AS => &Wx::NewId;
use constant MI_SLICE_SVG => &Wx::NewId;
+use constant MI_PLATER_EXPORT_GCODE => &Wx::NewId;
+use constant MI_PLATER_EXPORT_STL => &Wx::NewId;
+use constant MI_PLATER_EXPORT_AMF => &Wx::NewId;
+
use constant MI_TAB_PLATER => &Wx::NewId;
use constant MI_TAB_PRINT => &Wx::NewId;
use constant MI_TAB_FILAMENT => &Wx::NewId;
@@ -47,7 +51,7 @@ sub OnInit {
$self->{notifier} = Slic3r::GUI::Notifier->new;
# locate or create data directory
- $datadir = Wx::StandardPaths::Get->GetUserDataDir;
+ $datadir ||= Wx::StandardPaths::Get->GetUserDataDir;
Slic3r::debugf "Data directory: %s\n", $datadir;
my $run_wizard = (-d $datadir) ? 0 : 1;
for ($datadir, "$datadir/print", "$datadir/filament", "$datadir/printer") {
@@ -98,6 +102,17 @@ sub OnInit {
EVT_MENU($frame, wxID_EXIT, sub {$_[0]->Close(0)});
}
+ # Plater menu
+ my $platerMenu = Wx::Menu->new;
+ {
+ $platerMenu->Append(MI_PLATER_EXPORT_GCODE, "Export G-code...", 'Export current plate as G-code');
+ $platerMenu->Append(MI_PLATER_EXPORT_STL, "Export STL...", 'Export current plate as STL');
+ $platerMenu->Append(MI_PLATER_EXPORT_AMF, "Export AMF...", 'Export current plate as AMF');
+ EVT_MENU($frame, MI_PLATER_EXPORT_GCODE, sub { $self->{skeinpanel}{plater}->export_gcode });
+ EVT_MENU($frame, MI_PLATER_EXPORT_STL, sub { $self->{skeinpanel}{plater}->export_stl });
+ EVT_MENU($frame, MI_PLATER_EXPORT_AMF, sub { $self->{skeinpanel}{plater}->export_amf });
+ }
+
# Window menu
my $windowMenu = Wx::Menu->new;
{
@@ -128,6 +143,7 @@ sub OnInit {
{
my $menubar = Wx::MenuBar->new;
$menubar->Append($fileMenu, "&File");
+ $menubar->Append($platerMenu, "&Plater");
$menubar->Append($windowMenu, "&Window");
$menubar->Append($helpMenu, "&Help");
$frame->SetMenuBar($menubar);
diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
index 7155f21ee..ff98c7458 100644
--- a/lib/Slic3r/GUI/Plater.pm
+++ b/lib/Slic3r/GUI/Plater.pm
@@ -296,7 +296,7 @@ sub load_file {
my $process_dialog = Wx::ProgressDialog->new('Loading…', "Processing input file…", 100, $self, 0);
$process_dialog->Pulse;
local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self);
- $self->{print}->add_object_from_file($input_file);
+ $self->{print}->add_objects_from_file($input_file);
my $obj_idx = $#{$self->{print}->objects};
$process_dialog->Destroy;
@@ -419,9 +419,7 @@ sub rotate {
# rotate, realign to 0,0 and update size
$object->mesh->rotate($angle);
$object->mesh->align_to_origin;
- my @size = $object->mesh->size;
- $object->x_length($size[X]);
- $object->y_length($size[Y]);
+ $object->size([ $object->mesh->size ]);
$self->make_thumbnail($obj_idx);
$self->recenter;
@@ -458,9 +456,7 @@ sub changescale {
my $mesh = $object->mesh;
$mesh->scale($scale/100 / $self->{scale}[$obj_idx]);
$object->mesh->align_to_origin;
- my @size = $object->mesh->size;
- $object->x_length($size[X]);
- $object->y_length($size[Y]);
+ $object->size([ $object->mesh->size ]);
$self->{scale}[$obj_idx] = $scale/100;
$self->{list}->SetItem($obj_idx, 2, "$scale%");
@@ -512,6 +508,8 @@ sub export_gcode {
# set this before spawning the thread because ->config needs GetParent and it's not available there
$self->{print}->config($self->skeinpanel->config);
+ $self->{print}->extra_variables->{"${_}_preset"} = $self->skeinpanel->{options_tabs}{$_}->current_preset->{name}
+ for qw(print filament printer);
# select output file
$self->{output_file} = $main::opt{output};
@@ -634,39 +632,58 @@ sub on_export_failed {
sub export_stl {
my $self = shift;
-
- my $print = $self->{print};
- # select output file
+ my $output_file = $self->_get_export_file('STL') or return;
+ Slic3r::Format::STL->write_file($output_file, $self->make_model, binary => 1);
+ $self->statusbar->SetStatusText("STL file exported to $output_file");
+}
+
+sub export_amf {
+ my $self = shift;
+
+ my $output_file = $self->_get_export_file('AMF') or return;
+ Slic3r::Format::AMF->write_file($output_file, $self->make_model);
+ $self->statusbar->SetStatusText("AMF file exported to $output_file");
+}
+
+sub _get_export_file {
+ my $self = shift;
+ my ($format) = @_;
+
+ my $suffix = $format eq 'STL' ? '.stl' : '.amf.xml';
+
my $output_file = $main::opt{output};
{
- $output_file = $print->expanded_output_filepath($output_file);
- $output_file =~ s/\.gcode$/.stl/i;
- my $dlg = Wx::FileDialog->new($self, 'Save STL file as:', dirname($output_file),
+ $output_file = $self->{print}->expanded_output_filepath($output_file);
+ $output_file =~ s/\.gcode$/$suffix/i;
+ my $dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file),
basename($output_file), $Slic3r::GUI::SkeinPanel::model_wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if ($dlg->ShowModal != wxID_OK) {
$dlg->Destroy;
- return;
+ return undef;
}
$output_file = $Slic3r::GUI::SkeinPanel::last_output_file = $dlg->GetPath;
$dlg->Destroy;
}
+ return $output_file;
+}
+
+sub make_model {
+ my $self = shift;
- my $mesh = Slic3r::TriangleMesh->new(facets => [], vertices => []);
- for my $obj_idx (0 .. $#{$print->objects}) {
- for my $copy (@{$print->copies->[$obj_idx]}) {
- my $cloned_mesh = $print->objects->[$obj_idx]->mesh->clone;
- $cloned_mesh->move(@$copy);
- my $vertices_offset = scalar @{$mesh->vertices};
- push @{$mesh->vertices}, @{$cloned_mesh->vertices};
- push @{$mesh->facets}, map [ $_->[0], map $vertices_offset + $_, @$_[-3..-1] ], @{$cloned_mesh->facets};
+ my $model = Slic3r::Model->new;
+ for my $obj_idx (0 .. $#{$self->{print}->objects}) {
+ my $mesh = $self->{print}->objects->[$obj_idx]->mesh->clone;
+ $mesh->scale(&Slic3r::SCALING_FACTOR);
+ my $object = $model->add_object(vertices => $mesh->vertices);
+ $object->add_volume(facets => $mesh->facets);
+ for my $copy (@{$self->{print}->copies->[$obj_idx]}) {
+ $object->add_instance(rotation => 0, offset => [ map unscale $_, @$copy ]);
}
}
- $mesh->scale(&Slic3r::SCALING_FACTOR);
- $mesh->align_to_origin;
+ # TODO: $model->align_to_origin;
- Slic3r::Format::STL->write_file($output_file, $mesh, 1);
- $self->statusbar->SetStatusText("STL file exported to $output_file");
+ return $model;
}
sub make_thumbnail {
diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm
index 764f60a81..babd1cfbc 100644
--- a/lib/Slic3r/GUI/SkeinPanel.pm
+++ b/lib/Slic3r/GUI/SkeinPanel.pm
@@ -96,7 +96,7 @@ sub do_slice {
Slic3r::GUI->save_settings;
my $print = Slic3r::Print->new(config => $config);
- $print->add_object_from_file($input_file);
+ $print->add_objects_from_file($input_file);
$print->validate;
# select output file
diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm
index d4d6ec8e5..6ca407571 100644
--- a/lib/Slic3r/GUI/Tab.pm
+++ b/lib/Slic3r/GUI/Tab.pm
@@ -405,7 +405,7 @@ sub build {
},
{
title => 'Advanced',
- options => [qw(infill_every_layers fill_angle solid_infill_below_area)],
+ options => [qw(infill_every_layers fill_angle solid_infill_below_area only_retract_when_crossing_perimeters)],
},
]);
diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm
index 979a2eb0e..dfad17ad0 100644
--- a/lib/Slic3r/Geometry.pm
+++ b/lib/Slic3r/Geometry.pm
@@ -20,6 +20,7 @@ our @EXPORT_OK = qw(
shortest_path collinear scale unscale merge_collinear_lines
rad2deg_dir bounding_box_center line_intersects_any douglas_peucker
polyline_remove_short_segments normal triangle_normal polygon_is_convex
+ scaled_epsilon
);
@@ -38,6 +39,7 @@ use constant MAX => 1;
our $parallel_degrees_limit = abs(deg2rad(3));
sub epsilon () { 1E-4 }
+sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR }
sub scale ($) { $_[0] / &Slic3r::SCALING_FACTOR }
sub unscale ($) { $_[0] * &Slic3r::SCALING_FACTOR }
diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm
index a76048fc4..5a1dfdd91 100644
--- a/lib/Slic3r/Geometry/Clipper.pm
+++ b/lib/Slic3r/Geometry/Clipper.pm
@@ -4,27 +4,32 @@ use warnings;
require Exporter;
our @ISA = qw(Exporter);
-our @EXPORT_OK = qw(explode_expolygon explode_expolygons safety_offset offset
+our @EXPORT_OK = qw(safety_offset offset offset_ex
diff_ex diff union_ex intersection_ex xor_ex PFT_EVENODD JT_MITER JT_ROUND
JT_SQUARE is_counter_clockwise);
-use Math::Clipper 1.09 ':all';
+use Math::Clipper 1.09 qw(:cliptypes :polyfilltypes :jointypes is_counter_clockwise area);
use Slic3r::Geometry qw(scale);
our $clipper = Math::Clipper->new;
-sub explode_expolygon {
- my ($expolygon) = @_;
- return ($expolygon->{outer}, @{ $expolygon->{holes} });
+sub safety_offset {
+ my ($polygons, $factor) = @_;
+ return Math::Clipper::offset($polygons, $factor || (scale 1e-05), 100, JT_MITER, 2);
}
-sub explode_expolygons {
- my ($expolygons) = @_;
- return map explode_expolygon($_), @$expolygons;
+sub offset {
+ my ($polygons, $distance, $scale, $joinType, $miterLimit) = @_;
+ $scale ||= &Slic3r::SCALING_FACTOR * 1000000;
+ $joinType = JT_MITER if !defined $joinType;
+ $miterLimit ||= 2;
+
+ my $offsets = Math::Clipper::offset($polygons, $distance, $scale, $joinType, $miterLimit);
+ return @$offsets;
}
-sub safety_offset {
- my ($polygons, $factor) = @_;
- return Math::Clipper::offset($polygons, $factor || (scale 1e-05), 100, JT_MITER, 2);
+sub offset_ex {
+ # offset polygons and then apply holes to the right contours
+ return @{ union_ex([ offset(@_) ]) };
}
sub diff_ex {
diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm
index fc33cf420..a6560b66e 100644
--- a/lib/Slic3r/Layer.pm
+++ b/lib/Slic3r/Layer.pm
@@ -318,7 +318,7 @@ sub make_perimeters {
{
my @thin_paths = ();
my %properties = (
- role => EXTR_ROLE_PERIMETER,
+ role => EXTR_ROLE_EXTERNAL_PERIMETER,
flow_spacing => $self->perimeter_flow->spacing,
);
for (@{ $self->thin_walls }) {
@@ -358,13 +358,30 @@ sub prepare_fill_surfaces {
if ($Slic3r::Config->fill_density == 0) {
@surfaces = grep $_->surface_type != S_TYPE_INTERNAL, @surfaces;
}
+
+ # remove unprintable regions (they would slow down the infill process and also cause
+ # some weird failures during bridge neighbor detection)
+ {
+ my $distance = scale $self->infill_flow->spacing / 2;
+ @surfaces = map {
+ my $surface = $_;
+
+ # offset inwards
+ my @offsets = $surface->expolygon->offset_ex(-$distance);
+ @offsets = @{union_ex(Math::Clipper::offset([ map @$_, @offsets ], $distance, 100, JT_MITER))};
+ map Slic3r::Surface->new(
+ expolygon => $_,
+ surface_type => $surface->surface_type,
+ ), @offsets;
+ } @surfaces;
+ }
# turn too small internal regions into solid regions
{
my $min_area = scale scale $Slic3r::Config->solid_infill_below_area; # scaling an area requires two calls!
my @small = grep $_->surface_type == S_TYPE_INTERNAL && $_->expolygon->contour->area <= $min_area, @surfaces;
$_->surface_type(S_TYPE_INTERNALSOLID) for @small;
- Slic3r::debugf "identified %d small surfaces at layer %d\n", scalar(@small), $self->id if @small > 0;
+ Slic3r::debugf "identified %d small solid surfaces at layer %d\n", scalar(@small), $self->id if @small > 0;
}
$self->fill_surfaces([@surfaces]);
diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm
new file mode 100644
index 000000000..b648d4b34
--- /dev/null
+++ b/lib/Slic3r/Model.pm
@@ -0,0 +1,112 @@
+package Slic3r::Model;
+use Moo;
+
+use Slic3r::Geometry qw(X Y Z);
+
+has 'materials' => (is => 'ro', default => sub { {} });
+has 'objects' => (is => 'ro', default => sub { [] });
+
+sub add_object {
+ my $self = shift;
+
+ my $object = Slic3r::Model::Object->new(model => $self, @_);
+ push @{$self->objects}, $object;
+ return $object;
+}
+
+# flattens everything to a single mesh
+sub mesh {
+ my $self = shift;
+
+ my $vertices = [];
+ my $facets = [];
+ foreach my $object (@{$self->objects}) {
+ my @instances = $object->instances ? @{$object->instances} : (undef);
+ foreach my $instance (@instances) {
+ my @vertices = @{$object->vertices};
+ if ($instance) {
+ # save Z coordinates, as rotation and translation discard them
+ my @z = map $_->[Z], @vertices;
+
+ if ($instance->rotation) {
+ # transform vertex coordinates
+ my $rad = Slic3r::Geometry::deg2rad($instance->rotation);
+ @vertices = Slic3r::Geometry::rotate_points($rad, undef, @vertices);
+ }
+ @vertices = Slic3r::Geometry::move_points($instance->offset, @vertices);
+
+ # reapply Z coordinates
+ $vertices[$_][Z] = $z[$_] for 0 .. $#z;
+ }
+
+ my $v_offset = @$vertices;
+ push @$vertices, @vertices;
+ foreach my $volume (@{$object->volumes}) {
+ push @$facets, map {
+ my $f = [@$_];
+ $f->[$_] += $v_offset for -3..-1;
+ $f;
+ } @{$volume->facets};
+ }
+ }
+ }
+
+ return Slic3r::TriangleMesh->new(
+ vertices => $vertices,
+ facets => $facets,
+ );
+}
+
+package Slic3r::Model::Material;
+use Moo;
+
+has 'model' => (is => 'ro', weak_ref => 1, required => 1);
+has 'attributes' => (is => 'rw', default => sub { {} });
+
+package Slic3r::Model::Object;
+use Moo;
+
+has 'model' => (is => 'ro', weak_ref => 1, required => 1);
+has 'vertices' => (is => 'ro', default => sub { [] });
+has 'volumes' => (is => 'ro', default => sub { [] });
+has 'instances' => (is => 'rw');
+
+sub add_volume {
+ my $self = shift;
+
+ my $volume = Slic3r::Model::Volume->new(object => $self, @_);
+ push @{$self->volumes}, $volume;
+ return $volume;
+}
+
+sub add_instance {
+ my $self = shift;
+
+ $self->instances([]) if !defined $self->instances;
+ push @{$self->instances}, Slic3r::Model::Instance->new(object => $self, @_);
+ return $self->instances->[-1];
+}
+
+package Slic3r::Model::Volume;
+use Moo;
+
+has 'object' => (is => 'ro', weak_ref => 1, required => 1);
+has 'material_id' => (is => 'rw');
+has 'facets' => (is => 'rw', default => sub { [] });
+
+sub mesh {
+ my $self = shift;
+ return Slic3r::TriangleMesh->new(
+ vertices => $self->object->vertices,
+ facets => $self->facets,
+ );
+}
+
+package Slic3r::Model::Instance;
+use Moo;
+
+has 'object' => (is => 'ro', weak_ref => 1, required => 1);
+has 'rotation' => (is => 'rw', default => sub { 0 });
+has 'offset' => (is => 'rw');
+
+1;
diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm
index 224478905..70cf1ae14 100644
--- a/lib/Slic3r/Polygon.pm
+++ b/lib/Slic3r/Polygon.pm
@@ -76,13 +76,7 @@ sub safety_offset {
sub offset {
my $self = shift;
- my ($distance, $scale, $joinType, $miterLimit) = @_;
- $scale ||= &Slic3r::SCALING_FACTOR * 1000000;
- $joinType = JT_MITER if !defined $joinType;
- $miterLimit ||= 2;
-
- my $offsets = Math::Clipper::offset([$self], $distance, $scale, $joinType, $miterLimit);
- return map Slic3r::Polygon->new($_), @$offsets;
+ return map Slic3r::Polygon->new($_), Slic3r::Geometry::Clipper::offset([$self], @_);
}
# this method subdivides the polygon segments to that no one of them
diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm
index 786957dab..da9cc0479 100644
--- a/lib/Slic3r/Polyline.pm
+++ b/lib/Slic3r/Polyline.pm
@@ -144,6 +144,7 @@ sub rotate {
my $self = shift;
my ($angle, $center) = @_;
@$self = Slic3r::Geometry::rotate_points($angle, $center, @$self);
+ bless $_, 'Slic3r::Point' for @$self;
return $self;
}
@@ -151,6 +152,7 @@ sub translate {
my $self = shift;
my ($x, $y) = @_;
@$self = Slic3r::Geometry::move_points([$x, $y], @$self);
+ bless $_, 'Slic3r::Point' for @$self;
return $self;
}
diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm
index 3f2b1769f..7ec3e40fc 100644
--- a/lib/Slic3r/Print.pm
+++ b/lib/Slic3r/Print.pm
@@ -5,11 +5,12 @@ use File::Basename qw(basename fileparse);
use File::Spec;
use Math::ConvexHull 1.0.4 qw(convex_hull);
use Slic3r::ExtrusionPath ':roles';
-use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 PI scale unscale move_points);
+use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 PI scale unscale move_points nearest_point);
use Slic3r::Geometry::Clipper qw(diff_ex union_ex intersection_ex offset JT_ROUND JT_SQUARE);
use Time::HiRes qw(gettimeofday tv_interval);
has 'config' => (is => 'rw', default => sub { Slic3r::Config->new_from_defaults }, trigger => 1);
+has 'extra_variables' => (is => 'rw', default => sub {{}});
has 'objects' => (is => 'rw', default => sub {[]});
has 'copies' => (is => 'rw', default => sub {[]}); # obj_idx => [copies...]
has 'total_extrusion_length' => (is => 'rw');
@@ -42,6 +43,14 @@ sub _trigger_config {
# store config in a handy place
$Slic3r::Config = $self->config;
+ # legacy with existing config files
+ $self->config->set('first_layer_height', $self->config->layer_height)
+ if !$self->config->first_layer_height;
+ $self->config->set_ifndef('small_perimeter_speed', $self->config->perimeter_speed);
+ $self->config->set_ifndef('bridge_speed', $self->config->infill_speed);
+ $self->config->set_ifndef('solid_infill_speed', $self->config->infill_speed);
+ $self->config->set_ifndef('top_solid_infill_speed', $self->config->solid_infill_speed);
+
# initialize extruder(s)
$Slic3r::extruders = [];
for my $t (0, map $_-1, map $self->config->get($_), qw(perimeter_extruder infill_extruder support_material_extruder)) {
@@ -71,36 +80,45 @@ sub _trigger_config {
# G-code flavors
$self->config->set('extrusion_axis', 'A') if $self->config->gcode_flavor eq 'mach3';
$self->config->set('extrusion_axis', '') if $self->config->gcode_flavor eq 'no-extrusion';
-
- # legacy with existing config files
- $self->config->set_ifndef('small_perimeter_speed', $self->config->perimeter_speed);
- $self->config->set_ifndef('bridge_speed', $self->config->infill_speed);
- $self->config->set_ifndef('solid_infill_speed', $self->config->infill_speed);
- $self->config->set_ifndef('top_solid_infill_speed', $self->config->solid_infill_speed);
}
-sub add_object_from_file {
+sub add_objects_from_file {
my $self = shift;
my ($input_file) = @_;
- my $object;
- if ($input_file =~ /\.stl$/i) {
- my $mesh = Slic3r::Format::STL->read_file($input_file);
- $mesh->check_manifoldness;
- $object = $self->add_object_from_mesh($mesh);
- } elsif ($input_file =~ /\.obj$/i) {
- my $mesh = Slic3r::Format::OBJ->read_file($input_file);
+ my $model = $input_file =~ /\.stl$/i ? Slic3r::Format::STL->read_file($input_file)
+ : $input_file =~ /\.obj$/i ? Slic3r::Format::OBJ->read_file($input_file)
+ : $input_file =~ /\.amf(\.xml)?$/i ? Slic3r::Format::AMF->read_file($input_file)
+ : die "Input file must have .stl, .obj or .amf(.xml) extension\n";
+
+ my @print_objects = $self->add_model($model);
+ $_->input_file($input_file) for @print_objects;
+}
+
+sub add_model {
+ my $self = shift;
+ my ($model) = @_;
+
+ my @print_objects = ();
+ foreach my $object (@{ $model->objects }) {
+ my $mesh = $object->volumes->[0]->mesh;
$mesh->check_manifoldness;
- $object = $self->add_object_from_mesh($mesh);
- } elsif ( $input_file =~ /\.amf(\.xml)?$/i) {
- my ($materials, $meshes_by_material) = Slic3r::Format::AMF->read_file($input_file);
- $_->check_manifoldness for values %$meshes_by_material;
- $object = $self->add_object_from_mesh($meshes_by_material->{_} || +(values %$meshes_by_material)[0]);
- } else {
- die "Input file must have .stl, .obj or .amf(.xml) extension\n";
+
+ if ($object->instances) {
+ # we ignore the per-instance rotation currently and only
+ # consider the first one
+ $mesh->rotate($object->instances->[0]->rotation);
+ }
+
+ push @print_objects, $self->add_object_from_mesh($mesh);
+
+ if ($object->instances) {
+ # replace the default [0,0] instance with the custom ones
+ @{$self->copies->[-1]} = map [ scale $_->offset->[X], scale $_->offset->[X] ], @{$object->instances};
+ }
}
- $object->input_file($input_file);
- return $object;
+
+ return @print_objects;
}
sub add_object_from_mesh {
@@ -112,11 +130,9 @@ sub add_object_from_mesh {
$mesh->align_to_origin;
# initialize print object
- my @size = $mesh->size;
my $object = Slic3r::Print::Object->new(
- mesh => $mesh,
- x_length => $size[X],
- y_length => $size[Y],
+ mesh => $mesh,
+ size => [ $mesh->size ],
);
push @{$self->objects}, $object;
@@ -201,8 +217,8 @@ sub duplicate {
for my $x_copy (1..$Slic3r::Config->duplicate_grid->[X]) {
for my $y_copy (1..$Slic3r::Config->duplicate_grid->[Y]) {
push @{$self->copies->[0]}, [
- ($object->x_length + $dist) * ($x_copy-1),
- ($object->y_length + $dist) * ($y_copy-1),
+ ($object->size->[X] + $dist) * ($x_copy-1),
+ ($object->size->[Y] + $dist) * ($y_copy-1),
];
}
}
@@ -220,8 +236,8 @@ sub arrange_objects {
my $total_parts = scalar map @$_, @{$self->copies};
my $partx = my $party = 0;
foreach my $object (@{$self->objects}) {
- $partx = $object->x_length if $object->x_length > $partx;
- $party = $object->y_length if $object->y_length > $party;
+ $partx = $object->size->[X] if $object->size->[X] > $partx;
+ $party = $object->size->[Y] if $object->size->[Y] > $party;
}
# object distance is max(duplicate_distance, clearance_radius)
@@ -246,9 +262,9 @@ sub bounding_box {
foreach my $copy (@{$self->copies->[$obj_idx]}) {
push @points,
[ $copy->[X], $copy->[Y] ],
- [ $copy->[X] + $object->x_length, $copy->[Y] ],
- [ $copy->[X] + $object->x_length, $copy->[Y] + $object->y_length ],
- [ $copy->[X], $copy->[Y] + $object->y_length ];
+ [ $copy->[X] + $object->size->[X], $copy->[Y] ],
+ [ $copy->[X] + $object->size->[X], $copy->[Y] + $object->size->[Y] ],
+ [ $copy->[X], $copy->[Y] + $object->size->[Y] ];
}
}
return Slic3r::Geometry::bounding_box(\@points);
@@ -497,7 +513,7 @@ sub make_skirt {
my @skirt = ();
for (my $i = $Slic3r::Config->skirts; $i > 0; $i--) {
my $distance = scale ($Slic3r::Config->skirt_distance + ($flow->spacing * $i));
- my $outline = offset([$convex_hull], $distance, &Slic3r::SCALING_FACTOR * 100, JT_ROUND);
+ my $outline = Math::Clipper::offset([$convex_hull], $distance, &Slic3r::SCALING_FACTOR * 100, JT_ROUND);
push @skirt, Slic3r::ExtrusionLoop->pack(
polygon => Slic3r::Polygon->new(@{$outline->[0]}),
role => EXTR_ROLE_SKIRT,
@@ -569,15 +585,6 @@ sub write_gcode {
print $fh $gcodegen->set_tool(0);
print $fh $gcodegen->set_fan(0, 1) if $Slic3r::Config->cooling && $Slic3r::Config->disable_fan_first_layers;
- # this spits out some platic at start from each extruder when they are first used;
- # the primary extruder will compensate by the normal retraction length, while
- # the others will compensate for their toolchange length + restart extra.
- # this is a temporary solution as all extruders should use some kind of skirt
- # to be put into a consistent state.
- $_->retracted($_->retract_length_toolchange + $_->retract_restart_extra_toolchange)
- for @{$Slic3r::extruders}[1 .. $#{$Slic3r::extruders}];
- $gcodegen->retract;
-
# write start commands to file
printf $fh $gcodegen->set_bed_temperature($Slic3r::Config->first_layer_bed_temperature, 1),
if $Slic3r::Config->first_layer_bed_temperature && $Slic3r::Config->start_gcode !~ /M190/i;
@@ -667,7 +674,9 @@ sub write_gcode {
# extrude brim
if ($layer_id == 0 && !$brim_done) {
- $gcodegen->set_shift(@shift);
+ $gcode .= $gcodegen->set_tool($Slic3r::Config->support_material_extruder-1);
+ $gcodegen->shift_x($shift[X]);
+ $gcodegen->shift_y($shift[Y]);
$gcode .= $gcodegen->extrude_loop($_, 'brim') for @{$self->brim};
$brim_done = 1;
$gcodegen->straight_once(1);
@@ -838,6 +847,7 @@ sub expanded_output_filepath {
return $Slic3r::Config->replace_options($path, {
input_filename => $input_filename,
input_filename_base => $input_filename_base,
+ %{ $self->extra_variables },
});
}
diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm
index c06358b83..696d72ca3 100644
--- a/lib/Slic3r/Print/Object.pm
+++ b/lib/Slic3r/Print/Object.pm
@@ -8,8 +8,7 @@ use Slic3r::Surface ':types';
has 'input_file' => (is => 'rw', required => 0);
has 'mesh' => (is => 'rw', required => 0);
-has 'x_length' => (is => 'rw', required => 1);
-has 'y_length' => (is => 'rw', required => 1);
+has 'size' => (is => 'rw', required => 1);
has 'layers' => (
traits => ['Array'],
diff --git a/slic3r.pl b/slic3r.pl
index 9fda632f3..13a75366c 100755
--- a/slic3r.pl
+++ b/slic3r.pl
@@ -27,6 +27,7 @@ my %cli_options = ();
'save=s' => \$opt{save},
'load=s@' => \$opt{load},
'ignore-nonexistent-config' => \$opt{ignore_nonexistent_config},
+ 'datadir=s' => \$opt{datadir},
'export-svg' => \$opt{export_svg},
'merge|m' => \$opt{merge},
);
@@ -70,6 +71,10 @@ if ($opt{save}) {
# launch GUI
my $gui;
if (!@ARGV && !$opt{save} && eval "require Slic3r::GUI; 1") {
+ {
+ no warnings 'once';
+ $Slic3r::GUI::datadir = $opt{datadir} if $opt{datadir};
+ }
$gui = Slic3r::GUI->new;
$gui->{skeinpanel}->load_config_file($_) for @{$opt{load}};
$gui->{skeinpanel}->load_config($cli_config);
@@ -83,9 +88,9 @@ if (@ARGV) { # slicing from command line
while (my $input_file = shift @ARGV) {
my $print = Slic3r::Print->new(config => $config);
- $print->add_object_from_file($input_file);
+ $print->add_objects_from_file($input_file);
if ($opt{merge}) {
- $print->add_object_from_file($_) for splice @ARGV, 0;
+ $print->add_objects_from_file($_) for splice @ARGV, 0;
}
$print->duplicate;
$print->arrange_objects if @{$print->objects} > 1;
@@ -214,6 +219,9 @@ $j
--extra-perimeters Add more perimeters when needed (default: yes)
--randomize-start Randomize starting point across layers (default: yes)
--avoid-crossing-perimeters Optimize travel moves so that no perimeters are crossed (default: no)
+ --only-retract-when-crossing-perimeters
+ Disable retraction when travelling between infill paths inside the same island.
+ (default: no)
--solid-infill-below-area
Force solid infill when a region has a smaller area than this threshold
(mm^2, default: $config->{solid_infill_below_area})
diff --git a/t/arcs.t b/t/arcs.t
index 1ffcd87b0..972620402 100644
--- a/t/arcs.t
+++ b/t/arcs.t
@@ -11,7 +11,7 @@ BEGIN {
use Slic3r;
use Slic3r::ExtrusionPath ':roles';
-use Slic3r::Geometry qw(epsilon scale X Y);
+use Slic3r::Geometry qw(scaled_epsilon scale X Y);
{
my $path = Slic3r::ExtrusionPath->new(polyline => Slic3r::Polyline->new(
@@ -61,17 +61,17 @@ use Slic3r::Geometry qw(epsilon scale X Y);
isa_ok $collection2->paths->[0], 'Slic3r::ExtrusionPath::Arc', 'path';
my $expected_length = scale 7.06858347057701;
- ok abs($collection1->paths->[0]->length - $expected_length) < scale epsilon, 'cw oriented arc has correct length';
- ok abs($collection2->paths->[0]->length - $expected_length) < scale epsilon, 'ccw oriented arc has correct length';
+ ok abs($collection1->paths->[0]->length - $expected_length) < scaled_epsilon, 'cw oriented arc has correct length';
+ ok abs($collection2->paths->[0]->length - $expected_length) < scaled_epsilon, 'ccw oriented arc has correct length';
is $collection1->paths->[0]->orientation, 'cw', 'cw orientation was correctly detected';
is $collection2->paths->[0]->orientation, 'ccw', 'ccw orientation was correctly detected';
my $center1 = [ map sprintf('%.0f', $_), @{ $collection1->paths->[0]->center } ];
- ok abs($center1->[X] - scale 10) < scale epsilon && abs($center1->[Y] - scale 10) < scale epsilon, 'center was correctly detected';
+ ok abs($center1->[X] - scale 10) < scaled_epsilon && abs($center1->[Y] - scale 10) < scaled_epsilon, 'center was correctly detected';
my $center2 = [ map sprintf('%.0f', $_), @{ $collection2->paths->[0]->center } ];
- ok abs($center2->[X] - scale 10) < scale epsilon && abs($center1->[Y] - scale 10) < scale epsilon, 'center was correctly detected';
+ ok abs($center2->[X] - scale 10) < scaled_epsilon && abs($center1->[Y] - scale 10) < scaled_epsilon, 'center was correctly detected';
}
#==========================================================
diff --git a/t/fill.t b/t/fill.t
index f45aab2da..2a7ef1f93 100644
--- a/t/fill.t
+++ b/t/fill.t
@@ -2,7 +2,7 @@ use Test::More;
use strict;
use warnings;
-plan tests => 2;
+plan tests => 4;
BEGIN {
use FindBin;
@@ -10,14 +10,13 @@ BEGIN {
}
use Slic3r;
+use Slic3r::Geometry qw(scale X Y);
+use Slic3r::Surface qw(:types);
-my $print = Slic3r::Print->new(
- x_length => 50,
- y_length => 50,
-);
+sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
{
- my $filler = Slic3r::Fill::Rectilinear->new(print => $print);
+ my $filler = Slic3r::Fill::Rectilinear->new(print => Slic3r::Print->new);
my $surface_width = 250;
my $distance = $filler->adjust_solid_spacing(
width => $surface_width,
@@ -27,4 +26,20 @@ my $print = Slic3r::Print->new(
is $surface_width % $distance, 0, 'adjusted solid distance';
}
+{
+ my $filler = Slic3r::Fill::Rectilinear->new(
+ print => Slic3r::Print->new,
+ max_print_dimension => scale 100,
+ );
+ my $surface = Slic3r::Surface->new(
+ surface_type => S_TYPE_TOP,
+ expolygon => Slic3r::ExPolygon->new([ scale_points [0,0], [50,0], [50,50], [0,50] ]),
+ );
+ foreach my $angle (0, 45) {
+ $surface->expolygon->rotate($angle, [0,0]);
+ my ($params, @paths) = $filler->fill_surface($surface, flow_spacing => 0.69, density => 0.4);
+ is scalar @paths, 1, 'one continuous path';
+ }
+}
+
__END__
diff --git a/utils/amf-to-stl.pl b/utils/amf-to-stl.pl
index 847e5e749..b421dd33a 100755
--- a/utils/amf-to-stl.pl
+++ b/utils/amf-to-stl.pl
@@ -25,12 +25,12 @@ my %opt = ();
}
{
- my $mesh = Slic3r::Format::AMF->read_file($ARGV[0]);
+ my $model = Slic3r::Format::AMF->read_file($ARGV[0]);
my $output_file = $ARGV[0];
$output_file =~ s/\.amf(?:\.xml)?$/\.stl/i;
printf "Writing to %s\n", basename($output_file);
- Slic3r::Format::STL->write_file($output_file, $mesh, !$opt{ascii});
+ Slic3r::Format::STL->write_file($output_file, $model, binary => !$opt{ascii});
}
diff --git a/utils/post-processing/filament-weight.pl b/utils/post-processing/filament-weight.pl
index c564b4d80..5ed836461 100755
--- a/utils/post-processing/filament-weight.pl
+++ b/utils/post-processing/filament-weight.pl
@@ -1,20 +1,30 @@
#!/usr/bin/perl -i
#
-# Post-processing script for adding weight of required filament to
-# G-code output.
+# Post-processing script for adding weight and cost of required
+# filament to G-code output.
use strict;
use warnings;
# example densities, adjust according to filament specifications
-use constant PLA => 1.25; # g/cm3
-use constant ABS => 1.05; # g/cm3
+use constant PLA_P => 1.25; # g/cm3
+use constant ABS_P => 1.05; # g/cm3
+
+# example costs, adjust according to filament prices
+use constant PLA_PRICE => 0.05; # EUR/g
+use constant ABS_PRICE => 0.02; # EUR/g
+use constant CURRENCY => "EUR";
while (<>) {
if (/^(;\s+filament\s+used\s+=\s.*\((\d+(?:\.\d+)?)cm3)\)/) {
- my $pla = $2 * PLA;
- my $abs = $2 * ABS;
- printf "%s or %.2fg PLA/%.2fg ABS)\n", $1, $pla, $abs;
+ my $pla_weight = $2 * PLA_P;
+ my $abs_weight = $2 * ABS_P;
+
+ my $pla_costs = $pla_weight * PLA_PRICE;
+ my $abs_costs = $abs_weight * ABS_PRICE;
+
+ printf "%s or %.2fg PLA/%.2fg ABS)\n", $1, $pla_weight, $abs_weight;
+ printf "; costs = %s %.2f (PLA), %s %.2f (ABS)\n", CURRENCY, $pla_costs, CURRENCY, $abs_costs;
} else {
print;
}
diff --git a/utils/split_stl.pl b/utils/split_stl.pl
index af9890116..42d2926bd 100755
--- a/utils/split_stl.pl
+++ b/utils/split_stl.pl
@@ -25,15 +25,20 @@ my %opt = ();
}
{
- my $mesh = Slic3r::Format::STL->read_file($ARGV[0]);
+ my $model = Slic3r::Format::STL->read_file($ARGV[0]);
my $basename = $ARGV[0];
$basename =~ s/\.stl$//i;
my $part_count = 0;
- foreach my $new_mesh ($mesh->split_mesh) {
+ foreach my $new_mesh ($model->mesh->split_mesh) {
+ my $new_model = Slic3r::Model->new;
+ $new_model
+ ->add_object(vertices => $new_mesh->vertices)
+ ->add_volume(facets => $new_mesh->facets);
+
my $output_file = sprintf '%s_%02d.stl', $basename, ++$part_count;
printf "Writing to %s\n", basename($output_file);
- Slic3r::Format::STL->write_file($output_file, $new_mesh, !$opt{ascii});
+ Slic3r::Format::STL->write_file($output_file, $new_model, binary => !$opt{ascii});
}
}
diff --git a/utils/stl-to-amf.pl b/utils/stl-to-amf.pl
index f4805369b..78e5989b4 100755
--- a/utils/stl-to-amf.pl
+++ b/utils/stl-to-amf.pl
@@ -18,29 +18,50 @@ my %opt = ();
{
my %options = (
'help' => sub { usage() },
+ 'distinct-materials' => \$opt{distinct_materials},
);
GetOptions(%options) or usage(1);
$ARGV[0] or usage(1);
}
{
- my @meshes = map Slic3r::Format::STL->read_file($_), @ARGV;
+ my @models = map Slic3r::Format::STL->read_file($_), @ARGV;
my $output_file = $ARGV[0];
$output_file =~ s/\.stl$/.amf.xml/i;
- my $materials = {};
- my $meshes_by_material = {};
- if (@meshes == 1) {
- $meshes_by_material->{_} = $meshes[0];
+ my $new_model = Slic3r::Model->new;
+
+ if ($opt{distinct_materials} && @models > 1) {
+ my $new_object = $new_model->add_object;
+ for my $m (0 .. $#models) {
+ my $model = $models[$m];
+ my $v_offset = @{$new_object->vertices};
+ push @{$new_object->vertices}, @{$model->objects->[0]->vertices};
+ my @new_facets = map {
+ my $f = [@$_];
+ $f->[$_] += $v_offset for -3..-1;
+ $f;
+ } @{ $model->objects->[0]->volumes->[0]->facets };
+
+ my $material_id = scalar keys %{$new_model->materials};
+ $new_model->materials->{$material_id} = { Name => basename($ARGV[$m]) };
+ $new_object->add_volume(
+ material_id => $material_id,
+ facets => [@new_facets],
+ );
+ }
} else {
- for (0..$#meshes) {
- $materials->{$_+1} = { Name => basename($ARGV[$_]) };
- $meshes_by_material->{$_+1} = $meshes[$_];
+ foreach my $model (@models) {
+ $new_model->add_object(
+ vertices => $model->objects->[0]->vertices,
+ )->add_volume(
+ facets => $model->objects->[0]->volumes->[0]->facets,
+ );
}
}
printf "Writing to %s\n", basename($output_file);
- Slic3r::Format::AMF->write_file($output_file, $materials, $meshes_by_material);
+ Slic3r::Format::AMF->write_file($output_file, $new_model);
}
@@ -51,6 +72,7 @@ sub usage {
Usage: amf-to-stl.pl [ OPTIONS ] file.stl [ file2.stl [ file3.stl ] ]
--help Output this usage screen and exit
+ --distinct-materials Assign each STL file to a different material
EOF
exit ($exit_code || 0);