From 02356fd613e495934036cd36c5177325fd797b10 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 22 Sep 2012 19:04:36 +0200 Subject: [PATCH] Large refactoring to allow processing multimaterial files --- MANIFEST | 1 + lib/Slic3r.pm | 4 +- lib/Slic3r/Format/AMF.pm | 4 +- lib/Slic3r/Format/AMF/Parser.pm | 6 +- lib/Slic3r/GUI/SkeinPanel.pm | 2 +- lib/Slic3r/Layer.pm | 542 ++-------------------------- lib/Slic3r/Layer/Material.pm | 515 +++++++++++++++++++++++++++ lib/Slic3r/Model.pm | 11 + lib/Slic3r/Print.pm | 163 +++++---- lib/Slic3r/Print/Object.pm | 603 +++++++++++++++++--------------- lib/Slic3r/TriangleMesh.pm | 16 +- slic3r.pl | 4 +- utils/stl-to-amf.pl | 2 +- 13 files changed, 982 insertions(+), 891 deletions(-) create mode 100644 lib/Slic3r/Layer/Material.pm diff --git a/MANIFEST b/MANIFEST index ff89293dd..f006c50ae 100644 --- a/MANIFEST +++ b/MANIFEST @@ -34,6 +34,7 @@ lib/Slic3r/GUI/Plater.pm lib/Slic3r/GUI/SkeinPanel.pm lib/Slic3r/GUI/Tab.pm lib/Slic3r/Layer.pm +lib/Slic3r/Layer/Material.pm lib/Slic3r/Line.pm lib/Slic3r/Model.pm lib/Slic3r/Point.pm diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index f6bd643c4..fbeae4000 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -43,6 +43,7 @@ use Slic3r::Format::STL; use Slic3r::GCode; use Slic3r::Geometry qw(PI); use Slic3r::Layer; +use Slic3r::Layer::Material; use Slic3r::Line; use Slic3r::Model; use Slic3r::Point; @@ -70,8 +71,9 @@ sub parallelize { my %params = @_; 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; - $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) }; foreach my $th (map threads->create($thread_cb), 1..$Config->threads) { diff --git a/lib/Slic3r/Format/AMF.pm b/lib/Slic3r/Format/AMF.pm index 78d2656c5..d654ca1a5 100644 --- a/lib/Slic3r/Format/AMF.pm +++ b/lib/Slic3r/Format/AMF.pm @@ -35,8 +35,8 @@ sub write_file { for my $material_id (sort keys %{ $model->materials }) { my $material = $model->materials->{$material_id}; printf $fh qq{ \n}, $material_id; - for (keys %$material) { - printf $fh qq{ %s\n}, $_, $material->{$_}; + for (keys %{$material->attributes}) { + printf $fh qq{ %s\n}, $_, $material->attributes->{$_}; } printf $fh qq{ \n}; } diff --git a/lib/Slic3r/Format/AMF/Parser.pm b/lib/Slic3r/Format/AMF/Parser.pm index 5e0b28178..173dfc091 100644 --- a/lib/Slic3r/Format/AMF/Parser.pm +++ b/lib/Slic3r/Format/AMF/Parser.pm @@ -37,10 +37,10 @@ sub start_element { $self->{_vertex_idx} = $1-1; } elsif ($data->{LocalName} eq 'material') { 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') { $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') { $self->{_constellation} = 1; # we merge all constellations as we don't support more than one } elsif ($data->{LocalName} eq 'instance' && $self->{_constellation}) { @@ -63,7 +63,7 @@ sub characters { } elsif ($self->{_triangle} && defined $self->{_vertex_idx}) { $self->{_triangle}[ $self->{_vertex_idx} ] .= $data->{Data}; } 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}) { $self->{_instance}{ $self->{_instance_property} } .= $data->{Data}; } diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index 6ef5a64de..6712b52f3 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -96,7 +96,7 @@ sub do_slice { Slic3r::GUI->save_settings; my $print = Slic3r::Print->new(config => $config); - $print->add_objects_from_file($input_file); + $print->add_model(Slic3r::Model->read_from_file($input_file)); $print->validate; # select output file diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm index a6560b66e..43d014490 100644 --- a/lib/Slic3r/Layer.pm +++ b/lib/Slic3r/Layer.pm @@ -1,60 +1,25 @@ package Slic3r::Layer; use Moo; -use Math::Clipper ':all'; -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'; +use Slic3r::Geometry::Clipper qw(union_ex); -# a sequential number of layer, starting at 0 -has 'id' => ( - is => 'rw', - #isa => 'Int', - required => 1, -); +has 'id' => (is => 'rw', required => 1); # sequential number of layer, 0-based +has 'materials' => (is => 'ro', default => sub { [] }); +has 'slicing_errors' => (is => 'rw'); -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'); -has 'print_z' => (is => 'lazy'); -has 'height' => (is => 'lazy'); -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'); +# collection of surfaces generated by slicing the original geometry; +# also known as 'islands' (all materials are merged here) +has 'slices' => (is => 'rw'); # ordered collection of extrusion paths to fill surfaces for support material -has 'support_fills' => (is => 'rw'); - -# ordered collection of extrusion paths to fill surfaces -has 'fills' => (is => 'rw'); +has 'support_fills' => (is => 'rw'); # Z used for slicing sub _build_slice_z { @@ -99,473 +64,36 @@ sub _build_infill_flow { : $Slic3r::infill_flow; } -# build polylines from lines -sub make_surfaces { +sub material { my $self = shift; - my ($loops) = @_; + my ($material_idx) = @_; - { - 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} ], + for my $i (grep !defined $self->materials->[$_], 0..$material_idx) { + $self->materials->[$i] = Slic3r::Layer::Material->new( + layer => $self, ); } + 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 { my $self = shift; Slic3r::debugf "Making perimeters for layer %d\n", $self->id; - - 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; - } - } + $_->make_perimeters for @{$self->materials}; } 1; diff --git a/lib/Slic3r/Layer/Material.pm b/lib/Slic3r/Layer/Material.pm new file mode 100644 index 000000000..746d8c320 --- /dev/null +++ b/lib/Slic3r/Layer/Material.pm @@ -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; diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index aba13f230..9b9407c96 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -15,6 +15,7 @@ sub read_from_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"; + $_->input_file($input_file) for @{$model->objects}; return $model; } @@ -26,6 +27,16 @@ sub add_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 sub mesh { my $self = shift; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index d98a95e03..c131dbc4a 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -16,6 +16,8 @@ has 'objects' => (is => 'rw', default => sub {[]}); has 'total_extrusion_length' => (is => 'rw'); has 'processing_time' => (is => 'rw', required => 0); +has 'materials_count' => (is => 'rw', default => sub {1}); + # ordered collection of extrusion paths to build skirt loops has 'skirt' => ( is => 'rw', @@ -82,59 +84,52 @@ sub _trigger_config { $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 { my $self = shift; my ($model) = @_; - my @print_objects = (); + # update materials count + $self->materials_count(max($self->materials_count, scalar keys %{$model->materials})); + foreach my $object (@{ $model->objects }) { - my $mesh = $object->volumes->[0]->mesh; - $mesh->check_manifoldness; + my @meshes = (); # by material_id - if ($object->instances) { - # we ignore the per-instance rotation currently and only - # consider the first one - $mesh->rotate($object->instances->[0]->rotation); + foreach my $volume (@{$object->volumes}) { + # should the object contain multiple volumes of the same material, merge them + my $material_id = $volume->material_id // 0; #/ + $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) { # replace the default [0,0] instance with the custom ones @{$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 { @@ -147,7 +142,7 @@ sub validate { for my $obj_idx (0 .. $#{$self->objects}) { 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)); $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; 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"; } } @@ -283,9 +279,12 @@ sub export_gcode { $status_cb->(20, "Generating perimeters"); $_->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) - 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 # and split them in top/bottom/internal surfaces; @@ -294,12 +293,12 @@ sub export_gcode { # decide what surfaces are to be filled $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 # and rearrange top/bottom/internal surfaces $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 # they will be split in internal and internal-solid surfaces @@ -307,7 +306,7 @@ sub export_gcode { $_->discover_horizontal_shells for @{$self->objects}; # 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 $status_cb->(70, "Combining infill"); @@ -317,35 +316,45 @@ sub export_gcode { $status_cb->(80, "Infilling layers"); { 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( - 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 { my $q = shift; $Slic3r::Geometry::Clipper::clipper = Math::Clipper->new; my $fills = {}; 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}{$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; }, collect_cb => sub { my $fills = shift; foreach my $obj_idx (keys %$fills) { + my $object = $self->objects->[$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 { - foreach my $layer (map @{$_->layers}, @{$self->objects}) { - $layer->fills([ $fill_maker->make_fill($layer) ]); + foreach my $layerm (map @{$_->materials}, map @{$_->layers}, @{$self->objects}) { + $layerm->fills([ $fill_maker->make_fill($layerm) ]); } }, ); @@ -354,11 +363,11 @@ sub export_gcode { # generate support material if ($Slic3r::Config->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) - $_->fill_surfaces(undef) for map @{$_->layers}, @{$self->objects}; + $_->fill_surfaces(undef) for map @{$_->materials}, map @{$_->layers}, @{$self->objects}; # make 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 @layer_points = ( (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), ); 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 @object_islands = ( (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), ); 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_y($shift[Y] + unscale $copy->[Y]); - # extrude perimeters - $gcode .= $gcodegen->set_tool($Slic3r::Config->perimeter_extruder-1); - $gcode .= $gcodegen->extrude($_, 'perimeter') for @{ $layer->perimeters }; - - # extrude fills - $gcode .= $gcodegen->set_tool($Slic3r::Config->infill_extruder-1); - $gcode .= $gcodegen->set_acceleration($Slic3r::Config->infill_acceleration); - for my $fill (@{ $layer->fills }) { - if ($fill->isa('Slic3r::ExtrusionPath::Collection')) { - $gcode .= $gcodegen->extrude($_, 'fill') - for $fill->shortest_path($gcodegen->last_pos); - } else { - $gcode .= $gcodegen->extrude($fill, 'fill') ; + foreach my $material_id (0 .. ($self->materials_count-1)) { + my $layerm = $layer->materials->[$material_id]; + + # extrude perimeters + $gcode .= $gcodegen->set_tool($Slic3r::Config->perimeter_extruder-1); + $gcode .= $gcodegen->extrude($_, 'perimeter') for @{ $layerm->perimeters }; + + # extrude fills + $gcode .= $gcodegen->set_tool($Slic3r::Config->infill_extruder-1); + $gcode .= $gcodegen->set_acceleration($Slic3r::Config->infill_acceleration); + for my $fill (@{ $layerm->fills }) { + if ($fill->isa('Slic3r::ExtrusionPath::Collection')) { + $gcode .= $gcodegen->extrude($_, 'fill') + for $fill->shortest_path($gcodegen->last_pos); + } else { + $gcode .= $gcodegen->extrude($fill, 'fill') ; + } } } - + # extrude support material if ($layer->support_fills) { $gcode .= $gcodegen->set_tool($Slic3r::Config->support_material_extruder-1); diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index cfe2658dd..c1319fd45 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -6,17 +6,12 @@ use Slic3r::Geometry qw(scale unscale deg2rad); use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex union_ex); use Slic3r::Surface ':types'; +has 'print' => (is => 'ro', weak_ref => 1, required => 1); 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 'copies' => (is => 'rw', default => sub {[ [0,0] ]}); - -has 'layers' => ( - traits => ['Array'], - is => 'rw', - #isa => 'ArrayRef[Slic3r::Layer]', - default => sub { [] }, -); +has 'layers' => (is => 'rw', default => sub { [] }); sub layer_count { my $self = shift; @@ -43,22 +38,24 @@ sub slice { my %params = @_; # process facets - { + for my $material_id (0 .. $#{$self->meshes}) { + my $mesh = $self->meshes->[$material_id]; # ignore undef meshes + my $apply_lines = sub { my $lines = shift; foreach my $layer_id (keys %$lines) { - my $layer = $self->layer($layer_id); - push @{$layer->lines}, @{$lines->{$layer_id}}; + my $layerm = $self->layer($layer_id)->material($material_id); + push @{$layerm->lines}, @{$lines->{$layer_id}}; } }; Slic3r::parallelize( - disable => ($#{$self->mesh->facets} < 500), # don't parallelize when too few facets - items => [ 0..$#{$self->mesh->facets} ], + disable => ($#{$mesh->facets} < 500), # don't parallelize when too few facets + items => [ 0..$#{$mesh->facets} ], thread_cb => sub { my $q = shift; my $result_lines = {}; 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) { $result_lines->{$layer_id} ||= []; push @{ $result_lines->{$layer_id} }, @{ $lines->{$layer_id} }; @@ -70,8 +67,8 @@ sub slice { $apply_lines->($_[0]); }, no_threads_cb => sub { - for (0..$#{$self->mesh->facets}) { - my $lines = $self->mesh->slice_facet($self, $_); + for (0..$#{$mesh->facets}) { + my $lines = $mesh->slice_facet($self, $_); $apply_lines->($lines); } }, @@ -80,11 +77,11 @@ sub slice { die "Invalid input file\n" if !@{$self->layers}; # free memory - $self->mesh(undef) unless $params{keep_meshes}; + $self->meshes(undef) unless $params{keep_meshes}; # remove last layer if empty - # (we might have created it because of the $max_layer = ... + 1 code below) - pop @{$self->layers} if !@{$self->layers->[-1]->lines}; + # (we might have created it because of the $max_layer = ... + 1 code in TriangleMesh) + pop @{$self->layers} if !map @{$_->lines}, @{$self->layers->[-1]->materials}; foreach my $layer (@{ $self->layers }) { Slic3r::debugf "Making surfaces for layer %d (slice z = %f):\n", @@ -98,10 +95,17 @@ sub slice { # inside a closed polyline) # 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 - $layer->lines(undef); + # merge all materials' slices to get islands + $layer->make_slices; } # detect slicing errors @@ -119,31 +123,38 @@ sub slice { # neighbor layers Slic3r::debugf "Attempting to repair layer %d\n", $i; - my (@upper_surfaces, @lower_surfaces); - for (my $j = $i+1; $j <= $#{$self->layers}; $j++) { - if (!$self->layers->[$j]->slicing_errors) { - @upper_surfaces = @{$self->layers->[$j]->slices}; - last; + foreach my $material_id (0 .. $#{$layer->materials}) { + my $layerm = $layer->material($material_id); + + my (@upper_surfaces, @lower_surfaces); + 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--) { - if (!$self->layers->[$j]->slicing_errors) { - @lower_surfaces = @{$self->layers->[$j]->slices}; - last; + for (my $j = $i-1; $j >= 0; $j--) { + if (!$self->layers->[$j]->slicing_errors) { + @lower_surfaces = @{$self->layers->[$j]->material($material_id)->slices}; + 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([ - map $_->expolygon->contour, @upper_surfaces, @lower_surfaces, - ]); - 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; + + # update layer slices after repairing the single materials + $layer->make_slices; } # remove empty layers from bottom @@ -171,53 +182,57 @@ sub make_perimeters { # this algorithm makes sure that almost one perimeter is overlapping if ($Slic3r::Config->extra_perimeters && $Slic3r::Config->perimeters > 0) { - for my $layer_id (0 .. $self->layer_count-2) { - my $layer = $self->layers->[$layer_id]; - my $upper_layer = $self->layers->[$layer_id+1]; - - my $overlap = $layer->perimeter_flow->spacing; # one perimeter - - # compute polygons representing the thickness of the first external perimeter of - # the upper layer slices - my $upper = diff_ex( - [ map @$_, map $_->expolygon->offset_ex(+ 0.5 * scale $layer->perimeter_flow->spacing), @{$upper_layer->slices} ], - [ map @$_, map $_->expolygon->offset_ex(- scale($overlap) + (0.5 * scale $layer->perimeter_flow->spacing)), @{$upper_layer->slices} ], - ); - next if !@$upper; - - # 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} ], + for my $material_id (0 .. ($self->print->materials_count-1)) { + for my $layer_id (0 .. $self->layer_count-2) { + my $layerm = $self->layers->[$layer_id]->materials->[$material_id]; + my $upper_layerm = $self->layers->[$layer_id+1]->materials->[$material_id]; + my $perimeter_flow_spacing = $layerm->perimeter_flow->spacing; + my $scaled_perimeter_flow_spacing = scale $perimeter_flow_spacing; + + my $overlap = $perimeter_flow_spacing; # one perimeter + + # compute polygons representing the thickness of the first external perimeter of + # the upper layer slices + my $upper = diff_ex( + [ 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} ], ); - $ignore = [ map @$_, map $_->offset_ex(scale $layer->perimeter_flow->spacing), @$diff ]; - } - - foreach my $slice (@{$layer->slices}) { - 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) * scale $layer->perimeter_flow->spacing) ]; - 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; - $hypothetical_perimeter = diff_ex($outer, $inner); + next if !@$upper; + + # 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) * $scaled_perimeter_flow_spacing), @{$layerm->slices} ], + [ map @{$_->expolygon}, @{$upper_layerm->slices} ], + ); + $ignore = [ map @$_, map $_->offset_ex($scaled_perimeter_flow_spacing), @$diff ]; + } + + foreach my $slice (@{$layerm->slices}) { + 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 my $surface_difference = sub { - my ($subject_surfaces, $clip_surfaces, $result_type, $layer) = @_; + my ($subject_surfaces, $clip_surfaces, $result_type, $layerm) = @_; my $expolygons = diff_ex( [ map { ref $_ eq 'ARRAY' ? $_ : ref $_ eq 'Slic3r::ExPolygon' ? @$_ : $_->p } @$subject_surfaces ], [ map { ref $_ eq 'ARRAY' ? $_ : ref $_ eq 'Slic3r::ExPolygon' ? @$_ : $_->p } @$clip_surfaces ], 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), @$expolygons; }; - for (my $i = 0; $i < $self->layer_count; $i++) { - my $layer = $self->layers->[$i]; - my $upper_layer = $self->layers->[$i+1]; - my $lower_layer = $i > 0 ? $self->layers->[$i-1] : undef; - - my (@bottom, @top, @internal) = (); - - # find top surfaces (difference between current surfaces - # of current layer and upper one) - if ($upper_layer) { - @top = $surface_difference->($layer->slices, $upper_layer->slices, S_TYPE_TOP, $layer); - } else { - # if no upper layer, all surfaces of this one are solid - @top = @{$layer->slices}; - $_->surface_type(S_TYPE_TOP) for @top; + for my $material_id (0 .. ($self->print->materials_count-1)) { + for (my $i = 0; $i < $self->layer_count; $i++) { + my $layerm = $self->layers->[$i]->materials->[$material_id]; + + # comparison happens against the *full* slices (considering all materials) + my $upper_layer = $self->layers->[$i+1]; + my $lower_layer = $i > 0 ? $self->layers->[$i-1] : undef; + + my (@bottom, @top, @internal) = (); + + # find top surfaces (difference between current surfaces + # of current layer and upper one) + if ($upper_layer) { + @top = $surface_difference->($layerm->slices, $upper_layer->slices, S_TYPE_TOP, $layerm); + } 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 - # of current layer and lower one) - if ($lower_layer) { - @bottom = $surface_difference->($layer->slices, $lower_layer->slices, S_TYPE_BOTTOM, $layer); - } else { - # if no lower layer, all surfaces of this one are solid - @bottom = @{$layer->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", $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; + # clip surfaces to the fill boundaries + foreach my $layer (@{$self->layers}) { + my $layerm = $layer->materials->[$material_id]; + my $fill_boundaries = [ map @$_, @{$layerm->surfaces} ]; + @{$layerm->surfaces} = (); + foreach my $surface (@{$layerm->slices}) { + my $intersection = intersection_ex( + [ $surface->p ], + $fill_boundaries, + ); + push @{$layerm->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; - for (my $i = 0; $i < $self->layer_count; $i++) { - my $layer = $self->layers->[$i]; - foreach my $type (S_TYPE_TOP, S_TYPE_BOTTOM) { - # find surfaces of current type for current layer - # and offset them to take perimeters into account - my @surfaces = map $_->offset($Slic3r::Config->perimeters * scale $layer->perimeter_flow->width), - grep $_->surface_type == $type, @{$layer->fill_surfaces} or next; - my $surfaces_p = [ map $_->p, @surfaces ]; - 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++) { + for my $material_id (0 .. ($self->print->materials_count-1)) { + for (my $i = 0; $i < $self->layer_count; $i++) { + my $layerm = $self->layers->[$i]->materials->[$material_id]; + foreach my $type (S_TYPE_TOP, S_TYPE_BOTTOM) { + # find surfaces of current type for current layer + # and offset them to take perimeters into account + my @surfaces = map $_->offset($Slic3r::Config->perimeters * scale $layerm->perimeter_flow->width), + grep $_->surface_type == $type, @{$layerm->fill_surfaces} or next; + my $surfaces_p = [ map $_->p, @surfaces ]; + Slic3r::debugf "Layer %d has %d surfaces of type '%s'\n", + $i, scalar(@surfaces), ($type == S_TYPE_TOP ? 'top' : 'bottom'); - next if $n < 0 || $n >= $self->layer_count; - Slic3r::debugf " looking for neighbors on layer %d...\n", $n; - - my @neighbor_surfaces = @{$self->layers->[$n]->surfaces}; - my @neighbor_fill_surfaces = @{$self->layers->[$n]->fill_surfaces}; - - # find intersection between neighbor and current layer's surfaces - # intersections have contours and holes - my $new_internal_solid = intersection_ex( - $surfaces_p, - [ map $_->p, grep { $_->surface_type == S_TYPE_INTERNAL || $_->surface_type == S_TYPE_INTERNALSOLID } @neighbor_surfaces ], - undef, 1, - ); - next if !@$new_internal_solid; - - # internal-solid are the union of the existing internal-solid surfaces - # and new ones - my $internal_solid = union_ex([ - ( map $_->p, grep $_->surface_type == S_TYPE_INTERNALSOLID, @neighbor_fill_surfaces ), - ( map @$_, @$new_internal_solid ), - ]); - - # subtract intersections from layer surfaces to get resulting inner surfaces - my $internal = diff_ex( - [ map $_->p, grep $_->surface_type == S_TYPE_INTERNAL, @neighbor_fill_surfaces ], - [ map @$_, @$internal_solid ], - 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]->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 ], + 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; + Slic3r::debugf " looking for neighbors on layer %d...\n", $n; + + my @neighbor_surfaces = @{$self->layers->[$n]->materials->[$material_id]->surfaces}; + my @neighbor_fill_surfaces = @{$self->layers->[$n]->materials->[$material_id]->fill_surfaces}; + + # find intersection between neighbor and current layer's surfaces + # intersections have contours and holes + my $new_internal_solid = intersection_ex( + $surfaces_p, + [ map $_->p, grep { $_->surface_type == S_TYPE_INTERNAL || $_->surface_type == S_TYPE_INTERNALSOLID } @neighbor_surfaces ], + undef, 1, + ); + next if !@$new_internal_solid; + + # internal-solid are the union of the existing internal-solid surfaces + # and new ones + my $internal_solid = union_ex([ + ( map $_->p, grep $_->surface_type == S_TYPE_INTERNALSOLID, @neighbor_fill_surfaces ), + ( map @$_, @$new_internal_solid ), + ]); + + # subtract intersections from layer surfaces to get resulting inner surfaces + my $internal = diff_ex( + [ map $_->p, grep $_->surface_type == S_TYPE_INTERNAL, @neighbor_fill_surfaces ], + [ map @$_, @$internal_solid ], 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 - (expolygon => $_, surface_type => $s->[0]->surface_type, bridge_angle => $s->[0]->bridge_angle) - for @$solid_surfaces; + (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, + ); + 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; - # start from bottom, skip first layer - for (my $i = 1; $i < $self->layer_count; $i++) { - my $layer = $self->layer($i); - - # 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); + for my $material_id (0 .. ($self->print->materials_count-1)) { + # start from bottom, skip first layer + for (my $i = 1; $i < $self->layer_count; $i++) { + my $layerm = $self->layers->[$i]->materials->[$material_id]; - # 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_layer->fill_surfaces}; - next if !@lower_surfaces; + # skip layer if no internal fill surfaces + next if !grep $_->surface_type == S_TYPE_INTERNAL, @{$layerm->fill_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, @{$layer->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, @{$layer->fill_surfaces}; - push @new_surfaces, map Slic3r::Surface->new - (expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $d + 1), @$intersection; + # 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_layerm = $self->layer($i - 1)->materials->[$material_id]; - 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 - (expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $depth), - - # difference between our internal layers with depth == $depth - # and the intersection found - @{diff_ex( - [ - map $_->p, grep $_->surface_type == S_TYPE_INTERNAL && $_->depth_layers == $depth, - @{$layer->fill_surfaces}, - ], - [ map @$_, @$intersection ], - 1, - )}; + (expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $d + 1), @$intersection; + + foreach my $depth (reverse $d..$Slic3r::Config->infill_every_layers) { + push @new_surfaces, map Slic3r::Surface->new + (expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $depth), + + # difference between our internal layers with depth == $depth + # and the intersection found + @{diff_ex( + [ + 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 - { - my @new_surfaces = (); - push @new_surfaces, grep $_->surface_type != S_TYPE_INTERNAL, @{$lower_layer->fill_surfaces}; - foreach my $depth (1..$Slic3r::Config->infill_every_layers) { - 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 - @{diff_ex( - [ - map $_->p, grep $_->surface_type == S_TYPE_INTERNAL && $_->depth_layers == $depth, - @{$lower_layer->fill_surfaces}, - ], - [ map @$_, @$intersection ], - 1, - )}; + + # now we remove the intersections from lower layer + { + my @new_surfaces = (); + push @new_surfaces, grep $_->surface_type != S_TYPE_INTERNAL, @{$lower_layerm->fill_surfaces}; + foreach my $depth (1..$Slic3r::Config->infill_every_layers) { + 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 + @{diff_ex( + [ + map $_->p, grep $_->surface_type == S_TYPE_INTERNAL && $_->depth_layers == $depth, + @{$lower_layerm->fill_surfaces}, + ], + [ map @$_, @$intersection ], + 1, + )}; + } + @{$lower_layerm->fill_surfaces} = @new_surfaces; } - @{$lower_layer->fill_surfaces} = @new_surfaces; } - - } } } sub generate_support_material { my $self = shift; - my %params = @_; 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)); @@ -537,7 +558,7 @@ sub generate_support_material { my @support_material_areas = map $_->offset_ex(- 0.5 * scale $Slic3r::support_material_flow->width), @{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); $filler->angle($Slic3r::Config->support_material_angle); { diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm index 9818196db..770a2a8c4 100644 --- a/lib/Slic3r/TriangleMesh.pm +++ b/lib/Slic3r/TriangleMesh.pm @@ -167,9 +167,8 @@ sub unpack_line { } sub make_loops { - my ($layer) = @_; - - my @lines = map unpack_line($_), @{$layer->lines}; + my ($lines) = @_; + my @lines = map unpack_line($_), @$lines; # remove tangent edges { @@ -258,6 +257,7 @@ sub make_loops { (0..$#lines); my (@polygons, @failed_loops, %visited_lines) = (); + my $slicing_errors = 0; CYCLE: for (my $i = 0; $i <= $#lines; $i++) { my $line = $lines[$i]; next if $visited_lines{$line}; @@ -272,24 +272,24 @@ sub make_loops { $next_line = $lines[$by_a_id{$line->[I_B_ID]}]; } else { 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; next CYCLE; } if (!$next_line || $visited_lines{$next_line}) { Slic3r::debugf " failed to close this loop\n"; - $layer->slicing_errors(1); + $slicing_errors = 1; push @failed_loops, [@points] if @points; next CYCLE; } 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"; - $layer->slicing_errors(1); + $slicing_errors = 1; push @failed_loops, [@points] if @points; next CYCLE; } elsif (defined $next_line->[I_A_ID] && $next_line->[I_A_ID] != $line->[I_B_ID]) { Slic3r::debugf " wrong a_id\n"; - $layer->slicing_errors(1); + $slicing_errors = 1; push @failed_loops, [@points] if @points; next CYCLE; } @@ -313,7 +313,7 @@ sub make_loops { if $Slic3r::debug; } - return [@polygons]; + return ($slicing_errors, [@polygons]); } sub rotate { diff --git a/slic3r.pl b/slic3r.pl index e5c63b99e..7fe0aa0a0 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -88,9 +88,9 @@ if (@ARGV) { # slicing from command line while (my $input_file = shift @ARGV) { 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}) { - $print->add_objects_from_file($_) for splice @ARGV, 0; + $print->add_model(Slic3r::Model->read_from_file($_)) for splice @ARGV, 0; } $print->duplicate; $print->arrange_objects if @{$print->objects} > 1; diff --git a/utils/stl-to-amf.pl b/utils/stl-to-amf.pl index 78e5989b4..e6fc1fa8d 100755 --- a/utils/stl-to-amf.pl +++ b/utils/stl-to-amf.pl @@ -44,7 +44,7 @@ my %opt = (); } @{ $model->objects->[0]->volumes->[0]->facets }; 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( material_id => $material_id, facets => [@new_facets],