diff --git a/Build.PL b/Build.PL index 06261f77a..68a9af16e 100644 --- a/Build.PL +++ b/Build.PL @@ -15,7 +15,6 @@ my %prereqs = qw( File::Basename 0 File::Spec 0 Getopt::Long 0 - Math::PlanePath 53 Module::Build::WithXSpp 0.14 Moo 1.003001 POSIX 0 @@ -112,11 +111,6 @@ EOF # make sure our cpanm is updated (old ones don't support the ~ syntax) system $cpanm, @cpanm_args, 'App::cpanminus'; - # install the Windows-compatible Math::Libm - if ($^O eq 'MSWin32' && !eval "use Math::Libm; 1") { - system $cpanm, @cpanm_args, 'https://github.com/alexrj/Math-Libm/tarball/master'; - } - my %modules = (%prereqs, %recommends); foreach my $module (sort keys %modules) { my $version = $modules{$module}; diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index aa9aea28c..4a1bd6026 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -39,7 +39,6 @@ use Slic3r::Config; use Slic3r::ExPolygon; use Slic3r::ExtrusionLoop; use Slic3r::ExtrusionPath; -use Slic3r::Fill; use Slic3r::Flow; use Slic3r::Format::AMF; use Slic3r::Format::OBJ; @@ -113,6 +112,12 @@ sub spawn_thread { return $thread; } +# If the threading is enabled, spawn a set of threads. +# Otherwise run the task on the current thread. +# Used for +# Slic3r::Print::Object->layers->make_perimeters +# Slic3r::Print::Object->layers->make_fill +# Slic3r::Print::SupportMaterial::generate_toolpaths sub parallelize { my %params = @_; @@ -193,6 +198,7 @@ sub thread_cleanup { *Slic3r::ExtrusionLoop::DESTROY = sub {}; *Slic3r::ExtrusionPath::DESTROY = sub {}; *Slic3r::ExtrusionPath::Collection::DESTROY = sub {}; + *Slic3r::Filler::DESTROY = sub {}; *Slic3r::Flow::DESTROY = sub {}; *Slic3r::GCode::DESTROY = sub {}; *Slic3r::GCode::AvoidCrossingPerimeters::DESTROY = sub {}; diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm deleted file mode 100644 index 908d07528..000000000 --- a/lib/Slic3r/Fill.pm +++ /dev/null @@ -1,303 +0,0 @@ -package Slic3r::Fill; -use Moo; - -use List::Util qw(max); -use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Fill::3DHoneycomb; -use Slic3r::Fill::Base; -use Slic3r::Fill::Concentric; -use Slic3r::Fill::Honeycomb; -use Slic3r::Fill::PlanePath; -use Slic3r::Fill::Rectilinear; -use Slic3r::Fill::AlignedRectilinear; -use Slic3r::Flow ':roles'; -use Slic3r::Geometry qw(X Y PI scale chained_path deg2rad); -use Slic3r::Geometry::Clipper qw(union union_ex diff diff_ex intersection_ex offset offset2); -use Slic3r::Surface ':types'; - - -has 'bounding_box' => (is => 'ro', required => 0); -has 'fillers' => (is => 'rw', default => sub { {} }); - -our %FillTypes = ( - archimedeanchords => 'Slic3r::Fill::ArchimedeanChords', - alignedrectilinear => 'Slic3r::Fill::AlignedRectilinear', - rectilinear => 'Slic3r::Fill::Rectilinear', - grid => 'Slic3r::Fill::Grid', - flowsnake => 'Slic3r::Fill::Flowsnake', - octagramspiral => 'Slic3r::Fill::OctagramSpiral', - hilbertcurve => 'Slic3r::Fill::HilbertCurve', - line => 'Slic3r::Fill::Line', - concentric => 'Slic3r::Fill::Concentric', - honeycomb => 'Slic3r::Fill::Honeycomb', - '3dhoneycomb' => 'Slic3r::Fill::3DHoneycomb', -); - -sub filler { - my $self = shift; - my ($filler) = @_; - - if (!ref $self) { - return $FillTypes{$filler}->new; - } - - $self->fillers->{$filler} ||= $FillTypes{$filler}->new( - bounding_box => $self->bounding_box, - ); - return $self->fillers->{$filler}; -} - -sub make_fill { - my $self = shift; - my ($layerm) = @_; - - Slic3r::debugf "Filling layer %d:\n", $layerm->layer->id; - - my $fill_density = $layerm->region->config->fill_density; - my $infill_flow = $layerm->flow(FLOW_ROLE_INFILL); - my $solid_infill_flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL); - my $top_solid_infill_flow = $layerm->flow(FLOW_ROLE_TOP_SOLID_INFILL); - - my @surfaces = (); - - # merge adjacent surfaces - # in case of bridge surfaces, the ones with defined angle will be attached to the ones - # without any angle (shouldn't this logic be moved to process_external_surfaces()?) - { - my @surfaces_with_bridge_angle = grep { $_->bridge_angle >= 0 } @{$layerm->fill_surfaces}; - - # group surfaces by distinct properties - my @groups = @{$layerm->fill_surfaces->group}; - - # merge compatible groups (we can generate continuous infill for them) - { - # cache flow widths and patterns used for all solid groups - # (we'll use them for comparing compatible groups) - my @is_solid = my @fw = my @pattern = (); - for (my $i = 0; $i <= $#groups; $i++) { - # we can only merge solid non-bridge surfaces, so discard - # non-solid surfaces - if ($groups[$i][0]->is_solid && (!$groups[$i][0]->is_bridge || $layerm->layer->id == 0)) { - $is_solid[$i] = 1; - $fw[$i] = ($groups[$i][0]->surface_type == S_TYPE_TOP) - ? $top_solid_infill_flow->width - : $solid_infill_flow->width; - $pattern[$i] = $groups[$i][0]->is_external - ? $layerm->region->config->external_fill_pattern - : 'rectilinear'; - } else { - $is_solid[$i] = 0; - $fw[$i] = 0; - $pattern[$i] = 'none'; - } - } - - # loop through solid groups - for (my $i = 0; $i <= $#groups; $i++) { - next if !$is_solid[$i]; - - # find compatible groups and append them to this one - for (my $j = $i+1; $j <= $#groups; $j++) { - next if !$is_solid[$j]; - - if ($fw[$i] == $fw[$j] && $pattern[$i] eq $pattern[$j]) { - # groups are compatible, merge them - push @{$groups[$i]}, @{$groups[$j]}; - splice @groups, $j, 1; - splice @is_solid, $j, 1; - splice @fw, $j, 1; - splice @pattern, $j, 1; - } - } - } - } - - # give priority to bridges - @groups = sort { ($a->[0]->bridge_angle >= 0) ? -1 : 0 } @groups; - - foreach my $group (@groups) { - my $union_p = union([ map $_->p, @$group ], 1); - - # subtract surfaces having a defined bridge_angle from any other - if (@surfaces_with_bridge_angle && $group->[0]->bridge_angle < 0) { - $union_p = diff( - $union_p, - [ map $_->p, @surfaces_with_bridge_angle ], - 1, - ); - } - - # subtract any other surface already processed - my $union = diff_ex( - $union_p, - [ map $_->p, @surfaces ], - 1, - ); - - push @surfaces, map $group->[0]->clone(expolygon => $_), @$union; - } - } - - # we need to detect any narrow surfaces that might collapse - # when adding spacing below - # such narrow surfaces are often generated in sloping walls - # by bridge_over_infill() and combine_infill() as a result of the - # subtraction of the combinable area from the layer infill area, - # which leaves small areas near the perimeters - # we are going to grow such regions by overlapping them with the void (if any) - # TODO: detect and investigate whether there could be narrow regions without - # any void neighbors - { - my $distance_between_surfaces = max( - $infill_flow->scaled_spacing, - $solid_infill_flow->scaled_spacing, - $top_solid_infill_flow->scaled_spacing, - ); - my $collapsed = diff( - [ map @{$_->expolygon}, @surfaces ], - offset2([ map @{$_->expolygon}, @surfaces ], -$distance_between_surfaces/2, +$distance_between_surfaces/2), - 1, - ); - push @surfaces, map Slic3r::Surface->new( - expolygon => $_, - surface_type => S_TYPE_INTERNALSOLID, - ), @{intersection_ex( - offset($collapsed, $distance_between_surfaces), - [ - (map @{$_->expolygon}, grep $_->surface_type == S_TYPE_INTERNALVOID, @surfaces), - (@$collapsed), - ], - 1, - )}; - } - - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output("fill_" . $layerm->print_z . ".svg", - expolygons => [ map $_->expolygon, grep !$_->is_solid, @surfaces ], - red_expolygons => [ map $_->expolygon, grep $_->is_solid, @surfaces ], - ); - } - - my @fills = (); - SURFACE: foreach my $surface (@surfaces) { - next if $surface->surface_type == S_TYPE_INTERNALVOID; - my $filler = $layerm->region->config->fill_pattern; - my $density = $fill_density; - my $role = ($surface->surface_type == S_TYPE_TOP) ? FLOW_ROLE_TOP_SOLID_INFILL - : $surface->is_solid ? FLOW_ROLE_SOLID_INFILL - : FLOW_ROLE_INFILL; - my $is_bridge = $layerm->layer->id > 0 && $surface->is_bridge; - my $is_solid = $surface->is_solid; - - if ($surface->is_solid) { - $density = 100; - $filler = 'rectilinear'; - if ($surface->is_external && !$is_bridge) { - $filler = $layerm->region->config->external_fill_pattern; - } - } else { - next SURFACE unless $density > 0; - } - - # get filler object - my $f = $self->filler($filler); - - # calculate the actual flow we'll be using for this infill - my $h = $surface->thickness == -1 ? $layerm->layer->height : $surface->thickness; - my $flow = $layerm->region->flow( - $role, - $h, - $is_bridge || $f->use_bridge_flow, - $layerm->layer->id == 0, - -1, - $layerm->layer->object, - ); - - # calculate flow spacing for infill pattern generation - my $using_internal_flow = 0; - if (!$is_solid && !$is_bridge) { - # it's internal infill, so we can calculate a generic flow spacing - # for all layers, for avoiding the ugly effect of - # misaligned infill on first layer because of different extrusion width and - # layer height - my $internal_flow = $layerm->region->flow( - FLOW_ROLE_INFILL, - $layerm->layer->object->config->layer_height, # TODO: handle infill_every_layers? - 0, # no bridge - 0, # no first layer - -1, # auto width - $layerm->layer->object, - ); - $f->spacing($internal_flow->spacing); - $using_internal_flow = 1; - # create the actual flow for internal flow that is used later. - $flow = Slic3r::Flow->new_from_spacing( - spacing => $internal_flow->spacing, - nozzle_diameter => $flow->nozzle_diameter, - layer_height => $h, - bridge => 0, - ); - } else { - $f->spacing($flow->spacing); - } - - $f->layer_id($layerm->layer->id); - $f->z($layerm->layer->print_z); - $f->angle(deg2rad($layerm->region->config->fill_angle)); - $f->loop_clipping(scale($flow->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); - - # apply half spacing using this flow's own spacing and generate infill - my @polylines = map $f->fill_surface( - $_, - density => $density/100, - layer_height => $h, - ), @{ $surface->offset(-scale($f->spacing)/2) }; - - next unless @polylines; - - # calculate actual flow from spacing (which might have been adjusted by the infill - # pattern generator) - if ($using_internal_flow) { - # if we used the internal flow we're not doing a solid infill - # so we can safely ignore the slight variation that might have - # been applied to $f->flow_spacing - } else { - $flow = Slic3r::Flow->new_from_spacing( - spacing => $f->spacing, - nozzle_diameter => $flow->nozzle_diameter, - layer_height => $h, - bridge => $is_bridge || $f->use_bridge_flow, - ); - } - my $mm3_per_mm = $flow->mm3_per_mm; - - # save into layer - { - my $role = $is_bridge ? EXTR_ROLE_BRIDGE - : $is_solid ? (($surface->surface_type == S_TYPE_TOP) ? EXTR_ROLE_TOPSOLIDFILL : EXTR_ROLE_SOLIDFILL) - : EXTR_ROLE_FILL; - - push @fills, my $collection = Slic3r::ExtrusionPath::Collection->new; - $collection->no_sort($f->no_sort); - $collection->append( - map Slic3r::ExtrusionPath->new( - polyline => $_, - role => $role, - mm3_per_mm => $mm3_per_mm, - width => $flow->width, - height => $flow->height, - ), @polylines, - ); - } - } - - # add thin fill regions - foreach my $thin_fill (@{$layerm->thin_fills}) { - push @fills, Slic3r::ExtrusionPath::Collection->new($thin_fill); - } - - return @fills; -} - -1; diff --git a/lib/Slic3r/Fill/3DHoneycomb.pm b/lib/Slic3r/Fill/3DHoneycomb.pm deleted file mode 100644 index 3bf7e547f..000000000 --- a/lib/Slic3r/Fill/3DHoneycomb.pm +++ /dev/null @@ -1,230 +0,0 @@ -package Slic3r::Fill::3DHoneycomb; -use Moo; - -extends 'Slic3r::Fill::Base'; - -use POSIX qw(ceil fmod); -use Slic3r::Geometry qw(scale scaled_epsilon); -use Slic3r::Geometry::Clipper qw(intersection_pl); - -# require bridge flow since most of this pattern hangs in air -sub use_bridge_flow { 1 } - -sub fill_surface { - my ($self, $surface, %params) = @_; - - my $expolygon = $surface->expolygon; - my $bb = $expolygon->bounding_box; - my $size = $bb->size; - - my $distance = scale($self->spacing) / $params{density}; - - # align bounding box to a multiple of our honeycomb grid module - # (a module is 2*$distance since one $distance half-module is - # growing while the other $distance half-module is shrinking) - { - my $min = $bb->min_point; - $min->translate( - -($bb->x_min % (2*$distance)), - -($bb->y_min % (2*$distance)), - ); - $bb->merge_point($min); - } - - # generate pattern - my @polylines = map Slic3r::Polyline->new(@$_), - makeGrid( - scale($self->z), - $distance, - ceil($size->x / $distance) + 1, - ceil($size->y / $distance) + 1, #// - (($self->layer_id / $surface->thickness_layers) % 2) + 1, - ); - - # move pattern in place - $_->translate($bb->x_min, $bb->y_min) for @polylines; - - # clip pattern to boundaries - @polylines = @{intersection_pl(\@polylines, \@$expolygon)}; - - # connect lines - unless ($params{dont_connect} || !@polylines) { # prevent calling leftmost_point() on empty collections - my ($expolygon_off) = @{$expolygon->offset_ex(scaled_epsilon)}; - my $collection = Slic3r::Polyline::Collection->new(@polylines); - @polylines = (); - foreach my $polyline (@{$collection->chained_path_from($collection->leftmost_point, 0)}) { - # try to append this polyline to previous one if any - if (@polylines) { - my $line = Slic3r::Line->new($polylines[-1]->last_point, $polyline->first_point); - if ($line->length <= 1.5*$distance && $expolygon_off->contains_line($line)) { - $polylines[-1]->append_polyline($polyline); - next; - } - } - - # make a clone before $collection goes out of scope - push @polylines, $polyline->clone; - } - } - - # TODO: return ExtrusionLoop objects to get better chained paths - return @polylines; -} - - -=head1 DESCRIPTION - -Creates a contiguous sequence of points at a specified height that make -up a horizontal slice of the edges of a space filling truncated -octahedron tesselation. The octahedrons are oriented so that the -square faces are in the horizontal plane with edges parallel to the X -and Y axes. - -Credits: David Eccles (gringer). - -=head2 makeGrid(z, gridSize, gridWidth, gridHeight, curveType) - -Generate a set of curves (array of array of 2d points) that describe a -horizontal slice of a truncated regular octahedron with a specified -grid square size. - -=cut - -sub makeGrid { - my ($z, $gridSize, $gridWidth, $gridHeight, $curveType) = @_; - my $scaleFactor = $gridSize; - my $normalisedZ = $z / $scaleFactor; - my @points = makeNormalisedGrid($normalisedZ, $gridWidth, $gridHeight, $curveType); - foreach my $lineRef (@points) { - foreach my $pointRef (@$lineRef) { - $pointRef->[0] *= $scaleFactor; - $pointRef->[1] *= $scaleFactor; - } - } - return @points; -} - -=head1 FUNCTIONS -=cut - -=head2 colinearPoints(offset, gridLength) - -Generate an array of points that are in the same direction as the -basic printing line (i.e. Y points for columns, X points for rows) - -Note: a negative offset only causes a change in the perpendicular -direction - -=cut - -sub colinearPoints { - my ($offset, $baseLocation, $gridLength) = @_; - - my @points = (); - push @points, $baseLocation - abs($offset/2); - for (my $i = 0; $i < $gridLength; $i++) { - push @points, $baseLocation + $i + abs($offset/2); - push @points, $baseLocation + ($i+1) - abs($offset/2); - } - push @points, $baseLocation + $gridLength + abs($offset/2); - return @points; -} - -=head2 colinearPoints(offset, baseLocation, gridLength) - -Generate an array of points for the dimension that is perpendicular to -the basic printing line (i.e. X points for columns, Y points for rows) - -=cut - -sub perpendPoints { - my ($offset, $baseLocation, $gridLength) = @_; - - my @points = (); - my $side = 2*(($baseLocation) % 2) - 1; - push @points, $baseLocation - $offset/2 * $side; - for (my $i = 0; $i < $gridLength; $i++) { - $side = 2*(($i+$baseLocation) % 2) - 1; - push @points, $baseLocation + $offset/2 * $side; - push @points, $baseLocation + $offset/2 * $side; - } - push @points, $baseLocation - $offset/2 * $side; - - return @points; -} - -=head2 trim(pointArrayRef, minX, minY, maxX, maxY) - -Trims an array of points to specified rectangular limits. Point -components that are outside these limits are set to the limits. - -=cut - -sub trim { - my ($pointArrayRef, $minX, $minY, $maxX, $maxY) = @_; - - foreach (@$pointArrayRef) { - $_->[0] = ($_->[0] < $minX) ? $minX : (($_->[0] > $maxX) ? $maxX : $_->[0]); - $_->[1] = ($_->[1] < $minY) ? $minY : (($_->[1] > $maxY) ? $maxY : $_->[1]); - } -} - -=head2 makeNormalisedGrid(z, gridWidth, gridHeight, curveType) - -Generate a set of curves (array of array of 2d points) that describe a -horizontal slice of a truncated regular octahedron with edge length 1. - -curveType specifies which lines to print, 1 for vertical lines -(columns), 2 for horizontal lines (rows), and 3 for both. - -=cut - -sub makeNormalisedGrid { - my ($z, $gridWidth, $gridHeight, $curveType) = @_; - - ## offset required to create a regular octagram - my $octagramGap = 0.5; - - # sawtooth wave function for range f($z) = [-$octagramGap .. $octagramGap] - my $a = sqrt(2); # period - my $wave = abs(fmod($z, $a) - $a/2)/$a*4 - 1; - my $offset = $wave * $octagramGap; - - my @points = (); - if (($curveType & 1) != 0) { - for (my $x = 0; $x <= $gridWidth; $x++) { - my @xPoints = perpendPoints($offset, $x, $gridHeight); - my @yPoints = colinearPoints($offset, 0, $gridHeight); - # This is essentially @newPoints = zip(@xPoints, @yPoints) - my @newPoints = map [ $xPoints[$_], $yPoints[$_] ], 0..$#xPoints; - - # trim points to grid edges - #trim(\@newPoints, 0, 0, $gridWidth, $gridHeight); - - if ($x % 2 == 0){ - push @points, [ @newPoints ]; - } else { - push @points, [ reverse @newPoints ]; - } - } - } - if (($curveType & 2) != 0) { - for (my $y = 0; $y <= $gridHeight; $y++) { - my @xPoints = colinearPoints($offset, 0, $gridWidth); - my @yPoints = perpendPoints($offset, $y, $gridWidth); - my @newPoints = map [ $xPoints[$_], $yPoints[$_] ], 0..$#xPoints; - - # trim points to grid edges - #trim(\@newPoints, 0, 0, $gridWidth, $gridHeight); - - if ($y % 2 == 0) { - push @points, [ @newPoints ]; - } else { - push @points, [ reverse @newPoints ]; - } - } - } - return @points; -} - -1; diff --git a/lib/Slic3r/Fill/Base.pm b/lib/Slic3r/Fill/Base.pm deleted file mode 100644 index 75c8e03e6..000000000 --- a/lib/Slic3r/Fill/Base.pm +++ /dev/null @@ -1,91 +0,0 @@ -package Slic3r::Fill::Base; -use Moo; - -has 'layer_id' => (is => 'rw'); -has 'z' => (is => 'rw'); # in unscaled coordinates -has 'angle' => (is => 'rw'); # in radians, ccw, 0 = East -has 'spacing' => (is => 'rw'); # in unscaled coordinates -has 'loop_clipping' => (is => 'rw', default => sub { 0 }); # in scaled coordinates -has 'bounding_box' => (is => 'ro', required => 0); # Slic3r::Geometry::BoundingBox object - -sub adjust_solid_spacing { - my $self = shift; - my %params = @_; - - my $number_of_lines = int($params{width} / $params{distance}) + 1; - return $params{distance} if $number_of_lines <= 1; - - my $extra_space = $params{width} % $params{distance}; - return $params{distance} + $extra_space / ($number_of_lines - 1); -} - -sub no_sort { 0 } -sub use_bridge_flow { 0 } - - -package Slic3r::Fill::WithDirection; -use Moo::Role; - -use Slic3r::Geometry qw(PI rad2deg); - -sub angles () { [0, PI/2] } - -sub infill_direction { - my $self = shift; - my ($surface) = @_; - - if (!defined $self->angle) { - warn "Using undefined infill angle"; - $self->angle(0); - } - - # set infill angle - my (@rotate); - $rotate[0] = $self->angle; - $rotate[1] = $self->bounding_box - ? $self->bounding_box->center - : $surface->expolygon->bounding_box->center; - my $shift = $rotate[1]->clone; - - if (defined $self->layer_id) { - # alternate fill direction - my $layer_num = $self->layer_id / $surface->thickness_layers; - my $angle = $self->angles->[$layer_num % @{$self->angles}]; - $rotate[0] = $self->angle + $angle if $angle; - } - - # use bridge angle - if ($surface->bridge_angle >= 0) { - Slic3r::debugf "Filling bridge with angle %d\n", rad2deg($surface->bridge_angle); - $rotate[0] = $surface->bridge_angle; - } - - $rotate[0] += PI/2; - $shift->rotate(@rotate); - return [\@rotate, $shift]; -} - -# this method accepts any object that implements rotate() and translate() -sub rotate_points { - my $self = shift; - my ($expolygon, $rotate_vector) = @_; - - # rotate points - my ($rotate, $shift) = @$rotate_vector; - $rotate = [ -$rotate->[0], $rotate->[1] ]; - $expolygon->rotate(@$rotate); - $expolygon->translate(@$shift); -} - -sub rotate_points_back { - my $self = shift; - my ($paths, $rotate_vector) = @_; - - my ($rotate, $shift) = @$rotate_vector; - $shift = [ map -$_, @$shift ]; - - $_->translate(@$shift) for @$paths; - $_->rotate(@$rotate) for @$paths; -} - -1; diff --git a/lib/Slic3r/Fill/Concentric.pm b/lib/Slic3r/Fill/Concentric.pm deleted file mode 100644 index ca1837c4e..000000000 --- a/lib/Slic3r/Fill/Concentric.pm +++ /dev/null @@ -1,57 +0,0 @@ -package Slic3r::Fill::Concentric; -use Moo; - -extends 'Slic3r::Fill::Base'; - -use Slic3r::Geometry qw(scale unscale X); -use Slic3r::Geometry::Clipper qw(offset offset2 union_pt_chained); - -sub no_sort { 1 } - -sub fill_surface { - my $self = shift; - my ($surface, %params) = @_; - - # no rotation is supported for this infill pattern - - my $expolygon = $surface->expolygon; - my $bounding_box = $expolygon->bounding_box; - - my $min_spacing = scale($self->spacing); - my $distance = $min_spacing / $params{density}; - - if ($params{density} == 1 && !$params{dont_adjust}) { - $distance = $self->adjust_solid_spacing( - width => $bounding_box->size->[X], - distance => $distance, - ); - $self->spacing(unscale $distance); - } - - my @loops = my @last = map $_->clone, @$expolygon; - while (@last) { - push @loops, @last = @{offset2(\@last, -($distance + 0.5*$min_spacing), +0.5*$min_spacing)}; - } - - # generate paths from the outermost to the innermost, to avoid - # adhesion problems of the first central tiny loops - @loops = map Slic3r::Polygon->new(@$_), - reverse @{union_pt_chained(\@loops)}; - - # split paths using a nearest neighbor search - my @paths = (); - my $last_pos = Slic3r::Point->new(0,0); - foreach my $loop (@loops) { - push @paths, $loop->split_at_index($last_pos->nearest_point_index(\@$loop)); - $last_pos = $paths[-1]->last_point; - } - - # clip the paths to prevent the extruder from getting exactly on the first point of the loop - $_->clip_end($self->loop_clipping) for @paths; - @paths = grep $_->is_valid, @paths; # remove empty paths (too short, thus eaten by clipping) - - # TODO: return ExtrusionLoop objects to get better chained paths - return @paths; -} - -1; diff --git a/lib/Slic3r/Fill/Honeycomb.pm b/lib/Slic3r/Fill/Honeycomb.pm deleted file mode 100644 index b0fbd65ff..000000000 --- a/lib/Slic3r/Fill/Honeycomb.pm +++ /dev/null @@ -1,129 +0,0 @@ -package Slic3r::Fill::Honeycomb; -use Moo; - -extends 'Slic3r::Fill::Base'; -with qw(Slic3r::Fill::WithDirection); - -has 'cache' => (is => 'rw', default => sub {{}}); - -use Slic3r::Geometry qw(PI X Y MIN MAX scale scaled_epsilon); -use Slic3r::Geometry::Clipper qw(intersection intersection_pl); - -sub angles () { [0, PI/3, PI/3*2] } - -sub fill_surface { - my $self = shift; - my ($surface, %params) = @_; - - my $rotate_vector = $self->infill_direction($surface); - - # cache hexagons math - my $cache_id = sprintf "d%s_s%s", $params{density}, $self->spacing; - my $m; - if (!($m = $self->cache->{$cache_id})) { - $m = $self->cache->{$cache_id} = {}; - my $min_spacing = scale($self->spacing); - $m->{distance} = $min_spacing / $params{density}; - $m->{hex_side} = $m->{distance} / (sqrt(3)/2); - $m->{hex_width} = $m->{distance} * 2; # $m->{hex_width} == $m->{hex_side} * sqrt(3); - my $hex_height = $m->{hex_side} * 2; - $m->{pattern_height} = $hex_height + $m->{hex_side}; - $m->{y_short} = $m->{distance} * sqrt(3)/3; - $m->{x_offset} = $min_spacing / 2; - $m->{y_offset} = $m->{x_offset} * sqrt(3)/3; - $m->{hex_center} = Slic3r::Point->new($m->{hex_width}/2, $m->{hex_side}); - } - - my @polygons = (); - { - # adjust actual bounding box to the nearest multiple of our hex pattern - # and align it so that it matches across layers - - my $bounding_box = $surface->expolygon->bounding_box; - { - # rotate bounding box according to infill direction - my $bb_polygon = $bounding_box->polygon; - $bb_polygon->rotate($rotate_vector->[0][0], $m->{hex_center}); - $bounding_box = $bb_polygon->bounding_box; - - # extend bounding box so that our pattern will be aligned with other layers - # $bounding_box->[X1] and [Y1] represent the displacement between new bounding box offset and old one - $bounding_box->merge_point(Slic3r::Point->new( - $bounding_box->x_min - ($bounding_box->x_min % $m->{hex_width}), - $bounding_box->y_min - ($bounding_box->y_min % $m->{pattern_height}), - )); - } - - my $x = $bounding_box->x_min; - while ($x <= $bounding_box->x_max) { - my $p = []; - - my @x = ($x + $m->{x_offset}, $x + $m->{distance} - $m->{x_offset}); - for (1..2) { - @$p = reverse @$p; # turn first half upside down - my @p = (); - for (my $y = $bounding_box->y_min; $y <= $bounding_box->y_max; $y += $m->{y_short} + $m->{hex_side} + $m->{y_short} + $m->{hex_side}) { - push @$p, - [ $x[1], $y + $m->{y_offset} ], - [ $x[0], $y + $m->{y_short} - $m->{y_offset} ], - [ $x[0], $y + $m->{y_short} + $m->{hex_side} + $m->{y_offset} ], - [ $x[1], $y + $m->{y_short} + $m->{hex_side} + $m->{y_short} - $m->{y_offset} ], - [ $x[1], $y + $m->{y_short} + $m->{hex_side} + $m->{y_short} + $m->{hex_side} + $m->{y_offset} ]; - } - @x = map $_ + $m->{distance}, reverse @x; # draw symmetrical pattern - $x += $m->{distance}; - } - - push @polygons, Slic3r::Polygon->new(@$p); - } - - $_->rotate(-$rotate_vector->[0][0], $m->{hex_center}) for @polygons; - } - - my @paths; - if ($params{complete} || 1) { - # we were requested to complete each loop; - # in this case we don't try to make more continuous paths - @paths = map $_->split_at_first_point, - @{intersection([ $surface->p ], \@polygons)}; - - } else { - # consider polygons as polylines without re-appending the initial point: - # this cuts the last segment on purpose, so that the jump to the next - # path is more straight - @paths = @{intersection_pl( - [ map Slic3r::Polyline->new(@$_), @polygons ], - [ @{$surface->expolygon} ], - )}; - - # connect paths - if (@paths) { # prevent calling leftmost_point() on empty collections - my $collection = Slic3r::Polyline::Collection->new(@paths); - @paths = (); - foreach my $path (@{$collection->chained_path_from($collection->leftmost_point, 0)}) { - if (@paths) { - # distance between first point of this path and last point of last path - my $distance = $paths[-1]->last_point->distance_to($path->first_point); - - if ($distance <= $m->{hex_width}) { - $paths[-1]->append_polyline($path); - next; - } - } - - # make a clone before $collection goes out of scope - push @paths, $path->clone; - } - } - - # clip paths again to prevent connection segments from crossing the expolygon boundaries - @paths = @{intersection_pl( - \@paths, - [ map @$_, @{$surface->expolygon->offset_ex(scaled_epsilon)} ], - )}; - } - - return @paths; -} - -1; diff --git a/lib/Slic3r/Fill/PlanePath.pm b/lib/Slic3r/Fill/PlanePath.pm deleted file mode 100644 index 556835ec4..000000000 --- a/lib/Slic3r/Fill/PlanePath.pm +++ /dev/null @@ -1,118 +0,0 @@ -package Slic3r::Fill::PlanePath; -use Moo; - -extends 'Slic3r::Fill::Base'; -with qw(Slic3r::Fill::WithDirection); - -use Slic3r::Geometry qw(scale X1 Y1 X2 Y2); -use Slic3r::Geometry::Clipper qw(intersection_pl); - -sub angles () { [0] } -sub multiplier () { 1 } - -sub process_polyline {} - -sub fill_surface { - my $self = shift; - my ($surface, %params) = @_; - - # rotate polygons - my $expolygon = $surface->expolygon->clone; - my $rotate_vector = $self->infill_direction($surface); - $self->rotate_points($expolygon, $rotate_vector); - - my $distance_between_lines = scale($self->spacing) / $params{density} * $self->multiplier; - - # align infill across layers using the object's bounding box - my $bb_polygon = $self->bounding_box->polygon; - $self->rotate_points($bb_polygon, $rotate_vector); - my $bounding_box = $bb_polygon->bounding_box; - - (ref $self) =~ /::([^:]+)$/; - my $path = "Math::PlanePath::$1"->new; - - my $translate = Slic3r::Point->new(0,0); # vector - if ($path->x_negative || $path->y_negative) { - # if the curve extends on both positive and negative coordinate space, - # center our expolygon around origin - $translate = $bounding_box->center->negative; - } else { - # if the curve does not extend in negative coordinate space, - # move expolygon entirely in positive coordinate space - $translate = $bounding_box->min_point->negative; - } - $expolygon->translate(@$translate); - $bounding_box->translate(@$translate); - - my ($n_lo, $n_hi) = $path->rect_to_n_range( - map { $_ / $distance_between_lines } - @{$bounding_box->min_point}, - @{$bounding_box->max_point}, - ); - - my $polyline = Slic3r::Polyline->new( - map [ map { $_ * $distance_between_lines } $path->n_to_xy($_) ], ($n_lo..$n_hi) - ); - return {} if @$polyline <= 1; - - $self->process_polyline($polyline, $bounding_box); - - my @paths = @{intersection_pl([$polyline], \@$expolygon)}; - - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output("fill.svg", - no_arrows => 1, - polygons => \@$expolygon, - green_polygons => [ $bounding_box->polygon ], - polylines => [ $polyline ], - red_polylines => \@paths, - ); - } - - # paths must be repositioned and rotated back - $_->translate(@{$translate->negative}) for @paths; - $self->rotate_points_back(\@paths, $rotate_vector); - - return @paths; -} - - -package Slic3r::Fill::ArchimedeanChords; -use Moo; -extends 'Slic3r::Fill::PlanePath'; -use Math::PlanePath::ArchimedeanChords; - - -package Slic3r::Fill::Flowsnake; -use Moo; -extends 'Slic3r::Fill::PlanePath'; -use Math::PlanePath::Flowsnake; -use Slic3r::Geometry qw(X); - -# Sorry, this fill is currently broken. - -sub process_polyline { - my $self = shift; - my ($polyline, $bounding_box) = @_; - - $_->[X] += $bounding_box->center->[X] for @$polyline; -} - - -package Slic3r::Fill::HilbertCurve; -use Moo; -extends 'Slic3r::Fill::PlanePath'; -use Math::PlanePath::HilbertCurve; - - -package Slic3r::Fill::OctagramSpiral; -use Moo; -extends 'Slic3r::Fill::PlanePath'; -use Math::PlanePath::OctagramSpiral; - -sub multiplier () { sqrt(2) } - - - -1; diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm deleted file mode 100644 index 66fbde330..000000000 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ /dev/null @@ -1,175 +0,0 @@ -package Slic3r::Fill::Rectilinear; -use Moo; - -extends 'Slic3r::Fill::Base'; -with qw(Slic3r::Fill::WithDirection); - -has '_min_spacing' => (is => 'rw'); -has '_line_spacing' => (is => 'rw'); -has '_diagonal_distance' => (is => 'rw'); -has '_line_oscillation' => (is => 'rw'); - -use Slic3r::Geometry qw(scale unscale scaled_epsilon); -use Slic3r::Geometry::Clipper qw(intersection_pl); - -sub horizontal_lines { 0 } - -sub fill_surface { - my $self = shift; - my ($surface, %params) = @_; - - # rotate polygons so that we can work with vertical lines here - my $expolygon = $surface->expolygon->clone; - my $rotate_vector = $self->infill_direction($surface); - $self->rotate_points($expolygon, $rotate_vector); - - $self->_min_spacing(scale $self->spacing); - $self->_line_spacing($self->_min_spacing / $params{density}); - $self->_diagonal_distance($self->_line_spacing * 2); - $self->_line_oscillation($self->_line_spacing - $self->_min_spacing); # only for Line infill - my $bounding_box = $expolygon->bounding_box; - - # define flow spacing according to requested density - if ($params{density} == 1 && !$params{dont_adjust}) { - $self->_line_spacing($self->adjust_solid_spacing( - width => $bounding_box->size->x, - distance => $self->_line_spacing, - )); - $self->spacing(unscale $self->_line_spacing); - } else { - # extend bounding box so that our pattern will be aligned with other layers - $bounding_box->merge_point(Slic3r::Point->new( - $bounding_box->x_min - ($bounding_box->x_min % $self->_line_spacing), - $bounding_box->y_min - ($bounding_box->y_min % $self->_line_spacing), - )); - } - - # generate the basic pattern - my $x_max = $bounding_box->x_max + scaled_epsilon; - my @lines = (); - for (my $x = $bounding_box->x_min; $x <= $x_max; $x += $self->_line_spacing) { - push @lines, $self->_line($#lines, $x, $bounding_box->y_min, $bounding_box->y_max); - } - if ($self->horizontal_lines) { - my $y_max = $bounding_box->y_max + scaled_epsilon; - for (my $y = $bounding_box->y_min; $y <= $y_max; $y += $self->_line_spacing) { - push @lines, Slic3r::Polyline->new( - [$bounding_box->x_min, $y], - [$bounding_box->x_max, $y], - ); - } - } - - # clip paths against a slightly larger expolygon, so that the first and last paths - # are kept even if the expolygon has vertical sides - # the minimum offset for preventing edge lines from being clipped is scaled_epsilon; - # however we use a larger offset to support expolygons with slightly skewed sides and - # not perfectly straight - my @polylines = @{intersection_pl(\@lines, $expolygon->offset(+scale 0.02))}; - - my $extra = $self->_min_spacing * &Slic3r::INFILL_OVERLAP_OVER_SPACING; - foreach my $polyline (@polylines) { - my ($first_point, $last_point) = @$polyline[0,-1]; - if ($first_point->y > $last_point->y) { #> - ($first_point, $last_point) = ($last_point, $first_point); - } - $first_point->set_y($first_point->y - $extra); #-- - $last_point->set_y($last_point->y + $extra); #++ - } - - # connect lines - unless ($params{dont_connect} || !@polylines) { # prevent calling leftmost_point() on empty collections - # offset the expolygon by max(min_spacing/2, extra) - my ($expolygon_off) = @{$expolygon->offset_ex($self->_min_spacing/2)}; - my $collection = Slic3r::Polyline::Collection->new(@polylines); - @polylines = (); - - foreach my $polyline (@{$collection->chained_path_from($collection->leftmost_point, 0)}) { - if (@polylines) { - my $first_point = $polyline->first_point; - my $last_point = $polylines[-1]->last_point; - my @distance = map abs($first_point->$_ - $last_point->$_), qw(x y); - - # TODO: we should also check that both points are on a fill_boundary to avoid - # connecting paths on the boundaries of internal regions - if ($self->_can_connect(@distance) && $expolygon_off->contains_line(Slic3r::Line->new($last_point, $first_point))) { - $polylines[-1]->append_polyline($polyline); - next; - } - } - - # make a clone before $collection goes out of scope - push @polylines, $polyline->clone; - } - } - - # paths must be rotated back - $self->rotate_points_back(\@polylines, $rotate_vector); - - return @polylines; -} - -sub _line { - my ($self, $i, $x, $y_min, $y_max) = @_; - - return Slic3r::Polyline->new( - [$x, $y_min], - [$x, $y_max], - ); -} - -sub _can_connect { - my ($self, $dist_X, $dist_Y) = @_; - - return $dist_X <= $self->_diagonal_distance - && $dist_Y <= $self->_diagonal_distance; -} - - -package Slic3r::Fill::Line; -use Moo; -extends 'Slic3r::Fill::Rectilinear'; - -use Slic3r::Geometry qw(scaled_epsilon); - -sub _line { - my ($self, $i, $x, $y_min, $y_max) = @_; - - if ($i % 2) { - return Slic3r::Polyline->new( - [$x - $self->_line_oscillation, $y_min], - [$x + $self->_line_oscillation, $y_max], - ); - } else { - return Slic3r::Polyline->new( - [$x, $y_min], - [$x, $y_max], - ); - } -} - -sub _can_connect { - my ($self, $dist_X, $dist_Y) = @_; - - my $TOLERANCE = 10 * scaled_epsilon; - return ($dist_X >= ($self->_line_spacing - $self->_line_oscillation) - $TOLERANCE) - && ($dist_X <= ($self->_line_spacing + $self->_line_oscillation) + $TOLERANCE) - && $dist_Y <= $self->_diagonal_distance; -} - - -package Slic3r::Fill::Grid; -use Moo; -extends 'Slic3r::Fill::Rectilinear'; - -sub angles () { [0] } -sub horizontal_lines { 1 } - - -package Slic3r::Fill::AlignedRectilinear; -use Moo; -extends 'Slic3r::Fill::Rectilinear'; - -sub angles () { [0, 0] } - -1; diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm index 32a6e62fc..b5e8e42f7 100644 --- a/lib/Slic3r/Layer.pm +++ b/lib/Slic3r/Layer.pm @@ -29,15 +29,6 @@ sub regions { return [ map $self->get_region($_), 0..($self->region_count-1) ]; } -sub make_fill { - my ($self) = @_; - - foreach my $layerm (@{$self->regions}) { - $layerm->fills->clear; - $layerm->fills->append($_) for $self->object->fill_maker->make_fill($layerm); - } -} - package Slic3r::Layer::Support; our @ISA = qw(Slic3r::Layer); diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 3f0b7f50d..da8487d9d 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -522,12 +522,12 @@ sub infill { thread_cb => sub { my $q = shift; while (defined (my $i = $q->dequeue)) { - $self->get_layer($i)->make_fill; + $self->get_layer($i)->make_fills; } }, no_threads_cb => sub { foreach my $layer (@{$self->layers}) { - $layer->make_fill; + $layer->make_fills; } }, ); diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm index eea6397af..16c3b944c 100644 --- a/lib/Slic3r/Print/SupportMaterial.pm +++ b/lib/Slic3r/Print/SupportMaterial.pm @@ -558,11 +558,6 @@ sub generate_toolpaths { $pattern = 'honeycomb'; } - my %fillers = ( - interface => $object->fill_maker->filler('rectilinear'), - support => $object->fill_maker->filler($pattern), - ); - my $interface_angle = $self->object_config->support_material_angle + 90; my $interface_spacing = $self->object_config->support_material_interface_spacing + $interface_flow->spacing; my $interface_density = $interface_spacing == 0 ? 1 : $interface_flow->spacing / $interface_spacing; @@ -673,10 +668,20 @@ sub generate_toolpaths { $layer->support_interface_fills->append(@loops); } + # Allocate the fillers exclusively in the worker threads! Don't allocate them at the main thread, + # as Perl copies the C++ pointers by default, so then the C++ objects are shared between threads! + my %fillers = ( + interface => Slic3r::Filler->new_from_type('rectilinear'), + support => Slic3r::Filler->new_from_type($pattern), + ); + my $bounding_box = $object->bounding_box; + $fillers{interface}->set_bounding_box($object->bounding_box); + $fillers{support}->set_bounding_box($object->bounding_box); + # interface and contact infill if (@$interface || @$contact_infill) { - $fillers{interface}->angle($interface_angle); - $fillers{interface}->spacing($_interface_flow->spacing); + $fillers{interface}->set_angle($interface_angle); + $fillers{interface}->set_spacing($_interface_flow->spacing); # find centerline of the external loop $interface = offset2($interface, +scaled_epsilon, -(scaled_epsilon + $_interface_flow->scaled_width/2)); @@ -702,7 +707,7 @@ sub generate_toolpaths { my @paths = (); foreach my $expolygon (@{union_ex($interface)}) { - my @p = $fillers{interface}->fill_surface( + my $p = $fillers{interface}->fill_surface( Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNAL), density => $interface_density, layer_height => $layer->height, @@ -716,7 +721,7 @@ sub generate_toolpaths { mm3_per_mm => $mm3_per_mm, width => $_interface_flow->width, height => $layer->height, - ), @p; + ), @$p; } $layer->support_interface_fills->append(@paths); @@ -725,11 +730,11 @@ sub generate_toolpaths { # support or flange if (@$base) { my $filler = $fillers{support}; - $filler->angle($angles[ ($layer_id) % @angles ]); + $filler->set_angle($angles[ ($layer_id) % @angles ]); # We don't use $base_flow->spacing because we need a constant spacing # value that guarantees that all layers are correctly aligned. - $filler->spacing($flow->spacing); + $filler->set_spacing($flow->spacing); my $density = $support_density; my $base_flow = $_flow; @@ -742,13 +747,13 @@ sub generate_toolpaths { # base flange if ($layer_id == 0) { $filler = $fillers{interface}; - $filler->angle($self->object_config->support_material_angle + 90); + $filler->set_angle($self->object_config->support_material_angle + 90); $density = 0.5; $base_flow = $self->first_layer_flow; # use the proper spacing for first layer as we don't need to align # its pattern to the other layers - $filler->spacing($base_flow->spacing); + $filler->set_spacing($base_flow->spacing); } else { # draw a perimeter all around support infill # TODO: use brim ordering algorithm @@ -767,7 +772,7 @@ sub generate_toolpaths { my $mm3_per_mm = $base_flow->mm3_per_mm; foreach my $expolygon (@$to_infill) { - my @p = $filler->fill_surface( + my $p = $filler->fill_surface( Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNAL), density => $density, layer_height => $layer->height, @@ -780,7 +785,7 @@ sub generate_toolpaths { mm3_per_mm => $mm3_per_mm, width => $base_flow->width, height => $layer->height, - ), @p; + ), @$p; } $layer->support_fills->append(@paths); diff --git a/t/fill.t b/t/fill.t index 6fb05196e..af17c86ff 100644 --- a/t/fill.t +++ b/t/fill.t @@ -11,7 +11,7 @@ BEGIN { use List::Util qw(first sum); use Slic3r; -use Slic3r::Geometry qw(X Y scale unscale convex_hull); +use Slic3r::Geometry qw(PI X Y scale unscale convex_hull); use Slic3r::Geometry::Clipper qw(union diff diff_ex offset offset2_ex); use Slic3r::Surface qw(:types); use Slic3r::Test; @@ -20,25 +20,17 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } { my $print = Slic3r::Print->new; - my $filler = Slic3r::Fill::Rectilinear->new( - print => $print, - bounding_box => Slic3r::Geometry::BoundingBox->new_from_points([ Slic3r::Point->new(0, 0), Slic3r::Point->new(10, 10) ]), - ); my $surface_width = 250; - my $distance = $filler->adjust_solid_spacing( - width => $surface_width, - distance => 100, - ); - is $distance, 125, 'adjusted solid distance'; + my $distance = Slic3r::Filler::adjust_solid_spacing($surface_width, 47); + is $distance, 50, 'adjusted solid distance'; is $surface_width % $distance, 0, 'adjusted solid distance'; } { my $expolygon = Slic3r::ExPolygon->new([ scale_points [0,0], [50,0], [50,50], [0,50] ]); - my $filler = Slic3r::Fill::Rectilinear->new( - bounding_box => $expolygon->bounding_box, - angle => 0, - ); + my $filler = Slic3r::Filler->new_from_type('rectilinear'); + $filler->set_bounding_box($expolygon->bounding_box); + $filler->set_angle(0); my $surface = Slic3r::Surface->new( surface_type => S_TYPE_TOP, expolygon => $expolygon, @@ -48,11 +40,11 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } height => 0.4, nozzle_diameter => 0.50, ); - $filler->spacing($flow->spacing); + $filler->set_spacing($flow->spacing); foreach my $angle (0, 45) { $surface->expolygon->rotate(Slic3r::Geometry::deg2rad($angle), [0,0]); - my @paths = $filler->fill_surface($surface, layer_height => 0.4, density => 0.4); - is scalar @paths, 1, 'one continuous path'; + my $paths = $filler->fill_surface($surface, layer_height => 0.4, density => 0.4); + is scalar @$paths, 1, 'one continuous path'; } } @@ -60,10 +52,9 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } my $test = sub { my ($expolygon, $flow_spacing, $angle, $density) = @_; - my $filler = Slic3r::Fill::Rectilinear->new( - bounding_box => $expolygon->bounding_box, - angle => $angle // 0, - ); + my $filler = Slic3r::Filler->new_from_type('rectilinear'); + $filler->set_bounding_box($expolygon->bounding_box); + $filler->set_angle($angle // 0); my $surface = Slic3r::Surface->new( surface_type => S_TYPE_BOTTOM, expolygon => $expolygon, @@ -73,15 +64,15 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } height => 0.4, nozzle_diameter => $flow_spacing, ); - $filler->spacing($flow->spacing); - my @paths = $filler->fill_surface( + $filler->set_spacing($flow->spacing); + my $paths = $filler->fill_surface( $surface, layer_height => $flow->height, density => $density // 1, ); # check whether any part was left uncovered - my @grown_paths = map @{Slic3r::Polyline->new(@$_)->grow(scale $filler->spacing/2)}, @paths; + my @grown_paths = map @{Slic3r::Polyline->new(@$_)->grow(scale $filler->spacing/2)}, @$paths; my $uncovered = diff_ex([ @$expolygon ], [ @grown_paths ], 1); # ignore very small dots @@ -93,8 +84,9 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } require "Slic3r/SVG.pm"; Slic3r::SVG::output( "uncovered.svg", - expolygons => [$expolygon], - red_expolygons => $uncovered, + expolygons => [$expolygon], + red_expolygons => $uncovered, + polylines => $paths, ); exit; } @@ -116,7 +108,7 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } $expolygon = Slic3r::ExPolygon->new( [[59515297,5422499],[59531249,5578697],[59695801,6123186],[59965713,6630228],[60328214,7070685],[60773285,7434379],[61274561,7702115],[61819378,7866770],[62390306,7924789],[62958700,7866744],[63503012,7702244],[64007365,7434357],[64449960,7070398],[64809327,6634999],[65082143,6123325],[65245005,5584454],[65266967,5422499],[66267307,5422499],[66269190,8310081],[66275379,17810072],[66277259,20697500],[65267237,20697500],[65245004,20533538],[65082082,19994444],[64811462,19488579],[64450624,19048208],[64012101,18686514],[63503122,18415781],[62959151,18251378],[62453416,18198442],[62390147,18197355],[62200087,18200576],[61813519,18252990],[61274433,18415918],[60768598,18686517],[60327567,19047892],[59963609,19493297],[59695865,19994587],[59531222,20539379],[59515153,20697500],[58502480,20697500],[58502480,5422499]] ); - $test->($expolygon, 0.524341649025257); + $test->($expolygon, 0.524341649025257, PI/2); $expolygon = Slic3r::ExPolygon->new([ scale_points [0,0], [98,0], [98,10], [0,10] ]); $test->($expolygon, 0.5, 45, 0.99); # non-solid infill diff --git a/utils/package_win32.ps1 b/utils/package_win32.ps1 index 0acbed6a2..f4e0bf250 100644 --- a/utils/package_win32.ps1 +++ b/utils/package_win32.ps1 @@ -97,17 +97,6 @@ pp ` -M LWP::Protocol::http ` -M LWP::UserAgent ` -M List::Util ` --M Math::Libm ` --M Math::PlanePath ` --M Math::PlanePath::ArchimedeanChords ` --M Math::PlanePath::Base::Digits ` --M Math::PlanePath::Base::Generic ` --M Math::PlanePath::Base::NSEW ` --M Math::PlanePath::Flowsnake ` --M Math::PlanePath::FlowsnakeCentres ` --M Math::PlanePath::HilbertCurve ` --M Math::PlanePath::OctagramSpiral ` --M Math::PlanePath::SacksSpiral ` -M Math::Trig ` -M Method::Generate::Accessor ` -M Method::Generate::BuildAll ` diff --git a/xs/Build.PL b/xs/Build.PL index fc82615cd..7d2556da8 100644 --- a/xs/Build.PL +++ b/xs/Build.PL @@ -154,6 +154,9 @@ if ($ENV{SLIC3R_DEBUG}) { # only on newer GCCs: -ftemplate-backtrace-limit=0 push @cflags, '-DSLIC3R_DEBUG'; push @cflags, $cpp_guess->is_msvc ? '-Gd' : '-g'; +} else { + # Disable asserts in the release builds. + push @cflags, '-DNDEBUG'; } if ($cpp_guess->is_gcc) { # check whether we're dealing with a buggy GCC version diff --git a/xs/MANIFEST b/xs/MANIFEST index 9c3154826..e3c4b9665 100644 --- a/xs/MANIFEST +++ b/xs/MANIFEST @@ -28,6 +28,22 @@ src/libslic3r/ExtrusionEntity.cpp src/libslic3r/ExtrusionEntity.hpp src/libslic3r/ExtrusionEntityCollection.cpp src/libslic3r/ExtrusionEntityCollection.hpp +src/libslic3r/Fill/Fill.cpp +src/libslic3r/Fill/Fill.hpp +src/libslic3r/Fill/FillBase.cpp +src/libslic3r/Fill/FillBase.hpp +src/libslic3r/Fill/FillConcentric.cpp +src/libslic3r/Fill/FillConcentric.hpp +src/libslic3r/Fill/FillHoneycomb.cpp +src/libslic3r/Fill/FillHoneycomb.hpp +src/libslic3r/Fill/Fill3DHoneycomb.cpp +src/libslic3r/Fill/Fill3DHoneycomb.hpp +src/libslic3r/Fill/FillPlanePath.cpp +src/libslic3r/Fill/FillPlanePath.hpp +src/libslic3r/Fill/FillRectilinear.cpp +src/libslic3r/Fill/FillRectilinear.hpp +src/libslic3r/Fill/FillRectilinear2.cpp +src/libslic3r/Fill/FillRectilinear2.hpp src/libslic3r/Flow.cpp src/libslic3r/Flow.hpp src/libslic3r/GCode.cpp @@ -136,6 +152,7 @@ xsp/Extruder.xsp xsp/ExtrusionEntityCollection.xsp xsp/ExtrusionLoop.xsp xsp/ExtrusionPath.xsp +xsp/Filler.xsp xsp/Flow.xsp xsp/GCode.xsp xsp/GCodeSender.xsp diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 4f53966de..364264d67 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -123,6 +123,17 @@ sub clone { ); } +package Slic3r::Filler; + +sub fill_surface { + my ($self, $surface, %args) = @_; + $self->set_density($args{density}) if defined($args{density}); + $self->set_dont_connect($args{dont_connect}) if defined($args{dont_connect}); + $self->set_dont_adjust($args{dont_adjust}) if defined($args{dont_adjust}); + $self->set_complete($args{complete}) if defined($args{complete}); + return $self->_fill_surface($surface); +} + package Slic3r::Flow; sub new { @@ -215,6 +226,7 @@ for my $class (qw( Slic3r::ExtrusionLoop Slic3r::ExtrusionPath Slic3r::ExtrusionPath::Collection + Slic3r::Filler Slic3r::Flow Slic3r::GCode Slic3r::GCode::AvoidCrossingPerimeters diff --git a/xs/src/libslic3r/BoundingBox.cpp b/xs/src/libslic3r/BoundingBox.cpp index 3ec2258a9..809f8925c 100644 --- a/xs/src/libslic3r/BoundingBox.cpp +++ b/xs/src/libslic3r/BoundingBox.cpp @@ -68,6 +68,26 @@ BoundingBox::polygon() const return p; } +BoundingBox BoundingBox::rotated(double angle) const +{ + BoundingBox out; + out.merge(this->min.rotated(angle)); + out.merge(this->max.rotated(angle)); + out.merge(Point(this->min.x, this->max.y).rotated(angle)); + out.merge(Point(this->max.x, this->min.y).rotated(angle)); + return out; +} + +BoundingBox BoundingBox::rotated(double angle, const Point ¢er) const +{ + BoundingBox out; + out.merge(this->min.rotated(angle, center)); + out.merge(this->max.rotated(angle, center)); + out.merge(Point(this->min.x, this->max.y).rotated(angle, center)); + out.merge(Point(this->max.x, this->min.y).rotated(angle, center)); + return out; +} + template void BoundingBoxBase::scale(double factor) { diff --git a/xs/src/libslic3r/BoundingBox.hpp b/xs/src/libslic3r/BoundingBox.hpp index f5260ee5f..71d0fa0b6 100644 --- a/xs/src/libslic3r/BoundingBox.hpp +++ b/xs/src/libslic3r/BoundingBox.hpp @@ -55,6 +55,14 @@ class BoundingBox : public BoundingBoxBase public: void polygon(Polygon* polygon) const; Polygon polygon() const; + BoundingBox rotated(double angle) const; + BoundingBox rotated(double angle, const Point ¢er) const; + void rotate(double angle) { + *this = this->rotated(angle); + } + void rotate(double angle, const Point ¢er) { + *this = this->rotated(angle, center); + } BoundingBox() : BoundingBoxBase() {}; BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase(pmin, pmax) {}; @@ -92,16 +100,6 @@ inline bool operator!=(const BoundingBoxBase &bb1, const BoundingBoxBase return !(bb1 == bb2); } -template -inline bool empty(const BoundingBoxBase &bb) -{ - return bb.min.x > bb.max.y || bb.min.y > bb.max.y; -} - -template -inline bool empty(const BoundingBox3Base &bb) -{ - return bb.min.x > bb.max.x || bb.min.y > bb.max.y || bb.min.z > bb.max.z;} } #endif diff --git a/xs/src/libslic3r/ExPolygon.cpp b/xs/src/libslic3r/ExPolygon.cpp index c9bb08048..19c3184f3 100644 --- a/xs/src/libslic3r/ExPolygon.cpp +++ b/xs/src/libslic3r/ExPolygon.cpp @@ -52,6 +52,15 @@ ExPolygon::translate(double x, double y) } } +void +ExPolygon::rotate(double angle) +{ + contour.rotate(angle); + for (Polygons::iterator it = holes.begin(); it != holes.end(); ++it) { + (*it).rotate(angle); + } +} + void ExPolygon::rotate(double angle, const Point ¢er) { @@ -497,15 +506,4 @@ ExPolygon::dump_perl() const return ret.str(); } -Polygons -to_polygons(const ExPolygons &expolygons) -{ - Slic3r::Polygons pp; - for (ExPolygons::const_iterator ex = expolygons.begin(); ex != expolygons.end(); ++ex) { - Slic3r::Polygons ppp = *ex; - pp.insert(pp.end(), ppp.begin(), ppp.end()); - } - return pp; -} - } diff --git a/xs/src/libslic3r/ExPolygon.hpp b/xs/src/libslic3r/ExPolygon.hpp index a9c86bf66..0b4140e90 100644 --- a/xs/src/libslic3r/ExPolygon.hpp +++ b/xs/src/libslic3r/ExPolygon.hpp @@ -20,6 +20,7 @@ class ExPolygon operator Polygons() const; void scale(double factor); void translate(double x, double y); + void rotate(double angle); void rotate(double angle, const Point ¢er); double area() const; bool is_valid() const; @@ -45,7 +46,14 @@ class ExPolygon std::string dump_perl() const; }; -Polygons to_polygons(const ExPolygons &expolygons); +inline Polygons +to_polygons(const ExPolygons &expolygons) +{ + Polygons pp; + for (ExPolygons::const_iterator ex = expolygons.begin(); ex != expolygons.end(); ++ex) + append_to(pp, (Polygons)*ex); + return pp; +} } diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.cpp b/xs/src/libslic3r/ExtrusionEntityCollection.cpp index 31aa3d4c8..1d16a5f50 100644 --- a/xs/src/libslic3r/ExtrusionEntityCollection.cpp +++ b/xs/src/libslic3r/ExtrusionEntityCollection.cpp @@ -101,6 +101,17 @@ ExtrusionEntityCollection::append(const ExtrusionPaths &paths) this->append(*path); } +void +ExtrusionEntityCollection::append(const Polylines &polylines, const ExtrusionPath &templ) +{ + this->entities.reserve(this->entities.size() + polylines.size()); + for (Polylines::const_iterator it_polyline = polylines.begin(); it_polyline != polylines.end(); ++ it_polyline) { + ExtrusionPath *path = templ.clone(); + path->polyline = *it_polyline; + this->entities.push_back(path); + } +} + void ExtrusionEntityCollection::replace(size_t i, const ExtrusionEntity &entity) { diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.hpp b/xs/src/libslic3r/ExtrusionEntityCollection.hpp index 7e44ccd18..491411aa7 100644 --- a/xs/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/xs/src/libslic3r/ExtrusionEntityCollection.hpp @@ -3,6 +3,7 @@ #include "libslic3r.h" #include "ExtrusionEntity.hpp" +#include "Polyline.hpp" namespace Slic3r { @@ -36,6 +37,7 @@ class ExtrusionEntityCollection : public ExtrusionEntity void append(const ExtrusionEntity &entity); void append(const ExtrusionEntitiesPtr &entities); void append(const ExtrusionPaths &paths); + void append(const Polylines &polylines, const ExtrusionPath &templ); void replace(size_t i, const ExtrusionEntity &entity); void remove(size_t i); ExtrusionEntityCollection chained_path(bool no_reverse = false, std::vector* orig_indices = NULL) const; diff --git a/xs/src/libslic3r/Fill/Fill.cpp b/xs/src/libslic3r/Fill/Fill.cpp new file mode 100644 index 000000000..5a0897413 --- /dev/null +++ b/xs/src/libslic3r/Fill/Fill.cpp @@ -0,0 +1,290 @@ +#include +#include +#include + +#include "../ClipperUtils.hpp" +#include "../Geometry.hpp" +#include "../Layer.hpp" +#include "../Print.hpp" +#include "../PrintConfig.hpp" +#include "../Surface.hpp" +#include "../SurfaceCollection.hpp" + +#include "FillBase.hpp" + +namespace Slic3r { + +struct SurfaceGroupAttrib +{ + SurfaceGroupAttrib() : is_solid(false), fw(0.f), pattern(-1) {} + bool operator==(const SurfaceGroupAttrib &other) const + { return is_solid == other.is_solid && fw == other.fw && pattern == other.pattern; } + bool is_solid; + float fw; + // pattern is of type InfillPattern, -1 for an unset pattern. + int pattern; +}; + +// Generate infills for a LayerRegion. +// The LayerRegion at this point of time may contain +// surfaces of various types (internal/bridge/top/bottom/solid). +// The infills are generated on the groups of surfaces with a compatible type. +// Returns an array of ExtrusionPathCollection objects containing the infills generated now +// and the thin fills generated by generate_perimeters(). +void make_fill(const LayerRegion &layerm, ExtrusionEntityCollection* out) +{ +// Slic3r::debugf "Filling layer %d:\n", $layerm->layer->id; + + double fill_density = layerm.region()->config.fill_density; + const Flow infill_flow = layerm.flow(frInfill); + const Flow solid_infill_flow = layerm.flow(frSolidInfill); + const Flow top_solid_infill_flow = layerm.flow(frTopSolidInfill); + + SurfaceCollection surfaces; + + // merge adjacent surfaces + // in case of bridge surfaces, the ones with defined angle will be attached to the ones + // without any angle (shouldn't this logic be moved to process_external_surfaces()?) + { + Polygons polygons_bridged; + polygons_bridged.reserve(layerm.fill_surfaces.surfaces.size()); + for (Surfaces::const_iterator it = layerm.fill_surfaces.surfaces.begin(); it != layerm.fill_surfaces.surfaces.end(); ++it) + if (it->bridge_angle >= 0) + append_to(polygons_bridged, (Polygons)*it); + + // group surfaces by distinct properties (equal surface_type, thickness, thickness_layers, bridge_angle) + // group is of type SurfaceCollection + // FIXME: Use some smart heuristics to merge similar surfaces to eliminate tiny regions. + std::vector groups; + layerm.fill_surfaces.group(&groups); + + // merge compatible groups (we can generate continuous infill for them) + { + // cache flow widths and patterns used for all solid groups + // (we'll use them for comparing compatible groups) + std::vector group_attrib(groups.size()); + for (size_t i = 0; i < groups.size(); ++i) { + // we can only merge solid non-bridge surfaces, so discard + // non-solid surfaces + const Surface &surface = *groups[i].front(); + if (surface.is_solid() && (!surface.is_bridge() || layerm.layer()->id() == 0)) { + group_attrib[i].is_solid = true; + group_attrib[i].fw = (surface.surface_type == stTop) ? top_solid_infill_flow.width : solid_infill_flow.width; + group_attrib[i].pattern = surface.is_external() ? layerm.region()->config.external_fill_pattern.value : ipRectilinear; + } + } + // Loop through solid groups, find compatible groups and append them to this one. + for (size_t i = 0; i < groups.size(); ++i) { + if (!group_attrib[i].is_solid) + continue; + for (size_t j = i + 1; j < groups.size();) { + if (group_attrib[i] == group_attrib[j]) { + // groups are compatible, merge them + append_to(groups[i], groups[j]); + groups.erase(groups.begin() + j); + group_attrib.erase(group_attrib.begin() + j); + } else { + ++j; + } + } + } + } + + // Give priority to bridges. Process the bridges in the first round, the rest of the surfaces in the 2nd round. + for (size_t round = 0; round < 2; ++ round) { + for (std::vector::const_iterator it_group = groups.begin(); it_group != groups.end(); ++ it_group) { + const SurfacesConstPtr &group = *it_group; + bool is_bridge = group.front()->bridge_angle >= 0; + if (is_bridge != (round == 0)) + continue; + + // Make a union of polygons defining the infiill regions of a group, use a safety offset. + Polygons union_p = union_(to_polygons(group), true); + + // Subtract surfaces having a defined bridge_angle from any other, use a safety offset. + if (!polygons_bridged.empty() && !is_bridge) + union_p = diff(union_p, polygons_bridged, true); + + // subtract any other surface already processed + //FIXME Vojtech: Because the bridge surfaces came first, they are subtracted twice! + surfaces.append( + diff_ex(union_p, to_polygons(surfaces), true), + *group.front() // template + ); + } + } + } + + // we need to detect any narrow surfaces that might collapse + // when adding spacing below + // such narrow surfaces are often generated in sloping walls + // by bridge_over_infill() and combine_infill() as a result of the + // subtraction of the combinable area from the layer infill area, + // which leaves small areas near the perimeters + // we are going to grow such regions by overlapping them with the void (if any) + // TODO: detect and investigate whether there could be narrow regions without + // any void neighbors + { + coord_t distance_between_surfaces = std::max( + std::max(infill_flow.scaled_spacing(), solid_infill_flow.scaled_spacing()), + top_solid_infill_flow.scaled_spacing() + ); + + Polygons surfaces_polygons = (Polygons)surfaces; + Polygons collapsed = diff( + surfaces_polygons, + offset2(surfaces_polygons, -distance_between_surfaces/2, +distance_between_surfaces/2), + true + ); + + Polygons to_subtract; + surfaces.filter_by_type(stInternalVoid, &to_subtract); + + append_to(to_subtract, collapsed); + surfaces.append( + intersection_ex( + offset(collapsed, distance_between_surfaces), + to_subtract, + true + ), + stInternalSolid + ); + } + + if (false) { +// require "Slic3r/SVG.pm"; +// Slic3r::SVG::output("fill_" . $layerm->print_z . ".svg", +// expolygons => [ map $_->expolygon, grep !$_->is_solid, @surfaces ], +// red_expolygons => [ map $_->expolygon, grep $_->is_solid, @surfaces ], +// ); + } + + for (Surfaces::const_iterator surface_it = surfaces.surfaces.begin(); + surface_it != surfaces.surfaces.end(); ++surface_it) { + + const Surface &surface = *surface_it; + if (surface.surface_type == stInternalVoid) + continue; + + InfillPattern fill_pattern = layerm.region()->config.fill_pattern.value; + double density = fill_density; + FlowRole role = (surface.surface_type == stTop) ? frTopSolidInfill + : surface.is_solid() ? frSolidInfill + : frInfill; + const bool is_bridge = layerm.layer()->id() > 0 && surface.is_bridge(); + + if (surface.is_solid()) { + density = 100.; + fill_pattern = (surface.is_external() && !is_bridge) + ? layerm.region()->config.external_fill_pattern.value + : ipRectilinear; + } else if (density <= 0) + continue; + + // get filler object + #if SLIC3R_CPPVER >= 11 + std::unique_ptr f = std::unique_ptr(Fill::new_from_type(fill_pattern)); + #else + std::auto_ptr f = std::auto_ptr(Fill::new_from_type(fill_pattern)); + #endif + f->set_bounding_box(layerm.layer()->object()->bounding_box()); + + // calculate the actual flow we'll be using for this infill + coordf_t h = (surface.thickness == -1) ? layerm.layer()->height : surface.thickness; + Flow flow = layerm.region()->flow( + role, + h, + is_bridge || f->use_bridge_flow(), // bridge flow? + layerm.layer()->id() == 0, // first layer? + -1, // auto width + *layerm.layer()->object() + ); + + // calculate flow spacing for infill pattern generation + bool using_internal_flow = false; + if (!surface.is_solid() && !is_bridge) { + // it's internal infill, so we can calculate a generic flow spacing + // for all layers, for avoiding the ugly effect of + // misaligned infill on first layer because of different extrusion width and + // layer height + Flow internal_flow = layerm.region()->flow( + frInfill, + layerm.layer()->object()->config.layer_height.value, // TODO: handle infill_every_layers? + false, // no bridge + false, // no first layer + -1, // auto width + *layerm.layer()->object() + ); + f->spacing = internal_flow.spacing(); + using_internal_flow = true; + } else { + f->spacing = flow.spacing(); + } + + f->layer_id = layerm.layer()->id(); + f->z = layerm.layer()->print_z; + f->angle = Geometry::deg2rad(layerm.region()->config.fill_angle.value); + + // Maximum length of the perimeter segment linking two infill lines. + f->link_max_length = (!is_bridge && density > 80) + ? scale_(3 * f->spacing) + : 0; + + // Used by the concentric infill pattern to clip the loops to create extrusion paths. + f->loop_clipping = scale_(flow.nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER; + + // apply half spacing using this flow's own spacing and generate infill + FillParams params; + params.density = density/100; + params.dont_adjust = false; + Polylines polylines = f->fill_surface(surface, params); + if (polylines.empty()) + continue; + + // calculate actual flow from spacing (which might have been adjusted by the infill + // pattern generator) + if (using_internal_flow) { + // if we used the internal flow we're not doing a solid infill + // so we can safely ignore the slight variation that might have + // been applied to f->spacing + } else { + flow = Flow::new_from_spacing(f->spacing, flow.nozzle_diameter, h, is_bridge || f->use_bridge_flow()); + } + + // Save into layer. + ExtrusionEntityCollection* coll = new ExtrusionEntityCollection(); + coll->no_sort = f->no_sort(); + out->entities.push_back(coll); + + { + ExtrusionRole role; + if (is_bridge) { + role = erBridgeInfill; + } else if (surface.is_solid()) { + role = (surface.surface_type == stTop) ? erTopSolidInfill : erSolidInfill; + } else { + role = erInternalInfill; + } + + ExtrusionPath templ(role); + templ.mm3_per_mm = flow.mm3_per_mm(); + templ.width = flow.width; + templ.height = flow.height; + + coll->append(STDMOVE(polylines), templ); + } + } + + // add thin fill regions + // thin_fills are of C++ Slic3r::ExtrusionEntityCollection, perl type Slic3r::ExtrusionPath::Collection + // Unpacks the collection, creates multiple collections per path so that they will + // be individually included in the nearest neighbor search. + // The path type could be ExtrusionPath, ExtrusionLoop or ExtrusionEntityCollection. + for (ExtrusionEntitiesPtr::const_iterator thin_fill = layerm.thin_fills.entities.begin(); thin_fill != layerm.thin_fills.entities.end(); ++ thin_fill) { + ExtrusionEntityCollection* coll = new ExtrusionEntityCollection(); + out->entities.push_back(coll); + coll->entities.push_back((*thin_fill)->clone()); + } +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/Fill/Fill.hpp b/xs/src/libslic3r/Fill/Fill.hpp new file mode 100644 index 000000000..58e446933 --- /dev/null +++ b/xs/src/libslic3r/Fill/Fill.hpp @@ -0,0 +1,23 @@ +#ifndef slic3r_Fill_hpp_ +#define slic3r_Fill_hpp_ + +#include +#include +#include + +#include "../libslic3r.h" +#include "../BoundingBox.hpp" +#include "../PrintConfig.hpp" + +#include "FillBase.hpp" + +namespace Slic3r { + +class ExtrusionEntityCollection; +class LayerRegion; + +void make_fill(const LayerRegion &layerm, ExtrusionEntityCollection* out); + +} // namespace Slic3r + +#endif // slic3r_Fill_hpp_ diff --git a/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp b/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp new file mode 100644 index 000000000..e23f77b2c --- /dev/null +++ b/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp @@ -0,0 +1,227 @@ +#include "../ClipperUtils.hpp" +#include "../PolylineCollection.hpp" +#include "../Surface.hpp" + +#include "Fill3DHoneycomb.hpp" + +namespace Slic3r { + +/* +Creates a contiguous sequence of points at a specified height that make +up a horizontal slice of the edges of a space filling truncated +octahedron tesselation. The octahedrons are oriented so that the +square faces are in the horizontal plane with edges parallel to the X +and Y axes. + +Credits: David Eccles (gringer). +*/ + +// Generate an array of points that are in the same direction as the +// basic printing line (i.e. Y points for columns, X points for rows) +// Note: a negative offset only causes a change in the perpendicular +// direction +static std::vector +colinearPoints(const coordf_t offset, const size_t baseLocation, size_t gridLength) +{ + const coordf_t offset2 = std::abs(offset / coordf_t(2.)); + std::vector points; + points.push_back(baseLocation - offset2); + for (size_t i = 0; i < gridLength; ++i) { + points.push_back(baseLocation + i + offset2); + points.push_back(baseLocation + i + 1 - offset2); + } + points.push_back(baseLocation + gridLength + offset2); + return points; +} + +// Generate an array of points for the dimension that is perpendicular to +// the basic printing line (i.e. X points for columns, Y points for rows) +static std::vector +perpendPoints(const coordf_t offset, const size_t baseLocation, size_t gridLength) +{ + const coordf_t offset2 = offset / coordf_t(2.); + coord_t side = 2 * (baseLocation & 1) - 1; + std::vector points; + points.push_back(baseLocation - offset2 * side); + for (size_t i = 0; i < gridLength; ++i) { + side = 2*((i+baseLocation) & 1) - 1; + points.push_back(baseLocation + offset2 * side); + points.push_back(baseLocation + offset2 * side); + } + points.push_back(baseLocation - offset2 * side); + return points; +} + +template +static inline T +clamp(T low, T high, T x) +{ + return std::max(low, std::min(high, x)); +} + +// Trims an array of points to specified rectangular limits. Point +// components that are outside these limits are set to the limits. +static inline void +trim(Pointfs &pts, coordf_t minX, coordf_t minY, coordf_t maxX, coordf_t maxY) +{ + for (Pointfs::iterator it = pts.begin(); it != pts.end(); ++ it) { + it->x = clamp(minX, maxX, it->x); + it->y = clamp(minY, maxY, it->y); + } +} + +static inline Pointfs +zip(const std::vector &x, const std::vector &y) +{ + assert(x.size() == y.size()); + Pointfs out; + out.reserve(x.size()); + for (size_t i = 0; i < x.size(); ++ i) + out.push_back(Pointf(x[i], y[i])); + return out; +} + +// Generate a set of curves (array of array of 2d points) that describe a +// horizontal slice of a truncated regular octahedron with edge length 1. +// curveType specifies which lines to print, 1 for vertical lines +// (columns), 2 for horizontal lines (rows), and 3 for both. +static std::vector +makeNormalisedGrid(coordf_t z, size_t gridWidth, size_t gridHeight, size_t curveType) +{ + // offset required to create a regular octagram + coordf_t octagramGap = coordf_t(0.5); + + // sawtooth wave function for range f($z) = [-$octagramGap .. $octagramGap] + coordf_t a = std::sqrt(coordf_t(2.)); // period + coordf_t wave = fabs(fmod(z, a) - a/2.)/a*4. - 1.; + coordf_t offset = wave * octagramGap; + + std::vector points; + if ((curveType & 1) != 0) { + for (size_t x = 0; x <= gridWidth; ++x) { + points.push_back(Pointfs()); + Pointfs &newPoints = points.back(); + newPoints = zip( + perpendPoints(offset, x, gridHeight), + colinearPoints(offset, 0, gridHeight)); + // trim points to grid edges + trim(newPoints, coordf_t(0.), coordf_t(0.), coordf_t(gridWidth), coordf_t(gridHeight)); + if (x & 1) + std::reverse(newPoints.begin(), newPoints.end()); + } + } + if ((curveType & 2) != 0) { + for (size_t y = 0; y <= gridHeight; ++y) { + points.push_back(Pointfs()); + Pointfs &newPoints = points.back(); + newPoints = zip( + colinearPoints(offset, 0, gridWidth), + perpendPoints(offset, y, gridWidth) + ); + // trim points to grid edges + trim(newPoints, coordf_t(0.), coordf_t(0.), coordf_t(gridWidth), coordf_t(gridHeight)); + if (y & 1) + std::reverse(newPoints.begin(), newPoints.end()); + } + } + return points; +} + +// Generate a set of curves (array of array of 2d points) that describe a +// horizontal slice of a truncated regular octahedron with a specified +// grid square size. +static Polylines +makeGrid(coord_t z, coord_t gridSize, size_t gridWidth, size_t gridHeight, size_t curveType) +{ + coord_t scaleFactor = gridSize; + coordf_t normalisedZ = coordf_t(z) / coordf_t(scaleFactor); + std::vector polylines = makeNormalisedGrid(normalisedZ, gridWidth, gridHeight, curveType); + Polylines result; + result.reserve(polylines.size()); + for (std::vector::const_iterator it_polylines = polylines.begin(); it_polylines != polylines.end(); ++ it_polylines) { + result.push_back(Polyline()); + Polyline &polyline = result.back(); + for (Pointfs::const_iterator it = it_polylines->begin(); it != it_polylines->end(); ++ it) + polyline.points.push_back(Point(coord_t(it->x * scaleFactor), coord_t(it->y * scaleFactor))); + } + return result; +} + +void +Fill3DHoneycomb::_fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines* polylines_out) +{ + // no rotation is supported for this infill pattern + BoundingBox bb = expolygon.contour.bounding_box(); + const coord_t distance = coord_t(scale_(this->spacing) / params.density); + + // align bounding box to a multiple of our honeycomb grid module + // (a module is 2*$distance since one $distance half-module is + // growing while the other $distance half-module is shrinking) + bb.min.align_to_grid(Point(2*distance, 2*distance)); + + // generate pattern + Polylines polylines = makeGrid( + scale_(this->z), + distance, + ceil(bb.size().x / distance) + 1, + ceil(bb.size().y / distance) + 1, + ((this->layer_id/thickness_layers) % 2) + 1 + ); + + // move pattern in place + for (Polylines::iterator it = polylines.begin(); it != polylines.end(); ++ it) + it->translate(bb.min.x, bb.min.y); + + // clip pattern to boundaries + polylines = intersection_pl(polylines, (Polygons)expolygon); + + // connect lines + if (!params.dont_connect && !polylines.empty()) { // prevent calling leftmost_point() on empty collections + ExPolygon expolygon_off; + { + ExPolygons expolygons_off = offset_ex(expolygon, SCALED_EPSILON); + if (!expolygons_off.empty()) { + // When expanding a polygon, the number of islands could only shrink. Therefore the offset_ex shall generate exactly one expanded island for one input island. + assert(expolygons_off.size() == 1); + std::swap(expolygon_off, expolygons_off.front()); + } + } + Polylines chained = PolylineCollection::chained_path_from( + STDMOVE(polylines), + PolylineCollection::leftmost_point(polylines), + false // reverse allowed + ); + bool first = true; + for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) { + if (!first) { + // Try to connect the lines. + Points &pts_end = polylines_out->back().points; + const Point &first_point = it_polyline->points.front(); + const Point &last_point = pts_end.back(); + // TODO: we should also check that both points are on a fill_boundary to avoid + // connecting paths on the boundaries of internal regions + if (first_point.distance_to(last_point) <= 1.5 * distance && + expolygon_off.contains(Line(last_point, first_point))) { + // Append the polyline. + pts_end.insert(pts_end.end(), it_polyline->points.begin(), it_polyline->points.end()); + continue; + } + } + // The lines cannot be connected. + #if SLIC3R_CPPVER >= 11 + polylines_out->push_back(std::move(*it_polyline)); + #else + polylines_out->push_back(Polyline()); + std::swap(polylines_out->back(), *it_polyline); + #endif + first = false; + } + } +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/Fill/Fill3DHoneycomb.hpp b/xs/src/libslic3r/Fill/Fill3DHoneycomb.hpp new file mode 100644 index 000000000..89b781e1e --- /dev/null +++ b/xs/src/libslic3r/Fill/Fill3DHoneycomb.hpp @@ -0,0 +1,31 @@ +#ifndef slic3r_Fill3DHoneycomb_hpp_ +#define slic3r_Fill3DHoneycomb_hpp_ + +#include + +#include "../libslic3r.h" + +#include "FillBase.hpp" + +namespace Slic3r { + +class Fill3DHoneycomb : public Fill +{ +public: + virtual ~Fill3DHoneycomb() {} + + // require bridge flow since most of this pattern hangs in air + virtual bool use_bridge_flow() const { return true; } + +protected: + virtual void _fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines* polylines_out); +}; + +} // namespace Slic3r + +#endif // slic3r_Fill3DHoneycomb_hpp_ diff --git a/xs/src/libslic3r/Fill/FillBase.cpp b/xs/src/libslic3r/Fill/FillBase.cpp new file mode 100644 index 000000000..987d51f3d --- /dev/null +++ b/xs/src/libslic3r/Fill/FillBase.cpp @@ -0,0 +1,138 @@ +#include +#include + +#include "../ClipperUtils.hpp" +#include "../Surface.hpp" +#include "../PrintConfig.hpp" + +#include "FillBase.hpp" +#include "FillConcentric.hpp" +#include "FillHoneycomb.hpp" +#include "Fill3DHoneycomb.hpp" +#include "FillPlanePath.hpp" +#include "FillRectilinear.hpp" +#include "FillRectilinear2.hpp" + +namespace Slic3r { + +Fill* +Fill::new_from_type(const InfillPattern type) +{ + switch (type) { + case ipConcentric: return new FillConcentric(); + case ipHoneycomb: return new FillHoneycomb(); + case ip3DHoneycomb: return new Fill3DHoneycomb(); + + case ipRectilinear: return new FillRectilinear(); + case ipLine: return new FillLine(); + case ipGrid: return new FillGrid(); + case ipAlignedRectilinear: return new FillAlignedRectilinear(); + + case ipRectilinear2: return new FillRectilinear2(); + case ipGrid2: return new FillGrid2(); + case ipTriangles: return new FillTriangles(); + case ipStars: return new FillStars(); + case ipCubic: return new FillCubic(); + + case ipArchimedeanChords: return new FillArchimedeanChords(); + case ipHilbertCurve: return new FillHilbertCurve(); + case ipOctagramSpiral: return new FillOctagramSpiral(); + + default: CONFESS("unknown type"); return NULL; + } +} + +Fill* +Fill::new_from_type(const std::string &type) +{ + static t_config_enum_values enum_keys_map = ConfigOptionEnum::get_enum_values(); + t_config_enum_values::const_iterator it = enum_keys_map.find(type); + return (it == enum_keys_map.end()) ? NULL : new_from_type(InfillPattern(it->second)); +} + +Polylines +Fill::fill_surface(const Surface &surface, const FillParams ¶ms) +{ + // Perform offset. + ExPolygons expp = offset_ex(surface.expolygon, -scale_(this->spacing)/2); + + // Create the infills for each of the regions. + Polylines polylines_out; + for (size_t i = 0; i < expp.size(); ++i) + this->_fill_surface_single( + params, + surface.thickness_layers, + this->_infill_direction(surface), + expp[i], + &polylines_out + ); + return polylines_out; +} + +// Calculate a new spacing to fill width with possibly integer number of lines, +// the first and last line being centered at the interval ends. +// This function possibly increases the spacing, never decreases, +// and for a narrow width the increase in spacing may become severe, +// therefore the adjustment is limited to 20% increase. +coord_t +Fill::adjust_solid_spacing(const coord_t width, const coord_t distance) +{ + assert(width >= 0); + assert(distance > 0); + // floor(width / distance) + coord_t number_of_intervals = floor(width / distance); + coord_t distance_new = (number_of_intervals == 0) + ? distance + : (width / number_of_intervals); + + const coordf_t factor = coordf_t(distance_new) / coordf_t(distance); + assert(factor > 1. - 1e-5); + + // How much could the extrusion width be increased? By 20%. + const coordf_t factor_max = 1.2; + if (factor > factor_max) + distance_new = floor((double)distance * factor_max + 0.5); + + return distance_new; +} + +// Returns orientation of the infill and the reference point of the infill pattern. +// For a normal print, the reference point is the center of a bounding box of the STL. +std::pair +Fill::_infill_direction(const Surface &surface) const +{ + // set infill angle + float out_angle = this->angle; + + // Bounding box is the bounding box of a Slic3r::PrintObject + // The bounding box is only undefined in unit tests. + Point out_shift = this->bounding_box.defined + ? this->bounding_box.center() + : surface.expolygon.contour.bounding_box().center(); + + #if 0 + if (!this->bounding_box.defined) { + printf("Fill::_infill_direction: empty bounding box!"); + } else { + printf("Fill::_infill_direction: reference point %d, %d\n", out_shift.x, out_shift.y); + } + #endif + + if (surface.bridge_angle >= 0) { + // use bridge angle + //FIXME Vojtech: Add a debugf? + // Slic3r::debugf "Filling bridge with angle %d\n", rad2deg($surface->bridge_angle); + #ifdef SLIC3R_DEBUG + printf("Filling bridge with angle %f\n", surface.bridge_angle); + #endif + out_angle = surface.bridge_angle; + } else if (this->layer_id != size_t(-1)) { + // alternate fill direction + out_angle += this->_layer_angle(this->layer_id / surface.thickness_layers); + } + + out_angle += float(M_PI/2.); + return std::pair(out_angle, out_shift); +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/Fill/FillBase.hpp b/xs/src/libslic3r/Fill/FillBase.hpp new file mode 100644 index 000000000..75627d29e --- /dev/null +++ b/xs/src/libslic3r/Fill/FillBase.hpp @@ -0,0 +1,111 @@ +#ifndef slic3r_FillBase_hpp_ +#define slic3r_FillBase_hpp_ + +#include +#include +#include +#include + +#include "../libslic3r.h" +#include "../BoundingBox.hpp" +#include "../ExPolygon.hpp" +#include "../Polyline.hpp" +#include "../PrintConfig.hpp" + +namespace Slic3r { + +class Surface; + +struct FillParams +{ + public: + FillParams() : density(0), dont_connect(false), dont_adjust(false), complete(false) {}; + + // Fill density, fraction in <0, 1> + float density; + + // Don't connect the fill lines around the inner perimeter. + bool dont_connect; + + // Don't adjust spacing to fill the space evenly. + bool dont_adjust; + + // For Honeycomb. + // we were requested to complete each loop; + // in this case we don't try to make more continuous paths + bool complete; +}; + +class Fill +{ +public: + // Index of the layer. + size_t layer_id; + + // Z coordinate of the top print surface, in unscaled coordinates + coordf_t z; + + // in unscaled coordinates + coordf_t spacing; + + // in radians, ccw, 0 = East + float angle; + + // In scaled coordinates. Maximum lenght of a perimeter segment connecting two infill lines. + // Used by the FillRectilinear2, FillGrid2, FillTriangles, FillStars and FillCubic. + // If left to zero, the links will not be limited. + coord_t link_max_length; + + // In scaled coordinates. Used by the concentric infill pattern to clip the loops to create extrusion paths. + coord_t loop_clipping; + + // In scaled coordinates. Bounding box of the 2D projection of the object. + BoundingBox bounding_box; + +public: + virtual ~Fill() {} + + static Fill* new_from_type(const InfillPattern type); + static Fill* new_from_type(const std::string &type); + + void set_bounding_box(const BoundingBox &bb) { this->bounding_box = bb; } + + // Use bridge flow for the fill? + virtual bool use_bridge_flow() const { return false; } + + // Do not sort the fill lines to optimize the print head path? + virtual bool no_sort() const { return false; } + + // Perform the fill. + virtual Polylines fill_surface(const Surface &surface, const FillParams ¶ms); + + static coord_t adjust_solid_spacing(const coord_t width, const coord_t distance); + +protected: + Fill() : + layer_id(size_t(-1)), + z(0.f), + spacing(0.f), + angle(0), + link_max_length(0), + loop_clipping(0) + {}; + + // The expolygon may be modified by the method to avoid a copy. + virtual void _fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines* polylines_out) {}; + + virtual float _layer_angle(size_t idx) const { + return (idx % 2) == 0 ? (M_PI/2.) : 0; + } + + std::pair _infill_direction(const Surface &surface) const; +}; + +} // namespace Slic3r + +#endif // slic3r_FillBase_hpp_ diff --git a/xs/src/libslic3r/Fill/FillConcentric.cpp b/xs/src/libslic3r/Fill/FillConcentric.cpp new file mode 100644 index 000000000..d31efcafc --- /dev/null +++ b/xs/src/libslic3r/Fill/FillConcentric.cpp @@ -0,0 +1,63 @@ +#include "../ClipperUtils.hpp" +#include "../ExPolygon.hpp" +#include "../Surface.hpp" + +#include "FillConcentric.hpp" + +namespace Slic3r { + +void +FillConcentric::_fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines* polylines_out) +{ + // no rotation is supported for this infill pattern + + const coord_t min_spacing = scale_(this->spacing); + coord_t distance = coord_t(min_spacing / params.density); + + if (params.density > 0.9999f && !params.dont_adjust) { + BoundingBox bounding_box = expolygon.contour.bounding_box(); + distance = this->adjust_solid_spacing(bounding_box.size().x, distance); + this->spacing = unscale(distance); + } + + Polygons loops = (Polygons)expolygon; + Polygons last = loops; + while (!last.empty()) { + last = offset2(last, -(distance + min_spacing/2), +min_spacing/2); + loops.insert(loops.end(), last.begin(), last.end()); + } + + // generate paths from the outermost to the innermost, to avoid + // adhesion problems of the first central tiny loops + loops = union_pt_chained(loops, false); + + // split paths using a nearest neighbor search + size_t iPathFirst = polylines_out->size(); + Point last_pos(0, 0); + for (Polygons::const_iterator it_loop = loops.begin(); it_loop != loops.end(); ++ it_loop) { + polylines_out->push_back(it_loop->split_at_index(last_pos.nearest_point_index(*it_loop))); + last_pos = polylines_out->back().last_point(); + } + + // clip the paths to prevent the extruder from getting exactly on the first point of the loop + // Keep valid paths only. + size_t j = iPathFirst; + for (size_t i = iPathFirst; i < polylines_out->size(); ++ i) { + (*polylines_out)[i].clip_end(this->loop_clipping); + if ((*polylines_out)[i].is_valid()) { + if (j < i) + std::swap((*polylines_out)[j], (*polylines_out)[i]); + ++j; + } + } + if (j < polylines_out->size()) + polylines_out->erase(polylines_out->begin() + j, polylines_out->end()); + // TODO: return ExtrusionLoop objects to get better chained paths +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/Fill/FillConcentric.hpp b/xs/src/libslic3r/Fill/FillConcentric.hpp new file mode 100644 index 000000000..a4e1ddb11 --- /dev/null +++ b/xs/src/libslic3r/Fill/FillConcentric.hpp @@ -0,0 +1,26 @@ +#ifndef slic3r_FillConcentric_hpp_ +#define slic3r_FillConcentric_hpp_ + +#include "FillBase.hpp" + +namespace Slic3r { + +class FillConcentric : public Fill +{ +public: + virtual ~FillConcentric() {} + +protected: + virtual void _fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines* polylines_out); + + virtual bool no_sort() const { return true; } +}; + +} // namespace Slic3r + +#endif // slic3r_FillConcentric_hpp_ diff --git a/xs/src/libslic3r/Fill/FillHoneycomb.cpp b/xs/src/libslic3r/Fill/FillHoneycomb.cpp new file mode 100644 index 000000000..e3a67f3f7 --- /dev/null +++ b/xs/src/libslic3r/Fill/FillHoneycomb.cpp @@ -0,0 +1,126 @@ +#include "FillHoneycomb.hpp" +#include "../ClipperUtils.hpp" +#include "../PolylineCollection.hpp" +#include "../Surface.hpp" + + +namespace Slic3r { + +void +FillHoneycomb::_fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines* polylines_out) +{ + // cache hexagons math + CacheID cache_id = std::make_pair(params.density, this->spacing); + Cache::iterator it_m = this->cache.find(cache_id); + if (it_m == this->cache.end()) { + it_m = this->cache.insert(it_m, std::pair(cache_id, CacheData())); + CacheData &m = it_m->second; + coord_t min_spacing = scale_(this->spacing); + m.distance = min_spacing / params.density; + m.hex_side = m.distance / (sqrt(3)/2); + m.hex_width = m.distance * 2; // $m->{hex_width} == $m->{hex_side} * sqrt(3); + coord_t hex_height = m.hex_side * 2; + m.pattern_height = hex_height + m.hex_side; + m.y_short = m.distance * sqrt(3)/3; + m.x_offset = min_spacing / 2; + m.y_offset = m.x_offset * sqrt(3)/3; + m.hex_center = Point(m.hex_width/2, m.hex_side); + } + CacheData &m = it_m->second; + + Polygons polygons; + { + // adjust actual bounding box to the nearest multiple of our hex pattern + // and align it so that it matches across layers + + BoundingBox bounding_box = expolygon.contour.bounding_box(); + { + // rotate bounding box according to infill direction + Polygon bb_polygon = bounding_box.polygon(); + bb_polygon.rotate(direction.first, m.hex_center); + bounding_box = bb_polygon.bounding_box(); + + // extend bounding box so that our pattern will be aligned with other layers + // $bounding_box->[X1] and [Y1] represent the displacement between new bounding box offset and old one + // The infill is not aligned to the object bounding box, but to a world coordinate system. Supposedly good enough. + bounding_box.min.align_to_grid(Point(m.hex_width, m.pattern_height)); + } + + for (coord_t x = bounding_box.min.x; x <= bounding_box.max.x; ) { + Polygon p; + coord_t ax[2] = { x + m.x_offset, x + m.distance - m.x_offset }; + for (size_t i = 0; i < 2; ++ i) { + std::reverse(p.points.begin(), p.points.end()); // turn first half upside down + for (coord_t y = bounding_box.min.y; y <= bounding_box.max.y; y += m.y_short + m.hex_side + m.y_short + m.hex_side) { + p.points.push_back(Point(ax[1], y + m.y_offset)); + p.points.push_back(Point(ax[0], y + m.y_short - m.y_offset)); + p.points.push_back(Point(ax[0], y + m.y_short + m.hex_side + m.y_offset)); + p.points.push_back(Point(ax[1], y + m.y_short + m.hex_side + m.y_short - m.y_offset)); + p.points.push_back(Point(ax[1], y + m.y_short + m.hex_side + m.y_short + m.hex_side + m.y_offset)); + } + ax[0] = ax[0] + m.distance; + ax[1] = ax[1] + m.distance; + std::swap(ax[0], ax[1]); // draw symmetrical pattern + x += m.distance; + } + p.rotate(-direction.first, m.hex_center); + polygons.push_back(p); + } + } + + if (true || params.complete) { + // we were requested to complete each loop; + // in this case we don't try to make more continuous paths + Polygons polygons_trimmed = intersection((Polygons)expolygon, polygons); + for (Polygons::iterator it = polygons_trimmed.begin(); it != polygons_trimmed.end(); ++ it) + polylines_out->push_back(it->split_at_first_point()); + } else { + // consider polygons as polylines without re-appending the initial point: + // this cuts the last segment on purpose, so that the jump to the next + // path is more straight + Polylines paths = intersection_pl( + to_polylines(polygons), + (Polygons)expolygon + ); + + // connect paths + if (!paths.empty()) { // prevent calling leftmost_point() on empty collections + Polylines chained = PolylineCollection::chained_path_from( + STDMOVE(paths), + PolylineCollection::leftmost_point(paths), + false + ); + assert(paths.empty()); + paths.clear(); + + for (Polylines::iterator it_path = chained.begin(); it_path != chained.end(); ++ it_path) { + if (!paths.empty()) { + // distance between first point of this path and last point of last path + double distance = paths.back().last_point().distance_to(it_path->first_point()); + if (distance <= m.hex_width) { + paths.back().points.insert(paths.back().points.end(), it_path->points.begin(), it_path->points.end()); + continue; + } + } + // Don't connect the paths. + paths.push_back(*it_path); + } + } + + // clip paths again to prevent connection segments from crossing the expolygon boundaries + paths = intersection_pl(paths, to_polygons(offset_ex(expolygon, SCALED_EPSILON))); + + // Move the polylines to the output, avoid a deep copy. + size_t j = polylines_out->size(); + polylines_out->resize(j + paths.size(), Polyline()); + for (size_t i = 0; i < paths.size(); ++ i) + std::swap((*polylines_out)[j++], paths[i]); + } +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/Fill/FillHoneycomb.hpp b/xs/src/libslic3r/Fill/FillHoneycomb.hpp new file mode 100644 index 000000000..70caaf7a0 --- /dev/null +++ b/xs/src/libslic3r/Fill/FillHoneycomb.hpp @@ -0,0 +1,47 @@ +#ifndef slic3r_FillHoneycomb_hpp_ +#define slic3r_FillHoneycomb_hpp_ + +#include + +#include "../libslic3r.h" + +#include "FillBase.hpp" + +namespace Slic3r { + +class FillHoneycomb : public Fill +{ +public: + virtual ~FillHoneycomb() {} + +protected: + virtual void _fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines* polylines_out + ); + + // Cache the hexagon math. + struct CacheData + { + coord_t distance; + coord_t hex_side; + coord_t hex_width; + coord_t pattern_height; + coord_t y_short; + coord_t x_offset; + coord_t y_offset; + Point hex_center; + }; + typedef std::pair CacheID; // density, spacing + typedef std::map Cache; + Cache cache; + + virtual float _layer_angle(size_t idx) const { return float(M_PI/3.) * (idx % 3); } +}; + +} // namespace Slic3r + +#endif // slic3r_FillHoneycomb_hpp_ diff --git a/xs/src/libslic3r/Fill/FillPlanePath.cpp b/xs/src/libslic3r/Fill/FillPlanePath.cpp new file mode 100644 index 000000000..7952daf5b --- /dev/null +++ b/xs/src/libslic3r/Fill/FillPlanePath.cpp @@ -0,0 +1,206 @@ +#include "../ClipperUtils.hpp" +#include "../PolylineCollection.hpp" +#include "../Surface.hpp" + +#include "FillPlanePath.hpp" + +namespace Slic3r { + +void FillPlanePath::_fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines* polylines_out) +{ + expolygon.rotate(-direction.first); + + const coord_t distance_between_lines = scale_(this->spacing) / params.density; + + // align infill across layers using the object's bounding box + // Rotated bounding box of the whole object. + BoundingBox bounding_box = this->bounding_box.rotated(-direction.first); + + const Point shift = this->_centered() + ? bounding_box.center() + : bounding_box.min; + expolygon.translate(-shift.x, -shift.y); + bounding_box.translate(-shift.x, -shift.y); + + const Pointfs pts = this->_generate( + coord_t(ceil(coordf_t(bounding_box.min.x) / distance_between_lines)), + coord_t(ceil(coordf_t(bounding_box.min.y) / distance_between_lines)), + coord_t(ceil(coordf_t(bounding_box.max.x) / distance_between_lines)), + coord_t(ceil(coordf_t(bounding_box.max.y) / distance_between_lines)) + ); + + Polylines polylines; + if (pts.size() >= 2) { + // Convert points to a polyline, upscale. + polylines.push_back(Polyline()); + Polyline &polyline = polylines.back(); + polyline.points.reserve(pts.size()); + for (Pointfs::const_iterator it = pts.begin(); it != pts.end(); ++ it) { + polyline.points.push_back(Point( + coord_t(floor(it->x * distance_between_lines + 0.5)), + coord_t(floor(it->y * distance_between_lines + 0.5)) + )); + } +// polylines = intersection_pl(polylines_src, offset((Polygons)expolygon, scale_(0.02))); + polylines = intersection_pl(polylines, (Polygons)expolygon); + +/* + if (1) { + require "Slic3r/SVG.pm"; + print "Writing fill.svg\n"; + Slic3r::SVG::output("fill.svg", + no_arrows => 1, + polygons => \@$expolygon, + green_polygons => [ $bounding_box->polygon ], + polylines => [ $polyline ], + red_polylines => \@paths, + ); + } +*/ + + // paths must be repositioned and rotated back + for (Polylines::iterator it = polylines.begin(); it != polylines.end(); ++ it) { + it->translate(shift.x, shift.y); + it->rotate(direction.first); + } + } + + // Move the polylines to the output, avoid a deep copy. + size_t j = polylines_out->size(); + polylines_out->resize(j + polylines.size(), Polyline()); + for (size_t i = 0; i < polylines.size(); ++ i) + std::swap((*polylines_out)[j++], polylines[i]); +} + +// Follow an Archimedean spiral, in polar coordinates: r=a+b\theta +Pointfs FillArchimedeanChords::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y) +{ + // Radius to achieve. + const coordf_t rmax = std::sqrt(coordf_t(max_x)*coordf_t(max_x)+coordf_t(max_y)*coordf_t(max_y)) * std::sqrt(2.) + 1.5; + // Now unwind the spiral. + const coordf_t a = 1.; + const coordf_t b = 1./(2.*M_PI); + coordf_t theta = 0.; + coordf_t r = 1; + Pointfs out; + //FIXME Vojtech: If used as a solid infill, there is a gap left at the center. + out.push_back(Pointf(0, 0)); + out.push_back(Pointf(1, 0)); + while (r < rmax) { + theta += 1. / r; + r = a + b * theta; + out.push_back(Pointf(r * cos(theta), r * sin(theta))); + } + return out; +} + +// Adapted from +// http://cpansearch.perl.org/src/KRYDE/Math-PlanePath-122/lib/Math/PlanePath/HilbertCurve.pm +// +// state=0 3--2 plain +// | +// 0--1 +// +// state=4 1--2 transpose +// | | +// 0 3 +// +// state=8 +// +// state=12 3 0 rot180 + transpose +// | | +// 2--1 +// +static inline Point hilbert_n_to_xy(const size_t n) +{ + static const int next_state[16] = { 4,0,0,12, 0,4,4,8, 12,8,8,4, 8,12,12,0 }; + static const int digit_to_x[16] = { 0,1,1,0, 0,0,1,1, 1,0,0,1, 1,1,0,0 }; + static const int digit_to_y[16] = { 0,0,1,1, 0,1,1,0, 1,1,0,0, 1,0,0,1 }; + + // Number of 2 bit digits. + size_t ndigits = 0; + { + size_t nc = n; + while(nc > 0) { + nc >>= 2; + ++ ndigits; + } + } + int state = (ndigits & 1) ? 4 : 0; + int dirstate = (ndigits & 1) ? 0 : 4; + coord_t x = 0; + coord_t y = 0; + for (int i = (int)ndigits - 1; i >= 0; -- i) { + int digit = (n >> (i * 2)) & 3; + state += digit; + if (digit != 3) + dirstate = state; // lowest non-3 digit + x |= digit_to_x[state] << i; + y |= digit_to_y[state] << i; + state = next_state[state]; + } + return Point(x, y); +} + +Pointfs FillHilbertCurve::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y) +{ + // Minimum power of two square to fit the domain. + size_t sz = 2; + size_t pw = 1; + { + size_t sz0 = std::max(max_x + 1 - min_x, max_y + 1 - min_y); + while (sz < sz0) { + sz = sz << 1; + ++pw; + } + } + + const size_t sz2 = sz * sz; + Pointfs line; + line.reserve(sz2); + for (size_t i = 0; i < sz2; ++ i) { + Point p = hilbert_n_to_xy(i); + line.push_back(Pointf(p.x + min_x, p.y + min_y)); + } + return line; +} + +Pointfs FillOctagramSpiral::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y) +{ + // Radius to achieve. + const coordf_t rmax = std::sqrt(coordf_t(max_x)*coordf_t(max_x)+coordf_t(max_y)*coordf_t(max_y)) * std::sqrt(2.) + 1.5; + // Now unwind the spiral. + coordf_t r = 0; + const coordf_t r_inc = sqrt(2.); + Pointfs out; + out.push_back(Pointf(0, 0)); + while (r < rmax) { + r += r_inc; + coordf_t rx = r / sqrt(2.); + coordf_t r2 = r + rx; + out.push_back(Pointf( r, 0.)); + out.push_back(Pointf( r2, rx)); + out.push_back(Pointf( rx, rx)); + out.push_back(Pointf( rx, r2)); + out.push_back(Pointf(0., r)); + out.push_back(Pointf(-rx, r2)); + out.push_back(Pointf(-rx, rx)); + out.push_back(Pointf(-r2, rx)); + out.push_back(Pointf(-r, 0.)); + out.push_back(Pointf(-r2, -rx)); + out.push_back(Pointf(-rx, -rx)); + out.push_back(Pointf(-rx, -r2)); + out.push_back(Pointf(0., -r)); + out.push_back(Pointf( rx, -r2)); + out.push_back(Pointf( rx, -rx)); + out.push_back(Pointf( r2+r_inc, -rx)); + } + return out; +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/Fill/FillPlanePath.hpp b/xs/src/libslic3r/Fill/FillPlanePath.hpp new file mode 100644 index 000000000..33b3ec1db --- /dev/null +++ b/xs/src/libslic3r/Fill/FillPlanePath.hpp @@ -0,0 +1,66 @@ +#ifndef slic3r_FillPlanePath_hpp_ +#define slic3r_FillPlanePath_hpp_ + +#include + +#include "../libslic3r.h" + +#include "FillBase.hpp" + +namespace Slic3r { + +// The original Perl code used path generators from Math::PlanePath library: +// http://user42.tuxfamily.org/math-planepath/ +// http://user42.tuxfamily.org/math-planepath/gallery.html + +class FillPlanePath : public Fill +{ +public: + virtual ~FillPlanePath() {} + +protected: + virtual void _fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines* polylines_out); + + virtual float _layer_angle(size_t idx) const { return 0.f; } + virtual bool _centered() const = 0; + virtual Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y) = 0; +}; + +class FillArchimedeanChords : public FillPlanePath +{ +public: + virtual ~FillArchimedeanChords() {} + +protected: + virtual bool _centered() const { return true; } + virtual Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y); +}; + +class FillHilbertCurve : public FillPlanePath +{ +public: + virtual ~FillHilbertCurve() {} + +protected: + virtual bool _centered() const { return false; } + virtual Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y); +}; + +class FillOctagramSpiral : public FillPlanePath +{ +public: + virtual ~FillOctagramSpiral() {} + +protected: + virtual bool _centered() const { return true; } + virtual Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y); +}; + +} // namespace Slic3r + +#endif // slic3r_FillPlanePath_hpp_ diff --git a/xs/src/libslic3r/Fill/FillRectilinear.cpp b/xs/src/libslic3r/Fill/FillRectilinear.cpp new file mode 100644 index 000000000..dde04d27b --- /dev/null +++ b/xs/src/libslic3r/Fill/FillRectilinear.cpp @@ -0,0 +1,138 @@ +#include "../ClipperUtils.hpp" +#include "../ExPolygon.hpp" +#include "../PolylineCollection.hpp" +#include "../Surface.hpp" + +#include "FillRectilinear.hpp" + +namespace Slic3r { + +void FillRectilinear::_fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines* polylines_out) +{ + assert(params.density > 0.0001f && params.density <= 1.f); + + // rotate polygons so that we can work with vertical lines here + expolygon.rotate(-direction.first); + + this->_min_spacing = scale_(this->spacing); + this->_line_spacing = coord_t(coordf_t(this->_min_spacing) / params.density); + this->_diagonal_distance = this->_line_spacing * 2; + this->_line_oscillation = this->_line_spacing - this->_min_spacing; // only for Line infill + BoundingBox bounding_box = expolygon.contour.bounding_box(); + + // define flow spacing according to requested density + if (params.density > 0.9999f && !params.dont_adjust) { + this->_line_spacing = this->adjust_solid_spacing(bounding_box.size().x, this->_line_spacing); + this->spacing = unscale(this->_line_spacing); + } else { + // extend bounding box so that our pattern will be aligned with other layers + // Transform the reference point to the rotated coordinate system. + bounding_box.min.align_to_grid( + Point(this->_line_spacing, this->_line_spacing), + direction.second.rotated(-direction.first) + ); + } + + // generate the basic pattern + const coord_t x_max = bounding_box.max.x + SCALED_EPSILON; + Lines lines; + for (coord_t x = bounding_box.min.x; x <= x_max; x += this->_line_spacing) + lines.push_back(this->_line(lines.size(), x, bounding_box.min.y, bounding_box.max.y)); + if (this->_horizontal_lines()) { + const coord_t y_max = bounding_box.max.y + SCALED_EPSILON; + for (coord_t y = bounding_box.min.y; y <= y_max; y += this->_line_spacing) + lines.push_back(Line(Point(bounding_box.min.x, y), Point(bounding_box.max.x, y))); + } + + // clip paths against a slightly larger expolygon, so that the first and last paths + // are kept even if the expolygon has vertical sides + // the minimum offset for preventing edge lines from being clipped is SCALED_EPSILON; + // however we use a larger offset to support expolygons with slightly skewed sides and + // not perfectly straight + + Polylines polylines = intersection_pl( + to_polylines(lines), + offset(expolygon, scale_(0.02)), + false + ); + + // FIXME Vojtech: This is only performed for horizontal lines, not for the vertical lines! + const float INFILL_OVERLAP_OVER_SPACING = 0.3f; + + // How much to extend an infill path from expolygon outside? + const coord_t extra = coord_t(floor(this->_min_spacing * INFILL_OVERLAP_OVER_SPACING + 0.5f)); + for (Polylines::iterator it_polyline = polylines.begin(); + it_polyline != polylines.end(); ++ it_polyline) { + Point *first_point = &it_polyline->points.front(); + Point *last_point = &it_polyline->points.back(); + if (first_point->y > last_point->y) + std::swap(first_point, last_point); + first_point->y -= extra; + last_point->y += extra; + } + + size_t n_polylines_out_old = polylines_out->size(); + + // connect lines + if (!params.dont_connect && !polylines.empty()) { // prevent calling leftmost_point() on empty collections + // offset the expolygon by max(min_spacing/2, extra) + ExPolygon expolygon_off; + { + ExPolygons expolygons_off = offset_ex(expolygon, this->_min_spacing/2); + if (!expolygons_off.empty()) { + // When expanding a polygon, the number of islands could only shrink. + // Therefore the offset_ex shall generate exactly one expanded island + // for one input island. + assert(expolygons_off.size() == 1); + std::swap(expolygon_off, expolygons_off.front()); + } + } + Polylines chained = PolylineCollection::chained_path_from( + STDMOVE(polylines), + PolylineCollection::leftmost_point(polylines), + false // reverse allowed + ); + bool first = true; + for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) { + if (!first) { + // Try to connect the lines. + Points &pts_end = polylines_out->back().points; + const Point &first_point = it_polyline->points.front(); + const Point &last_point = pts_end.back(); + // Distance in X, Y. + const Vector distance = first_point.vector_to(last_point); + // TODO: we should also check that both points are on a fill_boundary to avoid + // connecting paths on the boundaries of internal regions + if (this->_can_connect(std::abs(distance.x), std::abs(distance.y)) + && expolygon_off.contains(Line(last_point, first_point))) { + // Append the polyline. + append_to(pts_end, it_polyline->points); + continue; + } + } + // The lines cannot be connected. + #if SLIC3R_CPPVER >= 11 + polylines_out->push_back(std::move(*it_polyline)); + #else + polylines_out->push_back(Polyline()); + std::swap(polylines_out->back(), *it_polyline); + #endif + first = false; + } + } + + // paths must be rotated back + for (Polylines::iterator it = polylines_out->begin() + n_polylines_out_old; + it != polylines_out->end(); ++ it) { + // No need to translate, the absolute position is irrelevant. + // it->translate(- direction.second.x, - direction.second.y); + it->rotate(direction.first); + } +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/Fill/FillRectilinear.hpp b/xs/src/libslic3r/Fill/FillRectilinear.hpp new file mode 100644 index 000000000..03c0826bc --- /dev/null +++ b/xs/src/libslic3r/Fill/FillRectilinear.hpp @@ -0,0 +1,86 @@ +#ifndef slic3r_FillRectilinear_hpp_ +#define slic3r_FillRectilinear_hpp_ + +#include "../libslic3r.h" + +#include "FillBase.hpp" + +namespace Slic3r { + +class FillRectilinear : public Fill +{ +public: + virtual ~FillRectilinear() {} + +protected: + virtual void _fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines* polylines_out); + + coord_t _min_spacing; + coord_t _line_spacing; + // distance threshold for allowing the horizontal infill lines to be connected into a continuous path + coord_t _diagonal_distance; + // only for line infill + coord_t _line_oscillation; + + // Enabled for the grid infill, disabled for the rectilinear and line infill. + virtual bool _horizontal_lines() const { return false; }; + + virtual Line _line(int i, coord_t x, coord_t y_min, coord_t y_max) const + { return Line(Point(x, y_min), Point(x, y_max)); }; + + virtual bool _can_connect(coord_t dist_X, coord_t dist_Y) { + return dist_X <= this->_diagonal_distance + && dist_Y <= this->_diagonal_distance; + }; +}; + +class FillLine : public FillRectilinear +{ +public: + virtual ~FillLine() {} + +protected: + virtual Line _line(int i, coord_t x, coord_t y_min, coord_t y_max) const { + coord_t osc = (i & 1) ? this->_line_oscillation : 0; + return Line(Point(x - osc, y_min), Point(x + osc, y_max)); + }; + + virtual bool _can_connect(coord_t dist_X, coord_t dist_Y) + { + coord_t TOLERANCE = 10 * SCALED_EPSILON; + return (dist_X >= (this->_line_spacing - this->_line_oscillation) - TOLERANCE) + && (dist_X <= (this->_line_spacing + this->_line_oscillation) + TOLERANCE) + && (dist_Y <= this->_diagonal_distance); + }; +}; + +class FillGrid : public FillRectilinear +{ +public: + virtual ~FillGrid() {} + +protected: + // The grid fill will keep the angle constant between the layers,; see the implementation of Slic3r::Fill. + virtual float _layer_angle(size_t idx) const { return 0.f; } + // Flag for Slic3r::Fill::Rectilinear to fill both directions. + virtual bool _horizontal_lines() const { return true; }; +}; + +class FillAlignedRectilinear : public FillRectilinear +{ +public: + virtual ~FillAlignedRectilinear() {}; + +protected: + // Keep the angle constant in all layers. + virtual float _layer_angle(size_t idx) const { printf("ALIGNED\n"); return 0.f; }; +}; + +}; // namespace Slic3r + +#endif // slic3r_FillRectilinear_hpp_ diff --git a/xs/src/libslic3r/Fill/FillRectilinear2.cpp b/xs/src/libslic3r/Fill/FillRectilinear2.cpp new file mode 100644 index 000000000..1458c90ac --- /dev/null +++ b/xs/src/libslic3r/Fill/FillRectilinear2.cpp @@ -0,0 +1,1712 @@ +#include +#include + +#include +#include +#include + +#include + +#include "../ClipperUtils.hpp" +#include "../ExPolygon.hpp" +#include "../Surface.hpp" + +#include "FillRectilinear2.hpp" + +// #define SLIC3R_DEBUG + +// Make assert active if SLIC3R_DEBUG +#ifdef SLIC3R_DEBUG + #undef NDEBUG + #include "SVG.hpp" +#endif + +#include + +// We want our version of assert. +#include "../libslic3r.h" + +#ifndef myassert +#define myassert assert +#endif + +namespace Slic3r { + +#ifndef clamp +template +static inline T clamp(T low, T high, T x) +{ + return std::max(low, std::min(high, x)); +} +#endif /* clamp */ + +#ifndef sqr +template +static inline T sqr(T x) +{ + return x * x; +} +#endif /* sqr */ + +#ifndef mag2 +static inline coordf_t mag2(const Point &p) +{ + return sqr(coordf_t(p.x)) + sqr(coordf_t(p.y)); +} +#endif /* mag2 */ + +#ifndef mag +static inline coordf_t mag(const Point &p) +{ + return std::sqrt(mag2(p)); +} +#endif /* mag */ + +enum Orientation +{ + ORIENTATION_CCW = 1, + ORIENTATION_CW = -1, + ORIENTATION_COLINEAR = 0 +}; + +// Return orientation of the three points (clockwise, counter-clockwise, colinear) +// The predicate is exact for the coord_t type, using 64bit signed integers for the temporaries. +//FIXME Make sure the temporaries do not overflow, +// which means, the coord_t types must not have some of the topmost bits utilized. +static inline Orientation orient(const Point &a, const Point &b, const Point &c) +{ + // BOOST_STATIC_ASSERT(sizeof(coord_t) * 2 == sizeof(int64_t)); + int64_t u = int64_t(b.x) * int64_t(c.y) - int64_t(b.y) * int64_t(c.x); + int64_t v = int64_t(a.x) * int64_t(c.y) - int64_t(a.y) * int64_t(c.x); + int64_t w = int64_t(a.x) * int64_t(b.y) - int64_t(a.y) * int64_t(b.x); + int64_t d = u - v + w; + return (d > 0) ? ORIENTATION_CCW : ((d == 0) ? ORIENTATION_COLINEAR : ORIENTATION_CW); +} + +// Return orientation of the polygon. +// The input polygon must not contain duplicate points. +static inline bool is_ccw(const Polygon &poly) +{ + // The polygon shall be at least a triangle. + myassert(poly.points.size() >= 3); + if (poly.points.size() < 3) + return true; + + // 1) Find the lowest lexicographical point. + int imin = 0; + for (size_t i = 1; i < poly.points.size(); ++ i) { + const Point &pmin = poly.points[imin]; + const Point &p = poly.points[i]; + if (p.x < pmin.x || (p.x == pmin.x && p.y < pmin.y)) + imin = i; + } + + // 2) Detect the orientation of the corner imin. + size_t iPrev = ((imin == 0) ? poly.points.size() : imin) - 1; + size_t iNext = ((imin + 1 == poly.points.size()) ? 0 : imin + 1); + Orientation o = orient(poly.points[iPrev], poly.points[imin], poly.points[iNext]); + // The lowest bottom point must not be collinear if the polygon does not contain duplicate points + // or overlapping segments. + myassert(o != ORIENTATION_COLINEAR); + return o == ORIENTATION_CCW; +} + +// Having a segment of a closed polygon, calculate its Euclidian length. +// The segment indices seg1 and seg2 signify an end point of an edge in the forward direction of the loop, +// therefore the point p1 lies on poly.points[seg1-1], poly.points[seg1] etc. +static inline coordf_t segment_length(const Polygon &poly, size_t seg1, const Point &p1, size_t seg2, const Point &p2) +{ +#ifdef SLIC3R_DEBUG + // Verify that p1 lies on seg1. This is difficult to verify precisely, + // but at least verify, that p1 lies in the bounding box of seg1. + for (size_t i = 0; i < 2; ++ i) { + size_t seg = (i == 0) ? seg1 : seg2; + Point px = (i == 0) ? p1 : p2; + Point pa = poly.points[((seg == 0) ? poly.points.size() : seg) - 1]; + Point pb = poly.points[seg]; + if (pa.x > pb.x) + std::swap(pa.x, pb.x); + if (pa.y > pb.y) + std::swap(pa.y, pb.y); + myassert(px.x >= pa.x && px.x <= pb.x); + myassert(px.y >= pa.y && px.y <= pb.y); + } +#endif /* SLIC3R_DEBUG */ + const Point *pPrev = &p1; + const Point *pThis = NULL; + coordf_t len = 0; + if (seg1 <= seg2) { + for (size_t i = seg1; i < seg2; ++ i, pPrev = pThis) + len += pPrev->distance_to(*(pThis = &poly.points[i])); + } else { + for (size_t i = seg1; i < poly.points.size(); ++ i, pPrev = pThis) + len += pPrev->distance_to(*(pThis = &poly.points[i])); + for (size_t i = 0; i < seg2; ++ i, pPrev = pThis) + len += pPrev->distance_to(*(pThis = &poly.points[i])); + } + len += pPrev->distance_to(p2); + return len; +} + +// Append a segment of a closed polygon to a polyline. +// The segment indices seg1 and seg2 signify an end point of an edge in the forward direction of the loop. +// Only insert intermediate points between seg1 and seg2. +static inline void polygon_segment_append(Points &out, const Polygon &polygon, size_t seg1, size_t seg2) +{ + if (seg1 == seg2) { + // Nothing to append from this segment. + } else if (seg1 < seg2) { + // Do not append a point pointed to by seg2. + out.insert(out.end(), polygon.points.begin() + seg1, polygon.points.begin() + seg2); + } else { + out.reserve(out.size() + seg2 + polygon.points.size() - seg1); + out.insert(out.end(), polygon.points.begin() + seg1, polygon.points.end()); + // Do not append a point pointed to by seg2. + out.insert(out.end(), polygon.points.begin(), polygon.points.begin() + seg2); + } +} + +// Append a segment of a closed polygon to a polyline. +// The segment indices seg1 and seg2 signify an end point of an edge in the forward direction of the loop, +// but this time the segment is traversed backward. +// Only insert intermediate points between seg1 and seg2. +static inline void polygon_segment_append_reversed(Points &out, const Polygon &polygon, size_t seg1, size_t seg2) +{ + if (seg1 >= seg2) { + out.reserve(seg1 - seg2); + for (size_t i = seg1; i > seg2; -- i) + out.push_back(polygon.points[i - 1]); + } else { + // it could be, that seg1 == seg2. In that case, append the complete loop. + out.reserve(out.size() + seg2 + polygon.points.size() - seg1); + for (size_t i = seg1; i > 0; -- i) + out.push_back(polygon.points[i - 1]); + for (size_t i = polygon.points.size(); i > seg2; -- i) + out.push_back(polygon.points[i - 1]); + } +} + +// Intersection point of a vertical line with a polygon segment. +class SegmentIntersection +{ +public: + SegmentIntersection() : + iContour(0), + iSegment(0), + pos_p(0), + pos_q(1), + type(UNKNOWN), + consumed_vertical_up(false), + consumed_perimeter_right(false) + {} + + // Index of a contour in ExPolygonWithOffset, with which this vertical line intersects. + size_t iContour; + // Index of a segment in iContour, with which this vertical line intersects. + size_t iSegment; + // y position of the intersection, ratinal number. + int64_t pos_p; + uint32_t pos_q; + + coord_t pos() const { + // Division rounds both positive and negative down to zero. + // Add half of q for an arithmetic rounding effect. + int64_t p = pos_p; + if (p < 0) + p -= int64_t(pos_q>>1); + else + p += int64_t(pos_q>>1); + return coord_t(p / int64_t(pos_q)); + } + + // Kind of intersection. With the original contour, or with the inner offestted contour? + // A vertical segment will be at least intersected by OUTER_LOW, OUTER_HIGH, + // but it could be intersected with OUTER_LOW, INNER_LOW, INNER_HIGH, OUTER_HIGH, + // and there may be more than one pair of INNER_LOW, INNER_HIGH between OUTER_LOW, OUTER_HIGH. + enum SegmentIntersectionType { + OUTER_LOW = 0, + OUTER_HIGH = 1, + INNER_LOW = 2, + INNER_HIGH = 3, + UNKNOWN = -1 + }; + SegmentIntersectionType type; + + // Was this segment along the y axis consumed? + // Up means up along the vertical segment. + bool consumed_vertical_up; + // Was a segment of the inner perimeter contour consumed? + // Right means right from the vertical segment. + bool consumed_perimeter_right; + + // For the INNER_LOW type, this point may be connected to another INNER_LOW point following a perimeter contour. + // For the INNER_HIGH type, this point may be connected to another INNER_HIGH point following a perimeter contour. + // If INNER_LOW is connected to INNER_HIGH or vice versa, + // one has to make sure the vertical infill line does not overlap with the connecting perimeter line. + bool is_inner() const { return type == INNER_LOW || type == INNER_HIGH; } + bool is_outer() const { return type == OUTER_LOW || type == OUTER_HIGH; } + bool is_low () const { return type == INNER_LOW || type == OUTER_LOW; } + bool is_high () const { return type == INNER_HIGH || type == OUTER_HIGH; } + + // Compare two y intersection points given by rational numbers. + // Note that the rational number is given as pos_p/pos_q, where pos_p is int64 and pos_q is uint32. + // This function calculates pos_p * other.pos_q < other.pos_p * pos_q as a 48bit number. + // We don't use 128bit intrinsic data types as these are usually not supported by 32bit compilers and + // we don't need the full 128bit precision anyway. + bool operator<(const SegmentIntersection &other) const + { + assert(pos_q > 0); + assert(other.pos_q > 0); + if (pos_p == 0 || other.pos_p == 0) { + // Because the denominators are positive and one of the nominators is zero, + // following simple statement holds. + return pos_p < other.pos_p; + } else { + // None of the nominators is zero. + char sign1 = (pos_p > 0) ? 1 : -1; + char sign2 = (other.pos_p > 0) ? 1 : -1; + char signs = sign1 * sign2; + assert(signs == 1 || signs == -1); + if (signs < 0) { + // The nominators have different signs. + return sign1 < 0; + } else { + // The nominators have the same sign. + // Absolute values + uint64_t p1, p2; + if (sign1 > 0) { + p1 = uint64_t(pos_p); + p2 = uint64_t(other.pos_p); + } else { + p1 = uint64_t(- pos_p); + p2 = uint64_t(- other.pos_p); + }; + // Multiply low and high 32bit words of p1 by other_pos.q + // 32bit x 32bit => 64bit + // l_hi and l_lo overlap by 32 bits. + uint64_t l_hi = (p1 >> 32) * uint64_t(other.pos_q); + uint64_t l_lo = (p1 & 0xffffffffll) * uint64_t(other.pos_q); + l_hi += (l_lo >> 32); + uint64_t r_hi = (p2 >> 32) * uint64_t(pos_q); + uint64_t r_lo = (p2 & 0xffffffffll) * uint64_t(pos_q); + r_hi += (r_lo >> 32); + // Compare the high 64 bits. + if (l_hi == r_hi) { + // Compare the low 32 bits. + l_lo &= 0xffffffffll; + r_lo &= 0xffffffffll; + return (sign1 < 0) ? (l_lo > r_lo) : (l_lo < r_lo); + } + return (sign1 < 0) ? (l_hi > r_hi) : (l_hi < r_hi); + } + } + } + + bool operator==(const SegmentIntersection &other) const + { + assert(pos_q > 0); + assert(other.pos_q > 0); + if (pos_p == 0 || other.pos_p == 0) { + // Because the denominators are positive and one of the nominators is zero, + // following simple statement holds. + return pos_p == other.pos_p; + } + + // None of the nominators is zero, none of the denominators is zero. + bool positive = pos_p > 0; + if (positive != (other.pos_p > 0)) + return false; + // The nominators have the same sign. + // Absolute values + uint64_t p1 = positive ? uint64_t(pos_p) : uint64_t(- pos_p); + uint64_t p2 = positive ? uint64_t(other.pos_p) : uint64_t(- other.pos_p); + // Multiply low and high 32bit words of p1 by other_pos.q + // 32bit x 32bit => 64bit + // l_hi and l_lo overlap by 32 bits. + uint64_t l_lo = (p1 & 0xffffffffll) * uint64_t(other.pos_q); + uint64_t r_lo = (p2 & 0xffffffffll) * uint64_t(pos_q); + if (l_lo != r_lo) + return false; + uint64_t l_hi = (p1 >> 32) * uint64_t(other.pos_q); + uint64_t r_hi = (p2 >> 32) * uint64_t(pos_q); + return l_hi + (l_lo >> 32) == r_hi + (r_lo >> 32); + } +}; + +// A vertical line with intersection points with polygons. +class SegmentedIntersectionLine +{ +public: + // Index of this vertical intersection line. + size_t idx; + // x position of this vertical intersection line. + coord_t pos; + // List of intersection points with polygons, sorted increasingly by the y axis. + std::vector intersections; +}; + +// A container maintaining an expolygon with its inner offsetted polygon. +// The purpose of the inner offsetted polygon is to provide segments to connect the infill lines. +struct ExPolygonWithOffset +{ +public: + ExPolygonWithOffset( + const ExPolygon &expolygon, + float angle, + coord_t aoffset1, + coord_t aoffset2) + { + // Copy and rotate the source polygons. + polygons_src = expolygon; + polygons_src.contour.rotate(angle); + for (Polygons::iterator it = polygons_src.holes.begin(); it != polygons_src.holes.end(); ++ it) + it->rotate(angle); + + double mitterLimit = 3.; + // for the infill pattern, don't cut the corners. + // default miterLimt = 3 + //double mitterLimit = 10.; + myassert(aoffset1 < 0); + myassert(aoffset2 < 0); + myassert(aoffset2 < aoffset1); + bool sticks_removed = remove_sticks(polygons_src); +// if (sticks_removed) printf("Sticks removed!\n"); + polygons_outer = offset(polygons_src, aoffset1, + CLIPPER_OFFSET_SCALE, + ClipperLib::jtMiter, + mitterLimit); + polygons_inner = offset(polygons_outer, aoffset2 - aoffset1, + CLIPPER_OFFSET_SCALE, + ClipperLib::jtMiter, + mitterLimit); + // Filter out contours with zero area or small area, contours with 2 points only. + const double min_area_threshold = 0.01 * aoffset2 * aoffset2; + remove_small(polygons_outer, min_area_threshold); + remove_small(polygons_inner, min_area_threshold); + remove_sticks(polygons_outer); + remove_sticks(polygons_inner); + n_contours_outer = polygons_outer.size(); + n_contours_inner = polygons_inner.size(); + n_contours = n_contours_outer + n_contours_inner; + polygons_ccw.assign(n_contours, false); + for (size_t i = 0; i < n_contours; ++ i) { + contour(i).remove_duplicate_points(); + myassert(! contour(i).has_duplicate_points()); + polygons_ccw[i] = is_ccw(contour(i)); + } + } + + // Any contour with offset1 + bool is_contour_outer(size_t idx) const { return idx < n_contours_outer; } + // Any contour with offset2 + bool is_contour_inner(size_t idx) const { return idx >= n_contours_outer; } + + const Polygon& contour(size_t idx) const + { return is_contour_outer(idx) ? polygons_outer[idx] : polygons_inner[idx - n_contours_outer]; } + + Polygon& contour(size_t idx) + { return is_contour_outer(idx) ? polygons_outer[idx] : polygons_inner[idx - n_contours_outer]; } + + bool is_contour_ccw(size_t idx) const { return polygons_ccw[idx]; } + +#ifdef SLIC3R_DEBUG + void export_to_svg(Slic3r::SVG &svg) { + svg.draw_outline(polygons_src, "black"); + svg.draw_outline(polygons_outer, "green"); + svg.draw_outline(polygons_inner, "brown"); + } +#endif /* SLIC3R_DEBUG */ + + ExPolygon polygons_src; + Polygons polygons_outer; + Polygons polygons_inner; + + size_t n_contours_outer; + size_t n_contours_inner; + size_t n_contours; + +protected: + // For each polygon of polygons_inner, remember its orientation. + std::vector polygons_ccw; +}; + +static inline int distance_of_segmens(const Polygon &poly, size_t seg1, size_t seg2, bool forward) +{ + int d = int(seg2) - int(seg1); + if (! forward) + d = - d; + if (d < 0) + d += int(poly.points.size()); + return d; +} + +// For a vertical line, an inner contour and an intersection point, +// find an intersection point on the previous resp. next vertical line. +// The intersection point is connected with the prev resp. next intersection point with iInnerContour. +// Return -1 if there is no such point on the previous resp. next vertical line. +static inline int intersection_on_prev_next_vertical_line( + const ExPolygonWithOffset &poly_with_offset, + const std::vector &segs, + size_t iVerticalLine, + size_t iInnerContour, + size_t iIntersection, + bool dir_is_next) +{ + size_t iVerticalLineOther = iVerticalLine; + if (dir_is_next) { + if (++ iVerticalLineOther == segs.size()) + // No successive vertical line. + return -1; + } else if (iVerticalLineOther -- == 0) { + // No preceding vertical line. + return -1; + } + + const SegmentedIntersectionLine &il = segs[iVerticalLine]; + const SegmentIntersection &itsct = il.intersections[iIntersection]; + const SegmentedIntersectionLine &il2 = segs[iVerticalLineOther]; + const Polygon &poly = poly_with_offset.contour(iInnerContour); +// const bool ccw = poly_with_offset.is_contour_ccw(iInnerContour); + const bool forward = itsct.is_low() == dir_is_next; + // Resulting index of an intersection point on il2. + int out = -1; + // Find an intersection point on iVerticalLineOther, intersecting iInnerContour + // at the same orientation as iIntersection, and being closest to iIntersection + // in the number of contour segments, when following the direction of the contour. + int dmin = std::numeric_limits::max(); + for (size_t i = 0; i < il2.intersections.size(); ++ i) { + const SegmentIntersection &itsct2 = il2.intersections[i]; + if (itsct.iContour == itsct2.iContour && itsct.type == itsct2.type) { + /* + if (itsct.is_low()) { + myassert(itsct.type == SegmentIntersection::INNER_LOW); + myassert(iIntersection > 0); + myassert(il.intersections[iIntersection-1].type == SegmentIntersection::OUTER_LOW); + myassert(i > 0); + if (il2.intersections[i-1].is_inner()) + // Take only the lowest inner intersection point. + continue; + myassert(il2.intersections[i-1].type == SegmentIntersection::OUTER_LOW); + } else { + myassert(itsct.type == SegmentIntersection::INNER_HIGH); + myassert(iIntersection+1 < il.intersections.size()); + myassert(il.intersections[iIntersection+1].type == SegmentIntersection::OUTER_HIGH); + myassert(i+1 < il2.intersections.size()); + if (il2.intersections[i+1].is_inner()) + // Take only the highest inner intersection point. + continue; + myassert(il2.intersections[i+1].type == SegmentIntersection::OUTER_HIGH); + } + */ + // The intersection points lie on the same contour and have the same orientation. + // Find the intersection point with a shortest path in the direction of the contour. + int d = distance_of_segmens(poly, itsct.iSegment, itsct2.iSegment, forward); + if (d < dmin) { + out = i; + dmin = d; + } + } + } + //FIXME this routine is not asymptotic optimal, it will be slow if there are many intersection points along the line. + return out; +} + +static inline int intersection_on_prev_vertical_line( + const ExPolygonWithOffset &poly_with_offset, + const std::vector &segs, + size_t iVerticalLine, + size_t iInnerContour, + size_t iIntersection) +{ + return intersection_on_prev_next_vertical_line(poly_with_offset, segs, iVerticalLine, iInnerContour, iIntersection, false); +} + +static inline int intersection_on_next_vertical_line( + const ExPolygonWithOffset &poly_with_offset, + const std::vector &segs, + size_t iVerticalLine, + size_t iInnerContour, + size_t iIntersection) +{ + return intersection_on_prev_next_vertical_line(poly_with_offset, segs, iVerticalLine, iInnerContour, iIntersection, true); +} + +enum IntersectionTypeOtherVLine { + // There is no connection point on the other vertical line. + INTERSECTION_TYPE_OTHER_VLINE_UNDEFINED = -1, + // Connection point on the other vertical segment was found + // and it could be followed. + INTERSECTION_TYPE_OTHER_VLINE_OK = 0, + // The connection segment connects to a middle of a vertical segment. + // Cannot follow. + INTERSECTION_TYPE_OTHER_VLINE_INNER, + // Cannot extend the contor to this intersection point as either the connection segment + // or the succeeding vertical segment were already consumed. + INTERSECTION_TYPE_OTHER_VLINE_CONSUMED, + // Not the first intersection along the contor. This intersection point + // has been preceded by an intersection point along the vertical line. + INTERSECTION_TYPE_OTHER_VLINE_NOT_FIRST, +}; + +// Find an intersection on a previous line, but return -1, if the connecting segment of a perimeter was already extruded. +static inline IntersectionTypeOtherVLine intersection_type_on_prev_next_vertical_line( + const std::vector &segs, + size_t iVerticalLine, + size_t iIntersection, + size_t iIntersectionOther, + bool dir_is_next) +{ + // This routine will propose a connecting line even if the connecting perimeter segment intersects + // iVertical line multiple times before reaching iIntersectionOther. + if (iIntersectionOther == -1) + return INTERSECTION_TYPE_OTHER_VLINE_UNDEFINED; + myassert(dir_is_next ? (iVerticalLine + 1 < segs.size()) : (iVerticalLine > 0)); + const SegmentedIntersectionLine &il_this = segs[iVerticalLine]; + const SegmentIntersection &itsct_this = il_this.intersections[iIntersection]; + const SegmentedIntersectionLine &il_other = segs[dir_is_next ? (iVerticalLine+1) : (iVerticalLine-1)]; + const SegmentIntersection &itsct_other = il_other.intersections[iIntersectionOther]; + myassert(itsct_other.is_inner()); + myassert(iIntersectionOther > 0); + myassert(iIntersectionOther + 1 < il_other.intersections.size()); + // Is iIntersectionOther at the boundary of a vertical segment? + const SegmentIntersection &itsct_other2 = il_other.intersections[itsct_other.is_low() ? iIntersectionOther - 1 : iIntersectionOther + 1]; + if (itsct_other2.is_inner()) + // Cannot follow a perimeter segment into the middle of another vertical segment. + // Only perimeter segments connecting to the end of a vertical segment are followed. + return INTERSECTION_TYPE_OTHER_VLINE_INNER; + myassert(itsct_other.is_low() == itsct_other2.is_low()); + if (dir_is_next ? itsct_this.consumed_perimeter_right : itsct_other.consumed_perimeter_right) + // This perimeter segment was already consumed. + return INTERSECTION_TYPE_OTHER_VLINE_CONSUMED; + if (itsct_other.is_low() ? itsct_other.consumed_vertical_up : il_other.intersections[iIntersectionOther-1].consumed_vertical_up) + // This vertical segment was already consumed. + return INTERSECTION_TYPE_OTHER_VLINE_CONSUMED; + return INTERSECTION_TYPE_OTHER_VLINE_OK; +} + +static inline IntersectionTypeOtherVLine intersection_type_on_prev_vertical_line( + const std::vector &segs, + size_t iVerticalLine, + size_t iIntersection, + size_t iIntersectionPrev) +{ + return intersection_type_on_prev_next_vertical_line(segs, iVerticalLine, iIntersection, iIntersectionPrev, false); +} + +static inline IntersectionTypeOtherVLine intersection_type_on_next_vertical_line( + const std::vector &segs, + size_t iVerticalLine, + size_t iIntersection, + size_t iIntersectionNext) +{ + return intersection_type_on_prev_next_vertical_line(segs, iVerticalLine, iIntersection, iIntersectionNext, true); +} + +// Measure an Euclidian length of a perimeter segment when going from iIntersection to iIntersection2. +static inline coordf_t measure_perimeter_prev_next_segment_length( + const ExPolygonWithOffset &poly_with_offset, + const std::vector &segs, + size_t iVerticalLine, + size_t iInnerContour, + size_t iIntersection, + size_t iIntersection2, + bool dir_is_next) +{ + size_t iVerticalLineOther = iVerticalLine; + if (dir_is_next) { + if (++ iVerticalLineOther == segs.size()) + // No successive vertical line. + return coordf_t(-1); + } else if (iVerticalLineOther -- == 0) { + // No preceding vertical line. + return coordf_t(-1); + } + + const SegmentedIntersectionLine &il = segs[iVerticalLine]; + const SegmentIntersection &itsct = il.intersections[iIntersection]; + const SegmentedIntersectionLine &il2 = segs[iVerticalLineOther]; + const SegmentIntersection &itsct2 = il2.intersections[iIntersection2]; + const Polygon &poly = poly_with_offset.contour(iInnerContour); +// const bool ccw = poly_with_offset.is_contour_ccw(iInnerContour); + myassert(itsct.type == itsct2.type); + myassert(itsct.iContour == itsct2.iContour); + myassert(itsct.is_inner()); + const bool forward = itsct.is_low() == dir_is_next; + + Point p1(il.pos, itsct.pos()); + Point p2(il2.pos, itsct2.pos()); + return forward ? + segment_length(poly, itsct .iSegment, p1, itsct2.iSegment, p2) : + segment_length(poly, itsct2.iSegment, p2, itsct .iSegment, p1); +} + +static inline coordf_t measure_perimeter_prev_segment_length( + const ExPolygonWithOffset &poly_with_offset, + const std::vector &segs, + size_t iVerticalLine, + size_t iInnerContour, + size_t iIntersection, + size_t iIntersection2) +{ + return measure_perimeter_prev_next_segment_length(poly_with_offset, segs, iVerticalLine, iInnerContour, iIntersection, iIntersection2, false); +} + +static inline coordf_t measure_perimeter_next_segment_length( + const ExPolygonWithOffset &poly_with_offset, + const std::vector &segs, + size_t iVerticalLine, + size_t iInnerContour, + size_t iIntersection, + size_t iIntersection2) +{ + return measure_perimeter_prev_next_segment_length(poly_with_offset, segs, iVerticalLine, iInnerContour, iIntersection, iIntersection2, true); +} + +// Append the points of a perimeter segment when going from iIntersection to iIntersection2. +// The first point (the point of iIntersection) will not be inserted, +// the last point will be inserted. +static inline void emit_perimeter_prev_next_segment( + const ExPolygonWithOffset &poly_with_offset, + const std::vector &segs, + size_t iVerticalLine, + size_t iInnerContour, + size_t iIntersection, + size_t iIntersection2, + Polyline &out, + bool dir_is_next) +{ + size_t iVerticalLineOther = iVerticalLine; + if (dir_is_next) { + ++ iVerticalLineOther; + myassert(iVerticalLineOther < segs.size()); + } else { + myassert(iVerticalLineOther > 0); + -- iVerticalLineOther; + } + + const SegmentedIntersectionLine &il = segs[iVerticalLine]; + const SegmentIntersection &itsct = il.intersections[iIntersection]; + const SegmentedIntersectionLine &il2 = segs[iVerticalLineOther]; + const SegmentIntersection &itsct2 = il2.intersections[iIntersection2]; + const Polygon &poly = poly_with_offset.contour(iInnerContour); +// const bool ccw = poly_with_offset.is_contour_ccw(iInnerContour); + myassert(itsct.type == itsct2.type); + myassert(itsct.iContour == itsct2.iContour); + myassert(itsct.is_inner()); + const bool forward = itsct.is_low() == dir_is_next; + // Do not append the first point. + // out.points.push_back(Point(il.pos, itsct.pos)); + if (forward) + polygon_segment_append(out.points, poly, itsct.iSegment, itsct2.iSegment); + else + polygon_segment_append_reversed(out.points, poly, itsct.iSegment, itsct2.iSegment); + // Append the last point. + out.points.push_back(Point(il2.pos, itsct2.pos())); +} + +static inline coordf_t measure_perimeter_segment_on_vertical_line_length( + const ExPolygonWithOffset &poly_with_offset, + const std::vector &segs, + size_t iVerticalLine, + size_t iInnerContour, + size_t iIntersection, + size_t iIntersection2, + bool forward) +{ + const SegmentedIntersectionLine &il = segs[iVerticalLine]; + const SegmentIntersection &itsct = il.intersections[iIntersection]; + const SegmentIntersection &itsct2 = il.intersections[iIntersection2]; + const Polygon &poly = poly_with_offset.contour(iInnerContour); + myassert(itsct.is_inner()); + myassert(itsct2.is_inner()); + myassert(itsct.type != itsct2.type); + myassert(itsct.iContour == iInnerContour); + myassert(itsct.iContour == itsct2.iContour); + Point p1(il.pos, itsct.pos()); + Point p2(il.pos, itsct2.pos()); + return forward ? + segment_length(poly, itsct .iSegment, p1, itsct2.iSegment, p2) : + segment_length(poly, itsct2.iSegment, p2, itsct .iSegment, p1); +} + +// Append the points of a perimeter segment when going from iIntersection to iIntersection2. +// The first point (the point of iIntersection) will not be inserted, +// the last point will be inserted. +static inline void emit_perimeter_segment_on_vertical_line( + const ExPolygonWithOffset &poly_with_offset, + const std::vector &segs, + size_t iVerticalLine, + size_t iInnerContour, + size_t iIntersection, + size_t iIntersection2, + Polyline &out, + bool forward) +{ + const SegmentedIntersectionLine &il = segs[iVerticalLine]; + const SegmentIntersection &itsct = il.intersections[iIntersection]; + const SegmentIntersection &itsct2 = il.intersections[iIntersection2]; + const Polygon &poly = poly_with_offset.contour(iInnerContour); + myassert(itsct.is_inner()); + myassert(itsct2.is_inner()); + myassert(itsct.type != itsct2.type); + myassert(itsct.iContour == iInnerContour); + myassert(itsct.iContour == itsct2.iContour); + // Do not append the first point. + // out.points.push_back(Point(il.pos, itsct.pos)); + if (forward) + polygon_segment_append(out.points, poly, itsct.iSegment, itsct2.iSegment); + else + polygon_segment_append_reversed(out.points, poly, itsct.iSegment, itsct2.iSegment); + // Append the last point. + out.points.push_back(Point(il.pos, itsct2.pos())); +} + +//TBD: For precise infill, measure the area of a slab spanned by an infill line. +/* +static inline float measure_outer_contour_slab( + const ExPolygonWithOffset &poly_with_offset, + const std::vector &segs, + size_t i_vline, + size_t iIntersection) +{ + const SegmentedIntersectionLine &il = segs[i_vline]; + const SegmentIntersection &itsct = il.intersections[i_vline]; + const SegmentIntersection &itsct2 = il.intersections[iIntersection2]; + const Polygon &poly = poly_with_offset.contour((itsct.iContour); + myassert(itsct.is_outer()); + myassert(itsct2.is_outer()); + myassert(itsct.type != itsct2.type); + myassert(itsct.iContour == itsct2.iContour); + if (! itsct.is_outer() || ! itsct2.is_outer() || itsct.type == itsct2.type || itsct.iContour != itsct2.iContour) + // Error, return zero area. + return 0.f; + + // Find possible connection points on the previous / next vertical line. + int iPrev = intersection_on_prev_vertical_line(poly_with_offset, segs, i_vline, itsct.iContour, i_intersection); + int iNext = intersection_on_next_vertical_line(poly_with_offset, segs, i_vline, itsct.iContour, i_intersection); + // Find possible connection points on the same vertical line. + int iAbove = iBelow = -1; + // Does the perimeter intersect the current vertical line above intrsctn? + for (size_t i = i_intersection + 1; i + 1 < seg.intersections.size(); ++ i) + if (seg.intersections[i].iContour == itsct.iContour) + { iAbove = i; break; } + // Does the perimeter intersect the current vertical line below intrsctn? + for (int i = int(i_intersection) - 1; i > 0; -- i) + if (seg.intersections[i].iContour == itsct.iContour) + { iBelow = i; break; } + + if (iSegAbove != -1 && seg.intersections[iAbove].type == SegmentIntersection::OUTER_HIGH) { + // Invalidate iPrev resp. iNext, if the perimeter crosses the current vertical line earlier than iPrev resp. iNext. + // The perimeter contour orientation. + const Polygon &poly = poly_with_offset.contour(itsct.iContour); + { + int d_horiz = (iPrev == -1) ? std::numeric_limits::max() : + distance_of_segmens(poly, segs[i_vline-1].intersections[iPrev].iSegment, itsct.iSegment, true); + int d_down = (iBelow == -1) ? std::numeric_limits::max() : + distance_of_segmens(poly, iSegBelow, itsct.iSegment, true); + int d_up = (iAbove == -1) ? std::numeric_limits::max() : + distance_of_segmens(poly, iSegAbove, itsct.iSegment, true); + if (intrsctn_type_prev == INTERSECTION_TYPE_OTHER_VLINE_OK && d_horiz > std::min(d_down, d_up)) + // The vertical crossing comes eralier than the prev crossing. + // Disable the perimeter going back. + intrsctn_type_prev = INTERSECTION_TYPE_OTHER_VLINE_NOT_FIRST; + if (d_up > std::min(d_horiz, d_down)) + // The horizontal crossing comes earlier than the vertical crossing. + vert_seg_dir_valid_mask &= ~DIR_BACKWARD; + } + { + int d_horiz = (iNext == -1) ? std::numeric_limits::max() : + distance_of_segmens(poly, itsct.iSegment, segs[i_vline+1].intersections[iNext].iSegment, true); + int d_down = (iSegBelow == -1) ? std::numeric_limits::max() : + distance_of_segmens(poly, itsct.iSegment, iSegBelow, true); + int d_up = (iSegAbove == -1) ? std::numeric_limits::max() : + distance_of_segmens(poly, itsct.iSegment, iSegAbove, true); + if (d_up > std::min(d_horiz, d_down)) + // The horizontal crossing comes earlier than the vertical crossing. + vert_seg_dir_valid_mask &= ~DIR_FORWARD; + } + } +} +*/ + +enum DirectionMask +{ + DIR_FORWARD = 1, + DIR_BACKWARD = 2 +}; + +bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillParams ¶ms, float angleBase, float pattern_shift, Polylines &polylines_out) +{ + // At the end, only the new polylines will be rotated back. + size_t n_polylines_out_initial = polylines_out.size(); + + // Shrink the input polygon a bit first to not push the infill lines out of the perimeters. +// const float INFILL_OVERLAP_OVER_SPACING = 0.3f; + const float INFILL_OVERLAP_OVER_SPACING = 0.45f; + myassert(INFILL_OVERLAP_OVER_SPACING > 0 && INFILL_OVERLAP_OVER_SPACING < 0.5f); + + // Rotate polygons so that we can work with vertical lines here + std::pair rotate_vector = this->_infill_direction(*surface); + rotate_vector.first += angleBase; + + myassert(params.density > 0.0001f && params.density <= 1.f); + coord_t line_spacing = coord_t(scale_(this->spacing) / params.density); + + // On the polygons of poly_with_offset, the infill lines will be connected. + ExPolygonWithOffset poly_with_offset( + surface->expolygon, + - rotate_vector.first, + scale_(- (0.5 - INFILL_OVERLAP_OVER_SPACING) * this->spacing), + scale_(- 0.5 * this->spacing)); + if (poly_with_offset.n_contours_inner == 0) { + // Not a single infill line fits. + //FIXME maybe one shall trigger the gap fill here? + return true; + } + + BoundingBox bounding_box(poly_with_offset.polygons_src); + + // define flow spacing according to requested density + bool full_infill = params.density > 0.9999f; + if (full_infill && !params.dont_adjust) { + line_spacing = this->adjust_solid_spacing(bounding_box.size().x, line_spacing); + this->spacing = unscale(line_spacing); + } else { + // extend bounding box so that our pattern will be aligned with other layers + // Transform the reference point to the rotated coordinate system. + Point refpt = rotate_vector.second.rotated(- rotate_vector.first); + // _align_to_grid will not work correctly with positive pattern_shift. + coord_t pattern_shift_scaled = coord_t(scale_(pattern_shift)) % line_spacing; + refpt.x -= (pattern_shift_scaled > 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled); + bounding_box.min.align_to_grid( + Point(line_spacing, line_spacing), + refpt + ); + } + + // Intersect a set of euqally spaced vertical lines wiht expolygon. + // n_vlines = ceil(bbox_width / line_spacing) + size_t n_vlines = (bounding_box.max.x - bounding_box.min.x + line_spacing - 1) / line_spacing; + coord_t x0 = bounding_box.min.x + (line_spacing + SCALED_EPSILON) / 2; + +#ifdef SLIC3R_DEBUG + static int iRun = 0; + BoundingBox bbox_svg(to_points(poly_with_offset.polygons_outer)); + ::Slic3r::SVG svg(debug_out_path("FillRectilinear2-%d.svg", iRun), bbox_svg); // , scale_(1.)); + poly_with_offset.export_to_svg(svg); + { + ::Slic3r::SVG svg(debug_out_path("FillRectilinear2-initial-%d.svg", iRun), bbox_svg); // , scale_(1.)); + poly_with_offset.export_to_svg(svg); + } + iRun ++; +#endif /* SLIC3R_DEBUG */ + + // For each contour + // Allocate storage for the segments. + std::vector segs(n_vlines, SegmentedIntersectionLine()); + for (size_t i = 0; i < n_vlines; ++ i) { + segs[i].idx = i; + segs[i].pos = x0 + i * line_spacing; + } + for (size_t iContour = 0; iContour < poly_with_offset.n_contours; ++ iContour) { + const Points &contour = poly_with_offset.contour(iContour).points; + if (contour.size() < 2) + continue; + // For each segment + for (size_t iSegment = 0; iSegment < contour.size(); ++ iSegment) { + size_t iPrev = ((iSegment == 0) ? contour.size() : iSegment) - 1; + const Point &p1 = contour[iPrev]; + const Point &p2 = contour[iSegment]; + // Which of the equally spaced vertical lines is intersected by this segment? + coord_t l = p1.x; + coord_t r = p2.x; + if (l > r) + std::swap(l, r); + // il, ir are the left / right indices of vertical lines intersecting a segment + int il = (l - x0) / line_spacing; + while (il * line_spacing + x0 < l) + ++ il; + il = std::max(int(0), il); + int ir = (r - x0 + line_spacing) / line_spacing; + while (ir * line_spacing + x0 > r) + -- ir; + ir = std::min(int(segs.size()) - 1, ir); + if (il > ir) + // No vertical line intersects this segment. + continue; + myassert(il >= 0 && il < segs.size()); + myassert(ir >= 0 && ir < segs.size()); + for (int i = il; i <= ir; ++ i) { + coord_t this_x = segs[i].pos; + assert(this_x == i * line_spacing + x0); + SegmentIntersection is; + is.iContour = iContour; + is.iSegment = iSegment; + myassert(l <= this_x); + myassert(r >= this_x); + // Calculate the intersection position in y axis. x is known. + if (p1.x == this_x) { + if (p2.x == this_x) { + // Ignore strictly vertical segments. + continue; + } + is.pos_p = p1.y; + is.pos_q = 1; + } else if (p2.x == this_x) { + is.pos_p = p2.y; + is.pos_q = 1; + } else { + // First calculate the intersection parameter 't' as a rational number with non negative denominator. + if (p2.x > p1.x) { + is.pos_p = this_x - p1.x; + is.pos_q = p2.x - p1.x; + } else { + is.pos_p = p1.x - this_x; + is.pos_q = p1.x - p2.x; + } + myassert(is.pos_p >= 0 && is.pos_p <= is.pos_q); + // Make an intersection point from the 't'. + is.pos_p *= int64_t(p2.y - p1.y); + is.pos_p += p1.y * int64_t(is.pos_q); + } + // +-1 to take rounding into account. + myassert(is.pos() + 1 >= std::min(p1.y, p2.y)); + myassert(is.pos() <= std::max(p1.y, p2.y) + 1); + segs[i].intersections.push_back(is); + } + } + } + + // Sort the intersections along their segments, specify the intersection types. + for (size_t i_seg = 0; i_seg < segs.size(); ++ i_seg) { + SegmentedIntersectionLine &sil = segs[i_seg]; + // Sort the intersection points using exact rational arithmetic. + std::sort(sil.intersections.begin(), sil.intersections.end()); + +#if 0 + // Verify the order, bubble sort the intersections until sorted. + bool modified = false; + do { + modified = false; + for (size_t i = 1; i < sil.intersections.size(); ++ i) { + size_t iContour1 = sil.intersections[i-1].iContour; + size_t iContour2 = sil.intersections[i].iContour; + const Points &contour1 = poly_with_offset.contour(iContour1).points; + const Points &contour2 = poly_with_offset.contour(iContour2).points; + size_t iSegment1 = sil.intersections[i-1].iSegment; + size_t iPrev1 = ((iSegment1 == 0) ? contour1.size() : iSegment1) - 1; + size_t iSegment2 = sil.intersections[i].iSegment; + size_t iPrev2 = ((iSegment2 == 0) ? contour2.size() : iSegment2) - 1; + bool swap = false; + if (iContour1 == iContour2 && iSegment1 == iSegment2) { + // The same segment, it has to be vertical. + myassert(iPrev1 == iPrev2); + swap = contour1[iPrev1].y > contour1[iContour1].y; + #ifdef SLIC3R_DEBUG + if (swap) + printf("Swapping when single vertical segment\n"); + #endif + } else { + // Segments are in a general position. Here an exact airthmetics may come into play. + coord_t y1max = std::max(contour1[iPrev1].y, contour1[iSegment1].y); + coord_t y2min = std::min(contour2[iPrev2].y, contour2[iSegment2].y); + if (y1max < y2min) { + // The segments are separated, nothing to do. + } else { + // Use an exact predicate to verify, that segment1 is below segment2. + const Point *a = &contour1[iPrev1]; + const Point *b = &contour1[iSegment1]; + const Point *c = &contour2[iPrev2]; + const Point *d = &contour2[iSegment2]; +#ifdef SLIC3R_DEBUG + const Point x1(sil.pos, sil.intersections[i-1].pos); + const Point x2(sil.pos, sil.intersections[i ].pos); + bool successive = false; +#endif /* SLIC3R_DEBUG */ + // Sort the points in the two segments by x. + if (a->x > b->x) + std::swap(a, b); + if (c->x > d->x) + std::swap(c, d); + myassert(a->x <= sil.pos); + myassert(c->x <= sil.pos); + myassert(b->x >= sil.pos); + myassert(d->x >= sil.pos); + // Sort the two segments, so the segment will be on the left of . + bool upper_more_left = false; + if (a->x > c->x) { + upper_more_left = true; + std::swap(a, c); + std::swap(b, d); + } + if (a == c) { + // The segments iSegment1 and iSegment2 are directly connected. + myassert(iContour1 == iContour2); + myassert(iSegment1 == iPrev2 || iPrev1 == iSegment2); + std::swap(c, d); + myassert(a != c && b != c); +#ifdef SLIC3R_DEBUG + successive = true; +#endif /* SLIC3R_DEBUG */ + } +#ifdef SLIC3R_DEBUG + else if (b == d) { + // The segments iSegment1 and iSegment2 are directly connected. + myassert(iContour1 == iContour2); + myassert(iSegment1 == iPrev2 || iPrev1 == iSegment2); + myassert(a != c && b != c); + successive = true; + } +#endif /* SLIC3R_DEBUG */ + Orientation o = orient(*a, *b, *c); + myassert(o != ORIENTATION_COLINEAR); + swap = upper_more_left != (o == ORIENTATION_CW); +#ifdef SLIC3R_DEBUG + if (swap) + printf(successive ? + "Swapping when iContour1 == iContour2 and successive segments\n" : + "Swapping when exact predicate\n"); +#endif + } + } + if (swap) { + // Swap the intersection points, but keep the original positions, so they stay sorted by the y axis. + std::swap(sil.intersections[i-1], sil.intersections[i]); + std::swap(sil.intersections[i-1].pos_p, sil.intersections[i].pos_p); + std::swap(sil.intersections[i-1].pos_q, sil.intersections[i].pos_q); + modified = true; + } + } + } while (modified); +#endif + + // Assign the intersection types, remove duplicate or overlapping intersection points. + // When a loop vertex touches a vertical line, intersection point is generated for both segments. + // If such two segments are oriented equally, then one of them is removed. + // Otherwise the vertex is tangential to the vertical line and both segments are removed. + // The same rule applies, if the loop is pinched into a single point and this point touches the vertical line: + // The loop has a zero vertical size at the vertical line, therefore the intersection point is removed. + size_t j = 0; + for (size_t i = 0; i < sil.intersections.size(); ++ i) { + // What is the orientation of the segment at the intersection point? + size_t iContour = sil.intersections[i].iContour; + const Points &contour = poly_with_offset.contour(iContour).points; + size_t iSegment = sil.intersections[i].iSegment; + size_t iPrev = ((iSegment == 0) ? contour.size() : iSegment) - 1; + coord_t dir = contour[iSegment].x - contour[iPrev].x; + // bool ccw = poly_with_offset.is_contour_ccw(iContour); + // bool low = (dir > 0) == ccw; + bool low = dir > 0; + sil.intersections[i].type = poly_with_offset.is_contour_outer(iContour) ? + (low ? SegmentIntersection::OUTER_LOW : SegmentIntersection::OUTER_HIGH) : + (low ? SegmentIntersection::INNER_LOW : SegmentIntersection::INNER_HIGH); + if (j > 0 && + sil.intersections[i].pos() == sil.intersections[j-1].pos() && + sil.intersections[i].iContour == sil.intersections[j-1].iContour) { + if (sil.intersections[i].type == sil.intersections[j-1].type) { + // This has to be a corner point crossing the vertical line. + // Remove the second intersection point. + #ifdef SLIC3R_DEBUG + size_t iSegment2 = sil.intersections[j-1].iSegment; + size_t iPrev2 = ((iSegment2 == 0) ? contour.size() : iSegment2) - 1; + myassert(iSegment == iPrev2 || iSegment2 == iPrev); + #endif /* SLIC3R_DEBUG */ + } else { + // This is a loop returning to the same point. + // It may as well be a vertex of a loop touching this vertical line. + // Remove both the lines. + -- j; + } + } else { + if (j < i) + sil.intersections[j] = sil.intersections[i]; + ++ j; + } + //FIXME solve a degenerate case, where there is a vertical segment on this vertical line and the contour + // follows from left to right or vice versa, leading to low,low or high,high intersections. + } + // Shrink the list of intersections, if any of the intersection was removed during the classification. + if (j < sil.intersections.size()) + sil.intersections.erase(sil.intersections.begin() + j, sil.intersections.end()); + } + + // Verify the segments. If something is wrong, give up. +#define ASSERT_OR_RETURN(CONDITION) do { assert(CONDITION); if (! (CONDITION)) return false; } while (0) + for (size_t i_seg = 0; i_seg < segs.size(); ++ i_seg) { + SegmentedIntersectionLine &sil = segs[i_seg]; + // The intersection points have to be even. + ASSERT_OR_RETURN((sil.intersections.size() & 1) == 0); + for (size_t i = 0; i < sil.intersections.size();) { + // An intersection segment crossing the bigger contour may cross the inner offsetted contour even number of times. + ASSERT_OR_RETURN(sil.intersections[i].type == SegmentIntersection::OUTER_LOW); + size_t j = i + 1; + ASSERT_OR_RETURN(j < sil.intersections.size()); + ASSERT_OR_RETURN(sil.intersections[j].type == SegmentIntersection::INNER_LOW || sil.intersections[j].type == SegmentIntersection::OUTER_HIGH); + for (; j < sil.intersections.size() && sil.intersections[j].is_inner(); ++ j) ; + ASSERT_OR_RETURN(j < sil.intersections.size()); + ASSERT_OR_RETURN((j & 1) == 1); + ASSERT_OR_RETURN(sil.intersections[j].type == SegmentIntersection::OUTER_HIGH); + ASSERT_OR_RETURN(i + 1 == j || sil.intersections[j - 1].type == SegmentIntersection::INNER_HIGH); + i = j + 1; + } + } +#undef ASSERT_OR_RETURN + +#ifdef SLIC3R_DEBUG + // Paint the segments and finalize the SVG file. + for (size_t i_seg = 0; i_seg < segs.size(); ++ i_seg) { + SegmentedIntersectionLine &sil = segs[i_seg]; + for (size_t i = 0; i < sil.intersections.size();) { + size_t j = i + 1; + for (; j < sil.intersections.size() && sil.intersections[j].is_inner(); ++ j) ; + if (i + 1 == j) { + svg.draw(Line(Point(sil.pos, sil.intersections[i].pos()), Point(sil.pos, sil.intersections[j].pos())), "blue"); + } else { + svg.draw(Line(Point(sil.pos, sil.intersections[i].pos()), Point(sil.pos, sil.intersections[i+1].pos())), "green"); + svg.draw(Line(Point(sil.pos, sil.intersections[i+1].pos()), Point(sil.pos, sil.intersections[j-1].pos())), (j - i + 1 > 4) ? "yellow" : "magenta"); + svg.draw(Line(Point(sil.pos, sil.intersections[j-1].pos()), Point(sil.pos, sil.intersections[j].pos())), "green"); + } + i = j + 1; + } + } + svg.Close(); +#endif /* SLIC3R_DEBUG */ + + // For each outer only chords, measure their maximum distance to the bow of the outer contour. + // Mark an outer only chord as consumed, if the distance is low. + for (size_t i_vline = 0; i_vline < segs.size(); ++ i_vline) { + SegmentedIntersectionLine &seg = segs[i_vline]; + for (size_t i_intersection = 0; i_intersection + 1 < seg.intersections.size(); ++ i_intersection) { + if (seg.intersections[i_intersection].type == SegmentIntersection::OUTER_LOW && + seg.intersections[i_intersection+1].type == SegmentIntersection::OUTER_HIGH) { + bool consumed = false; +// if (full_infill) { +// measure_outer_contour_slab(poly_with_offset, segs, i_vline, i_ntersection); +// } else + consumed = true; + seg.intersections[i_intersection].consumed_vertical_up = consumed; + } + } + } + + // Now construct a graph. + // Find the first point. + // Naively one would expect to achieve best results by chaining the paths by the shortest distance, + // but that procedure does not create the longest continuous paths. + // A simple "sweep left to right" procedure achieves better results. + size_t i_vline = 0; + size_t i_intersection = size_t(-1); + // Follow the line, connect the lines into a graph. + // Until no new line could be added to the output path: + Point pointLast; + Polyline *polyline_current = NULL; + if (! polylines_out.empty()) + pointLast = polylines_out.back().points.back(); + for (;;) { + if (i_intersection == size_t(-1)) { + // The path has been interrupted. Find a next starting point, closest to the previous extruder position. + coordf_t dist2min = std::numeric_limits().max(); + for (size_t i_vline2 = 0; i_vline2 < segs.size(); ++ i_vline2) { + const SegmentedIntersectionLine &seg = segs[i_vline2]; + if (! seg.intersections.empty()) { + myassert(seg.intersections.size() > 1); + // Even number of intersections with the loops. + myassert((seg.intersections.size() & 1) == 0); + myassert(seg.intersections.front().type == SegmentIntersection::OUTER_LOW); + for (size_t i = 0; i < seg.intersections.size(); ++ i) { + const SegmentIntersection &intrsctn = seg.intersections[i]; + if (intrsctn.is_outer()) { + myassert(intrsctn.is_low() || i > 0); + bool consumed = intrsctn.is_low() ? + intrsctn.consumed_vertical_up : + seg.intersections[i-1].consumed_vertical_up; + if (! consumed) { + coordf_t dist2 = sqr(coordf_t(pointLast.x - seg.pos)) + sqr(coordf_t(pointLast.y - intrsctn.pos())); + if (dist2 < dist2min) { + dist2min = dist2; + i_vline = i_vline2; + i_intersection = i; + //FIXME We are taking the first left point always. Verify, that the caller chains the paths + // by a shortest distance, while reversing the paths if needed. + //if (polylines_out.empty()) + // Initial state, take the first line, which is the first from the left. + goto found; + } + } + } + } + } + } + if (i_intersection == size_t(-1)) + // We are finished. + break; + found: + // Start a new path. + polylines_out.push_back(Polyline()); + polyline_current = &polylines_out.back(); + // Emit the first point of a path. + pointLast = Point(segs[i_vline].pos, segs[i_vline].intersections[i_intersection].pos()); + polyline_current->points.push_back(pointLast); + } + + // From the initial point (i_vline, i_intersection), follow a path. + SegmentedIntersectionLine &seg = segs[i_vline]; + SegmentIntersection *intrsctn = &seg.intersections[i_intersection]; + bool going_up = intrsctn->is_low(); + bool try_connect = false; + if (going_up) { + myassert(! intrsctn->consumed_vertical_up); + myassert(i_intersection + 1 < seg.intersections.size()); + // Step back to the beginning of the vertical segment to mark it as consumed. + if (intrsctn->is_inner()) { + myassert(i_intersection > 0); + -- intrsctn; + -- i_intersection; + } + // Consume the complete vertical segment up to the outer contour. + do { + intrsctn->consumed_vertical_up = true; + ++ intrsctn; + ++ i_intersection; + myassert(i_intersection < seg.intersections.size()); + } while (intrsctn->type != SegmentIntersection::OUTER_HIGH); + if ((intrsctn - 1)->is_inner()) { + // Step back. + -- intrsctn; + -- i_intersection; + myassert(intrsctn->type == SegmentIntersection::INNER_HIGH); + try_connect = true; + } + } else { + // Going down. + myassert(intrsctn->is_high()); + myassert(i_intersection > 0); + myassert(! (intrsctn - 1)->consumed_vertical_up); + // Consume the complete vertical segment up to the outer contour. + if (intrsctn->is_inner()) + intrsctn->consumed_vertical_up = true; + do { + myassert(i_intersection > 0); + -- intrsctn; + -- i_intersection; + intrsctn->consumed_vertical_up = true; + } while (intrsctn->type != SegmentIntersection::OUTER_LOW); + if ((intrsctn + 1)->is_inner()) { + // Step back. + ++ intrsctn; + ++ i_intersection; + myassert(intrsctn->type == SegmentIntersection::INNER_LOW); + try_connect = true; + } + } + if (try_connect) { + // Decide, whether to finish the segment, or whether to follow the perimeter. + + // 1) Find possible connection points on the previous / next vertical line. + int iPrev = intersection_on_prev_vertical_line(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection); + int iNext = intersection_on_next_vertical_line(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection); + IntersectionTypeOtherVLine intrsctn_type_prev = intersection_type_on_prev_vertical_line(segs, i_vline, i_intersection, iPrev); + IntersectionTypeOtherVLine intrsctn_type_next = intersection_type_on_next_vertical_line(segs, i_vline, i_intersection, iNext); + + // 2) Find possible connection points on the same vertical line. + int iAbove = -1; + int iBelow = -1; + int iSegAbove = -1; + int iSegBelow = -1; + { + SegmentIntersection::SegmentIntersectionType type_crossing = (intrsctn->type == SegmentIntersection::INNER_LOW) ? + SegmentIntersection::INNER_HIGH : SegmentIntersection::INNER_LOW; + // Does the perimeter intersect the current vertical line above intrsctn? + for (size_t i = i_intersection + 1; i + 1 < seg.intersections.size(); ++ i) +// if (seg.intersections[i].iContour == intrsctn->iContour && seg.intersections[i].type == type_crossing) { + if (seg.intersections[i].iContour == intrsctn->iContour) { + iAbove = i; + iSegAbove = seg.intersections[i].iSegment; + break; + } + // Does the perimeter intersect the current vertical line below intrsctn? + for (size_t i = i_intersection - 1; i > 0; -- i) +// if (seg.intersections[i].iContour == intrsctn->iContour && seg.intersections[i].type == type_crossing) { + if (seg.intersections[i].iContour == intrsctn->iContour) { + iBelow = i; + iSegBelow = seg.intersections[i].iSegment; + break; + } + } + + // 3) Sort the intersection points, clear iPrev / iNext / iSegBelow / iSegAbove, + // if it is preceded by any other intersection point along the contour. + unsigned int vert_seg_dir_valid_mask = + (going_up ? + (iSegAbove != -1 && seg.intersections[iAbove].type == SegmentIntersection::INNER_LOW) : + (iSegBelow != -1 && seg.intersections[iBelow].type == SegmentIntersection::INNER_HIGH)) ? + (DIR_FORWARD | DIR_BACKWARD) : + 0; + { + // Invalidate iPrev resp. iNext, if the perimeter crosses the current vertical line earlier than iPrev resp. iNext. + // The perimeter contour orientation. + const bool forward = intrsctn->is_low(); // == poly_with_offset.is_contour_ccw(intrsctn->iContour); + const Polygon &poly = poly_with_offset.contour(intrsctn->iContour); + { + int d_horiz = (iPrev == -1) ? std::numeric_limits::max() : + distance_of_segmens(poly, segs[i_vline-1].intersections[iPrev].iSegment, intrsctn->iSegment, forward); + int d_down = (iSegBelow == -1) ? std::numeric_limits::max() : + distance_of_segmens(poly, iSegBelow, intrsctn->iSegment, forward); + int d_up = (iSegAbove == -1) ? std::numeric_limits::max() : + distance_of_segmens(poly, iSegAbove, intrsctn->iSegment, forward); + if (intrsctn_type_prev == INTERSECTION_TYPE_OTHER_VLINE_OK && d_horiz > std::min(d_down, d_up)) + // The vertical crossing comes eralier than the prev crossing. + // Disable the perimeter going back. + intrsctn_type_prev = INTERSECTION_TYPE_OTHER_VLINE_NOT_FIRST; + if (going_up ? (d_up > std::min(d_horiz, d_down)) : (d_down > std::min(d_horiz, d_up))) + // The horizontal crossing comes earlier than the vertical crossing. + vert_seg_dir_valid_mask &= ~(forward ? DIR_BACKWARD : DIR_FORWARD); + } + { + int d_horiz = (iNext == -1) ? std::numeric_limits::max() : + distance_of_segmens(poly, intrsctn->iSegment, segs[i_vline+1].intersections[iNext].iSegment, forward); + int d_down = (iSegBelow == -1) ? std::numeric_limits::max() : + distance_of_segmens(poly, intrsctn->iSegment, iSegBelow, forward); + int d_up = (iSegAbove == -1) ? std::numeric_limits::max() : + distance_of_segmens(poly, intrsctn->iSegment, iSegAbove, forward); + if (intrsctn_type_next == INTERSECTION_TYPE_OTHER_VLINE_OK && d_horiz > std::min(d_down, d_up)) + // The vertical crossing comes eralier than the prev crossing. + // Disable the perimeter going forward. + intrsctn_type_next = INTERSECTION_TYPE_OTHER_VLINE_NOT_FIRST; + if (going_up ? (d_up > std::min(d_horiz, d_down)) : (d_down > std::min(d_horiz, d_up))) + // The horizontal crossing comes earlier than the vertical crossing. + vert_seg_dir_valid_mask &= ~(forward ? DIR_FORWARD : DIR_BACKWARD); + } + } + + // 4) Try to connect to a previous or next vertical line, making a zig-zag pattern. + if (intrsctn_type_prev == INTERSECTION_TYPE_OTHER_VLINE_OK || intrsctn_type_next == INTERSECTION_TYPE_OTHER_VLINE_OK) { + coordf_t distPrev = (intrsctn_type_prev != INTERSECTION_TYPE_OTHER_VLINE_OK) ? std::numeric_limits::max() : + measure_perimeter_prev_segment_length(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, iPrev); + coordf_t distNext = (intrsctn_type_next != INTERSECTION_TYPE_OTHER_VLINE_OK) ? std::numeric_limits::max() : + measure_perimeter_next_segment_length(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, iNext); + // Take the shorter path. + //FIXME this may not be always the best strategy to take the shortest connection line now. + bool take_next = (intrsctn_type_prev == INTERSECTION_TYPE_OTHER_VLINE_OK && intrsctn_type_next == INTERSECTION_TYPE_OTHER_VLINE_OK) ? + (distNext < distPrev) : + intrsctn_type_next == INTERSECTION_TYPE_OTHER_VLINE_OK; + myassert(intrsctn->is_inner()); + bool skip = params.dont_connect || (link_max_length > 0 && (take_next ? distNext : distPrev) > link_max_length); + if (skip) { + // Just skip the connecting contour and start a new path. + goto dont_connect; + polyline_current->points.push_back(Point(seg.pos, intrsctn->pos())); + polylines_out.push_back(Polyline()); + polyline_current = &polylines_out.back(); + const SegmentedIntersectionLine &il2 = segs[take_next ? (i_vline + 1) : (i_vline - 1)]; + polyline_current->points.push_back(Point(il2.pos, il2.intersections[take_next ? iNext : iPrev].pos())); + } else { + polyline_current->points.push_back(Point(seg.pos, intrsctn->pos())); + emit_perimeter_prev_next_segment(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, take_next ? iNext : iPrev, *polyline_current, take_next); + } + // Mark both the left and right connecting segment as consumed, because one cannot go to this intersection point as it has been consumed. + if (iPrev != -1) + segs[i_vline-1].intersections[iPrev].consumed_perimeter_right = true; + if (iNext != -1) + intrsctn->consumed_perimeter_right = true; + //FIXME consume the left / right connecting segments at the other end of this line? Currently it is not critical because a perimeter segment is not followed if the vertical segment at the other side has already been consumed. + // Advance to the neighbor line. + if (take_next) { + ++ i_vline; + i_intersection = iNext; + } else { + -- i_vline; + i_intersection = iPrev; + } + continue; + } + + // 5) Try to connect to a previous or next point on the same vertical line. + if (vert_seg_dir_valid_mask) { + bool valid = true; + // Verify, that there is no intersection with the inner contour up to the end of the contour segment. + // Verify, that the successive segment has not been consumed yet. + if (going_up) { + if (seg.intersections[iAbove].consumed_vertical_up) { + valid = false; + } else { + for (int i = (int)i_intersection + 1; i < iAbove && valid; ++i) + if (seg.intersections[i].is_inner()) + valid = false; + } + } else { + if (seg.intersections[iBelow-1].consumed_vertical_up) { + valid = false; + } else { + for (int i = iBelow + 1; i < (int)i_intersection && valid; ++i) + if (seg.intersections[i].is_inner()) + valid = false; + } + } + if (valid) { + const Polygon &poly = poly_with_offset.contour(intrsctn->iContour); + int iNext = going_up ? iAbove : iBelow; + int iSegNext = going_up ? iSegAbove : iSegBelow; + bool dir_forward = (vert_seg_dir_valid_mask == (DIR_FORWARD | DIR_BACKWARD)) ? + // Take the shorter length between the current and the next intersection point. + (distance_of_segmens(poly, intrsctn->iSegment, iSegNext, true) < + distance_of_segmens(poly, intrsctn->iSegment, iSegNext, false)) : + (vert_seg_dir_valid_mask == DIR_FORWARD); + // Skip this perimeter line? + bool skip = params.dont_connect; + if (! skip && link_max_length > 0) { + coordf_t link_length = measure_perimeter_segment_on_vertical_line_length( + poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, iNext, dir_forward); + skip = link_length > link_max_length; + } + polyline_current->points.push_back(Point(seg.pos, intrsctn->pos())); + if (skip) { + // Just skip the connecting contour and start a new path. + polylines_out.push_back(Polyline()); + polyline_current = &polylines_out.back(); + polyline_current->points.push_back(Point(seg.pos, seg.intersections[iNext].pos())); + } else { + // Consume the connecting contour and the next segment. + emit_perimeter_segment_on_vertical_line(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, iNext, *polyline_current, dir_forward); + } + // Mark both the left and right connecting segment as consumed, because one cannot go to this intersection point as it has been consumed. + // If there are any outer intersection points skipped (bypassed) by the contour, + // mark them as processed. + if (going_up) { + for (int i = (int)i_intersection; i < iAbove; ++ i) + seg.intersections[i].consumed_vertical_up = true; + } else { + for (int i = iBelow; i < (int)i_intersection; ++ i) + seg.intersections[i].consumed_vertical_up = true; + } +// seg.intersections[going_up ? i_intersection : i_intersection - 1].consumed_vertical_up = true; + intrsctn->consumed_perimeter_right = true; + i_intersection = iNext; + if (going_up) + ++ intrsctn; + else + -- intrsctn; + intrsctn->consumed_perimeter_right = true; + continue; + } + } + dont_connect: + // No way to continue the current polyline. Take the rest of the line up to the outer contour. + // This will finish the polyline, starting another polyline at a new point. + if (going_up) + ++ intrsctn; + else + -- intrsctn; + } + + // Finish the current vertical line, + // reset the current vertical line to pick a new starting point in the next round. + myassert(intrsctn->is_outer()); + myassert(intrsctn->is_high() == going_up); + pointLast = Point(seg.pos, intrsctn->pos()); + polyline_current->points.push_back(pointLast); + // Handle duplicate points and zero length segments. + polyline_current->remove_duplicate_points(); + myassert(! polyline_current->has_duplicate_points()); + // Handle nearly zero length edges. + if (polyline_current->points.size() <= 1 || + (polyline_current->points.size() == 2 && + std::abs(polyline_current->points.front().x - polyline_current->points.back().x) < SCALED_EPSILON && + std::abs(polyline_current->points.front().y - polyline_current->points.back().y) < SCALED_EPSILON)) + polylines_out.pop_back(); + intrsctn = NULL; + i_intersection = -1; + polyline_current = NULL; + } + +#ifdef SLIC3R_DEBUG + { + { + ::Slic3r::SVG svg(debug_out_path("FillRectilinear2-final-%03d.svg", iRun), bbox_svg); // , scale_(1.)); + poly_with_offset.export_to_svg(svg); + for (size_t i = n_polylines_out_initial; i < polylines_out.size(); ++ i) + svg.draw(polylines_out[i].lines(), "black"); + } + // Paint a picture per polyline. This makes it easier to discover the order of the polylines and their overlap. + for (size_t i_polyline = n_polylines_out_initial; i_polyline < polylines_out.size(); ++ i_polyline) { + ::Slic3r::SVG svg(debug_out_path("FillRectilinear2-final-%03d-%03d.svg", iRun, i_polyline), bbox_svg); // , scale_(1.)); + svg.draw(polylines_out[i_polyline].lines(), "black"); + } + } +#endif /* SLIC3R_DEBUG */ + + // paths must be rotated back + for (Polylines::iterator it = polylines_out.begin() + n_polylines_out_initial; it != polylines_out.end(); ++ it) { + // No need to translate, the absolute position is irrelevant. + // it->translate(- rotate_vector.second.x, - rotate_vector.second.y); + myassert(! it->has_duplicate_points()); + it->rotate(rotate_vector.first); + //FIXME rather simplify the paths to avoid very short edges? + //myassert(! it->has_duplicate_points()); + it->remove_duplicate_points(); + } + +#ifdef SLIC3R_DEBUG + // Verify, that there are no duplicate points in the sequence. + for (Polylines::iterator it = polylines_out.begin(); it != polylines_out.end(); ++ it) + myassert(! it->has_duplicate_points()); +#endif /* SLIC3R_DEBUG */ + + return true; +} + +Polylines FillRectilinear2::fill_surface(const Surface &surface, const FillParams ¶ms) +{ + Polylines polylines_out; + if (! fill_surface_by_lines(&surface, params, 0.f, 0.f, polylines_out)) { + printf("FillRectilinear2::fill_surface() failed to fill a region.\n"); + } + return polylines_out; +} + +Polylines FillGrid2::fill_surface(const Surface &surface, const FillParams ¶ms) +{ + // Each linear fill covers half of the target coverage. + FillParams params2 = params; + params2.density *= 0.5f; + Polylines polylines_out; + if (! fill_surface_by_lines(&surface, params2, 0.f, 0.f, polylines_out) || + ! fill_surface_by_lines(&surface, params2, float(M_PI / 2.), 0.f, polylines_out)) { + printf("FillGrid2::fill_surface() failed to fill a region.\n"); + } + return polylines_out; +} + +Polylines FillTriangles::fill_surface(const Surface &surface, const FillParams ¶ms) +{ + // Each linear fill covers 1/3 of the target coverage. + FillParams params2 = params; + params2.density *= 0.333333333f; + Polylines polylines_out; + if (! fill_surface_by_lines(&surface, params2, 0.f, 0., polylines_out) || + ! fill_surface_by_lines(&surface, params2, float(M_PI / 3.), 0., polylines_out) || + ! fill_surface_by_lines(&surface, params2, float(2. * M_PI / 3.), 0.5 * this->spacing / params2.density, polylines_out)) { + printf("FillTriangles::fill_surface() failed to fill a region.\n"); + } + return polylines_out; +} + +Polylines FillStars::fill_surface(const Surface &surface, const FillParams ¶ms) +{ + // Each linear fill covers 1/3 of the target coverage. + FillParams params2 = params; + params2.density *= 0.333333333f; + Polylines polylines_out; + if (! fill_surface_by_lines(&surface, params2, 0.f, 0., polylines_out) || + ! fill_surface_by_lines(&surface, params2, float(M_PI / 3.), 0., polylines_out) || + ! fill_surface_by_lines(&surface, params2, float(2. * M_PI / 3.), 0., polylines_out)) { + printf("FillStars::fill_surface() failed to fill a region.\n"); + } + return polylines_out; +} + +Polylines FillCubic::fill_surface(const Surface &surface, const FillParams ¶ms) +{ + // Each linear fill covers 1/3 of the target coverage. + FillParams params2 = params; + params2.density *= 0.333333333f; + Polylines polylines_out; + if (! fill_surface_by_lines(&surface, params2, 0.f, z, polylines_out) || + ! fill_surface_by_lines(&surface, params2, float(M_PI / 3.), -z, polylines_out) || + // Rotated by PI*2/3 + PI to achieve reverse sloping wall. + ! fill_surface_by_lines(&surface, params2, float(M_PI * 2. / 3.), z, polylines_out)) { + printf("FillCubic::fill_surface() failed to fill a region.\n"); + } + return polylines_out; +} + +static inline bool is_stick(const Point &p1, const Point &p2, const Point &p3) +{ + Point v1 = p2 - p1; + Point v2 = p3 - p2; + int64_t dir = int64_t(v1.x) * int64_t(v2.x) + int64_t(v1.y) * int64_t(v2.y); + if (dir > 0) + // p3 does not turn back to p1. Do not remove p2. + return false; + double l2_1 = double(v1.x) * double(v1.x) + double(v1.y) * double(v1.y); + double l2_2 = double(v2.x) * double(v2.x) + double(v2.y) * double(v2.y); + if (dir == 0) + // p1, p2, p3 may make a perpendicular corner, or there is a zero edge length. + // Remove p2 if it is coincident with p1 or p2. + return l2_1 == 0 || l2_2 == 0; + // p3 turns back to p1 after p2. Are p1, p2, p3 collinear? + // Calculate distance from p3 to a segment (p1, p2) or from p1 to a segment(p2, p3), + // whichever segment is longer + double cross = double(v1.x) * double(v2.y) - double(v2.x) * double(v1.y); + double dist2 = cross * cross / std::max(l2_1, l2_2); + return dist2 < EPSILON * EPSILON; +} + +bool remove_sticks(Polygon &poly) +{ + bool modified = false; + size_t j = 1; + for (size_t i = 1; i + 1 < poly.points.size(); ++ i) { + if (! is_stick(poly[j-1], poly[i], poly[i+1])) { + // Keep the point. + if (j < i) + poly.points[j] = poly.points[i]; + ++ j; + } + } + if (++ j < poly.points.size()) { + poly.points[j-1] = poly.points.back(); + poly.points.erase(poly.points.begin() + j, poly.points.end()); + modified = true; + } + while (poly.points.size() >= 3 && is_stick(poly.points[poly.points.size()-2], poly.points.back(), poly.points.front())) { + poly.points.pop_back(); + modified = true; + } + while (poly.points.size() >= 3 && is_stick(poly.points.back(), poly.points.front(), poly.points[1])) + poly.points.erase(poly.points.begin()); + return modified; +} + +bool remove_sticks(Polygons &polys) +{ + bool modified = false; + size_t j = 0; + for (size_t i = 0; i < polys.size(); ++ i) { + modified |= remove_sticks(polys[i]); + if (polys[i].points.size() >= 3) { + if (j < i) + std::swap(polys[i].points, polys[j].points); + ++ j; + } + } + if (j < polys.size()) + polys.erase(polys.begin() + j, polys.end()); + return modified; +} + +bool remove_sticks(ExPolygon &poly) +{ + return remove_sticks(poly.contour) || remove_sticks(poly.holes); +} + +bool remove_small(Polygons &polys, double min_area) +{ + bool modified = false; + size_t j = 0; + for (size_t i = 0; i < polys.size(); ++ i) { + if (std::abs(polys[i].area()) >= min_area) { + if (j < i) + std::swap(polys[i].points, polys[j].points); + ++ j; + } else + modified = true; + } + if (j < polys.size()) + polys.erase(polys.begin() + j, polys.end()); + return modified; +} + + + +} // namespace Slic3r diff --git a/xs/src/libslic3r/Fill/FillRectilinear2.hpp b/xs/src/libslic3r/Fill/FillRectilinear2.hpp new file mode 100644 index 000000000..f1f8973de --- /dev/null +++ b/xs/src/libslic3r/Fill/FillRectilinear2.hpp @@ -0,0 +1,76 @@ +#ifndef slic3r_FillRectilinear2_hpp_ +#define slic3r_FillRectilinear2_hpp_ + +#include "../libslic3r.h" + +#include "FillBase.hpp" + +namespace Slic3r { + +class Surface; + +class FillRectilinear2 : public Fill +{ +public: + virtual ~FillRectilinear2() {} + virtual Polylines fill_surface(const Surface &surface, const FillParams ¶ms); + +protected: + bool fill_surface_by_lines(const Surface *surface, const FillParams ¶ms, float angleBase, float pattern_shift, Polylines &polylines_out); +}; + +class FillGrid2 : public FillRectilinear2 +{ +public: + virtual ~FillGrid2() {} + virtual Polylines fill_surface(const Surface &surface, const FillParams ¶ms); + +protected: + // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. + virtual float _layer_angle(size_t idx) const { return 0.f; } +}; + +class FillTriangles : public FillRectilinear2 +{ +public: + virtual ~FillTriangles() {} + virtual Polylines fill_surface(const Surface &surface, const FillParams ¶ms); + +protected: + // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. + virtual float _layer_angle(size_t idx) const { return 0.f; } +}; + +class FillStars : public FillRectilinear2 +{ +public: + virtual ~FillStars() {} + virtual Polylines fill_surface(const Surface &surface, const FillParams ¶ms); + +protected: + // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. + virtual float _layer_angle(size_t idx) const { return 0.f; } +}; + +class FillCubic : public FillRectilinear2 +{ +public: + virtual ~FillCubic() {} + virtual Polylines fill_surface(const Surface &surface, const FillParams ¶ms); + +protected: + // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. + virtual float _layer_angle(size_t idx) const { return 0.f; } +}; + + +// Remove sticks (tentacles with zero area) from the polygon. +extern bool remove_sticks(Polygon &poly); +extern bool remove_sticks(Polygons &polys); +extern bool remove_sticks(ExPolygon &poly); +extern bool remove_small(Polygons &polys, double min_area); + + +}; // namespace Slic3r + +#endif // slic3r_FillRectilinear2_hpp_ diff --git a/xs/src/libslic3r/Layer.cpp b/xs/src/libslic3r/Layer.cpp index 55d3c8811..da71552c9 100644 --- a/xs/src/libslic3r/Layer.cpp +++ b/xs/src/libslic3r/Layer.cpp @@ -2,7 +2,7 @@ #include "ClipperUtils.hpp" #include "Geometry.hpp" #include "Print.hpp" - +#include "Fill/Fill.hpp" namespace Slic3r { @@ -47,18 +47,6 @@ Layer::set_id(size_t id) this->_id = id; } -PrintObject* -Layer::object() -{ - return this->_object; -} - -const PrintObject* -Layer::object() const -{ - return this->_object; -} - size_t Layer::region_count() const @@ -249,16 +237,23 @@ Layer::make_perimeters() } } - -SupportLayer::SupportLayer(size_t id, PrintObject *object, coordf_t height, - coordf_t print_z, coordf_t slice_z) -: Layer(id, object, height, print_z, slice_z) +void +Layer::make_fills() { + #ifdef SLIC3R_DEBUG + printf("Making fills for layer %zu\n", this->id()); + #endif + + FOREACH_LAYERREGION(this, it_layerm) { + LayerRegion &layerm = **it_layerm; + layerm.fills.clear(); + make_fill(layerm, &layerm.fills); + + #ifndef NDEBUG + for (size_t i = 0; i < layerm.fills.entities.size(); ++i) + assert(dynamic_cast(layerm.fills.entities[i]) != NULL); + #endif + } } -SupportLayer::~SupportLayer() -{ -} - - } diff --git a/xs/src/libslic3r/Layer.hpp b/xs/src/libslic3r/Layer.hpp index 4badb8374..59badcb1f 100644 --- a/xs/src/libslic3r/Layer.hpp +++ b/xs/src/libslic3r/Layer.hpp @@ -25,8 +25,10 @@ class LayerRegion friend class Layer; public: - Layer* layer(); - PrintRegion* region(); + Layer* layer() { return this->_layer; }; + const Layer* layer() const { return this->_layer; }; + PrintRegion* region() { return this->_region; }; + const PrintRegion* region() const { return this->_region; }; // collection of surfaces generated by slicing the original geometry // divided by type top/bottom/internal @@ -64,8 +66,9 @@ class LayerRegion Layer *_layer; PrintRegion *_region; - LayerRegion(Layer *layer, PrintRegion *region); - ~LayerRegion(); + LayerRegion(Layer *layer, PrintRegion *region) + : _layer(layer), _region(region) {}; + ~LayerRegion() {}; }; @@ -77,8 +80,8 @@ class Layer { public: size_t id() const; void set_id(size_t id); - PrintObject* object(); - const PrintObject* object() const; + PrintObject* object() { return this->_object; }; + const PrintObject* object() const { return this->_object; }; Layer *upper_layer; Layer *lower_layer; @@ -102,10 +105,11 @@ class Layer { template bool any_internal_region_slice_contains(const T &item) const; template bool any_bottom_region_slice_contains(const T &item) const; void make_perimeters(); + void make_fills(); protected: size_t _id; // sequential number of layer, 0-based - PrintObject *_object; + PrintObject* _object; Layer(size_t id, PrintObject *object, coordf_t height, coordf_t print_z, @@ -126,9 +130,10 @@ class SupportLayer : public Layer { ExtrusionEntityCollection support_interface_fills; protected: - SupportLayer(size_t id, PrintObject *object, coordf_t height, coordf_t print_z, - coordf_t slice_z); - virtual ~SupportLayer(); + SupportLayer(size_t id, PrintObject *object, coordf_t height, + coordf_t print_z, coordf_t slice_z) + : Layer(id, object, height, print_z, slice_z) {}; + virtual ~SupportLayer() {}; }; diff --git a/xs/src/libslic3r/LayerRegion.cpp b/xs/src/libslic3r/LayerRegion.cpp index 9f803094e..b97fa0727 100644 --- a/xs/src/libslic3r/LayerRegion.cpp +++ b/xs/src/libslic3r/LayerRegion.cpp @@ -7,28 +7,6 @@ namespace Slic3r { -LayerRegion::LayerRegion(Layer *layer, PrintRegion *region) -: _layer(layer), - _region(region) -{ -} - -LayerRegion::~LayerRegion() -{ -} - -Layer* -LayerRegion::layer() -{ - return this->_layer; -} - -PrintRegion* -LayerRegion::region() -{ - return this->_region; -} - Flow LayerRegion::flow(FlowRole role, bool bridge, double width) const { @@ -167,18 +145,16 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer) { // merge top and bottom in a single collection SurfaceCollection tb = top; - tb.surfaces.insert(tb.surfaces.end(), bottom.surfaces.begin(), bottom.surfaces.end()); + tb.append(bottom); // group surfaces - std::vector groups; + std::vector groups; tb.group(&groups); - for (std::vector::const_iterator g = groups.begin(); g != groups.end(); ++g) { + for (std::vector::const_iterator g = groups.begin(); g != groups.end(); ++g) { Polygons subject; - for (SurfacesPtr::const_iterator s = g->begin(); s != g->end(); ++s) { - Polygons pp = **s; - subject.insert(subject.end(), pp.begin(), pp.end()); - } + for (SurfacesConstPtr::const_iterator s = g->begin(); s != g->end(); ++s) + append_to(subject, (Polygons)**s); ExPolygons expp = intersection_ex( subject, @@ -203,15 +179,13 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer) } // group surfaces - std::vector groups; + std::vector groups; other.group(&groups); - for (std::vector::const_iterator g = groups.begin(); g != groups.end(); ++g) { + for (std::vector::const_iterator g = groups.begin(); g != groups.end(); ++g) { Polygons subject; - for (SurfacesPtr::const_iterator s = g->begin(); s != g->end(); ++s) { - Polygons pp = **s; - subject.insert(subject.end(), pp.begin(), pp.end()); - } + for (SurfacesConstPtr::const_iterator s = g->begin(); s != g->end(); ++s) + append_to(subject, (Polygons)**s); ExPolygons expp = diff_ex( subject, diff --git a/xs/src/libslic3r/MultiPoint.cpp b/xs/src/libslic3r/MultiPoint.cpp index 701fa4c3a..297e7ec3c 100644 --- a/xs/src/libslic3r/MultiPoint.cpp +++ b/xs/src/libslic3r/MultiPoint.cpp @@ -33,22 +33,26 @@ MultiPoint::translate(const Point &vector) void MultiPoint::rotate(double angle) { - double s = sin(angle); - double c = cos(angle); + double s = sin(angle); + double c = cos(angle); for (Points::iterator it = points.begin(); it != points.end(); ++it) { - (*it).rotate(angle); - double cur_x = (double)it->x; - double cur_y = (double)it->y; - it->x = (coord_t)round(c * cur_x - s * cur_y); - it->y = (coord_t)round(c * cur_y + s * cur_x); + double cur_x = (double)it->x; + double cur_y = (double)it->y; + it->x = (coord_t)round(c * cur_x - s * cur_y); + it->y = (coord_t)round(c * cur_y + s * cur_x); } } void MultiPoint::rotate(double angle, const Point ¢er) { + double s = sin(angle); + double c = cos(angle); for (Points::iterator it = points.begin(); it != points.end(); ++it) { - (*it).rotate(angle, center); + double dx = double(it->x - center.x); + double dy = double(it->y - center.y); + it->x = (coord_t)round(double(center.x) + c * dx - s * dy); + it->y = (coord_t)round(double(center.y) + c * dy + s * dx); } } @@ -97,15 +101,33 @@ MultiPoint::bounding_box() const return BoundingBox(this->points); } -void +bool +MultiPoint::has_duplicate_points() const +{ + for (size_t i = 1; i < points.size(); ++i) + if (points[i-1].coincides_with(points[i])) + return true; + return false; +} + +bool MultiPoint::remove_duplicate_points() { - for (size_t i = 1; i < this->points.size(); ++i) { - if (this->points.at(i).coincides_with(this->points.at(i-1))) { - this->points.erase(this->points.begin() + i); - --i; + size_t j = 0; + for (size_t i = 1; i < points.size(); ++i) { + if (points[j].coincides_with(points[i])) { + // Just increase index i. + } else { + ++ j; + if (j < i) + points[j] = points[i]; } } + if (++ j < points.size()) { + points.erase(points.begin() + j, points.end()); + return true; + } + return false; } void diff --git a/xs/src/libslic3r/MultiPoint.hpp b/xs/src/libslic3r/MultiPoint.hpp index 9c71f42f4..99bc79281 100644 --- a/xs/src/libslic3r/MultiPoint.hpp +++ b/xs/src/libslic3r/MultiPoint.hpp @@ -34,7 +34,13 @@ class MultiPoint int find_point(const Point &point) const; bool has_boundary_point(const Point &point) const; BoundingBox bounding_box() const; - void remove_duplicate_points(); + + // Return true if there are exact duplicates. + bool has_duplicate_points() const; + + // Remove exact duplicates, return true if any duplicate has been removed. + bool remove_duplicate_points(); + void append(const Point &point); void append(const Points &points); void append(const Points::const_iterator &begin, const Points::const_iterator &end); diff --git a/xs/src/libslic3r/Point.cpp b/xs/src/libslic3r/Point.cpp index c70164e19..ba4576a21 100644 --- a/xs/src/libslic3r/Point.cpp +++ b/xs/src/libslic3r/Point.cpp @@ -72,8 +72,10 @@ Point::rotate(double angle, const Point ¢er) double cur_y = (double)this->y; double s = sin(angle); double c = cos(angle); - this->x = (coord_t)round( (double)center.x + c * (cur_x - (double)center.x) - s * (cur_y - (double)center.y) ); - this->y = (coord_t)round( (double)center.y + c * (cur_y - (double)center.y) + s * (cur_x - (double)center.x) ); + double dx = cur_x - (double)center.x; + double dy = cur_y - (double)center.y; + this->x = (coord_t)round( (double)center.x + c * dx - s * dy ); + this->y = (coord_t)round( (double)center.y + c * dy + s * dx ); } bool @@ -302,6 +304,27 @@ Point::vector_to(const Point &point) const return Vector(point.x - this->x, point.y - this->y); } +// Align a coordinate to a grid. The coordinate may be negative, +// the aligned value will never be bigger than the original one. +static coord_t +_align_to_grid(const coord_t coord, const coord_t spacing) { + // Current C++ standard defines the result of integer division to be rounded to zero, + // for both positive and negative numbers. Here we want to round down for negative + // numbers as well. + coord_t aligned = (coord < 0) ? + ((coord - spacing + 1) / spacing) * spacing : + (coord / spacing) * spacing; + assert(aligned <= coord); + return aligned; +} + +void +Point::align_to_grid(const Point &spacing, const Point &base) +{ + this->x = base.x + _align_to_grid(this->x - base.x, spacing.x); + this->y = base.y + _align_to_grid(this->y - base.y, spacing.y); +} + Point operator+(const Point& point1, const Point& point2) { diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp index 9c23252cb..c6c340446 100644 --- a/xs/src/libslic3r/Point.hpp +++ b/xs/src/libslic3r/Point.hpp @@ -2,10 +2,10 @@ #define slic3r_Point_hpp_ #include "libslic3r.h" -#include #include -#include #include +#include +#include namespace Slic3r { @@ -44,6 +44,16 @@ class Point void translate(const Vector &vector); void rotate(double angle); void rotate(double angle, const Point ¢er); + Point rotated(double angle) const { + Point p(*this); + p.rotate(angle); + return p; + } + Point rotated(double angle, const Point ¢er) const { + Point p(*this); + p.rotate(angle, center); + return p; + } bool coincides_with(const Point &point) const { return this->x == point.x && this->y == point.y; } bool coincides_with_epsilon(const Point &point) const; int nearest_point_index(const Points &points) const; @@ -62,6 +72,7 @@ class Point Point projection_onto(const Line &line) const; Point negative() const; Vector vector_to(const Point &point) const; + void align_to_grid(const Point &spacing, const Point &base = Point(0,0)); }; Point operator+(const Point& point1, const Point& point2); @@ -116,6 +127,16 @@ class Pointf3 : public Pointf Vectorf3 vector_to(const Pointf3 &point) const; }; +template +inline Points +to_points(const std::vector &items) +{ + Points pp; + for (typename std::vector::const_iterator it = items.begin(); it != items.end(); ++it) + append_to(pp, (Points)*it); + return pp; +} + } // start Boost diff --git a/xs/src/libslic3r/Polygon.hpp b/xs/src/libslic3r/Polygon.hpp index e3ea13f24..415aa404e 100644 --- a/xs/src/libslic3r/Polygon.hpp +++ b/xs/src/libslic3r/Polygon.hpp @@ -6,11 +6,11 @@ #include #include "Line.hpp" #include "MultiPoint.hpp" -#include "Polyline.hpp" namespace Slic3r { class Polygon; +class Polyline; typedef std::vector Polygons; class Polygon : public MultiPoint { diff --git a/xs/src/libslic3r/Polyline.hpp b/xs/src/libslic3r/Polyline.hpp index 4d6673f2c..dfd87fef7 100644 --- a/xs/src/libslic3r/Polyline.hpp +++ b/xs/src/libslic3r/Polyline.hpp @@ -4,6 +4,7 @@ #include "libslic3r.h" #include "Line.hpp" #include "MultiPoint.hpp" +#include "Polygon.hpp" #include #include @@ -42,6 +43,24 @@ class ThickPolyline : public Polyline { void reverse(); }; +inline Polylines +to_polylines(const Polygons &polygons) +{ + Polylines pp; + for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it) + pp.push_back((Polyline)*it); + return pp; +} + +inline Polylines +to_polylines(const Lines &lines) +{ + Polylines pp; + for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) + pp.push_back((Polyline)*it); + return pp; +} + } #endif diff --git a/xs/src/libslic3r/PolylineCollection.cpp b/xs/src/libslic3r/PolylineCollection.cpp index 136065127..f92db0b03 100644 --- a/xs/src/libslic3r/PolylineCollection.cpp +++ b/xs/src/libslic3r/PolylineCollection.cpp @@ -10,9 +10,8 @@ struct Chaining }; #ifndef sqr -template -inline sqr(T x) { return x * x; } -#endif /* sqr */ + #define sqr(x) (x * x); +#endif template inline int nearest_point_index(const std::vector &pairs, const Point &start_near, bool no_reverse) @@ -43,18 +42,17 @@ inline int nearest_point_index(const std::vector &pairs, const Point & } } } - return idx; } -Polylines PolylineCollection::chained_path_from( -#if SLIC3R_CPPVER > 11 - Polylines &&src, -#else +Polylines PolylineCollection::_chained_path_from( const Polylines &src, + Point start_near, + bool no_reverse +#if SLIC3R_CPPVER >= 11 + , bool move_from_src #endif - Point start_near, - bool no_reverse) + ) { std::vector endpoints; endpoints.reserve(src.size()); @@ -64,14 +62,19 @@ Polylines PolylineCollection::chained_path_from( if (! no_reverse) c.last = src[i].last_point(); c.idx = i; + endpoints.push_back(c); } - Polylines retval; while (! endpoints.empty()) { // find nearest point int endpoint_index = nearest_point_index(endpoints, start_near, no_reverse); + assert(endpoint_index >= 0 && endpoint_index < endpoints.size() * 2); #if SLIC3R_CPPVER > 11 - retval.push_back(std::move(src[endpoints[endpoint_index/2].idx])); + if (move_from_src) { + retval.push_back(std::move(src[endpoints[endpoint_index/2].idx])); + } else { + retval.push_back(src[endpoints[endpoint_index/2].idx]); + } #else retval.push_back(src[endpoints[endpoint_index/2].idx]); #endif @@ -80,33 +83,42 @@ Polylines PolylineCollection::chained_path_from( endpoints.erase(endpoints.begin() + endpoint_index/2); start_near = retval.back().last_point(); } + return retval; } -#if SLIC3R_CPPVER > 11 +#if SLIC3R_CPPVER >= 11 Polylines PolylineCollection::chained_path(Polylines &&src, bool no_reverse) { - return (src.empty() || src.front().empty()) ? + return (src.empty() || src.front().points.empty()) ? Polylines() : - chained_path_from(std::move(src), src.front().first_point(), no_reverse); + _chained_path_from(src, src.front().first_point(), no_reverse, true); } -Polylines PolylineCollection::chained_path_from(Polylines src, Point start_near, bool no_reverse) + +Polylines PolylineCollection::chained_path_from(Polylines &&src, Point start_near, bool no_reverse) { - return chained_path_from(std::move(src), start_near, no_reverse); + return _chained_path_from(src, start_near, no_reverse, true); } -Polylines PolylineCollection::chained_path(Polylines src, bool no_reverse) -{ - return (src.empty() || src.front().empty()) ? - Polylines() : - chained_path_from(std::move(src), src.front().first_point(), no_reverse); -} -#else +#endif + Polylines PolylineCollection::chained_path(const Polylines &src, bool no_reverse) { return (src.empty() || src.front().points.empty()) ? Polylines() : - chained_path_from(src, src.front().first_point(), no_reverse); -} + _chained_path_from(src, src.front().first_point(), no_reverse +#if SLIC3R_CPPVER >= 11 + , false #endif + ); +} + +Polylines PolylineCollection::chained_path_from(const Polylines &src, Point start_near, bool no_reverse) +{ + return _chained_path_from(src, start_near, no_reverse +#if SLIC3R_CPPVER >= 11 + , false +#endif + ); +} Point PolylineCollection::leftmost_point(const Polylines &polylines) { diff --git a/xs/src/libslic3r/PolylineCollection.hpp b/xs/src/libslic3r/PolylineCollection.hpp index 492f0318f..80d609410 100644 --- a/xs/src/libslic3r/PolylineCollection.hpp +++ b/xs/src/libslic3r/PolylineCollection.hpp @@ -8,26 +8,32 @@ namespace Slic3r { class PolylineCollection { + static Polylines _chained_path_from( + const Polylines &src, + Point start_near, + bool no_reverse +#if SLIC3R_CPPVER >= 11 + , bool move_from_src +#endif + ); + public: Polylines polylines; void chained_path(PolylineCollection* retval, bool no_reverse = false) const { retval->polylines = chained_path(this->polylines, no_reverse); } - void chained_path_from(Point start_near, PolylineCollection* retval, bool no_reverse = false) const + void chained_path_from(Point start_near, PolylineCollection* retval, bool no_reverse = false) const { retval->polylines = chained_path_from(this->polylines, start_near, no_reverse); } Point leftmost_point() const { return leftmost_point(polylines); } void append(const Polylines &polylines); static Point leftmost_point(const Polylines &polylines); -#if SLIC3R_CPPVER > 11 +#if SLIC3R_CPPVER >= 11 static Polylines chained_path(Polylines &&src, bool no_reverse = false); static Polylines chained_path_from(Polylines &&src, Point start_near, bool no_reverse = false); - static Polylines chained_path(Polylines src, bool no_reverse = false); - static Polylines chained_path_from(Polylines src, Point start_near, bool no_reverse = false); -#else - static Polylines chained_path(const Polylines &src, bool no_reverse = false); - static Polylines chained_path_from(const Polylines &src, Point start_near, bool no_reverse = false); #endif + static Polylines chained_path(const Polylines &src, bool no_reverse = false); + static Polylines chained_path_from(const Polylines &src, Point start_near, bool no_reverse = false); }; } diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 59dc5bfb1..fca964a74 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -368,6 +368,11 @@ PrintConfigDef::PrintConfigDef() def->enum_values.push_back("alignedrectilinear"); def->enum_values.push_back("grid"); def->enum_values.push_back("line"); + def->enum_values.push_back("rectilinear2"); + def->enum_values.push_back("grid2"); + def->enum_values.push_back("triangles"); + def->enum_values.push_back("stars"); + def->enum_values.push_back("cubic"); def->enum_values.push_back("concentric"); def->enum_values.push_back("honeycomb"); def->enum_values.push_back("3dhoneycomb"); @@ -375,9 +380,14 @@ PrintConfigDef::PrintConfigDef() def->enum_values.push_back("archimedeanchords"); def->enum_values.push_back("octagramspiral"); def->enum_labels.push_back("Rectilinear"); - def->enum_labels.push_back("AlignedRectilinear"); + def->enum_labels.push_back("Aligned Rectilinear"); def->enum_labels.push_back("Grid"); def->enum_labels.push_back("Line"); + def->enum_labels.push_back("Rectilinear 2"); + def->enum_labels.push_back("Grid 2"); + def->enum_labels.push_back("Triangles"); + def->enum_labels.push_back("Stars"); + def->enum_labels.push_back("Cubic"); def->enum_labels.push_back("Concentric"); def->enum_labels.push_back("Honeycomb"); def->enum_labels.push_back("3D Honeycomb"); diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 8d86bddfe..953487acd 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -30,7 +30,9 @@ enum GCodeFlavor { }; enum InfillPattern { - ipRectilinear, ipAlignedRectilinear, ipGrid, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, + ipRectilinear, ipGrid, ipLine, ipAlignedRectilinear, + ipRectilinear2, ipGrid2, ipTriangles, ipStars, ipCubic, + ipConcentric, ipHoneycomb, ip3DHoneycomb, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, }; @@ -62,6 +64,11 @@ template<> inline t_config_enum_values ConfigOptionEnum::get_enum keys_map["alignedrectilinear"] = ipAlignedRectilinear; keys_map["grid"] = ipGrid; keys_map["line"] = ipLine; + keys_map["rectilinear2"] = ipRectilinear2; + keys_map["grid2"] = ipGrid2; + keys_map["triangles"] = ipTriangles; + keys_map["stars"] = ipStars; + keys_map["cubic"] = ipCubic; keys_map["concentric"] = ipConcentric; keys_map["honeycomb"] = ipHoneycomb; keys_map["3dhoneycomb"] = ip3DHoneycomb; diff --git a/xs/src/libslic3r/Surface.cpp b/xs/src/libslic3r/Surface.cpp index ba32ecd10..4d2234e4d 100644 --- a/xs/src/libslic3r/Surface.cpp +++ b/xs/src/libslic3r/Surface.cpp @@ -54,15 +54,4 @@ Surface::is_bridge() const || this->surface_type == stInternalBridge; } -Polygons -to_polygons(const Surfaces &surfaces) -{ - Slic3r::Polygons pp; - for (Surfaces::const_iterator s = surfaces.begin(); s != surfaces.end(); ++s) { - Slic3r::Polygons ppp = *s; - pp.insert(pp.end(), ppp.begin(), ppp.end()); - } - return pp; -} - } diff --git a/xs/src/libslic3r/Surface.hpp b/xs/src/libslic3r/Surface.hpp index d445db208..895d7e904 100644 --- a/xs/src/libslic3r/Surface.hpp +++ b/xs/src/libslic3r/Surface.hpp @@ -33,8 +33,34 @@ class Surface typedef std::vector Surfaces; typedef std::vector SurfacesPtr; +typedef std::vector SurfacesConstPtr; -Polygons to_polygons(const Surfaces &surfaces); +inline Polygons +to_polygons(const Surfaces &surfaces) +{ + Slic3r::Polygons pp; + for (Surfaces::const_iterator s = surfaces.begin(); s != surfaces.end(); ++s) + append_to(pp, (Polygons)*s); + return pp; +} + +inline Polygons +to_polygons(const SurfacesPtr &surfaces) +{ + Slic3r::Polygons pp; + for (SurfacesPtr::const_iterator s = surfaces.begin(); s != surfaces.end(); ++s) + append_to(pp, (Polygons)**s); + return pp; +} + +inline Polygons +to_polygons(const SurfacesConstPtr &surfaces) +{ + Slic3r::Polygons pp; + for (SurfacesConstPtr::const_iterator s = surfaces.begin(); s != surfaces.end(); ++s) + append_to(pp, (Polygons)**s); + return pp; +} } diff --git a/xs/src/libslic3r/SurfaceCollection.cpp b/xs/src/libslic3r/SurfaceCollection.cpp index 21e6eb0cc..fb92796ca 100644 --- a/xs/src/libslic3r/SurfaceCollection.cpp +++ b/xs/src/libslic3r/SurfaceCollection.cpp @@ -41,13 +41,13 @@ SurfaceCollection::simplify(double tolerance) /* group surfaces by common properties */ void -SurfaceCollection::group(std::vector *retval) +SurfaceCollection::group(std::vector *retval) const { - for (Surfaces::iterator it = this->surfaces.begin(); it != this->surfaces.end(); ++it) { + for (Surfaces::const_iterator it = this->surfaces.begin(); it != this->surfaces.end(); ++it) { // find a group with the same properties - SurfacesPtr* group = NULL; - for (std::vector::iterator git = retval->begin(); git != retval->end(); ++git) { - Surface* gkey = git->front(); + SurfacesConstPtr* group = NULL; + for (std::vector::iterator git = retval->begin(); git != retval->end(); ++git) { + const Surface* gkey = git->front(); if ( gkey->surface_type == it->surface_type && gkey->thickness == it->thickness && gkey->thickness_layers == it->thickness_layers @@ -104,17 +104,48 @@ void SurfaceCollection::filter_by_type(SurfaceType type, Polygons* polygons) { for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { - if (surface->surface_type == type) { - Polygons pp = surface->expolygon; - polygons->insert(polygons->end(), pp.begin(), pp.end()); - } + if (surface->surface_type == type) + append_to(*polygons, (Polygons)surface->expolygon); } } void SurfaceCollection::append(const SurfaceCollection &coll) { - this->surfaces.insert(this->surfaces.end(), coll.surfaces.begin(), coll.surfaces.end()); + this->append(coll.surfaces); +} + +void +SurfaceCollection::append(const Surfaces &surfaces) +{ + append_to(this->surfaces, surfaces); +} + +void +SurfaceCollection::append(const ExPolygons &src, const Surface &templ) +{ + this->surfaces.reserve(this->surfaces.size() + src.size()); + for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++ it) { + this->surfaces.push_back(templ); + this->surfaces.back().expolygon = *it; + } +} + +void +SurfaceCollection::append(const ExPolygons &src, SurfaceType surfaceType) +{ + this->surfaces.reserve(this->surfaces.size() + src.size()); + for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++ it) + this->surfaces.push_back(Surface(surfaceType, *it)); +} + +size_t +SurfaceCollection::polygons_count() const +{ + size_t count = 0; + for (Surfaces::const_iterator it = this->surfaces.begin(); it != this->surfaces.end(); ++ it) + count += 1 + it->expolygon.holes.size(); + return count; } } diff --git a/xs/src/libslic3r/SurfaceCollection.hpp b/xs/src/libslic3r/SurfaceCollection.hpp index a4a3a7e5d..42d26b892 100644 --- a/xs/src/libslic3r/SurfaceCollection.hpp +++ b/xs/src/libslic3r/SurfaceCollection.hpp @@ -18,12 +18,16 @@ class SurfaceCollection operator Polygons() const; operator ExPolygons() const; void simplify(double tolerance); - void group(std::vector *retval); + void group(std::vector *retval) const; template bool any_internal_contains(const T &item) const; template bool any_bottom_contains(const T &item) const; SurfacesPtr filter_by_type(SurfaceType type); void filter_by_type(SurfaceType type, Polygons* polygons); void append(const SurfaceCollection &coll); + void append(const Surfaces &surfaces); + void append(const ExPolygons &src, const Surface &templ); + void append(const ExPolygons &src, SurfaceType surfaceType); + size_t polygons_count() const; }; } diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index 680716bb8..d35018c12 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -5,6 +5,7 @@ #include #include #include +#include #define SLIC3R_VERSION "1.3.0-dev" @@ -37,6 +38,12 @@ namespace Slic3r { enum Axis { X=0, Y, Z }; +template +inline void append_to(std::vector &dst, const std::vector &src) +{ + dst.insert(dst.end(), src.begin(), src.end()); +} + } using namespace Slic3r; @@ -53,10 +60,13 @@ void confess_at(const char *file, int line, const char *func, const char *pat, . // For example, could optimized functions with move semantics be used? #if __cplusplus==201402L #define SLIC3R_CPPVER 14 + #define STDMOVE(WHAT) std::move(WHAT) #elif __cplusplus==201103L #define SLIC3R_CPPVER 11 + #define STDMOVE(WHAT) std::move(WHAT) #else #define SLIC3R_CPPVER 0 + #define STDMOVE(WHAT) (WHAT) #endif #endif diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index 6852cb8dd..ad87768b5 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -11,6 +11,7 @@ REGISTER_CLASS(ExtrusionLoop, "ExtrusionLoop"); // there is no ExtrusionLoop::Collection or ExtrusionEntity::Collection REGISTER_CLASS(ExtrusionEntityCollection, "ExtrusionPath::Collection"); REGISTER_CLASS(Flow, "Flow"); +REGISTER_CLASS(Filler, "Filler"); REGISTER_CLASS(AvoidCrossingPerimeters, "GCode::AvoidCrossingPerimeters"); REGISTER_CLASS(OozePrevention, "GCode::OozePrevention"); REGISTER_CLASS(Wipe, "GCode::Wipe"); diff --git a/xs/src/xsinit.h b/xs/src/xsinit.h index 99b0e8f6a..700319fb6 100644 --- a/xs/src/xsinit.h +++ b/xs/src/xsinit.h @@ -53,6 +53,7 @@ extern "C" { #include #include #include +#include #include #include #include @@ -151,6 +152,20 @@ SV* to_SV(TriangleMesh* THIS); SV* polynode_children_2_perl(const ClipperLib::PolyNode& node); SV* polynode2perl(const ClipperLib::PolyNode& node); +class Filler +{ + public: + Filler() : fill(NULL) {}; + ~Filler() { + if (fill != NULL) { + delete fill; + fill = NULL; + } + }; + Fill *fill; + FillParams params; +}; + } #endif diff --git a/xs/xsp/Filler.xsp b/xs/xsp/Filler.xsp new file mode 100644 index 000000000..85d83d549 --- /dev/null +++ b/xs/xsp/Filler.xsp @@ -0,0 +1,78 @@ +%module{Slic3r::XS}; + +%{ +#include +#include "libslic3r/Fill/Fill.hpp" +#include "libslic3r/PolylineCollection.hpp" +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/ExtrusionEntityCollection.hpp" +%} + +%name{Slic3r::Filler} class Filler { + ~Filler(); + + void set_bounding_box(BoundingBox *bbox) + %code{% THIS->fill->set_bounding_box(*bbox); %}; + void set_spacing(coordf_t spacing) + %code{% THIS->fill->spacing = spacing; %}; + coordf_t spacing() + %code{% RETVAL = THIS->fill->spacing; %}; + void set_layer_id(size_t layer_id) + %code{% THIS->fill->layer_id = layer_id; %}; + void set_z(coordf_t z) + %code{% THIS->fill->z = z; %}; + void set_angle(float angle) + %code{% THIS->fill->angle = angle; %}; + void set_link_max_length(coordf_t len) + %code{% THIS->fill->link_max_length = len; %}; + void set_loop_clipping(coordf_t clipping) + %code{% THIS->fill->loop_clipping = clipping; %}; + + bool use_bridge_flow() + %code{% RETVAL = THIS->fill->use_bridge_flow(); %}; + bool no_sort() + %code{% RETVAL = THIS->fill->no_sort(); %}; + + void set_density(float density) + %code{% THIS->params.density = density; %}; + void set_dont_connect(bool dont_connect) + %code{% THIS->params.dont_connect = dont_connect; %}; + void set_dont_adjust(bool dont_adjust) + %code{% THIS->params.dont_adjust = dont_adjust; %}; + void set_complete(bool complete) + %code{% THIS->params.complete = complete; %}; + + PolylineCollection* _fill_surface(Surface *surface) + %code{% + PolylineCollection *pc = NULL; + if (THIS->fill != NULL) { + pc = new PolylineCollection(); + pc->polylines = THIS->fill->fill_surface(*surface, THIS->params); + } + RETVAL = pc; + %}; + +%{ + +Filler* +new_from_type(CLASS, type) + char* CLASS; + std::string type; + CODE: + Filler *filler = new Filler(); + filler->fill = Fill::new_from_type(type); + RETVAL = filler; + OUTPUT: + RETVAL + +%} + +}; + +%package{Slic3r::Filler}; + +void make_fill(LayerRegion* layerm, ExtrusionEntityCollection* out) + %code{% make_fill(*layerm, out); %}; + +coord_t adjust_solid_spacing(coord_t width, coord_t distance) + %code{% RETVAL = Fill::adjust_solid_spacing(width, distance); %}; diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp index e5697ce89..4be9feccf 100644 --- a/xs/xsp/Layer.xsp +++ b/xs/xsp/Layer.xsp @@ -86,6 +86,7 @@ bool any_bottom_region_slice_contains_polyline(Polyline* polyline) %code%{ RETVAL = THIS->any_bottom_region_slice_contains(*polyline); %}; void make_perimeters(); + void make_fills(); }; %name{Slic3r::Layer::Support} class SupportLayer { diff --git a/xs/xsp/SurfaceCollection.xsp b/xs/xsp/SurfaceCollection.xsp index 19cf3f828..a4fade4e5 100644 --- a/xs/xsp/SurfaceCollection.xsp +++ b/xs/xsp/SurfaceCollection.xsp @@ -60,18 +60,18 @@ SV* SurfaceCollection::group() CODE: // perform grouping - std::vector groups; + std::vector groups; THIS->group(&groups); // build return arrayref AV* av = newAV(); av_fill(av, groups.size()-1); size_t i = 0; - for (std::vector::iterator it = groups.begin(); it != groups.end(); ++it) { + for (std::vector::const_iterator it = groups.begin(); it != groups.end(); ++it) { AV* innerav = newAV(); av_fill(innerav, it->size()-1); size_t j = 0; - for (SurfacesPtr::iterator it_s = it->begin(); it_s != it->end(); ++it_s) { + for (SurfacesConstPtr::const_iterator it_s = it->begin(); it_s != it->end(); ++it_s) { av_store(innerav, j++, perl_to_SV_clone_ref(**it_s)); } av_store(av, i++, newRV_noinc((SV*)innerav)); diff --git a/xs/xsp/my.map b/xs/xsp/my.map index b91ba4794..18110d5dd 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -1,3 +1,4 @@ +coord_t T_IV coordf_t T_NV std::string T_STD_STRING @@ -116,6 +117,10 @@ ExtrusionLoop* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T +Filler* O_OBJECT_SLIC3R +Ref O_OBJECT_SLIC3R_T +Clone O_OBJECT_SLIC3R_T + Flow* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 87039c564..94740e0e7 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -1,5 +1,6 @@ %typemap{bool}{simple}; %typemap{size_t}{simple}; +%typemap{coord_t}{simple}; %typemap{coordf_t}{simple}; %typemap{std::string}; %typemap{t_config_option_key}; @@ -60,6 +61,9 @@ %typemap{Flow*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; +%typemap{Filler*}; +%typemap{Ref}{simple}; +%typemap{Clone}{simple}; %typemap{Line*}; %typemap{Ref}{simple}; %typemap{Clone}{simple};