Honeycomb.pm 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. package Slic3r::Fill::Honeycomb;
  2. use Moo;
  3. extends 'Slic3r::Fill::Base';
  4. with qw(Slic3r::Fill::WithDirection);
  5. has 'cache' => (is => 'rw', default => sub {{}});
  6. use Slic3r::Geometry qw(PI X Y MIN MAX scale scaled_epsilon);
  7. use Slic3r::Geometry::Clipper qw(intersection intersection_pl);
  8. sub angles () { [0, PI/3, PI/3*2] }
  9. sub fill_surface {
  10. my $self = shift;
  11. my ($surface, %params) = @_;
  12. my $rotate_vector = $self->infill_direction($surface);
  13. # cache hexagons math
  14. my $cache_id = sprintf "d%s_s%s", $params{density}, $self->spacing;
  15. my $m;
  16. if (!($m = $self->cache->{$cache_id})) {
  17. $m = $self->cache->{$cache_id} = {};
  18. my $min_spacing = scale($self->spacing);
  19. $m->{distance} = $min_spacing / $params{density};
  20. $m->{hex_side} = $m->{distance} / (sqrt(3)/2);
  21. $m->{hex_width} = $m->{distance} * 2; # $m->{hex_width} == $m->{hex_side} * sqrt(3);
  22. my $hex_height = $m->{hex_side} * 2;
  23. $m->{pattern_height} = $hex_height + $m->{hex_side};
  24. $m->{y_short} = $m->{distance} * sqrt(3)/3;
  25. $m->{x_offset} = $min_spacing / 2;
  26. $m->{y_offset} = $m->{x_offset} * sqrt(3)/3;
  27. $m->{hex_center} = Slic3r::Point->new($m->{hex_width}/2, $m->{hex_side});
  28. }
  29. my @polygons = ();
  30. {
  31. # adjust actual bounding box to the nearest multiple of our hex pattern
  32. # and align it so that it matches across layers
  33. my $bounding_box = $surface->expolygon->bounding_box;
  34. {
  35. # rotate bounding box according to infill direction
  36. my $bb_polygon = $bounding_box->polygon;
  37. $bb_polygon->rotate($rotate_vector->[0][0], $m->{hex_center});
  38. $bounding_box = $bb_polygon->bounding_box;
  39. # extend bounding box so that our pattern will be aligned with other layers
  40. # $bounding_box->[X1] and [Y1] represent the displacement between new bounding box offset and old one
  41. $bounding_box->merge_point(Slic3r::Point->new(
  42. $bounding_box->x_min - ($bounding_box->x_min % $m->{hex_width}),
  43. $bounding_box->y_min - ($bounding_box->y_min % $m->{pattern_height}),
  44. ));
  45. }
  46. my $x = $bounding_box->x_min;
  47. while ($x <= $bounding_box->x_max) {
  48. my $p = [];
  49. my @x = ($x + $m->{x_offset}, $x + $m->{distance} - $m->{x_offset});
  50. for (1..2) {
  51. @$p = reverse @$p; # turn first half upside down
  52. my @p = ();
  53. 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}) {
  54. push @$p,
  55. [ $x[1], $y + $m->{y_offset} ],
  56. [ $x[0], $y + $m->{y_short} - $m->{y_offset} ],
  57. [ $x[0], $y + $m->{y_short} + $m->{hex_side} + $m->{y_offset} ],
  58. [ $x[1], $y + $m->{y_short} + $m->{hex_side} + $m->{y_short} - $m->{y_offset} ],
  59. [ $x[1], $y + $m->{y_short} + $m->{hex_side} + $m->{y_short} + $m->{hex_side} + $m->{y_offset} ];
  60. }
  61. @x = map $_ + $m->{distance}, reverse @x; # draw symmetrical pattern
  62. $x += $m->{distance};
  63. }
  64. push @polygons, Slic3r::Polygon->new(@$p);
  65. }
  66. $_->rotate(-$rotate_vector->[0][0], $m->{hex_center}) for @polygons;
  67. }
  68. my @paths;
  69. if ($params{complete} || 1) {
  70. # we were requested to complete each loop;
  71. # in this case we don't try to make more continuous paths
  72. @paths = map $_->split_at_first_point,
  73. @{intersection([ $surface->p ], \@polygons)};
  74. } else {
  75. # consider polygons as polylines without re-appending the initial point:
  76. # this cuts the last segment on purpose, so that the jump to the next
  77. # path is more straight
  78. @paths = @{intersection_pl(
  79. [ map Slic3r::Polyline->new(@$_), @polygons ],
  80. [ @{$surface->expolygon} ],
  81. )};
  82. # connect paths
  83. if (@paths) { # prevent calling leftmost_point() on empty collections
  84. my $collection = Slic3r::Polyline::Collection->new(@paths);
  85. @paths = ();
  86. foreach my $path (@{$collection->chained_path_from($collection->leftmost_point, 0)}) {
  87. if (@paths) {
  88. # distance between first point of this path and last point of last path
  89. my $distance = $paths[-1]->last_point->distance_to($path->first_point);
  90. if ($distance <= $m->{hex_width}) {
  91. $paths[-1]->append_polyline($path);
  92. next;
  93. }
  94. }
  95. # make a clone before $collection goes out of scope
  96. push @paths, $path->clone;
  97. }
  98. }
  99. # clip paths again to prevent connection segments from crossing the expolygon boundaries
  100. @paths = @{intersection_pl(
  101. \@paths,
  102. [ map @$_, @{$surface->expolygon->offset_ex(scaled_epsilon)} ],
  103. )};
  104. }
  105. return @paths;
  106. }
  107. 1;