mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-11-02 20:51:23 -07:00 
			
		
		
		
	Merge branch 'master' into sender
Conflicts: lib/Slic3r/GUI/Tab.pm
This commit is contained in:
		
						commit
						9ec7b43ca1
					
				
					 53 changed files with 1535 additions and 838 deletions
				
			
		| 
						 | 
				
			
			@ -134,6 +134,7 @@ The author of the Silk icon set is Mark James.
 | 
			
		|||
                            default: reprap)
 | 
			
		||||
        --use-relative-e-distances Enable this to get relative E values (default: no)
 | 
			
		||||
        --use-firmware-retraction  Enable firmware-controlled retraction using G10/G11 (default: no)
 | 
			
		||||
        --use-volumetric-e  Express E in cubic millimeters and prepend M200 (default: no)
 | 
			
		||||
        --gcode-arcs        Use G2/G3 commands for native arcs (experimental, not supported
 | 
			
		||||
                            by all firmwares)
 | 
			
		||||
        --gcode-comments    Make G-code verbose by adding comments (default: no)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -58,6 +58,7 @@ use Slic3r::GCode::VibrationLimit;
 | 
			
		|||
use Slic3r::Geometry qw(PI);
 | 
			
		||||
use Slic3r::Geometry::Clipper;
 | 
			
		||||
use Slic3r::Layer;
 | 
			
		||||
use Slic3r::Layer::PerimeterGenerator;
 | 
			
		||||
use Slic3r::Layer::Region;
 | 
			
		||||
use Slic3r::Line;
 | 
			
		||||
