Merge branch 'master' into avoid-crossing-perimeters

Conflicts:
	README.markdown
	lib/Slic3r/Config.pm
	lib/Slic3r/GCode.pm
	lib/Slic3r/Print.pm
	slic3r.pl
This commit is contained in:
Alessandro Ranellucci 2012-09-12 16:04:16 +02:00
commit cc8d8bd26f
34 changed files with 531 additions and 267 deletions

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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.',

View file

@ -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;
return Slic3r::Geometry::Clipper::offset($self, @_);
}
my $offsets = Math::Clipper::offset($self, $distance, $scale, $joinType, $miterLimit);
return @$offsets;
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) = @_;

View file

@ -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,
],
);

View file

@ -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],

View file

@ -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;

View file

@ -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;

View file

@ -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 {

View file

@ -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};
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};
my $vertices_count = 0;
foreach my $mesh (values %$meshes_by_material) {
$vertices_offset{$mesh} = $vertices_count;
foreach my $vertex (@{$mesh->vertices}, ) {
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};
foreach my $volume (@{ $object->volumes }) {
printf $fh qq{ <volume%s>\n},
($material_id eq '_') ? '' : " materialid=\"$material_id\"";
foreach my $facet (@{$mesh->facets}) {
(!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}, $_, $facet->[$_] + $vertices_offset{$mesh}, $_
for -3..-1;
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{ </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{</amf>\n};
close $fh;
}

View file

@ -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} ],
);
}
}
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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,17 +155,15 @@ 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) {
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]);
}
}
}
# go to first point of extrusion path
$gcode .= $self->travel_to($path->points->[0], "move to first $description point");
@ -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;

View file

@ -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);

View file

@ -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};
@ -635,38 +633,57 @@ sub on_export_failed {
sub export_stl {
my $self = shift;
my $print = $self->{print};
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';
# select output file
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;
}
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};
}
}
sub make_model {
my $self = shift;
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);
$mesh->align_to_origin;
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 ]);
}
}
# 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 {

View file

@ -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

View file

@ -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)],
},
]);

View file

@ -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 }

View file

@ -4,29 +4,34 @@ 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 explode_expolygons {
my ($expolygons) = @_;
return map explode_expolygon($_), @$expolygons;
}
sub safety_offset {
my ($polygons, $factor) = @_;
return Math::Clipper::offset($polygons, $factor || (scale 1e-05), 100, JT_MITER, 2);
}
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 offset_ex {
# offset polygons and then apply holes to the right contours
return @{ union_ex([ offset(@_) ]) };
}
sub diff_ex {
my ($subject, $clip, $safety_offset) = @_;

View file

@ -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 }) {
@ -359,12 +359,29 @@ sub prepare_fill_surfaces {
@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]);

112
lib/Slic3r/Model.pm Normal file
View file

@ -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;

View file

@ -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

View file

@ -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;
}

View file

@ -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);
$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";
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;
}
$object->input_file($input_file);
return $object;
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;
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};
}
}
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],
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 },
});
}

View file

@ -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'],

View file

@ -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})

View file

@ -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';
}
#==========================================================

View file

@ -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__

View file

@ -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});
}

View file

@ -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;
}

View file

@ -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});
}
}

View file

@ -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);