# Instantiated by Slic3r::Print::Object->_support_material() # only generate() and contact_distance() are called from the outside of this module. package Slic3r::Print::SupportMaterial; use Moo; use List::Util qw(sum min max); use Slic3r::ExtrusionPath ':roles'; use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(epsilon scale scaled_epsilon PI rad2deg deg2rad convex_hull); use Slic3r::Geometry::Clipper qw(offset diff union union_ex intersection offset_ex offset2 intersection_pl offset2_ex diff_pl JT_MITER JT_ROUND); use Slic3r::Surface ':types'; has 'print_config' => (is => 'rw', required => 1); has 'object_config' => (is => 'rw', required => 1); has 'flow' => (is => 'rw', required => 1); has 'first_layer_flow' => (is => 'rw', required => 1); has 'interface_flow' => (is => 'rw', required => 1); use constant DEBUG_CONTACT_ONLY => 0; # increment used to reach MARGIN in steps to avoid trespassing thin objects use constant MARGIN_STEP => MARGIN/3; # generate a tree-like structure to save material use constant PILLAR_SIZE => 2.5; use constant PILLAR_SPACING => 10; sub generate { # $object is Slic3r::Print::Object my ($self, $object) = @_; # Determine the top surfaces of the support, defined as: # contact = overhangs - clearance + margin # This method is responsible for identifying what contact surfaces # should the support material expose to the object in order to guarantee # that it will be effective, regardless of how it's built below. my ($contact, $overhang) = $self->contact_area($object); # Determine the top surfaces of the object. We need these to determine # the layer heights of support material and to clip support to the object # silhouette. my ($top) = $self->object_top($object, $contact); # We now know the upper and lower boundaries for our support material object # (@$contact_z and @$top_z), so we can generate intermediate layers. my $support_z = $self->support_layers_z( [ sort keys %$contact ], [ sort keys %$top ], max(map $_->height, @{$object->layers}) ); # If we wanted to apply some special logic to the first support layers lying on # object's top surfaces this is the place to detect them my $shape = []; if ($self->object_config->support_material_pattern eq 'pillars') { $self->generate_pillars_shape($contact, $support_z, $shape); } # Propagate contact layers downwards to generate interface layers my ($interface) = $self->generate_top_interface_layers($support_z, $contact, $top); $self->clip_with_object($interface, $support_z, $object); $self->clip_with_shape($interface, $shape) if @$shape; # Propagate contact layers and interface layers downwards to generate # the main support layers. my ($base) = $self->generate_base_layers($support_z, $contact, $interface, $top); $self->clip_with_object($base, $support_z, $object); $self->clip_with_shape($base, $shape) if @$shape; # Detect what part of base support layers are "reverse interfaces" because they # lie above object's top surfaces. $self->generate_bottom_interface_layers($support_z, $base, $top, $interface); # Install support layers into object. for my $i (0 .. $#$support_z) { $object->add_support_layer( $i, # id ($i == 0) ? $support_z->[$i] : ($support_z->[$i] - $support_z->[$i-1]), # height $support_z->[$i], # print_z ); if ($i >= 1) { $object->support_layers->[-2]->set_upper_layer($object->support_layers->[-1]); $object->support_layers->[-1]->set_lower_layer($object->support_layers->[-2]); } } # Generate the actual toolpaths and save them into each layer. $self->generate_toolpaths($object, $overhang, $contact, $interface, $base); } sub contact_area { # $object is Slic3r::Print::Object my ($self, $object) = @_; # if user specified a custom angle threshold, convert it to radians my $threshold_rad; if ($self->object_config->support_material_threshold) { $threshold_rad = deg2rad($self->object_config->support_material_threshold + 1); # +1 makes the threshold inclusive Slic3r::debugf "Threshold angle = %d°\n", rad2deg($threshold_rad); } # Build support on a build plate only? If so, then collect top surfaces into $buildplate_only_top_surfaces # and subtract $buildplate_only_top_surfaces from the contact surfaces, so # there is no contact surface supported by a top surface. my $buildplate_only = $self->object_config->support_material && $self->object_config->support_material_buildplate_only; my $buildplate_only_top_surfaces = []; # determine contact areas my %contact = (); # contact_z => [ polygons ] my %overhang = (); # contact_z => [ polygons ] - this stores the actual overhang supported by each contact layer for my $layer_id (0 .. $#{$object->layers}) { # note $layer_id might != $layer->id when raft_layers > 0 # so $layer_id == 0 means first object layer # and $layer->id == 0 means first print layer (including raft) if ($self->object_config->raft_layers == 0) { next if $layer_id == 0; } elsif (!$self->object_config->support_material) { # if we are only going to generate raft just check # the 'overhangs' of the first object layer last if $layer_id > 0; } my $layer = $object->get_layer($layer_id); if ($buildplate_only) { # Collect the top surfaces up to this layer and merge them. my $projection_new = []; push @$projection_new, ( map $_->p, map @{$_->slices->filter_by_type(S_TYPE_TOP)}, @{$layer->regions} ); if (@$projection_new) { # Merge the new top surfaces with the preceding top surfaces. # Apply the safety offset to the newly added polygons, so they will connect # with the polygons collected before, # but don't apply the safety offset during the union operation as it would # inflate the polygons over and over. push @$buildplate_only_top_surfaces, @{ offset($projection_new, scale(0.01)) }; $buildplate_only_top_surfaces = union($buildplate_only_top_surfaces, 0); } } # detect overhangs and contact areas needed to support them my (@overhang, @contact) = (); if ($layer_id == 0) { # this is the first object layer, so we're here just to get the object # footprint for the raft # we only consider contours and discard holes to get a more continuous raft push @overhang, map $_->clone, map $_->contour, @{$layer->slices}; # Extend by SUPPORT_MATERIAL_MARGIN, which is 1.5mm # MARGIN is the C++ Slic3r::SUPPORT_MATERIAL_MARGIN constant. push @contact, @{offset(\@overhang, scale +MARGIN)}; } else { my $lower_layer = $object->get_layer($layer_id-1); foreach my $layerm (@{$layer->regions}) { # Extrusion width accounts for the roundings of the extrudates. # It is the maximum widh of the extrudate. my $fw = $layerm->flow(FLOW_ROLE_EXTERNAL_PERIMETER)->scaled_width; my $diff; # If a threshold angle was specified, use a different logic for detecting overhangs. if (defined $threshold_rad || $layer_id < $self->object_config->support_material_enforce_layers || ($self->object_config->raft_layers > 0 && $layer_id == 0)) { my $d = defined $threshold_rad ? scale $lower_layer->height * ((cos $threshold_rad) / (sin $threshold_rad)) : 0; # Shrinking the supported layer by layer_height/atan(threshold_rad). $diff = diff( offset([ map $_->p, @{$layerm->slices} ], -$d), [ map @$_, @{$lower_layer->slices} ], ); # only enforce spacing from the object ($fw/2) if the threshold angle # is not too high: in that case, $d will be very small (as we need to catch # very short overhangs), and such contact area would be eaten by the # enforced spacing, resulting in high threshold angles to be almost ignored $diff = diff( offset($diff, $d - $fw/2), [ map @$_, @{$lower_layer->slices} ], ) if $d > $fw/2; } else { # Automatic overhang detection. $diff = diff( [ map $_->p, @{$layerm->slices} ], offset([ map @$_, @{$lower_layer->slices} ], #FIXME Vojtech: Why 2x extrusion width? Isn't this too much? Should it not be /2? +$fw/2), ); # collapse very tiny spots $diff = offset2($diff, -$fw/10, +$fw/10); # $diff now contains the ring or stripe comprised between the boundary of # lower slices and the centerline of the last perimeter in this overhanging layer. # Void $diff means that there's no upper perimeter whose centerline is # outside the lower slice boundary, thus no overhang } if ($self->object_config->dont_support_bridges) { # compute the area of bridging perimeters # Note: this is duplicate code from GCode.pm, we need to refactor my $bridged_perimeters; # Polygons { my $bridge_flow = $layerm->flow(FLOW_ROLE_PERIMETER, 1); my $nozzle_diameter = $self->print_config->get_at('nozzle_diameter', $layerm->region->config->perimeter_extruder-1); my $lower_grown_slices = offset([ map @$_, @{$lower_layer->slices} ], +scale($nozzle_diameter/2)); # TODO: split_at_first_point() could split a bridge mid-way my @overhang_perimeters = map { $_->isa('Slic3r::ExtrusionLoop') ? $_->polygon->split_at_first_point : $_->polyline->clone } map @$_, @{$layerm->perimeters}; # workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline() $_->[0]->translate(1,0) for @overhang_perimeters; @overhang_perimeters = @{diff_pl( \@overhang_perimeters, $lower_grown_slices, )}; # only consider straight overhangs @overhang_perimeters = grep $_->is_straight, @overhang_perimeters; # only consider overhangs having endpoints inside layer's slices foreach my $polyline (@overhang_perimeters) { $polyline->extend_start($fw); $polyline->extend_end($fw); } @overhang_perimeters = grep { $layer->slices->contains_point($_->first_point) && $layer->slices->contains_point($_->last_point) } @overhang_perimeters; # convert bridging polylines into polygons by inflating them with their thickness { # since we're dealing with bridges, we can't assume width is larger than spacing, # so we take the largest value and also apply safety offset to be ensure no gaps # are left in between my $w = max($bridge_flow->scaled_width, $bridge_flow->scaled_spacing); $bridged_perimeters = union([ map @{$_->grow($w/2 + 10)}, @overhang_perimeters ]); } } if (1) { # remove the entire bridges and only support the unsupported edges my @bridges = map $_->expolygon, grep $_->bridge_angle != -1, @{$layerm->fill_surfaces->filter_by_type(S_TYPE_BOTTOMBRIDGE)}; $diff = diff( $diff, [ (map @$_, @bridges), @$bridged_perimeters, ], 1, ); push @$diff, @{intersection( [ map @{$_->grow(+scale MARGIN)}, @{$layerm->unsupported_bridge_edges} ], [ map @$_, @bridges ], )}; } else { # just remove bridged areas $diff = diff( $diff, $layerm->bridged, 1, ); } } # if ($self->object_config->dont_support_bridges) if ($buildplate_only) { # Don't support overhangs above the top surfaces. # This step is done before the contact surface is calcuated by growing the overhang region. $diff = diff($diff, $buildplate_only_top_surfaces); } next if !@$diff; push @overhang, @$diff; # NOTE: this is not the full overhang as it misses the outermost half of the perimeter width! # Let's define the required contact area by using a max gap of half the upper # extrusion width and extending the area according to the configured margin. # We increment the area in steps because we don't want our support to overflow # on the other side of the object (if it's very thin). { my $slices_margin = offset([ map @$_, @{$lower_layer->slices} ], +$fw/2); if ($buildplate_only) { # Trim the inflated contact surfaces by the top surfaces as well. push @$slices_margin, map $_->clone, @{$buildplate_only_top_surfaces}; $slices_margin = union($slices_margin); } for ($fw/2, map {scale MARGIN_STEP} 1..(MARGIN / MARGIN_STEP)) { $diff = diff( offset( $diff, $_, JT_ROUND, scale(0.05)), $slices_margin ); } } push @contact, @$diff; } } next if !@contact; # now apply the contact areas to the layer were they need to be made { # get the average nozzle diameter used on this layer my @nozzle_diameters = map $self->print_config->get_at('nozzle_diameter', $_), map { $_->config->perimeter_extruder-1, $_->config->infill_extruder-1, $_->config->solid_infill_extruder-1 } map $_->region, @{$layer->regions}; my $nozzle_diameter = sum(@nozzle_diameters)/@nozzle_diameters; my $contact_z = $layer->print_z - $self->contact_distance($layer->height, $nozzle_diameter); # Ignore this contact area if it's too low. #FIXME Better to control the thickness of the interface layer printed, but that would # require having attributes (extrusion width / height, bridge flow etc) per island. next if $contact_z < $self->object_config->get_value('first_layer_height') - epsilon; $contact{$contact_z} = [ @contact ]; $overhang{$contact_z} = [ @overhang ]; if (0) { require "Slic3r/SVG.pm"; Slic3r::SVG::output(Slic3r::DEBUG_OUT_PATH_PREFIX . "contact_" . $contact_z . ".svg", green_expolygons => union_ex($buildplate_only_top_surfaces), blue_expolygons => union_ex(\@contact), red_expolygons => union_ex(\@overhang), ); } } } return (\%contact, \%overhang); } sub object_top { my ($self, $object, $contact) = @_; # find object top surfaces # we'll use them to clip our support and detect where does it stick my %top = (); # print_z => [ expolygons ] return \%top if ($self->object_config->support_material_buildplate_only); # Sum of unsupported contact areas above the current $layer->print_z. my $projection = []; foreach my $layer (reverse @{$object->layers}) { if (my @top = map @{$_->slices->filter_by_type(S_TYPE_TOP)}, @{$layer->regions}) { # compute projection of the contact areas above this top layer # first add all the 'new' contact areas to the current projection # ('new' means all the areas that are lower than the last top layer # we considered) my $min_top = min(keys %top) // max(keys %$contact); # use <= instead of just < because otherwise we'd ignore any contact regions # having the same Z of top layers push @$projection, map @{$contact->{$_}}, grep { $_ > $layer->print_z && $_ <= $min_top } keys %$contact; # Now find whether any projection of the contact surfaces above $layer->print_z not yet supported by any top surfaces above $layer->z falls onto this top surface. # $touching are the contact surfaces supported exclusively by this @top surfaaces. my $touching = intersection($projection, [ map $_->p, @top ]); if (@$touching) { # grow top surfaces so that interface and support generation are generated # with some spacing from object - it looks we don't need the actual # top shapes so this can be done here $top{ $layer->print_z } = offset($touching, $self->flow->scaled_width); } # remove the areas that touched from the projection that will continue on # next, lower, top surfaces $projection = diff($projection, $touching); } } return \%top; } sub support_layers_z { my ($self, $contact_z, $top_z, $max_object_layer_height) = @_; # quick table to check whether a given Z is a top surface my %top = map { $_ => 1 } @$top_z; # determine layer height for any non-contact layer # we use max() to prevent many ultra-thin layers to be inserted in case # layer_height > nozzle_diameter * 0.75 my $nozzle_diameter = $self->print_config->get_at('nozzle_diameter', $self->object_config->support_material_extruder-1); my $support_material_height = max($max_object_layer_height, $nozzle_diameter * 0.75); my $contact_distance = $self->contact_distance($support_material_height, $nozzle_diameter); # initialize known, fixed, support layers my @z = sort { $a <=> $b } @$contact_z, # TODO: why we have this? # Vojtech: To detect the bottom interface layers by finding a Z value in the $top_z. @$top_z, # Top surfaces of the bottom interface layers. (map $_ + $contact_distance, @$top_z); # enforce first layer height my $first_layer_height = $self->object_config->get_value('first_layer_height'); shift @z while @z && $z[0] <= $first_layer_height; unshift @z, $first_layer_height; # add raft layers by dividing the space between first layer and # first contact layer evenly if ($self->object_config->raft_layers > 1 && @z >= 2) { # $z[1] is last raft layer (contact layer for the first layer object) my $height = ($z[1] - $z[0]) / ($self->object_config->raft_layers - 1); # since we already have two raft layers ($z[0] and $z[1]) we need to insert # raft_layers-2 more splice @z, 1, 0, map { sprintf "%.2f", $_ } map { $z[0] + $height * $_ } 1..($self->object_config->raft_layers - 2); } # create other layers (skip raft layers as they're already done and use thicker layers) for (my $i = $#z; $i >= $self->object_config->raft_layers; $i--) { my $target_height = $support_material_height; if ($i > 0 && $top{ $z[$i-1] }) { # Bridge flow? #FIXME We want to enforce not only the bridge flow height, but also the interface gap! # This will introduce an additional layer if the gap is set to an extreme value! $target_height = $nozzle_diameter; } # enforce first layer height #FIXME better to split the layers regularly, than to bite a constant height one at a time, # and then be left with a very thin layer at the end. if (($i == 0 && $z[$i] > $target_height + $first_layer_height) || ($z[$i] - $z[$i-1] > $target_height + Slic3r::Geometry::epsilon)) { splice @z, $i, 0, ($z[$i] - $target_height); $i++; } } # remove duplicates and make sure all 0.x values have the leading 0 { my %sl = map { 1 * $_ => 1 } @z; @z = sort { $a <=> $b } keys %sl; } return \@z; } sub generate_top_interface_layers { my ($self, $support_z, $contact, $top) = @_; # If no interface layers are allowed, don't generate top interface layers. return if $self->object_config->support_material_interface_layers == 0; # let's now generate interface layers below contact areas my %interface = (); # layer_id => [ polygons ] my $interface_layers_num = $self->object_config->support_material_interface_layers; for my $layer_id (0 .. $#$support_z) { my $z = $support_z->[$layer_id]; my $this = $contact->{$z} // next; # count contact layer as interface layer for (my $i = $layer_id-1; $i >= 0 && $i > $layer_id-$interface_layers_num; $i--) { $z = $support_z->[$i]; my @overlapping_layers = $self->overlapping_layers($i, $support_z); my @overlapping_z = map $support_z->[$_], @overlapping_layers; # Compute interface area on this layer as diff of upper contact area # (or upper interface area) and layer slices. # This diff is responsible of the contact between support material and # the top surfaces of the object. We should probably offset the top # surfaces vertically before performing the diff, but this needs # investigation. $this = $interface{$i} = diff( [ @$this, # clipped projection of the current contact regions @{ $interface{$i} || [] }, # interface regions already applied to this layer ], [ (map @$_, map $top->{$_}, grep exists $top->{$_}, @overlapping_z), # top slices on this layer (map @$_, map $contact->{$_}, grep exists $contact->{$_}, @overlapping_z), # contact regions on this layer ], 1, ); } } return \%interface; } sub generate_bottom_interface_layers { my ($self, $support_z, $base, $top, $interface) = @_; # If no interface layers are allowed, don't generate bottom interface layers. return if $self->object_config->support_material_interface_layers == 0; my $area_threshold = $self->interface_flow->scaled_spacing ** 2; # loop through object's top surfaces foreach my $top_z (sort keys %$top) { my $this = $top->{$top_z}; # keep a count of the interface layers we generated for this top surface my $interface_layers = 0; # loop through support layers until we find the one(s) right above the top # surface foreach my $layer_id (0 .. $#$support_z) { my $z = $support_z->[$layer_id]; next unless $z > $top_z; if ($base->{$layer_id}) { # get the support material area that should be considered interface my $interface_area = intersection( $base->{$layer_id}, $this, ); # discard too small areas $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ]; # subtract new interface area from base $base->{$layer_id} = diff( $base->{$layer_id}, $interface_area, ); # add new interface area to interface push @{$interface->{$layer_id}}, @$interface_area; } $interface_layers++; last if $interface_layers == $self->object_config->support_material_interface_layers; } } } sub generate_base_layers { my ($self, $support_z, $contact, $interface, $top) = @_; # let's now generate support layers under interface layers my $base = {}; # layer_id => [ polygons ] { my $fillet_radius_scaled = scale($self->object_config->support_material_spacing); for my $i (reverse 0 .. $#$support_z-1) { my $z = $support_z->[$i]; my @overlapping_layers = $self->overlapping_layers($i, $support_z); my @overlapping_z = map $support_z->[$_], @overlapping_layers; # in case we have no interface layers, look at upper contact # (1 interface layer means we only have contact layer, so $interface->{$i+1} is empty) my @upper_contact = (); if ($self->object_config->support_material_interface_layers <= 1) { @upper_contact = @{ $contact->{$support_z->[$i+1]} || [] }; } my $trim_polygons = [ (map @$_, map $top->{$_}, grep exists $top->{$_}, @overlapping_z), # top slices on this layer (map @$_, map $interface->{$_}, grep exists $interface->{$_}, @overlapping_layers), # interface regions on this layer (map @$_, map $contact->{$_}, grep exists $contact->{$_}, @overlapping_z), # contact regions on this layer ]; $base->{$i} = diff( [ @{ $base->{$i+1} || [] }, # support regions on upper layer @{ $interface->{$i+1} || [] }, # interface regions on upper layer @upper_contact, # contact regions on upper layer ], $trim_polygons, 1, # safety offset to merge the touching source polygons ); if (0) { # Fillet the base polygons and trim them again with the top, interface and contact layers. $base->{$i} = diff( offset2( $base->{$i}, $fillet_radius_scaled, -$fillet_radius_scaled, # Use a geometric offsetting for filleting. JT_ROUND, 0.2*$fillet_radius_scaled), $trim_polygons, 0); # don't apply the safety offset. } } } return $base; } # This method removes object silhouette from support material # (it's used with interface and base only). It removes a bit more, # leaving a thin gap between object and support in the XY plane. sub clip_with_object { my ($self, $support, $support_z, $object) = @_; foreach my $i (keys %$support) { next if !@{$support->{$i}}; my $zmax = $support_z->[$i]; my $zmin = ($i == 0) ? 0 : $support_z->[$i-1]; my @layers = grep { $_->print_z > $zmin && ($_->print_z - $_->height) < $zmax } @{$object->layers}; # $layer->slices contains the full shape of layer, thus including # perimeter's width. $support contains the full shape of support # material, thus including the width of its foremost extrusion. # We leave a gap equal to a full extrusion width. $support->{$i} = diff( $support->{$i}, offset([ map @$_, map @{$_->slices}, @layers ], +$self->flow->scaled_width), ); } } sub generate_toolpaths { my ($self, $object, $overhang, $contact, $interface, $base) = @_; my $flow = $self->flow; my $interface_flow = $self->interface_flow; # shape of contact area my $contact_loops = 1; my $circle_radius = 1.5 * $interface_flow->scaled_width; my $circle_distance = 3 * $circle_radius; my $circle = Slic3r::Polygon->new(map [ $circle_radius * cos $_, $circle_radius * sin $_ ], (5*PI/3, 4*PI/3, PI, 2*PI/3, PI/3, 0)); Slic3r::debugf "Generating patterns\n"; # prepare fillers my $pattern = $self->object_config->support_material_pattern; my $with_sheath = $self->object_config->support_material_with_sheath; my @angles = ($self->object_config->support_material_angle); if ($pattern eq 'rectilinear-grid') { $pattern = 'rectilinear'; push @angles, $angles[0] + 90; } elsif ($pattern eq 'pillars') { $pattern = 'honeycomb'; } my $interface_angle = $self->object_config->support_material_angle + 90; my $interface_spacing = $self->object_config->support_material_interface_spacing + $interface_flow->spacing; my $interface_density = $interface_spacing == 0 ? 1 : $interface_flow->spacing / $interface_spacing; my $support_spacing = $self->object_config->support_material_spacing + $flow->spacing; my $support_density = $support_spacing == 0 ? 1 : $flow->spacing / $support_spacing; my $process_layer = sub { my ($layer_id) = @_; my $layer = $object->support_layers->[$layer_id]; my $z = $layer->print_z; # we redefine flows locally by applying this layer's height my $_flow = $flow->clone; my $_interface_flow = $interface_flow->clone; $_flow->set_height($layer->height); $_interface_flow->set_height($layer->height); my $overhang = $overhang->{$z} || []; my $contact = $contact->{$z} || []; my $interface = $interface->{$layer_id} || []; my $base = $base->{$layer_id} || []; if (DEBUG_CONTACT_ONLY) { $interface = []; $base = []; } if (0) { require "Slic3r/SVG.pm"; Slic3r::SVG::output(Slic3r::DEBUG_OUT_PATH_PREFIX . "layer_" . $z . ".svg", blue_expolygons => union_ex($base), red_expolygons => union_ex($contact), green_expolygons => union_ex($interface), ); } # islands $layer->support_islands->append(@{union_ex([ @$interface, @$base, @$contact ])}); # contact my $contact_infill = []; if ($self->object_config->support_material_interface_layers == 0) { # if no interface layers were requested we treat the contact layer # exactly as a generic base layer push @$base, @$contact; } elsif (@$contact && $contact_loops > 0) { # generate the outermost loop # find centerline of the external loop (or any other kind of extrusions should the loop be skipped) $contact = offset($contact, -$_interface_flow->scaled_width/2); my @loops0 = (); { # find centerline of the external loop of the contours my @external_loops = @$contact; # only consider the loops facing the overhang { my $overhang_with_margin = offset($overhang, +$_interface_flow->scaled_width/2); @external_loops = grep { @{intersection_pl( [ $_->split_at_first_point ], $overhang_with_margin, )} } @external_loops; } # apply a pattern to the loop my @positions = map @{Slic3r::Polygon->new(@$_)->equally_spaced_points($circle_distance)}, @external_loops; @loops0 = @{diff( [ @external_loops ], [ map { my $c = $circle->clone; $c->translate(@$_); $c } @positions ], )}; } # make more loops my @loops = @loops0; for my $i (2..$contact_loops) { my $d = ($i-1) * $_interface_flow->scaled_spacing; push @loops, @{offset2(\@loops0, -$d -0.5*$_interface_flow->scaled_spacing, +0.5*$_interface_flow->scaled_spacing)}; } # clip such loops to the side oriented towards the object @loops = @{intersection_pl( [ map $_->split_at_first_point, @loops ], offset($overhang, +scale MARGIN), )}; # add the contact infill area to the interface area # note that growing loops by $circle_radius ensures no tiny # extrusions are left inside the circles; however it creates # a very large gap between loops and contact_infill, so maybe another # solution should be found to achieve both goals $contact_infill = diff( $contact, [ map @{$_->grow($circle_radius*1.1)}, @loops ], ); # transform loops into ExtrusionPath objects my $mm3_per_mm = $_interface_flow->mm3_per_mm; @loops = map Slic3r::ExtrusionPath->new( polyline => $_, role => EXTR_ROLE_SUPPORTMATERIAL_INTERFACE, mm3_per_mm => $mm3_per_mm, width => $_interface_flow->width, height => $layer->height, ), @loops; $layer->support_interface_fills->append(@loops); } # Allocate the fillers exclusively in the worker threads! Don't allocate them at the main thread, # as Perl copies the C++ pointers by default, so then the C++ objects are shared between threads! my %fillers = ( interface => Slic3r::Filler->new_from_type('rectilinear'), support => Slic3r::Filler->new_from_type($pattern), ); my $bounding_box = $object->bounding_box; $fillers{interface}->set_bounding_box($object->bounding_box); $fillers{support}->set_bounding_box($object->bounding_box); # interface and contact infill if (@$interface || @$contact_infill) { $fillers{interface}->set_angle($interface_angle); $fillers{interface}->set_spacing($_interface_flow->spacing); # find centerline of the external loop $interface = offset2($interface, +scaled_epsilon, -(scaled_epsilon + $_interface_flow->scaled_width/2)); # join regions by offsetting them to ensure they're merged $interface = offset([ @$interface, @$contact_infill ], scaled_epsilon); # turn base support into interface when it's contained in our holes # (this way we get wider interface anchoring) { my @p = @$interface; @$interface = (); foreach my $p (@p) { if ($p->is_clockwise) { my $p2 = $p->clone; $p2->make_counter_clockwise; next if !@{diff([$p2], $base, 1)}; } push @$interface, $p; } } $base = diff($base, $interface); my @paths = (); foreach my $expolygon (@{union_ex($interface)}) { my $polylines = $fillers{interface}->fill_surface( Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNAL), density => $interface_density, layer_height => $layer->height, complete => 1, ); my $mm3_per_mm = $_interface_flow->mm3_per_mm; push @paths, map Slic3r::ExtrusionPath->new( polyline => Slic3r::Polyline->new(@$_), role => EXTR_ROLE_SUPPORTMATERIAL_INTERFACE, mm3_per_mm => $mm3_per_mm, width => $_interface_flow->width, height => $layer->height, ), @$polylines, } $layer->support_interface_fills->append(@paths); } # support or flange if (@$base) { my $filler = $fillers{support}; $filler->set_angle($angles[ ($layer_id) % @angles ]); # We don't use $base_flow->spacing because we need a constant spacing # value that guarantees that all layers are correctly aligned. $filler->set_spacing($flow->spacing); my $density = $support_density; my $base_flow = $_flow; # find centerline of the external loop/extrusions my $to_infill = offset2_ex($base, +scaled_epsilon, -(scaled_epsilon + $_flow->scaled_width/2)); if (0) { require "Slic3r/SVG.pm"; Slic3r::SVG::output(Slic3r::DEBUG_OUT_PATH_PREFIX . "to_infill_base" . $z . ".svg", red_expolygons => union_ex($contact), green_expolygons => union_ex($interface), blue_expolygons => $to_infill, ); } my @paths = (); # base flange if ($layer_id == 0) { $filler = $fillers{interface}; $filler->set_angle($self->object_config->support_material_angle + 90); $density = 0.5; $base_flow = $self->first_layer_flow; # use the proper spacing for first layer as we don't need to align # its pattern to the other layers $filler->set_spacing($base_flow->spacing); } elsif ($with_sheath) { # draw a perimeter all around support infill # TODO: use brim ordering algorithm my $mm3_per_mm = $_flow->mm3_per_mm; push @paths, map Slic3r::ExtrusionPath->new( polyline => $_->split_at_first_point, role => EXTR_ROLE_SUPPORTMATERIAL, mm3_per_mm => $mm3_per_mm, width => $_flow->width, height => $layer->height, ), map @$_, @$to_infill; # TODO: use offset2_ex() $to_infill = offset_ex([ map @$_, @$to_infill ], -$_flow->scaled_spacing); } foreach my $expolygon (@$to_infill) { my $polylines = $filler->fill_surface( Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNAL), density => $density, layer_height => $layer->height, complete => 1, ); push @paths, map Slic3r::ExtrusionPath->new( polyline => Slic3r::Polyline->new(@$_), role => EXTR_ROLE_SUPPORTMATERIAL, mm3_per_mm => $base_flow->mm3_per_mm, width => $base_flow->width, height => $layer->height, ), @$polylines; } $layer->support_fills->append(@paths); } if (0) { require "Slic3r/SVG.pm"; Slic3r::SVG::output("islands_" . $z . ".svg", red_expolygons => union_ex($contact), green_expolygons => union_ex($interface), green_polylines => [ map $_->unpack->polyline, @{$layer->support_contact_fills} ], polylines => [ map $_->unpack->polyline, @{$layer->support_fills} ], ); } }; Slic3r::parallelize( threads => $self->print_config->threads, items => [ 0 .. $#{$object->support_layers} ], thread_cb => sub { my $q = shift; while (defined (my $layer_id = $q->dequeue)) { $process_layer->($layer_id); } }, no_threads_cb => sub { $process_layer->($_) for 0 .. $#{$object->support_layers}; }, ); } sub generate_pillars_shape { my ($self, $contact, $support_z, $shape) = @_; # this prevents supplying an empty point set to BoundingBox constructor return if !%$contact; my $pillar_size = scale PILLAR_SIZE; my $pillar_spacing = scale PILLAR_SPACING; # A regular grid of pillars, filling the 2D bounding box. # arrayref of polygons my $grid; # arrayref of polygons { # Rectangle with a side of 2.5x2.5mm. my $pillar = Slic3r::Polygon->new( [0,0], [$pillar_size, 0], [$pillar_size, $pillar_size], [0, $pillar_size], ); # A regular grid of pillars, filling the 2D bounding box. my @pillars = (); # 2D bounding box of the projection of all contact polygons. my $bb = Slic3r::Geometry::BoundingBox->new_from_points([ map @$_, map @$_, values %$contact ]); for (my $x = $bb->x_min; $x <= $bb->x_max-$pillar_size; $x += $pillar_spacing) { for (my $y = $bb->y_min; $y <= $bb->y_max-$pillar_size; $y += $pillar_spacing) { push @pillars, my $p = $pillar->clone; $p->translate($x, $y); } } $grid = union(\@pillars); } # add pillars to every layer for my $i (0..$#$support_z) { $shape->[$i] = [ @$grid ]; } # build capitals for my $i (0..$#$support_z) { my $z = $support_z->[$i]; my $capitals = intersection( $grid, $contact->{$z} // [], ); # work on one pillar at time (if any) to prevent the capitals from being merged # but store the contact area supported by the capital because we need to make # sure nothing is left my $contact_supported_by_capitals = []; foreach my $capital (@$capitals) { # enlarge capital tops $capital = offset([$capital], +($pillar_spacing - $pillar_size)/2); push @$contact_supported_by_capitals, @$capital; for (my $j = $i-1; $j >= 0; $j--) { my $jz = $support_z->[$j]; $capital = offset($capital, -$self->interface_flow->scaled_width/2); last if !@$capitals; push @{ $shape->[$j] }, @$capital; } } # Capitals will not generally cover the whole contact area because there will be # remainders. For now we handle this situation by projecting such unsupported # areas to the ground, just like we would do with a normal support. my $contact_not_supported_by_capitals = diff( $contact->{$z} // [], $contact_supported_by_capitals, ); if (@$contact_not_supported_by_capitals) { for (my $j = $i-1; $j >= 0; $j--) { push @{ $shape->[$j] }, @$contact_not_supported_by_capitals; } } } } sub clip_with_shape { my ($self, $support, $shape) = @_; foreach my $i (keys %$support) { # don't clip bottom layer with shape so that we # can generate a continuous base flange # also don't clip raft layers next if $i == 0; next if $i < $self->object_config->raft_layers; $support->{$i} = intersection( $support->{$i}, $shape->[$i], ); } } # this method returns the indices of the layers overlapping with the given one sub overlapping_layers { my ($self, $i, $support_z) = @_; my $zmax = $support_z->[$i]; my $zmin = ($i == 0) ? 0 : $support_z->[$i-1]; return grep { my $zmax2 = $support_z->[$_]; my $zmin2 = ($_ == 0) ? 0 : $support_z->[$_-1]; $zmax > $zmin2 && $zmin < $zmax2; } 0..$#$support_z; } sub contact_distance { my ($self, $layer_height, $nozzle_diameter) = @_; my $extra = $self->object_config->support_material_contact_distance; if ($extra == 0) { return $layer_height; } else { return $nozzle_diameter + $extra; } } 1;