diff --git a/lib/Slic3r/Fill/Base.pm b/lib/Slic3r/Fill/Base.pm index adc1e74c6..763308232 100644 --- a/lib/Slic3r/Fill/Base.pm +++ b/lib/Slic3r/Fill/Base.pm @@ -36,6 +36,7 @@ sub infill_direction { return [\@rotate, \@shift]; } +# this method accepts any object that implements rotate() and translate() sub rotate_points { my $self = shift; my ($expolygon, $rotate_vector) = @_; diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm index a65bcbc07..4ad3ff78f 100644 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ b/lib/Slic3r/Fill/Rectilinear.pm @@ -25,17 +25,17 @@ sub fill_surface { my $line_oscillation = $distance_between_lines - $min_spacing; my $is_line_pattern = $self->isa('Slic3r::Fill::Line'); - my $cache_id = sprintf "d%s_s%s_a%s", + my $cache_id = sprintf "d%s_s%.2f_a%.2f", $params{density}, $params{flow_spacing}, $rotate_vector->[0][0]; if (!$self->cache->{$cache_id}) { # compute bounding box - my $bounding_box = [ @{$self->bounding_box} ]; # clone - $bounding_box->[$_] = 0 for X1, Y1; + my $bounding_box; { - my $bb_expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_from_bounding_box($bounding_box)); - $self->rotate_points($bb_expolygon, $rotate_vector); - $bounding_box = [ $bb_expolygon->bounding_box ]; + my $bb_polygon = Slic3r::Polygon->new_from_bounding_box($self->bounding_box); + $bb_polygon->scale(sqrt 2); + $self->rotate_points($bb_polygon, $rotate_vector); + $bounding_box = [ $bb_polygon->bounding_box ]; } # define flow spacing according to requested density diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index ae66ba8e7..4f63271d6 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -715,6 +715,7 @@ sub make_model { rotation => $plater_object->rotate, offset => [ @$_ ], ) for @{$plater_object->instances}; + $new_model_object->align_to_origin; } return $model; diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index 6eaf7fac0..e0848aa86 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -7,7 +7,7 @@ our @ISA = qw(Exporter); our @EXPORT_OK = qw( PI X Y Z A B X1 Y1 X2 Y2 MIN MAX epsilon slope line_atan lines_parallel line_point_belongs_to_segment points_coincide distance_between_points - chained_path_items chained_path_points normalize tan + chained_path_items chained_path_points normalize tan move_points_3D line_length midpoint point_in_polygon point_in_segment segment_in_segment point_is_on_left_of_segment polyline_lines polygon_lines nearest_point point_along_segment polygon_segment_having_point polygon_has_subsegment @@ -388,6 +388,15 @@ sub move_points { return map Slic3r::Point->new($shift->[X] + $_->[X], $shift->[Y] + $_->[Y]), @points; } +sub move_points_3D { + my ($shift, @points) = @_; + return map [ + $shift->[X] + $_->[X], + $shift->[Y] + $_->[Y], + $shift->[Z] + $_->[Z], + ], @points; +} + # implementation of Liang-Barsky algorithm # polygon must be convex and ccw sub clip_segment_polygon { diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index 58473fa9f..cb740ab74 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -1,7 +1,8 @@ package Slic3r::Model; use Moo; -use Slic3r::Geometry qw(X Y Z); +use List::Util qw(first max); +use Slic3r::Geometry qw(X Y Z MIN move_points); has 'materials' => (is => 'ro', default => sub { {} }); has 'objects' => (is => 'ro', default => sub { [] }); @@ -19,6 +20,38 @@ sub read_from_file { return $model; } +sub merge { + my $class = shift; + my @models = @_; + + my $new_model = $class->new; + foreach my $model (@models) { + # merge material attributes (should we rename them in case of duplicates?) + $new_model->set_material($_, { %{$model->materials->{$_}}, %{$model->materials->{$_} || {}} }) + for keys %{$model->materials}; + + foreach my $object (@{$model->objects}) { + my $new_object = $new_model->add_object( + input_file => $object->input_file, + vertices => $object->vertices, + layer_height_ranges => $object->layer_height_ranges, + ); + + $new_object->add_volume( + material_id => $_->material_id, + facets => $_->facets, + ) for @{$object->volumes}; + + $new_object->add_instance( + offset => $_->offset, + rotation => $_->rotation, + ) for @{ $object->instances // [] }; + } + } + + return $new_model; +} + sub add_object { my $self = shift; @@ -39,10 +72,122 @@ sub set_material { sub scale { my $self = shift; - $_->scale(@_) for @{$self->objects}; } +sub arrange_objects { + my $self = shift; + my ($config) = @_; + + # do we have objects with no position? + if (first { !defined $_->instances } @{$self->objects}) { + # we shall redefine positions for all objects + + my ($copies, @positions) = $self->_arrange( + config => $config, + items => $self->objects, + ); + + # apply positions to objects + foreach my $object (@{$self->objects}) { + $object->align_to_origin; + + $object->instances([]); + $object->add_instance( + offset => $_, + rotation => 0, + ) for splice @positions, 0, $copies; + } + + } else { + # we only have objects with defined position + + # align the whole model to origin as it is + $self->align_to_origin; + + # arrange this model as a whole + my ($copies, @positions) = $self->_arrange( + config => $config, + items => [$self], + ); + + # apply positions to objects by translating the current positions + foreach my $object (@{$self->objects}) { + my @old_instances = @{$object->instances}; + $object->instances([]); + foreach my $instance (@old_instances) { + $object->add_instance( + offset => $_, + rotation => $instance->rotation, + ) for move_points($instance->offset, @positions); + } + } + } +} + +sub _arrange { + my $self = shift; + my %params = @_; + + my $config = $params{config}; + my @items = @{$params{items}}; # can be Model or Object objects, they have to implement size() + + if ($config->duplicate_grid->[X] > 1 || $config->duplicate_grid->[Y] > 1) { + if (@items > 1) { + die "Grid duplication is not supported with multiple objects\n"; + } + my @positions = (); + my $size = $items[0]->size; + my $dist = $config->duplicate_distance; + for my $x_copy (1..$config->duplicate_grid->[X]) { + for my $y_copy (1..$config->duplicate_grid->[Y]) { + push @positions, [ + ($size->[X] + $dist) * ($x_copy-1), + ($size->[Y] + $dist) * ($y_copy-1), + ]; + } + } + return ($config->duplicate_grid->[X] * $config->duplicate_grid->[Y]), @positions; + } else { + my $total_parts = $config->duplicate * @items; + my $partx = max(map $_->size->[X], @items); + my $party = max(map $_->size->[Y], @items); + return $config->duplicate, + Slic3r::Geometry::arrange + ($total_parts, $partx, $party, (map $_, @{$config->bed_size}), + $config->min_object_distance, $config); + } +} + +sub vertices { + my $self = shift; + return [ map @{$_->vertices}, @{$self->objects} ]; +} + +sub size { + my $self = shift; + return [ Slic3r::Geometry::size_3D($self->vertices) ]; +} + +sub extents { + my $self = shift; + return Slic3r::Geometry::bounding_box_3D($self->vertices); +} + +sub align_to_origin { + my $self = shift; + + # calculate the displacements needed to + # have lowest value for each axis at coordinate 0 + my @extents = $self->extents; + $self->move(map -$extents[$_][MIN], X,Y,Z); +} + +sub move { + my $self = shift; + $_->move(@_) for @{$self->objects}; +} + # flattens everything to a single mesh sub mesh { my $self = shift; @@ -64,6 +209,47 @@ sub mesh { return Slic3r::TriangleMesh->merge(@meshes); } +# this method splits objects into multiple distinct objects by walking their meshes +sub split_meshes { + my $self = shift; + + my @objects = @{$self->objects}; + @{$self->objects} = (); + + foreach my $object (@objects) { + if (@{$object->volumes} > 1) { + # We can't split meshes if there's more than one material, because + # we can't group the resulting meshes by object afterwards + push @{$self->objects}, $object; + next; + } + + my $volume = $object->volumes->[0]; + foreach my $mesh ($volume->mesh->split_mesh) { + my $new_object = $self->add_object( + input_file => $object->input_file, + layer_height_ranges => $object->layer_height_ranges, + ); + $new_object->add_volume( + vertices => $mesh->vertices, + facets => $mesh->facets, + material_id => $volume->material_id, + ); + + # let's now align the new object to the origin and put its displacement + # (extents) in the instances info + my @extents = $mesh->extents; + $new_object->align_to_origin; + + # add one instance per original instance applying the displacement + $new_object->add_instance( + offset => [ $_->offset->[X] + $extents[X][MIN], $_->offset->[Y] + $extents[Y][MIN] ], + rotation => $_->rotation, + ) for @{ $object->instances // [] }; + } + } +} + package Slic3r::Model::Region; use Moo; @@ -74,7 +260,7 @@ package Slic3r::Model::Object; use Moo; use List::Util qw(first); -use Slic3r::Geometry qw(X Y Z); +use Slic3r::Geometry qw(X Y Z MIN move_points_3D); use Storable qw(dclone); has 'input_file' => (is => 'rw'); @@ -123,6 +309,30 @@ sub mesh { ); } +sub size { + my $self = shift; + return [ Slic3r::Geometry::size_3D($self->vertices) ]; +} + +sub extents { + my $self = shift; + return Slic3r::Geometry::bounding_box_3D($self->vertices); +} + +sub align_to_origin { + my $self = shift; + + # calculate the displacements needed to + # have lowest value for each axis at coordinate 0 + my @extents = $self->extents; + $self->move(map -$extents[$_][MIN], X,Y,Z); +} + +sub move { + my $self = shift; + @{$self->vertices} = move_points_3D([ @_ ], @{$self->vertices}); +} + sub scale { my $self = shift; my ($factor) = @_; @@ -134,6 +344,19 @@ sub scale { } } +sub rotate { + my $self = shift; + my ($deg) = @_; + return if $deg == 0; + + my $rad = Slic3r::Geometry::deg2rad($deg); + + # transform vertex coordinates + foreach my $vertex (@{$self->vertices}) { + @$vertex = (@{ +(Slic3r::Geometry::rotate_points($rad, undef, [ $vertex->[X], $vertex->[Y] ]))[0] }, $vertex->[Z]); + } +} + sub materials_count { my $self = shift; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index b5482f7a0..7a380fa54 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -87,6 +87,8 @@ sub _build_fill_maker { return Slic3r::Fill->new(print => $self); } +# caller is responsible for supplying models whose objects don't collide +# and have explicit instance positions sub add_model { my $self = shift; my ($model) = @_; @@ -103,13 +105,18 @@ sub add_model { } } + # optimization: if avoid_crossing_perimeters is enabled, split + # this mesh into distinct objects so that we reduce the complexity + # of the graphs + $model->split_meshes if $Slic3r::Config->avoid_crossing_perimeters && !$Slic3r::Config->complete_objects; + foreach my $object (@{ $model->objects }) { + # extract meshes by material my @meshes = (); # by region_id - foreach my $volume (@{$object->volumes}) { - # should the object contain multiple volumes of the same material, merge them my $region_id = defined $volume->material_id ? $materials{$volume->material_id} : 0; my $mesh = $volume->mesh->clone; + # should the object contain multiple volumes of the same material, merge them $meshes[$region_id] = $meshes[$region_id] ? Slic3r::TriangleMesh->merge($meshes[$region_id], $mesh) : $mesh; @@ -119,41 +126,22 @@ sub add_model { next unless $mesh; $mesh->check_manifoldness; - if ($object->instances) { - # we ignore the per-instance rotation currently and only - # consider the first one - $mesh->rotate($object->instances->[0]->rotation); - } + # we ignore the per-instance rotation currently and only + # consider the first one + $mesh->rotate($object->instances->[0]->rotation); - $mesh->rotate($Slic3r::Config->rotate); - $mesh->scale($Slic3r::Config->scale / &Slic3r::SCALING_FACTOR); + $mesh->scale(1 / &Slic3r::SCALING_FACTOR); } - my @defined_meshes = grep defined $_, @meshes; - my $complete_mesh = @defined_meshes == 1 ? $defined_meshes[0] : Slic3r::TriangleMesh->merge(@defined_meshes); - # initialize print object - my $print_object = Slic3r::Print::Object->new( + push @{$self->objects}, Slic3r::Print::Object->new( print => $self, meshes => [ @meshes ], - size => [ $complete_mesh->size ], + copies => [ map [ scale $_->offset->[X], scale $_->offset->[Y] ], @{$object->instances} ], + size => [ map scale $_, @{ $object->size } ], input_file => $object->input_file, layer_height_ranges => $object->layer_height_ranges, ); - push @{$self->objects}, $print_object; - - # align object to origin - { - my @extents = $complete_mesh->extents; - foreach my $mesh (grep defined $_, @meshes) { - $mesh->move(map -$extents[$_][MIN], X,Y,Z); - } - } - - if ($object->instances) { - # replace the default [0,0] instance with the custom ones - $print_object->copies([ map [ scale $_->offset->[X], scale $_->offset->[Y] ], @{$object->instances} ]); - } } } @@ -282,54 +270,12 @@ sub regions_count { return scalar @{$self->regions}; } -sub duplicate { - my $self = shift; - - if ($Slic3r::Config->duplicate_grid->[X] > 1 || $Slic3r::Config->duplicate_grid->[Y] > 1) { - if (@{$self->objects} > 1) { - die "Grid duplication is not supported with multiple objects\n"; - } - my $object = $self->objects->[0]; - - # generate offsets for copies - my $dist = scale $Slic3r::Config->duplicate_distance; - @{$self->objects->[0]->copies} = (); - for my $x_copy (1..$Slic3r::Config->duplicate_grid->[X]) { - for my $y_copy (1..$Slic3r::Config->duplicate_grid->[Y]) { - push @{$self->objects->[0]->copies}, [ - ($object->size->[X] + $dist) * ($x_copy-1), - ($object->size->[Y] + $dist) * ($y_copy-1), - ]; - } - } - } elsif ($Slic3r::Config->duplicate > 1) { - foreach my $object (@{$self->objects}) { - @{$object->copies} = map [0,0], 1..$Slic3r::Config->duplicate; - } - $self->arrange_objects; - } -} - -sub arrange_objects { - my $self = shift; - - my $total_parts = scalar map @{$_->copies}, @{$self->objects}; - my $partx = max(map $_->size->[X], @{$self->objects}); - my $party = max(map $_->size->[Y], @{$self->objects}); - - my @positions = Slic3r::Geometry::arrange - ($total_parts, $partx, $party, (map scale $_, @{$Slic3r::Config->bed_size}), scale $Slic3r::Config->min_object_distance, $self->config); - - @{$_->copies} = splice @positions, 0, scalar @{$_->copies} for @{$self->objects}; -} - sub bounding_box { my $self = shift; my @points = (); - foreach my $obj_idx (0 .. $#{$self->objects}) { - my $object = $self->objects->[$obj_idx]; - foreach my $copy (@{$self->objects->[$obj_idx]->copies}) { + foreach my $object (@{$self->objects}) { + foreach my $copy (@{$object->copies}) { push @points, [ $copy->[X], $copy->[Y] ], [ $copy->[X] + $object->size->[X], $copy->[Y] ], diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 7b21db874..39913d592 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -11,7 +11,7 @@ has 'print' => (is => 'ro', weak_ref => 1, required => 1); has 'input_file' => (is => 'rw', required => 0); has 'meshes' => (is => 'rw', default => sub { [] }); # by region_id has 'size' => (is => 'rw', required => 1); -has 'copies' => (is => 'rw', default => sub {[ [0,0] ]}, trigger => 1); +has 'copies' => (is => 'rw', trigger => 1); # in scaled coordinates has 'layers' => (is => 'rw', default => sub { [] }); has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ] @@ -76,6 +76,7 @@ sub BUILD { } } +# This should be probably moved in Print.pm at the point where we sort Layer objects sub _trigger_copies { my $self = shift; return unless @{$self->copies} > 1; @@ -166,6 +167,8 @@ sub slice { } }, ); + + $self->meshes->[$region_id] = undef; # free memory } die "Invalid input file\n" if !@{$self->layers}; diff --git a/lib/Slic3r/SVG.pm b/lib/Slic3r/SVG.pm index a79094854..0ac286395 100644 --- a/lib/Slic3r/SVG.pm +++ b/lib/Slic3r/SVG.pm @@ -9,7 +9,7 @@ use constant Y => 1; our $filltype = 'evenodd'; -sub factor {return 30; +sub factor { return &Slic3r::SCALING_FACTOR * 10; } diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index d6a10f374..63ee7ac64 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -30,7 +30,9 @@ sub model { } my $model = Slic3r::Model->new; - $model->add_object(vertices => $vertices)->add_volume(facets => $facets); + my $object = $model->add_object(vertices => $vertices); + $object->add_volume(facets => $facets); + $object->add_instance(offset => [0,0]); return $model; } diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm index 9ffe542fd..0968f7354 100644 --- a/lib/Slic3r/TriangleMesh.pm +++ b/lib/Slic3r/TriangleMesh.pm @@ -10,9 +10,9 @@ has 'vertices' => (is => 'ro', required => 1); # id => [$x,$y,$z] has 'facets' => (is => 'ro', required => 1); # id => [ $v1_id, $v2_id, $v3_id ] # private -has 'edges' => (is => 'ro', default => sub { [] }); # id => [ $v1_id, $v2_id ] -has 'facets_edges' => (is => 'ro', default => sub { [] }); # id => [ $e1_id, $e2_id, $e3_id ] -has 'edges_facets' => (is => 'ro', default => sub { [] }); # id => [ $f1_id, $f2_id, (...) ] +has 'edges' => (is => 'rw'); # id => [ $v1_id, $v2_id ] +has 'facets_edges' => (is => 'rw'); # id => [ $e1_id, $e2_id, $e3_id ] +has 'edges_facets' => (is => 'rw'); # id => [ $f1_id, $f2_id, (...) ] use constant MIN => 0; use constant MAX => 1; @@ -29,13 +29,13 @@ use constant I_FACET_EDGE => 6; use constant FE_TOP => 0; use constant FE_BOTTOM => 1; -# always make sure this method is idempotent sub analyze { my $self = shift; - @{$self->edges} = (); - @{$self->facets_edges} = (); - @{$self->edges_facets} = (); + return if defined $self->edges; + $self->edges([]); + $self->facets_edges([]); + $self->edges_facets([]); my %table = (); # edge_coordinates => edge_id for (my $facet_id = 0; $facet_id <= $#{$self->facets}; $facet_id++) { diff --git a/slic3r.pl b/slic3r.pl index b3dec4969..1d09441db 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -91,13 +91,19 @@ if (@ARGV) { # slicing from command line $config->validate; while (my $input_file = shift @ARGV) { - my $print = Slic3r::Print->new(config => $config); - $print->add_model(Slic3r::Model->read_from_file($input_file)); + my $model; if ($opt{merge}) { - $print->add_model(Slic3r::Model->read_from_file($_)) for splice @ARGV, 0; + my @models = map Slic3r::Model->read_from_file($_), $input_file, (splice @ARGV, 0); + $model = Slic3r::Model->merge(@models); + } else { + $model = Slic3r::Model->read_from_file($input_file); } - $print->duplicate; - $print->arrange_objects if @{$print->objects} > 1; + $_->scale($config->scale) for @{$model->objects}; + $_->rotate($config->rotate) for @{$model->objects}; + $model->arrange_objects($config); + + my $print = Slic3r::Print->new(config => $config); + $print->add_model($model); $print->validate; my %params = ( output_file => $opt{output}, diff --git a/t/slice.t b/t/slice.t index cfa78a60c..e47929f0f 100644 --- a/t/slice.t +++ b/t/slice.t @@ -16,8 +16,6 @@ my @lines; my $z = 20; my @points = ([3, 4], [8, 5], [1, 9]); # XY coordinates of the facet vertices -my $mesh = Slic3r::TriangleMesh->new(facets => [], vertices => []); - # NOTE: # the first point of the intersection lines is replaced by -1 because TriangleMesh.pm # is saving memory and doesn't store point A anymore since it's not actually needed. @@ -104,21 +102,23 @@ my @upper = intersect(20, 20, 10); is $lower[0][Slic3r::TriangleMesh::I_FACET_EDGE], Slic3r::TriangleMesh::FE_BOTTOM, 'bottom edge on layer'; is $upper[0][Slic3r::TriangleMesh::I_FACET_EDGE], Slic3r::TriangleMesh::FE_TOP, 'upper edge on layer'; +my $mesh; + +sub intersect { + $mesh = Slic3r::TriangleMesh->new( + facets => [], + vertices => [], + ); + push @{$mesh->facets}, [ [0,0,0], @{vertices(@_)} ]; + $mesh->analyze; + return map Slic3r::TriangleMesh::unpack_line($_), $mesh->intersect_facet($#{$mesh->facets}, $z); +} + sub vertices { push @{$mesh->vertices}, map [ @{$points[$_]}, $_[$_] ], 0..2; [ ($#{$mesh->vertices}-2) .. $#{$mesh->vertices} ] } -sub add_facet { - push @{$mesh->facets}, [ [0,0,0], @{vertices(@_)} ]; - $mesh->analyze; -} - -sub intersect { - add_facet(@_); - return map Slic3r::TriangleMesh::unpack_line($_), $mesh->intersect_facet($#{$mesh->facets}, $z); -} - sub lines { my @lines = intersect(@_); #$_->a->[X] = sprintf('%.0f', $_->a->[X]) for @lines;