Large refactoring to allow processing multimaterial files

This commit is contained in:
Alessandro Ranellucci 2012-09-22 19:04:36 +02:00
parent 04be94023b
commit 02356fd613
13 changed files with 982 additions and 891 deletions

View File

@ -34,6 +34,7 @@ lib/Slic3r/GUI/Plater.pm
lib/Slic3r/GUI/SkeinPanel.pm lib/Slic3r/GUI/SkeinPanel.pm
lib/Slic3r/GUI/Tab.pm lib/Slic3r/GUI/Tab.pm
lib/Slic3r/Layer.pm lib/Slic3r/Layer.pm
lib/Slic3r/Layer/Material.pm
lib/Slic3r/Line.pm lib/Slic3r/Line.pm
lib/Slic3r/Model.pm lib/Slic3r/Model.pm
lib/Slic3r/Point.pm lib/Slic3r/Point.pm

View File

@ -43,6 +43,7 @@ use Slic3r::Format::STL;
use Slic3r::GCode; use Slic3r::GCode;
use Slic3r::Geometry qw(PI); use Slic3r::Geometry qw(PI);
use Slic3r::Layer; use Slic3r::Layer;
use Slic3r::Layer::Material;
use Slic3r::Line; use Slic3r::Line;
use Slic3r::Model; use Slic3r::Model;
use Slic3r::Point; use Slic3r::Point;
@ -70,8 +71,9 @@ sub parallelize {
my %params = @_; my %params = @_;
if (!$params{disable} && $Slic3r::have_threads && $Config->threads > 1) { if (!$params{disable} && $Slic3r::have_threads && $Config->threads > 1) {
my @items = (ref $params{items} eq 'CODE') ? $params{items}->() : @{$params{items}};
my $q = Thread::Queue->new; my $q = Thread::Queue->new;
$q->enqueue(@{ $params{items} }, (map undef, 1..$Config->threads)); $q->enqueue(@items, (map undef, 1..$Config->threads));
my $thread_cb = sub { $params{thread_cb}->($q) }; my $thread_cb = sub { $params{thread_cb}->($q) };
foreach my $th (map threads->create($thread_cb), 1..$Config->threads) { foreach my $th (map threads->create($thread_cb), 1..$Config->threads) {

View File

@ -35,8 +35,8 @@ sub write_file {
for my $material_id (sort keys %{ $model->materials }) { for my $material_id (sort keys %{ $model->materials }) {
my $material = $model->materials->{$material_id}; my $material = $model->materials->{$material_id};
printf $fh qq{ <material id="%d">\n}, $material_id; printf $fh qq{ <material id="%d">\n}, $material_id;
for (keys %$material) { for (keys %{$material->attributes}) {
printf $fh qq{ <metadata type=\"%s\">%s</metadata>\n}, $_, $material->{$_}; printf $fh qq{ <metadata type=\"%s\">%s</metadata>\n}, $_, $material->attributes->{$_};
} }
printf $fh qq{ </material>\n}; printf $fh qq{ </material>\n};
} }

View File

@ -37,10 +37,10 @@ sub start_element {
$self->{_vertex_idx} = $1-1; $self->{_vertex_idx} = $1-1;
} elsif ($data->{LocalName} eq 'material') { } elsif ($data->{LocalName} eq 'material') {
my $material_id = $self->_get_attribute($data, 'id') || '_'; my $material_id = $self->_get_attribute($data, 'id') || '_';
$self->{_material} = $self->{_model}->materials->{ $material_id } = {}; $self->{_material} = $self->{_model}->set_material($material_id);
} elsif ($data->{LocalName} eq 'metadata' && $self->{_tree}[-1] eq 'material') { } elsif ($data->{LocalName} eq 'metadata' && $self->{_tree}[-1] eq 'material') {
$self->{_material_metadata_type} = $self->_get_attribute($data, 'type'); $self->{_material_metadata_type} = $self->_get_attribute($data, 'type');
$self->{_material}{ $self->{_material_metadata_type} } = ""; $self->{_material}->attributes->{ $self->{_material_metadata_type} } = "";
} elsif ($data->{LocalName} eq 'constellation') { } elsif ($data->{LocalName} eq 'constellation') {
$self->{_constellation} = 1; # we merge all constellations as we don't support more than one $self->{_constellation} = 1; # we merge all constellations as we don't support more than one
} elsif ($data->{LocalName} eq 'instance' && $self->{_constellation}) { } elsif ($data->{LocalName} eq 'instance' && $self->{_constellation}) {
@ -63,7 +63,7 @@ sub characters {
} elsif ($self->{_triangle} && defined $self->{_vertex_idx}) { } elsif ($self->{_triangle} && defined $self->{_vertex_idx}) {
$self->{_triangle}[ $self->{_vertex_idx} ] .= $data->{Data}; $self->{_triangle}[ $self->{_vertex_idx} ] .= $data->{Data};
} elsif ($self->{_material_metadata_type}) { } elsif ($self->{_material_metadata_type}) {
$self->{_material}{ $self->{_material_metadata_type} } .= $data->{Data}; $self->{_material}->attributes->{ $self->{_material_metadata_type} } .= $data->{Data};
} elsif ($self->{_instance_property}) { } elsif ($self->{_instance_property}) {
$self->{_instance}{ $self->{_instance_property} } .= $data->{Data}; $self->{_instance}{ $self->{_instance_property} } .= $data->{Data};
} }

View File

@ -96,7 +96,7 @@ sub do_slice {
Slic3r::GUI->save_settings; Slic3r::GUI->save_settings;
my $print = Slic3r::Print->new(config => $config); my $print = Slic3r::Print->new(config => $config);
$print->add_objects_from_file($input_file); $print->add_model(Slic3r::Model->read_from_file($input_file));
$print->validate; $print->validate;
# select output file # select output file

View File

@ -1,60 +1,25 @@
package Slic3r::Layer; package Slic3r::Layer;
use Moo; use Moo;
use Math::Clipper ':all'; use Slic3r::Geometry::Clipper qw(union_ex);
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Geometry qw(scale unscale collinear X Y A B PI rad2deg_dir bounding_box_center shortest_path);
use Slic3r::Geometry::Clipper qw(safety_offset union_ex diff_ex intersection_ex xor_ex is_counter_clockwise);
use Slic3r::Surface ':types';
# a sequential number of layer, starting at 0 has 'id' => (is => 'rw', required => 1); # sequential number of layer, 0-based
has 'id' => ( has 'materials' => (is => 'ro', default => sub { [] });
is => 'rw', has 'slicing_errors' => (is => 'rw');
#isa => 'Int',
required => 1,
);
has 'slicing_errors' => (is => 'rw'); has 'slice_z' => (is => 'lazy');
has 'print_z' => (is => 'lazy');
has 'height' => (is => 'lazy');
has 'flow' => (is => 'lazy');
has 'perimeter_flow' => (is => 'lazy');
has 'infill_flow' => (is => 'lazy');
has 'slice_z' => (is => 'lazy'); # collection of surfaces generated by slicing the original geometry;
has 'print_z' => (is => 'lazy'); # also known as 'islands' (all materials are merged here)
has 'height' => (is => 'lazy'); has 'slices' => (is => 'rw');
has 'flow' => (is => 'lazy');
has 'perimeter_flow' => (is => 'lazy');
has 'infill_flow' => (is => 'lazy');
# collection of spare segments generated by slicing the original geometry;
# these need to be merged in continuos (closed) polylines
has 'lines' => (is => 'rw', default => sub { [] });
# collection of surfaces generated by slicing the original geometry
has 'slices' => (is => 'rw');
# collection of polygons or polylines representing thin walls contained
# in the original geometry
has 'thin_walls' => (is => 'rw');
# collection of polygons or polylines representing thin infill regions that
# need to be filled with a medial axis
has 'thin_fills' => (is => 'rw');
# collection of expolygons generated by offsetting the innermost perimeter(s)
# they represent boundaries of areas to fill, typed (top/bottom/internal)
has 'surfaces' => (is => 'rw');
# collection of surfaces for infill generation. the difference between surfaces
# fill_surfaces is that this one honors fill_density == 0 and turns small internal
# surfaces into solid ones
has 'fill_surfaces' => (is => 'rw');
# ordered collection of extrusion paths/loops to build all perimeters
has 'perimeters' => (is => 'rw');
# ordered collection of extrusion paths to fill surfaces for support material # ordered collection of extrusion paths to fill surfaces for support material
has 'support_fills' => (is => 'rw'); has 'support_fills' => (is => 'rw');
# ordered collection of extrusion paths to fill surfaces
has 'fills' => (is => 'rw');
# Z used for slicing # Z used for slicing
sub _build_slice_z { sub _build_slice_z {
@ -99,473 +64,36 @@ sub _build_infill_flow {
: $Slic3r::infill_flow; : $Slic3r::infill_flow;
} }
# build polylines from lines sub material {
sub make_surfaces {
my $self = shift; my $self = shift;
my ($loops) = @_; my ($material_idx) = @_;
{ for my $i (grep !defined $self->materials->[$_], 0..$material_idx) {
my $safety_offset = scale 0.1; $self->materials->[$i] = Slic3r::Layer::Material->new(
# merge everything layer => $self,
my $expolygons = [ map $_->offset_ex(-$safety_offset), @{union_ex(safety_offset($loops, $safety_offset))} ];
Slic3r::debugf " %d surface(s) having %d holes detected from %d polylines\n",
scalar(@$expolygons), scalar(map $_->holes, @$expolygons), scalar(@$loops);
$self->slices([
map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL),
@$expolygons
]);
}
# the contours must be offsetted by half extrusion width inwards
{
my $distance = scale $self->perimeter_flow->width / 2;
my @surfaces = @{$self->slices};
@{$self->slices} = ();
foreach my $surface (@surfaces) {
push @{$self->slices}, map Slic3r::Surface->new
(expolygon => $_, surface_type => S_TYPE_INTERNAL),
map $_->offset_ex(+$distance),
$surface->expolygon->offset_ex(-2*$distance);
}
# now detect thin walls by re-outgrowing offsetted surfaces and subtracting
# them from the original slices
my $outgrown = Math::Clipper::offset([ map $_->p, @{$self->slices} ], $distance);
my $diff = diff_ex(
[ map $_->p, @surfaces ],
$outgrown,
1,
);
$self->thin_walls([]);
if (@$diff) {
my $area_threshold = scale($self->perimeter_flow->spacing) ** 2;
@$diff = grep $_->area > ($area_threshold), @$diff;
@{$self->thin_walls} = map $_->medial_axis(scale $self->perimeter_flow->width), @$diff;
Slic3r::debugf " %d thin walls detected\n", scalar(@{$self->thin_walls}) if @{$self->thin_walls};
}
}
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(undef, "surfaces.svg",
polygons => [ map $_->contour, @{$self->slices} ],
red_polygons => [ map $_->p, map @{$_->holes}, @{$self->slices} ],
); );
} }
return $self->materials->[$material_idx];
}
# merge all materials' slices to get islands
sub make_slices {
my $self = shift;
# optimization for single-material layers
my @materials_with_slices = grep { @{$_->slices} } @{$self->materials};
if (@materials_with_slices == 1) {
$self->slices($materials_with_slices[0]->slices);
return;
}
$self->slices(union_ex([ map @{$_->slices}, @{$self->materials} ]));
} }
sub make_perimeters { sub make_perimeters {
my $self = shift; my $self = shift;
Slic3r::debugf "Making perimeters for layer %d\n", $self->id; Slic3r::debugf "Making perimeters for layer %d\n", $self->id;
$_->make_perimeters for @{$self->materials};
my $gap_area_threshold = scale($self->perimeter_flow->width)** 2;
# this array will hold one arrayref per original surface (island);
# each item of this arrayref is an arrayref representing a depth (from outer
# perimeters to inner); each item of this arrayref is an ExPolygon:
# @perimeters = (
# [ # first island
# [ Slic3r::ExPolygon, Slic3r::ExPolygon... ], #depth 0: outer loop
# [ Slic3r::ExPolygon, Slic3r::ExPolygon... ], #depth 1: inner loop
# ],
# [ # second island
# ...
# ]
# )
my @perimeters = (); # one item per depth; each item
# organize islands using a shortest path search
my @surfaces = @{shortest_path([
map [ $_->contour->[0], $_ ], @{$self->slices},
])};
$self->perimeters([]);
$self->surfaces([]);
$self->thin_fills([]);
# for each island:
foreach my $surface (@surfaces) {
my @last_offsets = ($surface->expolygon);
# experimental hole compensation (see ArcCompensation in the RepRap wiki)
if (0) {
foreach my $hole ($last_offsets[0]->holes) {
my $circumference = abs($hole->length);
next unless $circumference <= &Slic3r::SMALL_PERIMETER_LENGTH;
# this compensation only works for circular holes, while it would
# overcompensate for hexagons and other shapes having straight edges.
# so we require a minimum number of vertices.
next unless $circumference / @$hole >= scale 3 * $Slic3r::flow->width;
# revert the compensation done in make_surfaces() and get the actual radius
# of the hole
my $radius = ($circumference / PI / 2) - scale $self->perimeter_flow->spacing/2;
my $new_radius = (scale($self->perimeter_flow->width) + sqrt((scale($self->perimeter_flow->width)**2) + (4*($radius**2)))) / 2;
# holes are always turned to contours, so reverse point order before and after
$hole->reverse;
my @offsetted = $hole->offset(+ ($new_radius - $radius));
# skip arc compensation when hole is not round (thus leads to multiple offsets)
@$hole = map Slic3r::Point->new($_), @{ $offsetted[0] } if @offsetted == 1;
$hole->reverse;
}
}
my $distance = scale $self->perimeter_flow->spacing;
my @gaps = ();
# generate perimeters inwards (loop 0 is the external one)
my $loop_number = $Slic3r::Config->perimeters + ($surface->additional_inner_perimeters || 0);
push @perimeters, [[@last_offsets]];
for (my $loop = 1; $loop < $loop_number; $loop++) {
# offsetting a polygon can result in one or many offset polygons
my @new_offsets = ();
foreach my $expolygon (@last_offsets) {
my @offsets = map $_->offset_ex(+0.5*$distance), $expolygon->offset_ex(-1.5*$distance);
push @new_offsets, @offsets;
my $diff = diff_ex(
[ map @$_, $expolygon->offset_ex(-$distance) ],
[ map @$_, @offsets ],
);
push @gaps, grep $_->area >= $gap_area_threshold, @$diff;
}
@last_offsets = @new_offsets;
last if !@last_offsets;
push @{ $perimeters[-1] }, [@last_offsets];
}
# create one more offset to be used as boundary for fill
{
my @fill_boundaries = map $_->offset_ex(-$distance), @last_offsets;
$_->simplify(scale &Slic3r::RESOLUTION) for @fill_boundaries;
push @{ $self->surfaces }, @fill_boundaries;
# detect the small gaps that we need to treat like thin polygons,
# thus generating the skeleton and using it to fill them
push @{ $self->thin_fills },
map $_->medial_axis(scale $self->perimeter_flow->width),
@gaps;
Slic3r::debugf " %d gaps filled\n", scalar @{ $self->thin_fills }
if @{ $self->thin_fills };
}
}
# process one island (original surface) at time
foreach my $island (@perimeters) {
# do holes starting from innermost one
my @holes = ();
my %is_external = ();
my @hole_depths = map [ map $_->holes, @$_ ], @$island;
# organize the outermost hole loops using a shortest path search
@{$hole_depths[0]} = @{shortest_path([
map [ $_->[0], $_ ], @{$hole_depths[0]},
])};
CYCLE: while (map @$_, @hole_depths) {
shift @hole_depths while !@{$hole_depths[0]};
# take first available hole
push @holes, shift @{$hole_depths[0]};
$is_external{$#holes} = 1;
my $current_depth = 0;
while (1) {
$current_depth++;
# look for the hole containing this one if any
next CYCLE if !$hole_depths[$current_depth];
my $parent_hole;
for (@{$hole_depths[$current_depth]}) {
if ($_->encloses_point($holes[-1]->[0])) {
$parent_hole = $_;
last;
}
}
next CYCLE if !$parent_hole;
# look for other holes contained in such parent
for (@{$hole_depths[$current_depth-1]}) {
if ($parent_hole->encloses_point($_->[0])) {
# we have a sibling, so let's move onto next iteration
next CYCLE;
}
}
push @holes, $parent_hole;
@{$hole_depths[$current_depth]} = grep $_ ne $parent_hole, @{$hole_depths[$current_depth]};
}
}
# do holes, then contours starting from innermost one
$self->_add_perimeter($holes[$_], $is_external{$_} ? EXTR_ROLE_EXTERNAL_PERIMETER : undef)
for reverse 0 .. $#holes;
for my $depth (reverse 0 .. $#$island) {
my $role = $depth == $#$island ? EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER
: $depth == 0 ? EXTR_ROLE_EXTERNAL_PERIMETER
: EXTR_ROLE_PERIMETER;
$self->_add_perimeter($_, $role) for map $_->contour, @{$island->[$depth]};
}
}
# add thin walls as perimeters
{
my @thin_paths = ();
my %properties = (
role => EXTR_ROLE_EXTERNAL_PERIMETER,
flow_spacing => $self->perimeter_flow->spacing,
);
for (@{ $self->thin_walls }) {
push @thin_paths, $_->isa('Slic3r::Polygon')
? Slic3r::ExtrusionLoop->pack(polygon => $_, %properties)
: Slic3r::ExtrusionPath->pack(polyline => $_, %properties);
}
my $collection = Slic3r::ExtrusionPath::Collection->new(paths => \@thin_paths);
push @{ $self->perimeters }, $collection->shortest_path;
}
}
sub _add_perimeter {
my $self = shift;
my ($polygon, $role) = @_;
return unless $polygon->is_printable($self->perimeter_flow->width);
push @{ $self->perimeters }, Slic3r::ExtrusionLoop->pack(
polygon => $polygon,
role => (abs($polygon->length) <= &Slic3r::SMALL_PERIMETER_LENGTH) ? EXTR_ROLE_SMALLPERIMETER : ($role // EXTR_ROLE_PERIMETER), #/
flow_spacing => $self->perimeter_flow->spacing,
);
}
sub prepare_fill_surfaces {
my $self = shift;
my @surfaces = @{$self->surfaces};
# if no solid layers are requested, turn top/bottom surfaces to internal
# note that this modifies $self->surfaces in place
if ($Slic3r::Config->solid_layers == 0) {
$_->surface_type(S_TYPE_INTERNAL) for grep $_->surface_type != S_TYPE_INTERNAL, @surfaces;
}
# if hollow object is requested, remove internal surfaces
if ($Slic3r::Config->fill_density == 0) {
@surfaces = grep $_->surface_type != S_TYPE_INTERNAL, @surfaces;
}
# remove unprintable regions (they would slow down the infill process and also cause
# some weird failures during bridge neighbor detection)
{
my $distance = scale $self->infill_flow->spacing / 2;
@surfaces = map {
my $surface = $_;
# offset inwards
my @offsets = $surface->expolygon->offset_ex(-$distance);
@offsets = @{union_ex(Math::Clipper::offset([ map @$_, @offsets ], $distance, 100, JT_MITER))};
map Slic3r::Surface->new(
expolygon => $_,
surface_type => $surface->surface_type,
), @offsets;
} @surfaces;
}
# turn too small internal regions into solid regions
{
my $min_area = scale scale $Slic3r::Config->solid_infill_below_area; # scaling an area requires two calls!
my @small = grep $_->surface_type == S_TYPE_INTERNAL && $_->expolygon->contour->area <= $min_area, @surfaces;
$_->surface_type(S_TYPE_INTERNALSOLID) for @small;
Slic3r::debugf "identified %d small solid surfaces at layer %d\n", scalar(@small), $self->id if @small > 0;
}
$self->fill_surfaces([@surfaces]);
}
# make bridges printable
sub process_bridges {
my $self = shift;
# no bridges are possible if we have no internal surfaces
return if $Slic3r::Config->fill_density == 0;
my @bridges = ();
# a bottom surface on a layer > 0 is either a bridge or a overhang
# or a combination of both; any top surface is a candidate for
# reverse bridge processing
my @solid_surfaces = grep {
($_->surface_type == S_TYPE_BOTTOM && $self->id > 0) || $_->surface_type == S_TYPE_TOP
} @{$self->fill_surfaces} or return;
my @internal_surfaces = grep { $_->surface_type == S_TYPE_INTERNAL || $_->surface_type == S_TYPE_INTERNALSOLID } @{$self->slices};
SURFACE: foreach my $surface (@solid_surfaces) {
my $expolygon = $surface->expolygon->safety_offset;
my $description = $surface->surface_type == S_TYPE_BOTTOM ? 'bridge/overhang' : 'reverse bridge';
# offset the contour and intersect it with the internal surfaces to discover
# which of them has contact with our bridge
my @supporting_surfaces = ();
my ($contour_offset) = $expolygon->contour->offset(scale $self->flow->spacing * sqrt(2));
foreach my $internal_surface (@internal_surfaces) {
my $intersection = intersection_ex([$contour_offset], [$internal_surface->p]);
if (@$intersection) {
push @supporting_surfaces, $internal_surface;
}
}
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(undef, "bridge_surfaces.svg",
green_polygons => [ map $_->p, @supporting_surfaces ],
red_polygons => [ @$expolygon ],
);
}
Slic3r::debugf "Found $description on layer %d with %d support(s)\n",
$self->id, scalar(@supporting_surfaces);
next SURFACE unless @supporting_surfaces;
my $bridge_angle = undef;
if ($surface->surface_type == S_TYPE_BOTTOM) {
# detect optimal bridge angle
my $bridge_over_hole = 0;
my @edges = (); # edges are POLYLINES
foreach my $supporting_surface (@supporting_surfaces) {
my @surface_edges = map $_->clip_with_polygon($contour_offset),
($supporting_surface->contour, $supporting_surface->holes);
if (@supporting_surfaces == 1 && @surface_edges == 1
&& @{$supporting_surface->contour} == @{$surface_edges[0]}) {
$bridge_over_hole = 1;
}
push @edges, grep { @$_ } @surface_edges;
}
Slic3r::debugf " Bridge is supported on %d edge(s)\n", scalar(@edges);
Slic3r::debugf " and covers a hole\n" if $bridge_over_hole;
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(undef, "bridge_edges.svg",
polylines => [ map $_->p, @edges ],
);
}
if (@edges == 2) {
my @chords = map Slic3r::Line->new($_->[0], $_->[-1]), @edges;
my @midpoints = map $_->midpoint, @chords;
my $line_between_midpoints = Slic3r::Line->new(@midpoints);
$bridge_angle = rad2deg_dir($line_between_midpoints->direction);
} elsif (@edges == 1) {
# TODO: this case includes both U-shaped bridges and plain overhangs;
# we need a trapezoidation algorithm to detect the actual bridged area
# and separate it from the overhang area.
# in the mean time, we're treating as overhangs all cases where
# our supporting edge is a straight line
if (@{$edges[0]} > 2) {
my $line = Slic3r::Line->new($edges[0]->[0], $edges[0]->[-1]);
$bridge_angle = rad2deg_dir($line->direction);
}
} elsif (@edges) {
my $center = bounding_box_center([ map @$_, @edges ]);
my $x = my $y = 0;
foreach my $point (map @$, @edges) {
my $line = Slic3r::Line->new($center, $point);
my $dir = $line->direction;
my $len = $line->length;
$x += cos($dir) * $len;
$y += sin($dir) * $len;
}
$bridge_angle = rad2deg_dir(atan2($y, $x));
}
Slic3r::debugf " Optimal infill angle of bridge on layer %d is %d degrees\n",
$self->id, $bridge_angle if defined $bridge_angle;
}
# now, extend our bridge by taking a portion of supporting surfaces
{
# offset the bridge by the specified amount of mm (minimum 3)
my $bridge_overlap = scale 3;
my ($bridge_offset) = $expolygon->contour->offset($bridge_overlap);
# calculate the new bridge
my $intersection = intersection_ex(
[ @$expolygon, map $_->p, @supporting_surfaces ],
[ $bridge_offset ],
);
push @bridges, map Slic3r::Surface->new(
expolygon => $_,
surface_type => $surface->surface_type,
bridge_angle => $bridge_angle,
), @$intersection;
}
}
# now we need to merge bridges to avoid overlapping
{
# build a list of unique bridge types
my @surface_groups = Slic3r::Surface->group(@bridges);
# merge bridges of the same type, removing any of the bridges already merged;
# the order of @surface_groups determines the priority between bridges having
# different surface_type or bridge_angle
@bridges = ();
foreach my $surfaces (@surface_groups) {
my $union = union_ex([ map $_->p, @$surfaces ]);
my $diff = diff_ex(
[ map @$_, @$union ],
[ map $_->p, @bridges ],
);
push @bridges, map Slic3r::Surface->new(
expolygon => $_,
surface_type => $surfaces->[0]->surface_type,
bridge_angle => $surfaces->[0]->bridge_angle,
), @$union;
}
}
# apply bridges to layer
{
my @surfaces = @{$self->fill_surfaces};
@{$self->fill_surfaces} = ();
# intersect layer surfaces with bridges to get actual bridges
foreach my $bridge (@bridges) {
my $actual_bridge = intersection_ex(
[ map $_->p, @surfaces ],
[ $bridge->p ],
);
push @{$self->fill_surfaces}, map Slic3r::Surface->new(
expolygon => $_,
surface_type => $bridge->surface_type,
bridge_angle => $bridge->bridge_angle,
), @$actual_bridge;
}
# difference between layer surfaces and bridges are the other surfaces
foreach my $group (Slic3r::Surface->group(@surfaces)) {
my $difference = diff_ex(
[ map $_->p, @$group ],
[ map $_->p, @bridges ],
);
push @{$self->fill_surfaces}, map Slic3r::Surface->new(
expolygon => $_,
surface_type => $group->[0]->surface_type), @$difference;
}
}
} }
1; 1;

View File

@ -0,0 +1,515 @@
package Slic3r::Layer::Material;
use Moo;
use Math::Clipper ':all';
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Geometry qw(scale shortest_path);
use Slic3r::Geometry::Clipper qw(safety_offset union_ex diff_ex intersection_ex);
use Slic3r::Surface ':types';
has 'layer' => (
is => 'ro',
weak_ref => 1,
required => 1,
handles => [qw(id slice_z print_z height flow perimeter_flow infill_flow)],
);
# collection of spare segments generated by slicing the original geometry;
# these need to be merged in continuos (closed) polylines
has 'lines' => (is => 'rw', default => sub { [] });
# collection of surfaces generated by slicing the original geometry
has 'slices' => (is => 'rw');
# collection of polygons or polylines representing thin walls contained
# in the original geometry
has 'thin_walls' => (is => 'rw');
# collection of polygons or polylines representing thin infill regions that
# need to be filled with a medial axis
has 'thin_fills' => (is => 'rw');
# collection of expolygons generated by offsetting the innermost perimeter(s)
# they represent boundaries of areas to fill, typed (top/bottom/internal)
has 'surfaces' => (is => 'rw');
# collection of surfaces for infill generation. the difference between surfaces
# fill_surfaces is that this one honors fill_density == 0 and turns small internal
# surfaces into solid ones
has 'fill_surfaces' => (is => 'rw');
# ordered collection of extrusion paths/loops to build all perimeters
has 'perimeters' => (is => 'rw');
# ordered collection of extrusion paths to fill surfaces
has 'fills' => (is => 'rw');
# build polylines from lines
sub make_surfaces {
my $self = shift;
my ($loops) = @_;
{
my $safety_offset = scale 0.1;
# merge everything
my $expolygons = [ map $_->offset_ex(-$safety_offset), @{union_ex(safety_offset($loops, $safety_offset))} ];
Slic3r::debugf " %d surface(s) having %d holes detected from %d polylines\n",
scalar(@$expolygons), scalar(map $_->holes, @$expolygons), scalar(@$loops);
$self->slices([
map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL),
@$expolygons
]);
}
# the contours must be offsetted by half extrusion width inwards
{
my $distance = scale $self->perimeter_flow->width / 2;
my @surfaces = @{$self->slices};
@{$self->slices} = ();
foreach my $surface (@surfaces) {
push @{$self->slices}, map Slic3r::Surface->new
(expolygon => $_, surface_type => S_TYPE_INTERNAL),
map $_->offset_ex(+$distance),
$surface->expolygon->offset_ex(-2*$distance);
}
# now detect thin walls by re-outgrowing offsetted surfaces and subtracting
# them from the original slices
my $outgrown = Math::Clipper::offset([ map $_->p, @{$self->slices} ], $distance);
my $diff = diff_ex(
[ map $_->p, @surfaces ],
$outgrown,
1,
);
$self->thin_walls([]);
if (@$diff) {
my $area_threshold = scale($self->perimeter_flow->spacing) ** 2;
@$diff = grep $_->area > ($area_threshold), @$diff;
@{$self->thin_walls} = map $_->medial_axis(scale $self->perimeter_flow->width), @$diff;
Slic3r::debugf " %d thin walls detected\n", scalar(@{$self->thin_walls}) if @{$self->thin_walls};
}
}
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(undef, "surfaces.svg",
polygons => [ map $_->contour, @{$self->slices} ],
red_polygons => [ map $_->p, map @{$_->holes}, @{$self->slices} ],
);
}
}
sub make_perimeters {
my $self = shift;
my $gap_area_threshold = scale($self->perimeter_flow->width)** 2;
# this array will hold one arrayref per original surface (island);
# each item of this arrayref is an arrayref representing a depth (from outer
# perimeters to inner); each item of this arrayref is an ExPolygon:
# @perimeters = (
# [ # first island
# [ Slic3r::ExPolygon, Slic3r::ExPolygon... ], #depth 0: outer loop
# [ Slic3r::ExPolygon, Slic3r::ExPolygon... ], #depth 1: inner loop
# ],
# [ # second island
# ...
# ]
# )
my @perimeters = (); # one item per depth; each item
# organize islands using a shortest path search
my @surfaces = @{shortest_path([
map [ $_->contour->[0], $_ ], @{$self->slices},
])};
$self->perimeters([]);
$self->surfaces([]);
$self->thin_fills([]);
# for each island:
foreach my $surface (@surfaces) {
my @last_offsets = ($surface->expolygon);
# experimental hole compensation (see ArcCompensation in the RepRap wiki)
if (0) {
foreach my $hole ($last_offsets[0]->holes) {
my $circumference = abs($hole->length);
next unless $circumference <= &Slic3r::SMALL_PERIMETER_LENGTH;
# this compensation only works for circular holes, while it would
# overcompensate for hexagons and other shapes having straight edges.
# so we require a minimum number of vertices.
next unless $circumference / @$hole >= scale 3 * $Slic3r::flow->width;
# revert the compensation done in make_surfaces() and get the actual radius
# of the hole
my $radius = ($circumference / PI / 2) - scale $self->perimeter_flow->spacing/2;
my $new_radius = (scale($self->perimeter_flow->width) + sqrt((scale($self->perimeter_flow->width)**2) + (4*($radius**2)))) / 2;
# holes are always turned to contours, so reverse point order before and after
$hole->reverse;
my @offsetted = $hole->offset(+ ($new_radius - $radius));
# skip arc compensation when hole is not round (thus leads to multiple offsets)
@$hole = map Slic3r::Point->new($_), @{ $offsetted[0] } if @offsetted == 1;
$hole->reverse;
}
}
my $distance = scale $self->perimeter_flow->spacing;
my @gaps = ();
# generate perimeters inwards (loop 0 is the external one)
my $loop_number = $Slic3r::Config->perimeters + ($surface->additional_inner_perimeters || 0);
push @perimeters, [[@last_offsets]];
for (my $loop = 1; $loop < $loop_number; $loop++) {
# offsetting a polygon can result in one or many offset polygons
my @new_offsets = ();
foreach my $expolygon (@last_offsets) {
my @offsets = map $_->offset_ex(+0.5*$distance), $expolygon->offset_ex(-1.5*$distance);
push @new_offsets, @offsets;
my $diff = diff_ex(
[ map @$_, $expolygon->offset_ex(-$distance) ],
[ map @$_, @offsets ],
);
push @gaps, grep $_->area >= $gap_area_threshold, @$diff;
}
@last_offsets = @new_offsets;
last if !@last_offsets;
push @{ $perimeters[-1] }, [@last_offsets];
}
# create one more offset to be used as boundary for fill
{
my @fill_boundaries = map $_->offset_ex(-$distance), @last_offsets;
$_->simplify(scale &Slic3r::RESOLUTION) for @fill_boundaries;
push @{ $self->surfaces }, @fill_boundaries;
# detect the small gaps that we need to treat like thin polygons,
# thus generating the skeleton and using it to fill them
push @{ $self->thin_fills },
map $_->medial_axis(scale $self->perimeter_flow->width),
@gaps;
Slic3r::debugf " %d gaps filled\n", scalar @{ $self->thin_fills }
if @{ $self->thin_fills };
}
}
# process one island (original surface) at time
foreach my $island (@perimeters) {
# do holes starting from innermost one
my @holes = ();
my %is_external = ();
my @hole_depths = map [ map $_->holes, @$_ ], @$island;
# organize the outermost hole loops using a shortest path search
@{$hole_depths[0]} = @{shortest_path([
map [ $_->[0], $_ ], @{$hole_depths[0]},
])};
CYCLE: while (map @$_, @hole_depths) {
shift @hole_depths while !@{$hole_depths[0]};
# take first available hole
push @holes, shift @{$hole_depths[0]};
$is_external{$#holes} = 1;
my $current_depth = 0;
while (1) {
$current_depth++;
# look for the hole containing this one if any
next CYCLE if !$hole_depths[$current_depth];
my $parent_hole;
for (@{$hole_depths[$current_depth]}) {
if ($_->encloses_point($holes[-1]->[0])) {
$parent_hole = $_;
last;
}
}
next CYCLE if !$parent_hole;
# look for other holes contained in such parent
for (@{$hole_depths[$current_depth-1]}) {
if ($parent_hole->encloses_point($_->[0])) {
# we have a sibling, so let's move onto next iteration
next CYCLE;
}
}
push @holes, $parent_hole;
@{$hole_depths[$current_depth]} = grep $_ ne $parent_hole, @{$hole_depths[$current_depth]};
}
}
# do holes, then contours starting from innermost one
$self->_add_perimeter($holes[$_], $is_external{$_} ? EXTR_ROLE_EXTERNAL_PERIMETER : undef)
for reverse 0 .. $#holes;
for my $depth (reverse 0 .. $#$island) {
my $role = $depth == $#$island ? EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER
: $depth == 0 ? EXTR_ROLE_EXTERNAL_PERIMETER
: EXTR_ROLE_PERIMETER;
$self->_add_perimeter($_, $role) for map $_->contour, @{$island->[$depth]};
}
}
# add thin walls as perimeters
{
my @thin_paths = ();
my %properties = (
role => EXTR_ROLE_EXTERNAL_PERIMETER,
flow_spacing => $self->perimeter_flow->spacing,
);
for (@{ $self->thin_walls }) {
push @thin_paths, $_->isa('Slic3r::Polygon')
? Slic3r::ExtrusionLoop->pack(polygon => $_, %properties)
: Slic3r::ExtrusionPath->pack(polyline => $_, %properties);
}
my $collection = Slic3r::ExtrusionPath::Collection->new(paths => \@thin_paths);
push @{ $self->perimeters }, $collection->shortest_path;
}
}
sub _add_perimeter {
my $self = shift;
my ($polygon, $role) = @_;
return unless $polygon->is_printable($self->perimeter_flow->width);
push @{ $self->perimeters }, Slic3r::ExtrusionLoop->pack(
polygon => $polygon,
role => (abs($polygon->length) <= &Slic3r::SMALL_PERIMETER_LENGTH) ? EXTR_ROLE_SMALLPERIMETER : ($role // EXTR_ROLE_PERIMETER), #/
flow_spacing => $self->perimeter_flow->spacing,
);
}
sub prepare_fill_surfaces {
my $self = shift;
my @surfaces = @{$self->surfaces};
# if no solid layers are requested, turn top/bottom surfaces to internal
# note that this modifies $self->surfaces in place
if ($Slic3r::Config->solid_layers == 0) {
$_->surface_type(S_TYPE_INTERNAL) for grep $_->surface_type != S_TYPE_INTERNAL, @surfaces;
}
# if hollow object is requested, remove internal surfaces
if ($Slic3r::Config->fill_density == 0) {
@surfaces = grep $_->surface_type != S_TYPE_INTERNAL, @surfaces;
}
# remove unprintable regions (they would slow down the infill process and also cause
# some weird failures during bridge neighbor detection)
{
my $distance = scale $self->infill_flow->spacing / 2;
@surfaces = map {
my $surface = $_;
# offset inwards
my @offsets = $surface->expolygon->offset_ex(-$distance);
@offsets = @{union_ex(Math::Clipper::offset([ map @$_, @offsets ], $distance, 100, JT_MITER))};
map Slic3r::Surface->new(
expolygon => $_,
surface_type => $surface->surface_type,
), @offsets;
} @surfaces;
}
# turn too small internal regions into solid regions
{
my $min_area = scale scale $Slic3r::Config->solid_infill_below_area; # scaling an area requires two calls!
my @small = grep $_->surface_type == S_TYPE_INTERNAL && $_->expolygon->contour->area <= $min_area, @surfaces;
$_->surface_type(S_TYPE_INTERNALSOLID) for @small;
Slic3r::debugf "identified %d small solid surfaces at layer %d\n", scalar(@small), $self->id if @small > 0;
}
$self->fill_surfaces([@surfaces]);
}
# make bridges printable
sub process_bridges {
my $self = shift;
# no bridges are possible if we have no internal surfaces
return if $Slic3r::Config->fill_density == 0;
my @bridges = ();
# a bottom surface on a layer > 0 is either a bridge or a overhang
# or a combination of both; any top surface is a candidate for
# reverse bridge processing
my @solid_surfaces = grep {
($_->surface_type == S_TYPE_BOTTOM && $self->id > 0) || $_->surface_type == S_TYPE_TOP
} @{$self->fill_surfaces} or return;
my @internal_surfaces = grep { $_->surface_type == S_TYPE_INTERNAL || $_->surface_type == S_TYPE_INTERNALSOLID } @{$self->slices};
SURFACE: foreach my $surface (@solid_surfaces) {
my $expolygon = $surface->expolygon->safety_offset;
my $description = $surface->surface_type == S_TYPE_BOTTOM ? 'bridge/overhang' : 'reverse bridge';
# offset the contour and intersect it with the internal surfaces to discover
# which of them has contact with our bridge
my @supporting_surfaces = ();
my ($contour_offset) = $expolygon->contour->offset(scale $self->flow->spacing * sqrt(2));
foreach my $internal_surface (@internal_surfaces) {
my $intersection = intersection_ex([$contour_offset], [$internal_surface->p]);
if (@$intersection) {
push @supporting_surfaces, $internal_surface;
}
}
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(undef, "bridge_surfaces.svg",
green_polygons => [ map $_->p, @supporting_surfaces ],
red_polygons => [ @$expolygon ],
);
}
Slic3r::debugf "Found $description on layer %d with %d support(s)\n",
$self->id, scalar(@supporting_surfaces);
next SURFACE unless @supporting_surfaces;
my $bridge_angle = undef;
if ($surface->surface_type == S_TYPE_BOTTOM) {
# detect optimal bridge angle
my $bridge_over_hole = 0;
my @edges = (); # edges are POLYLINES
foreach my $supporting_surface (@supporting_surfaces) {
my @surface_edges = map $_->clip_with_polygon($contour_offset),
($supporting_surface->contour, $supporting_surface->holes);
if (@supporting_surfaces == 1 && @surface_edges == 1
&& @{$supporting_surface->contour} == @{$surface_edges[0]}) {
$bridge_over_hole = 1;
}
push @edges, grep { @$_ } @surface_edges;
}
Slic3r::debugf " Bridge is supported on %d edge(s)\n", scalar(@edges);
Slic3r::debugf " and covers a hole\n" if $bridge_over_hole;
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(undef, "bridge_edges.svg",
polylines => [ map $_->p, @edges ],
);
}
if (@edges == 2) {
my @chords = map Slic3r::Line->new($_->[0], $_->[-1]), @edges;
my @midpoints = map $_->midpoint, @chords;
my $line_between_midpoints = Slic3r::Line->new(@midpoints);
$bridge_angle = rad2deg_dir($line_between_midpoints->direction);
} elsif (@edges == 1) {
# TODO: this case includes both U-shaped bridges and plain overhangs;
# we need a trapezoidation algorithm to detect the actual bridged area
# and separate it from the overhang area.
# in the mean time, we're treating as overhangs all cases where
# our supporting edge is a straight line
if (@{$edges[0]} > 2) {
my $line = Slic3r::Line->new($edges[0]->[0], $edges[0]->[-1]);
$bridge_angle = rad2deg_dir($line->direction);
}
} elsif (@edges) {
my $center = bounding_box_center([ map @$_, @edges ]);
my $x = my $y = 0;
foreach my $point (map @$, @edges) {
my $line = Slic3r::Line->new($center, $point);
my $dir = $line->direction;
my $len = $line->length;
$x += cos($dir) * $len;
$y += sin($dir) * $len;
}
$bridge_angle = rad2deg_dir(atan2($y, $x));
}
Slic3r::debugf " Optimal infill angle of bridge on layer %d is %d degrees\n",
$self->id, $bridge_angle if defined $bridge_angle;
}
# now, extend our bridge by taking a portion of supporting surfaces
{
# offset the bridge by the specified amount of mm (minimum 3)
my $bridge_overlap = scale 3;
my ($bridge_offset) = $expolygon->contour->offset($bridge_overlap);
# calculate the new bridge
my $intersection = intersection_ex(
[ @$expolygon, map $_->p, @supporting_surfaces ],
[ $bridge_offset ],
);
push @bridges, map Slic3r::Surface->new(
expolygon => $_,
surface_type => $surface->surface_type,
bridge_angle => $bridge_angle,
), @$intersection;
}
}
# now we need to merge bridges to avoid overlapping
{
# build a list of unique bridge types
my @surface_groups = Slic3r::Surface->group(@bridges);
# merge bridges of the same type, removing any of the bridges already merged;
# the order of @surface_groups determines the priority between bridges having
# different surface_type or bridge_angle
@bridges = ();
foreach my $surfaces (@surface_groups) {
my $union = union_ex([ map $_->p, @$surfaces ]);
my $diff = diff_ex(
[ map @$_, @$union ],
[ map $_->p, @bridges ],
);
push @bridges, map Slic3r::Surface->new(
expolygon => $_,
surface_type => $surfaces->[0]->surface_type,
bridge_angle => $surfaces->[0]->bridge_angle,
), @$union;
}
}
# apply bridges to layer
{
my @surfaces = @{$self->fill_surfaces};
@{$self->fill_surfaces} = ();
# intersect layer surfaces with bridges to get actual bridges
foreach my $bridge (@bridges) {
my $actual_bridge = intersection_ex(
[ map $_->p, @surfaces ],
[ $bridge->p ],
);
push @{$self->fill_surfaces}, map Slic3r::Surface->new(
expolygon => $_,
surface_type => $bridge->surface_type,
bridge_angle => $bridge->bridge_angle,
), @$actual_bridge;
}
# difference between layer surfaces and bridges are the other surfaces
foreach my $group (Slic3r::Surface->group(@surfaces)) {
my $difference = diff_ex(
[ map $_->p, @$group ],
[ map $_->p, @bridges ],
);
push @{$self->fill_surfaces}, map Slic3r::Surface->new(
expolygon => $_,
surface_type => $group->[0]->surface_type), @$difference;
}
}
}
1;

View File

@ -15,6 +15,7 @@ sub read_from_file {
: $input_file =~ /\.amf(\.xml)?$/i ? Slic3r::Format::AMF->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"; : die "Input file must have .stl, .obj or .amf(.xml) extension\n";
$_->input_file($input_file) for @{$model->objects};
return $model; return $model;
} }
@ -26,6 +27,16 @@ sub add_object {
return $object; return $object;
} }
sub set_material {
my $self = shift;
my ($material_id, $attributes) = @_;
return $self->materials->{$material_id} = Slic3r::Model::Material->new(
model => $self,
attributes => $attributes || {},
);
}
# flattens everything to a single mesh # flattens everything to a single mesh
sub mesh { sub mesh {
my $self = shift; my $self = shift;

View File

@ -16,6 +16,8 @@ has 'objects' => (is => 'rw', default => sub {[]});
has 'total_extrusion_length' => (is => 'rw'); has 'total_extrusion_length' => (is => 'rw');
has 'processing_time' => (is => 'rw', required => 0); has 'processing_time' => (is => 'rw', required => 0);
has 'materials_count' => (is => 'rw', default => sub {1});
# ordered collection of extrusion paths to build skirt loops # ordered collection of extrusion paths to build skirt loops
has 'skirt' => ( has 'skirt' => (
is => 'rw', is => 'rw',
@ -82,59 +84,52 @@ sub _trigger_config {
$self->config->set('extrusion_axis', '') if $self->config->gcode_flavor eq 'no-extrusion'; $self->config->set('extrusion_axis', '') if $self->config->gcode_flavor eq 'no-extrusion';
} }
sub add_objects_from_file {
my $self = shift;
my ($input_file) = @_;
my $model = Slic3r::Model->read_from_file($input_file);
my @print_objects = $self->add_model($model);
$_->input_file($input_file) for @print_objects;
}
sub add_model { sub add_model {
my $self = shift; my $self = shift;
my ($model) = @_; my ($model) = @_;
my @print_objects = (); # update materials count
$self->materials_count(max($self->materials_count, scalar keys %{$model->materials}));
foreach my $object (@{ $model->objects }) { foreach my $object (@{ $model->objects }) {
my $mesh = $object->volumes->[0]->mesh; my @meshes = (); # by material_id
$mesh->check_manifoldness;
if ($object->instances) { foreach my $volume (@{$object->volumes}) {
# we ignore the per-instance rotation currently and only # should the object contain multiple volumes of the same material, merge them
# consider the first one my $material_id = $volume->material_id // 0; #/
$mesh->rotate($object->instances->[0]->rotation); $meshes[$material_id] = $meshes[$material_id]
? Slic3r::TriangleMesh->merge($meshes[$material_id], $volume->mesh)
: $volume->mesh;
} }
push @print_objects, $self->add_object_from_mesh($mesh, input_file => $object->input_file); foreach my $mesh (@meshes) {
next unless $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);
}
$mesh->rotate($Slic3r::Config->rotate);
$mesh->scale($Slic3r::Config->scale / &Slic3r::SCALING_FACTOR);
$mesh->align_to_origin;
}
# initialize print object
push @{$self->objects}, Slic3r::Print::Object->new(
print => $self,
meshes => [ @meshes ],
size => [ $object->mesh->size ],
input_file => $object->input_file
);
if ($object->instances) { if ($object->instances) {
# replace the default [0,0] instance with the custom ones # replace the default [0,0] instance with the custom ones
@{$self->objects->[-1]->copies} = map [ scale $_->offset->[X], scale $_->offset->[Y] ], @{$object->instances}; @{$self->objects->[-1]->copies} = map [ scale $_->offset->[X], scale $_->offset->[Y] ], @{$object->instances};
} }
} }
return @print_objects;
}
sub add_object_from_mesh {
my $self = shift;
my ($mesh, %attributes) = @_;
$mesh->rotate($Slic3r::Config->rotate);
$mesh->scale($Slic3r::Config->scale / &Slic3r::SCALING_FACTOR);
$mesh->align_to_origin;
# initialize print object
my $object = Slic3r::Print::Object->new(
mesh => $mesh,
size => [ $mesh->size ],
%attributes,
);
push @{$self->objects}, $object;
return $object;
} }
sub validate { sub validate {
@ -147,7 +142,7 @@ sub validate {
for my $obj_idx (0 .. $#{$self->objects}) { for my $obj_idx (0 .. $#{$self->objects}) {
my $clearance; my $clearance;
{ {
my @points = map [ @$_[X,Y] ], @{$self->objects->[$obj_idx]->mesh->vertices}; my @points = map [ @$_[X,Y] ], map @{$_->vertices}, @{$self->objects->[$obj_idx]->meshes};
my $convex_hull = Slic3r::Polygon->new(convex_hull(\@points)); my $convex_hull = Slic3r::Polygon->new(convex_hull(\@points));
$clearance = +($convex_hull->offset(scale $Slic3r::Config->extruder_clearance_radius / 2, 1, JT_ROUND))[0]; $clearance = +($convex_hull->offset(scale $Slic3r::Config->extruder_clearance_radius / 2, 1, JT_ROUND))[0];
} }
@ -166,7 +161,8 @@ sub validate {
{ {
my @obj_copies = $self->object_copies; my @obj_copies = $self->object_copies;
pop @obj_copies; # ignore the last copy: its height doesn't matter pop @obj_copies; # ignore the last copy: its height doesn't matter
if (grep { +($self->objects->[$_->[0]]->mesh->size)[Z] > scale $Slic3r::Config->extruder_clearance_height } @obj_copies) { my $scaled_clearance = scale $Slic3r::Config->extruder_clearance_height;
if (grep { +($_->size)[Z] > $scaled_clearance } map @{$self->objects->[$_->[0]]->meshes}, @obj_copies) {
die "Some objects are too tall and cannot be printed without extruder collisions.\n"; die "Some objects are too tall and cannot be printed without extruder collisions.\n";
} }
} }
@ -283,9 +279,12 @@ sub export_gcode {
$status_cb->(20, "Generating perimeters"); $status_cb->(20, "Generating perimeters");
$_->make_perimeters for @{$self->objects}; $_->make_perimeters for @{$self->objects};
# simplify slices, we only need the max resolution for perimeters # simplify slices (both layer and material slices),
# we only need the max resolution for perimeters
$_->simplify(scale &Slic3r::RESOLUTION) $_->simplify(scale &Slic3r::RESOLUTION)
for map @{$_->expolygon}, map @{$_->slices}, map @{$_->layers}, @{$self->objects}; for map @{$_->expolygon},
map { my $layer = $_; ((map @{$_->slices}, @{$layer->materials}), @{$layer->slices}) }
map @{$_->layers}, @{$self->objects};
# this will clip $layer->surfaces to the infill boundaries # this will clip $layer->surfaces to the infill boundaries
# and split them in top/bottom/internal surfaces; # and split them in top/bottom/internal surfaces;
@ -294,12 +293,12 @@ sub export_gcode {
# decide what surfaces are to be filled # decide what surfaces are to be filled
$status_cb->(35, "Preparing infill surfaces"); $status_cb->(35, "Preparing infill surfaces");
$_->prepare_fill_surfaces for map @{$_->layers}, @{$self->objects}; $_->prepare_fill_surfaces for map @{$_->materials}, map @{$_->layers}, @{$self->objects};
# this will detect bridges and reverse bridges # this will detect bridges and reverse bridges
# and rearrange top/bottom/internal surfaces # and rearrange top/bottom/internal surfaces
$status_cb->(45, "Detect bridges"); $status_cb->(45, "Detect bridges");
$_->process_bridges for map @{$_->layers}, @{$self->objects}; $_->process_bridges for map @{$_->materials}, map @{$_->layers}, @{$self->objects};
# detect which fill surfaces are near external layers # detect which fill surfaces are near external layers
# they will be split in internal and internal-solid surfaces # they will be split in internal and internal-solid surfaces
@ -307,7 +306,7 @@ sub export_gcode {
$_->discover_horizontal_shells for @{$self->objects}; $_->discover_horizontal_shells for @{$self->objects};
# free memory # free memory
$_->surfaces(undef) for map @{$_->layers}, @{$self->objects}; $_->surfaces(undef) for map @{$_->materials}, map @{$_->layers}, @{$self->objects};
# combine fill surfaces to honor the "infill every N layers" option # combine fill surfaces to honor the "infill every N layers" option
$status_cb->(70, "Combining infill"); $status_cb->(70, "Combining infill");
@ -317,35 +316,45 @@ sub export_gcode {
$status_cb->(80, "Infilling layers"); $status_cb->(80, "Infilling layers");
{ {
my $fill_maker = Slic3r::Fill->new('print' => $self); my $fill_maker = Slic3r::Fill->new('print' => $self);
my @items = (); # [obj_idx, layer_id]
foreach my $obj_idx (0 .. $#{$self->objects}) {
push @items, map [$obj_idx, $_], 0..$#{$self->objects->[$obj_idx]->layers};
}
Slic3r::parallelize( Slic3r::parallelize(
items => [@items], items => sub {
my @items = (); # [obj_idx, layer_id]
for my $obj_idx (0 .. $#{$self->objects}) {
for my $material_id (0 .. ($self->materials_count-1)) {
push @items, map [$obj_idx, $_, $material_id], 0..($self->objects->[$obj_idx]->layer_count-1);
}
}
@items;
},
thread_cb => sub { thread_cb => sub {
my $q = shift; my $q = shift;
$Slic3r::Geometry::Clipper::clipper = Math::Clipper->new; $Slic3r::Geometry::Clipper::clipper = Math::Clipper->new;
my $fills = {}; my $fills = {};
while (defined (my $obj_layer = $q->dequeue)) { while (defined (my $obj_layer = $q->dequeue)) {
my ($obj_idx, $layer_id) = @$obj_layer; my ($obj_idx, $layer_id, $material_id) = @$obj_layer;
$fills->{$obj_idx} ||= {}; $fills->{$obj_idx} ||= {};
$fills->{$obj_idx}{$layer_id} = [ $fill_maker->make_fill($self->objects->[$obj_idx]->layers->[$layer_id]) ]; $fills->{$obj_idx}{$layer_id} ||= {};
$fills->{$obj_idx}{$layer_id}{$material_id} = [
$fill_maker->make_fill($self->objects->[$obj_idx]->layers->[$layer_id]->materials->[$material_id]),
];
} }
return $fills; return $fills;
}, },
collect_cb => sub { collect_cb => sub {
my $fills = shift; my $fills = shift;
foreach my $obj_idx (keys %$fills) { foreach my $obj_idx (keys %$fills) {
my $object = $self->objects->[$obj_idx];
foreach my $layer_id (keys %{$fills->{$obj_idx}}) { foreach my $layer_id (keys %{$fills->{$obj_idx}}) {
$self->objects->[$obj_idx]->layers->[$layer_id]->fills($fills->{$obj_idx}{$layer_id}); my $layer = $object->layers->[$layer_id];
foreach my $material_id (keys %{$fills->{$obj_idx}{$layer_id}}) {
$layer->materials->[$material_id]->fills($fills->{$obj_idx}{$layer_id}{$material_id});
}
} }
} }
}, },
no_threads_cb => sub { no_threads_cb => sub {
foreach my $layer (map @{$_->layers}, @{$self->objects}) { foreach my $layerm (map @{$_->materials}, map @{$_->layers}, @{$self->objects}) {
$layer->fills([ $fill_maker->make_fill($layer) ]); $layerm->fills([ $fill_maker->make_fill($layerm) ]);
} }
}, },
); );
@ -354,11 +363,11 @@ sub export_gcode {
# generate support material # generate support material
if ($Slic3r::Config->support_material) { if ($Slic3r::Config->support_material) {
$status_cb->(85, "Generating support material"); $status_cb->(85, "Generating support material");
$_->generate_support_material(print => $self) for @{$self->objects}; $_->generate_support_material for @{$self->objects};
} }
# free memory (note that support material needs fill_surfaces) # free memory (note that support material needs fill_surfaces)
$_->fill_surfaces(undef) for map @{$_->layers}, @{$self->objects}; $_->fill_surfaces(undef) for map @{$_->materials}, map @{$_->layers}, @{$self->objects};
# make skirt # make skirt
$status_cb->(88, "Generating skirt"); $status_cb->(88, "Generating skirt");
@ -486,7 +495,7 @@ sub make_skirt {
my @layers = map $self->objects->[$obj_idx]->layer($_), 0..($skirt_height-1); my @layers = map $self->objects->[$obj_idx]->layer($_), 0..($skirt_height-1);
my @layer_points = ( my @layer_points = (
(map @$_, map @{$_->expolygon}, map @{$_->slices}, @layers), (map @$_, map @{$_->expolygon}, map @{$_->slices}, @layers),
(map @$_, map @{$_->thin_walls}, @layers), (map @$_, map @{$_->thin_walls}, map @{$_->materials}, @layers),
(map @{$_->unpack->polyline}, map @{$_->support_fills->paths}, grep $_->support_fills, @layers), (map @{$_->unpack->polyline}, map @{$_->support_fills->paths}, grep $_->support_fills, @layers),
); );
push @points, map move_points($_, @layer_points), @{$self->objects->[$obj_idx]->copies}; push @points, map move_points($_, @layer_points), @{$self->objects->[$obj_idx]->copies};
@ -521,7 +530,7 @@ sub make_brim {
my $layer0 = $self->objects->[$obj_idx]->layers->[0]; my $layer0 = $self->objects->[$obj_idx]->layers->[0];
my @object_islands = ( my @object_islands = (
(map $_->contour, @{$layer0->slices}), (map $_->contour, @{$layer0->slices}),
(map { $_->isa('Slic3r::Polygon') ? $_ : $_->grow($grow_distance) } @{$layer0->thin_walls}), (map { $_->isa('Slic3r::Polygon') ? $_ : $_->grow($grow_distance) } map @{$_->thin_walls}, @{$layer0->materials}),
(map $_->unpack->polyline->grow($grow_distance), map @{$_->support_fills->paths}, grep $_->support_fills, $layer0), (map $_->unpack->polyline->grow($grow_distance), map @{$_->support_fills->paths}, grep $_->support_fills, $layer0),
); );
foreach my $copy (@{$self->objects->[$obj_idx]->copies}) { foreach my $copy (@{$self->objects->[$obj_idx]->copies}) {
@ -660,22 +669,26 @@ sub write_gcode {
$gcodegen->shift_x($shift[X] + unscale $copy->[X]); $gcodegen->shift_x($shift[X] + unscale $copy->[X]);
$gcodegen->shift_y($shift[Y] + unscale $copy->[Y]); $gcodegen->shift_y($shift[Y] + unscale $copy->[Y]);
# extrude perimeters foreach my $material_id (0 .. ($self->materials_count-1)) {
$gcode .= $gcodegen->set_tool($Slic3r::Config->perimeter_extruder-1); my $layerm = $layer->materials->[$material_id];
$gcode .= $gcodegen->extrude($_, 'perimeter') for @{ $layer->perimeters };
# extrude perimeters
# extrude fills $gcode .= $gcodegen->set_tool($Slic3r::Config->perimeter_extruder-1);
$gcode .= $gcodegen->set_tool($Slic3r::Config->infill_extruder-1); $gcode .= $gcodegen->extrude($_, 'perimeter') for @{ $layerm->perimeters };
$gcode .= $gcodegen->set_acceleration($Slic3r::Config->infill_acceleration);
for my $fill (@{ $layer->fills }) { # extrude fills
if ($fill->isa('Slic3r::ExtrusionPath::Collection')) { $gcode .= $gcodegen->set_tool($Slic3r::Config->infill_extruder-1);
$gcode .= $gcodegen->extrude($_, 'fill') $gcode .= $gcodegen->set_acceleration($Slic3r::Config->infill_acceleration);
for $fill->shortest_path($gcodegen->last_pos); for my $fill (@{ $layerm->fills }) {
} else { if ($fill->isa('Slic3r::ExtrusionPath::Collection')) {
$gcode .= $gcodegen->extrude($fill, 'fill') ; $gcode .= $gcodegen->extrude($_, 'fill')
for $fill->shortest_path($gcodegen->last_pos);
} else {
$gcode .= $gcodegen->extrude($fill, 'fill') ;
}
} }
} }
# extrude support material # extrude support material
if ($layer->support_fills) { if ($layer->support_fills) {
$gcode .= $gcodegen->set_tool($Slic3r::Config->support_material_extruder-1); $gcode .= $gcodegen->set_tool($Slic3r::Config->support_material_extruder-1);

View File

@ -6,17 +6,12 @@ use Slic3r::Geometry qw(scale unscale deg2rad);
use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex union_ex); use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex union_ex);
use Slic3r::Surface ':types'; use Slic3r::Surface ':types';
has 'print' => (is => 'ro', weak_ref => 1, required => 1);
has 'input_file' => (is => 'rw', required => 0); has 'input_file' => (is => 'rw', required => 0);
has 'mesh' => (is => 'rw', required => 0); has 'meshes' => (is => 'rw', default => sub { [] }); # by material_id
has 'size' => (is => 'rw', required => 1); has 'size' => (is => 'rw', required => 1);
has 'copies' => (is => 'rw', default => sub {[ [0,0] ]}); has 'copies' => (is => 'rw', default => sub {[ [0,0] ]});
has 'layers' => (is => 'rw', default => sub { [] });
has 'layers' => (
traits => ['Array'],
is => 'rw',
#isa => 'ArrayRef[Slic3r::Layer]',
default => sub { [] },
);
sub layer_count { sub layer_count {
my $self = shift; my $self = shift;
@ -43,22 +38,24 @@ sub slice {
my %params = @_; my %params = @_;
# process facets # process facets
{ for my $material_id (0 .. $#{$self->meshes}) {
my $mesh = $self->meshes->[$material_id]; # ignore undef meshes
my $apply_lines = sub { my $apply_lines = sub {
my $lines = shift; my $lines = shift;
foreach my $layer_id (keys %$lines) { foreach my $layer_id (keys %$lines) {
my $layer = $self->layer($layer_id); my $layerm = $self->layer($layer_id)->material($material_id);
push @{$layer->lines}, @{$lines->{$layer_id}}; push @{$layerm->lines}, @{$lines->{$layer_id}};
} }
}; };
Slic3r::parallelize( Slic3r::parallelize(
disable => ($#{$self->mesh->facets} < 500), # don't parallelize when too few facets disable => ($#{$mesh->facets} < 500), # don't parallelize when too few facets
items => [ 0..$#{$self->mesh->facets} ], items => [ 0..$#{$mesh->facets} ],
thread_cb => sub { thread_cb => sub {
my $q = shift; my $q = shift;
my $result_lines = {}; my $result_lines = {};
while (defined (my $facet_id = $q->dequeue)) { while (defined (my $facet_id = $q->dequeue)) {
my $lines = $self->mesh->slice_facet($self, $facet_id); my $lines = $mesh->slice_facet($self, $facet_id);
foreach my $layer_id (keys %$lines) { foreach my $layer_id (keys %$lines) {
$result_lines->{$layer_id} ||= []; $result_lines->{$layer_id} ||= [];
push @{ $result_lines->{$layer_id} }, @{ $lines->{$layer_id} }; push @{ $result_lines->{$layer_id} }, @{ $lines->{$layer_id} };
@ -70,8 +67,8 @@ sub slice {
$apply_lines->($_[0]); $apply_lines->($_[0]);
}, },
no_threads_cb => sub { no_threads_cb => sub {
for (0..$#{$self->mesh->facets}) { for (0..$#{$mesh->facets}) {
my $lines = $self->mesh->slice_facet($self, $_); my $lines = $mesh->slice_facet($self, $_);
$apply_lines->($lines); $apply_lines->($lines);
} }
}, },
@ -80,11 +77,11 @@ sub slice {
die "Invalid input file\n" if !@{$self->layers}; die "Invalid input file\n" if !@{$self->layers};
# free memory # free memory
$self->mesh(undef) unless $params{keep_meshes}; $self->meshes(undef) unless $params{keep_meshes};
# remove last layer if empty # remove last layer if empty
# (we might have created it because of the $max_layer = ... + 1 code below) # (we might have created it because of the $max_layer = ... + 1 code in TriangleMesh)
pop @{$self->layers} if !@{$self->layers->[-1]->lines}; pop @{$self->layers} if !map @{$_->lines}, @{$self->layers->[-1]->materials};
foreach my $layer (@{ $self->layers }) { foreach my $layer (@{ $self->layers }) {
Slic3r::debugf "Making surfaces for layer %d (slice z = %f):\n", Slic3r::debugf "Making surfaces for layer %d (slice z = %f):\n",
@ -98,10 +95,17 @@ sub slice {
# inside a closed polyline) # inside a closed polyline)
# build surfaces from sparse lines # build surfaces from sparse lines
$layer->make_surfaces(Slic3r::TriangleMesh::make_loops($layer)); foreach my $layerm (@{$layer->materials}) {
my ($slicing_errors, $loops) = Slic3r::TriangleMesh::make_loops($layerm->lines);
$layer->slicing_errors(1) if $slicing_errors;
$layerm->make_surfaces($loops);
# free memory
$layerm->lines(undef);
}
# free memory # merge all materials' slices to get islands
$layer->lines(undef); $layer->make_slices;
} }
# detect slicing errors # detect slicing errors
@ -119,31 +123,38 @@ sub slice {
# neighbor layers # neighbor layers
Slic3r::debugf "Attempting to repair layer %d\n", $i; Slic3r::debugf "Attempting to repair layer %d\n", $i;
my (@upper_surfaces, @lower_surfaces); foreach my $material_id (0 .. $#{$layer->materials}) {
for (my $j = $i+1; $j <= $#{$self->layers}; $j++) { my $layerm = $layer->material($material_id);
if (!$self->layers->[$j]->slicing_errors) {
@upper_surfaces = @{$self->layers->[$j]->slices}; my (@upper_surfaces, @lower_surfaces);
last; for (my $j = $i+1; $j <= $#{$self->layers}; $j++) {
if (!$self->layers->[$j]->slicing_errors) {
@upper_surfaces = @{$self->layers->[$j]->material($material_id)->slices};
last;
}
} }
} for (my $j = $i-1; $j >= 0; $j--) {
for (my $j = $i-1; $j >= 0; $j--) { if (!$self->layers->[$j]->slicing_errors) {
if (!$self->layers->[$j]->slicing_errors) { @lower_surfaces = @{$self->layers->[$j]->material($material_id)->slices};
@lower_surfaces = @{$self->layers->[$j]->slices}; last;
last; }
} }
my $union = union_ex([
map $_->expolygon->contour, @upper_surfaces, @lower_surfaces,
]);
my $diff = diff_ex(
[ map @$_, @$union ],
[ map $_->expolygon->holes, @upper_surfaces, @lower_surfaces, ],
);
@{$layerm->slices} = map Slic3r::Surface->new
(expolygon => $_, surface_type => S_TYPE_INTERNAL),
@$diff;
} }
my $union = union_ex([ # update layer slices after repairing the single materials
map $_->expolygon->contour, @upper_surfaces, @lower_surfaces, $layer->make_slices;
]);
my $diff = diff_ex(
[ map @$_, @$union ],
[ map $_->expolygon->holes, @upper_surfaces, @lower_surfaces, ],
);
@{$layer->slices} = map Slic3r::Surface->new
(expolygon => $_, surface_type => S_TYPE_INTERNAL),
@$diff;
} }
# remove empty layers from bottom # remove empty layers from bottom
@ -171,53 +182,57 @@ sub make_perimeters {
# this algorithm makes sure that almost one perimeter is overlapping # this algorithm makes sure that almost one perimeter is overlapping
if ($Slic3r::Config->extra_perimeters && $Slic3r::Config->perimeters > 0) { if ($Slic3r::Config->extra_perimeters && $Slic3r::Config->perimeters > 0) {
for my $layer_id (0 .. $self->layer_count-2) { for my $material_id (0 .. ($self->print->materials_count-1)) {
my $layer = $self->layers->[$layer_id]; for my $layer_id (0 .. $self->layer_count-2) {
my $upper_layer = $self->layers->[$layer_id+1]; my $layerm = $self->layers->[$layer_id]->materials->[$material_id];
my $upper_layerm = $self->layers->[$layer_id+1]->materials->[$material_id];
my $overlap = $layer->perimeter_flow->spacing; # one perimeter my $perimeter_flow_spacing = $layerm->perimeter_flow->spacing;
my $scaled_perimeter_flow_spacing = scale $perimeter_flow_spacing;
# compute polygons representing the thickness of the first external perimeter of
# the upper layer slices my $overlap = $perimeter_flow_spacing; # one perimeter
my $upper = diff_ex(
[ map @$_, map $_->expolygon->offset_ex(+ 0.5 * scale $layer->perimeter_flow->spacing), @{$upper_layer->slices} ], # compute polygons representing the thickness of the first external perimeter of
[ map @$_, map $_->expolygon->offset_ex(- scale($overlap) + (0.5 * scale $layer->perimeter_flow->spacing)), @{$upper_layer->slices} ], # the upper layer slices
); my $upper = diff_ex(
next if !@$upper; [ map @$_, map $_->expolygon->offset_ex(+ 0.5 * $scaled_perimeter_flow_spacing), @{$upper_layerm->slices} ],
[ map @$_, map $_->expolygon->offset_ex(- scale($overlap) + (0.5 * $scaled_perimeter_flow_spacing)), @{$upper_layerm->slices} ],
# we need to limit our detection to the areas which would actually benefit from
# more perimeters. so, let's compute the area we want to ignore
my $ignore = [];
{
my $diff = diff_ex(
[ map @$_, map $_->expolygon->offset_ex(- ($Slic3r::Config->perimeters-0.5) * scale $layer->perimeter_flow->spacing), @{$layer->slices} ],
[ map @{$_->expolygon}, @{$upper_layer->slices} ],
); );
$ignore = [ map @$_, map $_->offset_ex(scale $layer->perimeter_flow->spacing), @$diff ]; next if !@$upper;
}
# we need to limit our detection to the areas which would actually benefit from
foreach my $slice (@{$layer->slices}) { # more perimeters. so, let's compute the area we want to ignore
my $hypothetical_perimeter_num = $Slic3r::Config->perimeters + 1; my $ignore = [];
CYCLE: while (1) { {
# compute polygons representing the thickness of the hypotetical new internal perimeter my $diff = diff_ex(
# of our slice [ map @$_, map $_->expolygon->offset_ex(- ($Slic3r::Config->perimeters-0.5) * $scaled_perimeter_flow_spacing), @{$layerm->slices} ],
my $hypothetical_perimeter; [ map @{$_->expolygon}, @{$upper_layerm->slices} ],
{ );
my $outer = [ map @$_, $slice->expolygon->offset_ex(- ($hypothetical_perimeter_num-1.5) * scale $layer->perimeter_flow->spacing) ]; $ignore = [ map @$_, map $_->offset_ex($scaled_perimeter_flow_spacing), @$diff ];
last CYCLE if !@$outer; }
my $inner = [ map @$_, $slice->expolygon->offset_ex(- ($hypothetical_perimeter_num-0.5) * scale $layer->perimeter_flow->spacing) ];
last CYCLE if !@$inner; foreach my $slice (@{$layerm->slices}) {
$hypothetical_perimeter = diff_ex($outer, $inner); my $hypothetical_perimeter_num = $Slic3r::Config->perimeters + 1;
CYCLE: while (1) {
# compute polygons representing the thickness of the hypotetical new internal perimeter
# of our slice
my $hypothetical_perimeter;
{
my $outer = [ map @$_, $slice->expolygon->offset_ex(- ($hypothetical_perimeter_num-1.5) * $scaled_perimeter_flow_spacing) ];
last CYCLE if !@$outer;
my $inner = [ map @$_, $slice->expolygon->offset_ex(- ($hypothetical_perimeter_num-0.5) * $scaled_perimeter_flow_spacing) ];
last CYCLE if !@$inner;
$hypothetical_perimeter = diff_ex($outer, $inner);
}
last CYCLE if !@$hypothetical_perimeter;
my $intersection = intersection_ex([ map @$_, @$upper ], [ map @$_, @$hypothetical_perimeter ]);
$intersection = diff_ex([ map @$_, @$intersection ], $ignore) if @$ignore;
last CYCLE if !@{ $intersection };
Slic3r::debugf " adding one more perimeter at layer %d\n", $layer_id;
$slice->additional_inner_perimeters(($slice->additional_inner_perimeters || 0) + 1);
$hypothetical_perimeter_num++;
} }
last CYCLE if !@$hypothetical_perimeter;
my $intersection = intersection_ex([ map @$_, @$upper ], [ map @$_, @$hypothetical_perimeter ]);
$intersection = diff_ex([ map @$_, @$intersection ], $ignore) if @$ignore;
last CYCLE if !@{ $intersection };
Slic3r::debugf " adding one more perimeter at layer %d\n", $layer_id;
$slice->additional_inner_perimeters(($slice->additional_inner_perimeters || 0) + 1);
$hypothetical_perimeter_num++;
} }
} }
} }
@ -232,75 +247,80 @@ sub detect_surfaces_type {
# prepare a reusable subroutine to make surface differences # prepare a reusable subroutine to make surface differences
my $surface_difference = sub { my $surface_difference = sub {
my ($subject_surfaces, $clip_surfaces, $result_type, $layer) = @_; my ($subject_surfaces, $clip_surfaces, $result_type, $layerm) = @_;
my $expolygons = diff_ex( my $expolygons = diff_ex(
[ map { ref $_ eq 'ARRAY' ? $_ : ref $_ eq 'Slic3r::ExPolygon' ? @$_ : $_->p } @$subject_surfaces ], [ map { ref $_ eq 'ARRAY' ? $_ : ref $_ eq 'Slic3r::ExPolygon' ? @$_ : $_->p } @$subject_surfaces ],
[ map { ref $_ eq 'ARRAY' ? $_ : ref $_ eq 'Slic3r::ExPolygon' ? @$_ : $_->p } @$clip_surfaces ], [ map { ref $_ eq 'ARRAY' ? $_ : ref $_ eq 'Slic3r::ExPolygon' ? @$_ : $_->p } @$clip_surfaces ],
1, 1,
); );
return grep $_->contour->is_printable($layer->flow->width), return grep $_->contour->is_printable($layerm->flow->width),
map Slic3r::Surface->new(expolygon => $_, surface_type => $result_type), map Slic3r::Surface->new(expolygon => $_, surface_type => $result_type),
@$expolygons; @$expolygons;
}; };
for (my $i = 0; $i < $self->layer_count; $i++) { for my $material_id (0 .. ($self->print->materials_count-1)) {
my $layer = $self->layers->[$i]; for (my $i = 0; $i < $self->layer_count; $i++) {
my $upper_layer = $self->layers->[$i+1]; my $layerm = $self->layers->[$i]->materials->[$material_id];
my $lower_layer = $i > 0 ? $self->layers->[$i-1] : undef;
# comparison happens against the *full* slices (considering all materials)
my (@bottom, @top, @internal) = (); my $upper_layer = $self->layers->[$i+1];
my $lower_layer = $i > 0 ? $self->layers->[$i-1] : undef;
# find top surfaces (difference between current surfaces
# of current layer and upper one) my (@bottom, @top, @internal) = ();
if ($upper_layer) {
@top = $surface_difference->($layer->slices, $upper_layer->slices, S_TYPE_TOP, $layer); # find top surfaces (difference between current surfaces
} else { # of current layer and upper one)
# if no upper layer, all surfaces of this one are solid if ($upper_layer) {
@top = @{$layer->slices}; @top = $surface_difference->($layerm->slices, $upper_layer->slices, S_TYPE_TOP, $layerm);
$_->surface_type(S_TYPE_TOP) for @top; } else {
# if no upper layer, all surfaces of this one are solid
@top = @{$layerm->slices};
$_->surface_type(S_TYPE_TOP) for @top;
}
# find bottom surfaces (difference between current surfaces
# of current layer and lower one)
if ($lower_layer) {
@bottom = $surface_difference->($layerm->slices, $lower_layer->slices, S_TYPE_BOTTOM, $layerm);
} else {
# if no lower layer, all surfaces of this one are solid
@bottom = @{$layerm->slices};
$_->surface_type(S_TYPE_BOTTOM) for @bottom;
}
# now, if the object contained a thin membrane, we could have overlapping bottom
# and top surfaces; let's do an intersection to discover them and consider them
# as bottom surfaces (to allow for bridge detection)
if (@top && @bottom) {
my $overlapping = intersection_ex([ map $_->p, @top ], [ map $_->p, @bottom ]);
Slic3r::debugf " layer %d contains %d membrane(s)\n", $layerm->id, scalar(@$overlapping);
@top = $surface_difference->([@top], $overlapping, S_TYPE_TOP, $layerm);
}
# find internal surfaces (difference between top/bottom surfaces and others)
@internal = $surface_difference->($layerm->slices, [@top, @bottom], S_TYPE_INTERNAL, $layerm);
# save surfaces to layer
@{$layerm->slices} = (@bottom, @top, @internal);
Slic3r::debugf " layer %d has %d bottom, %d top and %d internal surfaces\n",
$layerm->id, scalar(@bottom), scalar(@top), scalar(@internal);
} }
# find bottom surfaces (difference between current surfaces # clip surfaces to the fill boundaries
# of current layer and lower one) foreach my $layer (@{$self->layers}) {
if ($lower_layer) { my $layerm = $layer->materials->[$material_id];
@bottom = $surface_difference->($layer->slices, $lower_layer->slices, S_TYPE_BOTTOM, $layer); my $fill_boundaries = [ map @$_, @{$layerm->surfaces} ];
} else { @{$layerm->surfaces} = ();
# if no lower layer, all surfaces of this one are solid foreach my $surface (@{$layerm->slices}) {
@bottom = @{$layer->slices}; my $intersection = intersection_ex(
$_->surface_type(S_TYPE_BOTTOM) for @bottom; [ $surface->p ],
} $fill_boundaries,
);
# now, if the object contained a thin membrane, we could have overlapping bottom push @{$layerm->surfaces}, map Slic3r::Surface->new
# and top surfaces; let's do an intersection to discover them and consider them (expolygon => $_, surface_type => $surface->surface_type),
# as bottom surfaces (to allow for bridge detection) @$intersection;
if (@top && @bottom) { }
my $overlapping = intersection_ex([ map $_->p, @top ], [ map $_->p, @bottom ]);
Slic3r::debugf " layer %d contains %d membrane(s)\n", $layer->id, scalar(@$overlapping);
@top = $surface_difference->([@top], $overlapping, S_TYPE_TOP, $layer);
}
# find internal surfaces (difference between top/bottom surfaces and others)
@internal = $surface_difference->($layer->slices, [@top, @bottom], S_TYPE_INTERNAL, $layer);
# save surfaces to layer
@{$layer->slices} = (@bottom, @top, @internal);
Slic3r::debugf " layer %d has %d bottom, %d top and %d internal surfaces\n",
$layer->id, scalar(@bottom), scalar(@top), scalar(@internal);
}
# clip surfaces to the fill boundaries
foreach my $layer (@{$self->layers}) {
my $fill_boundaries = [ map @$_, @{$layer->surfaces} ];
@{$layer->surfaces} = ();
foreach my $surface (@{$layer->slices}) {
my $intersection = intersection_ex(
[ $surface->p ],
$fill_boundaries,
);
push @{$layer->surfaces}, map Slic3r::Surface->new
(expolygon => $_, surface_type => $surface->surface_type),
@$intersection;
} }
} }
} }
@ -312,82 +332,84 @@ sub discover_horizontal_shells {
my $area_threshold = scale($Slic3r::flow->spacing) ** 2; my $area_threshold = scale($Slic3r::flow->spacing) ** 2;
for (my $i = 0; $i < $self->layer_count; $i++) { for my $material_id (0 .. ($self->print->materials_count-1)) {
my $layer = $self->layers->[$i]; for (my $i = 0; $i < $self->layer_count; $i++) {
foreach my $type (S_TYPE_TOP, S_TYPE_BOTTOM) { my $layerm = $self->layers->[$i]->materials->[$material_id];
# find surfaces of current type for current layer foreach my $type (S_TYPE_TOP, S_TYPE_BOTTOM) {
# and offset them to take perimeters into account # find surfaces of current type for current layer
my @surfaces = map $_->offset($Slic3r::Config->perimeters * scale $layer->perimeter_flow->width), # and offset them to take perimeters into account
grep $_->surface_type == $type, @{$layer->fill_surfaces} or next; my @surfaces = map $_->offset($Slic3r::Config->perimeters * scale $layerm->perimeter_flow->width),
my $surfaces_p = [ map $_->p, @surfaces ]; grep $_->surface_type == $type, @{$layerm->fill_surfaces} or next;
Slic3r::debugf "Layer %d has %d surfaces of type '%s'\n", my $surfaces_p = [ map $_->p, @surfaces ];
$i, scalar(@surfaces), ($type == S_TYPE_TOP ? 'top' : 'bottom'); Slic3r::debugf "Layer %d has %d surfaces of type '%s'\n",
$i, scalar(@surfaces), ($type == S_TYPE_TOP ? 'top' : 'bottom');
for (my $n = $type == S_TYPE_TOP ? $i-1 : $i+1;
abs($n - $i) <= $Slic3r::Config->solid_layers-1;
$type == S_TYPE_TOP ? $n-- : $n++) {
next if $n < 0 || $n >= $self->layer_count; for (my $n = $type == S_TYPE_TOP ? $i-1 : $i+1;
Slic3r::debugf " looking for neighbors on layer %d...\n", $n; abs($n - $i) <= $Slic3r::Config->solid_layers-1;
$type == S_TYPE_TOP ? $n-- : $n++) {
my @neighbor_surfaces = @{$self->layers->[$n]->surfaces};
my @neighbor_fill_surfaces = @{$self->layers->[$n]->fill_surfaces}; next if $n < 0 || $n >= $self->layer_count;
Slic3r::debugf " looking for neighbors on layer %d...\n", $n;
# find intersection between neighbor and current layer's surfaces
# intersections have contours and holes my @neighbor_surfaces = @{$self->layers->[$n]->materials->[$material_id]->surfaces};
my $new_internal_solid = intersection_ex( my @neighbor_fill_surfaces = @{$self->layers->[$n]->materials->[$material_id]->fill_surfaces};
$surfaces_p,
[ map $_->p, grep { $_->surface_type == S_TYPE_INTERNAL || $_->surface_type == S_TYPE_INTERNALSOLID } @neighbor_surfaces ], # find intersection between neighbor and current layer's surfaces
undef, 1, # intersections have contours and holes
); my $new_internal_solid = intersection_ex(
next if !@$new_internal_solid; $surfaces_p,
[ map $_->p, grep { $_->surface_type == S_TYPE_INTERNAL || $_->surface_type == S_TYPE_INTERNALSOLID } @neighbor_surfaces ],
# internal-solid are the union of the existing internal-solid surfaces undef, 1,
# and new ones );
my $internal_solid = union_ex([ next if !@$new_internal_solid;
( map $_->p, grep $_->surface_type == S_TYPE_INTERNALSOLID, @neighbor_fill_surfaces ),
( map @$_, @$new_internal_solid ), # internal-solid are the union of the existing internal-solid surfaces
]); # and new ones
my $internal_solid = union_ex([
# subtract intersections from layer surfaces to get resulting inner surfaces ( map $_->p, grep $_->surface_type == S_TYPE_INTERNALSOLID, @neighbor_fill_surfaces ),
my $internal = diff_ex( ( map @$_, @$new_internal_solid ),
[ map $_->p, grep $_->surface_type == S_TYPE_INTERNAL, @neighbor_fill_surfaces ], ]);
[ map @$_, @$internal_solid ],
1, # subtract intersections from layer surfaces to get resulting inner surfaces
); my $internal = diff_ex(
Slic3r::debugf " %d internal-solid and %d internal surfaces found\n", [ map $_->p, grep $_->surface_type == S_TYPE_INTERNAL, @neighbor_fill_surfaces ],
scalar(@$internal_solid), scalar(@$internal); [ map @$_, @$internal_solid ],
# Note: due to floating point math we're going to get some very small
# polygons as $internal; they will be removed by removed_small_features()
# assign resulting inner surfaces to layer
my $neighbor_fill_surfaces = $self->layers->[$n]->fill_surfaces;
@$neighbor_fill_surfaces = ();
push @$neighbor_fill_surfaces, Slic3r::Surface->new
(expolygon => $_, surface_type => S_TYPE_INTERNAL)
for @$internal;
# assign new internal-solid surfaces to layer
push @$neighbor_fill_surfaces, Slic3r::Surface->new
(expolygon => $_, surface_type => S_TYPE_INTERNALSOLID)
for @$internal_solid;
# assign top and bottom surfaces to layer
foreach my $s (Slic3r::Surface->group(grep { $_->surface_type == S_TYPE_TOP || $_->surface_type == S_TYPE_BOTTOM } @neighbor_fill_surfaces)) {
my $solid_surfaces = diff_ex(
[ map $_->p, @$s ],
[ map @$_, @$internal_solid, @$internal ],
1, 1,
); );
Slic3r::debugf " %d internal-solid and %d internal surfaces found\n",
scalar(@$internal_solid), scalar(@$internal);
# Note: due to floating point math we're going to get some very small
# polygons as $internal; they will be removed by removed_small_features()
# assign resulting inner surfaces to layer
my $neighbor_fill_surfaces = $self->layers->[$n]->materials->[$material_id]->fill_surfaces;
@$neighbor_fill_surfaces = ();
push @$neighbor_fill_surfaces, Slic3r::Surface->new push @$neighbor_fill_surfaces, Slic3r::Surface->new
(expolygon => $_, surface_type => $s->[0]->surface_type, bridge_angle => $s->[0]->bridge_angle) (expolygon => $_, surface_type => S_TYPE_INTERNAL)
for @$solid_surfaces; for @$internal;
# assign new internal-solid surfaces to layer
push @$neighbor_fill_surfaces, Slic3r::Surface->new
(expolygon => $_, surface_type => S_TYPE_INTERNALSOLID)
for @$internal_solid;
# assign top and bottom surfaces to layer
foreach my $s (Slic3r::Surface->group(grep { $_->surface_type == S_TYPE_TOP || $_->surface_type == S_TYPE_BOTTOM } @neighbor_fill_surfaces)) {
my $solid_surfaces = diff_ex(
[ map $_->p, @$s ],
[ map @$_, @$internal_solid, @$internal ],
1,
);
push @$neighbor_fill_surfaces, Slic3r::Surface->new
(expolygon => $_, surface_type => $s->[0]->surface_type, bridge_angle => $s->[0]->bridge_angle)
for @$solid_surfaces;
}
} }
} }
@{$layerm->fill_surfaces} = grep $_->expolygon->area > $area_threshold, @{$layerm->fill_surfaces};
} }
@{$layer->fill_surfaces} = grep $_->expolygon->area > $area_threshold, @{$layer->fill_surfaces};
} }
} }
@ -398,93 +420,92 @@ sub combine_infill {
my $area_threshold = scale($Slic3r::flow->spacing) ** 2; my $area_threshold = scale($Slic3r::flow->spacing) ** 2;
# start from bottom, skip first layer for my $material_id (0 .. ($self->print->materials_count-1)) {
for (my $i = 1; $i < $self->layer_count; $i++) { # start from bottom, skip first layer
my $layer = $self->layer($i); for (my $i = 1; $i < $self->layer_count; $i++) {
my $layerm = $self->layers->[$i]->materials->[$material_id];
# skip layer if no internal fill surfaces
next if !grep $_->surface_type == S_TYPE_INTERNAL, @{$layer->fill_surfaces};
# for each possible depth, look for intersections with the lower layer
# we do this from the greater depth to the smaller
for (my $d = $Slic3r::Config->infill_every_layers - 1; $d >= 1; $d--) {
next if ($i - $d) < 0;
my $lower_layer = $self->layer($i - 1);
# select surfaces of the lower layer having the depth we're looking for # skip layer if no internal fill surfaces
my @lower_surfaces = grep $_->depth_layers == $d && $_->surface_type == S_TYPE_INTERNAL, next if !grep $_->surface_type == S_TYPE_INTERNAL, @{$layerm->fill_surfaces};
@{$lower_layer->fill_surfaces};
next if !@lower_surfaces;
# calculate intersection between our surfaces and theirs # for each possible depth, look for intersections with the lower layer
my $intersection = intersection_ex( # we do this from the greater depth to the smaller
[ map $_->p, grep $_->depth_layers <= $d, @lower_surfaces ], for (my $d = $Slic3r::Config->infill_every_layers - 1; $d >= 1; $d--) {
[ map $_->p, grep $_->surface_type == S_TYPE_INTERNAL, @{$layer->fill_surfaces} ], next if ($i - $d) < 0;
undef, 1, my $lower_layerm = $self->layer($i - 1)->materials->[$material_id];
);
# purge intersections, skip tiny regions
@$intersection = grep $_->area > $area_threshold, @$intersection;
next if !@$intersection;
# new fill surfaces of the current layer are:
# - any non-internal surface
# - intersections found (with a $d + 1 depth)
# - any internal surface not belonging to the intersection (with its original depth)
{
my @new_surfaces = ();
push @new_surfaces, grep $_->surface_type != S_TYPE_INTERNAL, @{$layer->fill_surfaces};
push @new_surfaces, map Slic3r::Surface->new
(expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $d + 1), @$intersection;
foreach my $depth (reverse $d..$Slic3r::Config->infill_every_layers) { # select surfaces of the lower layer having the depth we're looking for
my @lower_surfaces = grep $_->depth_layers == $d && $_->surface_type == S_TYPE_INTERNAL,
@{$lower_layerm->fill_surfaces};
next if !@lower_surfaces;
# calculate intersection between our surfaces and theirs
my $intersection = intersection_ex(
[ map $_->p, grep $_->depth_layers <= $d, @lower_surfaces ],
[ map $_->p, grep $_->surface_type == S_TYPE_INTERNAL, @{$layerm->fill_surfaces} ],
undef, 1,
);
# purge intersections, skip tiny regions
@$intersection = grep $_->area > $area_threshold, @$intersection;
next if !@$intersection;
# new fill surfaces of the current layer are:
# - any non-internal surface
# - intersections found (with a $d + 1 depth)
# - any internal surface not belonging to the intersection (with its original depth)
{
my @new_surfaces = ();
push @new_surfaces, grep $_->surface_type != S_TYPE_INTERNAL, @{$layerm->fill_surfaces};
push @new_surfaces, map Slic3r::Surface->new push @new_surfaces, map Slic3r::Surface->new
(expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $depth), (expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $d + 1), @$intersection;
# difference between our internal layers with depth == $depth foreach my $depth (reverse $d..$Slic3r::Config->infill_every_layers) {
# and the intersection found push @new_surfaces, map Slic3r::Surface->new
@{diff_ex( (expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $depth),
[
map $_->p, grep $_->surface_type == S_TYPE_INTERNAL && $_->depth_layers == $depth, # difference between our internal layers with depth == $depth
@{$layer->fill_surfaces}, # and the intersection found
], @{diff_ex(
[ map @$_, @$intersection ], [
1, map $_->p, grep $_->surface_type == S_TYPE_INTERNAL && $_->depth_layers == $depth,
)}; @{$layerm->fill_surfaces},
],
[ map @$_, @$intersection ],
1,
)};
}
@{$layerm->fill_surfaces} = @new_surfaces;
} }
@{$layer->fill_surfaces} = @new_surfaces;
} # now we remove the intersections from lower layer
{
# now we remove the intersections from lower layer my @new_surfaces = ();
{ push @new_surfaces, grep $_->surface_type != S_TYPE_INTERNAL, @{$lower_layerm->fill_surfaces};
my @new_surfaces = (); foreach my $depth (1..$Slic3r::Config->infill_every_layers) {
push @new_surfaces, grep $_->surface_type != S_TYPE_INTERNAL, @{$lower_layer->fill_surfaces}; push @new_surfaces, map Slic3r::Surface->new
foreach my $depth (1..$Slic3r::Config->infill_every_layers) { (expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $depth),
push @new_surfaces, map Slic3r::Surface->new
(expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $depth), # difference between internal layers with depth == $depth
# and the intersection found
# difference between internal layers with depth == $depth @{diff_ex(
# and the intersection found [
@{diff_ex( map $_->p, grep $_->surface_type == S_TYPE_INTERNAL && $_->depth_layers == $depth,
[ @{$lower_layerm->fill_surfaces},
map $_->p, grep $_->surface_type == S_TYPE_INTERNAL && $_->depth_layers == $depth, ],
@{$lower_layer->fill_surfaces}, [ map @$_, @$intersection ],
], 1,
[ map @$_, @$intersection ], )};
1, }
)}; @{$lower_layerm->fill_surfaces} = @new_surfaces;
} }
@{$lower_layer->fill_surfaces} = @new_surfaces;
} }
} }
} }
} }
sub generate_support_material { sub generate_support_material {
my $self = shift; my $self = shift;
my %params = @_;
my $threshold_rad = deg2rad($Slic3r::Config->support_material_threshold + 1); # +1 makes the threshold inclusive my $threshold_rad = deg2rad($Slic3r::Config->support_material_threshold + 1); # +1 makes the threshold inclusive
my $overhang_width = $threshold_rad == 0 ? undef : scale $Slic3r::Config->layer_height * ((cos $threshold_rad) / (sin $threshold_rad)); my $overhang_width = $threshold_rad == 0 ? undef : scale $Slic3r::Config->layer_height * ((cos $threshold_rad) / (sin $threshold_rad));
@ -537,7 +558,7 @@ sub generate_support_material {
my @support_material_areas = map $_->offset_ex(- 0.5 * scale $Slic3r::support_material_flow->width), my @support_material_areas = map $_->offset_ex(- 0.5 * scale $Slic3r::support_material_flow->width),
@{union_ex([ map $_->contour, map @$_, values %layers ])}; @{union_ex([ map $_->contour, map @$_, values %layers ])};
my $fill = Slic3r::Fill->new(print => $params{print}); my $fill = Slic3r::Fill->new(print => $self->print);
my $filler = $fill->filler($Slic3r::Config->support_material_pattern); my $filler = $fill->filler($Slic3r::Config->support_material_pattern);
$filler->angle($Slic3r::Config->support_material_angle); $filler->angle($Slic3r::Config->support_material_angle);
{ {

View File

@ -167,9 +167,8 @@ sub unpack_line {
} }
sub make_loops { sub make_loops {
my ($layer) = @_; my ($lines) = @_;
my @lines = map unpack_line($_), @$lines;
my @lines = map unpack_line($_), @{$layer->lines};
# remove tangent edges # remove tangent edges
{ {
@ -258,6 +257,7 @@ sub make_loops {
(0..$#lines); (0..$#lines);
my (@polygons, @failed_loops, %visited_lines) = (); my (@polygons, @failed_loops, %visited_lines) = ();
my $slicing_errors = 0;
CYCLE: for (my $i = 0; $i <= $#lines; $i++) { CYCLE: for (my $i = 0; $i <= $#lines; $i++) {
my $line = $lines[$i]; my $line = $lines[$i];
next if $visited_lines{$line}; next if $visited_lines{$line};
@ -272,24 +272,24 @@ sub make_loops {
$next_line = $lines[$by_a_id{$line->[I_B_ID]}]; $next_line = $lines[$by_a_id{$line->[I_B_ID]}];
} else { } else {
Slic3r::debugf " line has no next_facet_index or b_id\n"; Slic3r::debugf " line has no next_facet_index or b_id\n";
$layer->slicing_errors(1); $slicing_errors = 1;
push @failed_loops, [@points] if @points; push @failed_loops, [@points] if @points;
next CYCLE; next CYCLE;
} }
if (!$next_line || $visited_lines{$next_line}) { if (!$next_line || $visited_lines{$next_line}) {
Slic3r::debugf " failed to close this loop\n"; Slic3r::debugf " failed to close this loop\n";
$layer->slicing_errors(1); $slicing_errors = 1;
push @failed_loops, [@points] if @points; push @failed_loops, [@points] if @points;
next CYCLE; next CYCLE;
} elsif (defined $next_line->[I_PREV_FACET_INDEX] && $next_line->[I_PREV_FACET_INDEX] != $line->[I_FACET_INDEX]) { } elsif (defined $next_line->[I_PREV_FACET_INDEX] && $next_line->[I_PREV_FACET_INDEX] != $line->[I_FACET_INDEX]) {
Slic3r::debugf " wrong prev_facet_index\n"; Slic3r::debugf " wrong prev_facet_index\n";
$layer->slicing_errors(1); $slicing_errors = 1;
push @failed_loops, [@points] if @points; push @failed_loops, [@points] if @points;
next CYCLE; next CYCLE;
} elsif (defined $next_line->[I_A_ID] && $next_line->[I_A_ID] != $line->[I_B_ID]) { } elsif (defined $next_line->[I_A_ID] && $next_line->[I_A_ID] != $line->[I_B_ID]) {
Slic3r::debugf " wrong a_id\n"; Slic3r::debugf " wrong a_id\n";
$layer->slicing_errors(1); $slicing_errors = 1;
push @failed_loops, [@points] if @points; push @failed_loops, [@points] if @points;
next CYCLE; next CYCLE;
} }
@ -313,7 +313,7 @@ sub make_loops {
if $Slic3r::debug; if $Slic3r::debug;
} }
return [@polygons]; return ($slicing_errors, [@polygons]);
} }
sub rotate { sub rotate {

View File

@ -88,9 +88,9 @@ if (@ARGV) { # slicing from command line
while (my $input_file = shift @ARGV) { while (my $input_file = shift @ARGV) {
my $print = Slic3r::Print->new(config => $config); my $print = Slic3r::Print->new(config => $config);
$print->add_objects_from_file($input_file); $print->add_model(Slic3r::Model->read_from_file($input_file));
if ($opt{merge}) { if ($opt{merge}) {
$print->add_objects_from_file($_) for splice @ARGV, 0; $print->add_model(Slic3r::Model->read_from_file($_)) for splice @ARGV, 0;
} }
$print->duplicate; $print->duplicate;
$print->arrange_objects if @{$print->objects} > 1; $print->arrange_objects if @{$print->objects} > 1;

View File

@ -44,7 +44,7 @@ my %opt = ();
} @{ $model->objects->[0]->volumes->[0]->facets }; } @{ $model->objects->[0]->volumes->[0]->facets };
my $material_id = scalar keys %{$new_model->materials}; my $material_id = scalar keys %{$new_model->materials};
$new_model->materials->{$material_id} = { Name => basename($ARGV[$m]) }; $new_model->set_material($material_id, { Name => basename($ARGV[$m]) });
$new_object->add_volume( $new_object->add_volume(
material_id => $material_id, material_id => $material_id,
facets => [@new_facets], facets => [@new_facets],