package Slic3r::Fill::Honeycomb; use Moo; extends 'Slic3r::Fill::Base'; has 'cache' => (is => 'rw', default => sub {{}}); use Slic3r::Geometry qw(PI X Y MIN MAX scale scaled_epsilon); use Slic3r::Geometry::Clipper qw(intersection); 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}, $params{flow_spacing}; my $m; if (!($m = $self->cache->{$cache_id})) { $m = $self->cache->{$cache_id} = {}; my $min_spacing = scale $params{flow_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->extents->[X][MIN] -= $bounding_box->x_min % $m->{hex_width}; $bounding_box->extents->[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}) { # 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->expolygon, \@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 = map Slic3r::Polyline->new($_), @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection( $surface->expolygon, \@polygons, ) }; # connect paths { my $collection = Slic3r::Polyline::Collection->new(polylines => [@paths]); @paths = (); foreach my $path ($collection->chained_path) { if (@paths) { # distance between first point of this path and last point of last path my $distance = $paths[-1][-1]->distance_to($path->[0]); if ($distance <= $m->{hex_width}) { push @{$paths[-1]}, @$path; next; } } push @paths, $path; } } # clip paths again to prevent connection segments from crossing the expolygon boundaries @paths = map Slic3r::Polyline->new($_), @{ Boost::Geometry::Utils::multi_polygon_multi_linestring_intersection( [ $surface->expolygon->offset_ex(scaled_epsilon) ], [ @paths ], ) } if @paths; # this temporary check is a workaround for the multilinestring bug in B::G::U } return { flow_spacing => $params{flow_spacing} }, @paths; } 1;