MotionPlanner.pm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. package Slic3r::GCode::MotionPlanner;
  2. use Moo;
  3. has 'islands' => (is => 'ro', required => 1);
  4. has 'no_internal' => (is => 'ro');
  5. has 'last_crossings'=> (is => 'rw');
  6. has '_inner' => (is => 'rw', default => sub { [] }); # arrayref of arrayrefs of expolygons
  7. has '_outer' => (is => 'rw', default => sub { [] }); # arrayref of arrayrefs of polygons
  8. has '_contours_ex' => (is => 'rw', default => sub { [] }); # arrayref of arrayrefs of expolygons
  9. has '_pointmap' => (is => 'rw', default => sub { {} }); # { id => $point }
  10. has '_edges' => (is => 'rw', default => sub { {} }); # node_idx => { node_idx => distance, ... }
  11. has '_crossing_edges' => (is => 'rw', default => sub { {} }); # edge_idx => bool
  12. has '_tolerance' => (is => 'lazy');
  13. use List::Util qw(first);
  14. use Slic3r::Geometry qw(A B scale epsilon);
  15. use Slic3r::Geometry::Clipper qw(diff_ex offset JT_MITER);
  16. # clearance (in mm) from the perimeters
  17. has '_inner_margin' => (is => 'ro', default => sub { scale 0.5 });
  18. has '_outer_margin' => (is => 'ro', default => sub { scale 2 });
  19. # this factor weigths the crossing of a perimeter
  20. # vs. the alternative path. a value of 5 means that
  21. # a perimeter will be crossed if the alternative path
  22. # is >= 5x the length of the straight line we could
  23. # follow if we decided to cross the perimeter.
  24. # a nearly-infinite value for this will only permit
  25. # perimeter crossing when there's no alternative path.
  26. use constant CROSSING_FACTOR => 20;
  27. use constant INFINITY => 'inf';
  28. sub _build__tolerance { scale epsilon }
  29. # setup our configuration space
  30. sub BUILD {
  31. my $self = shift;
  32. my $edges = $self->_edges;
  33. my $crossing_edges = $self->_crossing_edges;
  34. # simplify islands
  35. @{$self->islands} = map $_->simplify($self->_inner_margin), @{$self->islands};
  36. # process individual islands
  37. for my $i (0 .. $#{$self->islands}) {
  38. # offset the island inwards to make the boundaries for internal movements
  39. # so that no motion along external perimeters happens
  40. $self->_inner->[$i] = $self->no_internal
  41. ? []
  42. : $self->islands->[$i]->offset_ex(-$self->_inner_margin);
  43. # offset the island outwards to make the boundaries for external movements
  44. $self->_outer->[$i] = offset([ $self->islands->[$i]->contour], $self->_outer_margin);
  45. # if internal motion is enabled, build a set of utility expolygons representing
  46. # the outer boundaries (as contours) and the inner boundaries (as holes). whenever
  47. # we jump from a hole to a contour or viceversa, we know we're crossing a perimeter
  48. if (!$self->no_internal) {
  49. $self->_contours_ex->[$i] = diff_ex(
  50. $self->_outer->[$i],
  51. [ map $_->contour, @{$self->_inner->[$i]} ],
  52. );
  53. # lines enclosed in inner expolygons are visible
  54. $self->_add_expolygon($_) for @{ $self->_inner->[$i] };
  55. # lines enclosed in expolygons covering perimeters are visible
  56. # (but discouraged)
  57. $self->_add_expolygon($_, 1) for @{ $self->_contours_ex->[$i] };
  58. }
  59. }
  60. {
  61. my @outer = (map @$_, @{$self->_outer});
  62. my @outer_ex = map [$_], @outer; # as ExPolygons
  63. # lines of outer polygons connect visible points
  64. for my $i (0 .. $#outer) {
  65. foreach my $line ($outer[$i]->lines) {
  66. my $dist = $line->length;
  67. $edges->{$line->[A]}{$line->[B]} = $dist;
  68. $edges->{$line->[B]}{$line->[A]} = $dist;
  69. }
  70. }
  71. # lines connecting outer polygons are visible
  72. for my $i (0 .. $#outer) {
  73. for my $j (($i+1) .. $#outer) {
  74. for my $m (0 .. $#{$outer[$i]}) {
  75. for my $n (0 .. $#{$outer[$j]}) {
  76. my $line = Slic3r::Line->new($outer[$i][$m], $outer[$j][$n]);
  77. if (!@{Boost::Geometry::Utils::multi_polygon_multi_linestring_intersection(\@outer_ex, [$line])}) {
  78. # this line does not cross any polygon
  79. my $dist = $line->length;
  80. $edges->{$outer[$i][$m]}{$outer[$j][$n]} = $dist;
  81. $edges->{$outer[$j][$n]}{$outer[$i][$m]} = $dist;
  82. }
  83. }
  84. }
  85. }
  86. }
  87. }
  88. # lines connecting inner polygons contours are visible but discouraged
  89. if (!$self->no_internal) {
  90. my @inner = (map $_->contour, map @$_, @{$self->_inner});
  91. my @inner_ex = map [$_], @inner; # as ExPolygons
  92. for my $i (0 .. $#inner) {
  93. for my $j (($i+1) .. $#inner) {
  94. for my $m (0 .. $#{$inner[$i]}) {
  95. for my $n (0 .. $#{$inner[$j]}) {
  96. my $line = Slic3r::Line->new($inner[$i][$m], $inner[$j][$n]);
  97. if (!@{Boost::Geometry::Utils::multi_polygon_multi_linestring_intersection(\@inner_ex, [$line])}) {
  98. # this line does not cross any polygon
  99. my $dist = $line->length * CROSSING_FACTOR;
  100. $edges->{$inner[$i][$m]}{$inner[$j][$n]} = $dist;
  101. $edges->{$inner[$j][$n]}{$inner[$i][$m]} = $dist;
  102. $crossing_edges->{$inner[$i][$m]}{$inner[$j][$n]} = 1;
  103. $crossing_edges->{$inner[$j][$n]}{$inner[$i][$m]} = 1;
  104. }
  105. }
  106. }
  107. }
  108. }
  109. }
  110. $self->_pointmap({
  111. map +("$_" => $_),
  112. (map @$_, map @$_, map @$_, @{$self->_inner}),
  113. (map @$_, map @$_, @{$self->_outer}),
  114. (map @$_, map @$_, map @$_, @{$self->_contours_ex}),
  115. });
  116. if (0) {
  117. my @lines = ();
  118. my %lines = ();
  119. for my $i (keys %{$self->_edges}) {
  120. for my $j (keys %{$self->_edges->{$i}}) {
  121. next if $lines{join '_', sort $i, $j};
  122. push @lines, [ map $self->_pointmap->{$_}, $i, $j ];
  123. $lines{join '_', sort $i, $j} = 1;
  124. }
  125. }
  126. require "Slic3r/SVG.pm";
  127. Slic3r::SVG::output("space.svg",
  128. lines => \@lines,
  129. points => [ values %{$self->_pointmap} ],
  130. no_arrows => 1,
  131. expolygons => $self->islands,
  132. #red_polygons => [ map @{$_->holes}, map @$_, @{$self->_inner} ],
  133. #white_polygons => [ map @$_, @{$self->_outer} ],
  134. );
  135. printf "%d islands\n", scalar @{$self->islands};
  136. eval "use Devel::Size";
  137. print "MEMORY USAGE:\n";
  138. printf " %-19s = %.1fMb\n", $_, Devel::Size::total_size($self->$_)/1024/1024
  139. for qw(_inner _outer _contours_ex _pointmap _edges _crossing_edges islands last_crossings);
  140. printf " %-19s = %.1fMb\n", 'self', Devel::Size::total_size($self)/1024/1024;
  141. }
  142. }
  143. # given an expolygon, this subroutine connects all its visible points
  144. sub _add_expolygon {
  145. my $self = shift;
  146. my ($expolygon, $crosses_perimeter) = @_;
  147. my $edges = $self->_edges;
  148. my $crossing_edges = $self->_crossing_edges;
  149. my @points = map @$_, @$expolygon;
  150. for my $i (0 .. $#points) {
  151. for my $j (($i+1) .. $#points) {
  152. my $line = Slic3r::Line->new($points[$i], $points[$j]);
  153. if ($expolygon->encloses_line($line, $self->_tolerance)) {
  154. my $dist = $line->length * ($crosses_perimeter ? CROSSING_FACTOR : 1);
  155. $edges->{$points[$i]}{$points[$j]} = $dist;
  156. $edges->{$points[$j]}{$points[$i]} = $dist;
  157. $crossing_edges->{$points[$i]}{$points[$j]} = 1;
  158. $crossing_edges->{$points[$j]}{$points[$i]} = 1;
  159. }
  160. }
  161. }
  162. }
  163. sub find_node {
  164. my $self = shift;
  165. my ($point, $near_to) = @_;
  166. # for optimal pathing, we should check visibility from $point to all $candidates, and then
  167. # choose the one that is nearest to $near_to among the visible ones; however this is probably too slow
  168. # if we're inside a hole, move to a point on hole;
  169. {
  170. my $polygon = first { $_->encloses_point($point) } (map @{$_->holes}, map @$_, @{$self->_inner});
  171. return $point->nearest_point([ @$polygon ]) if $polygon;
  172. }
  173. # if we're inside an expolygon move to a point on contour or holes
  174. {
  175. my $expolygon = first { $_->encloses_point_quick($point) } (map @$_, @{$self->_inner});
  176. return $point->nearest_point([ map @$_, @$expolygon ]) if $expolygon;
  177. }
  178. {
  179. my $outer_polygon_idx;
  180. if (!$self->no_internal) {
  181. # look for an outer expolygon whose contour contains our point
  182. $outer_polygon_idx = first { first { $_->contour->encloses_point($point) } @{$self->_contours_ex->[$_]} }
  183. 0 .. $#{ $self->_contours_ex };
  184. } else {
  185. # # look for an outer expolygon containing our point
  186. $outer_polygon_idx = first { first { $_->encloses_point($point) } @{$self->_outer->[$_]} }
  187. 0 .. $#{ $self->_outer };
  188. }
  189. my $candidates = defined $outer_polygon_idx
  190. ? [ map @{$_->contour}, @{$self->_inner->[$outer_polygon_idx]} ]
  191. : [ map @$_, map @$_, @{$self->_outer} ];
  192. $candidates = [ map @$_, @{$self->_outer->[$outer_polygon_idx]} ]
  193. if @$candidates == 0;
  194. return $point->nearest_point($candidates);
  195. }
  196. }
  197. sub shortest_path {
  198. my $self = shift;
  199. my ($from, $to) = @_;
  200. return Slic3r::Polyline->new($from, $to) if !@{$self->islands};
  201. # find nearest nodes
  202. my $new_from = $self->find_node($from, $to);
  203. my $new_to = $self->find_node($to, $from);
  204. my $root = "$new_from";
  205. my $target = "$new_to";
  206. my $edges = $self->_edges;
  207. my %dist = map { $_ => INFINITY } keys %$edges;
  208. $dist{$root} = 0;
  209. my %prev = map { $_ => undef } keys %$edges;
  210. my @unsolved = keys %$edges;
  211. my %crossings = (); # node_idx => bool
  212. while (@unsolved) {
  213. # sort unsolved by distance from root
  214. # using a sorting option that accounts for infinity
  215. @unsolved = sort {
  216. $dist{$a} eq INFINITY ? +1 :
  217. $dist{$b} eq INFINITY ? -1 :
  218. $dist{$a} <=> $dist{$b};
  219. } @unsolved;
  220. # we'll solve the closest node
  221. last if $dist{$unsolved[0]} eq INFINITY;
  222. my $n = shift @unsolved;
  223. # stop search
  224. last if $n eq $target;
  225. # now, look at all the nodes connected to n
  226. foreach my $n2 (keys %{$edges->{$n}}) {
  227. # .. and find out if any of their estimated distances
  228. # can be improved if we go through n
  229. if ( ($dist{$n2} eq INFINITY) || ($dist{$n2} > ($dist{$n} + $edges->{$n}{$n2})) ) {
  230. $dist{$n2} = $dist{$n} + $edges->{$n}{$n2};
  231. $prev{$n2} = $n;
  232. $crossings{$n} = 1 if $self->_crossing_edges->{$n}{$n2};
  233. }
  234. }
  235. }
  236. my @points = ();
  237. my $crossings = 0;
  238. {
  239. my $pointmap = $self->_pointmap;
  240. my $u = $target;
  241. while (defined $prev{$u}) {
  242. unshift @points, $pointmap->{$u};
  243. $crossings++ if $crossings{$u};
  244. $u = $prev{$u};
  245. }
  246. }
  247. $self->last_crossings($crossings);
  248. return Slic3r::Polyline->new($from, $new_from, @points, $to); # @points already includes $new_to
  249. }
  250. 1;