use Slic3r::Model;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -207,8 +207,11 @@ sub extrude_loop {
 | 
			
		|||
        my $last_path_polyline = $paths[-1]->polyline;
 | 
			
		||||
        # detect angle between last and first segment
 | 
			
		||||
        # the side depends on the original winding order of the polygon (left for contours, right for holes)
 | 
			
		||||
        my @points = $was_clockwise ? (-2, 1) : (1, -2);
 | 
			
		||||
        my $angle = Slic3r::Geometry::angle3points(@$last_path_polyline[0, @points]) / 3;
 | 
			
		||||
        my @points = ($paths[0][1], $paths[-1][-2]);
 | 
			
		||||
        @points = reverse @points if $was_clockwise;
 | 
			
		||||
        my $angle = $paths[0]->first_point->ccw_angle(@points) / 3;
 | 
			
		||||
        
 | 
			
		||||
        # turn left if contour, turn right if hole
 | 
			
		||||
        $angle *= -1 if $was_clockwise;
 | 
			
		||||
        
 | 
			
		||||
        # create the destination point along the first segment and rotate it
 | 
			
		||||
| 
						 | 
				
			
			@ -332,48 +335,75 @@ sub _extrude_path {
 | 
			
		|||
sub travel_to {
 | 
			
		||||
    my ($self, $point, $role, $comment) = @_;
 | 
			
		||||
    
 | 
			
		||||
    my $gcode = "";
 | 
			
		||||
    
 | 
			
		||||
    # Define the travel move as a line between current position and the taget point.
 | 
			
		||||
    # This is expressed in print coordinates, so it will need to be translated by
 | 
			
		||||
    # $self->origin in order to get G-code coordinates.
 | 
			
		||||
    my $travel = Slic3r::Line->new($self->last_pos, $point);
 | 
			
		||||
    my $travel = Slic3r::Polyline->new($self->last_pos, $point);
 | 
			
		||||
    
 | 
			
		||||
    # Skip retraction at all in the following cases:
 | 
			
		||||
    # - travel length is shorter than the configured threshold
 | 
			
		||||
    # - user has enabled "Only retract when crossing perimeters" and the travel move is
 | 
			
		||||
    #   contained in a single internal fill_surface (this includes the bottom layer when
 | 
			
		||||
    #   bottom_solid_layers == 0) or in a single internal slice (this would exclude such
 | 
			
		||||
    #   bottom layer but preserve perimeter-to-infill moves in all the other layers)
 | 
			
		||||
    # - the path that will be extruded after this travel move is a support material
 | 
			
		||||
    #   extrusion and the travel move is contained in a single support material island
 | 
			
		||||
    if ($travel->length < scale $self->config->get_at('retract_before_travel', $self->writer->extruder->id)
 | 
			
		||||
        || ($self->config->only_retract_when_crossing_perimeters
 | 
			
		||||
            && $self->config->fill_density > 0
 | 
			
		||||
            && defined($self->layer)
 | 
			
		||||
            && ($self->layer->any_internal_region_slice_contains_line($travel)
 | 
			
		||||
             || $self->layer->any_internal_region_fill_surface_contains_line($travel)))
 | 
			
		||||
        || (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands->contains_line($travel))
 | 
			
		||||
        ) {
 | 
			
		||||
        # Just perform a straight travel move without any retraction.
 | 
			
		||||
        $gcode .= $self->writer->travel_to_xy($self->point_to_gcode($point), $comment || '');
 | 
			
		||||
    } elsif ($self->config->avoid_crossing_perimeters && !$self->avoid_crossing_perimeters->disable_once) {
 | 
			
		||||
        # If avoid_crossing_perimeters is enabled and the disable_once flag is not set
 | 
			
		||||
        # we need to plan a multi-segment travel move inside the configuration space.
 | 
			
		||||
        $gcode .= $self->avoid_crossing_perimeters->travel_to($self, $point, $comment || '');
 | 
			
		||||
    } else {
 | 
			
		||||
        # If avoid_crossing_perimeters is disabled or the disable_once flag is set,
 | 
			
		||||
        # perform a straight move with a retraction.
 | 
			
		||||
        $gcode .= $self->retract;
 | 
			
		||||
        $gcode .= $self->writer->travel_to_xy($self->point_to_gcode($point), $comment || '');
 | 
			
		||||
    # check whether a straight travel move would need retraction
 | 
			
		||||
    my $needs_retraction = $self->needs_retraction($travel, $role);
 | 
			
		||||
    
 | 
			
		||||
    # if a retraction would be needed, try to use avoid_crossing_perimeters to plan a
 | 
			
		||||
    # multi-hop travel path inside the configuration space
 | 
			
		||||
    if ($needs_retraction
 | 
			
		||||
        && $self->config->avoid_crossing_perimeters
 | 
			
		||||
        && !$self->avoid_crossing_perimeters->disable_once) {
 | 
			
		||||
        $travel = $self->avoid_crossing_perimeters->travel_to($self, $point);
 | 
			
		||||
        
 | 
			
		||||
        # check again whether the new travel path still needs a retraction
 | 
			
		||||
        $needs_retraction = $self->needs_retraction($travel, $role);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    # Re-allow avoid_crossing_perimeters for the next travel moves
 | 
			
		||||
    $self->avoid_crossing_perimeters->disable_once(0);
 | 
			
		||||
    $self->avoid_crossing_perimeters->use_external_mp_once(0);
 | 
			
		||||
    
 | 
			
		||||
    # generate G-code for the travel move
 | 
			
		||||
    my $gcode = "";
 | 
			
		||||
    $gcode .= $self->retract if $needs_retraction;
 | 
			
		||||
    
 | 
			
		||||
    # use G1 because we rely on paths being straight (G0 may make round paths)
 | 
			
		||||
    $gcode .= $self->writer->travel_to_xy($self->point_to_gcode($_->b), $comment)
 | 
			
		||||
        for @{$travel->lines};
 | 
			
		||||
    
 | 
			
		||||
    return $gcode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub needs_retraction {
 | 
			
		||||
    my ($self, $travel, $role) = @_;
 | 
			
		||||
    
 | 
			
		||||
    if ($travel->length < scale $self->config->get_at('retract_before_travel', $self->writer->extruder->id)) {
 | 
			
		||||
        # skip retraction if the move is shorter than the configured threshold
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    if (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands->contains_polyline($travel)) {
 | 
			
		||||
        # skip retraction if this is a travel move inside a support material island
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    if ($self->config->only_retract_when_crossing_perimeters && defined $self->layer) {
 | 
			
		||||
        if ($self->config->fill_density > 0
 | 
			
		||||
            && $self->layer->any_internal_region_slice_contains_polyline($travel)) {
 | 
			
		||||
            # skip retraction if travel is contained in an internal slice *and*
 | 
			
		||||
            # internal infill is enabled (so that stringing is entirely not visible)
 | 
			
		||||
            return 0;
 | 
			
		||||
        } elsif ($self->layer->any_bottom_region_slice_contains_polyline($travel)
 | 
			
		||||
            && defined $self->layer->upper_layer
 | 
			
		||||
            && $self->layer->upper_layer->slices->contains_polyline($travel)
 | 
			
		||||
            && ($self->config->bottom_solid_layers >= 2 || $self->config->fill_density > 0)) {
 | 
			
		||||
            # skip retraction if travel is contained in an *infilled* bottom slice
 | 
			
		||||
            # but only if it's also covered by an *infilled* upper layer's slice
 | 
			
		||||
            # so that it's not visible from above (a bottom surface might not have an
 | 
			
		||||
            # upper slice in case of a thin membrane)
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    # retract if only_retract_when_crossing_perimeters is disabled or doesn't apply
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub retract {
 | 
			
		||||
    my ($self, $toolchange) = @_;
 | 
			
		||||
    
 | 
			
		||||
| 
						 | 
				
			
			@ -568,9 +598,10 @@ has '_external_mp'          => (is => 'rw');
 | 
			
		|||
has '_layer_mp'             => (is => 'rw');
 | 
			
		||||
has 'use_external_mp'       => (is => 'rw', default => sub {0});
 | 
			
		||||
has 'use_external_mp_once'  => (is => 'rw', default => sub {0});   # this flag triggers the use of the external configuration space for avoid_crossing_perimeters for the next travel move
 | 
			
		||||
has 'disable_once'          => (is => 'rw', default => sub {1});   # this flag disables avoid_crossing_perimeters just for the next travel move
 | 
			
		||||
 | 
			
		||||
use Slic3r::Geometry qw(scale);
 | 
			
		||||
# this flag disables avoid_crossing_perimeters just for the next travel move
 | 
			
		||||
# we enable it by default for the first travel move in print
 | 
			
		||||
has 'disable_once'          => (is => 'rw', default => sub {1});
 | 
			
		||||
 | 
			
		||||
sub init_external_mp {
 | 
			
		||||
    my ($self, $islands) = @_;
 | 
			
		||||
| 
						 | 
				
			
			@ -583,47 +614,30 @@ sub init_layer_mp {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
sub travel_to {
 | 
			
		||||
    my ($self, $gcodegen, $point, $comment) = @_;
 | 
			
		||||
    
 | 
			
		||||
    my $gcode = "";
 | 
			
		||||
    my ($self, $gcodegen, $point) = @_;
 | 
			
		||||
    
 | 
			
		||||
    if ($self->use_external_mp || $self->use_external_mp_once) {
 | 
			
		||||
        $self->use_external_mp_once(0);
 | 
			
		||||
        # get current origin set in $gcodegen
 | 
			
		||||
        # (the one that will be used to translate the G-code coordinates by)
 | 
			
		||||
        my $scaled_origin = Slic3r::Point->new_scale(@{$gcodegen->origin});
 | 
			
		||||
        
 | 
			
		||||
        # represent $point in G-code coordinates
 | 
			
		||||
        # represent last_pos in absolute G-code coordinates
 | 
			
		||||
        my $last_pos = $gcodegen->last_pos->clone;
 | 
			
		||||
        $last_pos->translate(@$scaled_origin);
 | 
			
		||||
        
 | 
			
		||||
        # represent $point in absolute G-code coordinates
 | 
			
		||||
        $point = $point->clone;
 | 
			
		||||
        my $origin = $gcodegen->origin;
 | 
			
		||||
        $point->translate(map scale $_, @$origin);
 | 
			
		||||
        $point->translate(@$scaled_origin);
 | 
			
		||||
        # calculate path
 | 
			
		||||
        my $travel = $self->_external_mp->shortest_path($last_pos, $point);
 | 
			
		||||
        
 | 
			
		||||
        # calculate path (external_mp uses G-code coordinates so we set a temporary null origin)
 | 
			
		||||
        $gcodegen->set_origin(Slic3r::Pointf->new(0,0));
 | 
			
		||||
        $gcode .= $self->_plan($gcodegen, $self->_external_mp, $point, $comment);
 | 
			
		||||
        $gcodegen->set_origin($origin);
 | 
			
		||||
        # translate the path back into the shifted coordinate system that $gcodegen
 | 
			
		||||
        # is currently using for writing coordinates
 | 
			
		||||
        $travel->translate(@{$scaled_origin->negative});
 | 
			
		||||
        return $travel;
 | 
			
		||||
    } else {
 | 
			
		||||
        $gcode .= $self->_plan($gcodegen, $self->_layer_mp, $point, $comment);
 | 
			
		||||
        return $self->_layer_mp->shortest_path($gcodegen->last_pos, $point);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return $gcode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _plan {
 | 
			
		||||
    my ($self, $gcodegen, $mp, $point, $comment) = @_;
 | 
			
		||||
    
 | 
			
		||||
    my $gcode = "";
 | 
			
		||||
    my $travel = $mp->shortest_path($gcodegen->last_pos, $point);
 | 
			
		||||
    
 | 
			
		||||
    # if the path is not contained in a single island we need to retract
 | 
			
		||||
    $gcode .= $gcodegen->retract
 | 
			
		||||
        if !$gcodegen->config->only_retract_when_crossing_perimeters
 | 
			
		||||
        || !$gcodegen->layer->any_internal_region_fill_surface_contains_polyline($travel);
 | 
			
		||||
    
 | 
			
		||||
    # append the actual path and return
 | 
			
		||||
    # use G1 because we rely on paths being straight (G0 may make round paths)
 | 
			
		||||
    $gcode .= join '',
 | 
			
		||||
        map $gcodegen->writer->travel_to_xy($gcodegen->point_to_gcode($_->b), $comment),
 | 
			
		||||
        @{$travel->lines};
 | 
			
		||||
    
 | 
			
		||||
    return $gcode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
1;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,7 +24,7 @@ sub BUILD {
 | 
			
		|||
 | 
			
		||||
sub process {
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my ($gcode) = @_;
 | 
			
		||||
    my ($gcode, $flush) = @_;
 | 
			
		||||
    
 | 
			
		||||
    my $new_gcode = "";
 | 
			
		||||
    
 | 
			
		||||
| 
						 | 
				
			
			@ -51,7 +51,7 @@ sub process {
 | 
			
		|||
                if (abs($new_advance - $self->_advance) > 1E-5) {
 | 
			
		||||
                    my $new_E = ($self->config->use_relative_e_distances ? 0 : $reader->E) + ($new_advance - $self->_advance);
 | 
			
		||||
                    $new_gcode .= sprintf "G1 %s%.5f F%.3f ; pressure advance\n",
 | 
			
		||||
                        $self->_extrusion_axis, $new_E, $self->unretract_speed;
 | 
			
		||||
                        $self->_extrusion_axis, $new_E, $self->_unretract_speed;
 | 
			
		||||
                    $new_gcode .= sprintf "G92 %s%.5f ; restore E\n", $self->_extrusion_axis, $reader->E
 | 
			
		||||
                        if !$self->config->use_relative_e_distances;
 | 
			
		||||
                    $self->_advance($new_advance);
 | 
			
		||||
| 
						 | 
				
			
			@ -61,20 +61,33 @@ sub process {
 | 
			
		|||
            }
 | 
			
		||||
        } elsif (($info->{retracting} || $cmd eq 'G10') && $self->_advance != 0) {
 | 
			
		||||
            # We need to bring pressure to zero when retracting.
 | 
			
		||||
            my $new_E = ($self->config->use_relative_e_distances ? 0 : $reader->E) - $self->_advance;
 | 
			
		||||
            $new_gcode .= sprintf "G1 %s%.5f F%.3f ; pressure discharge\n",
 | 
			
		||||
                $self->_extrusion_axis, $new_E, $args->{F} // $self->unretract_speed;
 | 
			
		||||
            $new_gcode .= sprintf "G92 %s%.5f ; restore E\n", $self->_extrusion_axis, $reader->E
 | 
			
		||||
                if !$self->config->use_relative_e_distances;
 | 
			
		||||
            $new_gcode .= $self->_discharge($args->{F});
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $new_gcode .= "$info->{raw}\n";
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    if ($flush) {
 | 
			
		||||
        $new_gcode .= $self->_discharge;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return $new_gcode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub unretract_speed {
 | 
			
		||||
sub _discharge {
 | 
			
		||||
    my ($self, $F) = @_;
 | 
			
		||||
    
 | 
			
		||||
    my $new_E = ($self->config->use_relative_e_distances ? 0 : $self->reader->E) - $self->_advance;
 | 
			
		||||
    my $gcode = sprintf "G1 %s%.5f F%.3f ; pressure discharge\n",
 | 
			
		||||
        $self->_extrusion_axis, $new_E, $F // $self->_unretract_speed;
 | 
			
		||||
    $gcode .= sprintf "G92 %s%.5f ; restore E\n", $self->_extrusion_axis, $self->reader->E
 | 
			
		||||
        if !$self->config->use_relative_e_distances;
 | 
			
		||||
    $self->_advance(0);
 | 
			
		||||
    
 | 
			
		||||
    return $gcode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _unretract_speed {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    return $self->config->get_at('retract_speed', $self->_tool) * 60;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,7 @@ use base 'Wx::Dialog';
 | 
			
		|||
sub new {
 | 
			
		||||
    my $class = shift;
 | 
			
		||||
    my ($parent) = @_;
 | 
			
		||||
    my $self = $class->SUPER::new($parent, -1, 'About Slic3r', wxDefaultPosition, [600, 270]);
 | 
			
		||||
    my $self = $class->SUPER::new($parent, -1, 'About Slic3r', wxDefaultPosition, [600, 300]);
 | 
			
		||||
 | 
			
		||||
    $self->SetBackgroundColour(Wx::wxWHITE);
 | 
			
		||||
    my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
 | 
			
		||||
| 
						 | 
				
			
			@ -47,7 +47,7 @@ sub new {
 | 
			
		|||
        '<html>' .
 | 
			
		||||
        '<body bgcolor="#ffffff" link="#808080">' .
 | 
			
		||||
        '<font color="#808080">' .
 | 
			
		||||
        'Copyright © 2011-2014 Alessandro Ranellucci. <br />' .
 | 
			
		||||
        'Copyright © 2011-2015 Alessandro Ranellucci. <br />' .
 | 
			
		||||
        '<a href="http://slic3r.org/">Slic3r</a> is licensed under the ' .
 | 
			
		||||
        '<a href="http://www.gnu.org/licenses/agpl-3.0.html">GNU Affero General Public License, version 3</a>.' .
 | 
			
		||||
        '<br /><br /><br />' .
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -301,7 +301,7 @@ sub Render {
 | 
			
		|||
        foreach my $layerm (@{$layer->regions}) {
 | 
			
		||||
            if ($object->step_done(STEP_PERIMETERS)) {
 | 
			
		||||
                $self->color([0.7, 0, 0]);
 | 
			
		||||
                $self->_draw($object, $print_z, $_) for @{$layerm->perimeters};
 | 
			
		||||
                $self->_draw($object, $print_z, $_) for map @$_, @{$layerm->perimeters};
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if ($object->step_done(STEP_INFILL)) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -44,7 +44,7 @@ use constant TRACKBALLSIZE => 0.8;
 | 
			
		|||
use constant TURNTABLE_MODE => 1;
 | 
			
		||||
use constant GROUND_Z       => -0.02;
 | 
			
		||||
use constant SELECTED_COLOR => [0,1,0,1];
 | 
			
		||||
use constant HOVER_COLOR    => [0.8,0.8,0,1];
 | 
			
		||||
use constant HOVER_COLOR    => [0.4,0.9,0,1];
 | 
			
		||||
use constant COLORS => [ [1,1,0], [1,0.5,0.5], [0.5,1,0.5], [0.5,0.5,1] ];
 | 
			
		||||
 | 
			
		||||
# make OpenGL::Array thread-safe
 | 
			
		||||
| 
						 | 
				
			
			@ -90,7 +90,7 @@ sub new {
 | 
			
		|||
        my $zoom = $e->GetWheelRotation() / $e->GetWheelDelta();
 | 
			
		||||
        $zoom = max(min($zoom, 4), -4);
 | 
			
		||||
        $zoom /= 10;
 | 
			
		||||
        $self->_zoom($self->_zoom * (1-$zoom));
 | 
			
		||||
        $self->_zoom($self->_zoom / (1-$zoom));
 | 
			
		||||
        
 | 
			
		||||
        # In order to zoom around the mouse point we need to translate
 | 
			
		||||
        # the camera target
 | 
			
		||||
| 
						 | 
				
			
			@ -171,7 +171,7 @@ sub mouse_event {
 | 
			
		|||
        $self->_drag_start_pos($cur_pos);
 | 
			
		||||
        $self->_dragged(1);
 | 
			
		||||
        $self->Refresh;
 | 
			
		||||
    } elsif ($e->Dragging && !defined $self->_hover_volume_idx) {
 | 
			
		||||
    } elsif ($e->Dragging) {
 | 
			
		||||
        if ($e->LeftIsDown) {
 | 
			
		||||
            # if dragging over blank area with left button, rotate
 | 
			
		||||
            if (defined $self->_drag_start_pos) {
 | 
			
		||||
| 
						 | 
				
			
			@ -208,7 +208,7 @@ sub mouse_event {
 | 
			
		|||
            }
 | 
			
		||||
            $self->_drag_start_xy($pos);
 | 
			
		||||
        }
 | 
			
		||||
    } elsif ($e->LeftUp || $e->RightUp) {
 | 
			
		||||
    } elsif ($e->LeftUp || $e->MiddleUp || $e->RightUp) {
 | 
			
		||||
        if ($self->on_move && defined $self->_drag_volume_idx) {
 | 
			
		||||
            $self->on_move->($self->_drag_volume_idx) if $self->_dragged;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -629,17 +629,16 @@ sub InitGL {
 | 
			
		|||
    glEnable(GL_MULTISAMPLE);
 | 
			
		||||
    
 | 
			
		||||
    # ambient lighting
 | 
			
		||||
    glLightModelfv_p(GL_LIGHT_MODEL_AMBIENT, 0.1, 0.1, 0.1, 1);
 | 
			
		||||
    glLightModelfv_p(GL_LIGHT_MODEL_AMBIENT, 0.3, 0.3, 0.3, 1);
 | 
			
		||||
    
 | 
			
		||||
    glEnable(GL_LIGHTING);
 | 
			
		||||
    glEnable(GL_LIGHT0);
 | 
			
		||||
    glEnable(GL_LIGHT1);
 | 
			
		||||
    glLightfv_p(GL_LIGHT0, GL_POSITION, 0.5, 0.5, 1, 0);
 | 
			
		||||
    glLightfv_p(GL_LIGHT0, GL_SPECULAR, 0.5, 0.5, 0.5, 1);
 | 
			
		||||
    glLightfv_p(GL_LIGHT0, GL_DIFFUSE,  0.8, 0.8, 0.8, 1);
 | 
			
		||||
    glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 0.5, 0);
 | 
			
		||||
    glLightfv_p(GL_LIGHT1, GL_SPECULAR, 0.5, 0.5, 0.5, 1);
 | 
			
		||||
    glLightfv_p(GL_LIGHT1, GL_DIFFUSE,  1, 1, 1, 1);
 | 
			
		||||
    
 | 
			
		||||
    # light from camera
 | 
			
		||||
    glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0);
 | 
			
		||||
    glLightfv_p(GL_LIGHT1, GL_SPECULAR, 0.3, 0.3, 0.3, 1);
 | 
			
		||||
    glLightfv_p(GL_LIGHT1, GL_DIFFUSE,  0.2, 0.2, 0.2, 1);
 | 
			
		||||
    
 | 
			
		||||
    # Enables Smooth Color Shading; try GL_FLAT for (lack of) fun.
 | 
			
		||||
    glShadeModel(GL_SMOOTH);
 | 
			
		||||
| 
						 | 
				
			
			@ -681,6 +680,11 @@ sub Render {
 | 
			
		|||
    }
 | 
			
		||||
    glTranslatef(@{ $self->_camera_target->negative });
 | 
			
		||||
    
 | 
			
		||||
    # light from above
 | 
			
		||||
    glLightfv_p(GL_LIGHT0, GL_POSITION, -0.5, -0.5, 1, 0);
 | 
			
		||||
    glLightfv_p(GL_LIGHT0, GL_SPECULAR, 0.2, 0.2, 0.2, 1);
 | 
			
		||||
    glLightfv_p(GL_LIGHT0, GL_DIFFUSE,  0.5, 0.5, 0.5, 1);
 | 
			
		||||
    
 | 
			
		||||
    if ($self->enable_picking) {
 | 
			
		||||
        glDisable(GL_LIGHTING);
 | 
			
		||||
        $self->draw_volumes(1);
 | 
			
		||||
| 
						 | 
				
			
			@ -706,6 +710,7 @@ sub Render {
 | 
			
		|||
    
 | 
			
		||||
    # draw fixed background
 | 
			
		||||
    if ($self->background) {
 | 
			
		||||
        glDisable(GL_LIGHTING);
 | 
			
		||||
        glPushMatrix();
 | 
			
		||||
        glLoadIdentity();
 | 
			
		||||
        
 | 
			
		||||
| 
						 | 
				
			
			@ -725,85 +730,72 @@ sub Render {
 | 
			
		|||
        
 | 
			
		||||
        glMatrixMode(GL_MODELVIEW);
 | 
			
		||||
        glPopMatrix();
 | 
			
		||||
        glEnable(GL_LIGHTING);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    # draw ground and axes
 | 
			
		||||
    glDisable(GL_LIGHTING);
 | 
			
		||||
    my $z0 = 0;
 | 
			
		||||
    
 | 
			
		||||
    # draw ground
 | 
			
		||||
    my $ground_z = GROUND_Z;
 | 
			
		||||
    if ($self->bed_triangles) {
 | 
			
		||||
        glDisable(GL_DEPTH_TEST);
 | 
			
		||||
        
 | 
			
		||||
        glEnable(GL_BLEND);
 | 
			
		||||
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 | 
			
		||||
        
 | 
			
		||||
        glEnableClientState(GL_VERTEX_ARRAY);
 | 
			
		||||
        glColor4f(0.8, 0.6, 0.5, 0.4);
 | 
			
		||||
        glNormal3d(0,0,1);
 | 
			
		||||
        glVertexPointer_p(3, $self->bed_triangles);
 | 
			
		||||
        glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3);
 | 
			
		||||
        glDisableClientState(GL_VERTEX_ARRAY);
 | 
			
		||||
        
 | 
			
		||||
        # we need depth test for grid, otherwise it would disappear when looking
 | 
			
		||||
        # the object from below
 | 
			
		||||
        glEnable(GL_DEPTH_TEST);
 | 
			
		||||
    
 | 
			
		||||
        # draw grid
 | 
			
		||||
        glLineWidth(3);
 | 
			
		||||
        glColor4f(0.2, 0.2, 0.2, 0.4);
 | 
			
		||||
        glEnableClientState(GL_VERTEX_ARRAY);
 | 
			
		||||
        glVertexPointer_p(3, $self->bed_grid_lines);
 | 
			
		||||
        glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3);
 | 
			
		||||
        glDisableClientState(GL_VERTEX_ARRAY);
 | 
			
		||||
        
 | 
			
		||||
        glDisable(GL_BLEND);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    my $volumes_bb = $self->volumes_bounding_box;
 | 
			
		||||
    
 | 
			
		||||
    {
 | 
			
		||||
        # draw ground
 | 
			
		||||
        my $ground_z = GROUND_Z;
 | 
			
		||||
        if ($self->bed_triangles) {
 | 
			
		||||
            glDisable(GL_DEPTH_TEST);
 | 
			
		||||
            
 | 
			
		||||
            glEnable(GL_BLEND);
 | 
			
		||||
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 | 
			
		||||
            
 | 
			
		||||
            glEnableClientState(GL_VERTEX_ARRAY);
 | 
			
		||||
            glColor4f(0.8, 0.6, 0.5, 0.4);
 | 
			
		||||
            glNormal3d(0,0,1);
 | 
			
		||||
            glVertexPointer_p(3, $self->bed_triangles);
 | 
			
		||||
            glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3);
 | 
			
		||||
            glDisableClientState(GL_VERTEX_ARRAY);
 | 
			
		||||
            
 | 
			
		||||
            glEnable(GL_DEPTH_TEST);
 | 
			
		||||
        
 | 
			
		||||
            # draw grid
 | 
			
		||||
            glLineWidth(3);
 | 
			
		||||
            glColor4f(0.2, 0.2, 0.2, 0.4);
 | 
			
		||||
            glEnableClientState(GL_VERTEX_ARRAY);
 | 
			
		||||
            glVertexPointer_p(3, $self->bed_grid_lines);
 | 
			
		||||
            glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3);
 | 
			
		||||
            glDisableClientState(GL_VERTEX_ARRAY);
 | 
			
		||||
            
 | 
			
		||||
            glDisable(GL_BLEND);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        my $volumes_bb = $self->volumes_bounding_box;
 | 
			
		||||
        
 | 
			
		||||
        {
 | 
			
		||||
            # draw axes
 | 
			
		||||
            $ground_z += 0.02;
 | 
			
		||||
            my $origin = $self->origin;
 | 
			
		||||
            my $axis_len = max(
 | 
			
		||||
                0.3 * max(@{ $self->bed_bounding_box->size }),
 | 
			
		||||
                  2 * max(@{ $volumes_bb->size }),
 | 
			
		||||
            );
 | 
			
		||||
            glLineWidth(2);
 | 
			
		||||
            glBegin(GL_LINES);
 | 
			
		||||
            # draw line for x axis
 | 
			
		||||
            glColor3f(1, 0, 0);
 | 
			
		||||
            glVertex3f(@$origin, $ground_z);
 | 
			
		||||
            glVertex3f($origin->x + $axis_len, $origin->y, $ground_z);  #,,
 | 
			
		||||
            # draw line for y axis
 | 
			
		||||
            glColor3f(0, 1, 0);
 | 
			
		||||
            glVertex3f(@$origin, $ground_z);
 | 
			
		||||
            glVertex3f($origin->x, $origin->y + $axis_len, $ground_z);  #++
 | 
			
		||||
            # draw line for Z axis
 | 
			
		||||
            glColor3f(0, 0, 1);
 | 
			
		||||
            glVertex3f(@$origin, $ground_z);
 | 
			
		||||
            glVertex3f(@$origin, $ground_z+$axis_len);
 | 
			
		||||
            glEnd();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        # draw cutting plane
 | 
			
		||||
        if (defined $self->cutting_plane_z) {
 | 
			
		||||
            my $plane_z = $z0 + $self->cutting_plane_z;
 | 
			
		||||
            my $bb = $volumes_bb;
 | 
			
		||||
            glDisable(GL_CULL_FACE);
 | 
			
		||||
            glEnable(GL_BLEND);
 | 
			
		||||
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 | 
			
		||||
            glBegin(GL_QUADS);
 | 
			
		||||
            glColor4f(0.8, 0.8, 0.8, 0.5);
 | 
			
		||||
            glVertex3f($bb->x_min-20, $bb->y_min-20, $plane_z);
 | 
			
		||||
            glVertex3f($bb->x_max+20, $bb->y_min-20, $plane_z);
 | 
			
		||||
            glVertex3f($bb->x_max+20, $bb->y_max+20, $plane_z);
 | 
			
		||||
            glVertex3f($bb->x_min-20, $bb->y_max+20, $plane_z);
 | 
			
		||||
            glEnd();
 | 
			
		||||
            glEnable(GL_CULL_FACE);
 | 
			
		||||
            glDisable(GL_BLEND);
 | 
			
		||||
        }
 | 
			
		||||
        # draw axes
 | 
			
		||||
        # disable depth testing so that axes are not covered by ground
 | 
			
		||||
        glDisable(GL_DEPTH_TEST);
 | 
			
		||||
        my $origin = $self->origin;
 | 
			
		||||
        my $axis_len = max(
 | 
			
		||||
            0.3 * max(@{ $self->bed_bounding_box->size }),
 | 
			
		||||
              2 * max(@{ $volumes_bb->size }),
 | 
			
		||||
        );
 | 
			
		||||
        glLineWidth(2);
 | 
			
		||||
        glBegin(GL_LINES);
 | 
			
		||||
        # draw line for x axis
 | 
			
		||||
        glColor3f(1, 0, 0);
 | 
			
		||||
        glVertex3f(@$origin, $ground_z);
 | 
			
		||||
        glVertex3f($origin->x + $axis_len, $origin->y, $ground_z);  #,,
 | 
			
		||||
        # draw line for y axis
 | 
			
		||||
        glColor3f(0, 1, 0);
 | 
			
		||||
        glVertex3f(@$origin, $ground_z);
 | 
			
		||||
        glVertex3f($origin->x, $origin->y + $axis_len, $ground_z);  #++
 | 
			
		||||
        glEnd();
 | 
			
		||||
        # draw line for Z axis
 | 
			
		||||
        # (re-enable depth test so that axis is correctly shown when objects are behind it)
 | 
			
		||||
        glEnable(GL_DEPTH_TEST);
 | 
			
		||||
        glBegin(GL_LINES);
 | 
			
		||||
        glColor3f(0, 0, 1);
 | 
			
		||||
        glVertex3f(@$origin, $ground_z);
 | 
			
		||||
        glVertex3f(@$origin, $ground_z+$axis_len);
 | 
			
		||||
        glEnd();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    glEnable(GL_LIGHTING);
 | 
			
		||||
| 
						 | 
				
			
			@ -811,6 +803,25 @@ sub Render {
 | 
			
		|||
    # draw objects
 | 
			
		||||
    $self->draw_volumes;
 | 
			
		||||
    
 | 
			
		||||
    # draw cutting plane
 | 
			
		||||
    if (defined $self->cutting_plane_z) {
 | 
			
		||||
        my $plane_z = $self->cutting_plane_z;
 | 
			
		||||
        my $bb = $volumes_bb;
 | 
			
		||||
        glDisable(GL_CULL_FACE);
 | 
			
		||||
        glDisable(GL_LIGHTING);
 | 
			
		||||
        glEnable(GL_BLEND);
 | 
			
		||||
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 | 
			
		||||
        glBegin(GL_QUADS);
 | 
			
		||||
        glColor4f(0.8, 0.8, 0.8, 0.5);
 | 
			
		||||
        glVertex3f($bb->x_min-20, $bb->y_min-20, $plane_z);
 | 
			
		||||
        glVertex3f($bb->x_max+20, $bb->y_min-20, $plane_z);
 | 
			
		||||
        glVertex3f($bb->x_max+20, $bb->y_max+20, $plane_z);
 | 
			
		||||
        glVertex3f($bb->x_min-20, $bb->y_max+20, $plane_z);
 | 
			
		||||
        glEnd();
 | 
			
		||||
        glEnable(GL_CULL_FACE);
 | 
			
		||||
        glDisable(GL_BLEND);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    glFlush();
 | 
			
		||||
 
 | 
			
		||||
    $self->SwapBuffers();
 | 
			
		||||
| 
						 | 
				
			
			@ -866,9 +877,13 @@ sub draw_volumes {
 | 
			
		|||
                                glLineWidth(0);
 | 
			
		||||
                                glColor3f(@{COLORS->[0]});
 | 
			
		||||
                                glBegin(GL_QUADS);
 | 
			
		||||
                                glNormal3f((map $_/$line->length, @{$line->normal}), 0);
 | 
			
		||||
                                # We'll use this for the middle normal when using 4 quads:
 | 
			
		||||
                                #my $xy_normal = $line->normal;
 | 
			
		||||
                                #$_xynormal->scale(1/$line->length);
 | 
			
		||||
                                glNormal3f(0,0,-1);
 | 
			
		||||
                                glVertex3f((map unscale($_), @{$line->a}), $bottom_z);
 | 
			
		||||
                                glVertex3f((map unscale($_), @{$line->b}), $bottom_z);
 | 
			
		||||
                                glNormal3f(0,0,1);
 | 
			
		||||
                                glVertex3f((map unscale($_), @{$line->b}), $top_z);
 | 
			
		||||
                                glVertex3f((map unscale($_), @{$line->a}), $top_z);
 | 
			
		||||
                                glEnd();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -928,6 +928,7 @@ sub build {
 | 
			
		|||
        serial_port serial_speed
 | 
			
		||||
        octoprint_host octoprint_apikey
 | 
			
		||||
        use_firmware_retraction pressure_advance vibration_limit
 | 
			
		||||
        use_volumetric_e
 | 
			
		||||
        start_gcode end_gcode layer_gcode toolchange_gcode
 | 
			
		||||
        nozzle_diameter extruder_offset
 | 
			
		||||
        retract_length retract_lift retract_speed retract_restart_extra retract_before_travel retract_layer_change wipe
 | 
			
		||||
| 
						 | 
				
			
			@ -1019,8 +1020,8 @@ sub build {
 | 
			
		|||
        {
 | 
			
		||||
            my $optgroup = $page->new_optgroup('OctoPrint upload');
 | 
			
		||||
            
 | 
			
		||||
            # append a button to the Host line
 | 
			
		||||
            my $octoprint_host_widget = sub {
 | 
			
		||||
            # append two buttons to the Host line
 | 
			
		||||
            my $octoprint_host_browse = sub {
 | 
			
		||||
                my ($parent) = @_;
 | 
			
		||||
                
 | 
			
		||||
                my $btn = Wx::Button->new($parent, -1, "Browse…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
 | 
			
		||||
| 
						 | 
				
			
			@ -1032,10 +1033,7 @@ sub build {
 | 
			
		|||
                if (!eval "use Net::Bonjour; 1") {
 | 
			
		||||
                    $btn->Disable;
 | 
			
		||||
                }
 | 
			
		||||
        
 | 
			
		||||
                my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
 | 
			
		||||
                $sizer->Add($btn);
 | 
			
		||||
        
 | 
			
		||||
                
 | 
			
		||||
                EVT_BUTTON($self, $btn, sub {
 | 
			
		||||
                    my $dlg = Slic3r::GUI::BonjourBrowser->new($self);
 | 
			
		||||
                    if ($dlg->ShowModal == wxID_OK) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1047,22 +1045,51 @@ sub build {
 | 
			
		|||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                
 | 
			
		||||
                return $sizer;
 | 
			
		||||
                return $btn;
 | 
			
		||||
            };
 | 
			
		||||
            my $octoprint_host_test = sub {
 | 
			
		||||
                my ($parent) = @_;
 | 
			
		||||
                
 | 
			
		||||
                my $btn = $self->{octoprint_host_test_btn} = Wx::Button->new($parent, -1, "Test", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
 | 
			
		||||
                $btn->SetFont($Slic3r::GUI::small_font);
 | 
			
		||||
                if ($Slic3r::GUI::have_button_icons) {
 | 
			
		||||
                    $btn->SetBitmap(Wx::Bitmap->new("$Slic3r::var/wrench.png", wxBITMAP_TYPE_PNG));
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                EVT_BUTTON($self, $btn, sub {
 | 
			
		||||
                    my $ua = LWP::UserAgent->new;
 | 
			
		||||
                    $ua->timeout(10);
 | 
			
		||||
    
 | 
			
		||||
                    my $res = $ua->post(
 | 
			
		||||
                        "http://" . $self->{config}->octoprint_host . "/api/version",
 | 
			
		||||
                        'X-Api-Key' => $self->{config}->octoprint_apikey,
 | 
			
		||||
                    );
 | 
			
		||||
                    if ($res->is_success) {
 | 
			
		||||
                        Slic3r::GUI::show_info($self, "Connection to OctoPrint works correctly.", "Success!");
 | 
			
		||||
                    } else {
 | 
			
		||||
                        Slic3r::GUI::show_error($self,
 | 
			
		||||
                            "I wasn't able to connect to OctoPrint (" . $res->status_line . "). "
 | 
			
		||||
                            . "Check hostname and OctoPrint version (at least 1.1.0 is required).");
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                return $btn;
 | 
			
		||||
            };
 | 
			
		||||
            
 | 
			
		||||
            my $host_line = $optgroup->create_single_option_line('octoprint_host');
 | 
			
		||||
            $host_line->append_widget($octoprint_host_widget);
 | 
			
		||||
            $host_line->append_widget($octoprint_host_browse);
 | 
			
		||||
            $host_line->append_widget($octoprint_host_test);
 | 
			
		||||
            $optgroup->append_line($host_line);
 | 
			
		||||
            $optgroup->append_single_option_line('octoprint_apikey');
 | 
			
		||||
        }
 | 
			
		||||
        {
 | 
			
		||||
            my $optgroup = $page->new_optgroup('Firmware');
 | 
			
		||||
            $optgroup->append_single_option_line('gcode_flavor');
 | 
			
		||||
            $optgroup->append_single_option_line('use_relative_e_distances');
 | 
			
		||||
        }
 | 
			
		||||
        {
 | 
			
		||||
            my $optgroup = $page->new_optgroup('Advanced');
 | 
			
		||||
            $optgroup->append_single_option_line('use_relative_e_distances');
 | 
			
		||||
            $optgroup->append_single_option_line('use_firmware_retraction');
 | 
			
		||||
            $optgroup->append_single_option_line('use_volumetric_e');
 | 
			
		||||
            $optgroup->append_single_option_line('pressure_advance');
 | 
			
		||||
            $optgroup->append_single_option_line('vibration_limit');
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -1201,6 +1228,11 @@ sub _update {
 | 
			
		|||
    my $config = $self->{config};
 | 
			
		||||
    
 | 
			
		||||
    $self->get_field('serial_speed')->toggle($config->get('serial_port'));
 | 
			
		||||
    if ($config->get('octoprint_host') && eval "use LWP::UserAgent; 1") {
 | 
			
		||||
        $self->{octoprint_host_test_btn}->Enable;
 | 
			
		||||
    } else {
 | 
			
		||||
        $self->{octoprint_host_test_btn}->Disable;
 | 
			
		||||
    }
 | 
			
		||||
    $self->get_field('octoprint_apikey')->toggle($config->get('octoprint_host'));
 | 
			
		||||
    
 | 
			
		||||
    my $have_multiple_extruders = $self->{extruders_count} > 1;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										462
									
								
								lib/Slic3r/Layer/PerimeterGenerator.pm
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										462
									
								
								lib/Slic3r/Layer/PerimeterGenerator.pm
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,462 @@
 | 
			
		|||
package Slic3r::Layer::PerimeterGenerator;
 | 
			
		||||
use Moo;
 | 
			
		||||
 | 
			
		||||
use Slic3r::ExtrusionLoop ':roles';
 | 
			
		||||
use Slic3r::ExtrusionPath ':roles';
 | 
			
		||||
use Slic3r::Geometry qw(scale unscale chained_path);
 | 
			
		||||
use Slic3r::Geometry::Clipper qw(union_ex diff diff_ex intersection_ex offset offset2
 | 
			
		||||
    offset_ex offset2_ex union_pt intersection_ppl diff_ppl);
 | 
			
		||||
use Slic3r::Surface ':types';
 | 
			
		||||
 | 
			
		||||
has 'slices'                => (is => 'ro', required => 1);  # SurfaceCollection
 | 
			
		||||
has 'lower_slices'          => (is => 'ro', required => 0);
 | 
			
		||||
has 'layer_height'          => (is => 'ro', required => 1);
 | 
			
		||||
has 'layer_id'              => (is => 'ro', required => 0, default => sub { -1 });
 | 
			
		||||
has 'perimeter_flow'        => (is => 'ro', required => 1);
 | 
			
		||||
has 'ext_perimeter_flow'    => (is => 'ro', required => 1);
 | 
			
		||||
has 'overhang_flow'         => (is => 'ro', required => 1);
 | 
			
		||||
has 'solid_infill_flow'     => (is => 'ro', required => 1);
 | 
			
		||||
has 'config'                => (is => 'ro', default => sub { Slic3r::Config::PrintRegion->new });
 | 
			
		||||
has 'print_config'          => (is => 'ro', default => sub { Slic3r::Config::Print->new });
 | 
			
		||||
has '_lower_slices_p'       => (is => 'rw', default => sub { [] });
 | 
			
		||||
has '_holes_pt'             => (is => 'rw');
 | 
			
		||||
has '_ext_mm3_per_mm'       => (is => 'rw');
 | 
			
		||||
has '_mm3_per_mm'           => (is => 'rw');
 | 
			
		||||
has '_mm3_per_mm_overhang'  => (is => 'rw');
 | 
			
		||||
has '_thin_wall_polylines'  => (is => 'rw', default => sub { [] });
 | 
			
		||||
 | 
			
		||||
# generated loops will be put here
 | 
			
		||||
has 'loops'         => (is => 'ro', default => sub { Slic3r::ExtrusionPath::Collection->new });
 | 
			
		||||
 | 
			
		||||
# generated gap fills will be put here
 | 
			
		||||
has 'gap_fill'      => (is => 'ro', default => sub { Slic3r::ExtrusionPath::Collection->new });
 | 
			
		||||
 | 
			
		||||
# generated fill surfaces will be put here
 | 
			
		||||
has 'fill_surfaces' => (is => 'ro', default => sub { Slic3r::Surface::Collection->new });
 | 
			
		||||
 | 
			
		||||
sub BUILDARGS {
 | 
			
		||||
    my ($class, %args) = @_;
 | 
			
		||||
    
 | 
			
		||||
    if (my $flow = delete $args{flow}) {
 | 
			
		||||
        $args{perimeter_flow}       //= $flow;
 | 
			
		||||
        $args{ext_perimeter_flow}   //= $flow;
 | 
			
		||||
        $args{overhang_flow}        //= $flow;
 | 
			
		||||
        $args{solid_infill_flow}    //= $flow;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return { %args };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub process {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    
 | 
			
		||||
    # other perimeters
 | 
			
		||||
    $self->_mm3_per_mm($self->perimeter_flow->mm3_per_mm);
 | 
			
		||||
    my $pwidth              = $self->perimeter_flow->scaled_width;
 | 
			
		||||
    my $pspacing            = $self->perimeter_flow->scaled_spacing;
 | 
			
		||||
    
 | 
			
		||||
    # external perimeters
 | 
			
		||||
    $self->_ext_mm3_per_mm($self->ext_perimeter_flow->mm3_per_mm);
 | 
			
		||||
    my $ext_pwidth          = $self->ext_perimeter_flow->scaled_width;
 | 
			
		||||
    my $ext_pspacing        = scale($self->ext_perimeter_flow->spacing_to($self->perimeter_flow));
 | 
			
		||||
    
 | 
			
		||||
    # overhang perimeters
 | 
			
		||||
    $self->_mm3_per_mm_overhang($self->overhang_flow->mm3_per_mm);
 | 
			
		||||
    
 | 
			
		||||
    # solid infill
 | 
			
		||||
    my $ispacing            = $self->solid_infill_flow->scaled_spacing;
 | 
			
		||||
    my $gap_area_threshold  = $pwidth ** 2;
 | 
			
		||||
    
 | 
			
		||||
    # Calculate the minimum required spacing between two adjacent traces.
 | 
			
		||||
    # This should be equal to the nominal flow spacing but we experiment
 | 
			
		||||
    # with some tolerance in order to avoid triggering medial axis when
 | 
			
		||||
    # some squishing might work. Loops are still spaced by the entire
 | 
			
		||||
    # flow spacing; this only applies to collapsing parts.
 | 
			
		||||
    my $min_spacing         = $pspacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
 | 
			
		||||
    my $ext_min_spacing     = $ext_pspacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
 | 
			
		||||
    
 | 
			
		||||
    # prepare grown lower layer slices for overhang detection
 | 
			
		||||
    if ($self->lower_slices && $self->config->overhangs) {
 | 
			
		||||
        # We consider overhang any part where the entire nozzle diameter is not supported by the
 | 
			
		||||
        # lower layer, so we take lower slices and offset them by half the nozzle diameter used 
 | 
			
		||||
        # in the current layer
 | 
			
		||||
        my $nozzle_diameter = $self->print_config->get_at('nozzle_diameter', $self->config->perimeter_extruder-1);
 | 
			
		||||
        
 | 
			
		||||
        $self->_lower_slices_p(
 | 
			
		||||
            offset([ map @$_, @{$self->lower_slices} ], scale +$nozzle_diameter/2)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    # we need to process each island separately because we might have different
 | 
			
		||||
    # extra perimeters for each one
 | 
			
		||||
    foreach my $surface (@{$self->slices}) {
 | 
			
		||||
        my @contours    = ();    # array of Polygons with ccw orientation
 | 
			
		||||
        my @holes       = ();    # array of Polygons with cw orientation
 | 
			
		||||
        my @thin_walls  = ();    # array of ExPolygons
 | 
			
		||||
        
 | 
			
		||||
        # detect how many perimeters must be generated for this island
 | 
			
		||||
        my $loop_number = $self->config->perimeters + ($surface->extra_perimeters || 0);
 | 
			
		||||
        
 | 
			
		||||
        my @last = @{$surface->expolygon};
 | 
			
		||||
        my @gaps = ();    # array of ExPolygons
 | 
			
		||||
        if ($loop_number > 0) {
 | 
			
		||||
            # we loop one time more than needed in order to find gaps after the last perimeter was applied
 | 
			
		||||
            for my $i (1 .. ($loop_number+1)) {  # outer loop is 1
 | 
			
		||||
                my @offsets = ();
 | 
			
		||||
                if ($i == 1) {
 | 
			
		||||
                    # the minimum thickness of a single loop is:
 | 
			
		||||
                    # ext_width/2 + ext_spacing/2 + spacing/2 + width/2
 | 
			
		||||
                    if ($self->config->thin_walls) {
 | 
			
		||||
                        @offsets = @{offset2(
 | 
			
		||||
                            \@last,
 | 
			
		||||
                            -(0.5*$ext_pwidth + 0.5*$ext_min_spacing - 1),
 | 
			
		||||
                            +(0.5*$ext_min_spacing - 1),
 | 
			
		||||
                        )};
 | 
			
		||||
                    } else {
 | 
			
		||||
                        @offsets = @{offset(
 | 
			
		||||
                            \@last,
 | 
			
		||||
                            -0.5*$ext_pwidth,
 | 
			
		||||
                        )};
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    # look for thin walls
 | 
			
		||||
                    if ($self->config->thin_walls) {
 | 
			
		||||
                        my $diff = diff_ex(
 | 
			
		||||
                            \@last,
 | 
			
		||||
                            offset(\@offsets, +0.5*$ext_pwidth),
 | 
			
		||||
                            1,  # medial axis requires non-overlapping geometry
 | 
			
		||||
                        );
 | 
			
		||||
                        push @thin_walls, @$diff;
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    my $distance = ($i == 2) ? $ext_pspacing : $pspacing;
 | 
			
		||||
                    
 | 
			
		||||
                    if ($self->config->thin_walls) {
 | 
			
		||||
                        @offsets = @{offset2(
 | 
			
		||||
                            \@last,
 | 
			
		||||
                            -($distance + 0.5*$min_spacing - 1),
 | 
			
		||||
                            +(0.5*$min_spacing - 1),
 | 
			
		||||
                        )};
 | 
			
		||||
                    } else {
 | 
			
		||||
                        @offsets = @{offset(
 | 
			
		||||
                            \@last,
 | 
			
		||||
                            -$distance,
 | 
			
		||||
                        )};
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    # look for gaps
 | 
			
		||||
                    if ($self->config->gap_fill_speed > 0 && $self->config->fill_density > 0) {
 | 
			
		||||
                        # not using safety offset here would "detect" very narrow gaps
 | 
			
		||||
                        # (but still long enough to escape the area threshold) that gap fill
 | 
			
		||||
                        # won't be able to fill but we'd still remove from infill area
 | 
			
		||||
                        my $diff = diff_ex(
 | 
			
		||||
                            offset(\@last, -0.5*$pspacing),
 | 
			
		||||
                            offset(\@offsets, +0.5*$pspacing + 10),  # safety offset
 | 
			
		||||
                        );
 | 
			
		||||
                        push @gaps, grep abs($_->area) >= $gap_area_threshold, @$diff;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            
 | 
			
		||||
                last if !@offsets;
 | 
			
		||||
                last if $i > $loop_number; # we were only looking for gaps this time
 | 
			
		||||
            
 | 
			
		||||
                # clone polygons because these ExPolygons will go out of scope very soon
 | 
			
		||||
                @last = @offsets;
 | 
			
		||||
                foreach my $polygon (@offsets) {
 | 
			
		||||
                    if ($polygon->is_counter_clockwise) {
 | 
			
		||||
                        push @contours, $polygon;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        push @holes, $polygon;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        # fill gaps
 | 
			
		||||
        if (@gaps) {
 | 
			
		||||
            if (0) {
 | 
			
		||||
                require "Slic3r/SVG.pm";
 | 
			
		||||
                Slic3r::SVG::output(
 | 
			
		||||
                    "gaps.svg",
 | 
			
		||||
                    expolygons => \@gaps,
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            # where $pwidth < thickness < 2*$pspacing, infill with width = 1.5*$pwidth
 | 
			
		||||
            # where 0.5*$pwidth < thickness < $pwidth, infill with width = 0.5*$pwidth
 | 
			
		||||
            my @gap_sizes = (
 | 
			
		||||
                [ $pwidth, 2*$pspacing, unscale 1.5*$pwidth ],
 | 
			
		||||
                [ 0.5*$pwidth, $pwidth, unscale 0.5*$pwidth ],
 | 
			
		||||
            );
 | 
			
		||||
            foreach my $gap_size (@gap_sizes) {
 | 
			
		||||
                my @gap_fill = $self->_fill_gaps(@$gap_size, \@gaps);
 | 
			
		||||
                $self->gap_fill->append($_) for @gap_fill;
 | 
			
		||||
            
 | 
			
		||||
                # Make sure we don't infill narrow parts that are already gap-filled
 | 
			
		||||
                # (we only consider this surface's gaps to reduce the diff() complexity).
 | 
			
		||||
                # Growing actual extrusions ensures that gaps not filled by medial axis
 | 
			
		||||
                # are not subtracted from fill surfaces (they might be too short gaps
 | 
			
		||||
                # that medial axis skips but infill might join with other infill regions
 | 
			
		||||
                # and use zigzag).
 | 
			
		||||
                my $w = $gap_size->[2];
 | 
			
		||||
                my @filled = map {
 | 
			
		||||
                    @{($_->isa('Slic3r::ExtrusionLoop') ? $_->polygon->split_at_first_point : $_->polyline)
 | 
			
		||||
                        ->grow(scale $w/2)};
 | 
			
		||||
                } @gap_fill;
 | 
			
		||||
                @last = @{diff(\@last, \@filled)};
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        # create one more offset to be used as boundary for fill
 | 
			
		||||
        # we offset by half the perimeter spacing (to get to the actual infill boundary)
 | 
			
		||||
        # and then we offset back and forth by half the infill spacing to only consider the
 | 
			
		||||
        # non-collapsing regions
 | 
			
		||||
        my $min_perimeter_infill_spacing = $ispacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
 | 
			
		||||
        $self->fill_surfaces->append($_)
 | 
			
		||||
            for map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL),  # use a bogus surface type
 | 
			
		||||
                @{offset2_ex(
 | 
			
		||||
                    [ map @{$_->simplify_p(&Slic3r::SCALED_RESOLUTION)}, @{union_ex(\@last)} ],
 | 
			
		||||
                    -($pspacing/2 + $min_perimeter_infill_spacing/2),
 | 
			
		||||
                    +$min_perimeter_infill_spacing/2,
 | 
			
		||||
                )};
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
        # process thin walls by collapsing slices to single passes
 | 
			
		||||
        if (@thin_walls) {
 | 
			
		||||
            # the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
 | 
			
		||||
            # (actually, something larger than that still may exist due to mitering or other causes)
 | 
			
		||||
            my $min_width = $pwidth / 4;
 | 
			
		||||
            @thin_walls = @{offset2_ex([ map @$_, @thin_walls ], -$min_width/2, +$min_width/2)};
 | 
			
		||||
        
 | 
			
		||||
            # the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
 | 
			
		||||
            $self->_thin_wall_polylines([ map @{$_->medial_axis($pwidth + $pspacing, $min_width)}, @thin_walls ]);
 | 
			
		||||
            Slic3r::debugf "  %d thin walls detected\n", scalar(@{$self->_thin_wall_polylines}) if $Slic3r::debug;
 | 
			
		||||
        
 | 
			
		||||
            if (0) {
 | 
			
		||||
                require "Slic3r/SVG.pm";
 | 
			
		||||
                Slic3r::SVG::output(
 | 
			
		||||
                    "medial_axis.svg",
 | 
			
		||||
                    no_arrows => 1,
 | 
			
		||||
                    expolygons      => \@thin_walls,
 | 
			
		||||
                    green_polylines => [ map $_->polygon->split_at_first_point, @{$self->perimeters} ],
 | 
			
		||||
                    red_polylines   => $self->_thin_wall_polylines,
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        # find nesting hierarchies separately for contours and holes
 | 
			
		||||
        my $contours_pt = union_pt(\@contours);
 | 
			
		||||
        $self->_holes_pt(union_pt(\@holes));
 | 
			
		||||
    
 | 
			
		||||
        # order loops from inner to outer (in terms of object slices)
 | 
			
		||||
        my @loops = $self->_traverse_pt($contours_pt, 0, 1);
 | 
			
		||||
    
 | 
			
		||||
        # if brim will be printed, reverse the order of perimeters so that
 | 
			
		||||
        # we continue inwards after having finished the brim
 | 
			
		||||
        # TODO: add test for perimeter order
 | 
			
		||||
        @loops = reverse @loops
 | 
			
		||||
            if $self->config->external_perimeters_first
 | 
			
		||||
                || ($self->layer_id == 0 && $self->print_config->brim_width > 0);
 | 
			
		||||
        
 | 
			
		||||
        # append perimeters for this slice as a collection
 | 
			
		||||
        $self->loops->append(Slic3r::ExtrusionPath::Collection->new(@loops));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _traverse_pt {
 | 
			
		||||
    my ($self, $polynodes, $depth, $is_contour) = @_;
 | 
			
		||||
    
 | 
			
		||||
    # convert all polynodes to ExtrusionLoop objects
 | 
			
		||||
    my $collection = Slic3r::ExtrusionPath::Collection->new;  # temporary collection
 | 
			
		||||
    my @children = ();
 | 
			
		||||
    foreach my $polynode (@$polynodes) {
 | 
			
		||||
        my $polygon = ($polynode->{outer} // $polynode->{hole})->clone;
 | 
			
		||||
        
 | 
			
		||||
        my $role        = EXTR_ROLE_PERIMETER;
 | 
			
		||||
        my $loop_role   = EXTRL_ROLE_DEFAULT;
 | 
			
		||||
        
 | 
			
		||||
        my $root_level  = $depth == 0;
 | 
			
		||||
        my $no_children = !@{ $polynode->{children} };
 | 
			
		||||
        my $is_external = $is_contour ? $root_level : $no_children;
 | 
			
		||||
        my $is_internal = $is_contour ? $no_children : $root_level;
 | 
			
		||||
        if ($is_contour && $is_internal) {
 | 
			
		||||
            # internal perimeters are root level in case of holes
 | 
			
		||||
            # and items with no children in case of contours
 | 
			
		||||
            # Note that we set loop role to ContourInternalPerimeter
 | 
			
		||||
            # also when loop is both internal and external (i.e.
 | 
			
		||||
            # there's only one contour loop).
 | 
			
		||||
            $loop_role  = EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER;
 | 
			
		||||
        }
 | 
			
		||||
        if ($is_external) {
 | 
			
		||||
            # external perimeters are root level in case of contours
 | 
			
		||||
            # and items with no children in case of holes
 | 
			
		||||
            $role       = EXTR_ROLE_EXTERNAL_PERIMETER;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        # detect overhanging/bridging perimeters
 | 
			
		||||
        my @paths = ();
 | 
			
		||||
        if ($self->config->overhangs && $self->layer_id > 0) {
 | 
			
		||||
            # get non-overhang paths by intersecting this loop with the grown lower slices
 | 
			
		||||
            foreach my $polyline (@{ intersection_ppl([ $polygon ], $self->_lower_slices_p) }) {
 | 
			
		||||
                push @paths, Slic3r::ExtrusionPath->new(
 | 
			
		||||
                    polyline        => $polyline,
 | 
			
		||||
                    role            => $role,
 | 
			
		||||
                    mm3_per_mm      => ($is_external ? $self->_ext_mm3_per_mm : $self->_mm3_per_mm),
 | 
			
		||||
                    width           => ($is_external ? $self->ext_perimeter_flow->width : $self->perimeter_flow->width),
 | 
			
		||||
                    height          => $self->layer_height,
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            # get overhang paths by checking what parts of this loop fall 
 | 
			
		||||
            # outside the grown lower slices (thus where the distance between
 | 
			
		||||
            # the loop centerline and original lower slices is >= half nozzle diameter
 | 
			
		||||
            foreach my $polyline (@{ diff_ppl([ $polygon ], $self->_lower_slices_p) }) {
 | 
			
		||||
                push @paths, Slic3r::ExtrusionPath->new(
 | 
			
		||||
                    polyline        => $polyline,
 | 
			
		||||
                    role            => EXTR_ROLE_OVERHANG_PERIMETER,
 | 
			
		||||
                    mm3_per_mm      => $self->_mm3_per_mm_overhang,
 | 
			
		||||
                    width           => $self->overhang_flow->width,
 | 
			
		||||
                    height          => $self->layer_height,
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            # reapply the nearest point search for starting point
 | 
			
		||||
            # (clone because the collection gets DESTROY'ed)
 | 
			
		||||
            # We allow polyline reversal because Clipper may have randomly
 | 
			
		||||
            # reversed polylines during clipping.
 | 
			
		||||
            my $collection = Slic3r::ExtrusionPath::Collection->new(@paths); # temporary collection
 | 
			
		||||
            @paths = map $_->clone, @{$collection->chained_path(0)};
 | 
			
		||||
        } else {
 | 
			
		||||
            push @paths, Slic3r::ExtrusionPath->new(
 | 
			
		||||
                polyline        => $polygon->split_at_first_point,
 | 
			
		||||
                role            => $role,
 | 
			
		||||
                mm3_per_mm      => $self->_mm3_per_mm,
 | 
			
		||||
                width           => $self->perimeter_flow->width,
 | 
			
		||||
                height          => $self->layer_height,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        my $loop = Slic3r::ExtrusionLoop->new_from_paths(@paths);
 | 
			
		||||
        $loop->role($loop_role);
 | 
			
		||||
        
 | 
			
		||||
        # return ccw contours and cw holes
 | 
			
		||||
        # GCode.pm will convert all of them to ccw, but it needs to know
 | 
			
		||||
        # what the holes are in order to compute the correct inwards move
 | 
			
		||||
        # We do this on the final Loop object because overhang clipping
 | 
			
		||||
        # does not keep orientation.
 | 
			
		||||
        if ($is_contour) {
 | 
			
		||||
            $loop->make_counter_clockwise;
 | 
			
		||||
        } else {
 | 
			
		||||
            $loop->make_clockwise;
 | 
			
		||||
        }
 | 
			
		||||
        $collection->append($loop);
 | 
			
		||||
        
 | 
			
		||||
        # save the children
 | 
			
		||||
        push @children, $polynode->{children};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # if we're handling the top-level contours, add thin walls as candidates too
 | 
			
		||||
    # in order to include them in the nearest-neighbor search
 | 
			
		||||
    if ($is_contour && $depth == 0) {
 | 
			
		||||
        foreach my $polyline (@{$self->_thin_wall_polylines}) {
 | 
			
		||||
            $collection->append(Slic3r::ExtrusionPath->new(
 | 
			
		||||
                polyline        => $polyline,
 | 
			
		||||
                role            => EXTR_ROLE_EXTERNAL_PERIMETER,
 | 
			
		||||
                mm3_per_mm      => $self->_mm3_per_mm,
 | 
			
		||||
                width           => $self->perimeter_flow->width,
 | 
			
		||||
                height          => $self->layer_height,
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    # use a nearest neighbor search to order these children
 | 
			
		||||
    # TODO: supply second argument to chained_path() too?
 | 
			
		||||
    # (We used to skip this chained_path() when $is_contour &&
 | 
			
		||||
    # $depth == 0 because slices are ordered at G_code export 
 | 
			
		||||
    # time, but multiple top-level perimeters might belong to
 | 
			
		||||
    # the same slice actually, so that was a broken optimization.)
 | 
			
		||||
    # We supply no_reverse = false because we want to permit reversal
 | 
			
		||||
    # of thin walls, but we rely on the fact that loops will never
 | 
			
		||||
    # be reversed anyway.
 | 
			
		||||
    my $sorted_collection = $collection->chained_path_indices(0);
 | 
			
		||||
    my @orig_indices = @{$sorted_collection->orig_indices};
 | 
			
		||||
    
 | 
			
		||||
    my @loops = ();
 | 
			
		||||
    foreach my $loop (@$sorted_collection) {
 | 
			
		||||
        my $orig_index = shift @orig_indices;
 | 
			
		||||
        
 | 
			
		||||
        if ($loop->isa('Slic3r::ExtrusionPath')) {
 | 
			
		||||
            push @loops, $loop->clone;
 | 
			
		||||
        } else {
 | 
			
		||||
            # if this is an external contour find all holes belonging to this contour(s)
 | 
			
		||||
            # and prepend them
 | 
			
		||||
            if ($is_contour && $depth == 0) {
 | 
			
		||||
                # $loop is the outermost loop of an island
 | 
			
		||||
                my @holes = ();
 | 
			
		||||
                for (my $i = 0; $i <= $#{$self->_holes_pt}; $i++) {
 | 
			
		||||
                    if ($loop->polygon->contains_point($self->_holes_pt->[$i]{outer}->first_point)) {
 | 
			
		||||
                        push @holes, splice @{$self->_holes_pt}, $i, 1;  # remove from candidates to reduce complexity
 | 
			
		||||
                        $i--;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                # order holes efficiently
 | 
			
		||||
                @holes = @holes[@{chained_path([ map {($_->{outer} // $_->{hole})->first_point} @holes ])}];
 | 
			
		||||
                
 | 
			
		||||
                push @loops, reverse map $self->_traverse_pt([$_], 0, 0), @holes;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            # traverse children and prepend them to this loop
 | 
			
		||||
            push @loops, $self->_traverse_pt($children[$orig_index], $depth+1, $is_contour);
 | 
			
		||||
            push @loops, $loop->clone;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return @loops;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _fill_gaps {
 | 
			
		||||
    my ($self, $min, $max, $w, $gaps) = @_;
 | 
			
		||||
    
 | 
			
		||||
    my $this = diff_ex(
 | 
			
		||||
        offset2([ map @$_, @$gaps ], -$min/2, +$min/2),
 | 
			
		||||
        offset2([ map @$_, @$gaps ], -$max/2, +$max/2),
 | 
			
		||||
        1,
 | 
			
		||||
    );
 | 
			
		||||
    my @polylines = map @{$_->medial_axis($max, $min/2)}, @$this;
 | 
			
		||||
    return if !@polylines;
 | 
			
		||||
    
 | 
			
		||||
    Slic3r::debugf "  %d gaps filled with extrusion width = %s\n", scalar @$this, $w
 | 
			
		||||
        if @$this;
 | 
			
		||||
 | 
			
		||||
    #my $flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL, 0, $w);
 | 
			
		||||
    my $flow = Slic3r::Flow->new(
 | 
			
		||||
        width           => $w,
 | 
			
		||||
        height          => $self->layer_height,
 | 
			
		||||
        nozzle_diameter => $self->solid_infill_flow->nozzle_diameter,
 | 
			
		||||
    );
 | 
			
		||||
    
 | 
			
		||||
    my %path_args = (
 | 
			
		||||
        role        => EXTR_ROLE_GAPFILL,
 | 
			
		||||
        mm3_per_mm  => $flow->mm3_per_mm,
 | 
			
		||||
        width       => $flow->width,
 | 
			
		||||
        height      => $self->layer_height,
 | 
			
		||||
    );
 | 
			
		||||
    
 | 
			
		||||
    my @entities = ();
 | 
			
		||||
    foreach my $polyline (@polylines) {
 | 
			
		||||
        #if ($polylines[$i]->isa('Slic3r::Polygon')) {
 | 
			
		||||
        #    my $loop = Slic3r::ExtrusionLoop->new;
 | 
			
		||||
        #    $loop->append(Slic3r::ExtrusionPath->new(polyline => $polylines[$i]->split_at_first_point, %path_args));
 | 
			
		||||
        #    $polylines[$i] = $loop;
 | 
			
		||||
        if ($polyline->is_valid && $polyline->first_point->coincides_with($polyline->last_point)) {
 | 
			
		||||
            # since medial_axis() now returns only Polyline objects, detect loops here
 | 
			
		||||
            push @entities, my $loop = Slic3r::ExtrusionLoop->new;
 | 
			
		||||
            $loop->append(Slic3r::ExtrusionPath->new(polyline => $polyline, %path_args));
 | 
			
		||||
        } else {
 | 
			
		||||
            push @entities, Slic3r::ExtrusionPath->new(polyline => $polyline, %path_args);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return @entities;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
1;
 | 
			
		||||
| 
						 | 
				
			
			@ -2,14 +2,11 @@ package Slic3r::Layer::Region;
 | 
			
		|||
use strict;
 | 
			
		||||
use warnings;
 | 
			
		||||
 | 
			
		||||
use List::Util qw(sum first);
 | 
			
		||||
use Slic3r::ExtrusionLoop ':roles';
 | 
			
		||||
use Slic3r::ExtrusionPath ':roles';
 | 
			
		||||
use Slic3r::Flow ':roles';
 | 
			
		||||
use Slic3r::Geometry qw(PI A B scale unscale chained_path);
 | 
			
		||||
use Slic3r::Geometry::Clipper qw(union_ex diff_ex intersection_ex 
 | 
			
		||||
    offset offset_ex offset2 offset2_ex union_pt diff intersection
 | 
			
		||||
    union diff intersection_ppl diff_ppl);
 | 
			
		||||
use Slic3r::Geometry qw(scale);
 | 
			
		||||
use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex 
 | 
			
		||||
    );
 | 
			
		||||
use Slic3r::Surface ':types';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -31,418 +28,28 @@ sub config  { return $_[0]->region->config; }
 | 
			
		|||
sub make_perimeters {
 | 
			
		||||
    my ($self, $slices, $fill_surfaces) = @_;
 | 
			
		||||
    
 | 
			
		||||
    # other perimeters
 | 
			
		||||
    my $perimeter_flow      = $self->flow(FLOW_ROLE_PERIMETER);
 | 
			
		||||
    my $mm3_per_mm          = $perimeter_flow->mm3_per_mm;
 | 
			
		||||
    my $pwidth              = $perimeter_flow->scaled_width;
 | 
			
		||||
    my $pspacing            = $perimeter_flow->scaled_spacing;
 | 
			
		||||
    
 | 
			
		||||
    # external perimeters
 | 
			
		||||
    my $ext_perimeter_flow  = $self->flow(FLOW_ROLE_EXTERNAL_PERIMETER);
 | 
			
		||||
    my $ext_mm3_per_mm      = $ext_perimeter_flow->mm3_per_mm;
 | 
			
		||||
    my $ext_pwidth          = $ext_perimeter_flow->scaled_width;
 | 
			
		||||
    my $ext_pspacing        = scale($ext_perimeter_flow->spacing_to($perimeter_flow));
 | 
			
		||||
    
 | 
			
		||||
    # overhang perimeters
 | 
			
		||||
    my $overhang_flow       = $self->region->flow(FLOW_ROLE_PERIMETER, -1, 1, 0, -1, $self->layer->object);
 | 
			
		||||
    my $mm3_per_mm_overhang = $overhang_flow->mm3_per_mm;
 | 
			
		||||
    
 | 
			
		||||
    # solid infill
 | 
			
		||||
    my $solid_infill_flow   = $self->flow(FLOW_ROLE_SOLID_INFILL);
 | 
			
		||||
    my $ispacing            = $solid_infill_flow->scaled_spacing;
 | 
			
		||||
    my $gap_area_threshold  = $pwidth ** 2;
 | 
			
		||||
    
 | 
			
		||||
    # Calculate the minimum required spacing between two adjacent traces.
 | 
			
		||||
    # This should be equal to the nominal flow spacing but we experiment
 | 
			
		||||
    # with some tolerance in order to avoid triggering medial axis when
 | 
			
		||||
    # some squishing might work. Loops are still spaced by the entire
 | 
			
		||||
    # flow spacing; this only applies to collapsing parts.
 | 
			
		||||
    my $min_spacing         = $pspacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
 | 
			
		||||
    my $ext_min_spacing     = $ext_pspacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
 | 
			
		||||
    
 | 
			
		||||
    $self->perimeters->clear;
 | 
			
		||||
    $self->thin_fills->clear;
 | 
			
		||||
    
 | 
			
		||||
    my @contours    = ();    # array of Polygons with ccw orientation
 | 
			
		||||
    my @holes       = ();    # array of Polygons with cw orientation
 | 
			
		||||
    my @thin_walls  = ();    # array of ExPolygons
 | 
			
		||||
    
 | 
			
		||||
    # we need to process each island separately because we might have different
 | 
			
		||||
    # extra perimeters for each one
 | 
			
		||||
    foreach my $surface (@$slices) {
 | 
			
		||||
        # detect how many perimeters must be generated for this island
 | 
			
		||||
        my $loop_number = $self->config->perimeters + ($surface->extra_perimeters || 0);
 | 
			
		||||
    my $generator = Slic3r::Layer::PerimeterGenerator->new(
 | 
			
		||||
        # input:
 | 
			
		||||
        config              => $self->config,
 | 
			
		||||
        print_config        => $self->layer->print->config,
 | 
			
		||||
        layer_height        => $self->height,
 | 
			
		||||
        layer_id            => $self->layer->id,
 | 
			
		||||
        slices              => $slices,
 | 
			
		||||
        lower_slices        => defined($self->layer->lower_layer) ? $self->layer->lower_layer->slices : undef,
 | 
			
		||||
        perimeter_flow      => $self->flow(FLOW_ROLE_PERIMETER),
 | 
			
		||||
        ext_perimeter_flow  => $self->flow(FLOW_ROLE_EXTERNAL_PERIMETER),
 | 
			
		||||
        overhang_flow       => $self->region->flow(FLOW_ROLE_PERIMETER, -1, 1, 0, -1, $self->layer->object),
 | 
			
		||||
        solid_infill_flow   => $self->flow(FLOW_ROLE_SOLID_INFILL),
 | 
			
		||||
        
 | 
			
		||||
        my @last = @{$surface->expolygon};
 | 
			
		||||
        my @gaps = ();    # array of ExPolygons
 | 
			
		||||
        if ($loop_number > 0) {
 | 
			
		||||
            # we loop one time more than needed in order to find gaps after the last perimeter was applied
 | 
			
		||||
            for my $i (1 .. ($loop_number+1)) {  # outer loop is 1
 | 
			
		||||
                my @offsets = ();
 | 
			
		||||
                if ($i == 1) {
 | 
			
		||||
                    # the minimum thickness of a single loop is:
 | 
			
		||||
                    # ext_width/2 + ext_spacing/2 + spacing/2 + width/2
 | 
			
		||||
                    if ($self->config->thin_walls) {
 | 
			
		||||
                        @offsets = @{offset2(
 | 
			
		||||
                            \@last,
 | 
			
		||||
                            -(0.5*$ext_pwidth + 0.5*$ext_min_spacing - 1),
 | 
			
		||||
                            +(0.5*$ext_min_spacing - 1),
 | 
			
		||||
                        )};
 | 
			
		||||
                    } else {
 | 
			
		||||
                        @offsets = @{offset(
 | 
			
		||||
                            \@last,
 | 
			
		||||
                            -0.5*$ext_pwidth,
 | 
			
		||||
                        )};
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    # look for thin walls
 | 
			
		||||
                    if ($self->config->thin_walls) {
 | 
			
		||||
                        my $diff = diff_ex(
 | 
			
		||||
                            \@last,
 | 
			
		||||
                            offset(\@offsets, +0.5*$ext_pwidth),
 | 
			
		||||
                            1,  # medial axis requires non-overlapping geometry
 | 
			
		||||
                        );
 | 
			
		||||
                        push @thin_walls, @$diff;
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    my $distance = ($i == 2) ? $ext_pspacing : $pspacing;
 | 
			
		||||
                    
 | 
			
		||||
                    if ($self->config->thin_walls) {
 | 
			
		||||
                        @offsets = @{offset2(
 | 
			
		||||
                            \@last,
 | 
			
		||||
                            -($distance + 0.5*$min_spacing - 1),
 | 
			
		||||
                            +(0.5*$min_spacing - 1),
 | 
			
		||||
                        )};
 | 
			
		||||
                    } else {
 | 
			
		||||
                        @offsets = @{offset(
 | 
			
		||||
                            \@last,
 | 
			
		||||
                            -$distance,
 | 
			
		||||
                        )};
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    # look for gaps
 | 
			
		||||
                    if ($self->region->config->gap_fill_speed > 0 && $self->config->fill_density > 0) {
 | 
			
		||||
                        # not using safety offset here would "detect" very narrow gaps
 | 
			
		||||
                        # (but still long enough to escape the area threshold) that gap fill
 | 
			
		||||
                        # won't be able to fill but we'd still remove from infill area
 | 
			
		||||
                        my $diff = diff_ex(
 | 
			
		||||
                            offset(\@last, -0.5*$pspacing),
 | 
			
		||||
                            offset(\@offsets, +0.5*$pspacing + 10),  # safety offset
 | 
			
		||||
                        );
 | 
			
		||||
                        push @gaps, grep abs($_->area) >= $gap_area_threshold, @$diff;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            
 | 
			
		||||
                last if !@offsets;
 | 
			
		||||
                last if $i > $loop_number; # we were only looking for gaps this time
 | 
			
		||||
            
 | 
			
		||||
                # clone polygons because these ExPolygons will go out of scope very soon
 | 
			
		||||
                @last = @offsets;
 | 
			
		||||
                foreach my $polygon (@offsets) {
 | 
			
		||||
                    if ($polygon->is_counter_clockwise) {
 | 
			
		||||
                        push @contours, $polygon;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        push @holes, $polygon;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        # fill gaps
 | 
			
		||||
        if (@gaps) {
 | 
			
		||||
            if (0) {
 | 
			
		||||
                require "Slic3r/SVG.pm";
 | 
			
		||||
                Slic3r::SVG::output(
 | 
			
		||||
                    "gaps.svg",
 | 
			
		||||
                    expolygons => \@gaps,
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            # where $pwidth < thickness < 2*$pspacing, infill with width = 1.5*$pwidth
 | 
			
		||||
            # where 0.5*$pwidth < thickness < $pwidth, infill with width = 0.5*$pwidth
 | 
			
		||||
            my @gap_sizes = (
 | 
			
		||||
                [ $pwidth, 2*$pspacing, unscale 1.5*$pwidth ],
 | 
			
		||||
                [ 0.5*$pwidth, $pwidth, unscale 0.5*$pwidth ],
 | 
			
		||||
            );
 | 
			
		||||
            foreach my $gap_size (@gap_sizes) {
 | 
			
		||||
                my @gap_fill = $self->_fill_gaps(@$gap_size, \@gaps);
 | 
			
		||||
                $self->thin_fills->append($_) for @gap_fill;
 | 
			
		||||
            
 | 
			
		||||
                # Make sure we don't infill narrow parts that are already gap-filled
 | 
			
		||||
                # (we only consider this surface's gaps to reduce the diff() complexity).
 | 
			
		||||
                # Growing actual extrusions ensures that gaps not filled by medial axis
 | 
			
		||||
                # are not subtracted from fill surfaces (they might be too short gaps
 | 
			
		||||
                # that medial axis skips but infill might join with other infill regions
 | 
			
		||||
                # and use zigzag).
 | 
			
		||||
                my $w = $gap_size->[2];
 | 
			
		||||
                my @filled = map {
 | 
			
		||||
                    @{($_->isa('Slic3r::ExtrusionLoop') ? $_->polygon->split_at_first_point : $_->polyline)
 | 
			
		||||
                        ->grow(scale $w/2)};
 | 
			
		||||
                } @gap_fill;
 | 
			
		||||
                @last = @{diff(\@last, \@filled)};
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        # create one more offset to be used as boundary for fill
 | 
			
		||||
        # we offset by half the perimeter spacing (to get to the actual infill boundary)
 | 
			
		||||
        # and then we offset back and forth by half the infill spacing to only consider the
 | 
			
		||||
        # non-collapsing regions
 | 
			
		||||
        my $min_perimeter_infill_spacing = $ispacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
 | 
			
		||||
        $fill_surfaces->append($_)
 | 
			
		||||
            for map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL),  # use a bogus surface type
 | 
			
		||||
                @{offset2_ex(
 | 
			
		||||
                    [ map @{$_->simplify_p(&Slic3r::SCALED_RESOLUTION)}, @{union_ex(\@last)} ],
 | 
			
		||||
                    -($pspacing/2 + $min_perimeter_infill_spacing/2),
 | 
			
		||||
                    +$min_perimeter_infill_spacing/2,
 | 
			
		||||
                )};
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    # process thin walls by collapsing slices to single passes
 | 
			
		||||
    my @thin_wall_polylines = ();
 | 
			
		||||
    if (@thin_walls) {
 | 
			
		||||
        # the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
 | 
			
		||||
        # (actually, something larger than that still may exist due to mitering or other causes)
 | 
			
		||||
        my $min_width = $pwidth / 4;
 | 
			
		||||
        @thin_walls = @{offset2_ex([ map @$_, @thin_walls ], -$min_width/2, +$min_width/2)};
 | 
			
		||||
        
 | 
			
		||||
        # the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
 | 
			
		||||
        @thin_wall_polylines = map @{$_->medial_axis($pwidth + $pspacing, $min_width)}, @thin_walls;
 | 
			
		||||
        Slic3r::debugf "  %d thin walls detected\n", scalar(@thin_wall_polylines) if $Slic3r::debug;
 | 
			
		||||
        
 | 
			
		||||
        if (0) {
 | 
			
		||||
            require "Slic3r/SVG.pm";
 | 
			
		||||
            Slic3r::SVG::output(
 | 
			
		||||
                "medial_axis.svg",
 | 
			
		||||
                no_arrows => 1,
 | 
			
		||||
                expolygons      => \@thin_walls,
 | 
			
		||||
                green_polylines => [ map $_->polygon->split_at_first_point, @{$self->perimeters} ],
 | 
			
		||||
                red_polylines   => \@thin_wall_polylines,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    # find nesting hierarchies separately for contours and holes
 | 
			
		||||
    my $contours_pt = union_pt(\@contours);
 | 
			
		||||
    my $holes_pt    = union_pt(\@holes);
 | 
			
		||||
    
 | 
			
		||||
    # prepare grown lower layer slices for overhang detection
 | 
			
		||||
    my $lower_slices = Slic3r::ExPolygon::Collection->new;
 | 
			
		||||
    if ($self->layer->lower_layer && $self->region->config->overhangs) {
 | 
			
		||||
        # We consider overhang any part where the entire nozzle diameter is not supported by the
 | 
			
		||||
        # lower layer, so we take lower slices and offset them by half the nozzle diameter used 
 | 
			
		||||
        # in the current layer
 | 
			
		||||
        my $nozzle_diameter = $self->layer->print->config->get_at('nozzle_diameter', $self->region->config->perimeter_extruder-1);
 | 
			
		||||
        $lower_slices->append($_)
 | 
			
		||||
            for @{offset_ex([ map @$_, @{$self->layer->lower_layer->slices} ], scale +$nozzle_diameter/2)};
 | 
			
		||||
    }
 | 
			
		||||
    my $lower_slices_p = $lower_slices->polygons;
 | 
			
		||||
    
 | 
			
		||||
    # prepare a coderef for traversing the PolyTree object
 | 
			
		||||
    # external contours are root items of $contours_pt
 | 
			
		||||
    # internal contours are the ones next to external
 | 
			
		||||
    my $traverse;
 | 
			
		||||
    $traverse = sub {
 | 
			
		||||
        my ($polynodes, $depth, $is_contour) = @_;
 | 
			
		||||
        
 | 
			
		||||
        # convert all polynodes to ExtrusionLoop objects
 | 
			
		||||
        my $collection = Slic3r::ExtrusionPath::Collection->new;  # temporary collection
 | 
			
		||||
        my @children = ();
 | 
			
		||||
        foreach my $polynode (@$polynodes) {
 | 
			
		||||
            my $polygon = ($polynode->{outer} // $polynode->{hole})->clone;
 | 
			
		||||
            
 | 
			
		||||
            my $role        = EXTR_ROLE_PERIMETER;
 | 
			
		||||
            my $loop_role   = EXTRL_ROLE_DEFAULT;
 | 
			
		||||
            
 | 
			
		||||
            my $root_level  = $depth == 0;
 | 
			
		||||
            my $no_children = !@{ $polynode->{children} };
 | 
			
		||||
            my $is_external = $is_contour ? $root_level : $no_children;
 | 
			
		||||
            my $is_internal = $is_contour ? $no_children : $root_level;
 | 
			
		||||
            if ($is_contour && $is_internal) {
 | 
			
		||||
                # internal perimeters are root level in case of holes
 | 
			
		||||
                # and items with no children in case of contours
 | 
			
		||||
                # Note that we set loop role to ContourInternalPerimeter
 | 
			
		||||
                # also when loop is both internal and external (i.e.
 | 
			
		||||
                # there's only one contour loop).
 | 
			
		||||
                $loop_role  = EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER;
 | 
			
		||||
            }
 | 
			
		||||
            if ($is_external) {
 | 
			
		||||
                # external perimeters are root level in case of contours
 | 
			
		||||
                # and items with no children in case of holes
 | 
			
		||||
                $role       = EXTR_ROLE_EXTERNAL_PERIMETER;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            # detect overhanging/bridging perimeters
 | 
			
		||||
            my @paths = ();
 | 
			
		||||
            if ($self->region->config->overhangs && $self->layer->id > 0) {
 | 
			
		||||
                # get non-overhang paths by intersecting this loop with the grown lower slices
 | 
			
		||||
                foreach my $polyline (@{ intersection_ppl([ $polygon ], $lower_slices_p) }) {
 | 
			
		||||
                    push @paths, Slic3r::ExtrusionPath->new(
 | 
			
		||||
                        polyline        => $polyline,
 | 
			
		||||
                        role            => $role,
 | 
			
		||||
                        mm3_per_mm      => ($is_external ? $ext_mm3_per_mm : $mm3_per_mm),
 | 
			
		||||
                        width           => ($is_external ? $ext_perimeter_flow->width : $perimeter_flow->width),
 | 
			
		||||
                        height          => $self->height,
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                # get overhang paths by checking what parts of this loop fall 
 | 
			
		||||
                # outside the grown lower slices (thus where the distance between
 | 
			
		||||
                # the loop centerline and original lower slices is >= half nozzle diameter
 | 
			
		||||
                foreach my $polyline (@{ diff_ppl([ $polygon ], $lower_slices_p) }) {
 | 
			
		||||
                    push @paths, Slic3r::ExtrusionPath->new(
 | 
			
		||||
                        polyline        => $polyline,
 | 
			
		||||
                        role            => EXTR_ROLE_OVERHANG_PERIMETER,
 | 
			
		||||
                        mm3_per_mm      => $mm3_per_mm_overhang,
 | 
			
		||||
                        width           => $overhang_flow->width,
 | 
			
		||||
                        height          => $self->height,
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                # reapply the nearest point search for starting point
 | 
			
		||||
                # (clone because the collection gets DESTROY'ed)
 | 
			
		||||
                # We allow polyline reversal because Clipper may have randomly
 | 
			
		||||
                # reversed polylines during clipping.
 | 
			
		||||
                my $collection = Slic3r::ExtrusionPath::Collection->new(@paths); # temporary collection
 | 
			
		||||
                @paths = map $_->clone, @{$collection->chained_path(0)};
 | 
			
		||||
            } else {
 | 
			
		||||
                push @paths, Slic3r::ExtrusionPath->new(
 | 
			
		||||
                    polyline        => $polygon->split_at_first_point,
 | 
			
		||||
                    role            => $role,
 | 
			
		||||
                    mm3_per_mm      => $mm3_per_mm,
 | 
			
		||||
                    width           => $perimeter_flow->width,
 | 
			
		||||
                    height          => $self->height,
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            my $loop = Slic3r::ExtrusionLoop->new_from_paths(@paths);
 | 
			
		||||
            $loop->role($loop_role);
 | 
			
		||||
            
 | 
			
		||||
            # return ccw contours and cw holes
 | 
			
		||||
            # GCode.pm will convert all of them to ccw, but it needs to know
 | 
			
		||||
            # what the holes are in order to compute the correct inwards move
 | 
			
		||||
            # We do this on the final Loop object instead of the polygon because
 | 
			
		||||
            # overhang clipping might have reversed its order since Clipper does
 | 
			
		||||
            # not preserve polyline orientation.
 | 
			
		||||
            if ($is_contour) {
 | 
			
		||||
                $loop->make_counter_clockwise;
 | 
			
		||||
            } else {
 | 
			
		||||
                $loop->make_clockwise;
 | 
			
		||||
            }
 | 
			
		||||
            $collection->append($loop);
 | 
			
		||||
            
 | 
			
		||||
            # save the children
 | 
			
		||||
            push @children, $polynode->{children};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        # if we're handling the top-level contours, add thin walls as candidates too
 | 
			
		||||
        # in order to include them in the nearest-neighbor search
 | 
			
		||||
        if ($is_contour && $depth == 0) {
 | 
			
		||||
            foreach my $polyline (@thin_wall_polylines) {
 | 
			
		||||
                $collection->append(Slic3r::ExtrusionPath->new(
 | 
			
		||||
                    polyline        => $polyline,
 | 
			
		||||
                    role            => EXTR_ROLE_EXTERNAL_PERIMETER,
 | 
			
		||||
                    mm3_per_mm      => $mm3_per_mm,
 | 
			
		||||
                    width           => $perimeter_flow->width,
 | 
			
		||||
                    height          => $self->height,
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        # use a nearest neighbor search to order these children
 | 
			
		||||
        # TODO: supply second argument to chained_path() too?
 | 
			
		||||
        # (We used to skip this chiained_path() when $is_contour &&
 | 
			
		||||
        # $depth == 0 because slices are ordered at G_code export 
 | 
			
		||||
        # time, but multiple top-level perimeters might belong to
 | 
			
		||||
        # the same slice actually, so that was a broken optimization.)
 | 
			
		||||
        my $sorted_collection = $collection->chained_path_indices(0);
 | 
			
		||||
        my @orig_indices = @{$sorted_collection->orig_indices};
 | 
			
		||||
        
 | 
			
		||||
        my @loops = ();
 | 
			
		||||
        foreach my $loop (@$sorted_collection) {
 | 
			
		||||
            my $orig_index = shift @orig_indices;
 | 
			
		||||
            
 | 
			
		||||
            if ($loop->isa('Slic3r::ExtrusionPath')) {
 | 
			
		||||
                push @loops, $loop->clone;
 | 
			
		||||
            } else {
 | 
			
		||||
                # if this is an external contour find all holes belonging to this contour(s)
 | 
			
		||||
                # and prepend them
 | 
			
		||||
                if ($is_contour && $depth == 0) {
 | 
			
		||||
                    # $loop is the outermost loop of an island
 | 
			
		||||
                    my @holes = ();
 | 
			
		||||
                    for (my $i = 0; $i <= $#$holes_pt; $i++) {
 | 
			
		||||
                        if ($loop->polygon->contains_point($holes_pt->[$i]{outer}->first_point)) {
 | 
			
		||||
                            push @holes, splice @$holes_pt, $i, 1;  # remove from candidates to reduce complexity
 | 
			
		||||
                            $i--;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    # order holes efficiently
 | 
			
		||||
                    @holes = @holes[@{chained_path([ map {($_->{outer} // $_->{hole})->first_point} @holes ])}];
 | 
			
		||||
                    
 | 
			
		||||
                    push @loops, reverse map $traverse->([$_], 0, 0), @holes;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                # traverse children and prepend them to this loop
 | 
			
		||||
                push @loops, $traverse->($children[$orig_index], $depth+1, $is_contour);
 | 
			
		||||
                push @loops, $loop->clone;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return @loops;
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    # order loops from inner to outer (in terms of object slices)
 | 
			
		||||
    my @loops = $traverse->($contours_pt, 0, 1);
 | 
			
		||||
    
 | 
			
		||||
    # if brim will be printed, reverse the order of perimeters so that
 | 
			
		||||
    # we continue inwards after having finished the brim
 | 
			
		||||
    # TODO: add test for perimeter order
 | 
			
		||||
    @loops = reverse @loops
 | 
			
		||||
        if $self->region->config->external_perimeters_first
 | 
			
		||||
            || ($self->layer->id == 0 && $self->print->config->brim_width > 0);
 | 
			
		||||
    
 | 
			
		||||
    # append perimeters
 | 
			
		||||
    $self->perimeters->append($_) for @loops;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _fill_gaps {
 | 
			
		||||
    my ($self, $min, $max, $w, $gaps) = @_;
 | 
			
		||||
    
 | 
			
		||||
    my $this = diff_ex(
 | 
			
		||||
        offset2([ map @$_, @$gaps ], -$min/2, +$min/2),
 | 
			
		||||
        offset2([ map @$_, @$gaps ], -$max/2, +$max/2),
 | 
			
		||||
        1,
 | 
			
		||||
        # output:
 | 
			
		||||
        loops               => $self->perimeters,
 | 
			
		||||
        gap_fill            => $self->thin_fills,
 | 
			
		||||
        fill_surfaces       => $fill_surfaces,
 | 
			
		||||
    );
 | 
			
		||||
    my @polylines = map @{$_->medial_axis($max, $min/2)}, @$this;
 | 
			
		||||
    
 | 
			
		||||
    return if !@polylines;
 | 
			
		||||
    
 | 
			
		||||
    Slic3r::debugf "  %d gaps filled with extrusion width = %s\n", scalar @$this, $w
 | 
			
		||||
        if @$this;
 | 
			
		||||
 | 
			
		||||
    my $flow = $self->flow(FLOW_ROLE_SOLID_INFILL, 0, $w);
 | 
			
		||||
    my %path_args = (
 | 
			
		||||
        role        => EXTR_ROLE_GAPFILL,
 | 
			
		||||
        mm3_per_mm  => $flow->mm3_per_mm,
 | 
			
		||||
        width       => $flow->width,
 | 
			
		||||
        height      => $self->height,
 | 
			
		||||
    );
 | 
			
		||||
    
 | 
			
		||||
    my @entities = ();
 | 
			
		||||
    foreach my $polyline (@polylines) {
 | 
			
		||||
        #if ($polylines[$i]->isa('Slic3r::Polygon')) {
 | 
			
		||||
        #    my $loop = Slic3r::ExtrusionLoop->new;
 | 
			
		||||
        #    $loop->append(Slic3r::ExtrusionPath->new(polyline => $polylines[$i]->split_at_first_point, %path_args));
 | 
			
		||||
        #    $polylines[$i] = $loop;
 | 
			
		||||
        if ($polyline->is_valid && $polyline->first_point->coincides_with($polyline->last_point)) {
 | 
			
		||||
            # since medial_axis() now returns only Polyline objects, detect loops here
 | 
			
		||||
            push @entities, my $loop = Slic3r::ExtrusionLoop->new;
 | 
			
		||||
            $loop->append(Slic3r::ExtrusionPath->new(polyline => $polyline, %path_args));
 | 
			
		||||
        } else {
 | 
			
		||||
            push @entities, Slic3r::ExtrusionPath->new(polyline => $polyline, %path_args);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return @entities;
 | 
			
		||||
    $generator->process;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub prepare_fill_surfaces {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,11 @@ sub new_scale {
 | 
			
		|||
    return $class->new(map Slic3r::Geometry::scale($_), @_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub dump_perl {
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    return sprintf "[%s,%s]", @$self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
package Slic3r::Pointf;
 | 
			
		||||
use strict;
 | 
			
		||||
use warnings;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -54,7 +54,7 @@ sub BUILD {
 | 
			
		|||
        if $self->config->spiral_vase;
 | 
			
		||||
    
 | 
			
		||||
    $self->_vibration_limit(Slic3r::GCode::VibrationLimit->new(config => $self->config))
 | 
			
		||||
        if $self->config->vibration_limit > 0;
 | 
			
		||||
        if $self->config->vibration_limit != 0;
 | 
			
		||||
    
 | 
			
		||||
    $self->_arc_fitting(Slic3r::GCode::ArcFitting->new(config => $self->config))
 | 
			
		||||
        if $self->config->gcode_arcs;
 | 
			
		||||
| 
						 | 
				
			
			@ -121,23 +121,30 @@ sub export {
 | 
			
		|||
    
 | 
			
		||||
    # initialize a motion planner for object-to-object travel moves
 | 
			
		||||
    if ($self->config->avoid_crossing_perimeters) {
 | 
			
		||||
        my $distance_from_objects = 1;
 | 
			
		||||
        my $distance_from_objects = scale 1;
 | 
			
		||||
        
 | 
			
		||||
        # compute the offsetted convex hull for each object and repeat it for each copy.
 | 
			
		||||
        my @islands = ();
 | 
			
		||||
        foreach my $obj_idx (0 .. ($self->print->object_count - 1)) {
 | 
			
		||||
        my @islands_p = ();
 | 
			
		||||
        foreach my $object (@{$self->objects}) {
 | 
			
		||||
            # compute the convex hull of the entire object
 | 
			
		||||
            my $convex_hull = convex_hull([
 | 
			
		||||
                map @{$_->contour}, map @{$_->slices}, @{$self->objects->[$obj_idx]->layers},
 | 
			
		||||
                map @{$_->contour}, map @{$_->slices}, @{$object->layers},
 | 
			
		||||
            ]);
 | 
			
		||||
            # discard layers only containing thin walls (offset would fail on an empty polygon)
 | 
			
		||||
            if (@$convex_hull) {
 | 
			
		||||
                my $expolygon = Slic3r::ExPolygon->new($convex_hull);
 | 
			
		||||
                my @island = @{$expolygon->offset_ex(scale $distance_from_objects, 1, JT_SQUARE)};
 | 
			
		||||
                foreach my $copy (@{ $self->objects->[$obj_idx]->_shifted_copies }) {
 | 
			
		||||
                    push @islands, map { my $c = $_->clone; $c->translate(@$copy); $c } @island;
 | 
			
		||||
                }
 | 
			
		||||
            
 | 
			
		||||
            # discard objects only containing thin walls (offset would fail on an empty polygon)
 | 
			
		||||
            next if !@$convex_hull;
 | 
			
		||||
            
 | 
			
		||||
            # grow convex hull by the wanted clearance
 | 
			
		||||
            my @obj_islands_p = @{offset([$convex_hull], $distance_from_objects, 1, JT_SQUARE)};
 | 
			
		||||
            
 | 
			
		||||
            # translate convex hull for each object copy and append it to the islands array
 | 
			
		||||
            foreach my $copy (@{ $object->_shifted_copies }) {
 | 
			
		||||
                my @copy_islands_p = map $_->clone, @obj_islands_p;
 | 
			
		||||
                $_->translate(@$copy) for @copy_islands_p;
 | 
			
		||||
                push @islands_p, @copy_islands_p;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $gcodegen->avoid_crossing_perimeters->init_external_mp(union_ex([ map @$_, @islands ]));
 | 
			
		||||
        $gcodegen->avoid_crossing_perimeters->init_external_mp(union_ex(\@islands_p));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    # calculate wiping points if needed
 | 
			
		||||
| 
						 | 
				
			
			@ -208,7 +215,7 @@ sub export {
 | 
			
		|||
                    }
 | 
			
		||||
                    $self->process_layer($layer, [$copy]);
 | 
			
		||||
                }
 | 
			
		||||
                $self->flush_cooling_buffer;
 | 
			
		||||
                $self->flush_filters;
 | 
			
		||||
                $finished_objects++;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -234,7 +241,7 @@ sub export {
 | 
			
		|||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $self->flush_cooling_buffer;
 | 
			
		||||
        $self->flush_filters;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    # write end commands to file
 | 
			
		||||
| 
						 | 
				
			
			@ -357,7 +364,12 @@ sub process_layer {
 | 
			
		|||
        }
 | 
			
		||||
        $self->_skirt_done->{$layer->print_z} = 1;
 | 
			
		||||
        $self->_gcodegen->avoid_crossing_perimeters->use_external_mp(0);
 | 
			
		||||
        $self->_gcodegen->avoid_crossing_perimeters->disable_once(1);
 | 
			
		||||
        
 | 
			
		||||
        # allow a straight travel move to the first object point if this is the first layer
 | 
			
		||||
        # (but don't in next layers)
 | 
			
		||||
        if ($layer->id == 0) {
 | 
			
		||||
            $self->_gcodegen->avoid_crossing_perimeters->disable_once(1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    # extrude brim
 | 
			
		||||
| 
						 | 
				
			
			@ -369,6 +381,8 @@ sub process_layer {
 | 
			
		|||
            for @{$self->print->brim};
 | 
			
		||||
        $self->_brim_done(1);
 | 
			
		||||
        $self->_gcodegen->avoid_crossing_perimeters->use_external_mp(0);
 | 
			
		||||
        
 | 
			
		||||
        # allow a straight travel move to the first object point
 | 
			
		||||
        $self->_gcodegen->avoid_crossing_perimeters->disable_once(1);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
| 
						 | 
				
			
			@ -413,17 +427,17 @@ sub process_layer {
 | 
			
		|||
            # process perimeters
 | 
			
		||||
            {
 | 
			
		||||
                my $extruder_id = $region->config->perimeter_extruder-1;
 | 
			
		||||
                foreach my $perimeter (@{$layerm->perimeters}) {
 | 
			
		||||
                foreach my $perimeter_coll (@{$layerm->perimeters}) {
 | 
			
		||||
                    # init by_extruder item only if we actually use the extruder
 | 
			
		||||
                    $by_extruder{$extruder_id} //= [];
 | 
			
		||||
                    
 | 
			
		||||
                    # $perimeter is an ExtrusionLoop or ExtrusionPath object
 | 
			
		||||
                    # $perimeter_coll is an ExtrusionPath::Collection object representing a single slice
 | 
			
		||||
                    for my $i (0 .. $#{$layer->slices}) {
 | 
			
		||||
                        if ($i == $#{$layer->slices}
 | 
			
		||||
                            || $layer->slices->[$i]->contour->contains_point($perimeter->first_point)) {
 | 
			
		||||
                            || $layer->slices->[$i]->contour->contains_point($perimeter_coll->first_point)) {
 | 
			
		||||
                            $by_extruder{$extruder_id}[$i] //= { perimeters => {} };
 | 
			
		||||
                            $by_extruder{$extruder_id}[$i]{perimeters}{$region_id} //= [];
 | 
			
		||||
                            push @{ $by_extruder{$extruder_id}[$i]{perimeters}{$region_id} }, $perimeter;
 | 
			
		||||
                            push @{ $by_extruder{$extruder_id}[$i]{perimeters}{$region_id} }, @$perimeter_coll;
 | 
			
		||||
                            last;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
| 
						 | 
				
			
			@ -532,28 +546,29 @@ sub _extrude_infill {
 | 
			
		|||
    return $gcode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub flush_cooling_buffer {
 | 
			
		||||
sub flush_filters {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    print {$self->fh} $self->filter($self->_cooling_buffer->flush);
 | 
			
		||||
    
 | 
			
		||||
    print {$self->fh} $self->filter($self->_cooling_buffer->flush, 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub filter {
 | 
			
		||||
    my ($self, $gcode) = @_;
 | 
			
		||||
    my ($self, $gcode, $flush) = @_;
 | 
			
		||||
    
 | 
			
		||||
    # apply vibration limit if enabled;
 | 
			
		||||
    # this injects pauses according to time (thus depends on actual speeds)
 | 
			
		||||
    $gcode = $self->_vibration_limit->process($gcode)
 | 
			
		||||
        if $self->print->config->vibration_limit != 0;
 | 
			
		||||
        if defined $self->_vibration_limit;
 | 
			
		||||
    
 | 
			
		||||
    # apply pressure regulation if enabled;
 | 
			
		||||
    # this depends on actual speeds
 | 
			
		||||
    $gcode = $self->_pressure_regulator->process($gcode)
 | 
			
		||||
        if $self->print->config->pressure_advance > 0;
 | 
			
		||||
    $gcode = $self->_pressure_regulator->process($gcode, $flush)
 | 
			
		||||
        if defined $self->_pressure_regulator;
 | 
			
		||||
    
 | 
			
		||||
    # apply arc fitting if enabled;
 | 
			
		||||
    # this does not depend on speeds but changes G1 XY commands into G2/G2 IJ
 | 
			
		||||
    $gcode = $self->_arc_fitting->process($gcode)
 | 
			
		||||
        if $self->print->config->gcode_arcs;
 | 
			
		||||
        if defined $self->_arc_fitting;
 | 
			
		||||
    
 | 
			
		||||
    return $gcode;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -178,7 +178,7 @@ sub contact_area {
 | 
			
		|||
                        # 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 }
 | 
			
		||||
                            @{$layerm->perimeters};
 | 
			
		||||
                            map @$_, @{$layerm->perimeters};
 | 
			
		||||
                        
 | 
			
		||||
                        # workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline()
 | 
			
		||||
                        $_->[0]->translate(1,0) for @overhang_perimeters;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -286,6 +286,7 @@ $j
 | 
			
		|||
                        default: $config->{gcode_flavor})
 | 
			
		||||
    --use-relative-e-distances Enable this to get relative E values (default: no)
 | 
			
		||||
    --use-firmware-retraction  Enable firmware-controlled retraction using G10/G11 (default: no)
 | 
			
		||||
    --use-volumetric-e  Express E in cubic millimeters and prepend M200 (default: no)
 | 
			
		||||
    --gcode-arcs        Use G2/G3 commands for native arcs (experimental, not supported
 | 
			
		||||
                        by all firmwares)
 | 
			
		||||
    --gcode-comments    Make G-code verbose by adding comments (default: no)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
use Test::More tests => 11;
 | 
			
		||||
use Test::More tests => 29;
 | 
			
		||||
use strict;
 | 
			
		||||
use warnings;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -7,6 +7,8 @@ BEGIN {
 | 
			
		|||
    use lib "$FindBin::Bin/../lib";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
use Slic3r::ExtrusionLoop ':roles';
 | 
			
		||||
use Slic3r::ExtrusionPath ':roles';
 | 
			
		||||
use List::Util qw(first);
 | 
			
		||||
use Slic3r;
 | 
			
		||||
use Slic3r::Flow ':roles';
 | 
			
		||||
| 
						 | 
				
			
			@ -188,7 +190,7 @@ use Slic3r::Test;
 | 
			
		|||
    my $pflow = $layerm->flow(FLOW_ROLE_PERIMETER);
 | 
			
		||||
    my $iflow = $layerm->flow(FLOW_ROLE_INFILL);
 | 
			
		||||
    my $covered_by_perimeters = union_ex([
 | 
			
		||||
        (map @{$_->polygon->split_at_first_point->grow($pflow->scaled_width/2)}, @{$layerm->perimeters}),
 | 
			
		||||
        (map @{$_->polygon->split_at_first_point->grow($pflow->scaled_width/2)}, map @$_, @{$layerm->perimeters}),
 | 
			
		||||
    ]);
 | 
			
		||||
    my $covered_by_infill = union_ex([
 | 
			
		||||
        (map $_->p, @{$layerm->fill_surfaces}),
 | 
			
		||||
| 
						 | 
				
			
			@ -285,4 +287,89 @@ use Slic3r::Test;
 | 
			
		|||
    $test->('small_dorito');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
{
 | 
			
		||||
    my $flow = Slic3r::Flow->new(
 | 
			
		||||
        width           => 1,
 | 
			
		||||
        height          => 1,
 | 
			
		||||
        nozzle_diameter => 1,
 | 
			
		||||
    );
 | 
			
		||||
    
 | 
			
		||||
    my $config = Slic3r::Config->new;
 | 
			
		||||
    my $test = sub {
 | 
			
		||||
        my ($expolygons, %expected) = @_;
 | 
			
		||||
        
 | 
			
		||||
        my $slices = Slic3r::Surface::Collection->new;
 | 
			
		||||
        $slices->append(Slic3r::Surface->new(
 | 
			
		||||
            surface_type => S_TYPE_INTERNAL,
 | 
			
		||||
            expolygon => $_,
 | 
			
		||||
        )) for @$expolygons;
 | 
			
		||||
    
 | 
			
		||||
        my $g = Slic3r::Layer::PerimeterGenerator->new(
 | 
			
		||||
            # input:
 | 
			
		||||
            layer_height    => 1,
 | 
			
		||||
            slices          => $slices,
 | 
			
		||||
            flow            => $flow,
 | 
			
		||||
        );
 | 
			
		||||
        $g->config->apply_dynamic($config);
 | 
			
		||||
        $g->process;
 | 
			
		||||
        
 | 
			
		||||
        is scalar(@{$g->loops}),
 | 
			
		||||
            scalar(@$expolygons), 'expected number of collections';
 | 
			
		||||
        ok !defined(first { !$_->isa('Slic3r::ExtrusionPath::Collection') } @{$g->loops}),
 | 
			
		||||
            'everything is returned as collections';
 | 
			
		||||
        is scalar(map @$_, @{$g->loops}),
 | 
			
		||||
            $expected{total}, 'expected number of loops';
 | 
			
		||||
        is scalar(grep $_->role == EXTR_ROLE_EXTERNAL_PERIMETER, map @$_, map @$_, @{$g->loops}),
 | 
			
		||||
            $expected{external}, 'expected number of external loops';
 | 
			
		||||
        is scalar(grep $_->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER, map @$_, @{$g->loops}),
 | 
			
		||||
            $expected{cinternal}, 'expected number of internal contour loops';
 | 
			
		||||
        is scalar(grep $_->polygon->is_counter_clockwise, map @$_, @{$g->loops}),
 | 
			
		||||
            $expected{ccw}, 'expected number of ccw loops';
 | 
			
		||||
        
 | 
			
		||||
        return $g;
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    $config->set('perimeters', 3);
 | 
			
		||||
    $test->(
 | 
			
		||||
        [
 | 
			
		||||
            Slic3r::ExPolygon->new(
 | 
			
		||||
                Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]),
 | 
			
		||||
            ),
 | 
			
		||||
        ],
 | 
			
		||||
        total       => 3,
 | 
			
		||||
        external    => 1,
 | 
			
		||||
        cinternal   => 1,
 | 
			
		||||
        ccw         => 3,
 | 
			
		||||
    );
 | 
			
		||||
    $test->(
 | 
			
		||||
        [
 | 
			
		||||
            Slic3r::ExPolygon->new(
 | 
			
		||||
                Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]),
 | 
			
		||||
                Slic3r::Polygon->new_scale([40,40], [40,60], [60,60], [60,40]),
 | 
			
		||||
            ),
 | 
			
		||||
        ],
 | 
			
		||||
        total       => 6,
 | 
			
		||||
        external    => 2,
 | 
			
		||||
        cinternal   => 1,
 | 
			
		||||
        ccw         => 3,
 | 
			
		||||
    );
 | 
			
		||||
    $test->(
 | 
			
		||||
        [
 | 
			
		||||
            Slic3r::ExPolygon->new(
 | 
			
		||||
                Slic3r::Polygon->new_scale([0,0], [200,0], [200,200], [0,200]),
 | 
			
		||||
                Slic3r::Polygon->new_scale([20,20], [20,180], [180,180], [180,20]),
 | 
			
		||||
            ),
 | 
			
		||||
            # nested:
 | 
			
		||||
            Slic3r::ExPolygon->new(
 | 
			
		||||
                Slic3r::Polygon->new_scale([50,50], [150,50], [150,150], [50,150]),
 | 
			
		||||
                Slic3r::Polygon->new_scale([80,80], [80,120], [120,120], [120,80]),
 | 
			
		||||
            ),
 | 
			
		||||
        ],
 | 
			
		||||
        total       => 4*3,
 | 
			
		||||
        external    => 4,
 | 
			
		||||
        cinternal   => 2,
 | 
			
		||||
        ccw         => 2*3,
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
__END__
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										36
									
								
								t/pressure.t
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								t/pressure.t
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,36 @@
 | 
			
		|||
use Test::More tests => 1;
 | 
			
		||||
use strict;
 | 
			
		||||
use warnings;
 | 
			
		||||
 | 
			
		||||
BEGIN {
 | 
			
		||||
    use FindBin;
 | 
			
		||||
    use lib "$FindBin::Bin/../lib";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
use List::Util qw();
 | 
			
		||||
use Slic3r;
 | 
			
		||||
use Slic3r::Geometry qw(epsilon);
 | 
			
		||||
use Slic3r::Test;
 | 
			
		||||
 | 
			
		||||
{
 | 
			
		||||
    my $config = Slic3r::Config->new_from_defaults;
 | 
			
		||||
    $config->set('pressure_advance', 10);
 | 
			
		||||
    $config->set('retract_length', [1]);
 | 
			
		||||
    
 | 
			
		||||
    my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2);
 | 
			
		||||
    my $retracted = $config->retract_length->[0];
 | 
			
		||||
    Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
 | 
			
		||||
        my ($self, $cmd, $args, $info) = @_;
 | 
			
		||||
        
 | 
			
		||||
        if ($info->{extruding} && !$info->{dist_XY}) {
 | 
			
		||||
            $retracted += $info->{dist_E};
 | 
			
		||||
        } elsif ($info->{retracting}) {
 | 
			
		||||
            $retracted += $info->{dist_E};
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    ok abs($retracted) < epsilon, 'all retractions are compensated';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
__END__
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
use Test::More tests => 19;
 | 
			
		||||
use Test::More tests => 18;
 | 
			
		||||
use strict;
 | 
			
		||||
use warnings;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -200,29 +200,4 @@ use Slic3r::Test qw(_eq);
 | 
			
		|||
    ok $retracted, 'retracting also when --retract-length is 0 but --use-firmware-retraction is enabled';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
{
 | 
			
		||||
    my $config = Slic3r::Config->new_from_defaults;
 | 
			
		||||
    $config->set('only_retract_when_crossing_perimeters', 1);
 | 
			
		||||
    $config->set('fill_density', 0);
 | 
			
		||||
    
 | 
			
		||||
    my $print = Slic3r::Test::init_print('cube_with_hole', config => $config);
 | 
			
		||||
    my $retracted = 0;
 | 
			
		||||
    my $traveling_without_retraction = 0;
 | 
			
		||||
    Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
 | 
			
		||||
        my ($self, $cmd, $args, $info) = @_;
 | 
			
		||||
        
 | 
			
		||||
        if ($info->{retracting}) {
 | 
			
		||||
            $retracted = 1;
 | 
			
		||||
        } elsif ($info->{extruding} && $retracted) {
 | 
			
		||||
            $retracted = 0;
 | 
			
		||||
        } elsif ($info->{travel} && !$retracted) {
 | 
			
		||||
            if ($info->{dist_XY} > $config->retract_before_travel->[0]) {
 | 
			
		||||
                $traveling_without_retraction = 1;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    ok !$traveling_without_retraction, 'always retract when using only_retract_when_crossing_perimeters and fill_density = 0';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
__END__
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -105,6 +105,23 @@ ExPolygon::contains(const Point &point) const
 | 
			
		|||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// inclusive version of contains() that also checks whether point is on boundaries
 | 
			
		||||
bool
 | 
			
		||||
ExPolygon::contains_b(const Point &point) const
 | 
			
		||||
{
 | 
			
		||||
    return this->contains(point) || this->has_boundary_point(point);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
ExPolygon::has_boundary_point(const Point &point) const
 | 
			
		||||
{
 | 
			
		||||
    if (this->contour.has_boundary_point(point)) return true;
 | 
			
		||||
    for (Polygons::const_iterator h = this->holes.begin(); h != this->holes.end(); ++h) {
 | 
			
		||||
        if (h->has_boundary_point(point)) return true;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Polygons
 | 
			
		||||
ExPolygon::simplify_p(double tolerance) const
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -364,6 +381,16 @@ ExPolygon::triangulate_p2t(Polygons* polygons) const
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Lines
 | 
			
		||||
ExPolygon::lines() const
 | 
			
		||||
{
 | 
			
		||||
    Lines lines;
 | 
			
		||||
    this->contour.lines(&lines);
 | 
			
		||||
    for (Polygons::const_iterator h = this->holes.begin(); h != this->holes.end(); ++h)
 | 
			
		||||
        h->lines(&lines);
 | 
			
		||||
    return lines;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef SLIC3RXS
 | 
			
		||||
 | 
			
		||||
REGISTER_CLASS(ExPolygon, "ExPolygon");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,6 +25,8 @@ class ExPolygon
 | 
			
		|||
    bool contains(const Line &line) const;
 | 
			
		||||
    bool contains(const Polyline &polyline) const;
 | 
			
		||||
    bool contains(const Point &point) const;
 | 
			
		||||
    bool contains_b(const Point &point) const;
 | 
			
		||||
    bool has_boundary_point(const Point &point) const;
 | 
			
		||||
    Polygons simplify_p(double tolerance) const;
 | 
			
		||||
    ExPolygons simplify(double tolerance) const;
 | 
			
		||||
    void simplify(double tolerance, ExPolygons &expolygons) const;
 | 
			
		||||
| 
						 | 
				
			
			@ -36,6 +38,7 @@ class ExPolygon
 | 
			
		|||
    void triangulate(Polygons* polygons) const;
 | 
			
		||||
    void triangulate_pp(Polygons* polygons) const;
 | 
			
		||||
    void triangulate_p2t(Polygons* polygons) const;
 | 
			
		||||
    Lines lines() const;
 | 
			
		||||
    
 | 
			
		||||
    #ifdef SLIC3RXS
 | 
			
		||||
    void from_SV(SV* poly_sv);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,11 @@
 | 
			
		|||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
 | 
			
		||||
ExPolygonCollection::ExPolygonCollection(const ExPolygon &expolygon)
 | 
			
		||||
{
 | 
			
		||||
    this->expolygons.push_back(expolygon);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ExPolygonCollection::operator Points() const
 | 
			
		||||
{
 | 
			
		||||
    Points points;
 | 
			
		||||
| 
						 | 
				
			
			@ -68,6 +73,15 @@ template bool ExPolygonCollection::contains<Point>(const Point &item) const;
 | 
			
		|||
template bool ExPolygonCollection::contains<Line>(const Line &item) const;
 | 
			
		||||
template bool ExPolygonCollection::contains<Polyline>(const Polyline &item) const;
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
ExPolygonCollection::contains_b(const Point &point) const
 | 
			
		||||
{
 | 
			
		||||
    for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) {
 | 
			
		||||
        if (it->contains_b(point)) return true;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
ExPolygonCollection::simplify(double tolerance)
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -87,6 +101,17 @@ ExPolygonCollection::convex_hull(Polygon* hull) const
 | 
			
		|||
    Slic3r::Geometry::convex_hull(pp, hull);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Lines
 | 
			
		||||
ExPolygonCollection::lines() const
 | 
			
		||||
{
 | 
			
		||||
    Lines lines;
 | 
			
		||||
    for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) {
 | 
			
		||||
        Lines ex_lines = it->lines();
 | 
			
		||||
        lines.insert(lines.end(), ex_lines.begin(), ex_lines.end());
 | 
			
		||||
    }
 | 
			
		||||
    return lines;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef SLIC3RXS
 | 
			
		||||
REGISTER_CLASS(ExPolygonCollection, "ExPolygon::Collection");
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,6 +17,7 @@ class ExPolygonCollection
 | 
			
		|||
    ExPolygons expolygons;
 | 
			
		||||
    
 | 
			
		||||
    ExPolygonCollection() {};
 | 
			
		||||
    ExPolygonCollection(const ExPolygon &expolygon);
 | 
			
		||||
    ExPolygonCollection(const ExPolygons &expolygons) : expolygons(expolygons) {};
 | 
			
		||||
    operator Points() const;
 | 
			
		||||
    operator Polygons() const;
 | 
			
		||||
| 
						 | 
				
			
			@ -25,8 +26,10 @@ class ExPolygonCollection
 | 
			
		|||
    void translate(double x, double y);
 | 
			
		||||
    void rotate(double angle, const Point ¢er);
 | 
			
		||||
    template <class T> bool contains(const T &item) const;
 | 
			
		||||
    bool contains_b(const Point &point) const;
 | 
			
		||||
    void simplify(double tolerance);
 | 
			
		||||
    void convex_hull(Polygon* hull) const;
 | 
			
		||||
    Lines lines() const;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,8 +9,12 @@ Extruder::Extruder(int id, GCodeConfig *config)
 | 
			
		|||
    reset();
 | 
			
		||||
    
 | 
			
		||||
    // cache values that are going to be called often
 | 
			
		||||
    this->e_per_mm3 = this->extrusion_multiplier()
 | 
			
		||||
        * (4 / ((this->filament_diameter() * this->filament_diameter()) * PI));
 | 
			
		||||
    if (config->use_volumetric_e) {
 | 
			
		||||
        this->e_per_mm3 = this->extrusion_multiplier();
 | 
			
		||||
    } else {
 | 
			
		||||
        this->e_per_mm3 = this->extrusion_multiplier()
 | 
			
		||||
            * (4 / ((this->filament_diameter() * this->filament_diameter()) * PI));
 | 
			
		||||
    }
 | 
			
		||||
    this->retract_speed_mm_min = this->retract_speed() * 60;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -80,12 +84,22 @@ Extruder::e_per_mm(double mm3_per_mm) const
 | 
			
		|||
double
 | 
			
		||||
Extruder::extruded_volume() const
 | 
			
		||||
{
 | 
			
		||||
    if (this->config->use_volumetric_e) {
 | 
			
		||||
        // Any current amount of retraction should not affect used filament, since
 | 
			
		||||
        // it represents empty volume in the nozzle. We add it back to E.
 | 
			
		||||
        return this->absolute_E + this->retracted;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return this->used_filament() * (this->filament_diameter() * this->filament_diameter()) * PI/4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
double
 | 
			
		||||
Extruder::used_filament() const
 | 
			
		||||
{
 | 
			
		||||
    if (this->config->use_volumetric_e) {
 | 
			
		||||
        return this->extruded_volume() / (this->filament_diameter() * this->filament_diameter() * PI/4);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Any current amount of retraction should not affect used filament, since
 | 
			
		||||
    // it represents empty volume in the nozzle. We add it back to E.
 | 
			
		||||
    return this->absolute_E + this->retracted;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -278,7 +278,7 @@ ExtrusionLoop::split_at(const Point &point)
 | 
			
		|||
{
 | 
			
		||||
    if (this->paths.empty()) return;
 | 
			
		||||
    
 | 
			
		||||
    // find the closest path and closest point
 | 
			
		||||
    // find the closest path and closest point belonging to that path
 | 
			
		||||
    size_t path_idx = 0;
 | 
			
		||||
    Point p = this->paths.front().first_point();
 | 
			
		||||
    double min = point.distance_to(p);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,6 +35,9 @@ enum ExtrusionLoopRole {
 | 
			
		|||
class ExtrusionEntity
 | 
			
		||||
{
 | 
			
		||||
    public:
 | 
			
		||||
    virtual bool is_loop() const {
 | 
			
		||||
        return false;
 | 
			
		||||
    };
 | 
			
		||||
    virtual ExtrusionEntity* clone() const = 0;
 | 
			
		||||
    virtual ~ExtrusionEntity() {};
 | 
			
		||||
    virtual void reverse() = 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -84,6 +87,9 @@ class ExtrusionLoop : public ExtrusionEntity
 | 
			
		|||
    ExtrusionLoopRole role;
 | 
			
		||||
    
 | 
			
		||||
    ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : role(role) {};
 | 
			
		||||
    bool is_loop() const {
 | 
			
		||||
        return true;
 | 
			
		||||
    };
 | 
			
		||||
    operator Polygon() const;
 | 
			
		||||
    ExtrusionLoop* clone() const;
 | 
			
		||||
    bool make_clockwise();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,7 +37,9 @@ void
 | 
			
		|||
ExtrusionEntityCollection::reverse()
 | 
			
		||||
{
 | 
			
		||||
    for (ExtrusionEntitiesPtr::iterator it = this->entities.begin(); it != this->entities.end(); ++it) {
 | 
			
		||||
        (*it)->reverse();
 | 
			
		||||
        // Don't reverse it if it's a loop, as it doesn't change anything in terms of elements ordering
 | 
			
		||||
        // and caller might rely on winding order
 | 
			
		||||
        if (!(*it)->is_loop()) (*it)->reverse();
 | 
			
		||||
    }
 | 
			
		||||
    std::reverse(this->entities.begin(), this->entities.end());
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -96,7 +98,8 @@ ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEntityCo
 | 
			
		|||
        int start_index = start_near.nearest_point_index(endpoints);
 | 
			
		||||
        int path_index = start_index/2;
 | 
			
		||||
        ExtrusionEntity* entity = my_paths.at(path_index);
 | 
			
		||||
        if (start_index % 2 && !no_reverse) {
 | 
			
		||||
        // never reverse loops, since it's pointless for chained path and callers might depend on orientation
 | 
			
		||||
        if (start_index % 2 && !no_reverse && !entity->is_loop()) {
 | 
			
		||||
            entity->reverse();
 | 
			
		||||
        }
 | 
			
		||||
        retval->entities.push_back(my_paths.at(path_index));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -48,22 +48,32 @@ GCodeWriter::set_extruders(const std::vector<unsigned int> &extruder_ids)
 | 
			
		|||
std::string
 | 
			
		||||
GCodeWriter::preamble()
 | 
			
		||||
{
 | 
			
		||||
    std::string gcode;
 | 
			
		||||
    std::ostringstream gcode;
 | 
			
		||||
    
 | 
			
		||||
    if (FLAVOR_IS_NOT(gcfMakerWare)) {
 | 
			
		||||
        gcode += "G21 ; set units to millimeters\n";
 | 
			
		||||
        gcode += "G90 ; use absolute coordinates\n";
 | 
			
		||||
        gcode << "G21 ; set units to millimeters\n";
 | 
			
		||||
        gcode << "G90 ; use absolute coordinates\n";
 | 
			
		||||
    }
 | 
			
		||||
    if (FLAVOR_IS(gcfRepRap) || FLAVOR_IS(gcfTeacup)) {
 | 
			
		||||
        if (this->config.use_relative_e_distances) {
 | 
			
		||||
            gcode += "M83 ; use relative distances for extrusion\n";
 | 
			
		||||
            gcode << "M83 ; use relative distances for extrusion\n";
 | 
			
		||||
        } else {
 | 
			
		||||
            gcode += "M82 ; use absolute distances for extrusion\n";
 | 
			
		||||
            gcode << "M82 ; use absolute distances for extrusion\n";
 | 
			
		||||
        }
 | 
			
		||||
        gcode += this->reset_e(true);
 | 
			
		||||
        if (this->config.use_volumetric_e && this->config.start_gcode.value.find("M200") == std::string::npos) {
 | 
			
		||||
            for (std::map<unsigned int,Extruder>::const_iterator it = this->extruders.begin(); it != this->extruders.end(); ++it) {
 | 
			
		||||
                unsigned int extruder_id = it->first;
 | 
			
		||||
                gcode << "M200 D" << E_NUM(this->config.filament_diameter.get_at(extruder_id));
 | 
			
		||||
                if (this->multiple_extruders || extruder_id != 0) {
 | 
			
		||||
                    gcode << " T" << extruder_id;
 | 
			
		||||
                }
 | 
			
		||||
                gcode << " ; set filament diameter\n";
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        gcode << this->reset_e(true);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return gcode;
 | 
			
		||||
    return gcode.str();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string
 | 
			
		||||
| 
						 | 
				
			
			@ -423,6 +433,14 @@ GCodeWriter::_retract(double length, double restart_extra, const std::string &co
 | 
			
		|||
        might be 0, in which case the retraction logic gets skipped. */
 | 
			
		||||
    if (this->config.use_firmware_retraction) length = 1;
 | 
			
		||||
    
 | 
			
		||||
    // If we use volumetric E values we turn lengths into volumes */
 | 
			
		||||
    if (this->config.use_volumetric_e) {
 | 
			
		||||
        double d = this->_extruder->filament_diameter();
 | 
			
		||||
        double area = d * d * PI/4;
 | 
			
		||||
        length = length * area;
 | 
			
		||||
        restart_extra = restart_extra * area;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    double dE = this->_extruder->retract(length, restart_extra);
 | 
			
		||||
    if (dE != 0) {
 | 
			
		||||
        if (this->config.use_firmware_retraction) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -129,20 +129,18 @@ Layer::any_internal_region_slice_contains(const T &item) const
 | 
			
		|||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
template bool Layer::any_internal_region_slice_contains<Line>(const Line &item) const;
 | 
			
		||||
template bool Layer::any_internal_region_slice_contains<Polyline>(const Polyline &item) const;
 | 
			
		||||
 | 
			
		||||
template <class T>
 | 
			
		||||
bool
 | 
			
		||||
Layer::any_internal_region_fill_surface_contains(const T &item) const
 | 
			
		||||
Layer::any_bottom_region_slice_contains(const T &item) const
 | 
			
		||||
{
 | 
			
		||||
    FOREACH_LAYERREGION(this, layerm) {
 | 
			
		||||
        if ((*layerm)->fill_surfaces.any_internal_contains(item)) return true;
 | 
			
		||||
        if ((*layerm)->slices.any_bottom_contains(item)) return true;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
template bool Layer::any_internal_region_fill_surface_contains<Line>(const Line &item) const;
 | 
			
		||||
template bool Layer::any_internal_region_fill_surface_contains<Polyline>(const Polyline &item) const;
 | 
			
		||||
 | 
			
		||||
template bool Layer::any_bottom_region_slice_contains<Polyline>(const Polyline &item) const;
 | 
			
		||||
 | 
			
		||||
#ifdef SLIC3RXS
 | 
			
		||||
REGISTER_CLASS(Layer, "Layer");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -94,8 +94,8 @@ class Layer {
 | 
			
		|||
    
 | 
			
		||||
    void make_slices();
 | 
			
		||||
    template <class T> bool any_internal_region_slice_contains(const T &item) const;
 | 
			
		||||
    template <class T> bool any_internal_region_fill_surface_contains(const T &item) const;
 | 
			
		||||
 | 
			
		||||
    template <class T> bool any_bottom_region_slice_contains(const T &item) const;
 | 
			
		||||
    
 | 
			
		||||
    protected:
 | 
			
		||||
    int _id;     // sequential number of layer, 0-based
 | 
			
		||||
    PrintObject *_object;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -44,7 +44,8 @@ void
 | 
			
		|||
LayerRegion::merge_slices()
 | 
			
		||||
{
 | 
			
		||||
    ExPolygons expp;
 | 
			
		||||
    union_(this->slices, &expp);
 | 
			
		||||
    // without safety offset, artifacts are generated (GH #2494)
 | 
			
		||||
    union_(this->slices, &expp, true);
 | 
			
		||||
    this->slices.surfaces.clear();
 | 
			
		||||
    this->slices.surfaces.reserve(expp.size());
 | 
			
		||||
    
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,6 +16,13 @@ Line::wkt() const
 | 
			
		|||
    return ss.str();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Line::operator Lines() const
 | 
			
		||||
{
 | 
			
		||||
    Lines lines;
 | 
			
		||||
    lines.push_back(*this);
 | 
			
		||||
    return lines;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Line::operator Polyline() const
 | 
			
		||||
{
 | 
			
		||||
    Polyline pl;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,6 +9,7 @@ namespace Slic3r {
 | 
			
		|||
class Line;
 | 
			
		||||
class Linef3;
 | 
			
		||||
class Polyline;
 | 
			
		||||
typedef std::vector<Line> Lines;
 | 
			
		||||
 | 
			
		||||
class Line
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -18,6 +19,7 @@ class Line
 | 
			
		|||
    Line() {};
 | 
			
		||||
    explicit Line(Point _a, Point _b): a(_a), b(_b) {};
 | 
			
		||||
    std::string wkt() const;
 | 
			
		||||
    operator Lines() const;
 | 
			
		||||
    operator Polyline() const;
 | 
			
		||||
    void scale(double factor);
 | 
			
		||||
    void translate(double x, double y);
 | 
			
		||||
| 
						 | 
				
			
			@ -45,8 +47,6 @@ class Line
 | 
			
		|||
    #endif
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
typedef std::vector<Line> Lines;
 | 
			
		||||
 | 
			
		||||
class Linef3
 | 
			
		||||
{
 | 
			
		||||
    public:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -72,11 +72,23 @@ MotionPlanner::initialize()
 | 
			
		|||
    this->initialized = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ExPolygonCollection
 | 
			
		||||
MotionPlanner::get_env(size_t island_idx) const
 | 
			
		||||
{
 | 
			
		||||
    if (island_idx == -1) {
 | 
			
		||||
        return ExPolygonCollection(this->outer);
 | 
			
		||||
    } else {
 | 
			
		||||
        return this->inner[island_idx];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
MotionPlanner::shortest_path(const Point &from, const Point &to, Polyline* polyline)
 | 
			
		||||
{
 | 
			
		||||
    // lazy generation of configuration space
 | 
			
		||||
    if (!this->initialized) this->initialize();
 | 
			
		||||
    
 | 
			
		||||
    // if we have an empty configuration space, return a straight move
 | 
			
		||||
    if (this->islands.empty()) {
 | 
			
		||||
        polyline->points.push_back(from);
 | 
			
		||||
        polyline->points.push_back(to);
 | 
			
		||||
| 
						 | 
				
			
			@ -99,28 +111,28 @@ MotionPlanner::shortest_path(const Point &from, const Point &to, Polyline* polyl
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // get environment
 | 
			
		||||
    ExPolygonCollection env = this->get_env(island_idx);
 | 
			
		||||
    if (env.expolygons.empty()) {
 | 
			
		||||
        // if this environment is empty (probably because it's too small), perform straight move
 | 
			
		||||
        // and avoid running the algorithms on empty dataset
 | 
			
		||||
        polyline->points.push_back(from);
 | 
			
		||||
        polyline->points.push_back(to);
 | 
			
		||||
        return; // bye bye
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Now check whether points are inside the environment.
 | 
			
		||||
    Point inner_from    = from;
 | 
			
		||||
    Point inner_to      = to;
 | 
			
		||||
    bool from_is_inside, to_is_inside;
 | 
			
		||||
    if (island_idx == -1) {
 | 
			
		||||
        if (!(from_is_inside = this->outer.contains(from))) {
 | 
			
		||||
            // Find the closest inner point to start from.
 | 
			
		||||
            from.nearest_point(this->outer, &inner_from);
 | 
			
		||||
        }
 | 
			
		||||
        if (!(to_is_inside = this->outer.contains(to))) {
 | 
			
		||||
            // Find the closest inner point to start from.
 | 
			
		||||
            to.nearest_point(this->outer, &inner_to);
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        if (!(from_is_inside = this->inner[island_idx].contains(from))) {
 | 
			
		||||
            // Find the closest inner point to start from.
 | 
			
		||||
            from.nearest_point(this->inner[island_idx], &inner_from);
 | 
			
		||||
        }
 | 
			
		||||
        if (!(to_is_inside = this->inner[island_idx].contains(to))) {
 | 
			
		||||
            // Find the closest inner point to start from.
 | 
			
		||||
            to.nearest_point(this->inner[island_idx], &inner_to);
 | 
			
		||||
        }
 | 
			
		||||
    
 | 
			
		||||
    if (!(from_is_inside = env.contains(from))) {
 | 
			
		||||
        // Find the closest inner point to start from.
 | 
			
		||||
        inner_from = this->nearest_env_point(env, from, to);
 | 
			
		||||
    }
 | 
			
		||||
    if (!(to_is_inside = env.contains(to))) {
 | 
			
		||||
        // Find the closest inner point to start from.
 | 
			
		||||
        inner_to = this->nearest_env_point(env, to, inner_from);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // perform actual path search
 | 
			
		||||
| 
						 | 
				
			
			@ -129,85 +141,147 @@ MotionPlanner::shortest_path(const Point &from, const Point &to, Polyline* polyl
 | 
			
		|||
    
 | 
			
		||||
    polyline->points.insert(polyline->points.begin(), from);
 | 
			
		||||
    polyline->points.push_back(to);
 | 
			
		||||
    
 | 
			
		||||
    {
 | 
			
		||||
        // grow our environment slightly in order for simplify_by_visibility()
 | 
			
		||||
        // to work best by considering moves on boundaries valid as well
 | 
			
		||||
        ExPolygonCollection grown_env;
 | 
			
		||||
        offset(env, &grown_env.expolygons, +SCALED_EPSILON);
 | 
			
		||||
        
 | 
			
		||||
        // remove unnecessary vertices
 | 
			
		||||
        polyline->simplify_by_visibility(grown_env);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /*
 | 
			
		||||
        SVG svg("shortest_path.svg");
 | 
			
		||||
        svg.draw(this->outer);
 | 
			
		||||
        svg.arrows = false;
 | 
			
		||||
        for (MotionPlannerGraph::adjacency_list_t::const_iterator it = graph->adjacency_list.begin(); it != graph->adjacency_list.end(); ++it) {
 | 
			
		||||
            Point a = graph->nodes[it - graph->adjacency_list.begin()];
 | 
			
		||||
            for (std::vector<MotionPlannerGraph::neighbor>::const_iterator n = it->begin(); n != it->end(); ++n) {
 | 
			
		||||
                Point b = graph->nodes[n->target];
 | 
			
		||||
                svg.draw(Line(a, b));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        svg.arrows = true;
 | 
			
		||||
        svg.draw(from);
 | 
			
		||||
        svg.draw(inner_from, "red");
 | 
			
		||||
        svg.draw(to);
 | 
			
		||||
        svg.draw(inner_to, "red");
 | 
			
		||||
        svg.draw(*polyline, "red");
 | 
			
		||||
        svg.Close();
 | 
			
		||||
    */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Point
 | 
			
		||||
MotionPlanner::nearest_env_point(const ExPolygonCollection &env, const Point &from, const Point &to) const
 | 
			
		||||
{
 | 
			
		||||
    /*  In order to ensure that the move between 'from' and the initial env point does
 | 
			
		||||
        not violate any of the configuration space boundaries, we limit our search to
 | 
			
		||||
        the points that satisfy this condition. */
 | 
			
		||||
    
 | 
			
		||||
    /*  Assume that this method is never called when 'env' contains 'from';
 | 
			
		||||
        so 'from' is either inside a hole or outside all contours */
 | 
			
		||||
    
 | 
			
		||||
    // get the points of the hole containing 'from', if any
 | 
			
		||||
    Points pp;
 | 
			
		||||
    for (ExPolygons::const_iterator ex = env.expolygons.begin(); ex != env.expolygons.end(); ++ex) {
 | 
			
		||||
        for (Polygons::const_iterator h = ex->holes.begin(); h != ex->holes.end(); ++h) {
 | 
			
		||||
            if (h->contains(from)) {
 | 
			
		||||
                pp = *h;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (!pp.empty()) break;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /*  If 'from' is not inside a hole, it's outside of all contours, so take all
 | 
			
		||||
        contours' points */
 | 
			
		||||
    if (pp.empty()) {
 | 
			
		||||
        for (ExPolygons::const_iterator ex = env.expolygons.begin(); ex != env.expolygons.end(); ++ex) {
 | 
			
		||||
            Points contour_pp = ex->contour;
 | 
			
		||||
            pp.insert(pp.end(), contour_pp.begin(), contour_pp.end());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /*  Find the candidate result and check that it doesn't cross any boundary.
 | 
			
		||||
        (We could skip all of the above polygon finding logic and directly test all points
 | 
			
		||||
        in env, but this way we probably reduce complexity). */
 | 
			
		||||
    Polygons env_pp = env;
 | 
			
		||||
    while (pp.size() >= 2) {
 | 
			
		||||
        // find the point in pp that is closest to both 'from' and 'to'
 | 
			
		||||
        size_t result = from.nearest_waypoint_index(pp, to);
 | 
			
		||||
        
 | 
			
		||||
        if (intersects((Lines)Line(from, pp[result]), env_pp)) {
 | 
			
		||||
            // discard result
 | 
			
		||||
            pp.erase(pp.begin() + result);
 | 
			
		||||
        } else {
 | 
			
		||||
            return pp[result];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // if we're here, return last point if any (better than nothing)
 | 
			
		||||
    if (!pp.empty()) return pp.front();
 | 
			
		||||
    
 | 
			
		||||
    // if we have no points at all, then we have an empty environment and we
 | 
			
		||||
    // make this method behave as a no-op (we shouldn't get here by the way)
 | 
			
		||||
    return from;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MotionPlannerGraph*
 | 
			
		||||
MotionPlanner::init_graph(int island_idx)
 | 
			
		||||
{
 | 
			
		||||
    if (this->graphs[island_idx + 1] == NULL) {
 | 
			
		||||
        Polygons pp;
 | 
			
		||||
        if (island_idx == -1) {
 | 
			
		||||
            pp = this->outer;
 | 
			
		||||
        } else {
 | 
			
		||||
            pp = this->inner[island_idx];
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // if this graph doesn't exist, initialize it
 | 
			
		||||
        MotionPlannerGraph* graph = this->graphs[island_idx + 1] = new MotionPlannerGraph();
 | 
			
		||||
        
 | 
			
		||||
        // add polygon boundaries as edges
 | 
			
		||||
        size_t node_idx = 0;
 | 
			
		||||
        Lines lines;
 | 
			
		||||
        for (Polygons::const_iterator polygon = pp.begin(); polygon != pp.end(); ++polygon) {
 | 
			
		||||
            graph->nodes.push_back(polygon->points.back());
 | 
			
		||||
            node_idx++;
 | 
			
		||||
            for (Points::const_iterator p = polygon->points.begin(); p != polygon->points.end(); ++p) {
 | 
			
		||||
                graph->nodes.push_back(*p);
 | 
			
		||||
                double dist = graph->nodes[node_idx-1].distance_to(*p);
 | 
			
		||||
                graph->add_edge(node_idx-1, node_idx, dist);
 | 
			
		||||
                graph->add_edge(node_idx, node_idx-1, dist);
 | 
			
		||||
                node_idx++;
 | 
			
		||||
            }
 | 
			
		||||
            polygon->lines(&lines);
 | 
			
		||||
        }
 | 
			
		||||
        /*  We don't add polygon boundaries as graph edges, because we'd need to connect
 | 
			
		||||
            them to the Voronoi-generated edges by recognizing coinciding nodes. */
 | 
			
		||||
        
 | 
			
		||||
        // add Voronoi edges as internal edges
 | 
			
		||||
        {
 | 
			
		||||
            typedef voronoi_diagram<double> VD;
 | 
			
		||||
            typedef std::map<const VD::vertex_type*,size_t> t_vd_vertices;
 | 
			
		||||
            VD vd;
 | 
			
		||||
            t_vd_vertices vd_vertices;
 | 
			
		||||
        typedef voronoi_diagram<double> VD;
 | 
			
		||||
        VD vd;
 | 
			
		||||
        
 | 
			
		||||
        // mapping between Voronoi vertices and graph nodes
 | 
			
		||||
        typedef std::map<const VD::vertex_type*,size_t> t_vd_vertices;
 | 
			
		||||
        t_vd_vertices vd_vertices;
 | 
			
		||||
        
 | 
			
		||||
        // get boundaries as lines
 | 
			
		||||
        ExPolygonCollection env = this->get_env(island_idx);
 | 
			
		||||
        Lines lines = env.lines();
 | 
			
		||||
        boost::polygon::construct_voronoi(lines.begin(), lines.end(), &vd);
 | 
			
		||||
        
 | 
			
		||||
        // traverse the Voronoi diagram and generate graph nodes and edges
 | 
			
		||||
        for (VD::const_edge_iterator edge = vd.edges().begin(); edge != vd.edges().end(); ++edge) {
 | 
			
		||||
            if (edge->is_infinite()) continue;
 | 
			
		||||
            
 | 
			
		||||
            boost::polygon::construct_voronoi(lines.begin(), lines.end(), &vd);
 | 
			
		||||
            for (VD::const_edge_iterator edge = vd.edges().begin(); edge != vd.edges().end(); ++edge) {
 | 
			
		||||
                if (edge->is_infinite()) continue;
 | 
			
		||||
                
 | 
			
		||||
                const VD::vertex_type* v0 = edge->vertex0();
 | 
			
		||||
                const VD::vertex_type* v1 = edge->vertex1();
 | 
			
		||||
                Point p0 = Point(v0->x(), v0->y());
 | 
			
		||||
                Point p1 = Point(v1->x(), v1->y());
 | 
			
		||||
                // contains() should probably be faster than contains(),
 | 
			
		||||
                // and should it fail on any boundary points it's not a big problem
 | 
			
		||||
                if (island_idx == -1) {
 | 
			
		||||
                    if (!this->outer.contains(p0) || !this->outer.contains(p1)) continue;
 | 
			
		||||
                } else {
 | 
			
		||||
                    if (!this->inner[island_idx].contains(p0) || !this->inner[island_idx].contains(p1)) continue;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                t_vd_vertices::const_iterator i_v0 = vd_vertices.find(v0);
 | 
			
		||||
                size_t v0_idx;
 | 
			
		||||
                if (i_v0 == vd_vertices.end()) {
 | 
			
		||||
                    graph->nodes.push_back(p0);
 | 
			
		||||
                    v0_idx = node_idx;
 | 
			
		||||
                    vd_vertices[v0] = node_idx;
 | 
			
		||||
                    node_idx++;
 | 
			
		||||
                } else {
 | 
			
		||||
                    v0_idx = i_v0->second;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                t_vd_vertices::const_iterator i_v1 = vd_vertices.find(v1);
 | 
			
		||||
                size_t v1_idx;
 | 
			
		||||
                if (i_v1 == vd_vertices.end()) {
 | 
			
		||||
                    graph->nodes.push_back(p1);
 | 
			
		||||
                    v1_idx = node_idx;
 | 
			
		||||
                    vd_vertices[v1] = node_idx;
 | 
			
		||||
                    node_idx++;
 | 
			
		||||
                } else {
 | 
			
		||||
                    v1_idx = i_v1->second;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                double dist = graph->nodes[v0_idx].distance_to(graph->nodes[v1_idx]);
 | 
			
		||||
                graph->add_edge(v0_idx, v1_idx, dist);
 | 
			
		||||
            const VD::vertex_type* v0 = edge->vertex0();
 | 
			
		||||
            const VD::vertex_type* v1 = edge->vertex1();
 | 
			
		||||
            Point p0 = Point(v0->x(), v0->y());
 | 
			
		||||
            Point p1 = Point(v1->x(), v1->y());
 | 
			
		||||
            
 | 
			
		||||
            // skip edge if any of its endpoints is outside our configuration space
 | 
			
		||||
            if (!env.contains_b(p0) || !env.contains_b(p1)) continue;
 | 
			
		||||
            
 | 
			
		||||
            t_vd_vertices::const_iterator i_v0 = vd_vertices.find(v0);
 | 
			
		||||
            size_t v0_idx;
 | 
			
		||||
            if (i_v0 == vd_vertices.end()) {
 | 
			
		||||
                graph->nodes.push_back(p0);
 | 
			
		||||
                vd_vertices[v0] = v0_idx = graph->nodes.size()-1;
 | 
			
		||||
            } else {
 | 
			
		||||
                v0_idx = i_v0->second;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            t_vd_vertices::const_iterator i_v1 = vd_vertices.find(v1);
 | 
			
		||||
            size_t v1_idx;
 | 
			
		||||
            if (i_v1 == vd_vertices.end()) {
 | 
			
		||||
                graph->nodes.push_back(p1);
 | 
			
		||||
                vd_vertices[v1] = v1_idx = graph->nodes.size()-1;
 | 
			
		||||
            } else {
 | 
			
		||||
                v1_idx = i_v1->second;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // Euclidean distance is used as weight for the graph edge
 | 
			
		||||
            double dist = graph->nodes[v0_idx].distance_to(graph->nodes[v1_idx]);
 | 
			
		||||
            graph->add_edge(v0_idx, v1_idx, dist);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return graph;
 | 
			
		||||
| 
						 | 
				
			
			@ -244,38 +318,61 @@ MotionPlannerGraph::shortest_path(size_t from, size_t to, Polyline* polyline)
 | 
			
		|||
    
 | 
			
		||||
    const weight_t max_weight = std::numeric_limits<weight_t>::infinity();
 | 
			
		||||
    
 | 
			
		||||
    std::vector<weight_t> min_distance;
 | 
			
		||||
    std::vector<weight_t> dist;
 | 
			
		||||
    std::vector<node_t> previous;
 | 
			
		||||
    {
 | 
			
		||||
        int n = this->adjacency_list.size();
 | 
			
		||||
        min_distance.clear();
 | 
			
		||||
        min_distance.resize(n, max_weight);
 | 
			
		||||
        min_distance[from] = 0;
 | 
			
		||||
        // number of nodes
 | 
			
		||||
        size_t n = this->adjacency_list.size();
 | 
			
		||||
        
 | 
			
		||||
        // initialize dist and previous
 | 
			
		||||
        dist.clear();
 | 
			
		||||
        dist.resize(n, max_weight);
 | 
			
		||||
        dist[from] = 0;  // distance from 'from' to itself
 | 
			
		||||
        previous.clear();
 | 
			
		||||
        previous.resize(n, -1);
 | 
			
		||||
        std::set<std::pair<weight_t, node_t> > vertex_queue;
 | 
			
		||||
        vertex_queue.insert(std::make_pair(min_distance[from], from));
 | 
			
		||||
 
 | 
			
		||||
        while (!vertex_queue.empty()) 
 | 
			
		||||
        
 | 
			
		||||
        // initialize the Q with all nodes
 | 
			
		||||
        std::set<node_t> Q;
 | 
			
		||||
        for (node_t i = 0; i < n; ++i) Q.insert(i);
 | 
			
		||||
        
 | 
			
		||||
        while (!Q.empty()) 
 | 
			
		||||
        {
 | 
			
		||||
            weight_t dist = vertex_queue.begin()->first;
 | 
			
		||||
            node_t u = vertex_queue.begin()->second;
 | 
			
		||||
            vertex_queue.erase(vertex_queue.begin());
 | 
			
		||||
 
 | 
			
		||||
            // Visit each edge exiting u
 | 
			
		||||
            // get node in Q having the minimum dist ('from' in the first loop)
 | 
			
		||||
            node_t u;
 | 
			
		||||
            {
 | 
			
		||||
                double min_dist = -1;
 | 
			
		||||
                for (std::set<node_t>::const_iterator n = Q.begin(); n != Q.end(); ++n) {
 | 
			
		||||
                    if (dist[*n] < min_dist || min_dist == -1) {
 | 
			
		||||
                        u = *n;
 | 
			
		||||
                        min_dist = dist[*n];
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Q.erase(u);
 | 
			
		||||
            
 | 
			
		||||
            // stop searching if we reached our destination
 | 
			
		||||
            if (u == to) break;
 | 
			
		||||
            
 | 
			
		||||
            // Visit each edge starting from node u
 | 
			
		||||
            const std::vector<neighbor> &neighbors = this->adjacency_list[u];
 | 
			
		||||
            for (std::vector<neighbor>::const_iterator neighbor_iter = neighbors.begin();
 | 
			
		||||
                 neighbor_iter != neighbors.end();
 | 
			
		||||
                 neighbor_iter++)
 | 
			
		||||
            {
 | 
			
		||||
                // neighbor node is v
 | 
			
		||||
                node_t v = neighbor_iter->target;
 | 
			
		||||
                weight_t weight = neighbor_iter->weight;
 | 
			
		||||
                weight_t distance_through_u = dist + weight;
 | 
			
		||||
                if (distance_through_u < min_distance[v]) {
 | 
			
		||||
                    vertex_queue.erase(std::make_pair(min_distance[v], v));
 | 
			
		||||
                    min_distance[v] = distance_through_u;
 | 
			
		||||
                
 | 
			
		||||
                // skip if we already visited this
 | 
			
		||||
                if (Q.find(v) == Q.end()) continue;
 | 
			
		||||
                
 | 
			
		||||
                // calculate total distance
 | 
			
		||||
                weight_t alt = dist[u] + neighbor_iter->weight;
 | 
			
		||||
                
 | 
			
		||||
                // if total distance through u is shorter than the previous
 | 
			
		||||
                // distance (if any) between 'from' and 'v', replace it
 | 
			
		||||
                if (alt < dist[v]) {
 | 
			
		||||
                    dist[v]     = alt;
 | 
			
		||||
                    previous[v] = u;
 | 
			
		||||
                    vertex_queue.insert(std::make_pair(min_distance[v], v));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -284,6 +381,7 @@ MotionPlannerGraph::shortest_path(size_t from, size_t to, Polyline* polyline)
 | 
			
		|||
    
 | 
			
		||||
    for (node_t vertex = to; vertex != -1; vertex = previous[vertex])
 | 
			
		||||
        polyline->points.push_back(this->nodes[vertex]);
 | 
			
		||||
    polyline->points.push_back(this->nodes[from]);
 | 
			
		||||
    polyline->reverse();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,10 +33,14 @@ class MotionPlanner
 | 
			
		|||
    
 | 
			
		||||
    void initialize();
 | 
			
		||||
    MotionPlannerGraph* init_graph(int island_idx);
 | 
			
		||||
    ExPolygonCollection get_env(size_t island_idx) const;
 | 
			
		||||
    Point nearest_env_point(const ExPolygonCollection &env, const Point &from, const Point &to) const;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MotionPlannerGraph
 | 
			
		||||
{
 | 
			
		||||
    friend class MotionPlanner;
 | 
			
		||||
    
 | 
			
		||||
    private:
 | 
			
		||||
    typedef size_t node_t;
 | 
			
		||||
    typedef double weight_t;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -76,6 +76,13 @@ MultiPoint::find_point(const Point &point) const
 | 
			
		|||
    return -1;  // not found
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
MultiPoint::has_boundary_point(const Point &point) const
 | 
			
		||||
{
 | 
			
		||||
    double dist = point.distance_to(point.projection_onto(*this));
 | 
			
		||||
    return dist < SCALED_EPSILON;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
MultiPoint::bounding_box(BoundingBox* bb) const
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,6 +30,7 @@ class MultiPoint
 | 
			
		|||
    double length() const;
 | 
			
		||||
    bool is_valid() const;
 | 
			
		||||
    int find_point(const Point &point) const;
 | 
			
		||||
    bool has_boundary_point(const Point &point) const;
 | 
			
		||||
    void bounding_box(BoundingBox* bb) const;
 | 
			
		||||
    
 | 
			
		||||
    static Points _douglas_peucker(const Points &points, const double tolerance);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -104,6 +104,32 @@ Point::nearest_point_index(const PointConstPtrs &points) const
 | 
			
		|||
    return idx;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* This method finds the point that is closest to both this point and the supplied one */
 | 
			
		||||
size_t
 | 
			
		||||
Point::nearest_waypoint_index(const Points &points, const Point &dest) const
 | 
			
		||||
{
 | 
			
		||||
    size_t idx = -1;
 | 
			
		||||
    double distance = -1;  // double because long is limited to 2147483647 on some platforms and it's not enough
 | 
			
		||||
    
 | 
			
		||||
    for (Points::const_iterator p = points.begin(); p != points.end(); ++p) {
 | 
			
		||||
        // distance from this to candidate
 | 
			
		||||
        double d = pow(this->x - p->x, 2) + pow(this->y - p->y, 2);
 | 
			
		||||
        
 | 
			
		||||
        // distance from candidate to dest
 | 
			
		||||
        d += pow(p->x - dest.x, 2) + pow(p->y - dest.y, 2);
 | 
			
		||||
        
 | 
			
		||||
        // if the total distance is greater than current min distance, ignore it
 | 
			
		||||
        if (distance != -1 && d > distance) continue;
 | 
			
		||||
        
 | 
			
		||||
        idx = p - points.begin();
 | 
			
		||||
        distance = d;
 | 
			
		||||
        
 | 
			
		||||
        if (distance < EPSILON) break;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return idx;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
Point::nearest_point_index(const PointPtrs &points) const
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -123,6 +149,15 @@ Point::nearest_point(const Points &points, Point* point) const
 | 
			
		|||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
Point::nearest_waypoint(const Points &points, const Point &dest, Point* point) const
 | 
			
		||||
{
 | 
			
		||||
    int idx = this->nearest_waypoint_index(points, dest);
 | 
			
		||||
    if (idx == -1) return false;
 | 
			
		||||
    *point = points.at(idx);
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
double
 | 
			
		||||
Point::distance_to(const Point &point) const
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,7 +45,9 @@ class Point
 | 
			
		|||
    int nearest_point_index(const Points &points) const;
 | 
			
		||||
    int nearest_point_index(const PointConstPtrs &points) const;
 | 
			
		||||
    int nearest_point_index(const PointPtrs &points) const;
 | 
			
		||||
    size_t nearest_waypoint_index(const Points &points, const Point &point) const;
 | 
			
		||||
    bool nearest_point(const Points &points, Point* point) const;
 | 
			
		||||
    bool nearest_waypoint(const Points &points, const Point &dest, Point* point) const;
 | 
			
		||||
    double distance_to(const Point &point) const;
 | 
			
		||||
    double distance_to(const Line &line) const;
 | 
			
		||||
    double perp_distance_to(const Line &line) const;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,6 @@
 | 
			
		|||
#include "Polyline.hpp"
 | 
			
		||||
#include "ExPolygon.hpp"
 | 
			
		||||
#include "ExPolygonCollection.hpp"
 | 
			
		||||
#include "Line.hpp"
 | 
			
		||||
#include "Polygon.hpp"
 | 
			
		||||
#include <iostream>
 | 
			
		||||
| 
						 | 
				
			
			@ -127,6 +129,37 @@ Polyline::simplify(double tolerance)
 | 
			
		|||
    this->points = MultiPoint::_douglas_peucker(this->points, tolerance);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* This method simplifies all *lines* contained in the supplied area */
 | 
			
		||||
template <class T>
 | 
			
		||||
void
 | 
			
		||||
Polyline::simplify_by_visibility(const T &area)
 | 
			
		||||
{
 | 
			
		||||
    Points &pp = this->points;
 | 
			
		||||
    
 | 
			
		||||
    // find first point in area
 | 
			
		||||
    size_t start = 0;
 | 
			
		||||
    while (start < pp.size() && !area.contains(pp[start])) {
 | 
			
		||||
        start++;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    for (size_t s = start; s < pp.size() && !pp.empty(); ++s) {
 | 
			
		||||
        // find the farthest point to which we can build
 | 
			
		||||
        // a line that is contained in the supplied area
 | 
			
		||||
        // a binary search would be more efficient for this
 | 
			
		||||
        for (size_t e = pp.size()-1; e > (s + 1); --e) {
 | 
			
		||||
            if (area.contains(Line(pp[s], pp[e]))) {
 | 
			
		||||
                // we can suppress points between s and e
 | 
			
		||||
                pp.erase(pp.begin() + s + 1, pp.begin() + e);
 | 
			
		||||
                
 | 
			
		||||
                // repeat recursively until no further simplification is possible
 | 
			
		||||
                return this->simplify_by_visibility(area);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
template void Polyline::simplify_by_visibility<ExPolygon>(const ExPolygon &area);
 | 
			
		||||
template void Polyline::simplify_by_visibility<ExPolygonCollection>(const ExPolygonCollection &area);
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -159,7 +192,7 @@ Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const
 | 
			
		|||
    p2->points.clear();
 | 
			
		||||
    p2->points.push_back(point);
 | 
			
		||||
    for (Lines::const_iterator line = lines.begin() + line_idx; line != lines.end(); ++line) {
 | 
			
		||||
        if (!line->b.coincides_with(p)) p2->points.push_back(line->b);
 | 
			
		||||
        p2->points.push_back(line->b);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,7 @@
 | 
			
		|||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
 | 
			
		||||
class ExPolygon;
 | 
			
		||||
class Polyline;
 | 
			
		||||
typedef std::vector<Polyline> Polylines;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -23,6 +24,7 @@ class Polyline : public MultiPoint {
 | 
			
		|||
    void extend_start(double distance);
 | 
			
		||||
    void equally_spaced_points(double distance, Points* points) const;
 | 
			
		||||
    void simplify(double tolerance);
 | 
			
		||||
    template <class T> void simplify_by_visibility(const T &area);
 | 
			
		||||
    void split_at(const Point &point, Polyline* p1, Polyline* p2) const;
 | 
			
		||||
    bool is_straight() const;
 | 
			
		||||
    std::string wkt() const;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -561,25 +561,26 @@ Print::validate() const
 | 
			
		|||
            FOREACH_OBJECT(this, i_object) {
 | 
			
		||||
                PrintObject* object = *i_object;
 | 
			
		||||
                
 | 
			
		||||
                // get convex hulls of all meshes assigned to this print object
 | 
			
		||||
                Polygons mesh_convex_hulls;
 | 
			
		||||
                for (size_t i = 0; i < this->regions.size(); ++i) {
 | 
			
		||||
                    for (std::vector<int>::const_iterator it = object->region_volumes[i].begin(); it != object->region_volumes[i].end(); ++it) {
 | 
			
		||||
                        Polygon hull;
 | 
			
		||||
                        object->model_object()->volumes[*it]->mesh.convex_hull(&hull);
 | 
			
		||||
                        mesh_convex_hulls.push_back(hull);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                // make a single convex hull for all of them
 | 
			
		||||
                /*  get convex hull of all meshes assigned to this print object
 | 
			
		||||
                    (this is the same as model_object()->raw_mesh.convex_hull()
 | 
			
		||||
                    but probably more efficient */
 | 
			
		||||
                Polygon convex_hull;
 | 
			
		||||
                Slic3r::Geometry::convex_hull(mesh_convex_hulls, &convex_hull);
 | 
			
		||||
                {
 | 
			
		||||
                    Polygons mesh_convex_hulls;
 | 
			
		||||
                    for (size_t i = 0; i < this->regions.size(); ++i) {
 | 
			
		||||
                        for (std::vector<int>::const_iterator it = object->region_volumes[i].begin(); it != object->region_volumes[i].end(); ++it) {
 | 
			
		||||
                            Polygon hull;
 | 
			
		||||
                            object->model_object()->volumes[*it]->mesh.convex_hull(&hull);
 | 
			
		||||
                            mesh_convex_hulls.push_back(hull);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                
 | 
			
		||||
                    // make a single convex hull for all of them
 | 
			
		||||
                    Slic3r::Geometry::convex_hull(mesh_convex_hulls, &convex_hull);
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                // apply the same transformations we apply to the actual meshes when slicing them
 | 
			
		||||
                object->model_object()->instances.front()->transform_polygon(&convex_hull);
 | 
			
		||||
        
 | 
			
		||||
                // align object to Z = 0 and apply XY shift
 | 
			
		||||
                convex_hull.translate(object->_copies_shift);
 | 
			
		||||
                
 | 
			
		||||
                // grow convex hull with the clearance margin
 | 
			
		||||
                {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -964,6 +964,11 @@ PrintConfigDef::build_def() {
 | 
			
		|||
    Options["use_relative_e_distances"].tooltip = "If your firmware requires relative E values, check this, otherwise leave it unchecked. Most firmwares use absolute values.";
 | 
			
		||||
    Options["use_relative_e_distances"].cli = "use-relative-e-distances!";
 | 
			
		||||
 | 
			
		||||
    Options["use_volumetric_e"].type = coBool;
 | 
			
		||||
    Options["use_volumetric_e"].label = "Use volumetric E";
 | 
			
		||||
    Options["use_volumetric_e"].tooltip = "This experimental setting uses outputs the E values in cubic millimeters instead of linear millimeters. The M200 command is prepended to the generated G-code, unless it is found in the configured start G-code. This is only supported in recent Marlin.";
 | 
			
		||||
    Options["use_volumetric_e"].cli = "use-volumetric-e!";
 | 
			
		||||
 | 
			
		||||
    Options["vibration_limit"].type = coFloat;
 | 
			
		||||
    Options["vibration_limit"].label = "Vibration limit (deprecated)";
 | 
			
		||||
    Options["vibration_limit"].tooltip = "This experimental option will slow down those moves hitting the configured frequency limit. The purpose of limiting vibrations is to avoid mechanical resonance. Set zero to disable.";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -321,11 +321,13 @@ class PrintRegionConfig : public virtual StaticPrintConfig
 | 
			
		|||
class GCodeConfig : public virtual StaticPrintConfig
 | 
			
		||||
{
 | 
			
		||||
    public:
 | 
			
		||||
    ConfigOptionString              end_gcode;
 | 
			
		||||
    ConfigOptionString              extrusion_axis;
 | 
			
		||||
    ConfigOptionFloats              extrusion_multiplier;
 | 
			
		||||
    ConfigOptionFloats              filament_diameter;
 | 
			
		||||
    ConfigOptionBool                gcode_comments;
 | 
			
		||||
    ConfigOptionEnum<GCodeFlavor>   gcode_flavor;
 | 
			
		||||
    ConfigOptionString              layer_gcode;
 | 
			
		||||
    ConfigOptionFloat               pressure_advance;
 | 
			
		||||
    ConfigOptionFloats              retract_length;
 | 
			
		||||
    ConfigOptionFloats              retract_length_toolchange;
 | 
			
		||||
| 
						 | 
				
			
			@ -333,11 +335,15 @@ class GCodeConfig : public virtual StaticPrintConfig
 | 
			
		|||
    ConfigOptionFloats              retract_restart_extra;
 | 
			
		||||
    ConfigOptionFloats              retract_restart_extra_toolchange;
 | 
			
		||||
    ConfigOptionInts                retract_speed;
 | 
			
		||||
    ConfigOptionString              start_gcode;
 | 
			
		||||
    ConfigOptionString              toolchange_gcode;
 | 
			
		||||
    ConfigOptionFloat               travel_speed;
 | 
			
		||||
    ConfigOptionBool                use_firmware_retraction;
 | 
			
		||||
    ConfigOptionBool                use_relative_e_distances;
 | 
			
		||||
    ConfigOptionBool                use_volumetric_e;
 | 
			
		||||
    
 | 
			
		||||
    GCodeConfig() : StaticPrintConfig() {
 | 
			
		||||
        this->end_gcode.value                                    = "M104 S0 ; turn off temperature\nG28 X0  ; home X axis\nM84     ; disable motors\n";
 | 
			
		||||
        this->extrusion_axis.value                               = "E";
 | 
			
		||||
        this->extrusion_multiplier.values.resize(1);
 | 
			
		||||
        this->extrusion_multiplier.values[0]                     = 1;
 | 
			
		||||
| 
						 | 
				
			
			@ -345,6 +351,7 @@ class GCodeConfig : public virtual StaticPrintConfig
 | 
			
		|||
        this->filament_diameter.values[0]                        = 3;
 | 
			
		||||
        this->gcode_comments.value                               = false;
 | 
			
		||||
        this->gcode_flavor.value                                 = gcfRepRap;
 | 
			
		||||
        this->layer_gcode.value                                  = "";
 | 
			
		||||
        this->pressure_advance.value                             = 0;
 | 
			
		||||
        this->retract_length.values.resize(1);
 | 
			
		||||
        this->retract_length.values[0]                           = 1;
 | 
			
		||||
| 
						 | 
				
			
			@ -358,17 +365,22 @@ class GCodeConfig : public virtual StaticPrintConfig
 | 
			
		|||
        this->retract_restart_extra_toolchange.values[0]         = 0;
 | 
			
		||||
        this->retract_speed.values.resize(1);
 | 
			
		||||
        this->retract_speed.values[0]                            = 30;
 | 
			
		||||
        this->start_gcode.value                                  = "G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n";
 | 
			
		||||
        this->toolchange_gcode.value                             = "";
 | 
			
		||||
        this->travel_speed.value                                 = 130;
 | 
			
		||||
        this->use_firmware_retraction.value                      = false;
 | 
			
		||||
        this->use_relative_e_distances.value                     = false;
 | 
			
		||||
        this->use_volumetric_e.value                             = false;
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    ConfigOption* option(const t_config_option_key opt_key, bool create = false) {
 | 
			
		||||
        if (opt_key == "end_gcode")                                  return &this->end_gcode;
 | 
			
		||||
        if (opt_key == "extrusion_axis")                             return &this->extrusion_axis;
 | 
			
		||||
        if (opt_key == "extrusion_multiplier")                       return &this->extrusion_multiplier;
 | 
			
		||||
        if (opt_key == "filament_diameter")                          return &this->filament_diameter;
 | 
			
		||||
        if (opt_key == "gcode_comments")                             return &this->gcode_comments;
 | 
			
		||||
        if (opt_key == "gcode_flavor")                               return &this->gcode_flavor;
 | 
			
		||||
        if (opt_key == "layer_gcode")                                return &this->layer_gcode;
 | 
			
		||||
        if (opt_key == "pressure_advance")                           return &this->pressure_advance;
 | 
			
		||||
        if (opt_key == "retract_length")                             return &this->retract_length;
 | 
			
		||||
        if (opt_key == "retract_length_toolchange")                  return &this->retract_length_toolchange;
 | 
			
		||||
| 
						 | 
				
			
			@ -376,9 +388,12 @@ class GCodeConfig : public virtual StaticPrintConfig
 | 
			
		|||
        if (opt_key == "retract_restart_extra")                      return &this->retract_restart_extra;
 | 
			
		||||
        if (opt_key == "retract_restart_extra_toolchange")           return &this->retract_restart_extra_toolchange;
 | 
			
		||||
        if (opt_key == "retract_speed")                              return &this->retract_speed;
 | 
			
		||||
        if (opt_key == "start_gcode")                                return &this->start_gcode;
 | 
			
		||||
        if (opt_key == "toolchange_gcode")                           return &this->toolchange_gcode;
 | 
			
		||||
        if (opt_key == "travel_speed")                               return &this->travel_speed;
 | 
			
		||||
        if (opt_key == "use_firmware_retraction")                    return &this->use_firmware_retraction;
 | 
			
		||||
        if (opt_key == "use_relative_e_distances")                   return &this->use_relative_e_distances;
 | 
			
		||||
        if (opt_key == "use_volumetric_e")                           return &this->use_volumetric_e;
 | 
			
		||||
        
 | 
			
		||||
        return NULL;
 | 
			
		||||
    };
 | 
			
		||||
| 
						 | 
				
			
			@ -409,7 +424,6 @@ class PrintConfig : public GCodeConfig
 | 
			
		|||
    ConfigOptionFloat               default_acceleration;
 | 
			
		||||
    ConfigOptionInt                 disable_fan_first_layers;
 | 
			
		||||
    ConfigOptionFloat               duplicate_distance;
 | 
			
		||||
    ConfigOptionString              end_gcode;
 | 
			
		||||
    ConfigOptionFloat               extruder_clearance_height;
 | 
			
		||||
    ConfigOptionFloat               extruder_clearance_radius;
 | 
			
		||||
    ConfigOptionPoints              extruder_offset;
 | 
			
		||||
| 
						 | 
				
			
			@ -423,7 +437,6 @@ class PrintConfig : public GCodeConfig
 | 
			
		|||
    ConfigOptionBool                gcode_arcs;
 | 
			
		||||
    ConfigOptionFloat               infill_acceleration;
 | 
			
		||||
    ConfigOptionBool                infill_first;
 | 
			
		||||
    ConfigOptionString              layer_gcode;
 | 
			
		||||
    ConfigOptionInt                 max_fan_speed;
 | 
			
		||||
    ConfigOptionInt                 min_fan_speed;
 | 
			
		||||
    ConfigOptionInt                 min_print_speed;
 | 
			
		||||
| 
						 | 
				
			
			@ -444,10 +457,8 @@ class PrintConfig : public GCodeConfig
 | 
			
		|||
    ConfigOptionInt                 slowdown_below_layer_time;
 | 
			
		||||
    ConfigOptionBool                spiral_vase;
 | 
			
		||||
    ConfigOptionInt                 standby_temperature_delta;
 | 
			
		||||
    ConfigOptionString              start_gcode;
 | 
			
		||||
    ConfigOptionInts                temperature;
 | 
			
		||||
    ConfigOptionInt                 threads;
 | 
			
		||||
    ConfigOptionString              toolchange_gcode;
 | 
			
		||||
    ConfigOptionFloat               vibration_limit;
 | 
			
		||||
    ConfigOptionBools               wipe;
 | 
			
		||||
    ConfigOptionFloat               z_offset;
 | 
			
		||||
| 
						 | 
				
			
			@ -467,7 +478,6 @@ class PrintConfig : public GCodeConfig
 | 
			
		|||
        this->default_acceleration.value                         = 0;
 | 
			
		||||
        this->disable_fan_first_layers.value                     = 1;
 | 
			
		||||
        this->duplicate_distance.value                           = 6;
 | 
			
		||||
        this->end_gcode.value                                    = "M104 S0 ; turn off temperature\nG28 X0  ; home X axis\nM84     ; disable motors\n";
 | 
			
		||||
        this->extruder_clearance_height.value                    = 20;
 | 
			
		||||
        this->extruder_clearance_radius.value                    = 20;
 | 
			
		||||
        this->extruder_offset.values.resize(1);
 | 
			
		||||
| 
						 | 
				
			
			@ -485,7 +495,6 @@ class PrintConfig : public GCodeConfig
 | 
			
		|||
        this->gcode_arcs.value                                   = false;
 | 
			
		||||
        this->infill_acceleration.value                          = 0;
 | 
			
		||||
        this->infill_first.value                                 = false;
 | 
			
		||||
        this->layer_gcode.value                                  = "";
 | 
			
		||||
        this->max_fan_speed.value                                = 100;
 | 
			
		||||
        this->min_fan_speed.value                                = 35;
 | 
			
		||||
        this->min_print_speed.value                              = 10;
 | 
			
		||||
| 
						 | 
				
			
			@ -508,11 +517,9 @@ class PrintConfig : public GCodeConfig
 | 
			
		|||
        this->slowdown_below_layer_time.value                    = 30;
 | 
			
		||||
        this->spiral_vase.value                                  = false;
 | 
			
		||||
        this->standby_temperature_delta.value                    = -5;
 | 
			
		||||
        this->start_gcode.value                                  = "G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n";
 | 
			
		||||
        this->temperature.values.resize(1);
 | 
			
		||||
        this->temperature.values[0]                              = 200;
 | 
			
		||||
        this->threads.value                                      = 2;
 | 
			
		||||
        this->toolchange_gcode.value                             = "";
 | 
			
		||||
        this->vibration_limit.value                              = 0;
 | 
			
		||||
        this->wipe.values.resize(1);
 | 
			
		||||
        this->wipe.values[0]                                     = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -531,7 +538,6 @@ class PrintConfig : public GCodeConfig
 | 
			
		|||
        if (opt_key == "default_acceleration")                       return &this->default_acceleration;
 | 
			
		||||
        if (opt_key == "disable_fan_first_layers")                   return &this->disable_fan_first_layers;
 | 
			
		||||
        if (opt_key == "duplicate_distance")                         return &this->duplicate_distance;
 | 
			
		||||
        if (opt_key == "end_gcode")                                  return &this->end_gcode;
 | 
			
		||||
        if (opt_key == "extruder_clearance_height")                  return &this->extruder_clearance_height;
 | 
			
		||||
        if (opt_key == "extruder_clearance_radius")                  return &this->extruder_clearance_radius;
 | 
			
		||||
        if (opt_key == "extruder_offset")                            return &this->extruder_offset;
 | 
			
		||||
| 
						 | 
				
			
			@ -545,7 +551,6 @@ class PrintConfig : public GCodeConfig
 | 
			
		|||
        if (opt_key == "gcode_arcs")                                 return &this->gcode_arcs;
 | 
			
		||||
        if (opt_key == "infill_acceleration")                        return &this->infill_acceleration;
 | 
			
		||||
        if (opt_key == "infill_first")                               return &this->infill_first;
 | 
			
		||||
        if (opt_key == "layer_gcode")                                return &this->layer_gcode;
 | 
			
		||||
        if (opt_key == "max_fan_speed")                              return &this->max_fan_speed;
 | 
			
		||||
        if (opt_key == "min_fan_speed")                              return &this->min_fan_speed;
 | 
			
		||||
        if (opt_key == "min_print_speed")                            return &this->min_print_speed;
 | 
			
		||||
| 
						 | 
				
			
			@ -566,10 +571,8 @@ class PrintConfig : public GCodeConfig
 | 
			
		|||
        if (opt_key == "slowdown_below_layer_time")                  return &this->slowdown_below_layer_time;
 | 
			
		||||
        if (opt_key == "spiral_vase")                                return &this->spiral_vase;
 | 
			
		||||
        if (opt_key == "standby_temperature_delta")                  return &this->standby_temperature_delta;
 | 
			
		||||
        if (opt_key == "start_gcode")                                return &this->start_gcode;
 | 
			
		||||
        if (opt_key == "temperature")                                return &this->temperature;
 | 
			
		||||
        if (opt_key == "threads")                                    return &this->threads;
 | 
			
		||||
        if (opt_key == "toolchange_gcode")                           return &this->toolchange_gcode;
 | 
			
		||||
        if (opt_key == "vibration_limit")                            return &this->vibration_limit;
 | 
			
		||||
        if (opt_key == "wipe")                                       return &this->wipe;
 | 
			
		||||
        if (opt_key == "z_offset")                                   return &this->z_offset;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,12 @@
 | 
			
		|||
#include "SVG.hpp"
 | 
			
		||||
#include <iostream>
 | 
			
		||||
 | 
			
		||||
#define COORD(x) ((float)unscale(x)*10)
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
 | 
			
		||||
SVG::SVG(const char* filename)
 | 
			
		||||
    : arrows(true), filename(filename), fill("grey"), stroke("black")
 | 
			
		||||
{
 | 
			
		||||
    this->f = fopen(filename, "w");
 | 
			
		||||
    fprintf(this->f,
 | 
			
		||||
| 
						 | 
				
			
			@ -13,21 +17,14 @@ SVG::SVG(const char* filename)
 | 
			
		|||
		"      <polyline fill=\"darkblue\" points=\"0,0 10,5 0,10 1,5\" />\n"
 | 
			
		||||
	    "   </marker>\n"
 | 
			
		||||
	    );
 | 
			
		||||
	this->arrows = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float
 | 
			
		||||
SVG::coordinate(long c)
 | 
			
		||||
{
 | 
			
		||||
    return (float)unscale(c)*10;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
SVG::AddLine(const Line &line)
 | 
			
		||||
SVG::draw(const Line &line, std::string stroke)
 | 
			
		||||
{
 | 
			
		||||
    fprintf(this->f,
 | 
			
		||||
        "   <line x1=\"%f\" y1=\"%f\" x2=\"%f\" y2=\"%f\" style=\"stroke: black; stroke-width: 2\"",
 | 
			
		||||
        this->coordinate(line.a.x), this->coordinate(line.a.y), this->coordinate(line.b.x), this->coordinate(line.b.y)
 | 
			
		||||
        "   <line x1=\"%f\" y1=\"%f\" x2=\"%f\" y2=\"%f\" style=\"stroke: %s; stroke-width: 1\"",
 | 
			
		||||
        COORD(line.a.x), COORD(line.a.y), COORD(line.b.x), COORD(line.b.y), stroke.c_str()
 | 
			
		||||
        );
 | 
			
		||||
    if (this->arrows)
 | 
			
		||||
        fprintf(this->f, " marker-end=\"url(#endArrow)\"");
 | 
			
		||||
| 
						 | 
				
			
			@ -37,7 +34,72 @@ SVG::AddLine(const Line &line)
 | 
			
		|||
void
 | 
			
		||||
SVG::AddLine(const IntersectionLine &line)
 | 
			
		||||
{
 | 
			
		||||
    this->AddLine(Line(line.a, line.b));
 | 
			
		||||
    this->draw(Line(line.a, line.b));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
SVG::draw(const ExPolygon &expolygon, std::string fill)
 | 
			
		||||
{
 | 
			
		||||
    this->fill = fill;
 | 
			
		||||
    
 | 
			
		||||
    std::string d;
 | 
			
		||||
    Polygons pp = expolygon;
 | 
			
		||||
    for (Polygons::const_iterator p = pp.begin(); p != pp.end(); ++p) {
 | 
			
		||||
        d += this->get_path_d(*p, true) + " ";
 | 
			
		||||
    }
 | 
			
		||||
    this->path(d, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
SVG::draw(const Polygon &polygon, std::string fill)
 | 
			
		||||
{
 | 
			
		||||
    this->fill = fill;
 | 
			
		||||
    this->path(this->get_path_d(polygon, true), true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
SVG::draw(const Polyline &polyline, std::string stroke)
 | 
			
		||||
{
 | 
			
		||||
    this->stroke = stroke;
 | 
			
		||||
    this->path(this->get_path_d(polyline, false), false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
SVG::draw(const Point &point, std::string fill, unsigned int radius)
 | 
			
		||||
{
 | 
			
		||||
    std::ostringstream svg;
 | 
			
		||||
    svg << "   <circle cx=\"" << COORD(point.x) << "\" cy=\"" << COORD(point.y)
 | 
			
		||||
        << "\" r=\"" << radius << "\" "
 | 
			
		||||
        << "style=\"stroke: none; fill: " << fill << "\" />";
 | 
			
		||||
    
 | 
			
		||||
    fprintf(this->f, "%s\n", svg.str().c_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
SVG::path(const std::string &d, bool fill)
 | 
			
		||||
{
 | 
			
		||||
    fprintf(
 | 
			
		||||
        this->f,
 | 
			
		||||
        "   <path d=\"%s\" style=\"fill: %s; stroke: %s; stroke-width: %s; fill-type: evenodd\" %s />\n",
 | 
			
		||||
        d.c_str(),
 | 
			
		||||
        fill ? this->fill.c_str() : "none",
 | 
			
		||||
        this->stroke.c_str(),
 | 
			
		||||
        fill ? "0" : "2",
 | 
			
		||||
        (this->arrows && !fill) ? " marker-end=\"url(#endArrow)\"" : ""
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string
 | 
			
		||||
SVG::get_path_d(const MultiPoint &mp, bool closed) const
 | 
			
		||||
{
 | 
			
		||||
    std::ostringstream d;
 | 
			
		||||
    d << "M ";
 | 
			
		||||
    for (Points::const_iterator p = mp.points.begin(); p != mp.points.end(); ++p) {
 | 
			
		||||
        d << COORD(p->x) << " ";
 | 
			
		||||
        d << COORD(p->y) << " ";
 | 
			
		||||
    }
 | 
			
		||||
    if (closed) d << "z";
 | 
			
		||||
    return d.str();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
| 
						 | 
				
			
			@ -45,7 +107,7 @@ SVG::Close()
 | 
			
		|||
{
 | 
			
		||||
    fprintf(this->f, "</svg>\n");
 | 
			
		||||
    fclose(this->f);
 | 
			
		||||
    printf("SVG file written.\n");
 | 
			
		||||
    printf("SVG written to %s\n", this->filename.c_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@
 | 
			
		|||
#define slic3r_SVG_hpp_
 | 
			
		||||
 | 
			
		||||
#include <myinit.h>
 | 
			
		||||
#include "ExPolygon.hpp"
 | 
			
		||||
#include "Line.hpp"
 | 
			
		||||
#include "TriangleMesh.hpp"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -9,15 +10,25 @@ namespace Slic3r {
 | 
			
		|||
 | 
			
		||||
class SVG
 | 
			
		||||
{
 | 
			
		||||
    private:
 | 
			
		||||
    FILE* f;
 | 
			
		||||
    float coordinate(long c);
 | 
			
		||||
    public:
 | 
			
		||||
    bool arrows;
 | 
			
		||||
    std::string fill, stroke;
 | 
			
		||||
    
 | 
			
		||||
    SVG(const char* filename);
 | 
			
		||||
    void AddLine(const Line &line);
 | 
			
		||||
    void AddLine(const IntersectionLine &line);
 | 
			
		||||
    void draw(const Line &line, std::string stroke = "black");
 | 
			
		||||
    void draw(const ExPolygon &expolygon, std::string fill = "grey");
 | 
			
		||||
    void draw(const Polygon &polygon, std::string fill = "grey");
 | 
			
		||||
    void draw(const Polyline &polyline, std::string stroke = "black");
 | 
			
		||||
    void draw(const Point &point, std::string fill = "black", unsigned int radius = 3);
 | 
			
		||||
    void Close();
 | 
			
		||||
    
 | 
			
		||||
    private:
 | 
			
		||||
    std::string filename;
 | 
			
		||||
    FILE* f;
 | 
			
		||||
    
 | 
			
		||||
    void path(const std::string &d, bool fill);
 | 
			
		||||
    std::string get_path_d(const MultiPoint &mp, bool closed = false) const;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -77,9 +77,19 @@ SurfaceCollection::any_internal_contains(const T &item) const
 | 
			
		|||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
template bool SurfaceCollection::any_internal_contains<Line>(const Line &item) const;
 | 
			
		||||
template bool SurfaceCollection::any_internal_contains<Polyline>(const Polyline &item) const;
 | 
			
		||||
 | 
			
		||||
template <class T>
 | 
			
		||||
bool
 | 
			
		||||
SurfaceCollection::any_bottom_contains(const T &item) const
 | 
			
		||||
{
 | 
			
		||||
    for (Surfaces::const_iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) {
 | 
			
		||||
        if (surface->is_bottom() && surface->expolygon.contains(item)) return true;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
template bool SurfaceCollection::any_bottom_contains<Polyline>(const Polyline &item) const;
 | 
			
		||||
 | 
			
		||||
SurfacesPtr
 | 
			
		||||
SurfaceCollection::filter_by_type(SurfaceType type)
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,6 +16,7 @@ class SurfaceCollection
 | 
			
		|||
    void simplify(double tolerance);
 | 
			
		||||
    void group(std::vector<SurfacesPtr> *retval);
 | 
			
		||||
    template <class T> bool any_internal_contains(const T &item) const;
 | 
			
		||||
    template <class T> bool any_bottom_contains(const T &item) const;
 | 
			
		||||
    SurfacesPtr filter_by_type(SurfaceType type);
 | 
			
		||||
    void filter_by_type(SurfaceType type, Polygons* polygons);
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -614,10 +614,8 @@ TriangleMeshSlicer::slice_facet(float slice_z, const stl_facet &facet, const int
 | 
			
		|||
    if (!points.empty()) {
 | 
			
		||||
        assert(points.size() == 2); // facets must intersect each plane 0 or 2 times
 | 
			
		||||
        IntersectionLine line;
 | 
			
		||||
        line.a.x        = points[1].x;
 | 
			
		||||
        line.a.y        = points[1].y;
 | 
			
		||||
        line.b.x        = points[0].x;
 | 
			
		||||
        line.b.y        = points[0].y;
 | 
			
		||||
        line.a          = (Point)points[1];
 | 
			
		||||
        line.b          = (Point)points[0];
 | 
			
		||||
        line.a_id       = points[1].point_id;
 | 
			
		||||
        line.b_id       = points[0].point_id;
 | 
			
		||||
        line.edge_a_id  = points[1].edge_id;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@
 | 
			
		|||
#include <admesh/stl.h>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include "BoundingBox.hpp"
 | 
			
		||||
#include "Line.hpp"
 | 
			
		||||
#include "Point.hpp"
 | 
			
		||||
#include "Polygon.hpp"
 | 
			
		||||
#include "ExPolygon.hpp"
 | 
			
		||||
| 
						 | 
				
			
			@ -71,11 +72,9 @@ class IntersectionPoint : public Point
 | 
			
		|||
    IntersectionPoint() : point_id(-1), edge_id(-1) {};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class IntersectionLine
 | 
			
		||||
class IntersectionLine : public Line
 | 
			
		||||
{
 | 
			
		||||
    public:
 | 
			
		||||
    Point           a;
 | 
			
		||||
    Point           b;
 | 
			
		||||
    int             a_id;
 | 
			
		||||
    int             b_id;
 | 
			
		||||
    int             edge_a_id;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,7 @@
 | 
			
		|||
#include <iostream>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
 | 
			
		||||
#define SLIC3R_VERSION "1.2.4"
 | 
			
		||||
#define SLIC3R_VERSION "1.2.5-dev"
 | 
			
		||||
 | 
			
		||||
#define EPSILON 1e-4
 | 
			
		||||
#define SCALING_FACTOR 0.000001
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,7 @@ use warnings;
 | 
			
		|||
 | 
			
		||||
use List::Util qw(sum);
 | 
			
		||||
use Slic3r::XS;
 | 
			
		||||
use Test::More tests => 46;
 | 
			
		||||
use Test::More tests => 48;
 | 
			
		||||
 | 
			
		||||
{
 | 
			
		||||
    my $square = [
 | 
			
		||||
| 
						 | 
				
			
			@ -133,8 +133,10 @@ use Test::More tests => 46;
 | 
			
		|||
        Slic3r::ExtrusionPath->new(polyline => $polylines[2], role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1),
 | 
			
		||||
        Slic3r::ExtrusionPath->new(polyline => $polylines[3], role => Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, mm3_per_mm => 1),
 | 
			
		||||
    );
 | 
			
		||||
    my $len = $loop->length;
 | 
			
		||||
    my $point = Slic3r::Point->new(4821067,9321068);
 | 
			
		||||
    $loop->split_at_vertex($point) or $loop->split_at($point);
 | 
			
		||||
    is $loop->length, $len, 'total length is preserved after splitting';
 | 
			
		||||
    is_deeply [ map $_->role, @$loop ], [
 | 
			
		||||
        Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER,
 | 
			
		||||
        Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER,
 | 
			
		||||
| 
						 | 
				
			
			@ -144,4 +146,15 @@ use Test::More tests => 46;
 | 
			
		|||
    ], 'order is correctly preserved after splitting';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
{
 | 
			
		||||
    my $loop = Slic3r::ExtrusionLoop->new;
 | 
			
		||||
    $loop->append(Slic3r::ExtrusionPath->new(
 | 
			
		||||
        polyline    => Slic3r::Polyline->new([15896783,15868739],[24842049,12117558],[33853238,15801279],[37591780,24780128],[37591780,24844970],[33853231,33825297],[24842049,37509013],[15896798,33757841],[12211841,24812544],[15896783,15868739]),
 | 
			
		||||
        role        => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1
 | 
			
		||||
    ));
 | 
			
		||||
    my $len = $loop->length;
 | 
			
		||||
    $loop->split_at(Slic3r::Point->new(15896783,15868739));
 | 
			
		||||
    is $loop->length, $len, 'split_at() preserves total length';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
__END__
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@ use strict;
 | 
			
		|||
use warnings;
 | 
			
		||||
 | 
			
		||||
use Slic3r::XS;
 | 
			
		||||
use Test::More tests => 16;
 | 
			
		||||
use Test::More tests => 21;
 | 
			
		||||
 | 
			
		||||
my $points = [
 | 
			
		||||
    [100, 100],
 | 
			
		||||
| 
						 | 
				
			
			@ -79,4 +79,49 @@ is_deeply $polyline->pp, [ @$points, @$points ], 'append_polyline';
 | 
			
		|||
    ok $p2->first_point->coincides_with($point), 'split_at';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
{
 | 
			
		||||
    my $polyline = Slic3r::Polyline->new(@$points[0,1,2,0]);
 | 
			
		||||
    my $p1 = Slic3r::Polyline->new;
 | 
			
		||||
    my $p2 = Slic3r::Polyline->new;
 | 
			
		||||
    $polyline->split_at($polyline->first_point, $p1, $p2);
 | 
			
		||||
    is scalar(@$p1), 1, 'split_at';
 | 
			
		||||
    is scalar(@$p2), 4, 'split_at';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
{
 | 
			
		||||
    my $polyline = Slic3r::Polyline->new(
 | 
			
		||||
        map [$_,10], (0,10,20,30,40,50,60)
 | 
			
		||||
    );
 | 
			
		||||
    {
 | 
			
		||||
        my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new(
 | 
			
		||||
            [25,0], [55,0], [55,30], [25,30],
 | 
			
		||||
        ));
 | 
			
		||||
        my $p = $polyline->clone;
 | 
			
		||||
        $p->simplify_by_visibility($expolygon);
 | 
			
		||||
        is_deeply $p->pp, [
 | 
			
		||||
            map [$_,10], (0,10,20,30,50,60)
 | 
			
		||||
        ], 'simplify_by_visibility()';
 | 
			
		||||
    }
 | 
			
		||||
    {
 | 
			
		||||
        my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new(
 | 
			
		||||
            [-15,0], [75,0], [75,30], [-15,30],
 | 
			
		||||
        ));
 | 
			
		||||
        my $p = $polyline->clone;
 | 
			
		||||
        $p->simplify_by_visibility($expolygon);
 | 
			
		||||
        is_deeply $p->pp, [
 | 
			
		||||
            map [$_,10], (0,60)
 | 
			
		||||
        ], 'simplify_by_visibility()';
 | 
			
		||||
    }
 | 
			
		||||
    {
 | 
			
		||||
        my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new(
 | 
			
		||||
            [-15,0], [25,0], [25,30], [-15,30],
 | 
			
		||||
        ));
 | 
			
		||||
        my $p = $polyline->clone;
 | 
			
		||||
        $p->simplify_by_visibility($expolygon);
 | 
			
		||||
        is_deeply $p->pp, [
 | 
			
		||||
            map [$_,10], (0,20,30,40,50,60)
 | 
			
		||||
        ], 'simplify_by_visibility()';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
__END__
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -69,12 +69,10 @@
 | 
			
		|||
        %code%{ RETVAL = (int)(intptr_t)THIS; %};
 | 
			
		||||
    
 | 
			
		||||
    void make_slices();
 | 
			
		||||
    bool any_internal_region_slice_contains_line(Line* line)
 | 
			
		||||
        %code%{ RETVAL = THIS->any_internal_region_slice_contains(*line); %};
 | 
			
		||||
    bool any_internal_region_fill_surface_contains_line(Line* line)
 | 
			
		||||
        %code%{ RETVAL = THIS->any_internal_region_fill_surface_contains(*line); %};
 | 
			
		||||
    bool any_internal_region_fill_surface_contains_polyline(Polyline* polyline)
 | 
			
		||||
        %code%{ RETVAL = THIS->any_internal_region_fill_surface_contains(*polyline); %};
 | 
			
		||||
    bool any_internal_region_slice_contains_polyline(Polyline* polyline)
 | 
			
		||||
        %code%{ RETVAL = THIS->any_internal_region_slice_contains(*polyline); %};
 | 
			
		||||
    bool any_bottom_region_slice_contains_polyline(Polyline* polyline)
 | 
			
		||||
        %code%{ RETVAL = THIS->any_bottom_region_slice_contains(*polyline); %};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
%name{Slic3r::Layer::Support} class SupportLayer {
 | 
			
		||||
| 
						 | 
				
			
			@ -121,10 +119,8 @@
 | 
			
		|||
    Ref<ExPolygonCollection> slices()
 | 
			
		||||
        %code%{ RETVAL = &THIS->slices; %};
 | 
			
		||||
    
 | 
			
		||||
    bool any_internal_region_slice_contains_line(Line* line)
 | 
			
		||||
        %code%{ RETVAL = THIS->any_internal_region_slice_contains(*line); %};
 | 
			
		||||
    bool any_internal_region_fill_surface_contains_line(Line* line)
 | 
			
		||||
        %code%{ RETVAL = THIS->any_internal_region_fill_surface_contains(*line); %};
 | 
			
		||||
    bool any_internal_region_fill_surface_contains_polyline(Polyline* polyline)
 | 
			
		||||
        %code%{ RETVAL = THIS->any_internal_region_fill_surface_contains(*polyline); %};
 | 
			
		||||
    bool any_internal_region_slice_contains_polyline(Polyline* polyline)
 | 
			
		||||
        %code%{ RETVAL = THIS->any_internal_region_slice_contains(*polyline); %};
 | 
			
		||||
    bool any_bottom_region_slice_contains_polyline(Polyline* polyline)
 | 
			
		||||
        %code%{ RETVAL = THIS->any_bottom_region_slice_contains(*polyline); %};
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,6 +32,8 @@
 | 
			
		|||
    void extend_end(double distance);
 | 
			
		||||
    void extend_start(double distance);
 | 
			
		||||
    void simplify(double tolerance);
 | 
			
		||||
    void simplify_by_visibility(ExPolygon* expolygon)
 | 
			
		||||
        %code{% THIS->simplify_by_visibility(*expolygon); %};
 | 
			
		||||
    void split_at(Point* point, Polyline* p1, Polyline* p2)
 | 
			
		||||
        %code{% THIS->split_at(*point, p1, p2); %};
 | 
			
		||||
    bool is_straight();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue