Region.pm 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. package Slic3r::Layer::Region;
  2. use Moo;
  3. use Slic3r::ExtrusionPath ':roles';
  4. use Slic3r::Geometry qw(PI scale chained_path_items points_coincide);
  5. use Slic3r::Geometry::Clipper qw(safety_offset union_ex diff_ex intersection_ex);
  6. use Slic3r::Surface ':types';
  7. has 'layer' => (
  8. is => 'ro',
  9. weak_ref => 1,
  10. required => 1,
  11. trigger => 1,
  12. handles => [qw(id slice_z print_z height flow)],
  13. );
  14. has 'region' => (is => 'ro', required => 1, handles => [qw(extruders)]);
  15. has 'perimeter_flow' => (is => 'rw');
  16. has 'infill_flow' => (is => 'rw');
  17. has 'infill_area_threshold' => (is => 'lazy');
  18. has 'overhang_width' => (is => 'lazy');
  19. # collection of spare segments generated by slicing the original geometry;
  20. # these need to be merged in continuos (closed) polylines
  21. has 'lines' => (is => 'rw', default => sub { [] });
  22. # collection of surfaces generated by slicing the original geometry
  23. has 'slices' => (is => 'rw', default => sub { [] });
  24. # collection of polygons or polylines representing thin walls contained
  25. # in the original geometry
  26. has 'thin_walls' => (is => 'rw', default => sub { [] });
  27. # collection of polygons or polylines representing thin infill regions that
  28. # need to be filled with a medial axis
  29. has 'thin_fills' => (is => 'rw', default => sub { [] });
  30. # collection of surfaces for infill generation
  31. has 'fill_surfaces' => (is => 'rw', default => sub { [] });
  32. # ordered collection of extrusion paths/loops to build all perimeters
  33. has 'perimeters' => (is => 'rw', default => sub { [] });
  34. # ordered collection of extrusion paths to fill surfaces
  35. has 'fills' => (is => 'rw', default => sub { [] });
  36. sub BUILD {
  37. my $self = shift;
  38. $self->_update_flows;
  39. }
  40. sub _trigger_layer {
  41. my $self = shift;
  42. $self->_update_flows;
  43. }
  44. sub _update_flows {
  45. my $self = shift;
  46. return if !$self->region;
  47. if ($self->id == 0) {
  48. $self->perimeter_flow
  49. ($self->region->first_layer_flows->{perimeter} || $self->region->flows->{perimeter});
  50. $self->infill_flow
  51. ($self->region->first_layer_flows->{infill} || $self->region->flows->{infill});
  52. } else {
  53. $self->perimeter_flow($self->region->flows->{perimeter});
  54. $self->infill_flow($self->region->flows->{infill});
  55. }
  56. }
  57. sub _build_overhang_width {
  58. my $self = shift;
  59. my $threshold_rad = PI/2 - atan2($self->perimeter_flow->width / $self->height / 2, 1);
  60. return scale($self->height * ((cos $threshold_rad) / (sin $threshold_rad)));
  61. }
  62. sub _build_infill_area_threshold {
  63. my $self = shift;
  64. return $self->infill_flow->scaled_spacing ** 2;
  65. }
  66. # build polylines from lines
  67. sub make_surfaces {
  68. my $self = shift;
  69. my ($loops) = @_;
  70. return if !@$loops;
  71. $self->slices([ _merge_loops($loops) ]);
  72. # detect thin walls by offsetting slices by half extrusion inwards
  73. {
  74. my $width = $self->perimeter_flow->scaled_width;
  75. my $outgrown = union_ex([
  76. Slic3r::Geometry::Clipper::offset(
  77. [Slic3r::Geometry::Clipper::offset([ map @$_, map $_->expolygon, @{$self->slices} ], -$width)],
  78. +$width,
  79. ),
  80. ]);
  81. my $diff = diff_ex(
  82. [ map $_->p, @{$self->slices} ],
  83. [ map @$_, @$outgrown ],
  84. 1,
  85. );
  86. $self->thin_walls([]);
  87. if (@$diff) {
  88. my $area_threshold = $self->perimeter_flow->scaled_spacing ** 2;
  89. @$diff = grep $_->area > ($area_threshold), @$diff;
  90. @{$self->thin_walls} = map $_->medial_axis($self->perimeter_flow->scaled_width), @$diff;
  91. Slic3r::debugf " %d thin walls detected\n", scalar(@{$self->thin_walls}) if @{$self->thin_walls};
  92. }
  93. }
  94. if (0) {
  95. require "Slic3r/SVG.pm";
  96. Slic3r::SVG::output("surfaces.svg",
  97. polygons => [ map $_->contour, @{$self->slices} ],
  98. red_polygons => [ map $_->p, map @{$_->holes}, @{$self->slices} ],
  99. );
  100. }
  101. }
  102. sub _merge_loops {
  103. my ($loops, $safety_offset) = @_;
  104. # Input loops are not suitable for evenodd nor nonzero fill types, as we might get
  105. # two consecutive concentric loops having the same winding order - and we have to
  106. # respect such order. In that case, evenodd would create wrong inversions, and nonzero
  107. # would ignore holes inside two concentric contours.
  108. # So we're ordering loops and collapse consecutive concentric loops having the same
  109. # winding order.
  110. # TODO: find a faster algorithm for this.
  111. my @loops = sort { $a->encloses_point($b->[0]) ? 0 : 1 } @$loops; # outer first
  112. $safety_offset //= scale 0.1;
  113. @loops = @{ safety_offset(\@loops, $safety_offset) };
  114. my $expolygons = [];
  115. while (my $loop = shift @loops) {
  116. bless $loop, 'Slic3r::Polygon';
  117. if ($loop->is_counter_clockwise) {
  118. $expolygons = union_ex([ $loop, map @$_, @$expolygons ]);
  119. } else {
  120. $expolygons = diff_ex([ map @$_, @$expolygons ], [$loop]);
  121. }
  122. }
  123. $expolygons = [ map $_->offset_ex(-$safety_offset), @$expolygons ];
  124. Slic3r::debugf " %d surface(s) having %d holes detected from %d polylines\n",
  125. scalar(@$expolygons), scalar(map $_->holes, @$expolygons), scalar(@$loops);
  126. return map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL), @$expolygons;
  127. }
  128. sub make_perimeters {
  129. my $self = shift;
  130. my $perimeter_spacing = $self->perimeter_flow->scaled_spacing;
  131. my $infill_spacing = $self->infill_flow->scaled_spacing;
  132. my $gap_area_threshold = $self->perimeter_flow->scaled_width ** 2;
  133. # this array will hold one arrayref per original surface (island);
  134. # each item of this arrayref is an arrayref representing a depth (from outer
  135. # perimeters to inner); each item of this arrayref is an ExPolygon:
  136. # @perimeters = (
  137. # [ # first island
  138. # [ Slic3r::ExPolygon, Slic3r::ExPolygon... ], #depth 0: outer loop
  139. # [ Slic3r::ExPolygon, Slic3r::ExPolygon... ], #depth 1: inner loop
  140. # ],
  141. # [ # second island
  142. # ...
  143. # ]
  144. # )
  145. my @perimeters = (); # one item per depth; each item
  146. # organize islands using a nearest-neighbor search
  147. my @surfaces = @{chained_path_items([
  148. map [ $_->contour->[0], $_ ], @{$self->slices},
  149. ])};
  150. $self->perimeters([]);
  151. $self->fill_surfaces([]);
  152. $self->thin_fills([]);
  153. # for each island:
  154. foreach my $surface (@surfaces) {
  155. my @last_offsets = ($surface->expolygon);
  156. # experimental hole compensation (see ArcCompensation in the RepRap wiki)
  157. if (0) {
  158. foreach my $hole ($last_offsets[0]->holes) {
  159. my $circumference = abs($hole->length);
  160. next unless $circumference <= &Slic3r::SMALL_PERIMETER_LENGTH;
  161. # this compensation only works for circular holes, while it would
  162. # overcompensate for hexagons and other shapes having straight edges.
  163. # so we require a minimum number of vertices.
  164. next unless $circumference / @$hole >= 3 * $self->perimeter_flow->scaled_width;
  165. # revert the compensation done in make_surfaces() and get the actual radius
  166. # of the hole
  167. my $radius = ($circumference / PI / 2) - $self->perimeter_flow->scaled_spacing/2;
  168. my $new_radius = ($self->perimeter_flow->scaled_width + sqrt(($self->perimeter_flow->scaled_width ** 2) + (4*($radius**2)))) / 2;
  169. # holes are always turned to contours, so reverse point order before and after
  170. $hole->reverse;
  171. my @offsetted = $hole->offset(+ ($new_radius - $radius));
  172. # skip arc compensation when hole is not round (thus leads to multiple offsets)
  173. @$hole = map Slic3r::Point->new($_), @{ $offsetted[0] } if @offsetted == 1;
  174. $hole->reverse;
  175. }
  176. }
  177. my @gaps = ();
  178. # generate perimeters inwards (loop 0 is the external one)
  179. my $loop_number = $Slic3r::Config->perimeters + ($surface->additional_inner_perimeters || 0);
  180. push @perimeters, [] if $loop_number > 0;
  181. # do one more loop (<= instead of <) so that we can detect gaps even after the desired
  182. # number of perimeters has been generated
  183. for (my $loop = 0; $loop <= $loop_number; $loop++) {
  184. my $spacing = $perimeter_spacing;
  185. $spacing /= 2 if $loop == 0;
  186. # offsetting a polygon can result in one or many offset polygons
  187. my @new_offsets = ();
  188. foreach my $expolygon (@last_offsets) {
  189. my @offsets = @{union_ex([
  190. Slic3r::Geometry::Clipper::offset(
  191. [Slic3r::Geometry::Clipper::offset($expolygon, -1.5*$spacing)],
  192. +0.5*$spacing,
  193. ),
  194. ])};
  195. push @new_offsets, @offsets;
  196. # where the above check collapses the expolygon, then there's no room for an inner loop
  197. # and we can extract the gap for later processing
  198. my $diff = diff_ex(
  199. [ map @$_, $expolygon->offset_ex(-0.5*$spacing) ],
  200. # +2 on the offset here makes sure that Clipper float truncation
  201. # won't shrink the clip polygon to be smaller than intended.
  202. [ Slic3r::Geometry::Clipper::offset([map @$_, @offsets], +0.5*$spacing + 2) ],
  203. );
  204. push @gaps, grep $_->area >= $gap_area_threshold, @$diff;
  205. }
  206. last if !@new_offsets || $loop == $loop_number;
  207. @last_offsets = @new_offsets;
  208. # sort loops before storing them
  209. @last_offsets = @{chained_path_items([
  210. map [ $_->contour->[0], $_ ], @last_offsets,
  211. ])};
  212. push @{ $perimeters[-1] }, [@last_offsets];
  213. }
  214. # create one more offset to be used as boundary for fill
  215. {
  216. # we offset by half the perimeter spacing (to get to the actual infill boundary)
  217. # and then we offset back and forth by the infill spacing to only consider the
  218. # non-collapsing regions
  219. my @fill_boundaries = @{union_ex([
  220. Slic3r::Geometry::Clipper::offset(
  221. [Slic3r::Geometry::Clipper::offset([ map @$_, @last_offsets ], -($perimeter_spacing/2 + $infill_spacing))],
  222. +$infill_spacing,
  223. ),
  224. ])};
  225. $_->simplify(&Slic3r::SCALED_RESOLUTION) for @fill_boundaries;
  226. push @{ $self->fill_surfaces }, @fill_boundaries;
  227. }
  228. # fill gaps
  229. if ($Slic3r::Config->gap_fill_speed > 0 && $Slic3r::Config->fill_density > 0) {
  230. my $filler = Slic3r::Fill::Rectilinear->new(layer_id => $self->layer->id);
  231. my $w = $self->perimeter_flow->width;
  232. my @widths = (1.5 * $w, $w, 0.5 * $w); # worth trying 0.2 too?
  233. foreach my $width (@widths) {
  234. my $flow = $self->perimeter_flow->clone(width => $width);
  235. # extract the gaps having this width
  236. my @this_width = map $_->offset_ex(+0.5*$flow->scaled_width),
  237. map $_->noncollapsing_offset_ex(-0.5*$flow->scaled_width),
  238. @gaps;
  239. if (0) { # remember to re-enable t/dynamic.t
  240. # fill gaps using dynamic extrusion width, by treating them like thin polygons,
  241. # thus generating the skeleton and using it to fill them
  242. my %path_args = (
  243. role => EXTR_ROLE_SOLIDFILL,
  244. flow_spacing => $flow->spacing,
  245. );
  246. push @{ $self->thin_fills }, map {
  247. $_->isa('Slic3r::Polygon')
  248. ? (map $_->pack, Slic3r::ExtrusionLoop->new(polygon => $_, %path_args)->split_at_first_point) # we should keep these as loops
  249. : Slic3r::ExtrusionPath->pack(polyline => $_, %path_args),
  250. } map $_->medial_axis($flow->scaled_width), @this_width;
  251. Slic3r::debugf " %d gaps filled with extrusion width = %s\n", scalar @this_width, $width
  252. if @{ $self->thin_fills };
  253. } else {
  254. # fill gaps using zigzag infill
  255. # since this is infill, we have to offset by half-extrusion width inwards
  256. my @infill = map $_->offset_ex(-0.5*$flow->scaled_width), @this_width;
  257. foreach my $expolygon (@infill) {
  258. my @paths = $filler->fill_surface(
  259. Slic3r::Surface->new(expolygon => $expolygon),
  260. density => 1,
  261. flow_spacing => $flow->spacing,
  262. );
  263. my $params = shift @paths;
  264. push @{ $self->thin_fills },
  265. map {
  266. $_->polyline->simplify($flow->scaled_width / 3);
  267. $_->pack;
  268. }
  269. map Slic3r::ExtrusionPath->new(
  270. polyline => Slic3r::Polyline->new(@$_),
  271. role => EXTR_ROLE_GAPFILL,
  272. height => $self->height,
  273. flow_spacing => $params->{flow_spacing},
  274. ), @paths;
  275. }
  276. }
  277. # check what's left
  278. @gaps = @{diff_ex(
  279. [ map @$_, @gaps ],
  280. [ map @$_, @this_width ],
  281. )};
  282. }
  283. }
  284. }
  285. # process one island (original surface) at time
  286. # islands are already sorted with a nearest-neighbor search
  287. foreach my $island (@perimeters) {
  288. # do holes starting from innermost one
  289. my @holes = ();
  290. my %is_external = ();
  291. # each item of @$island contains the expolygons having the same depth;
  292. # for each depth we build an arrayref containing all the holes
  293. my @hole_depths = map [ map $_->holes, @$_ ], @$island;
  294. # organize the outermost hole loops using a nearest-neighbor search
  295. @{$hole_depths[0]} = @{chained_path_items([
  296. map [ $_->[0], $_ ], @{$hole_depths[0]},
  297. ])};
  298. # loop while we have spare holes
  299. CYCLE: while (map @$_, @hole_depths) {
  300. # remove first depth container if it contains no holes anymore
  301. shift @hole_depths while !@{$hole_depths[0]};
  302. # take first available hole
  303. push @holes, shift @{$hole_depths[0]};
  304. $is_external{$#holes} = 1;
  305. my $current_depth = 0;
  306. while (1) {
  307. $current_depth++;
  308. # look for the hole containing this one if any
  309. next CYCLE if !$hole_depths[$current_depth];
  310. my $parent_hole;
  311. for (@{$hole_depths[$current_depth]}) {
  312. if ($_->encloses_point($holes[-1]->[0])) {
  313. $parent_hole = $_;
  314. last;
  315. }
  316. }
  317. next CYCLE if !$parent_hole;
  318. # look for other holes contained in such parent
  319. for (@{$hole_depths[$current_depth-1]}) {
  320. if ($parent_hole->encloses_point($_->[0])) {
  321. # we have a sibling, so let's move onto next iteration
  322. next CYCLE;
  323. }
  324. }
  325. push @holes, $parent_hole;
  326. @{$hole_depths[$current_depth]} = grep $_ ne $parent_hole, @{$hole_depths[$current_depth]};
  327. }
  328. }
  329. # do holes, then contours starting from innermost one
  330. $self->_add_perimeter($holes[$_], $is_external{$_} ? EXTR_ROLE_EXTERNAL_PERIMETER : undef)
  331. for reverse 0 .. $#holes;
  332. for my $depth (reverse 0 .. $#$island) {
  333. my $role = $depth == $#$island ? EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER
  334. : $depth == 0 ? EXTR_ROLE_EXTERNAL_PERIMETER
  335. : EXTR_ROLE_PERIMETER;
  336. $self->_add_perimeter($_, $role) for map $_->contour, @{$island->[$depth]};
  337. }
  338. }
  339. # if brim will be printed, reverse the order of perimeters so that
  340. # we continue inwards after having finished the brim
  341. if ($self->layer->id == 0 && $Slic3r::Config->brim_width > 0) {
  342. @{$self->perimeters} = reverse @{$self->perimeters};
  343. }
  344. # add thin walls as perimeters
  345. push @{ $self->perimeters }, Slic3r::ExtrusionPath::Collection->new(paths => [
  346. map {
  347. Slic3r::ExtrusionPath->pack(
  348. polyline => ($_->isa('Slic3r::Polygon') ? $_->split_at_first_point : $_),
  349. role => EXTR_ROLE_EXTERNAL_PERIMETER,
  350. flow_spacing => $self->perimeter_flow->spacing,
  351. );
  352. } @{ $self->thin_walls }
  353. ])->chained_path;
  354. }
  355. sub _add_perimeter {
  356. my $self = shift;
  357. my ($polygon, $role) = @_;
  358. return unless $polygon->is_printable($self->perimeter_flow->scaled_width);
  359. push @{ $self->perimeters }, Slic3r::ExtrusionLoop->pack(
  360. polygon => $polygon,
  361. role => ($role // EXTR_ROLE_PERIMETER),
  362. flow_spacing => $self->perimeter_flow->spacing,
  363. );
  364. }
  365. sub prepare_fill_surfaces {
  366. my $self = shift;
  367. # if hollow object is requested, remove internal surfaces
  368. if ($Slic3r::Config->fill_density == 0) {
  369. @{$self->fill_surfaces} = grep $_->surface_type != S_TYPE_INTERNAL, @{$self->fill_surfaces};
  370. }
  371. # if no solid layers are requested, turn top/bottom surfaces to internal
  372. if ($Slic3r::Config->top_solid_layers == 0) {
  373. $_->surface_type(S_TYPE_INTERNAL) for grep $_->surface_type == S_TYPE_TOP, @{$self->fill_surfaces};
  374. }
  375. if ($Slic3r::Config->bottom_solid_layers == 0) {
  376. $_->surface_type(S_TYPE_INTERNAL) for grep $_->surface_type == S_TYPE_BOTTOM, @{$self->fill_surfaces};
  377. }
  378. # turn too small internal regions into solid regions according to the user setting
  379. {
  380. my $min_area = scale scale $Slic3r::Config->solid_infill_below_area; # scaling an area requires two calls!
  381. my @small = grep $_->surface_type == S_TYPE_INTERNAL && $_->expolygon->contour->area <= $min_area, @{$self->fill_surfaces};
  382. $_->surface_type(S_TYPE_INTERNALSOLID) for @small;
  383. Slic3r::debugf "identified %d small solid surfaces at layer %d\n", scalar(@small), $self->id if @small > 0;
  384. }
  385. }
  386. sub process_external_surfaces {
  387. my $self = shift;
  388. # enlarge top and bottom surfaces
  389. {
  390. # get all external surfaces
  391. my @top = grep $_->surface_type == S_TYPE_TOP, @{$self->fill_surfaces};
  392. my @bottom = grep $_->surface_type == S_TYPE_BOTTOM, @{$self->fill_surfaces};
  393. # offset them and intersect the results with the actual fill boundaries
  394. my $margin = scale 3; # TODO: ensure this is greater than the total thickness of the perimeters
  395. @top = @{intersection_ex(
  396. [ Slic3r::Geometry::Clipper::offset([ map $_->p, @top ], +$margin) ],
  397. [ map $_->p, @{$self->fill_surfaces} ],
  398. undef,
  399. 1, # to ensure adjacent expolygons are unified
  400. )};
  401. @bottom = @{intersection_ex(
  402. [ Slic3r::Geometry::Clipper::offset([ map $_->p, @bottom ], +$margin) ],
  403. [ map $_->p, @{$self->fill_surfaces} ],
  404. undef,
  405. 1, # to ensure adjacent expolygons are unified
  406. )};
  407. # give priority to bottom surfaces
  408. @top = @{diff_ex(
  409. [ map @$_, @top ],
  410. [ map @$_, @bottom ],
  411. )};
  412. # generate new surfaces
  413. my @new_surfaces = ();
  414. push @new_surfaces, map Slic3r::Surface->new(
  415. expolygon => $_,
  416. surface_type => S_TYPE_TOP,
  417. ), @top;
  418. push @new_surfaces, map Slic3r::Surface->new(
  419. expolygon => $_,
  420. surface_type => S_TYPE_BOTTOM,
  421. ), @bottom;
  422. # subtract the new top surfaces from the other non-top surfaces and re-add them
  423. my @other = grep $_->surface_type != S_TYPE_TOP && $_->surface_type != S_TYPE_BOTTOM, @{$self->fill_surfaces};
  424. foreach my $group (Slic3r::Surface->group(@other)) {
  425. push @new_surfaces, map Slic3r::Surface->new(
  426. expolygon => $_,
  427. surface_type => $group->[0]->surface_type,
  428. ), @{diff_ex(
  429. [ map $_->p, @$group ],
  430. [ map $_->p, @new_surfaces ],
  431. )};
  432. }
  433. @{$self->fill_surfaces} = @new_surfaces;
  434. }
  435. # detect bridge direction (skip bottom layer)
  436. if ($self->id > 0) {
  437. my @bottom = grep $_->surface_type == S_TYPE_BOTTOM, @{$self->fill_surfaces}; # surfaces
  438. my @lower = @{$self->layer->object->layers->[ $self->id - 1 ]->slices}; # expolygons
  439. foreach my $surface (@bottom) {
  440. # detect what edges lie on lower slices
  441. my @edges = (); # polylines
  442. foreach my $lower (@lower) {
  443. # turn bridge contour and holes into polylines and then clip them
  444. # with each lower slice's contour
  445. my @clipped = map $_->split_at_first_point->clip_with_polygon($lower->contour), @{$surface->expolygon};
  446. if (@clipped == 2) {
  447. # If the split_at_first_point() call above happens to split the polygon inside the clipping area
  448. # we would get two consecutive polylines instead of a single one, so we use this ugly hack to
  449. # recombine them back into a single one in order to trigger the @edges == 2 logic below.
  450. # This needs to be replaced with something way better.
  451. if (points_coincide($clipped[0][0], $clipped[-1][-1])) {
  452. @clipped = (Slic3r::Polyline->new(@{$clipped[-1]}, @{$clipped[0]}));
  453. }
  454. if (points_coincide($clipped[-1][0], $clipped[0][-1])) {
  455. @clipped = (Slic3r::Polyline->new(@{$clipped[0]}, @{$clipped[1]}));
  456. }
  457. }
  458. push @edges, @clipped;
  459. }
  460. Slic3r::debugf "Found bridge on layer %d with %d support(s)\n", $self->id, scalar(@edges);
  461. next if !@edges;
  462. my $bridge_angle = undef;
  463. if (0) {
  464. require "Slic3r/SVG.pm";
  465. Slic3r::SVG::output("bridge.svg",
  466. polygons => [ $surface->p ],
  467. red_polygons => [ map @$_, @lower ],
  468. polylines => [ @edges ],
  469. );
  470. }
  471. if (@edges == 2) {
  472. my @chords = map Slic3r::Line->new($_->[0], $_->[-1]), @edges;
  473. my @midpoints = map $_->midpoint, @chords;
  474. my $line_between_midpoints = Slic3r::Line->new(@midpoints);
  475. $bridge_angle = Slic3r::Geometry::rad2deg_dir($line_between_midpoints->direction);
  476. } elsif (@edges == 1) {
  477. # TODO: this case includes both U-shaped bridges and plain overhangs;
  478. # we need a trapezoidation algorithm to detect the actual bridged area
  479. # and separate it from the overhang area.
  480. # in the mean time, we're treating as overhangs all cases where
  481. # our supporting edge is a straight line
  482. if (@{$edges[0]} > 2) {
  483. my $line = Slic3r::Line->new($edges[0]->[0], $edges[0]->[-1]);
  484. $bridge_angle = Slic3r::Geometry::rad2deg_dir($line->direction);
  485. }
  486. } elsif (@edges) {
  487. my $center = Slic3r::Geometry::bounding_box_center([ map @$_, @edges ]);
  488. my $x = my $y = 0;
  489. foreach my $point (map @$_, @edges) {
  490. my $line = Slic3r::Line->new($center, $point);
  491. my $dir = $line->direction;
  492. my $len = $line->length;
  493. $x += cos($dir) * $len;
  494. $y += sin($dir) * $len;
  495. }
  496. $bridge_angle = Slic3r::Geometry::rad2deg_dir(atan2($y, $x));
  497. }
  498. Slic3r::debugf " Optimal infill angle of bridge on layer %d is %d degrees\n",
  499. $self->id, $bridge_angle if defined $bridge_angle;
  500. $surface->bridge_angle($bridge_angle);
  501. }
  502. }
  503. }
  504. 1;