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:
commit
cc8d8bd26f
34 changed files with 531 additions and 267 deletions
1
MANIFEST
1
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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.',
|
||||
|
|
|
@ -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) = @_;
|
||||
|
|
|
@ -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,
|
||||
],
|
||||
);
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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} ],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)],
|
||||
},
|
||||
]);
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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) = @_;
|
||||
|
||||
|
|
|
@ -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
112
lib/Slic3r/Model.pm
Normal 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;
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 },
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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'],
|
||||
|
|
12
slic3r.pl
12
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})
|
||||
|
|
10
t/arcs.t
10
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';
|
||||
}
|
||||
|
||||
#==========================================================
|
||||
|
|
27
t/fill.t
27
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__
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue