diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index e052240a1..bcd337dec 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -54,7 +54,6 @@ use Slic3r::GCode::VibrationLimit; use Slic3r::Geometry qw(PI); use Slic3r::Geometry::Clipper; use Slic3r::Layer; -use Slic3r::Layer::PerimeterGenerator; use Slic3r::Layer::Region; use Slic3r::Line; use Slic3r::Model; @@ -79,7 +78,6 @@ use constant SCALED_RESOLUTION => RESOLUTION / SCALING_FACTOR; use constant LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER => 0.15; use constant INFILL_OVERLAP_OVER_SPACING => 0.3; use constant EXTERNAL_INFILL_MARGIN => 3; -use constant INSET_OVERLAP_TOLERANCE => 0.4; # keep track of threads we created my @my_threads = (); @@ -201,6 +199,7 @@ sub thread_cleanup { *Slic3r::Geometry::BoundingBox::DESTROY = sub {}; *Slic3r::Geometry::BoundingBoxf::DESTROY = sub {}; *Slic3r::Geometry::BoundingBoxf3::DESTROY = sub {}; + *Slic3r::Layer::PerimeterGenerator::DESTROY = sub {}; *Slic3r::Line::DESTROY = sub {}; *Slic3r::Linef3::DESTROY = sub {}; *Slic3r::Model::DESTROY = sub {}; diff --git a/lib/Slic3r/Layer/PerimeterGenerator.pm b/lib/Slic3r/Layer/PerimeterGenerator.pm deleted file mode 100644 index daf2ba8f8..000000000 --- a/lib/Slic3r/Layer/PerimeterGenerator.pm +++ /dev/null @@ -1,533 +0,0 @@ -package Slic3r::Layer::PerimeterGenerator; -use Moo; - -use Slic3r::ExtrusionLoop ':roles'; -use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Geometry qw(scale unscale chained_path); -use Slic3r::Geometry::Clipper qw(union_ex diff diff_ex intersection_ex offset offset2 - offset_ex offset2_ex intersection_ppl diff_ppl); -use Slic3r::Surface ':types'; - -has 'slices' => (is => 'ro', required => 1); # SurfaceCollection -has 'lower_slices' => (is => 'ro', required => 0); -has 'layer_height' => (is => 'ro', required => 1); -has 'layer_id' => (is => 'ro', required => 0, default => sub { -1 }); -has 'perimeter_flow' => (is => 'ro', required => 1); -has 'ext_perimeter_flow' => (is => 'ro', required => 1); -has 'overhang_flow' => (is => 'ro', required => 1); -has 'solid_infill_flow' => (is => 'ro', required => 1); -has 'config' => (is => 'ro', default => sub { Slic3r::Config::PrintRegion->new }); -has 'object_config' => (is => 'ro', default => sub { Slic3r::Config::PrintObject->new }); -has 'print_config' => (is => 'ro', default => sub { Slic3r::Config::Print->new }); -has '_lower_slices_p' => (is => 'rw', default => sub { [] }); -has '_holes_pt' => (is => 'rw'); -has '_ext_mm3_per_mm' => (is => 'rw'); -has '_mm3_per_mm' => (is => 'rw'); -has '_mm3_per_mm_overhang' => (is => 'rw'); - -# generated loops will be put here -has 'loops' => (is => 'ro', default => sub { Slic3r::ExtrusionPath::Collection->new }); - -# generated gap fills will be put here -has 'gap_fill' => (is => 'ro', default => sub { Slic3r::ExtrusionPath::Collection->new }); - -# generated fill surfaces will be put here -has 'fill_surfaces' => (is => 'ro', default => sub { Slic3r::Surface::Collection->new }); - -sub BUILDARGS { - my ($class, %args) = @_; - - if (my $flow = delete $args{flow}) { - $args{perimeter_flow} //= $flow; - $args{ext_perimeter_flow} //= $flow; - $args{overhang_flow} //= $flow; - $args{solid_infill_flow} //= $flow; - } - - return { %args }; -} - -sub process { - my ($self) = @_; - - # other perimeters - $self->_mm3_per_mm($self->perimeter_flow->mm3_per_mm); - my $pwidth = $self->perimeter_flow->scaled_width; - my $pspacing = $self->perimeter_flow->scaled_spacing; - - # external perimeters - $self->_ext_mm3_per_mm($self->ext_perimeter_flow->mm3_per_mm); - my $ext_pwidth = $self->ext_perimeter_flow->scaled_width; - my $ext_pspacing = scale($self->ext_perimeter_flow->spacing_to($self->perimeter_flow)); - - # overhang perimeters - $self->_mm3_per_mm_overhang($self->overhang_flow->mm3_per_mm); - - # solid infill - my $ispacing = $self->solid_infill_flow->scaled_spacing; - my $gap_area_threshold = $pwidth ** 2; - - # Calculate the minimum required spacing between two adjacent traces. - # This should be equal to the nominal flow spacing but we experiment - # with some tolerance in order to avoid triggering medial axis when - # some squishing might work. Loops are still spaced by the entire - # flow spacing; this only applies to collapsing parts. - my $min_spacing = $pspacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE); - my $ext_min_spacing = $ext_pspacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE); - - # prepare grown lower layer slices for overhang detection - if ($self->lower_slices && $self->config->overhangs) { - # We consider overhang any part where the entire nozzle diameter is not supported by the - # lower layer, so we take lower slices and offset them by half the nozzle diameter used - # in the current layer - my $nozzle_diameter = $self->print_config->get_at('nozzle_diameter', $self->config->perimeter_extruder-1); - - $self->_lower_slices_p( - offset([ map @$_, @{$self->lower_slices} ], scale +$nozzle_diameter/2) - ); - } - - # we need to process each island separately because we might have different - # extra perimeters for each one - foreach my $surface (@{$self->slices}) { - # detect how many perimeters must be generated for this island - my $loop_number = $self->config->perimeters + ($surface->extra_perimeters || 0); - $loop_number--; # 0-indexed loops - - my @gaps = (); # Polygons - - my @last = @{$surface->expolygon->simplify_p(&Slic3r::SCALED_RESOLUTION)}; - if ($loop_number >= 0) { # no loops = -1 - - my @contours = (); # depth => [ Polygon, Polygon ... ] - my @holes = (); # depth => [ Polygon, Polygon ... ] - my @thin_walls = (); # Polylines - - # we loop one time more than needed in order to find gaps after the last perimeter was applied - for my $i (0..($loop_number+1)) { # outer loop is 0 - my @offsets = (); - if ($i == 0) { - # the minimum thickness of a single loop is: - # ext_width/2 + ext_spacing/2 + spacing/2 + width/2 - if ($self->config->thin_walls) { - @offsets = @{offset2( - \@last, - -(0.5*$ext_pwidth + 0.5*$ext_min_spacing - 1), - +(0.5*$ext_min_spacing - 1), - )}; - } else { - @offsets = @{offset( - \@last, - -0.5*$ext_pwidth, - )}; - } - - # look for thin walls - if ($self->config->thin_walls) { - my $diff = diff( - \@last, - offset(\@offsets, +0.5*$ext_pwidth), - 1, # medial axis requires non-overlapping geometry - ); - - # the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width - # (actually, something larger than that still may exist due to mitering or other causes) - my $min_width = $ext_pwidth / 2; - @thin_walls = @{offset2_ex($diff, -$min_width/2, +$min_width/2)}; - - # the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop - @thin_walls = grep $_->length > $ext_pwidth*2, - map @{$_->medial_axis($ext_pwidth + $ext_pspacing, $min_width)}, @thin_walls; - Slic3r::debugf " %d thin walls detected\n", scalar(@thin_walls) if $Slic3r::debug; - - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output( - "medial_axis.svg", - no_arrows => 1, - #expolygons => \@expp, - polylines => \@thin_walls, - ); - } - } - } else { - my $distance = ($i == 1) ? $ext_pspacing : $pspacing; - - if ($self->config->thin_walls) { - @offsets = @{offset2( - \@last, - -($distance + 0.5*$min_spacing - 1), - +(0.5*$min_spacing - 1), - )}; - } else { - @offsets = @{offset( - \@last, - -$distance, - )}; - } - - # look for gaps - if ($self->config->gap_fill_speed > 0 && $self->config->fill_density > 0) { - # not using safety offset here would "detect" very narrow gaps - # (but still long enough to escape the area threshold) that gap fill - # won't be able to fill but we'd still remove from infill area - my $diff = diff_ex( - offset(\@last, -0.5*$distance), - offset(\@offsets, +0.5*$distance + 10), # safety offset - ); - push @gaps, map $_->clone, map @$_, grep abs($_->area) >= $gap_area_threshold, @$diff; - } - } - - last if !@offsets; - last if $i > $loop_number; # we were only looking for gaps this time - - @last = @offsets; - - $contours[$i] = []; - $holes[$i] = []; - foreach my $polygon (@offsets) { - my $loop = Slic3r::Layer::PerimeterGenerator::Loop->new( - polygon => $polygon, - is_contour => $polygon->is_counter_clockwise, - depth => $i, - ); - if ($loop->is_contour) { - push @{$contours[$i]}, $loop; - } else { - push @{$holes[$i]}, $loop; - } - } - } - - # nest loops: holes first - for my $d (0..$loop_number) { - # loop through all holes having depth $d - LOOP: for (my $i = 0; $i <= $#{$holes[$d]}; ++$i) { - my $loop = $holes[$d][$i]; - - # find the hole loop that contains this one, if any - for my $t (($d+1)..$loop_number) { - for (my $j = 0; $j <= $#{$holes[$t]}; ++$j) { - my $candidate_parent = $holes[$t][$j]; - if ($candidate_parent->polygon->contains_point($loop->polygon->first_point)) { - $candidate_parent->add_child($loop); - splice @{$holes[$d]}, $i, 1; - --$i; - next LOOP; - } - } - } - - # if no hole contains this hole, find the contour loop that contains it - for my $t (reverse 0..$loop_number) { - for (my $j = 0; $j <= $#{$contours[$t]}; ++$j) { - my $candidate_parent = $contours[$t][$j]; - if ($candidate_parent->polygon->contains_point($loop->polygon->first_point)) { - $candidate_parent->add_child($loop); - splice @{$holes[$d]}, $i, 1; - --$i; - next LOOP; - } - } - } - } - } - - # nest contour loops - for my $d (reverse 1..$loop_number) { - # loop through all contours having depth $d - LOOP: for (my $i = 0; $i <= $#{$contours[$d]}; ++$i) { - my $loop = $contours[$d][$i]; - - # find the contour loop that contains it - for my $t (reverse 0..($d-1)) { - for (my $j = 0; $j <= $#{$contours[$t]}; ++$j) { - my $candidate_parent = $contours[$t][$j]; - if ($candidate_parent->polygon->contains_point($loop->polygon->first_point)) { - $candidate_parent->add_child($loop); - splice @{$contours[$d]}, $i, 1; - --$i; - next LOOP; - } - } - } - } - } - - # at this point, all loops should be in $contours[0] - my @entities = $self->_traverse_loops($contours[0], \@thin_walls); - - # if brim will be printed, reverse the order of perimeters so that - # we continue inwards after having finished the brim - # TODO: add test for perimeter order - @entities = reverse @entities - if $self->config->external_perimeters_first - || ($self->layer_id == 0 && $self->print_config->brim_width > 0); - - # append perimeters for this slice as a collection - $self->loops->append(Slic3r::ExtrusionPath::Collection->new(@entities)) - if @entities; - } - - # fill gaps - if (@gaps) { - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output( - "gaps.svg", - expolygons => union_ex(\@gaps), - ); - } - - # where $pwidth < thickness < 2*$pspacing, infill with width = 2*$pwidth - # where 0.1*$pwidth < thickness < $pwidth, infill with width = 1*$pwidth - my @gap_sizes = ( - [ $pwidth, 2*$pspacing, unscale 2*$pwidth ], - [ 0.1*$pwidth, $pwidth, unscale 1*$pwidth ], - ); - foreach my $gap_size (@gap_sizes) { - my @gap_fill = $self->_fill_gaps(@$gap_size, \@gaps); - $self->gap_fill->append($_) for @gap_fill; - - # Make sure we don't infill narrow parts that are already gap-filled - # (we only consider this surface's gaps to reduce the diff() complexity). - # Growing actual extrusions ensures that gaps not filled by medial axis - # are not subtracted from fill surfaces (they might be too short gaps - # that medial axis skips but infill might join with other infill regions - # and use zigzag). - my $w = $gap_size->[2]; - my @filled = map { - @{($_->isa('Slic3r::ExtrusionLoop') ? $_->polygon->split_at_first_point : $_->polyline) - ->grow(scale $w/2)}; - } @gap_fill; - @last = @{diff(\@last, \@filled)}; - @gaps = @{diff(\@gaps, \@filled)}; # prevent more gap fill here - } - } - - # create one more offset to be used as boundary for fill - # we offset by half the perimeter spacing (to get to the actual infill boundary) - # and then we offset back and forth by half the infill spacing to only consider the - # non-collapsing regions - my $inset = 0; - if ($loop_number == 0) { - # one loop - $inset += $ext_pspacing/2; - } elsif ($loop_number > 0) { - # two or more loops - $inset += $pspacing/2; - } - - # only apply infill overlap if we actually have one perimeter - $inset -= $self->config->get_abs_value_over('infill_overlap', $inset + $ispacing/2) - if $inset > 0; - - my $min_perimeter_infill_spacing = $ispacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE); - $self->fill_surfaces->append($_) - for map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL), # use a bogus surface type - @{offset2_ex( - [ map @{$_->simplify_p(&Slic3r::SCALED_RESOLUTION)}, @{union_ex(\@last)} ], - -$inset -$min_perimeter_infill_spacing/2, - +$min_perimeter_infill_spacing/2, - )}; - } -} - -sub _traverse_loops { - my ($self, $loops, $thin_walls) = @_; - - # loops is an arrayref of ::Loop objects - # turn each one into an ExtrusionLoop object - my $coll = Slic3r::ExtrusionPath::Collection->new; - foreach my $loop (@$loops) { - my $is_external = $loop->is_external; - - my ($role, $loop_role); - if ($is_external) { - $role = EXTR_ROLE_EXTERNAL_PERIMETER; - } else { - $role = EXTR_ROLE_PERIMETER; - } - if ($loop->is_internal_contour) { - # Note that we set loop role to ContourInternalPerimeter - # also when loop is both internal and external (i.e. - # there's only one contour loop). - $loop_role = EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER; - } else { - $loop_role = EXTRL_ROLE_DEFAULT; - } - - # detect overhanging/bridging perimeters - my @paths = (); - if ($self->config->overhangs && $self->layer_id > 0 - && !($self->object_config->support_material && $self->object_config->support_material_contact_distance == 0)) { - # get non-overhang paths by intersecting this loop with the grown lower slices - foreach my $polyline (@{ intersection_ppl([ $loop->polygon ], $self->_lower_slices_p) }) { - push @paths, Slic3r::ExtrusionPath->new( - polyline => $polyline, - role => $role, - mm3_per_mm => ($is_external ? $self->_ext_mm3_per_mm : $self->_mm3_per_mm), - width => ($is_external ? $self->ext_perimeter_flow->width : $self->perimeter_flow->width), - height => $self->layer_height, - ); - } - - # get overhang paths by checking what parts of this loop fall - # outside the grown lower slices (thus where the distance between - # the loop centerline and original lower slices is >= half nozzle diameter - foreach my $polyline (@{ diff_ppl([ $loop->polygon ], $self->_lower_slices_p) }) { - push @paths, Slic3r::ExtrusionPath->new( - polyline => $polyline, - role => EXTR_ROLE_OVERHANG_PERIMETER, - mm3_per_mm => $self->_mm3_per_mm_overhang, - width => $self->overhang_flow->width, - height => $self->overhang_flow->height, - ); - } - - # reapply the nearest point search for starting point - # (clone because the collection gets DESTROY'ed) - # We allow polyline reversal because Clipper may have randomly - # reversed polylines during clipping. - my $collection = Slic3r::ExtrusionPath::Collection->new(@paths); # temporary collection - @paths = map $_->clone, @{$collection->chained_path(0)}; - } else { - push @paths, Slic3r::ExtrusionPath->new( - polyline => $loop->polygon->split_at_first_point, - role => $role, - mm3_per_mm => ($is_external ? $self->_ext_mm3_per_mm : $self->_mm3_per_mm), - width => ($is_external ? $self->ext_perimeter_flow->width : $self->perimeter_flow->width), - height => $self->layer_height, - ); - } - my $eloop = Slic3r::ExtrusionLoop->new_from_paths(@paths); - $eloop->role($loop_role); - $coll->append($eloop); - } - - # append thin walls to the nearest-neighbor search (only for first iteration) - if (@$thin_walls) { - foreach my $polyline (@$thin_walls) { - $coll->append(Slic3r::ExtrusionPath->new( - polyline => $polyline, - role => EXTR_ROLE_EXTERNAL_PERIMETER, - mm3_per_mm => $self->_mm3_per_mm, - width => $self->perimeter_flow->width, - height => $self->layer_height, - )); - } - - @$thin_walls = (); - } - - # sort entities - my $sorted_coll = $coll->chained_path_indices(0); - my @indices = @{$sorted_coll->orig_indices}; - - # traverse children - my @entities = (); - for my $i (0..$#indices) { - my $idx = $indices[$i]; - if ($idx > $#$loops) { - # this is a thin wall - # let's get it from the sorted collection as it might have been reversed - push @entities, $sorted_coll->[$i]->clone; - } else { - my $loop = $loops->[$idx]; - my $eloop = $coll->[$idx]->clone; - - my @children = $self->_traverse_loops($loop->children, $thin_walls); - if ($loop->is_contour) { - $eloop->make_counter_clockwise; - push @entities, @children, $eloop; - } else { - $eloop->make_clockwise; - push @entities, $eloop, @children; - } - } - } - return @entities; -} - -sub _fill_gaps { - my ($self, $min, $max, $w, $gaps) = @_; - - $min *= (1 - &Slic3r::INSET_OVERLAP_TOLERANCE); - - my $this = diff_ex( - offset2($gaps, -$min/2, +$min/2), - offset2($gaps, -$max/2, +$max/2), - 1, - ); - - my @polylines = map @{$_->medial_axis($max, $min/2)}, @$this; - return if !@polylines; - - Slic3r::debugf " %d gaps filled with extrusion width = %s\n", scalar @$this, $w - if @$this; - - #my $flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL, 0, $w); - my $flow = Slic3r::Flow->new( - width => $w, - height => $self->layer_height, - nozzle_diameter => $self->solid_infill_flow->nozzle_diameter, - ); - - my %path_args = ( - role => EXTR_ROLE_GAPFILL, - mm3_per_mm => $flow->mm3_per_mm, - width => $flow->width, - height => $self->layer_height, - ); - - my @entities = (); - foreach my $polyline (@polylines) { - #if ($polylines[$i]->isa('Slic3r::Polygon')) { - # my $loop = Slic3r::ExtrusionLoop->new; - # $loop->append(Slic3r::ExtrusionPath->new(polyline => $polylines[$i]->split_at_first_point, %path_args)); - # $polylines[$i] = $loop; - if ($polyline->is_valid && $polyline->first_point->coincides_with($polyline->last_point)) { - # since medial_axis() now returns only Polyline objects, detect loops here - push @entities, my $loop = Slic3r::ExtrusionLoop->new; - $loop->append(Slic3r::ExtrusionPath->new(polyline => $polyline, %path_args)); - } else { - push @entities, Slic3r::ExtrusionPath->new(polyline => $polyline, %path_args); - } - } - - return @entities; -} - - -package Slic3r::Layer::PerimeterGenerator::Loop; -use Moo; - -has 'polygon' => (is => 'ro', required => 1); -has 'is_contour' => (is => 'ro', required => 1); -has 'depth' => (is => 'ro', required => 1); -has 'children' => (is => 'ro', default => sub { [] }); - -use List::Util qw(first); - -sub add_child { - my ($self, $child) = @_; - push @{$self->children}, $child; -} - -sub is_external { - my ($self) = @_; - return $self->depth == 0; -} - -sub is_internal_contour { - my ($self) = @_; - - if ($self->is_contour) { - # an internal contour is a contour containing no other contours - return !defined first { $_->is_contour } @{$self->children}; - } - return 0; -} - -1; diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index f65e90129..51648c3ed 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -33,23 +33,25 @@ sub make_perimeters { my $generator = Slic3r::Layer::PerimeterGenerator->new( # input: - config => $self->config, - object_config => $self->layer->object->config, - print_config => $self->layer->print->config, - layer_height => $self->height, - layer_id => $self->layer->id, - slices => $slices, - lower_slices => defined($self->layer->lower_layer) ? $self->layer->lower_layer->slices : undef, - perimeter_flow => $self->flow(FLOW_ROLE_PERIMETER), - ext_perimeter_flow => $self->flow(FLOW_ROLE_EXTERNAL_PERIMETER), - overhang_flow => $self->region->flow(FLOW_ROLE_PERIMETER, -1, 1, 0, -1, $self->layer->object), - solid_infill_flow => $self->flow(FLOW_ROLE_SOLID_INFILL), + $slices, + $self->height, + $self->flow(FLOW_ROLE_PERIMETER), + $self->config, + $self->layer->object->config, + $self->layer->print->config, # output: - loops => $self->perimeters, - gap_fill => $self->thin_fills, - fill_surfaces => $fill_surfaces, + $self->perimeters, + $self->thin_fills, + $fill_surfaces, ); + $generator->set_lower_slices($self->layer->lower_layer->slices) + if defined($self->layer->lower_layer); + $generator->set_layer_id($self->id); + $generator->set_ext_perimeter_flow($self->flow(FLOW_ROLE_EXTERNAL_PERIMETER)); + $generator->set_overhang_flow($self->region->flow(FLOW_ROLE_PERIMETER, -1, 1, 0, -1, $self->layer->object)); + $generator->set_solid_infill_flow($self->flow(FLOW_ROLE_SOLID_INFILL)); + $generator->process; } diff --git a/t/perimeters.t b/t/perimeters.t index d103eb3f2..1e47b51f0 100644 --- a/t/perimeters.t +++ b/t/perimeters.t @@ -17,6 +17,101 @@ use Slic3r::Geometry::Clipper qw(union_ex diff union offset); use Slic3r::Surface ':types'; use Slic3r::Test; +{ + my $flow = Slic3r::Flow->new( + width => 1, + height => 1, + nozzle_diameter => 1, + ); + + my $config = Slic3r::Config->new; + my $test = sub { + my ($expolygons, %expected) = @_; + + my $slices = Slic3r::Surface::Collection->new; + $slices->append(Slic3r::Surface->new( + surface_type => S_TYPE_INTERNAL, + expolygon => $_, + )) for @$expolygons; + + my ($region_config, $object_config, $print_config, $loops, $gap_fill, $fill_surfaces); + my $g = Slic3r::Layer::PerimeterGenerator->new( + # input: + $slices, + 1, # layer height + $flow, + ($region_config = Slic3r::Config::PrintRegion->new), + ($object_config = Slic3r::Config::PrintObject->new), + ($print_config = Slic3r::Config::Print->new), + + # output: + ($loops = Slic3r::ExtrusionPath::Collection->new), + ($gap_fill = Slic3r::ExtrusionPath::Collection->new), + ($fill_surfaces = Slic3r::Surface::Collection->new), + ); + $g->config->apply_dynamic($config); + $g->process; + + is scalar(@$loops), + scalar(@$expolygons), 'expected number of collections'; + ok !defined(first { !$_->isa('Slic3r::ExtrusionPath::Collection') } @$loops), + 'everything is returned as collections'; + + my $flattened_loops = $loops->flatten; + my @loops = @$flattened_loops; + is scalar(@loops), + $expected{total}, 'expected number of loops'; + is scalar(grep $_->role == EXTR_ROLE_EXTERNAL_PERIMETER, map @$_, @loops), + $expected{external}, 'expected number of external loops'; + is scalar(grep $_->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER, @loops), + $expected{cinternal}, 'expected number of internal contour loops'; + is scalar(grep $_->polygon->is_counter_clockwise, @loops), + $expected{ccw}, 'expected number of ccw loops'; + }; + + $config->set('perimeters', 3); + $test->( + [ + Slic3r::ExPolygon->new( + Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]), + ), + ], + total => 3, + external => 1, + cinternal => 1, + ccw => 3, + ); + $test->( + [ + Slic3r::ExPolygon->new( + Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]), + Slic3r::Polygon->new_scale([40,40], [40,60], [60,60], [60,40]), + ), + ], + total => 6, + external => 2, + cinternal => 1, + ccw => 3, + ); + $test->( + [ + Slic3r::ExPolygon->new( + Slic3r::Polygon->new_scale([0,0], [200,0], [200,200], [0,200]), + Slic3r::Polygon->new_scale([20,20], [20,180], [180,180], [180,20]), + ), + # nested: + Slic3r::ExPolygon->new( + Slic3r::Polygon->new_scale([50,50], [150,50], [150,150], [50,150]), + Slic3r::Polygon->new_scale([80,80], [80,120], [120,120], [120,80]), + ), + ], + total => 4*3, + external => 4, + cinternal => 2, + ccw => 2*3, + ); +} + { my $config = Slic3r::Config->new_from_defaults; $config->set('skirts', 0); @@ -215,7 +310,7 @@ use Slic3r::Test; [ map @$_, (@$covered_by_perimeters, @$covered_by_infill) ], ); - if (0) { + if (1) { printf "max non covered = %f\n", List::Util::max(map unscale unscale $_->area, @$non_covered); require "Slic3r/SVG.pm"; Slic3r::SVG::output( @@ -223,9 +318,12 @@ use Slic3r::Test; expolygons => [ map $_->expolygon, @{$layerm->slices} ], red_expolygons => union_ex([ map @$_, (@$covered_by_perimeters, @$covered_by_infill) ]), green_expolygons => union_ex($non_covered), + no_arrows => 1, + polylines => [ + map $_->polygon->split_at_first_point, map @$_, @{$layerm->perimeters}, + ], ); } - ok !(defined first { $_->area > ($iflow->scaled_width**2) } @$non_covered), 'no gap between perimeters and infill'; } @@ -299,92 +397,4 @@ use Slic3r::Test; $test->('small_dorito'); } -{ - my $flow = Slic3r::Flow->new( - width => 1, - height => 1, - nozzle_diameter => 1, - ); - - my $config = Slic3r::Config->new; - my $test = sub { - my ($expolygons, %expected) = @_; - - my $slices = Slic3r::Surface::Collection->new; - $slices->append(Slic3r::Surface->new( - surface_type => S_TYPE_INTERNAL, - expolygon => $_, - )) for @$expolygons; - - my $g = Slic3r::Layer::PerimeterGenerator->new( - # input: - layer_height => 1, - slices => $slices, - flow => $flow, - ); - $g->config->apply_dynamic($config); - $g->process; - - is scalar(@{$g->loops}), - scalar(@$expolygons), 'expected number of collections'; - ok !defined(first { !$_->isa('Slic3r::ExtrusionPath::Collection') } @{$g->loops}), - 'everything is returned as collections'; - - my $flattened_loops = $g->loops->flatten; - my @loops = @$flattened_loops; - is scalar(@loops), - $expected{total}, 'expected number of loops'; - is scalar(grep $_->role == EXTR_ROLE_EXTERNAL_PERIMETER, map @$_, @loops), - $expected{external}, 'expected number of external loops'; - is scalar(grep $_->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER, @loops), - $expected{cinternal}, 'expected number of internal contour loops'; - is scalar(grep $_->polygon->is_counter_clockwise, @loops), - $expected{ccw}, 'expected number of ccw loops'; - - return $g; - }; - - $config->set('perimeters', 3); - $test->( - [ - Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]), - ), - ], - total => 3, - external => 1, - cinternal => 1, - ccw => 3, - ); - $test->( - [ - Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]), - Slic3r::Polygon->new_scale([40,40], [40,60], [60,60], [60,40]), - ), - ], - total => 6, - external => 2, - cinternal => 1, - ccw => 3, - ); - $test->( - [ - Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([0,0], [200,0], [200,200], [0,200]), - Slic3r::Polygon->new_scale([20,20], [20,180], [180,180], [180,20]), - ), - # nested: - Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([50,50], [150,50], [150,150], [50,150]), - Slic3r::Polygon->new_scale([80,80], [80,120], [120,120], [120,80]), - ), - ], - total => 4*3, - external => 4, - cinternal => 2, - ccw => 2*3, - ); -} - __END__ diff --git a/xs/MANIFEST b/xs/MANIFEST index 2011b9cf3..36ac111f6 100644 --- a/xs/MANIFEST +++ b/xs/MANIFEST @@ -1690,6 +1690,8 @@ src/libslic3r/MotionPlanner.cpp src/libslic3r/MotionPlanner.hpp src/libslic3r/MultiPoint.cpp src/libslic3r/MultiPoint.hpp +src/libslic3r/PerimeterGenerator.cpp +src/libslic3r/PerimeterGenerator.hpp src/libslic3r/PlaceholderParser.cpp src/libslic3r/PlaceholderParser.hpp src/libslic3r/Point.cpp @@ -1774,6 +1776,7 @@ xsp/Model.xsp xsp/MotionPlanner.xsp xsp/my.map xsp/mytype.map +xsp/PerimeterGenerator.xsp xsp/PlaceholderParser.xsp xsp/Point.xsp xsp/Polygon.xsp diff --git a/xs/src/libslic3r/ClipperUtils.cpp b/xs/src/libslic3r/ClipperUtils.cpp index ba243ee82..baddd8f45 100644 --- a/xs/src/libslic3r/ClipperUtils.cpp +++ b/xs/src/libslic3r/ClipperUtils.cpp @@ -136,6 +136,15 @@ offset(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float d ClipperPaths_to_Slic3rMultiPoints(output, retval); } +Slic3r::Polygons +offset(const Slic3r::Polygons &polygons, const float delta, + double scale, ClipperLib::JoinType joinType, double miterLimit) +{ + Slic3r::Polygons pp; + offset(polygons, &pp, delta, scale, joinType, miterLimit); + return pp; +} + void offset(const Slic3r::Polylines &polylines, ClipperLib::Paths* retval, const float delta, double scale, ClipperLib::JoinType joinType, double miterLimit) @@ -248,6 +257,15 @@ offset2(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float ClipperPaths_to_Slic3rMultiPoints(output, retval); } +Slic3r::Polygons +offset2(const Slic3r::Polygons &polygons, const float delta1, + const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit) +{ + Slic3r::Polygons pp; + offset2(polygons, &pp, delta1, delta2, scale, joinType, miterLimit); + return pp; +} + void offset2(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float delta1, const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit) @@ -260,6 +278,15 @@ offset2(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const floa ClipperPaths_to_Slic3rExPolygons(output, retval); } +Slic3r::ExPolygons +offset2_ex(const Slic3r::Polygons &polygons, const float delta1, + const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit) +{ + Slic3r::ExPolygons expp; + offset2(polygons, &expp, delta1, delta2, scale, joinType, miterLimit); + return expp; +} + template void _clipper_do(const ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, T* retval, const ClipperLib::PolyFillType fillType, const bool safety_offset_) @@ -437,6 +464,22 @@ void diff(const SubjectType &subject, const Slic3r::ExPolygons &clip, ResultType } template void diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, Slic3r::ExPolygons* retval, bool safety_offset_); +Slic3r::Polygons +diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_) +{ + Slic3r::Polygons pp; + diff(subject, clip, &pp, safety_offset_); + return pp; +} + +Slic3r::ExPolygons +diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_) +{ + Slic3r::ExPolygons expp; + diff(subject, clip, &expp, safety_offset_); + return expp; +} + template void intersection(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* retval, bool safety_offset_) { @@ -448,6 +491,22 @@ template void intersection(const Slic3r::Po template void intersection(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_); template void intersection(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, Slic3r::Lines* retval, bool safety_offset_); +Slic3r::Polygons +intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_) +{ + Slic3r::Polygons pp; + intersection(subject, clip, &pp, safety_offset_); + return pp; +} + +Slic3r::Polylines +intersection(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_) +{ + Slic3r::Polylines pp; + intersection(subject, clip, &pp, safety_offset_); + return pp; +} + template bool intersects(const SubjectType &subject, const Slic3r::Polygons &clip, bool safety_offset_) { @@ -474,6 +533,22 @@ void union_(const Slic3r::Polygons &subject, T* retval, bool safety_offset_) template void union_(const Slic3r::Polygons &subject, Slic3r::ExPolygons* retval, bool safety_offset_); template void union_(const Slic3r::Polygons &subject, Slic3r::Polygons* retval, bool safety_offset_); +Slic3r::Polygons +union_(const Slic3r::Polygons &subject, bool safety_offset) +{ + Polygons pp; + union_(subject, &pp, safety_offset); + return pp; +} + +Slic3r::ExPolygons +union_ex(const Slic3r::Polygons &subject, bool safety_offset) +{ + ExPolygons expp; + union_(subject, &expp, safety_offset); + return expp; +} + void union_(const Slic3r::Polygons &subject1, const Slic3r::Polygons &subject2, Slic3r::Polygons* retval, bool safety_offset) { Polygons pp = subject1; diff --git a/xs/src/libslic3r/ClipperUtils.hpp b/xs/src/libslic3r/ClipperUtils.hpp index ab144f202..4a3ba2e5c 100644 --- a/xs/src/libslic3r/ClipperUtils.hpp +++ b/xs/src/libslic3r/ClipperUtils.hpp @@ -40,6 +40,9 @@ void offset(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const f void offset(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float delta, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, + double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, + double miterLimit = 3); // offset Polylines void offset(const Slic3r::Polylines &polylines, ClipperLib::Paths* retval, const float delta, @@ -62,9 +65,15 @@ void offset2(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const void offset2(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float delta1, const float delta2, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +Slic3r::Polygons offset2(const Slic3r::Polygons &polygons, const float delta1, + const float delta2, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, + double miterLimit = 3); void offset2(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float delta1, const float delta2, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +Slic3r::ExPolygons offset2_ex(const Slic3r::Polygons &polygons, const float delta1, + const float delta2, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, + double miterLimit = 3); template void _clipper_do(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, @@ -86,9 +95,15 @@ void diff(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* template void diff(const SubjectType &subject, const Slic3r::ExPolygons &clip, ResultType* retval, bool safety_offset_ = false); +Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); +Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); + template void intersection(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* retval, bool safety_offset_ = false); +Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); +Slic3r::Polylines intersection(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); + template bool intersects(const SubjectType &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); @@ -98,6 +113,9 @@ void xor_(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r: template void union_(const Slic3r::Polygons &subject, T* retval, bool safety_offset_ = false); +Slic3r::Polygons union_(const Slic3r::Polygons &subject, bool safety_offset = false); +Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, bool safety_offset = false); + void union_(const Slic3r::Polygons &subject1, const Slic3r::Polygons &subject2, Slic3r::Polygons* retval, bool safety_offset = false); void union_pt(const Slic3r::Polygons &subject, ClipperLib::PolyTree* retval, bool safety_offset_ = false); diff --git a/xs/src/libslic3r/ExPolygon.cpp b/xs/src/libslic3r/ExPolygon.cpp index 53a1e9333..ee0473656 100644 --- a/xs/src/libslic3r/ExPolygon.cpp +++ b/xs/src/libslic3r/ExPolygon.cpp @@ -122,6 +122,13 @@ ExPolygon::has_boundary_point(const Point &point) const return false; } +void +ExPolygon::simplify_p(double tolerance, Polygons* polygons) const +{ + Polygons pp = this->simplify_p(tolerance); + polygons->insert(polygons->end(), pp.begin(), pp.end()); +} + Polygons ExPolygon::simplify_p(double tolerance) const { @@ -180,16 +187,17 @@ ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines) } // compute the Voronoi diagram - ma.build(polylines); + Polylines pp; + ma.build(&pp); // clip segments to our expolygon area // (do this before extending endpoints as external segments coule be extended into // expolygon, this leaving wrong things inside) - intersection(*polylines, *this, polylines); + pp = intersection(pp, *this); // extend initial and final segments of each polyline (they will be clipped) // unless they represent closed loops - for (Polylines::iterator polyline = polylines->begin(); polyline != polylines->end(); ++polyline) { + for (Polylines::iterator polyline = pp.begin(); polyline != pp.end(); ++polyline) { if (polyline->points.front().distance_to(polyline->points.back()) < min_width) continue; // TODO: we should *not* extend endpoints where other polylines start/end // (such as T joints, which are returned as three polylines by MedialAxis) @@ -198,18 +206,20 @@ ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines) } // clip again after extending endpoints to prevent them from exceeding the expolygon boundaries - intersection(*polylines, *this, polylines); + pp = intersection(pp, *this); // remove too short polylines // (we can't do this check before endpoints extension and clipping because we don't // know how long will the endpoints be extended since it depends on polygon thickness // which is variable - extension will be <= max_width/2 on each side) - for (size_t i = 0; i < polylines->size(); ++i) { - if ((*polylines)[i].length() < max_width) { - polylines->erase(polylines->begin() + i); + for (size_t i = 0; i < pp.size(); ++i) { + if (pp[i].length() < max_width) { + pp.erase(pp.begin() + i); --i; } } + + polylines->insert(polylines->end(), pp.begin(), pp.end()); } void diff --git a/xs/src/libslic3r/ExPolygon.hpp b/xs/src/libslic3r/ExPolygon.hpp index 3d7cd3540..7b7b0b760 100644 --- a/xs/src/libslic3r/ExPolygon.hpp +++ b/xs/src/libslic3r/ExPolygon.hpp @@ -27,6 +27,7 @@ class ExPolygon bool contains(const Point &point) const; bool contains_b(const Point &point) const; bool has_boundary_point(const Point &point) const; + void simplify_p(double tolerance, Polygons* polygons) const; Polygons simplify_p(double tolerance) const; ExPolygons simplify(double tolerance) const; void simplify(double tolerance, ExPolygons &expolygons) const; diff --git a/xs/src/libslic3r/ExtrusionEntity.hpp b/xs/src/libslic3r/ExtrusionEntity.hpp index bcb50155a..c1eef6af3 100644 --- a/xs/src/libslic3r/ExtrusionEntity.hpp +++ b/xs/src/libslic3r/ExtrusionEntity.hpp @@ -52,6 +52,7 @@ class ExtrusionEntity virtual Point last_point() const = 0; virtual Polygons grow() const = 0; virtual double min_mm3_per_mm() const = 0; + virtual Polyline as_polyline() const = 0; }; typedef std::vector ExtrusionEntitiesPtr; @@ -83,6 +84,9 @@ class ExtrusionPath : public ExtrusionEntity double min_mm3_per_mm() const { return this->mm3_per_mm; }; + Polyline as_polyline() const { + return this->polyline; + }; private: void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const; @@ -97,6 +101,8 @@ class ExtrusionLoop : public ExtrusionEntity ExtrusionLoopRole role; ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : role(role) {}; + ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault) + : paths(paths), role(role) {}; bool is_loop() const { return true; }; @@ -120,6 +126,9 @@ class ExtrusionLoop : public ExtrusionEntity bool is_solid_infill() const; Polygons grow() const; double min_mm3_per_mm() const; + Polyline as_polyline() const { + return this->polygon().split_at_first_point(); + }; }; } diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.cpp b/xs/src/libslic3r/ExtrusionEntityCollection.cpp index 79736cbcb..eb77aaede 100644 --- a/xs/src/libslic3r/ExtrusionEntityCollection.cpp +++ b/xs/src/libslic3r/ExtrusionEntityCollection.cpp @@ -8,9 +8,13 @@ namespace Slic3r { ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionEntityCollection& collection) : no_sort(collection.no_sort), orig_indices(collection.orig_indices) { - this->entities.reserve(collection.entities.size()); - for (ExtrusionEntitiesPtr::const_iterator it = collection.entities.begin(); it != collection.entities.end(); ++it) - this->entities.push_back((*it)->clone()); + this->append(collection.entities); +} + +ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionPaths &paths) + : no_sort(false) +{ + this->append(paths); } ExtrusionEntityCollection& ExtrusionEntityCollection::operator= (const ExtrusionEntityCollection &other) @@ -28,10 +32,30 @@ ExtrusionEntityCollection::swap (ExtrusionEntityCollection &c) std::swap(this->no_sort, c.no_sort); } +ExtrusionEntityCollection::~ExtrusionEntityCollection() +{ + for (ExtrusionEntitiesPtr::iterator it = this->entities.begin(); it != this->entities.end(); ++it) + delete *it; +} + +ExtrusionEntityCollection::operator ExtrusionPaths() const +{ + ExtrusionPaths paths; + for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) { + if (const ExtrusionPath* path = dynamic_cast(*it)) + paths.push_back(*path); + } + return paths; +} + ExtrusionEntityCollection* ExtrusionEntityCollection::clone() const { - return new ExtrusionEntityCollection(*this); + ExtrusionEntityCollection* coll = new ExtrusionEntityCollection(*this); + for (size_t i = 0; i < coll->entities.size(); ++i) { + coll->entities[i] = this->entities[i]->clone(); + } + return coll; } void @@ -57,6 +81,34 @@ ExtrusionEntityCollection::last_point() const return this->entities.back()->last_point(); } +void +ExtrusionEntityCollection::append(const ExtrusionEntity &entity) +{ + this->entities.push_back(entity.clone()); +} + +void +ExtrusionEntityCollection::append(const ExtrusionEntitiesPtr &entities) +{ + for (ExtrusionEntitiesPtr::const_iterator ptr = entities.begin(); ptr != entities.end(); ++ptr) + this->append(**ptr); +} + +void +ExtrusionEntityCollection::append(const ExtrusionPaths &paths) +{ + for (ExtrusionPaths::const_iterator path = paths.begin(); path != paths.end(); ++path) + this->append(*path); +} + +ExtrusionEntityCollection +ExtrusionEntityCollection::chained_path(bool no_reverse, std::vector* orig_indices) const +{ + ExtrusionEntityCollection coll; + this->chained_path(&coll, no_reverse, orig_indices); + return coll; +} + void ExtrusionEntityCollection::chained_path(ExtrusionEntityCollection* retval, bool no_reverse, std::vector* orig_indices) const { @@ -145,15 +197,21 @@ ExtrusionEntityCollection::flatten(ExtrusionEntityCollection* retval) const for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) { if ((*it)->is_collection()) { ExtrusionEntityCollection* collection = dynamic_cast(*it); - ExtrusionEntityCollection contents; - collection->flatten(&contents); - retval->entities.insert(retval->entities.end(), contents.entities.begin(), contents.entities.end()); + retval->append(collection->flatten().entities); } else { - retval->entities.push_back((*it)->clone()); + retval->append(**it); } } } +ExtrusionEntityCollection +ExtrusionEntityCollection::flatten() const +{ + ExtrusionEntityCollection coll; + this->flatten(&coll); + return coll; +} + double ExtrusionEntityCollection::min_mm3_per_mm() const { diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.hpp b/xs/src/libslic3r/ExtrusionEntityCollection.hpp index 24016d638..504c82ae1 100644 --- a/xs/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/xs/src/libslic3r/ExtrusionEntityCollection.hpp @@ -10,19 +10,30 @@ class ExtrusionEntityCollection : public ExtrusionEntity { public: ExtrusionEntityCollection* clone() const; - ExtrusionEntitiesPtr entities; + ExtrusionEntitiesPtr entities; // we own these entities std::vector orig_indices; // handy for XS bool no_sort; ExtrusionEntityCollection(): no_sort(false) {}; ExtrusionEntityCollection(const ExtrusionEntityCollection &collection); + ExtrusionEntityCollection(const ExtrusionPaths &paths); ExtrusionEntityCollection& operator= (const ExtrusionEntityCollection &other); + ~ExtrusionEntityCollection(); + operator ExtrusionPaths() const; + bool is_collection() const { return true; }; bool can_reverse() const { return !this->no_sort; }; + bool empty() const { + return this->entities.empty(); + }; void swap (ExtrusionEntityCollection &c); + void append(const ExtrusionEntity &entity); + void append(const ExtrusionEntitiesPtr &entities); + void append(const ExtrusionPaths &paths); + ExtrusionEntityCollection chained_path(bool no_reverse = false, std::vector* orig_indices = NULL) const; void chained_path(ExtrusionEntityCollection* retval, bool no_reverse = false, std::vector* orig_indices = NULL) const; void chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse = false, std::vector* orig_indices = NULL) const; void reverse(); @@ -31,7 +42,12 @@ class ExtrusionEntityCollection : public ExtrusionEntity Polygons grow() const; size_t items_count() const; void flatten(ExtrusionEntityCollection* retval) const; + ExtrusionEntityCollection flatten() const; double min_mm3_per_mm() const; + Polyline as_polyline() const { + CONFESS("Calling as_polyline() on a ExtrusionEntityCollection"); + return Polyline(); + }; }; } diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp new file mode 100644 index 000000000..f95930e2d --- /dev/null +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -0,0 +1,525 @@ +#include "PerimeterGenerator.hpp" +#include "ClipperUtils.hpp" +#include "ExtrusionEntityCollection.hpp" + +namespace Slic3r { + +void +PerimeterGenerator::process() +{ + // other perimeters + this->_mm3_per_mm = this->perimeter_flow.mm3_per_mm(); + coord_t pwidth = this->perimeter_flow.scaled_width(); + coord_t pspacing = this->perimeter_flow.scaled_spacing(); + + // external perimeters + this->_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm(); + coord_t ext_pwidth = this->ext_perimeter_flow.scaled_width(); + coord_t ext_pspacing = scale_(this->ext_perimeter_flow.spacing(this->perimeter_flow)); + + // overhang perimeters + this->_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); + + // solid infill + coord_t ispacing = this->solid_infill_flow.scaled_spacing(); + coord_t gap_area_threshold = pwidth * pwidth; + + // Calculate the minimum required spacing between two adjacent traces. + // This should be equal to the nominal flow spacing but we experiment + // with some tolerance in order to avoid triggering medial axis when + // some squishing might work. Loops are still spaced by the entire + // flow spacing; this only applies to collapsing parts. + coord_t min_spacing = pspacing * (1 - INSET_OVERLAP_TOLERANCE); + coord_t ext_min_spacing = ext_pspacing * (1 - INSET_OVERLAP_TOLERANCE); + + // prepare grown lower layer slices for overhang detection + if (this->lower_slices != NULL && this->config->overhangs) { + // We consider overhang any part where the entire nozzle diameter is not supported by the + // lower layer, so we take lower slices and offset them by half the nozzle diameter used + // in the current layer + double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder-1); + + this->_lower_slices_p = offset(*this->lower_slices, scale_(+nozzle_diameter/2)); + } + + // we need to process each island separately because we might have different + // extra perimeters for each one + for (Surfaces::const_iterator surface = this->slices->surfaces.begin(); + surface != this->slices->surfaces.end(); ++surface) { + // detect how many perimeters must be generated for this island + unsigned short loop_number = this->config->perimeters + surface->extra_perimeters; + loop_number--; // 0-indexed loops + + Polygons gaps; + + Polygons last = surface->expolygon.simplify_p(SCALED_RESOLUTION); + if (loop_number >= 0) { // no loops = -1 + + std::vector contours(loop_number+1); // depth => loops + std::vector holes(loop_number+1); // depth => loops + Polylines thin_walls; + + // we loop one time more than needed in order to find gaps after the last perimeter was applied + for (unsigned short i = 0; i <= loop_number+1; ++i) { // outer loop is 0 + Polygons offsets; + if (i == 0) { + // the minimum thickness of a single loop is: + // ext_width/2 + ext_spacing/2 + spacing/2 + width/2 + if (this->config->thin_walls) { + offsets = offset2( + last, + -(0.5*ext_pwidth + 0.5*ext_min_spacing - 1), + +(0.5*ext_min_spacing - 1) + ); + } else { + offsets = offset(last, -0.5*ext_pwidth); + } + + // look for thin walls + if (this->config->thin_walls) { + Polygons diffpp = diff( + last, + offset(offsets, +0.5*ext_pwidth), + true // medial axis requires non-overlapping geometry + ); + + // the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width + // (actually, something larger than that still may exist due to mitering or other causes) + coord_t min_width = ext_pwidth / 2; + ExPolygons expp = offset2_ex(diffpp, -min_width/2, +min_width/2); + + // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop + Polylines pp; + for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) + ex->medial_axis(ext_pwidth + ext_pspacing, min_width, &pp); + + double threshold = ext_pwidth * ext_pwidth; + for (Polylines::const_iterator p = pp.begin(); p != pp.end(); ++p) { + if (p->length() > threshold) { + thin_walls.push_back(*p); + } + } + + #ifdef DEBUG + printf(" %zu thin walls detected\n", thin_walls.size()); + #endif + + /* + if (false) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output( + "medial_axis.svg", + no_arrows => 1, + #expolygons => \@expp, + polylines => \@thin_walls, + ); + } + */ + } + } else { + coord_t distance = (i == 1) ? ext_pspacing : pspacing; + + if (this->config->thin_walls) { + offsets = offset2( + last, + -(distance + 0.5*min_spacing - 1), + +(0.5*min_spacing - 1) + ); + } else { + offsets = offset( + last, + -distance + ); + } + + // look for gaps + if (this->config->gap_fill_speed.value > 0 && this->config->fill_density.value > 0) { + // not using safety offset here would "detect" very narrow gaps + // (but still long enough to escape the area threshold) that gap fill + // won't be able to fill but we'd still remove from infill area + ExPolygons diff_expp = diff_ex( + offset(last, -0.5*distance), + offset(offsets, +0.5*distance + 10) // safety offset + ); + for (ExPolygons::const_iterator ex = diff_expp.begin(); ex != diff_expp.end(); ++ex) { + if (fabs(ex->area()) >= gap_area_threshold) { + Polygons pp = *ex; + gaps.insert(gaps.end(), pp.begin(), pp.end()); + } + } + } + } + + if (offsets.empty()) break; + if (i > loop_number) break; // we were only looking for gaps this time + + last = offsets; + for (Polygons::const_iterator polygon = offsets.begin(); polygon != offsets.end(); ++polygon) { + PerimeterGeneratorLoop loop(*polygon, i); + loop.is_contour = polygon->is_counter_clockwise(); + if (loop.is_contour) { + contours[i].push_back(loop); + } else { + holes[i].push_back(loop); + } + } + } + + // nest loops: holes first + for (unsigned short d = 0; d <= loop_number; ++d) { + PerimeterGeneratorLoops &holes_d = holes[d]; + + // loop through all holes having depth == d + for (unsigned short i = 0; i < holes_d.size(); ++i) { + const PerimeterGeneratorLoop &loop = holes_d[i]; + + // find the hole loop that contains this one, if any + for (unsigned short t = d+1; t <= loop_number; ++t) { + for (unsigned short j = 0; j < holes[t].size(); ++j) { + PerimeterGeneratorLoop &candidate_parent = holes[t][j]; + if (candidate_parent.polygon.contains(loop.polygon.first_point())) { + candidate_parent.children.push_back(loop); + holes_d.erase(holes_d.begin() + i); + --i; + goto NEXT_LOOP; + } + } + } + + // if no hole contains this hole, find the contour loop that contains it + for (unsigned short t = loop_number; t >= 0; --t) { + for (unsigned short j = 0; j < contours[t].size(); ++j) { + PerimeterGeneratorLoop &candidate_parent = contours[t][j]; + if (candidate_parent.polygon.contains(loop.polygon.first_point())) { + candidate_parent.children.push_back(loop); + holes_d.erase(holes_d.begin() + i); + --i; + goto NEXT_LOOP; + } + } + } + NEXT_LOOP: ; + } + } + + // nest contour loops + for (unsigned short d = loop_number; d >= 1; --d) { + PerimeterGeneratorLoops &contours_d = contours[d]; + + // loop through all contours having depth == d + for (unsigned short i = 0; i < contours_d.size(); ++i) { + const PerimeterGeneratorLoop &loop = contours_d[i]; + + // find the contour loop that contains it + for (unsigned short t = d-1; t >= 0; --t) { + for (unsigned short j = 0; j < contours[t].size(); ++j) { + PerimeterGeneratorLoop &candidate_parent = contours[t][j]; + if (candidate_parent.polygon.contains(loop.polygon.first_point())) { + candidate_parent.children.push_back(loop); + contours_d.erase(contours_d.begin() + i); + --i; + goto NEXT_CONTOUR; + } + } + } + + NEXT_CONTOUR: ; + } + } + + // at this point, all loops should be in contours[0] + + ExtrusionEntityCollection entities = this->_traverse_loops(contours.front(), thin_walls); + + // if brim will be printed, reverse the order of perimeters so that + // we continue inwards after having finished the brim + // TODO: add test for perimeter order + if (this->config->external_perimeters_first + || (this->layer_id == 0 && this->print_config->brim_width.value > 0)) + entities.reverse(); + + // append perimeters for this slice as a collection + if (!entities.empty()) + this->loops->append(entities); + } + + // fill gaps + if (!gaps.empty()) { + /* + if (false) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output( + "gaps.svg", + expolygons => union_ex(\@gaps), + ); + } + */ + + // where $pwidth < thickness < 2*$pspacing, infill with width = 2*$pwidth + // where 0.1*$pwidth < thickness < $pwidth, infill with width = 1*$pwidth + std::vector gap_sizes; + gap_sizes.push_back(PerimeterGeneratorGapSize(pwidth, 2*pspacing, unscale(2*pwidth))); + gap_sizes.push_back(PerimeterGeneratorGapSize(0.1*pwidth, pwidth, unscale(1*pwidth))); + + for (std::vector::const_iterator gap_size = gap_sizes.begin(); + gap_size != gap_sizes.end(); ++gap_size) { + ExtrusionEntityCollection gap_fill = this->_fill_gaps(gap_size->min, + gap_size->max, gap_size->width, gaps); + this->gap_fill->append(gap_fill.entities); + + // Make sure we don't infill narrow parts that are already gap-filled + // (we only consider this surface's gaps to reduce the diff() complexity). + // Growing actual extrusions ensures that gaps not filled by medial axis + // are not subtracted from fill surfaces (they might be too short gaps + // that medial axis skips but infill might join with other infill regions + // and use zigzag). + double dist = scale_(gap_size->width/2); + Polygons filled; + for (ExtrusionEntitiesPtr::const_iterator it = gap_fill.entities.begin(); + it != gap_fill.entities.end(); ++it) + offset((*it)->as_polyline(), &filled, dist); + + last = diff(last, filled); + gaps = diff(gaps, filled); // prevent more gap fill here + } + } + + // create one more offset to be used as boundary for fill + // we offset by half the perimeter spacing (to get to the actual infill boundary) + // and then we offset back and forth by half the infill spacing to only consider the + // non-collapsing regions + coord_t inset = 0; + if (loop_number == 0) { + // one loop + inset += ext_pspacing/2; + } else if (loop_number > 0) { + // two or more loops + inset += pspacing/2; + } + + // only apply infill overlap if we actually have one perimeter + if (inset > 0) + inset -= this->config->get_abs_value("infill_overlap", inset + ispacing/2); + + { + ExPolygons expp = union_ex(last); + + // simplify infill contours according to resolution + Polygons pp; + for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) + ex->simplify_p(SCALED_RESOLUTION, &pp); + + // collapse too narrow infill areas + coord_t min_perimeter_infill_spacing = ispacing * (1 - INSET_OVERLAP_TOLERANCE); + expp = offset2_ex( + pp, + -inset -min_perimeter_infill_spacing/2, + +min_perimeter_infill_spacing/2 + ); + + // append infill areas to fill_surfaces + for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) + this->fill_surfaces->surfaces.push_back(Surface(stInternal, *ex)); // use a bogus surface type + } + } +} + +ExtrusionEntityCollection +PerimeterGenerator::_traverse_loops(const PerimeterGeneratorLoops &loops, + Polylines &thin_walls) const +{ + // loops is an arrayref of ::Loop objects + // turn each one into an ExtrusionLoop object + ExtrusionEntityCollection coll; + for (PerimeterGeneratorLoops::const_iterator loop = loops.begin(); + loop != loops.end(); ++loop) { + bool is_external = loop->is_external(); + + ExtrusionRole role; + ExtrusionLoopRole loop_role; + role = is_external ? erExternalPerimeter : erPerimeter; + if (loop->is_internal_contour()) { + // Note that we set loop role to ContourInternalPerimeter + // also when loop is both internal and external (i.e. + // there's only one contour loop). + loop_role = elrContourInternalPerimeter; + } else { + loop_role = elrDefault; + } + + // detect overhanging/bridging perimeters + ExtrusionPaths paths; + if (this->config->overhangs && this->layer_id > 0 + && !(this->object_config->support_material && this->object_config->support_material_contact_distance.value == 0)) { + // get non-overhang paths by intersecting this loop with the grown lower slices + { + Polylines polylines; + intersection((Polygons)loop->polygon, this->_lower_slices_p, &polylines); + + for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) { + ExtrusionPath path(role); + path.polyline = *polyline; + path.mm3_per_mm = is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm; + path.width = is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width; + path.height = this->layer_height; + paths.push_back(path); + } + } + + // get overhang paths by checking what parts of this loop fall + // outside the grown lower slices (thus where the distance between + // the loop centerline and original lower slices is >= half nozzle diameter + { + Polylines polylines; + diff((Polygons)loop->polygon, this->_lower_slices_p, &polylines); + + for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) { + ExtrusionPath path(erOverhangPerimeter); + path.polyline = *polyline; + path.mm3_per_mm = this->_mm3_per_mm_overhang; + path.width = this->overhang_flow.width; + path.height = this->overhang_flow.height; + paths.push_back(path); + } + } + + // reapply the nearest point search for starting point + // We allow polyline reversal because Clipper may have randomly + // reversed polylines during clipping. + paths = ExtrusionEntityCollection(paths).chained_path(); + } else { + ExtrusionPath path(role); + path.polyline = loop->polygon.split_at_first_point(); + path.mm3_per_mm = is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm; + path.width = is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width; + path.height = this->layer_height; + paths.push_back(path); + } + + coll.append(ExtrusionLoop(paths, loop_role)); + } + + // append thin walls to the nearest-neighbor search (only for first iteration) + if (!thin_walls.empty()) { + for (Polylines::const_iterator polyline = thin_walls.begin(); polyline != thin_walls.end(); ++polyline) { + ExtrusionPath path(erExternalPerimeter); + path.polyline = *polyline; + path.mm3_per_mm = this->_mm3_per_mm; + path.width = this->perimeter_flow.width; + path.height = this->layer_height; + coll.append(path); + } + + thin_walls.clear(); + } + + // sort entities + ExtrusionEntityCollection sorted_coll; + coll.chained_path(&sorted_coll, false, &sorted_coll.orig_indices); + + // traverse children + ExtrusionEntityCollection entities; + for (unsigned short i = 0; i < sorted_coll.orig_indices.size(); ++i) { + size_t idx = sorted_coll.orig_indices[i]; + if (idx >= loops.size()) { + // this is a thin wall + // let's get it from the sorted collection as it might have been reversed + entities.append(*sorted_coll.entities[i]); + } else { + const PerimeterGeneratorLoop &loop = loops[i]; + ExtrusionLoop eloop = *dynamic_cast(coll.entities[idx]); + + ExtrusionEntityCollection children = this->_traverse_loops(loop.children, thin_walls); + if (loop.is_contour) { + eloop.make_counter_clockwise(); + entities.append(children.entities); + entities.append(eloop); + } else { + eloop.make_clockwise(); + entities.append(eloop); + entities.append(children.entities); + } + } + } + return entities; +} + +ExtrusionEntityCollection +PerimeterGenerator::_fill_gaps(double min, double max, double w, + const Polygons &gaps) const +{ + ExtrusionEntityCollection coll; + + min *= (1 - INSET_OVERLAP_TOLERANCE); + + ExPolygons curr = diff_ex( + offset2(gaps, -min/2, +min/2), + offset2(gaps, -max/2, +max/2), + true + ); + + Polylines polylines; + for (ExPolygons::const_iterator ex = curr.begin(); ex != curr.end(); ++ex) + ex->medial_axis(max, min/2, &polylines); + if (polylines.empty()) + return coll; + + #ifdef SLIC3R_DEBUG + if (!curr.empty()) + printf(" %zu gaps filled with extrusion width = %f\n", curr.size(), w); + #endif + + //my $flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL, 0, $w); + Flow flow( + w, this->layer_height, this->solid_infill_flow.nozzle_diameter + ); + + double mm3_per_mm = flow.mm3_per_mm(); + + for (Polylines::const_iterator p = polylines.begin(); p != polylines.end(); ++p) { + ExtrusionPath path(erGapFill); + path.polyline = *p; + path.mm3_per_mm = mm3_per_mm; + path.width = flow.width; + path.height = this->layer_height; + + if (p->is_valid() && p->first_point().coincides_with(p->last_point())) { + // since medial_axis() now returns only Polyline objects, detect loops here + ExtrusionLoop loop; + loop.paths.push_back(path); + coll.append(loop); + } else { + coll.append(path); + } + } + + return coll; +} + +#ifdef SLIC3RXS +REGISTER_CLASS(PerimeterGenerator, "Layer::PerimeterGenerator"); +#endif + +bool +PerimeterGeneratorLoop::is_external() const +{ + return this->depth == 0; +} + +bool +PerimeterGeneratorLoop::is_internal_contour() const +{ + if (this->is_contour) { + // an internal contour is a contour containing no other contours + for (std::vector::const_iterator loop = this->children.begin(); + loop != this->children.end(); ++loop) { + if (loop->is_contour) { + return false; + } + } + return true; + } + return false; +} + +} diff --git a/xs/src/libslic3r/PerimeterGenerator.hpp b/xs/src/libslic3r/PerimeterGenerator.hpp new file mode 100644 index 000000000..6465d63f9 --- /dev/null +++ b/xs/src/libslic3r/PerimeterGenerator.hpp @@ -0,0 +1,84 @@ +#ifndef slic3r_PerimeterGenerator_hpp_ +#define slic3r_PerimeterGenerator_hpp_ + +#include +#include +#include "ExPolygonCollection.hpp" +#include "Flow.hpp" +#include "Polygon.hpp" +#include "PrintConfig.hpp" +#include "SurfaceCollection.hpp" + +namespace Slic3r { + +class PerimeterGeneratorLoop; +typedef std::vector PerimeterGeneratorLoops; + +class PerimeterGeneratorLoop { + public: + Polygon polygon; + bool is_contour; + unsigned short depth; + std::vector children; + + PerimeterGeneratorLoop(Polygon polygon, unsigned short depth) + : polygon(polygon), depth(depth), is_contour(false) + {}; + bool is_external() const; + bool is_internal_contour() const; +}; + +class PerimeterGenerator { + public: + SurfaceCollection* slices; + ExPolygonCollection* lower_slices; + double layer_height; + int layer_id; + Flow perimeter_flow; + Flow ext_perimeter_flow; + Flow overhang_flow; + Flow solid_infill_flow; + PrintRegionConfig* config; + PrintObjectConfig* object_config; + PrintConfig* print_config; + ExtrusionEntityCollection* loops; + ExtrusionEntityCollection* gap_fill; + SurfaceCollection* fill_surfaces; + + PerimeterGenerator(SurfaceCollection* slices, double layer_height, Flow flow, + PrintRegionConfig* config, PrintObjectConfig* object_config, + PrintConfig* print_config, ExtrusionEntityCollection* loops, + ExtrusionEntityCollection* gap_fill, SurfaceCollection* fill_surfaces) + : slices(slices), lower_slices(NULL), layer_height(layer_height), + perimeter_flow(flow), ext_perimeter_flow(flow), overhang_flow(flow), + solid_infill_flow(flow), layer_id(-1), + config(config), object_config(object_config), print_config(print_config), + loops(loops), gap_fill(gap_fill), fill_surfaces(fill_surfaces), + _ext_mm3_per_mm(-1), _mm3_per_mm(-1), _mm3_per_mm_overhang(-1) + {}; + void process(); + + private: + double _ext_mm3_per_mm; + double _mm3_per_mm; + double _mm3_per_mm_overhang; + Polygons _lower_slices_p; + + ExtrusionEntityCollection _traverse_loops(const PerimeterGeneratorLoops &loops, + Polylines &thin_walls) const; + ExtrusionEntityCollection _fill_gaps(double min, double max, double w, + const Polygons &gaps) const; +}; + +class PerimeterGeneratorGapSize { + public: + coord_t min; + coord_t max; + coord_t width; + PerimeterGeneratorGapSize(coord_t min, coord_t max, coord_t width) + : min(min), max(max), width(width) {}; +}; + +} + +#endif diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index fcf90f133..87d98caf1 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -15,6 +15,7 @@ #define PI 3.141592653589793238 #define LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER 0.15 #define SMALL_PERIMETER_LENGTH (6.5 / SCALING_FACTOR) * 2 * PI +#define INSET_OVERLAP_TOLERANCE 0.4 #define scale_(val) (val / SCALING_FACTOR) #define unscale(val) (val * SCALING_FACTOR) #define SCALED_EPSILON scale_(EPSILON) diff --git a/xs/xsp/ExtrusionEntityCollection.xsp b/xs/xsp/ExtrusionEntityCollection.xsp index b7e439479..7570d078a 100644 --- a/xs/xsp/ExtrusionEntityCollection.xsp +++ b/xs/xsp/ExtrusionEntityCollection.xsp @@ -7,6 +7,7 @@ %name{Slic3r::ExtrusionPath::Collection} class ExtrusionEntityCollection { %name{_new} ExtrusionEntityCollection(); + ~ExtrusionEntityCollection(); Clone clone() %code{% RETVAL = THIS->clone(); %}; void reverse(); @@ -41,14 +42,6 @@ Polygons grow(); %{ -void -ExtrusionEntityCollection::DESTROY() - CODE: - for (ExtrusionEntitiesPtr::iterator it = THIS->entities.begin(); it != THIS->entities.end(); ++it) { - delete *it; - } - delete THIS; - SV* ExtrusionEntityCollection::arrayref() CODE: diff --git a/xs/xsp/PerimeterGenerator.xsp b/xs/xsp/PerimeterGenerator.xsp new file mode 100644 index 000000000..351c6f4c0 --- /dev/null +++ b/xs/xsp/PerimeterGenerator.xsp @@ -0,0 +1,34 @@ +%module{Slic3r::XS}; + +%{ +#include +#include "libslic3r/PerimeterGenerator.hpp" +%} + +%name{Slic3r::Layer::PerimeterGenerator} class PerimeterGenerator { + PerimeterGenerator(SurfaceCollection* slices, double layer_height, Flow* flow, + PrintRegionConfig* config, PrintObjectConfig* object_config, + PrintConfig* print_config, ExtrusionEntityCollection* loops, + ExtrusionEntityCollection* gap_fill, SurfaceCollection* fill_surfaces) + %code{% RETVAL = new PerimeterGenerator(slices, layer_height, *flow, + config, object_config, print_config, loops, gap_fill, fill_surfaces); %}; + ~PerimeterGenerator(); + + void set_lower_slices(ExPolygonCollection* lower_slices) + %code{% THIS->lower_slices = lower_slices; %}; + void set_layer_id(int layer_id) + %code{% THIS->layer_id = layer_id; %}; + void set_perimeter_flow(Flow* flow) + %code{% THIS->perimeter_flow = *flow; %}; + void set_ext_perimeter_flow(Flow* flow) + %code{% THIS->ext_perimeter_flow = *flow; %}; + void set_overhang_flow(Flow* flow) + %code{% THIS->overhang_flow = *flow; %}; + void set_solid_infill_flow(Flow* flow) + %code{% THIS->solid_infill_flow = *flow; %}; + + Ref config() + %code{% RETVAL = THIS->config; %}; + + void process(); +}; diff --git a/xs/xsp/my.map b/xs/xsp/my.map index af6f9f042..1f59b3fa0 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -198,6 +198,10 @@ BridgeDetector* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T +PerimeterGenerator* O_OBJECT_SLIC3R +Ref O_OBJECT_SLIC3R_T +Clone O_OBJECT_SLIC3R_T + GLVertexArray* O_OBJECT_SLIC3R Axis T_UV diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 8b4eb0850..6c7beb17f 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -94,6 +94,12 @@ %typemap{BridgeDetector*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; +%typemap{SurfaceCollection*}; +%typemap{Ref}{simple}; +%typemap{Clone}{simple}; +%typemap{PerimeterGenerator*}; +%typemap{Ref}{simple}; +%typemap{Clone}{simple}; %typemap{Surface*}; %typemap{Ref}{simple}; @@ -153,7 +159,6 @@ %typemap{Polygons*}; %typemap{TriangleMesh*}; %typemap{TriangleMeshPtrs}; -%typemap{Ref}{simple}; %typemap{Extruder*}; %typemap{Ref}{simple}; %typemap{Clone}{simple};