diff --git a/lib/Slic3r/ExtrusionLoop.pm b/lib/Slic3r/ExtrusionLoop.pm index 0ec9f829ba..710beaa414 100644 --- a/lib/Slic3r/ExtrusionLoop.pm +++ b/lib/Slic3r/ExtrusionLoop.pm @@ -2,16 +2,4 @@ package Slic3r::ExtrusionLoop; use strict; use warnings; -sub split_at { - my $self = shift; - - return Slic3r::ExtrusionPath->new( - polyline => $self->polygon->split_at(@_), - role => $self->role, - mm3_per_mm => $self->mm3_per_mm, - width => $self->width, - height => $self->height, - ); -} - 1; diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index eb2656a341..907a0048ac 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -19,7 +19,6 @@ has 'layer' => (is => 'rw'); has 'region' => (is => 'rw'); has '_layer_islands' => (is => 'rw'); has '_upper_layer_islands' => (is => 'rw'); -has '_lower_layer_slices' => (is => 'ro', default => sub { Slic3r::ExPolygon::Collection->new }); has 'shift_x' => (is => 'rw', default => sub {0} ); has 'shift_y' => (is => 'rw', default => sub {0} ); has 'z' => (is => 'rw'); @@ -111,17 +110,6 @@ sub change_layer { # avoid computing islands and overhangs if they're not needed $self->_layer_islands($layer->islands); $self->_upper_layer_islands($layer->upper_layer ? $layer->upper_layer->islands : []); - $self->_lower_layer_slices->clear; - if ($layer->lower_layer && ($self->print_config->overhangs || $self->print_config->start_perimeters_at_non_overhang)) { - # 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 $max_nozzle_diameter = max(map $layer->print->config->get_at('nozzle_diameter', $_->region->config->perimeter_extruder-1), @{$layer->regions}); - $self->_lower_layer_slices->append( - # clone ExPolygons because they come from Surface objects but will be used outside here - @{offset_ex([ map @$_, @{$layer->lower_layer->slices} ], +scale($max_nozzle_diameter/2))}, - ); - } if ($self->print_config->avoid_crossing_perimeters) { $self->layer_mp(Slic3r::GCode::MotionPlanner->new( islands => union_ex([ map @$_, @{$layer->slices} ], 1), @@ -199,17 +187,17 @@ sub extrude_loop { # extrude all loops ccw my $was_clockwise = $loop->make_counter_clockwise; - my $polygon = $loop->polygon; # find candidate starting points # start looking for concave vertices not being overhangs + my $polygon = $loop->polygon; my @concave = (); if ($self->print_config->start_perimeters_at_concave_points) { @concave = $polygon->concave_points; } my @candidates = (); if ($self->print_config->start_perimeters_at_non_overhang) { - @candidates = grep $self->_lower_layer_slices->contains_point($_), @concave; + @candidates = grep !$loop->has_overhang_point($_), @concave; } if (!@candidates) { # if none, look for any concave vertex @@ -217,11 +205,11 @@ sub extrude_loop { if (!@candidates) { # if none, look for any non-overhang vertex if ($self->print_config->start_perimeters_at_non_overhang) { - @candidates = grep $self->_lower_layer_slices->contains_point($_), @$polygon; + @candidates = grep !$loop->has_overhang_point($_), @$polygon; } if (!@candidates) { # if none, all points are valid candidates - @candidates = @{$polygon}; + @candidates = @$polygon; } } } @@ -234,71 +222,49 @@ sub extrude_loop { $last_pos->rotate(rand(2*PI), $self->print_config->print_center); } - # split the loop at the starting point and make a path - my $start_at = $last_pos->nearest_point(\@candidates); - my $extrusion_path = $loop->split_at($start_at); + # split the loop at the starting point + $loop->split_at($last_pos->nearest_point(\@candidates)); # clip the path to avoid the extruder to get exactly on the first point of the loop; # if polyline was shorter than the clipping distance we'd get a null polyline, so # we discard it in that case - $extrusion_path->clip_end(scale($self->extruder->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER) - if $self->enable_loop_clipping; - return '' if !@{$extrusion_path->polyline}; + my $clip_length = $self->enable_loop_clipping + ? scale($self->extruder->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER + : 0; - my @paths = (); - # detect overhanging/bridging perimeters - if ($self->layer->print->config->overhangs && $extrusion_path->is_perimeter && $self->_lower_layer_slices->count > 0) { - # get non-overhang paths by intersecting this loop with the grown lower slices - push @paths, - map $_->clone, - @{$extrusion_path->intersect_expolygons($self->_lower_layer_slices)}; - - # 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 $path (@{$extrusion_path->subtract_expolygons($self->_lower_layer_slices)}) { - $path = $path->clone; - $path->role(EXTR_ROLE_OVERHANG_PERIMETER); - $path->mm3_per_mm($self->region->flow(FLOW_ROLE_PERIMETER, -1, 1, 0, undef, $self->layer->object)->mm3_per_mm(-1)); - push @paths, $path; - } - - # reapply the nearest point search for starting point - # (clone because the collection gets DESTROY'ed) - my $collection = Slic3r::ExtrusionPath::Collection->new(@paths); - @paths = map $_->clone, @{$collection->chained_path_from($start_at, 1)}; - } else { - push @paths, $extrusion_path; - } + # get paths + my @paths = @{$loop->clip_end($clip_length)}; + return '' if !@paths; # apply the small perimeter speed my %params = (); - if ($extrusion_path->is_perimeter && abs($extrusion_path->length) <= &Slic3r::SMALL_PERIMETER_LENGTH) { + if ($paths[0]->is_perimeter && abs($loop->length) <= &Slic3r::SMALL_PERIMETER_LENGTH) { $params{speed} = 'small_perimeter'; } # extrude along the path my $gcode = join '', map $self->extrude_path($_, $description, %params), @paths; - $self->wipe_path($extrusion_path->polyline->clone) if $self->enable_wipe; + $self->wipe_path($paths[-1]->polyline->clone) if $self->enable_wipe; # TODO: don't limit wipe to last path # make a little move inwards before leaving loop - if ($loop->role == EXTR_ROLE_EXTERNAL_PERIMETER && defined $self->layer && $self->region->config->perimeters > 1) { + if ($paths[-1]->role == EXTR_ROLE_EXTERNAL_PERIMETER && defined $self->layer && $self->region->config->perimeters > 1) { + my $last_path_polyline = $paths[-1]->polyline; # detect angle between last and first segment # the side depends on the original winding order of the polygon (left for contours, right for holes) my @points = $was_clockwise ? (-2, 1) : (1, -2); - my $angle = Slic3r::Geometry::angle3points(@{$extrusion_path->polyline}[0, @points]) / 3; + my $angle = Slic3r::Geometry::angle3points(@$last_path_polyline[0, @points]) / 3; $angle *= -1 if $was_clockwise; # create the destination point along the first segment and rotate it # we make sure we don't exceed the segment length because we don't know # the rotation of the second segment so we might cross the object boundary - my $first_segment = Slic3r::Line->new(@{$extrusion_path->polyline}[0,1]); + my $first_segment = Slic3r::Line->new(@$last_path_polyline[0,1]); my $distance = min(scale($self->extruder->nozzle_diameter), $first_segment->length); my $point = $first_segment->point_at($distance); - $point->rotate($angle, $extrusion_path->first_point); + $point->rotate($angle, $last_path_polyline->first_point); # generate the travel move - $gcode .= $self->travel_to($point, $loop->role, "move inwards before travel"); + $gcode .= $self->travel_to($point, $paths[-1]->role, "move inwards before travel"); } return $gcode; diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index f91f9f06ce..652abb8dbe 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -8,6 +8,6 @@ our @EXPORT_OK = qw(offset offset_ex diff_ex diff union_ex intersection_ex xor_ex JT_ROUND JT_MITER JT_SQUARE is_counter_clockwise union_pt offset2 offset2_ex intersection intersection_pl diff_pl union CLIPPER_OFFSET_SCALE - union_pt_chained); + union_pt_chained diff_ppl intersection_ppl); 1; diff --git a/lib/Slic3r/Layer/BridgeDetector.pm b/lib/Slic3r/Layer/BridgeDetector.pm index 795f5a6241..56c738637f 100644 --- a/lib/Slic3r/Layer/BridgeDetector.pm +++ b/lib/Slic3r/Layer/BridgeDetector.pm @@ -3,7 +3,8 @@ use Moo; use List::Util qw(first sum max min); use Slic3r::Geometry qw(PI unscale scaled_epsilon rad2deg epsilon directions_parallel_within); -use Slic3r::Geometry::Clipper qw(intersection_pl intersection_ex union offset diff_pl union_ex); +use Slic3r::Geometry::Clipper qw(intersection_pl intersection_ex union offset diff_pl union_ex + intersection_ppl); has 'expolygon' => (is => 'ro', required => 1); has 'lower_slices' => (is => 'rw', required => 1); # ExPolygons or ExPolygonCollection @@ -26,7 +27,7 @@ sub BUILD { foreach my $lower (@{$self->lower_slices}) { # turn bridge contour and holes into polylines and then clip them # with each lower slice's contour - push @$edges, map @{$_->clip_as_polyline([$lower->contour])}, @$grown; + push @$edges, @{intersection_ppl($grown, [ $lower->contour ])}; } Slic3r::debugf " bridge has %d support(s)\n", scalar(@$edges); diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 14d6aa6b9e..184f7bab67 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -7,7 +7,7 @@ use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(PI A B scale unscale chained_path points_coincide); use Slic3r::Geometry::Clipper qw(union_ex diff_ex intersection_ex offset offset_ex offset2 offset2_ex union_pt diff intersection - union diff); + union diff intersection_ppl diff_ppl); use Slic3r::Surface ':types'; has 'layer' => ( @@ -63,6 +63,8 @@ sub make_perimeters { my $perimeter_flow = $self->flow(FLOW_ROLE_PERIMETER); my $mm3_per_mm = $perimeter_flow->mm3_per_mm($self->height); + my $overhang_flow = $self->region->flow(FLOW_ROLE_PERIMETER, -1, 1, 0, undef, $self->layer->object); + my $mm3_per_mm_overhang = $overhang_flow->mm3_per_mm(-1); my $pwidth = $perimeter_flow->scaled_width; my $pspacing = $perimeter_flow->scaled_spacing; my $solid_infill_flow = $self->flow(FLOW_ROLE_SOLID_INFILL); @@ -229,6 +231,19 @@ sub make_perimeters { my $contours_pt = union_pt(\@contours); my $holes_pt = union_pt(\@holes); + # prepare grown lower layer slices for overhang detection + my $lower_slices = Slic3r::ExPolygon::Collection->new; + if ($self->layer->lower_layer && $self->layer->print->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->layer->print->config->get_at('nozzle_diameter', $self->region->config->perimeter_extruder-1); + $lower_slices->append( + @{offset_ex([ map @$_, @{$self->layer->lower_layer->slices} ], scale +$nozzle_diameter/2)}, + ); + } + my $lower_slices_p = $lower_slices->polygons; + # prepare a coderef for traversing the PolyTree object # external contours are root items of $contours_pt # internal contours are the ones next to external @@ -242,15 +257,6 @@ sub make_perimeters { foreach my $polynode (@$polynodes) { my $polygon = ($polynode->{outer} // $polynode->{hole})->clone; - # return ccw contours and cw holes - # GCode.pm will convert all of them to ccw, but it needs to know - # what the holes are in order to compute the correct inwards move - if ($is_contour) { - $polygon->make_counter_clockwise; - } else { - $polygon->make_clockwise; - } - my $role = EXTR_ROLE_PERIMETER; if ($is_contour ? $depth == 0 : !@{ $polynode->{children} }) { # external perimeters are root level in case of contours @@ -260,13 +266,62 @@ sub make_perimeters { $role = EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER; } - $collection->append(Slic3r::ExtrusionLoop->new( - polygon => $polygon, - role => $role, - mm3_per_mm => $mm3_per_mm, - width => $perimeter_flow->width, - height => $self->height, - )); + # detect overhanging/bridging perimeters + my @paths = (); + if ($self->layer->print->config->overhangs && $lower_slices->count > 0) { + # get non-overhang paths by intersecting this loop with the grown lower slices + foreach my $polyline (@{ intersection_ppl([ $polygon ], $lower_slices_p) }) { + push @paths, Slic3r::ExtrusionPath->new( + polyline => $polyline, + role => $role, + mm3_per_mm => $mm3_per_mm, + width => $perimeter_flow->width, + height => $self->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([ $polygon ], $lower_slices_p) }) { + push @paths, Slic3r::ExtrusionPath->new( + polyline => $polyline, + role => EXTR_ROLE_OVERHANG_PERIMETER, + mm3_per_mm => $mm3_per_mm_overhang, + width => $overhang_flow->width, + height => $self->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); + @paths = map $_->clone, @{$collection->chained_path(0)}; + } else { + push @paths, Slic3r::ExtrusionPath->new( + polyline => $polygon->split_at_first_point, + role => $role, + mm3_per_mm => $mm3_per_mm, + width => $perimeter_flow->width, + height => $self->height, + ); + } + my $loop = Slic3r::ExtrusionLoop->new_from_paths(@paths); + + # return ccw contours and cw holes + # GCode.pm will convert all of them to ccw, but it needs to know + # what the holes are in order to compute the correct inwards move + # We do this on the final Loop object instead of the polygon because + # overhang clipping might have reversed its order since Clipper does + # not preserve polyline orientation. + if ($is_contour) { + $loop->make_counter_clockwise; + } else { + $loop->make_clockwise; + } + $collection->append($loop); # save the children push @children, $polynode->{children}; @@ -367,11 +422,16 @@ sub _fill_gaps { Slic3r::debugf " %d gaps filled with extrusion width = %s\n", scalar @$this, $w if @$this; - return map { - $_->isa('Slic3r::Polygon') - ? Slic3r::ExtrusionLoop->new(polygon => $_, %path_args)->split_at_first_point # should we keep these as loops? - : Slic3r::ExtrusionPath->new(polyline => $_, %path_args), - } @polylines; + for my $i (0..$#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; + } else { + $polylines[$i] = Slic3r::ExtrusionPath->new(polyline => $polylines[$i], %path_args); + } + } + return @polylines; } sub prepare_fill_surfaces { diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm index 873cf59236..ee0ec96b91 100644 --- a/lib/Slic3r/Polygon.pm +++ b/lib/Slic3r/Polygon.pm @@ -51,41 +51,4 @@ sub concave_points { -1 .. ($#points-1); } -sub clip_as_polyline { - my ($self, $polygons) = @_; - - my $self_pl = $self->split_at_first_point; - - # Clipper will remove a polyline segment if first point coincides with last one. - # Until that bug is not fixed upstream, we move one of those points slightly. - $self_pl->[0]->translate(1, 0); - - my @polylines = @{intersection_pl([$self_pl], $polygons)}; - if (@polylines == 1) { - if ($polylines[0][0]->coincides_with($self_pl->[0])) { - # compensate the above workaround for Clipper bug - $polylines[0][0]->translate(-1, 0); - } - } elsif (@polylines == 2) { - # If the split_at_first_point() call above happens to split the polygon inside the clipping area - # we would get two consecutive polylines instead of a single one, so we use this ugly hack to - # recombine them back into a single one in order to trigger the @edges == 2 logic below. - # This needs to be replaced with something way better. - if ($polylines[0][-1]->coincides_with($self_pl->[-1]) && $polylines[-1][0]->coincides_with($self_pl->[0])) { - my $p = $polylines[0]->clone; - $p->pop_back; - $p->append(@{$polylines[-1]}); - return [$p]; - } - if ($polylines[0][0]->coincides_with($self_pl->[0]) && $polylines[-1][-1]->coincides_with($self_pl->[-1])) { - my $p = $polylines[-1]->clone; - $p->pop_back; - $p->append(@{$polylines[0]}); - return [$p]; - } - } - - return [ @polylines ]; -} - 1; \ No newline at end of file diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 230c8211d4..7626bba816 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -705,12 +705,14 @@ sub make_skirt { for (my $i = $self->config->skirts; $i > 0; $i--) { $distance += scale $spacing; my $loop = offset([$convex_hull], $distance, 1, JT_ROUND, scale(0.1))->[0]; - $self->skirt->append(Slic3r::ExtrusionLoop->new( - polygon => Slic3r::Polygon->new(@$loop), - role => EXTR_ROLE_SKIRT, - mm3_per_mm => $mm3_per_mm, - width => $flow->width, - height => $first_layer_height, + $self->skirt->append(Slic3r::ExtrusionLoop->new_from_paths( + Slic3r::ExtrusionPath->new( + polyline => Slic3r::Polygon->new(@$loop)->split_at_first_point, + role => EXTR_ROLE_SKIRT, + mm3_per_mm => $mm3_per_mm, + width => $flow->width, + height => $first_layer_height, + ), )); if ($self->config->min_skirt_length > 0) { @@ -788,12 +790,14 @@ sub make_brim { push @loops, @{offset2(\@islands, ($i + 0.5) * $flow->scaled_spacing, -1.0 * $flow->scaled_spacing, 100000, JT_SQUARE)}; } - $self->brim->append(map Slic3r::ExtrusionLoop->new( - polygon => Slic3r::Polygon->new(@$_), - role => EXTR_ROLE_SKIRT, - mm3_per_mm => $mm3_per_mm, - width => $flow->width, - height => $first_layer_height, + $self->brim->append(map Slic3r::ExtrusionLoop->new_from_paths( + Slic3r::ExtrusionPath->new( + polyline => Slic3r::Polygon->new(@$_)->split_at_first_point, + role => EXTR_ROLE_SKIRT, + mm3_per_mm => $mm3_per_mm, + width => $flow->width, + height => $first_layer_height, + ), ), reverse @{union_pt_chained(\@loops)}); } @@ -913,7 +917,7 @@ sub write_gcode { # calculate wiping points if needed if ($self->config->ooze_prevention) { - my @skirt_points = map @$_, @{$self->skirt}; + my @skirt_points = map @$_, map @$_, @{$self->skirt}; if (@skirt_points) { my $outer_skirt = convex_hull(\@skirt_points); my @skirts = (); diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm index a55a0558c3..09973f8f32 100644 --- a/lib/Slic3r/Print/SupportMaterial.pm +++ b/lib/Slic3r/Print/SupportMaterial.pm @@ -70,14 +70,20 @@ sub generate { $self->clip_with_shape($base, $shape) if @$shape; # Install support layers into object. - push @{$object->support_layers}, map Slic3r::Layer::Support->new( - object => $object, - id => $_, - height => ($_ == 0) ? $support_z->[$_] : ($support_z->[$_] - $support_z->[$_-1]), - print_z => $support_z->[$_], - slice_z => -1, - slices => [], - ), 0 .. $#$support_z; + for my $i (0 .. $#$support_z) { + push @{$object->support_layers}, Slic3r::Layer::Support->new( + object => $object, + id => $i, + height => ($i == 0) ? $support_z->[$i] : ($support_z->[$i] - $support_z->[$i-1]), + print_z => $support_z->[$i], + slice_z => -1, + slices => [], + ); + if ($i >= 1) { + $object->support_layers->[-2]->upper_layer($object->support_layers->[-1]); + $object->support_layers->[-1]->lower_layer($object->support_layers->[-2]); + } + } # Generate the actual toolpaths and save them into each layer. $self->generate_toolpaths($object, $overhang, $contact, $interface, $base); diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 5529d24ba1..9cbd1d2f91 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -106,28 +106,12 @@ use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; -sub new { - my ($class, %args) = @_; +sub new_from_paths { + my ($class, @paths) = @_; - return $class->_new( - $args{polygon}, # required - $args{role}, # required - $args{mm3_per_mm} // die("Missing required mm3_per_mm in ExtrusionLoop constructor"), - $args{width} // -1, - $args{height} // -1, - ); -} - -sub clone { - my ($self, %args) = @_; - - return __PACKAGE__->_new( - $args{polygon} // $self->polygon, - $args{role} // $self->role, - $args{mm3_per_mm} // $self->mm3_per_mm, - $args{width} // $self->width, - $args{height} // $self->height, - ); + my $loop = $class->new; + $loop->append($_) for @paths; + return $loop; } package Slic3r::ExtrusionLoop::Ref; diff --git a/xs/src/ClipperUtils.cpp b/xs/src/ClipperUtils.cpp index 8de80d28b8..1c6d1fbb76 100644 --- a/xs/src/ClipperUtils.cpp +++ b/xs/src/ClipperUtils.cpp @@ -300,23 +300,24 @@ void _clipper_do(const ClipperLib::ClipType clipType, const Slic3r::Polygons &su } void _clipper_do(const ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, - const Slic3r::Polygons &clip, ClipperLib::PolyTree &retval, const ClipperLib::PolyFillType fillType) + const Slic3r::Polygons &clip, ClipperLib::PolyTree &retval, const ClipperLib::PolyFillType fillType, + const bool safety_offset_) { // read input - ClipperLib::Paths* input_subject = new ClipperLib::Paths(); - ClipperLib::Paths* input_clip = new ClipperLib::Paths(); - Slic3rMultiPoints_to_ClipperPaths(subject, *input_subject); - Slic3rMultiPoints_to_ClipperPaths(clip, *input_clip); + ClipperLib::Paths input_subject, input_clip; + Slic3rMultiPoints_to_ClipperPaths(subject, input_subject); + Slic3rMultiPoints_to_ClipperPaths(clip, input_clip); + + // perform safety offset + if (safety_offset_) safety_offset(&input_clip); // init Clipper ClipperLib::Clipper clipper; clipper.Clear(); // add polygons - clipper.AddPaths(*input_subject, ClipperLib::ptSubject, false); - delete input_subject; - clipper.AddPaths(*input_clip, ClipperLib::ptClip, true); - delete input_clip; + clipper.AddPaths(input_subject, ClipperLib::ptSubject, false); + clipper.AddPaths(input_clip, ClipperLib::ptClip, true); // perform operation clipper.Execute(clipType, retval, fillType, fillType); @@ -338,52 +339,114 @@ void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons &retval, bool safety_offset_) { // perform operation - ClipperLib::PolyTree* polytree = new ClipperLib::PolyTree(); - _clipper_do(clipType, subject, clip, *polytree, ClipperLib::pftNonZero, safety_offset_); + ClipperLib::PolyTree polytree; + _clipper_do(clipType, subject, clip, polytree, ClipperLib::pftNonZero, safety_offset_); // convert into ExPolygons - PolyTreeToExPolygons(*polytree, retval); - delete polytree; + PolyTreeToExPolygons(polytree, retval); } void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, - const Slic3r::Polygons &clip, Slic3r::Polylines &retval) + const Slic3r::Polygons &clip, Slic3r::Polylines &retval, bool safety_offset_) { // perform operation ClipperLib::PolyTree polytree; - _clipper_do(clipType, subject, clip, polytree, ClipperLib::pftNonZero); + _clipper_do(clipType, subject, clip, polytree, ClipperLib::pftNonZero, safety_offset_); - // convert into Polygons + // convert into Polylines ClipperLib::Paths output; ClipperLib::PolyTreeToPaths(polytree, output); ClipperPaths_to_Slic3rMultiPoints(output, retval); } -template -void diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, T &retval, bool safety_offset_) +void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, + const Slic3r::Polygons &clip, Slic3r::Polylines &retval, bool safety_offset_) +{ + // transform input polygons into polylines + Slic3r::Polylines polylines; + polylines.reserve(subject.size()); + for (Slic3r::Polygons::const_iterator polygon = subject.begin(); polygon != subject.end(); ++polygon) + polylines.push_back(*polygon); // implicit call to split_at_first_point() + + /* Clipper will remove a polyline segment if first point coincides with last one. + Until that bug is not fixed upstream, we move one of those points slightly. */ + for (Slic3r::Polylines::iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) + polyline->points.front().translate(1, 0); + + // perform clipping + _clipper(clipType, polylines, clip, retval, safety_offset_); + + // compensate for the above hack + for (Slic3r::Polylines::iterator polyline = retval.begin(); polyline != retval.end(); ++polyline) { + for (Slic3r::Polylines::iterator subj_polyline = polylines.begin(); subj_polyline != polylines.end(); ++subj_polyline) { + // if first point of clipped line coincides with first point of subject line, compensate for hack + if (polyline->points.front().coincides_with(subj_polyline->points.front())) { + polyline->points.front().translate(-1, 0); + break; + } + // since Clipper does not preserve orientation of polylines, check last point too + if (polyline->points.back().coincides_with(subj_polyline->points.front())) { + polyline->points.back().translate(-1, 0); + break; + } + } + } + + /* If the split_at_first_point() call above happens to split the polygon inside the clipping area + we would get two consecutive polylines instead of a single one, so we go through them in order + to recombine continuous polylines. */ + for (size_t i = 0; i < retval.size(); ++i) { + for (size_t j = i+1; j < retval.size(); ++j) { + if (retval[i].points.back().coincides_with(retval[j].points.front())) { + /* If last point of i coincides with first point of j, + append points of j to i and delete j */ + retval[i].points.insert(retval[i].points.end(), retval[j].points.begin()+1, retval[j].points.end()); + retval.erase(retval.begin() + j); + --j; + } else if (retval[i].points.front().coincides_with(retval[j].points.back())) { + /* If first point of i coincides with last point of j, + prepend points of j to i and delete j */ + retval[i].points.insert(retval[i].points.begin(), retval[j].points.begin(), retval[j].points.end()-1); + retval.erase(retval.begin() + j); + --j; + } else if (retval[i].points.front().coincides_with(retval[j].points.front())) { + /* Since Clipper does not preserve orientation of polylines, + also check the case when first point of i coincides with first point of j. */ + retval[j].reverse(); + retval[i].points.insert(retval[i].points.begin(), retval[j].points.begin(), retval[j].points.end()-1); + retval.erase(retval.begin() + j); + --j; + } else if (retval[i].points.back().coincides_with(retval[j].points.back())) { + /* Since Clipper does not preserve orientation of polylines, + also check the case when last point of i coincides with last point of j. */ + retval[j].reverse(); + retval[i].points.insert(retval[i].points.end(), retval[j].points.begin()+1, retval[j].points.end()); + retval.erase(retval.begin() + j); + --j; + } + } + } +} + +template +void diff(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType &retval, bool safety_offset_) { _clipper(ClipperLib::ctDifference, subject, clip, retval, safety_offset_); } -template void diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons &retval, bool safety_offset_); -template void diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polygons &retval, bool safety_offset_); +template void diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons &retval, bool safety_offset_); +template void diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polygons &retval, bool safety_offset_); +template void diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval, bool safety_offset_); +template void diff(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval, bool safety_offset_); -void diff(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval) -{ - _clipper(ClipperLib::ctDifference, subject, clip, retval); -} - -template -void intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, T &retval, bool safety_offset_) +template +void intersection(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType &retval, bool safety_offset_) { _clipper(ClipperLib::ctIntersection, subject, clip, retval, safety_offset_); } -template void intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons &retval, bool safety_offset_); -template void intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polygons &retval, bool safety_offset_); - -void intersection(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval) -{ - _clipper(ClipperLib::ctIntersection, subject, clip, retval); -} +template void intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons &retval, bool safety_offset_); +template void intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polygons &retval, bool safety_offset_); +template void intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval, bool safety_offset_); +template void intersection(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval, bool safety_offset_); void xor_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons &retval, bool safety_offset_) @@ -492,24 +555,19 @@ void simplify_polygons(const Slic3r::Polygons &subject, Slic3r::ExPolygons &retv PolyTreeToExPolygons(polytree, retval); } -void safety_offset(ClipperLib::Paths* &subject) +void safety_offset(ClipperLib::Paths* paths) { // scale input - scaleClipperPolygons(*subject, CLIPPER_OFFSET_SCALE); + scaleClipperPolygons(*paths, CLIPPER_OFFSET_SCALE); // perform offset (delta = scale 1e-05) - ClipperLib::Paths* retval = new ClipperLib::Paths(); ClipperLib::ClipperOffset co; co.MiterLimit = 2; - co.AddPaths(*subject, ClipperLib::jtMiter, ClipperLib::etClosedPolygon); - co.Execute(*retval, 10.0 * CLIPPER_OFFSET_SCALE); + co.AddPaths(*paths, ClipperLib::jtMiter, ClipperLib::etClosedPolygon); + co.Execute(*paths, 10.0 * CLIPPER_OFFSET_SCALE); // unscale output - scaleClipperPolygons(*retval, 1.0/CLIPPER_OFFSET_SCALE); - - // delete original data and switch pointer - delete subject; - subject = retval; + scaleClipperPolygons(*paths, 1.0/CLIPPER_OFFSET_SCALE); } /////////////////////// diff --git a/xs/src/ClipperUtils.hpp b/xs/src/ClipperUtils.hpp index b49dc2cd5f..2ab3ff775c 100644 --- a/xs/src/ClipperUtils.hpp +++ b/xs/src/ClipperUtils.hpp @@ -70,7 +70,7 @@ template void _clipper_do(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, T &retval, bool safety_offset_); void _clipper_do(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, - const Slic3r::Polygons &clip, ClipperLib::Paths &retval); + const Slic3r::Polygons &clip, ClipperLib::Paths &retval, bool safety_offset_); void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polygons &retval, bool safety_offset_); void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, @@ -78,15 +78,11 @@ void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval); -template -void diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, T &retval, bool safety_offset_ = false); +template +void diff(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType &retval, bool safety_offset_ = false); -void diff(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval); - -template -void intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, T &retval, bool safety_offset_ = false); - -void intersection(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval); +template +void intersection(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType &retval, bool safety_offset_ = false); void xor_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons &retval, bool safety_offset_ = false); @@ -101,7 +97,7 @@ static void traverse_pt(ClipperLib::PolyNodes &nodes, Slic3r::Polygons &retval); void simplify_polygons(const Slic3r::Polygons &subject, Slic3r::Polygons &retval, bool preserve_collinear = false); void simplify_polygons(const Slic3r::Polygons &subject, Slic3r::ExPolygons &retval, bool preserve_collinear = false); -void safety_offset(ClipperLib::Paths* &subject); +void safety_offset(ClipperLib::Paths* paths); ///////////////// diff --git a/xs/src/ExPolygon.cpp b/xs/src/ExPolygon.cpp index 6e21bd3742..e985353b75 100644 --- a/xs/src/ExPolygon.cpp +++ b/xs/src/ExPolygon.cpp @@ -223,7 +223,7 @@ ExPolygon::get_trapezoids2(Polygons* polygons) const // intersect with this expolygon Polygons trapezoids; - intersection(poly, *this, trapezoids); + intersection(poly, *this, trapezoids); // append results to return value polygons->insert(polygons->end(), trapezoids.begin(), trapezoids.end()); diff --git a/xs/src/ExtrusionEntity.cpp b/xs/src/ExtrusionEntity.cpp index 965c086f9f..2dae1be0ed 100644 --- a/xs/src/ExtrusionEntity.cpp +++ b/xs/src/ExtrusionEntity.cpp @@ -10,31 +10,6 @@ namespace Slic3r { -bool -ExtrusionEntity::is_perimeter() const -{ - return this->role == erPerimeter - || this->role == erExternalPerimeter - || this->role == erOverhangPerimeter - || this->role == erContourInternalPerimeter; -} - -bool -ExtrusionEntity::is_fill() const -{ - return this->role == erFill - || this->role == erSolidFill - || this->role == erTopSolidFill; -} - -bool -ExtrusionEntity::is_bridge() const -{ - return this->role == erBrige - || this->role == erInternalBridge - || this->role == erOverhangPerimeter; -} - ExtrusionPath* ExtrusionPath::clone() const { @@ -64,7 +39,7 @@ ExtrusionPath::intersect_expolygons(const ExPolygonCollection &collection, Extru { // perform clipping Polylines clipped; - intersection(this->polyline, collection, clipped); + intersection(this->polyline, collection, clipped); return this->_inflate_collection(clipped, retval); } @@ -73,7 +48,7 @@ ExtrusionPath::subtract_expolygons(const ExPolygonCollection &collection, Extrus { // perform clipping Polylines clipped; - diff(this->polyline, collection, clipped); + diff(this->polyline, collection, clipped); return this->_inflate_collection(clipped, retval); } @@ -95,6 +70,31 @@ ExtrusionPath::length() const return this->polyline.length(); } +bool +ExtrusionPath::is_perimeter() const +{ + return this->role == erPerimeter + || this->role == erExternalPerimeter + || this->role == erOverhangPerimeter + || this->role == erContourInternalPerimeter; +} + +bool +ExtrusionPath::is_fill() const +{ + return this->role == erFill + || this->role == erSolidFill + || this->role == erTopSolidFill; +} + +bool +ExtrusionPath::is_bridge() const +{ + return this->role == erBridge + || this->role == erInternalBridge + || this->role == erOverhangPerimeter; +} + void ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const { @@ -106,8 +106,22 @@ ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCo } #ifdef SLIC3RXS +SV* +ExtrusionPath::to_SV_ref() { + SV* sv = newSV(0); + sv_setref_pv( sv, perl_class_name_ref(this), this ); + return sv; +} + +SV* +ExtrusionPath::to_SV_clone_ref() const { + SV* sv = newSV(0); + sv_setref_pv( sv, perl_class_name(this), new ExtrusionPath(*this) ); + return sv; +} REGISTER_CLASS(ExtrusionPath, "ExtrusionPath"); +#endif std::string ExtrusionPath::gcode(Extruder* extruder, double e, double F, @@ -155,12 +169,12 @@ ExtrusionPath::gcode(Extruder* extruder, double e, double F, return stream.str(); } -#endif -ExtrusionLoop::ExtrusionLoop(const Polygon &polygon, ExtrusionRole role) +ExtrusionLoop::operator Polygon() const { - this->role = role; - this->set_polygon(polygon); + Polygon polygon; + this->polygon(&polygon); + return polygon; } ExtrusionLoop* @@ -169,32 +183,19 @@ ExtrusionLoop::clone() const return new ExtrusionLoop (*this); } -void -ExtrusionLoop::split_at_index(int index, ExtrusionPath* path) const +bool +ExtrusionLoop::make_clockwise() { - Polygon polygon; - this->polygon(&polygon); - - polygon.split_at_index(index, &path->polyline); - - path->role = this->role; - path->mm3_per_mm = this->mm3_per_mm; - path->width = this->width; - path->height = this->height; -} - -void -ExtrusionLoop::split_at_first_point(ExtrusionPath* path) const -{ - return this->split_at_index(0, path); + Polygon polygon = *this; + bool was_ccw = polygon.is_counter_clockwise(); + if (was_ccw) this->reverse(); + return was_ccw; } bool ExtrusionLoop::make_counter_clockwise() { - Polygon polygon; - this->polygon(&polygon); - + Polygon polygon = *this; bool was_cw = polygon.is_clockwise(); if (was_cw) this->reverse(); return was_cw; @@ -203,41 +204,116 @@ ExtrusionLoop::make_counter_clockwise() void ExtrusionLoop::reverse() { - for (Polylines::iterator polyline = this->polylines.begin(); polyline != this->polylines.end(); ++polyline) - polyline->reverse(); - std::reverse(this->polylines.begin(), this->polylines.end()); + for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path) + path->reverse(); + std::reverse(this->paths.begin(), this->paths.end()); } Point ExtrusionLoop::first_point() const { - return this->polylines.front().points.front(); + return this->paths.front().polyline.points.front(); } Point ExtrusionLoop::last_point() const { - return this->polylines.back().points.back(); // which coincides with first_point(), by the way -} - -void -ExtrusionLoop::set_polygon(const Polygon &polygon) -{ - Polyline polyline; - polygon.split_at_first_point(&polyline); - this->polylines.clear(); - this->polylines.push_back(polyline); + return this->paths.back().polyline.points.back(); // which coincides with first_point(), by the way } void ExtrusionLoop::polygon(Polygon* polygon) const { - for (Polylines::const_iterator polyline = this->polylines.begin(); polyline != this->polylines.end(); ++polyline) { + for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) { // for each polyline, append all points except the last one (because it coincides with the first one of the next polyline) - polygon->points.insert(polygon->points.end(), polyline->points.begin(), polyline->points.end()-1); + polygon->points.insert(polygon->points.end(), path->polyline.points.begin(), path->polyline.points.end()-1); } } +double +ExtrusionLoop::length() const +{ + double len = 0; + for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) + len += path->polyline.length(); + return len; +} + +void +ExtrusionLoop::split_at(const Point &point) +{ + for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path) { + int idx = path->polyline.find_point(point); + if (idx != -1) { + if (this->paths.size() == 1) { + // just change the order of points + path->polyline.points.insert(path->polyline.points.end(), path->polyline.points.begin() + 1, path->polyline.points.begin() + idx + 1); + path->polyline.points.erase(path->polyline.points.begin(), path->polyline.points.begin() + idx); + } else { + // if we have multiple paths we assume they have different types, so no need to + // check for continuity as we do for the single path case above + + // new paths list starts with the second half of current path + ExtrusionPaths new_paths; + { + ExtrusionPath p = *path; + p.polyline.points.erase(p.polyline.points.begin(), p.polyline.points.begin() + idx); + if (!p.polyline.points.empty()) new_paths.push_back(p); + } + + // then we add all paths until the end of current path list + new_paths.insert(new_paths.end(), this->paths.begin(), path); // not including this path + + // then we add all paths since the beginning of current list up to the previous one + new_paths.insert(new_paths.end(), path+1, this->paths.end()); // not including this path + + // finally we add the first half of current path + { + ExtrusionPath p = *path; + p.polyline.points.erase(p.polyline.points.begin() + idx + 1, p.polyline.points.end()); + if (!p.polyline.points.empty()) new_paths.push_back(p); + } + // we can now override the old path list with the new one and stop looping + this->paths = new_paths; + } + return; + } + } + CONFESS("Point not found"); +} + +void +ExtrusionLoop::clip_end(double distance, ExtrusionPaths* paths) const +{ + *paths = this->paths; + + while (distance > 0 && !paths->empty()) { + ExtrusionPath &last = paths->back(); + double len = last.length(); + if (len <= distance) { + paths->pop_back(); + distance -= len; + } else { + last.polyline.clip_end(distance); + break; + } + } +} + +bool +ExtrusionLoop::has_overhang_point(const Point &point) const +{ + for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) { + int pos = path->polyline.find_point(point); + if (pos != -1) { + // point belongs to this path + // we consider it overhang only if it's not an endpoint + return (path->is_bridge() && pos > 0 && pos != path->polyline.points.size()-1); + } + } + return false; +} + #ifdef SLIC3RXS REGISTER_CLASS(ExtrusionLoop, "ExtrusionLoop"); #endif diff --git a/xs/src/ExtrusionEntity.hpp b/xs/src/ExtrusionEntity.hpp index ac57e26b5e..562faf6f63 100644 --- a/xs/src/ExtrusionEntity.hpp +++ b/xs/src/ExtrusionEntity.hpp @@ -19,7 +19,7 @@ enum ExtrusionRole { erFill, erSolidFill, erTopSolidFill, - erBrige, + erBridge, erInternalBridge, erSkirt, erSupportMaterial, @@ -29,19 +29,11 @@ enum ExtrusionRole { class ExtrusionEntity { public: - ExtrusionEntity() : mm3_per_mm(-1), width(-1), height(-1) {}; virtual ExtrusionEntity* clone() const = 0; virtual ~ExtrusionEntity() {}; - ExtrusionRole role; - double mm3_per_mm; // mm^3 of plastic per mm of linear head motion - float width; - float height; virtual void reverse() = 0; virtual Point first_point() const = 0; virtual Point last_point() const = 0; - bool is_perimeter() const; - bool is_fill() const; - bool is_bridge() const; }; typedef std::vector ExtrusionEntitiesPtr; @@ -49,8 +41,14 @@ typedef std::vector ExtrusionEntitiesPtr; class ExtrusionPath : public ExtrusionEntity { public: - ExtrusionPath* clone() const; Polyline polyline; + ExtrusionRole role; + double mm3_per_mm; // mm^3 of plastic per mm of linear head motion + float width; + float height; + + ExtrusionPath() : mm3_per_mm(-1), width(-1), height(-1) {}; + ExtrusionPath* clone() const; void reverse(); Point first_point() const; Point last_point() const; @@ -59,32 +57,41 @@ class ExtrusionPath : public ExtrusionEntity void clip_end(double distance); void simplify(double tolerance); double length() const; - - #ifdef SLIC3RXS + bool is_perimeter() const; + bool is_fill() const; + bool is_bridge() const; std::string gcode(Extruder* extruder, double e, double F, double xofs, double yofs, std::string extrusion_axis, std::string gcode_line_suffix) const; + + #ifdef SLIC3RXS + SV* to_SV_ref(); + SV* to_SV_clone_ref() const; #endif private: void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const; }; +typedef std::vector ExtrusionPaths; + class ExtrusionLoop : public ExtrusionEntity { public: - Polylines polylines; + ExtrusionPaths paths; - ExtrusionLoop(const Polygon &polygon, ExtrusionRole role); + operator Polygon() const; ExtrusionLoop* clone() const; - void split_at_index(int index, ExtrusionPath* path) const; - void split_at_first_point(ExtrusionPath* path) const; + bool make_clockwise(); bool make_counter_clockwise(); void reverse(); Point first_point() const; Point last_point() const; - void set_polygon(const Polygon &polygon); void polygon(Polygon* polygon) const; + double length() const; + void split_at(const Point &point); + void clip_end(double distance, ExtrusionPaths* paths) const; + bool has_overhang_point(const Point &point) const; }; } diff --git a/xs/src/MultiPoint.cpp b/xs/src/MultiPoint.cpp index 42ec4f09d6..79912fc76f 100644 --- a/xs/src/MultiPoint.cpp +++ b/xs/src/MultiPoint.cpp @@ -55,6 +55,15 @@ MultiPoint::is_valid() const return this->points.size() >= 2; } +int +MultiPoint::find_point(const Point &point) const +{ + for (Points::const_iterator it = this->points.begin(); it != this->points.end(); ++it) { + if (it->coincides_with(point)) return it - this->points.begin(); + } + return -1; // not found +} + Points MultiPoint::_douglas_peucker(const Points &points, const double tolerance) { diff --git a/xs/src/MultiPoint.hpp b/xs/src/MultiPoint.hpp index 087b3cb47b..ccc12748a8 100644 --- a/xs/src/MultiPoint.hpp +++ b/xs/src/MultiPoint.hpp @@ -21,6 +21,7 @@ class MultiPoint virtual Lines lines() const = 0; double length() const; bool is_valid() const; + int find_point(const Point &point) const; static Points _douglas_peucker(const Points &points, const double tolerance); #ifdef SLIC3RXS diff --git a/xs/src/Polygon.cpp b/xs/src/Polygon.cpp index 72514b0dd5..ad31b5b62f 100644 --- a/xs/src/Polygon.cpp +++ b/xs/src/Polygon.cpp @@ -15,6 +15,13 @@ Polygon::operator Polygons() const return pp; } +Polygon::operator Polyline() const +{ + Polyline polyline; + this->split_at_first_point(&polyline); + return polyline; +} + Point& Polygon::operator[](Points::size_type idx) { diff --git a/xs/src/Polygon.hpp b/xs/src/Polygon.hpp index c8570d763d..67e6e4a476 100644 --- a/xs/src/Polygon.hpp +++ b/xs/src/Polygon.hpp @@ -15,6 +15,7 @@ typedef std::vector Polygons; class Polygon : public MultiPoint { public: operator Polygons() const; + operator Polyline() const; Point& operator[](Points::size_type idx); const Point& operator[](Points::size_type idx) const; Point last_point() const; diff --git a/xs/t/08_extrusionloop.t b/xs/t/08_extrusionloop.t index aad73dbb1e..0eff87c77b 100644 --- a/xs/t/08_extrusionloop.t +++ b/xs/t/08_extrusionloop.t @@ -3,37 +3,86 @@ use strict; use warnings; +use List::Util qw(sum); use Slic3r::XS; -use Test::More tests => 8; - -my $square = [ - [100, 100], - [200, 100], - [200, 200], - [100, 200], -]; - -my $loop = Slic3r::ExtrusionLoop->new( - polygon => Slic3r::Polygon->new(@$square), - role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, - mm3_per_mm => 1, -); -isa_ok $loop, 'Slic3r::ExtrusionLoop'; -isa_ok $loop->polygon, 'Slic3r::Polygon', 'loop polygon'; -is_deeply $loop->polygon->pp, $square, 'polygon points roundtrip'; - -$loop = $loop->clone; - -is $loop->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'role'; -$loop->role(Slic3r::ExtrusionPath::EXTR_ROLE_FILL); -is $loop->role, Slic3r::ExtrusionPath::EXTR_ROLE_FILL, 'modify role'; +use Test::More tests => 30; { - my $path = $loop->split_at_first_point; - is_deeply $path->polyline->pp, [ @$square[0,1,2,3,0] ], 'split_at_first_point'; - is $path->role, $loop->role, 'role preserved after split'; + my $square = [ + [100, 100], + [200, 100], + [200, 200], + [100, 200], + ]; + my $square_p = Slic3r::Polygon->new(@$square); + + my $loop = Slic3r::ExtrusionLoop->new; + $loop->append(Slic3r::ExtrusionPath->new( + polyline => $square_p->split_at_first_point, + role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, + mm3_per_mm => 1, + )); + + isa_ok $loop, 'Slic3r::ExtrusionLoop'; + isa_ok $loop->polygon, 'Slic3r::Polygon', 'loop polygon'; + is $loop->polygon->area, $square_p->area, 'polygon area'; + is $loop->length, $square_p->length(), 'loop length'; + + $loop = $loop->clone; + + is scalar(@$loop), 1, 'loop contains one path'; + { + my $path = $loop->[0]; + isa_ok $path, 'Slic3r::ExtrusionPath::Ref'; + is $path->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'role'; + $path->role(Slic3r::ExtrusionPath::EXTR_ROLE_FILL); + is $path->role, Slic3r::ExtrusionPath::EXTR_ROLE_FILL, 'modify role'; + } + + $loop->split_at($square_p->[2]); + is scalar(@$loop), 1, 'splitting a single-path loop results in a single path'; + is scalar(@{$loop->[0]->polyline}), 5, 'path has correct number of points'; + ok $loop->[0]->polyline->[0]->coincides_with($square_p->[2]), 'expected point order'; + ok $loop->[0]->polyline->[1]->coincides_with($square_p->[3]), 'expected point order'; + ok $loop->[0]->polyline->[2]->coincides_with($square_p->[0]), 'expected point order'; + ok $loop->[0]->polyline->[3]->coincides_with($square_p->[1]), 'expected point order'; + ok $loop->[0]->polyline->[4]->coincides_with($square_p->[2]), 'expected point order'; +} + +{ + my $polyline1 = Slic3r::Polyline->new([100,100], [200,100], [200,200]); + my $polyline2 = Slic3r::Polyline->new([200,200], [100,200], [100,100]); - is_deeply $loop->split_at_index(2)->polyline->pp, [ @$square[2,3,0,1,2] ], 'split_at_index'; + my $loop = Slic3r::ExtrusionLoop->new_from_paths( + Slic3r::ExtrusionPath->new( + polyline => $polyline1, + role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, + mm3_per_mm => 1, + ), + Slic3r::ExtrusionPath->new( + polyline => $polyline2, + role => Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, + mm3_per_mm => 1, + ), + ); + is $loop->length, sum($polyline1->length, $polyline2->length), 'length'; + is scalar(@$loop), 2, 'loop contains two paths'; + $loop->split_at($polyline1->[1]); + is $loop->length, sum($polyline1->length, $polyline2->length), 'length after splitting'; + is scalar(@$loop), 3, 'loop contains three paths after splitting'; + ok $loop->[0]->polyline->[0]->coincides_with($polyline1->[1]), 'expected starting point'; + ok $loop->[-1]->polyline->[-1]->coincides_with($polyline1->[1]), 'expected ending point'; + ok $loop->[0]->polyline->[-1]->coincides_with($loop->[1]->polyline->[0]), 'paths have common point'; + ok $loop->[1]->polyline->[-1]->coincides_with($loop->[2]->polyline->[0]), 'paths have common point'; + is $loop->[0]->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'expected order after splitting'; + is $loop->[1]->role, Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, 'expected order after splitting'; + is $loop->[2]->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'expected order after splitting'; + is scalar(@{$loop->[0]->polyline}), 2, 'path has correct number of points'; + is scalar(@{$loop->[1]->polyline}), 3, 'path has correct number of points'; + is scalar(@{$loop->[2]->polyline}), 2, 'path has correct number of points'; + + my @paths = @{$loop->clip_end(3)}; + is sum(map $_->length, @paths), $loop->length - 3, 'returned paths have expected length'; } __END__ diff --git a/xs/t/11_clipper.t b/xs/t/11_clipper.t index 84ea297550..ca00bc6e3e 100644 --- a/xs/t/11_clipper.t +++ b/xs/t/11_clipper.t @@ -4,20 +4,20 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 11; +use Test::More tests => 17; -my $square = [ # ccw +my $square = Slic3r::Polygon->new( # ccw [200, 100], [200, 200], [100, 200], [100, 100], -]; -my $hole_in_square = [ # cw +); +my $hole_in_square = Slic3r::Polygon->new( # cw [160, 140], [140, 140], [140, 160], [160, 160], -]; +); my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); { @@ -106,4 +106,53 @@ my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); } } +if (0) { # Clipper does not preserve polyline orientation + my $polyline = Slic3r::Polyline->new([50,150], [300,150]); + my $result = Slic3r::Geometry::Clipper::intersection_pl([$polyline], [$square]); + is scalar(@$result), 1, 'intersection_pl - correct number of result lines'; + is_deeply $result->[0]->pp, [[100,150], [200,150]], 'clipped line orientation is preserved'; +} + +if (0) { # Clipper does not preserve polyline orientation + my $polyline = Slic3r::Polyline->new([300,150], [50,150]); + my $result = Slic3r::Geometry::Clipper::intersection_pl([$polyline], [$square]); + is scalar(@$result), 1, 'intersection_pl - correct number of result lines'; + is_deeply $result->[0]->pp, [[200,150], [100,150]], 'clipped line orientation is preserved'; +} + +if (0) { # Clipper does not preserve polyline orientation + my $result = Slic3r::Geometry::Clipper::intersection_ppl([$hole_in_square], [$square]); + is_deeply $result->[0]->pp, $hole_in_square->split_at_first_point->pp, + 'intersection_ppl - clipping cw polygon as polyline preserves winding order'; +} + +{ + my $square2 = $square->clone; + $square2->translate(50,50); + { + my $result = Slic3r::Geometry::Clipper::intersection_ppl([$square2], [$square]); + is scalar(@$result), 1, 'intersection_ppl - result contains a single line'; + is scalar(@{$result->[0]}), 3, 'intersection_ppl - result contains expected number of points'; + # Clipper does not preserve polyline orientation so we only check the middle point + ###ok $result->[0][0]->coincides_with(Slic3r::Point->new(150,200)), 'intersection_ppl - expected point order'; + ok $result->[0][1]->coincides_with(Slic3r::Point->new(150,150)), 'intersection_ppl - expected point order'; + ###ok $result->[0][2]->coincides_with(Slic3r::Point->new(200,150)), 'intersection_ppl - expected point order'; + } +} + +{ + my $square2 = $square->clone; + $square2->reverse; + $square2->translate(50,50); + { + my $result = Slic3r::Geometry::Clipper::intersection_ppl([$square2], [$square]); + is scalar(@$result), 1, 'intersection_ppl - result contains a single line'; + is scalar(@{$result->[0]}), 3, 'intersection_ppl - result contains expected number of points'; + # Clipper does not preserve polyline orientation so we only check the middle point + ###ok $result->[0][0]->coincides_with(Slic3r::Point->new(200,150)), 'intersection_ppl - expected point order'; + ok $result->[0][1]->coincides_with(Slic3r::Point->new(150,150)), 'intersection_ppl - expected point order'; + ###ok $result->[0][2]->coincides_with(Slic3r::Point->new(150,200)), 'intersection_ppl - expected point order'; + } +} + __END__ diff --git a/xs/t/12_extrusionpathcollection.t b/xs/t/12_extrusionpathcollection.t index e198748b2a..6933f965a1 100644 --- a/xs/t/12_extrusionpathcollection.t +++ b/xs/t/12_extrusionpathcollection.t @@ -18,10 +18,12 @@ my $path = Slic3r::ExtrusionPath->new( mm3_per_mm => 1, ); -my $loop = Slic3r::ExtrusionLoop->new( - polygon => Slic3r::Polygon->new(@$points), - role => Slic3r::ExtrusionPath::EXTR_ROLE_FILL, - mm3_per_mm => 1, +my $loop = Slic3r::ExtrusionLoop->new_from_paths( + Slic3r::ExtrusionPath->new( + polyline => Slic3r::Polygon->new(@$points)->split_at_first_point, + role => Slic3r::ExtrusionPath::EXTR_ROLE_FILL, + mm3_per_mm => 1, + ), ); my $collection = Slic3r::ExtrusionPath::Collection->new($path); diff --git a/xs/xsp/Clipper.xsp b/xs/xsp/Clipper.xsp index dfd7ec5647..41fa3ef829 100644 --- a/xs/xsp/Clipper.xsp +++ b/xs/xsp/Clipper.xsp @@ -101,6 +101,15 @@ diff_pl(subject, clip) OUTPUT: RETVAL +Polylines +diff_ppl(subject, clip) + Polygons subject + Polygons clip + CODE: + diff(subject, clip, RETVAL); + OUTPUT: + RETVAL + Polygons intersection(subject, clip, safety_offset = false) Polygons subject @@ -130,6 +139,15 @@ intersection_pl(subject, clip) OUTPUT: RETVAL +Polylines +intersection_ppl(subject, clip) + Polygons subject + Polygons clip + CODE: + intersection(subject, clip, RETVAL); + OUTPUT: + RETVAL + ExPolygons xor_ex(subject, clip, safety_offset = false) Polygons subject diff --git a/xs/xsp/ExPolygonCollection.xsp b/xs/xsp/ExPolygonCollection.xsp index 31b100157f..825b5ad03d 100644 --- a/xs/xsp/ExPolygonCollection.xsp +++ b/xs/xsp/ExPolygonCollection.xsp @@ -21,6 +21,8 @@ bool contains_point(Point* point) %code{% RETVAL = THIS->contains_point(*point); %}; void simplify(double tolerance); + Polygons polygons() + %code{% RETVAL = *THIS; %}; %{ ExPolygonCollection* diff --git a/xs/xsp/ExtrusionLoop.xsp b/xs/xsp/ExtrusionLoop.xsp index b53eca32be..14805868a9 100644 --- a/xs/xsp/ExtrusionLoop.xsp +++ b/xs/xsp/ExtrusionLoop.xsp @@ -7,92 +7,38 @@ %} %name{Slic3r::ExtrusionLoop} class ExtrusionLoop { + ExtrusionLoop(); ~ExtrusionLoop(); - SV* arrayref() - %code{% Polygon polygon; THIS->polygon(&polygon); RETVAL = polygon.to_AV(); %}; - SV* pp() - %code{% Polygon polygon; THIS->polygon(&polygon); RETVAL = polygon.to_SV_pureperl(); %}; + Clone clone() + %code{% RETVAL = THIS; %}; void reverse(); - ExtrusionPath* split_at_index(int index) - %code{% RETVAL = new ExtrusionPath (); THIS->split_at_index(index, RETVAL); %}; - ExtrusionPath* split_at_first_point() - %code{% RETVAL = new ExtrusionPath (); THIS->split_at_first_point(RETVAL); %}; + bool make_clockwise(); bool make_counter_clockwise(); Clone first_point(); Clone last_point(); - bool is_perimeter(); - bool is_fill(); - bool is_bridge(); + Polygon* polygon() + %code{% RETVAL = new Polygon (*THIS); %}; + void append(ExtrusionPath* path) + %code{% THIS->paths.push_back(*path); %}; + double length(); + void split_at(Point* point) + %code{% THIS->split_at(*point); %}; + ExtrusionPaths clip_end(double distance) + %code{% THIS->clip_end(distance, &RETVAL); %}; + bool has_overhang_point(Point* point) + %code{% RETVAL = THIS->has_overhang_point(*point); %}; %{ -ExtrusionLoop* -_new(CLASS, polygon_sv, role, mm3_per_mm, width, height) - char* CLASS; - SV* polygon_sv; - ExtrusionRole role; - double mm3_per_mm; - float width; - float height; +SV* +ExtrusionLoop::arrayref() CODE: - Polygon polygon; - polygon.from_SV_check(polygon_sv); - RETVAL = new ExtrusionLoop (polygon, role); - RETVAL->mm3_per_mm = mm3_per_mm; - RETVAL->width = width; - RETVAL->height = height; - OUTPUT: - RETVAL - -Polygon* -ExtrusionLoop::polygon(...) - CODE: - if (items > 1) { - Polygon polygon; - polygon.from_SV_check( ST(1) ); - THIS->set_polygon(polygon); + AV* av = newAV(); + av_fill(av, THIS->paths.size()-1); + int i = 0; + for (ExtrusionPaths::iterator it = THIS->paths.begin(); it != THIS->paths.end(); ++it) { + av_store(av, i++, it->to_SV_ref()); } - RETVAL = new Polygon (); - THIS->polygon(RETVAL); - OUTPUT: - RETVAL - -ExtrusionRole -ExtrusionLoop::role(...) - CODE: - if (items > 1) { - THIS->role = (ExtrusionRole)SvUV(ST(1)); - } - RETVAL = THIS->role; - OUTPUT: - RETVAL - -double -ExtrusionLoop::mm3_per_mm(...) - CODE: - if (items > 1) { - THIS->mm3_per_mm = (double)SvNV(ST(1)); - } - RETVAL = THIS->mm3_per_mm; - OUTPUT: - RETVAL - -float -ExtrusionLoop::width(...) - CODE: - if (items > 1) { - THIS->width = (float)SvNV(ST(1)); - } - RETVAL = THIS->width; - OUTPUT: - RETVAL - -float -ExtrusionLoop::height(...) - CODE: - if (items > 1) { - THIS->height = (float)SvNV(ST(1)); - } - RETVAL = THIS->height; + RETVAL = newRV_noinc((SV*)av); OUTPUT: RETVAL diff --git a/xs/xsp/ExtrusionPath.xsp b/xs/xsp/ExtrusionPath.xsp index e4cc6e3f4e..a88a37f413 100644 --- a/xs/xsp/ExtrusionPath.xsp +++ b/xs/xsp/ExtrusionPath.xsp @@ -140,7 +140,7 @@ _constant() EXTR_ROLE_FILL = erFill EXTR_ROLE_SOLIDFILL = erSolidFill EXTR_ROLE_TOPSOLIDFILL = erTopSolidFill - EXTR_ROLE_BRIDGE = erBrige + EXTR_ROLE_BRIDGE = erBridge EXTR_ROLE_INTERNALBRIDGE = erInternalBridge EXTR_ROLE_SKIRT = erSkirt EXTR_ROLE_SUPPORTMATERIAL = erSupportMaterial diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 27afe53bb0..897e3d0f1f 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -97,6 +97,7 @@ Lines T_ARRAYREF Polygons T_ARRAYREF Polylines T_ARRAYREF ExPolygons T_ARRAYREF +ExtrusionPaths T_ARRAYREF Surfaces T_ARRAYREF # we return these types whenever we want the items to be returned diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 3aed02f7a4..82e4f4d18c 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -70,6 +70,7 @@ %typemap{Polylines}; %typemap{PrintState}; %typemap{ExPolygons}; +%typemap{ExtrusionPaths}; %typemap{Surfaces}; %typemap{Polygons*}; %typemap{TriangleMeshPtrs};