diff --git a/Build.PL b/Build.PL index 513a26955..4ad56e18e 100644 --- a/Build.PL +++ b/Build.PL @@ -12,7 +12,7 @@ my $build = Module::Build->new( 'File::Spec' => '0', 'Getopt::Long' => '0', 'Math::Clipper' => '1.09', - 'Math::ConvexHull' => '1.0.4', + 'Math::ConvexHull::MonotoneChain' => '0.01', 'Math::Geometry::Voronoi' => '1.3', 'Math::PlanePath' => '53', 'Moo' => '0.091009', diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index e80357767..410e0b0b0 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -66,6 +66,7 @@ use constant SMALL_PERIMETER_LENGTH => (6.5 / SCALING_FACTOR) * 2 * PI; # them here because it makes accessing them slightly faster. our $Config; our $flow; +our $first_layer_flow; sub parallelize { my %params = @_; diff --git a/lib/Slic3r/ExtrusionPath.pm b/lib/Slic3r/ExtrusionPath.pm index a006d8712..c5466bcf7 100644 --- a/lib/Slic3r/ExtrusionPath.pm +++ b/lib/Slic3r/ExtrusionPath.pm @@ -160,6 +160,7 @@ sub detect_arcs { $max_angle = deg2rad($max_angle || 15); $len_epsilon ||= 10 / &Slic3r::SCALING_FACTOR; + my $parallel_degrees_limit = abs(Slic3r::Geometry::deg2rad(3)); my @points = @{$self->points}; my @paths = (); @@ -191,8 +192,8 @@ sub detect_arcs { $s3_angle += 2*PI if $s3_angle < 0; my $s1s2_angle = $s2_angle - $s1_angle; my $s2s3_angle = $s3_angle - $s2_angle; - next if abs($s1s2_angle - $s2s3_angle) > $Slic3r::Geometry::parallel_degrees_limit; - next if abs($s1s2_angle) < $Slic3r::Geometry::parallel_degrees_limit; # ignore parallel lines + next if abs($s1s2_angle - $s2s3_angle) > $parallel_degrees_limit; + next if abs($s1s2_angle) < $parallel_degrees_limit; # ignore parallel lines next if $s1s2_angle > $max_angle; # ignore too sharp vertices my @arc_points = ($points[$i], $points[$i+3]), # first and last points @@ -205,7 +206,7 @@ sub detect_arcs { my $line_angle = $line->atan; $line_angle += 2*PI if $line_angle < 0; my $anglediff = $line_angle - $last_line_angle; - last if abs($s1s2_angle - $anglediff) > $Slic3r::Geometry::parallel_degrees_limit; + last if abs($s1s2_angle - $anglediff) > $parallel_degrees_limit; # point $j+1 belongs to the arc $arc_points[-1] = $points[$j+1]; diff --git a/lib/Slic3r/Fill/Concentric.pm b/lib/Slic3r/Fill/Concentric.pm index 699b526d9..2a7c7823b 100644 --- a/lib/Slic3r/Fill/Concentric.pm +++ b/lib/Slic3r/Fill/Concentric.pm @@ -63,7 +63,7 @@ sub fill_surface { my $path = $loop->split_at_index($index); # clip the path to avoid the extruder to get exactly on the first point of the loop - $path->clip_end($self->layer ? $self->layer->flow->scaled_width : $Slic3r::flow->scaled_width * 0.15); + $path->clip_end(($self->layer ? $self->layer->flow->scaled_width : $Slic3r::flow->scaled_width) * 0.15); push @paths, $path->points if @{$path->points}; } diff --git a/lib/Slic3r/Fill/Honeycomb.pm b/lib/Slic3r/Fill/Honeycomb.pm index 70dd1e2ad..66fbd7ebb 100644 --- a/lib/Slic3r/Fill/Honeycomb.pm +++ b/lib/Slic3r/Fill/Honeycomb.pm @@ -21,7 +21,7 @@ sub fill_surface { # infill math my $min_spacing = scale $params{flow_spacing}; my $distance = $min_spacing / $params{density}; - my $overlap_distance = $self->layer ? $self->layer->flow->scaled_width : $Slic3r::flow->scaled_width * 0.4; + my $overlap_distance = ($self->layer ? $self->layer->flow->scaled_width : $Slic3r::flow->scaled_width) * 0.4; my $cache_id = sprintf "d%s_s%s_a%s", $params{density}, $params{flow_spacing}, $rotate_vector->[0][0]; diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm index b4569dbc8..d13321015 100644 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ b/lib/Slic3r/Fill/Rectilinear.pm @@ -31,7 +31,7 @@ sub fill_surface { $flow_spacing = unscale $distance_between_lines; } - my $overlap_distance = $self->layer ? $self->layer->flow->scaled_width : $Slic3r::flow->scaled_width * 0.4; + my $overlap_distance = ($self->layer ? $self->layer->flow->scaled_width : $Slic3r::flow->scaled_width) * 0.4; my $x = $bounding_box->[X1]; my $is_line_pattern = $self->isa('Slic3r::Fill::Line'); diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index a135be271..7653e4c32 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -421,7 +421,7 @@ sub set_temperature { : ('M104', 'set temperature'); my $gcode = sprintf "$code %s%d %s; $comment\n", ($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature, - (defined $tool && $tool != $self->extruder->id) ? "T$tool " : ""; + (defined $tool && $self->multiple_extruders) ? "T$tool " : ""; $gcode .= "M116 ; wait for temperature to be reached\n" if $Slic3r::Config->gcode_flavor eq 'teacup' && $wait; diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 37dd165fa..6d1f093c3 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -5,7 +5,7 @@ use utf8; use File::Basename qw(basename dirname); use List::Util qw(max sum); -use Math::ConvexHull qw(convex_hull); +use Math::ConvexHull::MonotoneChain qw(convex_hull); use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX); use Slic3r::Geometry::Clipper qw(JT_ROUND); use threads::shared qw(shared_clone); @@ -301,7 +301,7 @@ sub load_file { name => basename($input_file), input_file => $input_file, input_file_object_id => $i, - mesh => $model->objects->[$i]->mesh, + model_object => $model->objects->[$i], instances => [ $model->objects->[$i]->instances ? (map $_->offset, @{$model->objects->[$i]->instances}) @@ -384,6 +384,7 @@ sub decrease { my ($obj_idx, $object) = $self->selected_object; if ($object->instances_count >= 2) { pop @{$object->instances}; + $self->{list}->SetItem($obj_idx, 1, $object->instances_count); } else { $self->remove; } @@ -454,7 +455,14 @@ sub split_object { my ($obj_idx, $current_object) = $self->selected_object; my $current_copies_num = $current_object->instances_count; - my $mesh = $current_object->get_mesh; + my $model_object = $current_object->get_model_object; + + if (@{$model_object->volumes} > 1) { + Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be splitted because it contains more than one volume/material."); + return; + } + + my $mesh = $model_object->mesh; $mesh->align_to_origin; my @new_meshes = $mesh->split_mesh; @@ -668,20 +676,24 @@ sub make_model { my $self = shift; my $model = Slic3r::Model->new; - foreach my $object (@{$self->{objects}}) { - my $mesh = $object->get_mesh; - $mesh->scale($object->scale); - my $model_object = $model->add_object( - vertices => $mesh->vertices, - input_file => $object->input_file, + foreach my $plater_object (@{$self->{objects}}) { + my $model_object = $plater_object->get_model_object; + my $new_model_object = $model->add_object( + vertices => $model_object->vertices, + input_file => $plater_object->input_file, ); - $model_object->add_volume( - facets => $mesh->facets, - ); - $model_object->add_instance( - rotation => $object->rotate, + foreach my $volume (@{$model_object->volumes}) { + $new_model_object->add_volume( + material_id => $volume->material_id, + facets => $volume->facets, + ); + $model->materials->{$volume->material_id || 0} ||= {}; + } + $new_model_object->scale($plater_object->scale); + $new_model_object->add_instance( + rotation => $plater_object->rotate, offset => [ @$_ ], - ) for @{$object->instances}; + ) for @{$plater_object->instances}; } return $model; @@ -710,7 +722,7 @@ sub on_thumbnail_made { my $self = shift; my ($obj_idx) = @_; - $self->{objects}[$obj_idx]->free_mesh; + $self->{objects}[$obj_idx]->free_model_object; $self->recenter; $self->{canvas}->Refresh; } @@ -1000,38 +1012,38 @@ sub OnDropFiles { package Slic3r::GUI::Plater::Object; use Moo; -use Math::ConvexHull qw(convex_hull); +use Math::ConvexHull::MonotoneChain qw(convex_hull); use Slic3r::Geometry qw(X Y); has 'name' => (is => 'rw', required => 1); has 'input_file' => (is => 'rw', required => 1); -has 'input_file_object_id' => (is => 'rw'); # undef means keep mesh -has 'mesh' => (is => 'rw', required => 1, trigger => 1); +has 'input_file_object_id' => (is => 'rw'); # undef means keep model object +has 'model_object' => (is => 'rw', required => 1, trigger => 1); has 'size' => (is => 'rw'); has 'scale' => (is => 'rw', default => sub { 1 }); has 'rotate' => (is => 'rw', default => sub { 0 }); has 'instances' => (is => 'rw', default => sub { [] }); # upward Y axis has 'thumbnail' => (is => 'rw'); -sub _trigger_mesh { +sub _trigger_model_object { my $self = shift; - $self->size([$self->mesh->size]) if $self->mesh; + $self->size([$self->model_object->mesh->size]) if $self->model_object; } -sub free_mesh { +sub free_model_object { my $self = shift; # only delete mesh from memory if we can retrieve it from the original file return unless $self->input_file && $self->input_file_object_id; - $self->mesh(undef); + $self->model_object(undef); } -sub get_mesh { +sub get_model_object { my $self = shift; - return $self->mesh->clone if $self->mesh; + return $self->model_object if $self->model_object; my $model = Slic3r::Model->read_from_file($self->input_file); - return $model->objects->[$self->input_file_object_id]->mesh; + return $model->objects->[$self->input_file_object_id]; } sub instances_count { @@ -1043,7 +1055,7 @@ sub make_thumbnail { my $self = shift; my %params = @_; - my @points = map [ @$_[X,Y] ], @{$self->mesh->vertices}; + my @points = map [ @$_[X,Y] ], @{$self->model_object->mesh->vertices}; my $convex_hull = Slic3r::Polygon->new(convex_hull(\@points)); for (@$convex_hull) { @$_ = map $_ * $params{scaling_factor}, @$_; @@ -1054,7 +1066,7 @@ sub make_thumbnail { $convex_hull->align_to_origin; $self->thumbnail($convex_hull); # ignored in multi-threaded environments - $self->mesh(undef) if defined $self->input_file_object_id; + $self->free_model_object; return $convex_hull; } diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 69eab8820..0a6eacab8 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -85,6 +85,12 @@ sub new { }); EVT_BUTTON($self, $self->{btn_save_preset}, sub { + + # since buttons (and choices too) don't get focus on Mac, we set focus manually + # to the treectrl so that the EVT_* events are fired for the input field having + # focus currently. is there anything better than this? + $self->{treectrl}->SetFocus; + my $preset = $self->current_preset; my $default_name = $preset->{default} ? 'Untitled' : basename($preset->{name}); $default_name =~ s/\.ini$//i; diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index b5bec3a8d..c89faaf6f 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -36,7 +36,7 @@ use constant X2 => 2; use constant Y2 => 3; use constant MIN => 0; use constant MAX => 1; -our $parallel_degrees_limit = abs(deg2rad(3)); +our $parallel_degrees_limit = abs(deg2rad(0.1)); sub epsilon () { 1E-4 } sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR } diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index 5a1dfdd91..fca00af57 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -14,12 +14,12 @@ our $clipper = Math::Clipper->new; sub safety_offset { my ($polygons, $factor) = @_; - return Math::Clipper::offset($polygons, $factor || (scale 1e-05), 100, JT_MITER, 2); + return Math::Clipper::offset($polygons, $factor || (scale 1e-05), 100000, JT_MITER, 2); } sub offset { my ($polygons, $distance, $scale, $joinType, $miterLimit) = @_; - $scale ||= &Slic3r::SCALING_FACTOR * 1000000; + $scale ||= 100000; $joinType = JT_MITER if !defined $joinType; $miterLimit ||= 2; diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index a25ef993f..e6cbff4c4 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -358,7 +358,7 @@ sub prepare_fill_surfaces { # offset inwards my @offsets = $surface->expolygon->offset_ex(-$distance); - @offsets = @{union_ex(Math::Clipper::offset([ map @$_, @offsets ], $distance, 100, JT_MITER))}; + @offsets = @{union_ex(Math::Clipper::offset([ map @$_, @offsets ], $distance, 100000, JT_MITER))}; map Slic3r::Surface->new( expolygon => $_, surface_type => $surface->surface_type, diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index 1e2a6b2ba..ffc5ab901 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -37,6 +37,12 @@ sub set_material { ); } +sub scale { + my $self = shift; + + $_->scale(@_) for @{$self->objects}; +} + # flattens everything to a single mesh sub mesh { my $self = shift; @@ -112,6 +118,17 @@ sub mesh { ); } +sub scale { + my $self = shift; + my ($factor) = @_; + return if $factor == 1; + + # transform vertex coordinates + foreach my $vertex (@{$self->vertices}) { + $vertex->[$_] *= $factor for X,Y,Z; + } +} + package Slic3r::Model::Volume; use Moo; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 9231fa9cc..3669187b2 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -4,7 +4,7 @@ use Moo; use File::Basename qw(basename fileparse); use File::Spec; use List::Util qw(max); -use Math::ConvexHull 1.0.4 qw(convex_hull); +use Math::ConvexHull::MonotoneChain qw(convex_hull); use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN PI scale unscale move_points nearest_point); use Slic3r::Geometry::Clipper qw(diff_ex union_ex intersection_ex offset JT_ROUND JT_SQUARE); @@ -185,8 +185,16 @@ sub init_extruders { ); } + # calculate default flows + $Slic3r::flow = $self->extruders->[0]->make_flow( + width => $self->config->extrusion_width, + ); + $Slic3r::first_layer_flow = $self->extruders->[0]->make_flow( + layer_height => $self->config->get_value('first_layer_height'), + width => $self->config->first_layer_extrusion_width, + ); + # calculate regions' flows - $Slic3r::flow = $self->extruders->[0]->make_flow(width => $self->config->extrusion_width); for my $region_id (0 .. $#{$self->regions}) { my $region = $self->regions->[$region_id]; @@ -421,7 +429,7 @@ sub export_gcode { # make skirt $status_cb->(88, "Generating skirt"); $self->make_skirt; - $self->make_brim; + $self->make_brim; # must come after make_skirt # output everything to a G-code file my $output_file = $self->expanded_output_filepath($params{output_file}); @@ -559,25 +567,22 @@ sub make_skirt { my $convex_hull = convex_hull(\@points); # draw outlines from outside to inside - my $flow = $Slic3r::first_layer_flow || $Slic3r::flow; - my @skirt = (); for (my $i = $Slic3r::Config->skirts; $i > 0; $i--) { - my $distance = scale ($Slic3r::Config->skirt_distance + ($flow->spacing * $i)); + my $distance = scale ($Slic3r::Config->skirt_distance + ($Slic3r::first_layer_flow->spacing * $i)); my $outline = Math::Clipper::offset([$convex_hull], $distance, &Slic3r::SCALING_FACTOR * 100, JT_ROUND); - push @skirt, Slic3r::ExtrusionLoop->pack( - polygon => Slic3r::Polygon->new(@{$outline->[0]}), - role => EXTR_ROLE_SKIRT, + push @{$self->skirt}, Slic3r::ExtrusionLoop->pack( + polygon => Slic3r::Polygon->new(@{$outline->[0]}), + role => EXTR_ROLE_SKIRT, + flow_spacing => $Slic3r::first_layer_flow->spacing, ); } - unshift @{$self->skirt}, @skirt; } sub make_brim { my $self = shift; return unless $Slic3r::Config->brim_width > 0; - my $flow = $Slic3r::first_layer_flow || $Slic3r::flow; - my $grow_distance = $flow->scaled_width / 2; + my $grow_distance = $Slic3r::first_layer_flow->scaled_width / 2; my @islands = (); # array of polygons foreach my $obj_idx (0 .. $#{$self->objects}) { my $layer0 = $self->objects->[$obj_idx]->layers->[0]; @@ -591,13 +596,19 @@ sub make_brim { } } - my $num_loops = sprintf "%.0f", $Slic3r::Config->brim_width / $flow->width; + # if brim touches skirt, make it around skirt too + if ($Slic3r::Config->skirt_distance + (($Slic3r::Config->skirts - 1) * $Slic3r::first_layer_flow->spacing) <= $Slic3r::Config->brim_width) { + push @islands, map $_->unpack->split_at_first_point->polyline->grow($grow_distance), @{$self->skirt}; + } + + my $num_loops = sprintf "%.0f", $Slic3r::Config->brim_width / $Slic3r::first_layer_flow->width; for my $i (reverse 1 .. $num_loops) { # JT_SQUARE ensures no vertex is outside the given offset distance push @{$self->brim}, Slic3r::ExtrusionLoop->pack( - polygon => Slic3r::Polygon->new($_), - role => EXTR_ROLE_SKIRT, - ) for @{Math::Clipper::offset(\@islands, $i * $flow->scaled_spacing, 100, JT_SQUARE)}; + polygon => Slic3r::Polygon->new($_), + role => EXTR_ROLE_SKIRT, + flow_spacing => $Slic3r::first_layer_flow->spacing, + ) for @{Math::Clipper::offset(\@islands, $i * $Slic3r::first_layer_flow->scaled_spacing, 100, JT_SQUARE)}; # TODO: we need the offset inwards/offset outwards logic to avoid overlapping extrusions } } @@ -699,7 +710,7 @@ sub write_gcode { $gcodegen->shift_y($shift[Y]); $gcode .= $gcodegen->set_acceleration($Slic3r::Config->perimeter_acceleration); # skip skirt if we have a large brim - if ($layer_id < $Slic3r::Config->skirt_height && ($layer_id != 0 || $Slic3r::Config->skirt_distance + (($Slic3r::Config->skirts - 1) * $Slic3r::flow->spacing) > $Slic3r::Config->brim_width)) { + if ($layer_id < $Slic3r::Config->skirt_height) { $gcode .= $gcodegen->extrude_loop($_, 'skirt') for @{$self->skirt}; } $skirt_done++; diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index ccb9723ff..3d7066a34 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -2,7 +2,7 @@ package Slic3r::Print::Object; use Moo; use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Geometry qw(scale unscale deg2rad); +use Slic3r::Geometry qw(scale unscale deg2rad scaled_epsilon); use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex union_ex); use Slic3r::Surface ':types'; @@ -211,7 +211,7 @@ sub make_perimeters { # of our slice my $hypothetical_perimeter; { - my $outer = [ map @$_, $slice->expolygon->offset_ex(- ($hypothetical_perimeter_num-1.5) * $perimeter_flow->scaled_spacing) ]; + my $outer = [ map @$_, $slice->expolygon->offset_ex(- ($hypothetical_perimeter_num-1.5) * $perimeter_flow->scaled_spacing - scaled_epsilon) ]; last CYCLE if !@$outer; my $inner = [ map @$_, $slice->expolygon->offset_ex(- ($hypothetical_perimeter_num-0.5) * $perimeter_flow->scaled_spacing) ]; last CYCLE if !@$inner; diff --git a/utils/file_info.pl b/utils/file_info.pl index 36e57e9b0..e8eb09456 100755 --- a/utils/file_info.pl +++ b/utils/file_info.pl @@ -25,12 +25,14 @@ my %opt = (); { my $input_file = $ARGV[0]; - my $mesh; - $mesh = Slic3r::Format::STL->read_file($input_file) if $input_file =~ /\.stl$/i; die "This script doesn't support AMF yet\n" if $input_file =~ /\.amf$/i; - die "Unable to read file\n" if !$mesh; + + my $model; + $model = Slic3r::Format::STL->read_file($input_file) if $input_file =~ /\.stl$/i; + die "Unable to read file\n" if !$model; printf "Info about %s:\n", basename($input_file); + my $mesh = $model->mesh; $mesh->check_manifoldness; printf " number of facets: %d\n", scalar @{$mesh->facets}; printf " size: x=%s y=%s z=%s\n", $mesh->size;