ExtrusionPath.pm 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. package Slic3r::ExtrusionPath;
  2. use Moo;
  3. require Exporter;
  4. our @ISA = qw(Exporter);
  5. our @EXPORT_OK = qw(EXTR_ROLE_PERIMETER EXTR_ROLE_EXTERNAL_PERIMETER
  6. EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER
  7. EXTR_ROLE_FILL EXTR_ROLE_SOLIDFILL EXTR_ROLE_TOPSOLIDFILL EXTR_ROLE_BRIDGE
  8. EXTR_ROLE_INTERNALBRIDGE EXTR_ROLE_SKIRT EXTR_ROLE_SUPPORTMATERIAL EXTR_ROLE_GAPFILL);
  9. our %EXPORT_TAGS = (roles => \@EXPORT_OK);
  10. use Slic3r::Geometry qw(PI X Y epsilon deg2rad rotate_points);
  11. # the underlying Slic3r::Polyline objects holds the geometry
  12. has 'polyline' => (
  13. is => 'rw',
  14. required => 1,
  15. handles => [qw(merge_continuous_lines lines length reverse clip_end)],
  16. );
  17. # height is the vertical thickness of the extrusion expressed in mm
  18. has 'height' => (is => 'rw');
  19. has 'flow_spacing' => (is => 'rw', required => 1);
  20. has 'role' => (is => 'rw', required => 1);
  21. use constant EXTR_ROLE_PERIMETER => 0;
  22. use constant EXTR_ROLE_EXTERNAL_PERIMETER => 2;
  23. use constant EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER => 3;
  24. use constant EXTR_ROLE_FILL => 4;
  25. use constant EXTR_ROLE_SOLIDFILL => 5;
  26. use constant EXTR_ROLE_TOPSOLIDFILL => 6;
  27. use constant EXTR_ROLE_BRIDGE => 7;
  28. use constant EXTR_ROLE_INTERNALBRIDGE => 8;
  29. use constant EXTR_ROLE_SKIRT => 9;
  30. use constant EXTR_ROLE_SUPPORTMATERIAL => 10;
  31. use constant EXTR_ROLE_GAPFILL => 11;
  32. use constant PACK_FMT => 'ffca*';
  33. # class or object method
  34. sub pack {
  35. my $self = shift;
  36. my %args = @_;
  37. if (ref $self) {
  38. %args = map { $_ => $self->$_ } qw(height flow_spacing role polyline);
  39. }
  40. my $o = \ pack PACK_FMT,
  41. $args{height} // -1,
  42. $args{flow_spacing} || -1,
  43. $args{role} // (die "Missing mandatory attribute 'role'"), #/
  44. $args{polyline}->serialize;
  45. bless $o, 'Slic3r::ExtrusionPath::Packed';
  46. return $o;
  47. }
  48. # no-op, this allows to use both packed and non-packed objects in Collections
  49. sub unpack { $_[0] }
  50. sub clip_with_polygon {
  51. my $self = shift;
  52. my ($polygon) = @_;
  53. return $self->clip_with_expolygon(Slic3r::ExPolygon->new($polygon));
  54. }
  55. sub clip_with_expolygon {
  56. my $self = shift;
  57. my ($expolygon) = @_;
  58. my @paths = ();
  59. foreach my $polyline ($self->polyline->clip_with_expolygon($expolygon)) {
  60. push @paths, (ref $self)->new(
  61. polyline => $polyline,
  62. height => $self->height,
  63. flow_spacing => $self->flow_spacing,
  64. role => $self->role,
  65. );
  66. }
  67. return @paths;
  68. }
  69. sub simplify {
  70. my $self = shift;
  71. $self->polyline($self->polyline->simplify(@_));
  72. }
  73. sub points {
  74. my $self = shift;
  75. return $self->polyline;
  76. }
  77. sub first_point {
  78. my $self = shift;
  79. return $self->polyline->[0];
  80. }
  81. sub is_perimeter {
  82. my $self = shift;
  83. return $self->role == EXTR_ROLE_PERIMETER
  84. || $self->role == EXTR_ROLE_EXTERNAL_PERIMETER
  85. || $self->role == EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER;
  86. }
  87. sub is_fill {
  88. my $self = shift;
  89. return $self->role == EXTR_ROLE_FILL
  90. || $self->role == EXTR_ROLE_SOLIDFILL
  91. || $self->role == EXTR_ROLE_TOPSOLIDFILL;
  92. }
  93. sub is_bridge {
  94. my $self = shift;
  95. return $self->role == EXTR_ROLE_BRIDGE
  96. || $self->role == EXTR_ROLE_INTERNALBRIDGE;
  97. }
  98. sub split_at_acute_angles {
  99. my $self = shift;
  100. # calculate angle limit
  101. my $angle_limit = abs(Slic3r::Geometry::deg2rad(40));
  102. my @points = @{$self->p};
  103. my @paths = ();
  104. # take first two points
  105. my @p = splice @points, 0, 2;
  106. # loop until we have one spare point
  107. while (my $p3 = shift @points) {
  108. my $angle = abs(Slic3r::Geometry::angle3points($p[-1], $p[-2], $p3));
  109. $angle = 2*PI - $angle if $angle > PI;
  110. if ($angle < $angle_limit) {
  111. # if the angle between $p[-2], $p[-1], $p3 is too acute
  112. # then consider $p3 only as a starting point of a new
  113. # path and stop the current one as it is
  114. push @paths, (ref $self)->new(
  115. polyline => Slic3r::Polyline->new(\@p),
  116. role => $self->role,
  117. height => $self->height,
  118. );
  119. @p = ($p3);
  120. push @p, grep $_, shift @points or last;
  121. } else {
  122. push @p, $p3;
  123. }
  124. }
  125. push @paths, (ref $self)->new(
  126. polyline => Slic3r::Polyline->new(\@p),
  127. role => $self->role,
  128. height => $self->height,
  129. ) if @p > 1;
  130. return @paths;
  131. }
  132. sub detect_arcs {
  133. my $self = shift;
  134. my ($max_angle, $len_epsilon) = @_;
  135. $max_angle = deg2rad($max_angle || 15);
  136. $len_epsilon ||= 10 / &Slic3r::SCALING_FACTOR;
  137. my $parallel_degrees_limit = abs(Slic3r::Geometry::deg2rad(3));
  138. my @points = @{$self->points};
  139. my @paths = ();
  140. # we require at least 3 consecutive segments to form an arc
  141. CYCLE: while (@points >= 4) {
  142. POINT: for (my $i = 0; $i <= $#points - 3; $i++) {
  143. my $s1 = Slic3r::Line->new($points[$i], $points[$i+1]);
  144. my $s2 = Slic3r::Line->new($points[$i+1], $points[$i+2]);
  145. my $s3 = Slic3r::Line->new($points[$i+2], $points[$i+3]);
  146. my $s1_len = $s1->length;
  147. my $s2_len = $s2->length;
  148. my $s3_len = $s3->length;
  149. # segments must have the same length
  150. if (abs($s3_len - $s2_len) > $len_epsilon) {
  151. # optimization: skip a cycle
  152. $i++;
  153. next;
  154. }
  155. next if abs($s2_len - $s1_len) > $len_epsilon;
  156. # segments must have the same relative angle
  157. my $s1_angle = $s1->atan;
  158. my $s2_angle = $s2->atan;
  159. my $s3_angle = $s3->atan;
  160. $s1_angle += 2*PI if $s1_angle < 0;
  161. $s2_angle += 2*PI if $s2_angle < 0;
  162. $s3_angle += 2*PI if $s3_angle < 0;
  163. my $s1s2_angle = $s2_angle - $s1_angle;
  164. my $s2s3_angle = $s3_angle - $s2_angle;
  165. next if abs($s1s2_angle - $s2s3_angle) > $parallel_degrees_limit;
  166. next if abs($s1s2_angle) < $parallel_degrees_limit; # ignore parallel lines
  167. next if $s1s2_angle > $max_angle; # ignore too sharp vertices
  168. my @arc_points = ($points[$i], $points[$i+3]), # first and last points
  169. # now look for more points
  170. my $last_line_angle = $s3_angle;
  171. my $last_j = $i+3;
  172. for (my $j = $i+3; $j < $#points; $j++) {
  173. my $line = Slic3r::Line->new($points[$j], $points[$j+1]);
  174. last if abs($line->length - $s1_len) > $len_epsilon;
  175. my $line_angle = $line->atan;
  176. $line_angle += 2*PI if $line_angle < 0;
  177. my $anglediff = $line_angle - $last_line_angle;
  178. last if abs($s1s2_angle - $anglediff) > $parallel_degrees_limit;
  179. # point $j+1 belongs to the arc
  180. $arc_points[-1] = $points[$j+1];
  181. $last_j = $j+1;
  182. $last_line_angle = $line_angle;
  183. }
  184. # s1, s2, s3 form an arc
  185. my $orientation = $s1->point_on_left($points[$i+2]) ? 'ccw' : 'cw';
  186. # to find the center, we intersect the perpendicular lines
  187. # passing by midpoints of $s1 and last segment
  188. # a better method would be to draw all the perpendicular lines
  189. # and find the centroid of the enclosed polygon, or to
  190. # intersect multiple lines and find the centroid of the convex hull
  191. # around the intersections
  192. my $arc_center;
  193. {
  194. my $s1_mid = $s1->midpoint;
  195. my $last_mid = Slic3r::Line->new($points[$last_j-1], $points[$last_j])->midpoint;
  196. my $rotation_angle = PI/2 * ($orientation eq 'ccw' ? -1 : 1);
  197. my $ray1 = Slic3r::Line->new($s1_mid, rotate_points($rotation_angle, $s1_mid, $points[$i+1]));
  198. my $last_ray = Slic3r::Line->new($last_mid, rotate_points($rotation_angle, $last_mid, $points[$last_j]));
  199. $arc_center = $ray1->intersection($last_ray, 0) or next POINT;
  200. }
  201. my $arc = Slic3r::ExtrusionPath::Arc->new(
  202. polyline => Slic3r::Polyline->new(\@arc_points),
  203. role => $self->role,
  204. flow_spacing => $self->flow_spacing,
  205. orientation => $orientation,
  206. center => $arc_center,
  207. radius => $arc_center->distance_to($points[$i]),
  208. );
  209. # points 0..$i form a linear path
  210. push @paths, (ref $self)->new(
  211. polyline => Slic3r::Polyline->new(@points[0..$i]),
  212. role => $self->role,
  213. flow_spacing => $self->flow_spacing,
  214. height => $self->height,
  215. ) if $i > 0;
  216. # add our arc
  217. push @paths, $arc;
  218. Slic3r::debugf "ARC DETECTED\n";
  219. # remove arc points from path, leaving one
  220. splice @points, 0, $last_j, ();
  221. next CYCLE;
  222. }
  223. last;
  224. }
  225. # remaining points form a linear path
  226. push @paths, (ref $self)->new(
  227. polyline => Slic3r::Polyline->new(\@points),
  228. role => $self->role,
  229. flow_spacing => $self->flow_spacing,
  230. height => $self->height,
  231. ) if @points > 1;
  232. return @paths;
  233. }
  234. package Slic3r::ExtrusionPath::Packed;
  235. sub unpack {
  236. my $self = shift;
  237. my ($height, $flow_spacing, $role, $polyline_s)
  238. = unpack Slic3r::ExtrusionPath::PACK_FMT, $$self;
  239. return Slic3r::ExtrusionPath->new(
  240. height => ($height == -1) ? undef : $height,
  241. flow_spacing => ($flow_spacing == -1) ? undef : $flow_spacing,
  242. role => $role,
  243. polyline => Slic3r::Polyline->deserialize($polyline_s),
  244. );
  245. }
  246. 1;