diff --git a/MANIFEST b/MANIFEST index 64d93f3687..c4c53288cc 100644 --- a/MANIFEST +++ b/MANIFEST @@ -35,6 +35,7 @@ lib/Slic3r/GUI/SkeinPanel.pm lib/Slic3r/GUI/Tab.pm lib/Slic3r/Layer.pm lib/Slic3r/Line.pm +lib/Slic3r/Model.pm lib/Slic3r/Point.pm lib/Slic3r/Polygon.pm lib/Slic3r/Polyline.pm diff --git a/README.markdown b/README.markdown index a335f0cb94..1dfe6552b1 100644 --- a/README.markdown +++ b/README.markdown @@ -9,8 +9,8 @@ A: Yes. Slic3r is a G-code generator for 3D printers. It's compatible with RepRaps, Makerbots, Ultimakers and many more machines. -See the [project homepage](http://slic3r.org/) at slic3r.org -for more information. +See the [project homepage](http://slic3r.org/) at slic3r.org and the +[documentation](https://github.com/alexrj/Slic3r/wiki/Documentation) on the Slic3r wiki for more information. ## What language is it written in? @@ -170,6 +170,9 @@ The author of the Silk icon set is Mark James. --layer-gcode Load layer-change G-code from the supplied file (default: nothing). --extra-perimeters Add more perimeters when needed (default: yes) --randomize-start Randomize starting point across layers (default: yes) + --only-retract-when-crossing-perimeters + Disable retraction when travelling between infill paths inside the same island. + (default: no) --solid-infill-below-area Force solid infill when a region has a smaller area than this threshold (mm^2, default: 70) @@ -272,38 +275,3 @@ If you want to change a preset file, just do If you want to slice a file overriding an option contained in your preset file: slic3r.pl --load config.ini --layer-height 0.25 file.stl - -## How can I integrate Slic3r with Pronterface? - -Put this into *slicecommand*: - - slic3r.pl $s --load config.ini --output $o - -And this into *sliceoptscommand*: - - slic3r.pl --load config.ini --ignore-nonexistent-config - -Replace `slic3r.pl` with the full path to the slic3r executable and `config.ini` -with the full path of your config file (put it in your home directory or where -you like). -On Mac, the executable has a path like this: - - /Applications/Slic3r.app/Contents/MacOS/slic3r - -## How can I specify a custom filename format for output G-code files? - -You can specify a filename format by using any of the config options. -Just enclose them in square brackets, and Slic3r will replace them upon -exporting. -The additional `[input_filename]` and `[input_filename_base]` options will -be replaced by the input file name (in the second case, the .stl extension -is stripped). - -The default format is `[input_filename_base].gcode`, meaning that if you slice -a *foo.stl* file, the output will be saved to *foo.gcode*. - -See below for more complex examples: - - [input_filename_base]_h[layer_height]_p[perimeters]_s[solid_layers].gcode - [input_filename]_center[print_center]_[layer_height]layers.gcode - diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index dc97761213..f6bd643c46 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -7,7 +7,7 @@ use strict; use warnings; require v5.10; -our $VERSION = "0.9.2-dev"; +our $VERSION = "0.9.3-dev"; our $debug = 0; sub debugf { @@ -21,6 +21,9 @@ BEGIN { $have_threads = $Config{useithreads} && eval "use threads; use Thread::Queue; 1"; } +warn "Running Slic3r under Perl >= 5.16 is not supported nor recommended\n" + if $^V >= v5.16; + use FindBin; our $var = "$FindBin::Bin/var"; @@ -41,6 +44,7 @@ use Slic3r::GCode; use Slic3r::Geometry qw(PI); use Slic3r::Layer; use Slic3r::Line; +use Slic3r::Model; use Slic3r::Point; use Slic3r::Polygon; use Slic3r::Polyline; diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 36aaa5ae75..5ded4a5927 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -178,6 +178,7 @@ our $Options = { # extruder mapping 'perimeter_extruder' => { label => 'Perimeter extruder', + tooltip => 'The extruder to use when printing perimeters.', cli => 'perimeter-extruder=i', type => 'i', aliases => [qw(perimeters_extruder)], @@ -185,12 +186,14 @@ our $Options = { }, 'infill_extruder' => { label => 'Infill extruder', + tooltip => 'The extruder to use when printing infill.', cli => 'infill-extruder=i', type => 'i', default => 1, }, 'support_material_extruder' => { label => 'Support material extruder', + tooltip => 'The extruder to use when printing support material. This affects brim too.', cli => 'support-material-extruder=i', type => 'i', default => 1, @@ -470,6 +473,13 @@ our $Options = { type => 'bool', default => 1, }, + 'only_retract_when_crossing_perimeters' => { + label => 'Only retract when crossing perimeters', + tooltip => 'Disables retraction when travelling between infill paths inside the same island.', + cli => 'only-retract-when-crossing-perimeters!', + type => 'bool', + default => 0, + }, 'support_material' => { label => 'Generate support material', tooltip => 'Enable support material generation.', diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index ed572d694a..26f0d471b9 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -61,13 +61,12 @@ sub boost_polygon { sub offset { my $self = shift; - my ($distance, $scale, $joinType, $miterLimit) = @_; - $scale ||= &Slic3r::SCALING_FACTOR * 1000000; - $joinType = JT_MITER if !defined $joinType; - $miterLimit ||= 2; - - my $offsets = Math::Clipper::offset($self, $distance, $scale, $joinType, $miterLimit); - return @$offsets; + return Slic3r::Geometry::Clipper::offset($self, @_); +} + +sub offset_ex { + my $self = shift; + return Slic3r::Geometry::Clipper::offset_ex($self, @_); } sub safety_offset { diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index 8068bb87e8..5c694d95e5 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -170,7 +170,7 @@ sub make_fill { ? ($surface->surface_type == S_TYPE_TOP ? EXTR_ROLE_TOPSOLIDFILL : EXTR_ROLE_SOLIDFILL) : EXTR_ROLE_FILL), depth_layers => $surface->depth_layers, - flow_spacing => $params->{flow_spacing} || $flow_spacing, + flow_spacing => $params->{flow_spacing} || (warn "Warning: no flow_spacing was returned by the infill engine, please report this to the developer\n"), ), @paths, ], ); diff --git a/lib/Slic3r/Fill/Concentric.pm b/lib/Slic3r/Fill/Concentric.pm index 94866e2aea..c0d172cfe3 100644 --- a/lib/Slic3r/Fill/Concentric.pm +++ b/lib/Slic3r/Fill/Concentric.pm @@ -18,7 +18,7 @@ sub fill_surface { my $min_spacing = scale $params{flow_spacing}; my $distance = $min_spacing / $params{density}; - my $flow_spacing; + my $flow_spacing = $params{flow_spacing}; if ($params{density} == 1) { $distance = $self->adjust_solid_spacing( width => $bounding_box->[X2] - $bounding_box->[X1], diff --git a/lib/Slic3r/Fill/Honeycomb.pm b/lib/Slic3r/Fill/Honeycomb.pm index 229c725686..1e407e2e6e 100644 --- a/lib/Slic3r/Fill/Honeycomb.pm +++ b/lib/Slic3r/Fill/Honeycomb.pm @@ -94,7 +94,8 @@ sub fill_surface { paths => [ map Slic3r::ExtrusionPath->pack(polyline => $_, role => -1), @paths ], ); - return {}, map $_->polyline, $collection->shortest_path; + return { flow_spacing => $params{flow_spacing} }, + map $_->polyline, $collection->shortest_path; } 1; diff --git a/lib/Slic3r/Fill/PlanePath.pm b/lib/Slic3r/Fill/PlanePath.pm index daeb13deae..d33b6e6fc3 100644 --- a/lib/Slic3r/Fill/PlanePath.pm +++ b/lib/Slic3r/Fill/PlanePath.pm @@ -60,7 +60,7 @@ sub fill_surface { # paths must be rotated back $self->rotate_points_back(\@paths, $rotate_vector); - return {}, @paths; + return { flow_spacing => $params{flow_spacing} }, @paths; } 1; diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm index 2de7872a6d..121249eaa8 100644 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ b/lib/Slic3r/Fill/Rectilinear.pm @@ -3,7 +3,7 @@ use Moo; extends 'Slic3r::Fill::Base'; -use Slic3r::Geometry qw(X1 Y1 X2 Y2 A B X Y scale unscale epsilon); +use Slic3r::Geometry qw(X1 Y1 X2 Y2 A B X Y scale unscale scaled_epsilon); sub fill_surface { my $self = shift; @@ -22,7 +22,7 @@ sub fill_surface { my $distance_between_lines = $min_spacing / $params{density}; my $line_oscillation = $distance_between_lines - $min_spacing; - my $flow_spacing; + my $flow_spacing = $params{flow_spacing}; if ($params{density} == 1) { $distance_between_lines = $self->adjust_solid_spacing( width => $bounding_box->[X2] - $bounding_box->[X1], @@ -36,7 +36,7 @@ sub fill_surface { my $x = $bounding_box->[X1]; my $is_line_pattern = $self->isa('Slic3r::Fill::Line'); my @vertical_lines = (); - for (my $i = 0; $x <= $bounding_box->[X2] + scale epsilon; $i++) { + for (my $i = 0; $x <= $bounding_box->[X2] + scaled_epsilon; $i++) { my $vertical_line = Slic3r::Line->new([$x, $bounding_box->[Y2]], [$x, $bounding_box->[Y1]]); if ($is_line_pattern && $i % 2) { $vertical_line->[A][X] += $line_oscillation; @@ -49,7 +49,7 @@ sub fill_surface { # clip paths against a slightly offsetted expolygon, so that the first and last paths # are kept even if the expolygon has vertical sides my @paths = @{ Boost::Geometry::Utils::polygon_linestring_intersection( - +($expolygon->offset_ex(scale epsilon))[0]->boost_polygon, # TODO: we should use all the resulting expolygons and clip the linestrings to a multipolygon object + +($expolygon->offset_ex(scaled_epsilon))[0]->boost_polygon, # TODO: we should use all the resulting expolygons and clip the linestrings to a multipolygon object Boost::Geometry::Utils::linestring(@vertical_lines), ) }; for (@paths) { @@ -64,7 +64,7 @@ sub fill_surface { ); @paths = (); - my $tolerance = 10 * scale epsilon; + my $tolerance = 10 * scaled_epsilon; my $diagonal_distance = $distance_between_lines * 5; my $can_connect = $is_line_pattern ? sub { diff --git a/lib/Slic3r/Format/AMF.pm b/lib/Slic3r/Format/AMF.pm index ce388ec2ec..552f1fdbd9 100644 --- a/lib/Slic3r/Format/AMF.pm +++ b/lib/Slic3r/Format/AMF.pm @@ -12,28 +12,18 @@ sub read_file { open my $fh, '<', $file or die "Failed to open $file\n"; - my $vertices = []; - my $materials = {}; - my $meshes_by_material = {}; + my $model = Slic3r::Model->new; XML::SAX::PurePerl - ->new(Handler => Slic3r::Format::AMF::Parser->new( - _vertices => $vertices, - _materials => $materials, - _meshes_by_material => $meshes_by_material, - )) + ->new(Handler => Slic3r::Format::AMF::Parser->new(_model => $model)) ->parse_file($fh); - close $fh; - $_ = Slic3r::TriangleMesh->new(vertices => $vertices, facets => $_) - for values %$meshes_by_material; - - return $materials, $meshes_by_material; + return $model; } sub write_file { my $self = shift; - my ($file, $materials, $meshes_by_material) = @_; + my ($file, $model, %params) = @_; my %vertices_offset = (); @@ -42,20 +32,21 @@ sub write_file { printf $fh qq{\n}; printf $fh qq{\n}; printf $fh qq{ Slic3r %s\n}, $Slic3r::VERSION; - foreach my $material_id (keys %$materials) { - printf $fh qq{ \n}, $material_id; - for (keys %{$materials->{$material_id}}) { - printf $fh qq{ %s\n}, $_, $materials->{$material_id}{$_}; + for my $material_id (sort keys %{ $model->materials }) { + my $material = $model->materials->{$material_id}; + printf $fh qq{ \n}, $material_id; + for (keys %$material) { + printf $fh qq{ %s\n}, $_, $material->{$_}; } printf $fh qq{ \n}; } - printf $fh qq{ \n}; - printf $fh qq{ \n}; - printf $fh qq{ \n}; - my $vertices_count = 0; - foreach my $mesh (values %$meshes_by_material) { - $vertices_offset{$mesh} = $vertices_count; - foreach my $vertex (@{$mesh->vertices}, ) { + my $instances = ''; + for my $object_id (0 .. $#{ $model->objects }) { + my $object = $model->objects->[$object_id]; + printf $fh qq{ \n}, $object_id; + printf $fh qq{ \n}; + printf $fh qq{ \n}; + foreach my $vertex (@{$object->vertices}, ) { printf $fh qq{ \n}; printf $fh qq{ \n}; printf $fh qq{ %s\n}, $vertex->[X]; @@ -63,24 +54,35 @@ sub write_file { printf $fh qq{ %s\n}, $vertex->[Z]; printf $fh qq{ \n}; printf $fh qq{ \n}; - $vertices_count++; + } + printf $fh qq{ \n}; + foreach my $volume (@{ $object->volumes }) { + printf $fh qq{ \n}, + (!defined $volume->material_id) ? '' : (sprintf ' materialid="%s"', $volume->material_id); + foreach my $facet (@{$volume->facets}) { + printf $fh qq{ \n}; + printf $fh qq{ %d\n}, (4+$_), $facet->[$_], (4+$_) for -3..-1; + printf $fh qq{ \n}; + } + printf $fh qq{ \n}; + } + printf $fh qq{ \n}; + printf $fh qq{ \n}; + if ($object->instances) { + foreach my $instance (@{$object->instances}) { + $instances .= sprintf qq{ \n}, $object_id; + $instances .= sprintf qq{ %s\n}, $instance->offset->[X]; + $instances .= sprintf qq{ %s\n}, $instance->offset->[Y]; + $instances .= sprintf qq{ %s\n}, $instance->rotation; + $instances .= sprintf qq{ \n}; + } } } - printf $fh qq{ \n}; - foreach my $material_id (sort keys %$meshes_by_material) { - my $mesh = $meshes_by_material->{$material_id}; - printf $fh qq{ \n}, - ($material_id eq '_') ? '' : " materialid=\"$material_id\""; - foreach my $facet (@{$mesh->facets}) { - printf $fh qq{ \n}; - printf $fh qq{ %d\n}, $_, $facet->[$_] + $vertices_offset{$mesh}, $_ - for -3..-1; - printf $fh qq{ \n}; - } - printf $fh qq{ \n}; + if ($instances) { + printf $fh qq{ \n}; + printf $fh $instances; + printf $fh qq{ \n}; } - printf $fh qq{ \n}; - printf $fh qq{ \n}; printf $fh qq{\n}; close $fh; } diff --git a/lib/Slic3r/Format/AMF/Parser.pm b/lib/Slic3r/Format/AMF/Parser.pm index 1d8d4dc396..93b35765eb 100644 --- a/lib/Slic3r/Format/AMF/Parser.pm +++ b/lib/Slic3r/Format/AMF/Parser.pm @@ -11,6 +11,8 @@ my %xyz_index = (x => 0, y => 1, z => 2); #= sub new { my $self = shift->SUPER::new(@_); $self->{_tree} = []; + $self->{_objects_map} = {}; # this hash maps AMF object IDs to object indexes in $model->objects + $self->{_instances} = {}; # apply these lazily to make sure all objects have been parsed $self; } @@ -18,23 +20,35 @@ sub start_element { my $self = shift; my $data = shift; - if ($data->{LocalName} eq 'vertex') { + if ($data->{LocalName} eq 'object') { + $self->{_object} = $self->{_model}->add_object; + $self->{_objects_map}{ $self->_get_attribute($data, 'id') } = $#{ $self->{_model}->objects }; + } elsif ($data->{LocalName} eq 'vertex') { $self->{_vertex} = ["", "", ""]; } elsif ($self->{_vertex} && $data->{LocalName} =~ /^[xyz]$/ && $self->{_tree}[-1] eq 'coordinates') { $self->{_coordinate} = $data->{LocalName}; } elsif ($data->{LocalName} eq 'volume') { - $self->{_volume_materialid} = $self->_get_attribute($data, 'materialid') || '_'; - $self->{_volume} = []; + $self->{_volume} = $self->{_object}->add_volume( + material_id => $self->_get_attribute($data, 'materialid') || undef, + ); } elsif ($data->{LocalName} eq 'triangle') { $self->{_triangle} = ["", "", ""]; } elsif ($self->{_triangle} && $data->{LocalName} =~ /^v([123])$/ && $self->{_tree}[-1] eq 'triangle') { $self->{_vertex_idx} = $1-1; } elsif ($data->{LocalName} eq 'material') { - $self->{_material_id} = $self->_get_attribute($data, 'id') || '_'; - $self->{_material} = {}; + my $material_id = $self->_get_attribute($data, 'id') || '_'; + $self->{_material} = $self->{_model}->materials->{ $material_id } = {}; } elsif ($data->{LocalName} eq 'metadata' && $self->{_tree}[-1] eq 'material') { $self->{_material_metadata_type} = $self->_get_attribute($data, 'type'); $self->{_material}{ $self->{_material_metadata_type} } = ""; + } elsif ($data->{LocalName} eq 'constellation') { + $self->{_constellation} = 1; # we merge all constellations as we don't support more than one + } elsif ($data->{LocalName} eq 'instance' && $self->{_constellation}) { + my $object_id = $self->_get_attribute($data, 'objectid'); + $self->{_instances}{$object_id} ||= []; + push @{ $self->{_instances}{$object_id} }, $self->{_instance} = {}; + } elsif ($data->{LocalName} =~ /^(?:deltax|deltay|rz)$/ && $self->{_instance}) { + $self->{_instance_property} = $data->{LocalName}; } push @{$self->{_tree}}, $data->{LocalName}; @@ -50,6 +64,8 @@ sub characters { $self->{_triangle}[ $self->{_vertex_idx} ] .= $data->{Data}; } elsif ($self->{_material_metadata_type}) { $self->{_material}{ $self->{_material_metadata_type} } .= $data->{Data}; + } elsif ($self->{_instance_property}) { + $self->{_instance}{ $self->{_instance_property} } .= $data->{Data}; } } @@ -59,26 +75,49 @@ sub end_element { pop @{$self->{_tree}}; - if ($data->{LocalName} eq 'vertex') { - push @{$self->{_vertices}}, $self->{_vertex}; + if ($data->{LocalName} eq 'object') { + $self->{_object} = undef; + } elsif ($data->{LocalName} eq 'vertex') { + push @{$self->{_object}->vertices}, $self->{_vertex}; $self->{_vertex} = undef; } elsif ($self->{_coordinate} && $data->{LocalName} =~ /^[xyz]$/) { $self->{_coordinate} = undef; } elsif ($data->{LocalName} eq 'volume') { - $self->{_meshes_by_material}{ $self->{_volume_materialid} } ||= []; - push @{ $self->{_meshes_by_material}{ $self->{_volume_materialid} } }, @{$self->{_volume}}; $self->{_volume} = undef; } elsif ($data->{LocalName} eq 'triangle') { - push @{$self->{_volume}}, $self->{_triangle}; + push @{$self->{_volume}->facets}, $self->{_triangle}; $self->{_triangle} = undef; - } elsif ($self->{_vertex_idx} && $data->{LocalName} =~ /^v[123]$/) { + } elsif (defined $self->{_vertex_idx} && $data->{LocalName} =~ /^v[123]$/) { $self->{_vertex_idx} = undef; } elsif ($data->{LocalName} eq 'material') { - $self->{_materials}{ $self->{_material_id} } = $self->{_material}; - $self->{_material_id} = undef; $self->{_material} = undef; } elsif ($data->{LocalName} eq 'metadata' && $self->{_material}) { $self->{_material_metadata_type} = undef; + } elsif ($data->{LocalName} eq 'constellation') { + $self->{_constellation} = undef; + } elsif ($data->{LocalName} eq 'instance') { + $self->{_instance} = undef; + } elsif ($data->{LocalName} =~ /^(?:deltax|deltay|rz)$/ && $self->{_instance}) { + $self->{_instance_property} = undef; + } +} + +sub end_document { + my $self = shift; + + foreach my $object_id (keys %{ $self->{_instances} }) { + my $new_object_id = $self->{_objects_map}{$object_id}; + if (!$new_object_id) { + warn "Undefined object $object_id referenced in constellation\n"; + next; + } + + foreach my $instance (@{ $self->{_instances}{$object_id} }) { + $self->{_model}->objects->[$new_object_id]->add_instance( + rotation => $instance->{rz} || 0, + offset => [ $instance->{deltax} || 0, $instance->{deltay} ], + ); + } } } diff --git a/lib/Slic3r/Format/OBJ.pm b/lib/Slic3r/Format/OBJ.pm index 210853025c..d663cfb5a7 100644 --- a/lib/Slic3r/Format/OBJ.pm +++ b/lib/Slic3r/Format/OBJ.pm @@ -17,7 +17,10 @@ sub read_file { } close $fh; - return Slic3r::TriangleMesh->new(vertices => $vertices, facets => $facets); + my $model = Slic3r::Model->new; + my $object = $model->add_object(vertices => $vertices); + my $volume = $object->add_volume(facets => $facets); + return $model; } 1; diff --git a/lib/Slic3r/Format/STL.pm b/lib/Slic3r/Format/STL.pm index 9706fd00dc..5325a4ea6f 100644 --- a/lib/Slic3r/Format/STL.pm +++ b/lib/Slic3r/Format/STL.pm @@ -117,7 +117,10 @@ sub read_file { } } - return Slic3r::TriangleMesh->new(vertices => $vertices, facets => $facets); + my $model = Slic3r::Model->new; + my $object = $model->add_object(vertices => $vertices); + my $volume = $object->add_volume(facets => $facets); + return $model; } sub _read_ascii { @@ -161,13 +164,13 @@ sub _read_binary { sub write_file { my $self = shift; - my ($file, $mesh, $binary) = @_; + my ($file, $model, %params) = @_; open my $fh, '>', $file; - $binary - ? _write_binary($fh, $mesh) - : _write_ascii($fh, $mesh); + $params{binary} + ? _write_binary($fh, $model->mesh) + : _write_ascii($fh, $model->mesh); close $fh; } diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index b5e020ace0..4f29fa0b95 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -1,8 +1,9 @@ package Slic3r::GCode; use Moo; +use List::Util qw(first); use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Geometry qw(scale unscale); +use Slic3r::Geometry qw(scale unscale scaled_epsilon points_coincide PI X Y); has 'layer' => (is => 'rw'); has 'shift_x' => (is => 'rw', default => sub {0} ); @@ -48,8 +49,6 @@ my %role_speeds = ( &EXTR_ROLE_SUPPORTMATERIAL => 'perimeter', ); -use Slic3r::Geometry qw(points_coincide PI X Y); - sub extruder { my $self = shift; return $Slic3r::extruders->[$self->extruder_idx]; @@ -132,15 +131,13 @@ sub extrude_path { my $gcode = ""; # retract if distance from previous position is greater or equal to the one - # specified by the user *and* to the maximum distance between infill lines + # specified by the user { - my $distance_from_last_pos = $self->last_pos->distance_to($path->points->[0]) * &Slic3r::SCALING_FACTOR; - my $distance_threshold = $self->extruder->retract_before_travel; - $distance_threshold = 2 * ($self->layer ? $self->layer->flow->width : $Slic3r::flow->width) / $Slic3r::Config->fill_density * sqrt(2) - if 0 && $Slic3r::Config->fill_density > 0 && $description =~ /fill/; - - if ($distance_from_last_pos >= $distance_threshold) { - $gcode .= $self->retract(travel_to => $path->points->[0]); + my $travel = Slic3r::Line->new($self->last_pos, $path->points->[0]); + if ($travel->length >= scale $self->extruder->retract_before_travel) { + if (!$Slic3r::Config->only_retract_when_crossing_perimeters || $path->role != EXTR_ROLE_FILL || !first { $_->expolygon->encloses_line($travel, scaled_epsilon) } @{$self->layer->slices}) { + $gcode .= $self->retract(travel_to => $path->points->[0]); + } } } @@ -424,25 +421,34 @@ sub set_temperature { return "" if $wait && $Slic3r::Config->gcode_flavor eq 'makerbot'; - my ($code, $comment) = $wait + my ($code, $comment) = ($wait && $Slic3r::Config->gcode_flavor ne 'teacup') ? ('M109', 'wait for temperature to be reached') : ('M104', 'set temperature'); - return sprintf "$code %s%d %s; $comment\n", + my $gcode = sprintf "$code %s%d %s; $comment\n", ($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature, (defined $tool && $tool != $self->extruder_idx) ? "T$tool " : ""; + + $gcode .= "M116 ; wait for temperature to be reached\n" + if $Slic3r::Config->gcode_flavor eq 'teacup' && $wait; + + return $gcode; } sub set_bed_temperature { my $self = shift; my ($temperature, $wait) = @_; - my ($code, $comment) = $wait + my ($code, $comment) = ($wait && $Slic3r::Config->gcode_flavor ne 'teacup') ? (($Slic3r::Config->gcode_flavor eq 'makerbot' ? 'M109' - : $Slic3r::Config->gcode_flavor eq 'teacup' ? 'M109 P1' : 'M190'), 'wait for bed temperature to be reached') : ('M140', 'set bed temperature'); - return sprintf "$code %s%d ; $comment\n", + my $gcode = sprintf "$code %s%d ; $comment\n", ($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature; + + $gcode .= "M116 ; wait for bed temperature to be reached\n" + if $Slic3r::Config->gcode_flavor eq 'teacup' && $wait; + + return $gcode; } 1; diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 8bd7fa684e..887d353202 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -22,6 +22,10 @@ use constant MI_REPEAT_QUICK => &Wx::NewId; use constant MI_QUICK_SAVE_AS => &Wx::NewId; use constant MI_SLICE_SVG => &Wx::NewId; +use constant MI_PLATER_EXPORT_GCODE => &Wx::NewId; +use constant MI_PLATER_EXPORT_STL => &Wx::NewId; +use constant MI_PLATER_EXPORT_AMF => &Wx::NewId; + use constant MI_TAB_PLATER => &Wx::NewId; use constant MI_TAB_PRINT => &Wx::NewId; use constant MI_TAB_FILAMENT => &Wx::NewId; @@ -47,7 +51,7 @@ sub OnInit { $self->{notifier} = Slic3r::GUI::Notifier->new; # locate or create data directory - $datadir = Wx::StandardPaths::Get->GetUserDataDir; + $datadir ||= Wx::StandardPaths::Get->GetUserDataDir; Slic3r::debugf "Data directory: %s\n", $datadir; my $run_wizard = (-d $datadir) ? 0 : 1; for ($datadir, "$datadir/print", "$datadir/filament", "$datadir/printer") { @@ -98,6 +102,17 @@ sub OnInit { EVT_MENU($frame, wxID_EXIT, sub {$_[0]->Close(0)}); } + # Plater menu + my $platerMenu = Wx::Menu->new; + { + $platerMenu->Append(MI_PLATER_EXPORT_GCODE, "Export G-code...", 'Export current plate as G-code'); + $platerMenu->Append(MI_PLATER_EXPORT_STL, "Export STL...", 'Export current plate as STL'); + $platerMenu->Append(MI_PLATER_EXPORT_AMF, "Export AMF...", 'Export current plate as AMF'); + EVT_MENU($frame, MI_PLATER_EXPORT_GCODE, sub { $self->{skeinpanel}{plater}->export_gcode }); + EVT_MENU($frame, MI_PLATER_EXPORT_STL, sub { $self->{skeinpanel}{plater}->export_stl }); + EVT_MENU($frame, MI_PLATER_EXPORT_AMF, sub { $self->{skeinpanel}{plater}->export_amf }); + } + # Window menu my $windowMenu = Wx::Menu->new; { @@ -128,6 +143,7 @@ sub OnInit { { my $menubar = Wx::MenuBar->new; $menubar->Append($fileMenu, "&File"); + $menubar->Append($platerMenu, "&Plater"); $menubar->Append($windowMenu, "&Window"); $menubar->Append($helpMenu, "&Help"); $frame->SetMenuBar($menubar); diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 7155f21eec..ff98c74588 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -296,7 +296,7 @@ sub load_file { my $process_dialog = Wx::ProgressDialog->new('Loading…', "Processing input file…", 100, $self, 0); $process_dialog->Pulse; local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self); - $self->{print}->add_object_from_file($input_file); + $self->{print}->add_objects_from_file($input_file); my $obj_idx = $#{$self->{print}->objects}; $process_dialog->Destroy; @@ -419,9 +419,7 @@ sub rotate { # rotate, realign to 0,0 and update size $object->mesh->rotate($angle); $object->mesh->align_to_origin; - my @size = $object->mesh->size; - $object->x_length($size[X]); - $object->y_length($size[Y]); + $object->size([ $object->mesh->size ]); $self->make_thumbnail($obj_idx); $self->recenter; @@ -458,9 +456,7 @@ sub changescale { my $mesh = $object->mesh; $mesh->scale($scale/100 / $self->{scale}[$obj_idx]); $object->mesh->align_to_origin; - my @size = $object->mesh->size; - $object->x_length($size[X]); - $object->y_length($size[Y]); + $object->size([ $object->mesh->size ]); $self->{scale}[$obj_idx] = $scale/100; $self->{list}->SetItem($obj_idx, 2, "$scale%"); @@ -512,6 +508,8 @@ sub export_gcode { # set this before spawning the thread because ->config needs GetParent and it's not available there $self->{print}->config($self->skeinpanel->config); + $self->{print}->extra_variables->{"${_}_preset"} = $self->skeinpanel->{options_tabs}{$_}->current_preset->{name} + for qw(print filament printer); # select output file $self->{output_file} = $main::opt{output}; @@ -634,39 +632,58 @@ sub on_export_failed { sub export_stl { my $self = shift; - - my $print = $self->{print}; - # select output file + my $output_file = $self->_get_export_file('STL') or return; + Slic3r::Format::STL->write_file($output_file, $self->make_model, binary => 1); + $self->statusbar->SetStatusText("STL file exported to $output_file"); +} + +sub export_amf { + my $self = shift; + + my $output_file = $self->_get_export_file('AMF') or return; + Slic3r::Format::AMF->write_file($output_file, $self->make_model); + $self->statusbar->SetStatusText("AMF file exported to $output_file"); +} + +sub _get_export_file { + my $self = shift; + my ($format) = @_; + + my $suffix = $format eq 'STL' ? '.stl' : '.amf.xml'; + my $output_file = $main::opt{output}; { - $output_file = $print->expanded_output_filepath($output_file); - $output_file =~ s/\.gcode$/.stl/i; - my $dlg = Wx::FileDialog->new($self, 'Save STL file as:', dirname($output_file), + $output_file = $self->{print}->expanded_output_filepath($output_file); + $output_file =~ s/\.gcode$/$suffix/i; + my $dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file), basename($output_file), $Slic3r::GUI::SkeinPanel::model_wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; - return; + return undef; } $output_file = $Slic3r::GUI::SkeinPanel::last_output_file = $dlg->GetPath; $dlg->Destroy; } + return $output_file; +} + +sub make_model { + my $self = shift; - my $mesh = Slic3r::TriangleMesh->new(facets => [], vertices => []); - for my $obj_idx (0 .. $#{$print->objects}) { - for my $copy (@{$print->copies->[$obj_idx]}) { - my $cloned_mesh = $print->objects->[$obj_idx]->mesh->clone; - $cloned_mesh->move(@$copy); - my $vertices_offset = scalar @{$mesh->vertices}; - push @{$mesh->vertices}, @{$cloned_mesh->vertices}; - push @{$mesh->facets}, map [ $_->[0], map $vertices_offset + $_, @$_[-3..-1] ], @{$cloned_mesh->facets}; + my $model = Slic3r::Model->new; + for my $obj_idx (0 .. $#{$self->{print}->objects}) { + my $mesh = $self->{print}->objects->[$obj_idx]->mesh->clone; + $mesh->scale(&Slic3r::SCALING_FACTOR); + my $object = $model->add_object(vertices => $mesh->vertices); + $object->add_volume(facets => $mesh->facets); + for my $copy (@{$self->{print}->copies->[$obj_idx]}) { + $object->add_instance(rotation => 0, offset => [ map unscale $_, @$copy ]); } } - $mesh->scale(&Slic3r::SCALING_FACTOR); - $mesh->align_to_origin; + # TODO: $model->align_to_origin; - Slic3r::Format::STL->write_file($output_file, $mesh, 1); - $self->statusbar->SetStatusText("STL file exported to $output_file"); + return $model; } sub make_thumbnail { diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index 764f60a81f..babd1cfbc6 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -96,7 +96,7 @@ sub do_slice { Slic3r::GUI->save_settings; my $print = Slic3r::Print->new(config => $config); - $print->add_object_from_file($input_file); + $print->add_objects_from_file($input_file); $print->validate; # select output file diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index cd68593aee..16107b5481 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -401,7 +401,7 @@ sub build { }, { title => 'Advanced', - options => [qw(infill_every_layers fill_angle solid_infill_below_area)], + options => [qw(infill_every_layers fill_angle solid_infill_below_area only_retract_when_crossing_perimeters)], }, ]); diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index 979a2eb0ef..dfad17ad05 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -20,6 +20,7 @@ our @EXPORT_OK = qw( shortest_path collinear scale unscale merge_collinear_lines rad2deg_dir bounding_box_center line_intersects_any douglas_peucker polyline_remove_short_segments normal triangle_normal polygon_is_convex + scaled_epsilon ); @@ -38,6 +39,7 @@ use constant MAX => 1; our $parallel_degrees_limit = abs(deg2rad(3)); sub epsilon () { 1E-4 } +sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR } sub scale ($) { $_[0] / &Slic3r::SCALING_FACTOR } sub unscale ($) { $_[0] * &Slic3r::SCALING_FACTOR } diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index a76048fc4e..5a1dfdd916 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -4,29 +4,34 @@ use warnings; require Exporter; our @ISA = qw(Exporter); -our @EXPORT_OK = qw(explode_expolygon explode_expolygons safety_offset offset +our @EXPORT_OK = qw(safety_offset offset offset_ex diff_ex diff union_ex intersection_ex xor_ex PFT_EVENODD JT_MITER JT_ROUND JT_SQUARE is_counter_clockwise); -use Math::Clipper 1.09 ':all'; +use Math::Clipper 1.09 qw(:cliptypes :polyfilltypes :jointypes is_counter_clockwise area); use Slic3r::Geometry qw(scale); our $clipper = Math::Clipper->new; -sub explode_expolygon { - my ($expolygon) = @_; - return ($expolygon->{outer}, @{ $expolygon->{holes} }); -} - -sub explode_expolygons { - my ($expolygons) = @_; - return map explode_expolygon($_), @$expolygons; -} - sub safety_offset { my ($polygons, $factor) = @_; return Math::Clipper::offset($polygons, $factor || (scale 1e-05), 100, JT_MITER, 2); } +sub offset { + my ($polygons, $distance, $scale, $joinType, $miterLimit) = @_; + $scale ||= &Slic3r::SCALING_FACTOR * 1000000; + $joinType = JT_MITER if !defined $joinType; + $miterLimit ||= 2; + + my $offsets = Math::Clipper::offset($polygons, $distance, $scale, $joinType, $miterLimit); + return @$offsets; +} + +sub offset_ex { + # offset polygons and then apply holes to the right contours + return @{ union_ex([ offset(@_) ]) }; +} + sub diff_ex { my ($subject, $clip, $safety_offset) = @_; diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm index 9794e0ed9e..2c1bc52ba8 100644 --- a/lib/Slic3r/Layer.pm +++ b/lib/Slic3r/Layer.pm @@ -346,7 +346,7 @@ sub make_perimeters { { my @thin_paths = (); my %properties = ( - role => EXTR_ROLE_PERIMETER, + role => EXTR_ROLE_EXTERNAL_PERIMETER, flow_spacing => $self->perimeter_flow->spacing, ); for (@{ $self->thin_walls }) { @@ -386,13 +386,30 @@ sub prepare_fill_surfaces { if ($Slic3r::Config->fill_density == 0) { @surfaces = grep $_->surface_type != S_TYPE_INTERNAL, @surfaces; } + + # remove unprintable regions (they would slow down the infill process and also cause + # some weird failures during bridge neighbor detection) + { + my $distance = scale $self->infill_flow->spacing / 2; + @surfaces = map { + my $surface = $_; + + # offset inwards + my @offsets = $surface->expolygon->offset_ex(-$distance); + @offsets = @{union_ex(Math::Clipper::offset([ map @$_, @offsets ], $distance, 100, JT_MITER))}; + map Slic3r::Surface->new( + expolygon => $_, + surface_type => $surface->surface_type, + ), @offsets; + } @surfaces; + } # turn too small internal regions into solid regions { my $min_area = scale scale $Slic3r::Config->solid_infill_below_area; # scaling an area requires two calls! my @small = grep $_->surface_type == S_TYPE_INTERNAL && $_->expolygon->contour->area <= $min_area, @surfaces; $_->surface_type(S_TYPE_INTERNALSOLID) for @small; - Slic3r::debugf "identified %d small surfaces at layer %d\n", scalar(@small), $self->id if @small > 0; + Slic3r::debugf "identified %d small solid surfaces at layer %d\n", scalar(@small), $self->id if @small > 0; } $self->fill_surfaces([@surfaces]); diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm new file mode 100644 index 0000000000..b648d4b34f --- /dev/null +++ b/lib/Slic3r/Model.pm @@ -0,0 +1,112 @@ +package Slic3r::Model; +use Moo; + +use Slic3r::Geometry qw(X Y Z); + +has 'materials' => (is => 'ro', default => sub { {} }); +has 'objects' => (is => 'ro', default => sub { [] }); + +sub add_object { + my $self = shift; + + my $object = Slic3r::Model::Object->new(model => $self, @_); + push @{$self->objects}, $object; + return $object; +} + +# flattens everything to a single mesh +sub mesh { + my $self = shift; + + my $vertices = []; + my $facets = []; + foreach my $object (@{$self->objects}) { + my @instances = $object->instances ? @{$object->instances} : (undef); + foreach my $instance (@instances) { + my @vertices = @{$object->vertices}; + if ($instance) { + # save Z coordinates, as rotation and translation discard them + my @z = map $_->[Z], @vertices; + + if ($instance->rotation) { + # transform vertex coordinates + my $rad = Slic3r::Geometry::deg2rad($instance->rotation); + @vertices = Slic3r::Geometry::rotate_points($rad, undef, @vertices); + } + @vertices = Slic3r::Geometry::move_points($instance->offset, @vertices); + + # reapply Z coordinates + $vertices[$_][Z] = $z[$_] for 0 .. $#z; + } + + my $v_offset = @$vertices; + push @$vertices, @vertices; + foreach my $volume (@{$object->volumes}) { + push @$facets, map { + my $f = [@$_]; + $f->[$_] += $v_offset for -3..-1; + $f; + } @{$volume->facets}; + } + } + } + + return Slic3r::TriangleMesh->new( + vertices => $vertices, + facets => $facets, + ); +} + +package Slic3r::Model::Material; +use Moo; + +has 'model' => (is => 'ro', weak_ref => 1, required => 1); +has 'attributes' => (is => 'rw', default => sub { {} }); + +package Slic3r::Model::Object; +use Moo; + +has 'model' => (is => 'ro', weak_ref => 1, required => 1); +has 'vertices' => (is => 'ro', default => sub { [] }); +has 'volumes' => (is => 'ro', default => sub { [] }); +has 'instances' => (is => 'rw'); + +sub add_volume { + my $self = shift; + + my $volume = Slic3r::Model::Volume->new(object => $self, @_); + push @{$self->volumes}, $volume; + return $volume; +} + +sub add_instance { + my $self = shift; + + $self->instances([]) if !defined $self->instances; + push @{$self->instances}, Slic3r::Model::Instance->new(object => $self, @_); + return $self->instances->[-1]; +} + +package Slic3r::Model::Volume; +use Moo; + +has 'object' => (is => 'ro', weak_ref => 1, required => 1); +has 'material_id' => (is => 'rw'); +has 'facets' => (is => 'rw', default => sub { [] }); + +sub mesh { + my $self = shift; + return Slic3r::TriangleMesh->new( + vertices => $self->object->vertices, + facets => $self->facets, + ); +} + +package Slic3r::Model::Instance; +use Moo; + +has 'object' => (is => 'ro', weak_ref => 1, required => 1); +has 'rotation' => (is => 'rw', default => sub { 0 }); +has 'offset' => (is => 'rw'); + +1; diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm index fa7ee712d8..ba09dcc2d5 100644 --- a/lib/Slic3r/Polygon.pm +++ b/lib/Slic3r/Polygon.pm @@ -71,13 +71,7 @@ sub safety_offset { sub offset { my $self = shift; - my ($distance, $scale, $joinType, $miterLimit) = @_; - $scale ||= &Slic3r::SCALING_FACTOR * 1000000; - $joinType = JT_MITER if !defined $joinType; - $miterLimit ||= 2; - - my $offsets = Math::Clipper::offset([$self], $distance, $scale, $joinType, $miterLimit); - return map Slic3r::Polygon->new($_), @$offsets; + return map Slic3r::Polygon->new($_), Slic3r::Geometry::Clipper::offset([$self], @_); } # this method subdivides the polygon segments to that no one of them diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm index 786957dab1..da9cc04795 100644 --- a/lib/Slic3r/Polyline.pm +++ b/lib/Slic3r/Polyline.pm @@ -144,6 +144,7 @@ sub rotate { my $self = shift; my ($angle, $center) = @_; @$self = Slic3r::Geometry::rotate_points($angle, $center, @$self); + bless $_, 'Slic3r::Point' for @$self; return $self; } @@ -151,6 +152,7 @@ sub translate { my $self = shift; my ($x, $y) = @_; @$self = Slic3r::Geometry::move_points([$x, $y], @$self); + bless $_, 'Slic3r::Point' for @$self; return $self; } diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index c5aec01a70..479cdc752a 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -5,11 +5,12 @@ use File::Basename qw(basename fileparse); use File::Spec; use Math::ConvexHull 1.0.4 qw(convex_hull); use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 PI scale unscale move_points); +use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 PI scale unscale move_points nearest_point); use Slic3r::Geometry::Clipper qw(diff_ex union_ex intersection_ex offset JT_ROUND JT_SQUARE); use Time::HiRes qw(gettimeofday tv_interval); has 'config' => (is => 'rw', default => sub { Slic3r::Config->new_from_defaults }, trigger => 1); +has 'extra_variables' => (is => 'rw', default => sub {{}}); has 'objects' => (is => 'rw', default => sub {[]}); has 'copies' => (is => 'rw', default => sub {[]}); # obj_idx => [copies...] has 'total_extrusion_length' => (is => 'rw'); @@ -42,6 +43,14 @@ sub _trigger_config { # store config in a handy place $Slic3r::Config = $self->config; + # legacy with existing config files + $self->config->set('first_layer_height', $self->config->layer_height) + if !$self->config->first_layer_height; + $self->config->set_ifndef('small_perimeter_speed', $self->config->perimeter_speed); + $self->config->set_ifndef('bridge_speed', $self->config->infill_speed); + $self->config->set_ifndef('solid_infill_speed', $self->config->infill_speed); + $self->config->set_ifndef('top_solid_infill_speed', $self->config->solid_infill_speed); + # initialize extruder(s) $Slic3r::extruders = []; for my $t (0, map $_-1, map $self->config->get($_), qw(perimeter_extruder infill_extruder support_material_extruder)) { @@ -71,36 +80,45 @@ sub _trigger_config { # G-code flavors $self->config->set('extrusion_axis', 'A') if $self->config->gcode_flavor eq 'mach3'; $self->config->set('extrusion_axis', '') if $self->config->gcode_flavor eq 'no-extrusion'; - - # legacy with existing config files - $self->config->set_ifndef('small_perimeter_speed', $self->config->perimeter_speed); - $self->config->set_ifndef('bridge_speed', $self->config->infill_speed); - $self->config->set_ifndef('solid_infill_speed', $self->config->infill_speed); - $self->config->set_ifndef('top_solid_infill_speed', $self->config->solid_infill_speed); } -sub add_object_from_file { +sub add_objects_from_file { my $self = shift; my ($input_file) = @_; - my $object; - if ($input_file =~ /\.stl$/i) { - my $mesh = Slic3r::Format::STL->read_file($input_file); + my $model = $input_file =~ /\.stl$/i ? Slic3r::Format::STL->read_file($input_file) + : $input_file =~ /\.obj$/i ? Slic3r::Format::OBJ->read_file($input_file) + : $input_file =~ /\.amf(\.xml)?$/i ? Slic3r::Format::AMF->read_file($input_file) + : die "Input file must have .stl, .obj or .amf(.xml) extension\n"; + + my @print_objects = $self->add_model($model); + $_->input_file($input_file) for @print_objects; +} + +sub add_model { + my $self = shift; + my ($model) = @_; + + my @print_objects = (); + foreach my $object (@{ $model->objects }) { + my $mesh = $object->volumes->[0]->mesh; $mesh->check_manifoldness; - $object = $self->add_object_from_mesh($mesh); - } elsif ($input_file =~ /\.obj$/i) { - my $mesh = Slic3r::Format::OBJ->read_file($input_file); - $mesh->check_manifoldness; - $object = $self->add_object_from_mesh($mesh); - } elsif ( $input_file =~ /\.amf(\.xml)?$/i) { - my ($materials, $meshes_by_material) = Slic3r::Format::AMF->read_file($input_file); - $_->check_manifoldness for values %$meshes_by_material; - $object = $self->add_object_from_mesh($meshes_by_material->{_} || +(values %$meshes_by_material)[0]); - } else { - die "Input file must have .stl, .obj or .amf(.xml) extension\n"; + + if ($object->instances) { + # we ignore the per-instance rotation currently and only + # consider the first one + $mesh->rotate($object->instances->[0]->rotation); + } + + push @print_objects, $self->add_object_from_mesh($mesh); + + if ($object->instances) { + # replace the default [0,0] instance with the custom ones + @{$self->copies->[-1]} = map [ scale $_->offset->[X], scale $_->offset->[X] ], @{$object->instances}; + } } - $object->input_file($input_file); - return $object; + + return @print_objects; } sub add_object_from_mesh { @@ -112,11 +130,9 @@ sub add_object_from_mesh { $mesh->align_to_origin; # initialize print object - my @size = $mesh->size; my $object = Slic3r::Print::Object->new( - mesh => $mesh, - x_length => $size[X], - y_length => $size[Y], + mesh => $mesh, + size => [ $mesh->size ], ); push @{$self->objects}, $object; @@ -201,8 +217,8 @@ sub duplicate { for my $x_copy (1..$Slic3r::Config->duplicate_grid->[X]) { for my $y_copy (1..$Slic3r::Config->duplicate_grid->[Y]) { push @{$self->copies->[0]}, [ - ($object->x_length + $dist) * ($x_copy-1), - ($object->y_length + $dist) * ($y_copy-1), + ($object->size->[X] + $dist) * ($x_copy-1), + ($object->size->[Y] + $dist) * ($y_copy-1), ]; } } @@ -220,8 +236,8 @@ sub arrange_objects { my $total_parts = scalar map @$_, @{$self->copies}; my $partx = my $party = 0; foreach my $object (@{$self->objects}) { - $partx = $object->x_length if $object->x_length > $partx; - $party = $object->y_length if $object->y_length > $party; + $partx = $object->size->[X] if $object->size->[X] > $partx; + $party = $object->size->[Y] if $object->size->[Y] > $party; } # object distance is max(duplicate_distance, clearance_radius) @@ -246,9 +262,9 @@ sub bounding_box { foreach my $copy (@{$self->copies->[$obj_idx]}) { push @points, [ $copy->[X], $copy->[Y] ], - [ $copy->[X] + $object->x_length, $copy->[Y] ], - [ $copy->[X] + $object->x_length, $copy->[Y] + $object->y_length ], - [ $copy->[X], $copy->[Y] + $object->y_length ]; + [ $copy->[X] + $object->size->[X], $copy->[Y] ], + [ $copy->[X] + $object->size->[X], $copy->[Y] + $object->size->[Y] ], + [ $copy->[X], $copy->[Y] + $object->size->[Y] ]; } } return Slic3r::Geometry::bounding_box(\@points); @@ -497,7 +513,7 @@ sub make_skirt { my @skirt = (); for (my $i = $Slic3r::Config->skirts; $i > 0; $i--) { my $distance = scale ($Slic3r::Config->skirt_distance + ($flow->spacing * $i)); - my $outline = offset([$convex_hull], $distance, &Slic3r::SCALING_FACTOR * 100, JT_ROUND); + 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, @@ -569,15 +585,6 @@ sub write_gcode { print $fh $gcodegen->set_tool(0); print $fh $gcodegen->set_fan(0, 1) if $Slic3r::Config->cooling && $Slic3r::Config->disable_fan_first_layers; - # this spits out some platic at start from each extruder when they are first used; - # the primary extruder will compensate by the normal retraction length, while - # the others will compensate for their toolchange length + restart extra. - # this is a temporary solution as all extruders should use some kind of skirt - # to be put into a consistent state. - $_->retracted($_->retract_length_toolchange + $_->retract_restart_extra_toolchange) - for @{$Slic3r::extruders}[1 .. $#{$Slic3r::extruders}]; - $gcodegen->retract; - # write start commands to file printf $fh $gcodegen->set_bed_temperature($Slic3r::Config->first_layer_bed_temperature, 1), if $Slic3r::Config->first_layer_bed_temperature && $Slic3r::Config->start_gcode !~ /M190/i; @@ -647,6 +654,7 @@ sub write_gcode { # extrude brim if ($layer_id == 0 && !$brim_done) { + $gcode .= $gcodegen->set_tool($Slic3r::Config->support_material_extruder-1); $gcodegen->shift_x($shift[X]); $gcodegen->shift_y($shift[Y]); $gcode .= $gcodegen->extrude_loop($_, 'brim') for @{$self->brim}; @@ -811,6 +819,7 @@ sub expanded_output_filepath { return $Slic3r::Config->replace_options($path, { input_filename => $input_filename, input_filename_base => $input_filename_base, + %{ $self->extra_variables }, }); } diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index c06358b835..696d72ca3c 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -8,8 +8,7 @@ use Slic3r::Surface ':types'; has 'input_file' => (is => 'rw', required => 0); has 'mesh' => (is => 'rw', required => 0); -has 'x_length' => (is => 'rw', required => 1); -has 'y_length' => (is => 'rw', required => 1); +has 'size' => (is => 'rw', required => 1); has 'layers' => ( traits => ['Array'], diff --git a/slic3r.pl b/slic3r.pl index 0f9baa58c6..e5c63b99e1 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -27,6 +27,7 @@ my %cli_options = (); 'save=s' => \$opt{save}, 'load=s@' => \$opt{load}, 'ignore-nonexistent-config' => \$opt{ignore_nonexistent_config}, + 'datadir=s' => \$opt{datadir}, 'export-svg' => \$opt{export_svg}, 'merge|m' => \$opt{merge}, ); @@ -70,6 +71,10 @@ if ($opt{save}) { # launch GUI my $gui; if (!@ARGV && !$opt{save} && eval "require Slic3r::GUI; 1") { + { + no warnings 'once'; + $Slic3r::GUI::datadir = $opt{datadir} if $opt{datadir}; + } $gui = Slic3r::GUI->new; $gui->{skeinpanel}->load_config_file($_) for @{$opt{load}}; $gui->{skeinpanel}->load_config($cli_config); @@ -83,9 +88,9 @@ if (@ARGV) { # slicing from command line while (my $input_file = shift @ARGV) { my $print = Slic3r::Print->new(config => $config); - $print->add_object_from_file($input_file); + $print->add_objects_from_file($input_file); if ($opt{merge}) { - $print->add_object_from_file($_) for splice @ARGV, 0; + $print->add_objects_from_file($_) for splice @ARGV, 0; } $print->duplicate; $print->arrange_objects if @{$print->objects} > 1; @@ -213,6 +218,9 @@ $j --layer-gcode Load layer-change G-code from the supplied file (default: nothing). --extra-perimeters Add more perimeters when needed (default: yes) --randomize-start Randomize starting point across layers (default: yes) + --only-retract-when-crossing-perimeters + Disable retraction when travelling between infill paths inside the same island. + (default: no) --solid-infill-below-area Force solid infill when a region has a smaller area than this threshold (mm^2, default: $config->{solid_infill_below_area}) diff --git a/t/arcs.t b/t/arcs.t index 1ffcd87b09..972620402b 100644 --- a/t/arcs.t +++ b/t/arcs.t @@ -11,7 +11,7 @@ BEGIN { use Slic3r; use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Geometry qw(epsilon scale X Y); +use Slic3r::Geometry qw(scaled_epsilon scale X Y); { my $path = Slic3r::ExtrusionPath->new(polyline => Slic3r::Polyline->new( @@ -61,17 +61,17 @@ use Slic3r::Geometry qw(epsilon scale X Y); isa_ok $collection2->paths->[0], 'Slic3r::ExtrusionPath::Arc', 'path'; my $expected_length = scale 7.06858347057701; - ok abs($collection1->paths->[0]->length - $expected_length) < scale epsilon, 'cw oriented arc has correct length'; - ok abs($collection2->paths->[0]->length - $expected_length) < scale epsilon, 'ccw oriented arc has correct length'; + ok abs($collection1->paths->[0]->length - $expected_length) < scaled_epsilon, 'cw oriented arc has correct length'; + ok abs($collection2->paths->[0]->length - $expected_length) < scaled_epsilon, 'ccw oriented arc has correct length'; is $collection1->paths->[0]->orientation, 'cw', 'cw orientation was correctly detected'; is $collection2->paths->[0]->orientation, 'ccw', 'ccw orientation was correctly detected'; my $center1 = [ map sprintf('%.0f', $_), @{ $collection1->paths->[0]->center } ]; - ok abs($center1->[X] - scale 10) < scale epsilon && abs($center1->[Y] - scale 10) < scale epsilon, 'center was correctly detected'; + ok abs($center1->[X] - scale 10) < scaled_epsilon && abs($center1->[Y] - scale 10) < scaled_epsilon, 'center was correctly detected'; my $center2 = [ map sprintf('%.0f', $_), @{ $collection2->paths->[0]->center } ]; - ok abs($center2->[X] - scale 10) < scale epsilon && abs($center1->[Y] - scale 10) < scale epsilon, 'center was correctly detected'; + ok abs($center2->[X] - scale 10) < scaled_epsilon && abs($center1->[Y] - scale 10) < scaled_epsilon, 'center was correctly detected'; } #========================================================== diff --git a/t/fill.t b/t/fill.t index f45aab2dae..2a7ef1f933 100644 --- a/t/fill.t +++ b/t/fill.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 2; +plan tests => 4; BEGIN { use FindBin; @@ -10,14 +10,13 @@ BEGIN { } use Slic3r; +use Slic3r::Geometry qw(scale X Y); +use Slic3r::Surface qw(:types); -my $print = Slic3r::Print->new( - x_length => 50, - y_length => 50, -); +sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } { - my $filler = Slic3r::Fill::Rectilinear->new(print => $print); + my $filler = Slic3r::Fill::Rectilinear->new(print => Slic3r::Print->new); my $surface_width = 250; my $distance = $filler->adjust_solid_spacing( width => $surface_width, @@ -27,4 +26,20 @@ my $print = Slic3r::Print->new( is $surface_width % $distance, 0, 'adjusted solid distance'; } +{ + my $filler = Slic3r::Fill::Rectilinear->new( + print => Slic3r::Print->new, + max_print_dimension => scale 100, + ); + my $surface = Slic3r::Surface->new( + surface_type => S_TYPE_TOP, + expolygon => Slic3r::ExPolygon->new([ scale_points [0,0], [50,0], [50,50], [0,50] ]), + ); + foreach my $angle (0, 45) { + $surface->expolygon->rotate($angle, [0,0]); + my ($params, @paths) = $filler->fill_surface($surface, flow_spacing => 0.69, density => 0.4); + is scalar @paths, 1, 'one continuous path'; + } +} + __END__ diff --git a/utils/amf-to-stl.pl b/utils/amf-to-stl.pl index 847e5e7498..b421dd33a0 100755 --- a/utils/amf-to-stl.pl +++ b/utils/amf-to-stl.pl @@ -25,12 +25,12 @@ my %opt = (); } { - my $mesh = Slic3r::Format::AMF->read_file($ARGV[0]); + my $model = Slic3r::Format::AMF->read_file($ARGV[0]); my $output_file = $ARGV[0]; $output_file =~ s/\.amf(?:\.xml)?$/\.stl/i; printf "Writing to %s\n", basename($output_file); - Slic3r::Format::STL->write_file($output_file, $mesh, !$opt{ascii}); + Slic3r::Format::STL->write_file($output_file, $model, binary => !$opt{ascii}); } diff --git a/utils/post-processing/filament-weight.pl b/utils/post-processing/filament-weight.pl index c564b4d805..5ed8364612 100755 --- a/utils/post-processing/filament-weight.pl +++ b/utils/post-processing/filament-weight.pl @@ -1,20 +1,30 @@ #!/usr/bin/perl -i # -# Post-processing script for adding weight of required filament to -# G-code output. +# Post-processing script for adding weight and cost of required +# filament to G-code output. use strict; use warnings; # example densities, adjust according to filament specifications -use constant PLA => 1.25; # g/cm3 -use constant ABS => 1.05; # g/cm3 +use constant PLA_P => 1.25; # g/cm3 +use constant ABS_P => 1.05; # g/cm3 + +# example costs, adjust according to filament prices +use constant PLA_PRICE => 0.05; # EUR/g +use constant ABS_PRICE => 0.02; # EUR/g +use constant CURRENCY => "EUR"; while (<>) { if (/^(;\s+filament\s+used\s+=\s.*\((\d+(?:\.\d+)?)cm3)\)/) { - my $pla = $2 * PLA; - my $abs = $2 * ABS; - printf "%s or %.2fg PLA/%.2fg ABS)\n", $1, $pla, $abs; + my $pla_weight = $2 * PLA_P; + my $abs_weight = $2 * ABS_P; + + my $pla_costs = $pla_weight * PLA_PRICE; + my $abs_costs = $abs_weight * ABS_PRICE; + + printf "%s or %.2fg PLA/%.2fg ABS)\n", $1, $pla_weight, $abs_weight; + printf "; costs = %s %.2f (PLA), %s %.2f (ABS)\n", CURRENCY, $pla_costs, CURRENCY, $abs_costs; } else { print; } diff --git a/utils/split_stl.pl b/utils/split_stl.pl index af98901163..42d2926bd0 100755 --- a/utils/split_stl.pl +++ b/utils/split_stl.pl @@ -25,15 +25,20 @@ my %opt = (); } { - my $mesh = Slic3r::Format::STL->read_file($ARGV[0]); + my $model = Slic3r::Format::STL->read_file($ARGV[0]); my $basename = $ARGV[0]; $basename =~ s/\.stl$//i; my $part_count = 0; - foreach my $new_mesh ($mesh->split_mesh) { + foreach my $new_mesh ($model->mesh->split_mesh) { + my $new_model = Slic3r::Model->new; + $new_model + ->add_object(vertices => $new_mesh->vertices) + ->add_volume(facets => $new_mesh->facets); + my $output_file = sprintf '%s_%02d.stl', $basename, ++$part_count; printf "Writing to %s\n", basename($output_file); - Slic3r::Format::STL->write_file($output_file, $new_mesh, !$opt{ascii}); + Slic3r::Format::STL->write_file($output_file, $new_model, binary => !$opt{ascii}); } } diff --git a/utils/stl-to-amf.pl b/utils/stl-to-amf.pl index f4805369b8..78e5989b4d 100755 --- a/utils/stl-to-amf.pl +++ b/utils/stl-to-amf.pl @@ -18,29 +18,50 @@ my %opt = (); { my %options = ( 'help' => sub { usage() }, + 'distinct-materials' => \$opt{distinct_materials}, ); GetOptions(%options) or usage(1); $ARGV[0] or usage(1); } { - my @meshes = map Slic3r::Format::STL->read_file($_), @ARGV; + my @models = map Slic3r::Format::STL->read_file($_), @ARGV; my $output_file = $ARGV[0]; $output_file =~ s/\.stl$/.amf.xml/i; - my $materials = {}; - my $meshes_by_material = {}; - if (@meshes == 1) { - $meshes_by_material->{_} = $meshes[0]; + my $new_model = Slic3r::Model->new; + + if ($opt{distinct_materials} && @models > 1) { + my $new_object = $new_model->add_object; + for my $m (0 .. $#models) { + my $model = $models[$m]; + my $v_offset = @{$new_object->vertices}; + push @{$new_object->vertices}, @{$model->objects->[0]->vertices}; + my @new_facets = map { + my $f = [@$_]; + $f->[$_] += $v_offset for -3..-1; + $f; + } @{ $model->objects->[0]->volumes->[0]->facets }; + + my $material_id = scalar keys %{$new_model->materials}; + $new_model->materials->{$material_id} = { Name => basename($ARGV[$m]) }; + $new_object->add_volume( + material_id => $material_id, + facets => [@new_facets], + ); + } } else { - for (0..$#meshes) { - $materials->{$_+1} = { Name => basename($ARGV[$_]) }; - $meshes_by_material->{$_+1} = $meshes[$_]; + foreach my $model (@models) { + $new_model->add_object( + vertices => $model->objects->[0]->vertices, + )->add_volume( + facets => $model->objects->[0]->volumes->[0]->facets, + ); } } printf "Writing to %s\n", basename($output_file); - Slic3r::Format::AMF->write_file($output_file, $materials, $meshes_by_material); + Slic3r::Format::AMF->write_file($output_file, $new_model); } @@ -51,6 +72,7 @@ sub usage { Usage: amf-to-stl.pl [ OPTIONS ] file.stl [ file2.stl [ file3.stl ] ] --help Output this usage screen and exit + --distinct-materials Assign each STL file to a different material EOF exit ($exit_code || 0);