diff --git a/lib/Slic3r/GCode/Layer.pm b/lib/Slic3r/GCode/Layer.pm index 0a24dfcc..e7c2f8b1 100644 --- a/lib/Slic3r/GCode/Layer.pm +++ b/lib/Slic3r/GCode/Layer.pm @@ -6,7 +6,7 @@ use Slic3r::Geometry qw(X Y unscale); has 'print' => (is => 'ro', required => 1, handles => [qw(extruders)]); has 'gcodegen' => (is => 'ro', required => 1); -has 'shift' => (is => 'ro', required => 1); +has 'shift' => (is => 'ro', default => sub { [0,0] }); has 'spiralvase' => (is => 'lazy'); has 'vibration_limit' => (is => 'lazy'); diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 59ba13a2..8b30c7f4 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -446,6 +446,7 @@ sub remove { splice @{$self->{objects}}, $obj_idx, 1; $self->{model}->delete_object($obj_idx); + $self->{print}->delete_object($obj_idx); $self->{list}->DeleteItem($obj_idx); $self->object_list_changed; @@ -459,6 +460,7 @@ sub reset { @{$self->{objects}} = (); $self->{model}->delete_all_objects; + $self->{print}->delete_all_objects; $self->{list}->DeleteAllItems; $self->object_list_changed; @@ -472,12 +474,12 @@ sub increase { my ($obj_idx, $object) = $self->selected_object; my $model_object = $self->{model}->objects->[$obj_idx]; my $last_instance = $model_object->instances->[-1]; - $model_object->add_instance( + my $i = $model_object->add_instance( offset => [ map 10+$_, @{$last_instance->offset} ], scaling_factor => $last_instance->scaling_factor, rotation => $last_instance->rotation, ); - $self->{print}->objects->[$obj_idx]->copies; + $self->{print}->objects->[$obj_idx]->add_copy(@{$i->offset}); $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); $self->arrange; } @@ -489,6 +491,7 @@ sub decrease { my $model_object = $self->{model}->objects->[$obj_idx]; if ($model_object->instances_count >= 2) { $model_object->delete_last_instance; + $self->{print}->objects->[$obj_idx]->delete_last_copy; $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); } else { $self->remove; diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index efb3f394..b52ba342 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -143,6 +143,7 @@ sub quick_slice { } $model->arrange_objects($config); } + $model->center_instances_around_point($config->print_center); $print->add_model_object($_) for @{ $model->objects }; $print->validate; diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index b960b492..9eb675a6 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -222,6 +222,23 @@ sub align_to_origin { } } +# input point is expressed in unscaled coordinates +sub center_instances_around_point { + my ($self, $point) = @_; + + my $bb = $self->bounding_box; + my $size = $bb->size; + my @shift = ( + -$bb->x_min + $point->[X] - $size->[X]/2, + -$bb->y_min + $point->[Y] - $size->[Y]/2, + ); + + foreach my $instance (map @{$_->instances}, @{$self->objects}) { + $instance->offset->[X] += $shift[X]; + $instance->offset->[Y] += $shift[Y]; + } +} + sub translate { my $self = shift; my @shift = @_; @@ -516,11 +533,11 @@ has 'scaling_factor' => (is => 'rw', default => sub { 1 }); has 'offset' => (is => 'rw'); # must be arrayref in *unscaled* coordinates sub transform_mesh { - my ($self, $mesh) = @_; + my ($self, $mesh, $dont_translate) = @_; $mesh->rotate($self->rotation, Slic3r::Point->new(0,0)); # rotate around mesh origin $mesh->scale($self->scaling_factor); # scale around mesh origin - $mesh->translate(@{$self->offset}, 0); + $mesh->translate(@{$self->offset}, 0) unless $dont_translate; } 1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index acc4dcce..83d928bc 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -106,41 +106,17 @@ sub add_model_object { $meshes{$region_id}->merge($volume->mesh); } - # bounding box of the original meshes in original position in unscaled coordinates - my $bb1 = Slic3r::Geometry::BoundingBox->merge(map $_->bounding_box, values %meshes); - foreach my $mesh (values %meshes) { # we ignore the per-instance transformations currently and only # consider the first one - $object->instances->[0]->transform_mesh($mesh); - } - - # we align object also after transformations so that we only work with positive coordinates - # and the assumption that bounding_box === size works - my $bb2 = Slic3r::Geometry::BoundingBox->merge(map $_->bounding_box, values %meshes); - $_->translate(@{$bb2->vector_to_origin}) for values %meshes; - - # prepare scaled object size - my $scaled_bb = $bb2->clone; - $scaled_bb->translate(@{$bb2->vector_to_origin}); # not needed for getting size, but who knows - $scaled_bb->scale(1 / &Slic3r::SCALING_FACTOR); - - # prepare copies - my @copies = (); - foreach my $instance (@{ $object->instances }) { - push @copies, Slic3r::Point->new( - scale($instance->offset->[X] - $bb1->extents->[X][MIN]), - scale($instance->offset->[Y] - $bb1->extents->[Y][MIN]), - ); + $object->instances->[0]->transform_mesh($mesh, 1); } # initialize print object push @{$self->objects}, Slic3r::Print::Object->new( print => $self, meshes => [ map $meshes{$_}, 0..$#{$self->regions} ], - copies => [ @copies ], - input_bounding_box => $bb1, - size => $scaled_bb->size, # transformed size + copies => [ map Slic3r::Point->new_scale(@{ $_->offset }), @{ $object->instances } ], input_file => $object->input_file, config_overrides => $object->config, layer_height_ranges => $object->layer_height_ranges, @@ -151,6 +127,19 @@ sub add_model_object { @{$self->extra_variables}{qw(input_filename input_filename_base)} = parse_filename($input_file); } } + # TODO: invalidate skirt and brim +} + +sub delete_object { + my ($self, $obj_idx) = @_; + splice @{$self->objects}, $obj_idx, 1; + # TODO: invalidate skirt and brim +} + +sub delete_all_objects { + my ($self) = @_; + @{$self->objects} = (); + # TODO: invalidate skirt and brim } sub validate { @@ -163,11 +152,10 @@ sub validate { for my $obj_idx (0 .. $#{$self->objects}) { my $clearance; { - my @points = map Slic3r::Point->new(@$_[X,Y]), map @{$_->vertices}, @{$self->objects->[$obj_idx]->meshes}; - my $convex_hull = convex_hull(\@points); - ($clearance) = @{offset([$convex_hull], scale $Slic3r::Config->extruder_clearance_radius / 2, 1, JT_ROUND)}; + my @convex_hulls = map $_->convex_hull, grep defined $_, @{$self->objects->[$obj_idx]->meshes}; + ($clearance) = @{offset([@convex_hulls], scale $Slic3r::Config->extruder_clearance_radius / 2, 1, JT_ROUND)}; } - for my $copy (@{$self->objects->[$obj_idx]->copies}) { + for my $copy (@{$self->objects->[$obj_idx]->_shifted_copies}) { my $copy_clearance = $clearance->clone; $copy_clearance->translate(@$copy); if (@{ intersection(\@a, [$copy_clearance]) }) { @@ -287,7 +275,7 @@ sub bounding_box { my @points = (); foreach my $object (@{$self->objects}) { - foreach my $copy (@{$object->copies}) { + foreach my $copy (@{$object->_shifted_copies}) { push @points, [ $copy->[X], $copy->[Y] ], [ $copy->[X] + $object->size->[X], $copy->[Y] + $object->size->[Y] ]; @@ -511,7 +499,7 @@ EOF # sort slices so that the outermost ones come first my @slices = sort { $a->contour->contains_point($b->contour->first_point) ? 0 : 1 } @{$layer->slices}; - foreach my $copy (@{$self->objects->[$obj_idx]->copies}) { + foreach my $copy (@{$self->objects->[$obj_idx]->_shifted_copies}) { foreach my $slice (@slices) { my $expolygon = $slice->clone; $expolygon->translate(@$copy); @@ -573,7 +561,7 @@ sub make_skirt { (map @{$_->polyline}, map @{$_->support_fills}, grep $_->support_fills, @support_layers), (map @{$_->polyline}, map @{$_->support_interface_fills}, grep $_->support_interface_fills, @support_layers); } - push @points, map move_points($_, @layer_points), @{$object->copies}; + push @points, map move_points($_, @layer_points), @{$object->_shifted_copies}; } return if @points < 3; # at least three points required for a convex hull @@ -641,7 +629,7 @@ sub make_brim { (map @{$_->polyline->grow($grow_distance)}, @{$support_layer0->support_interface_fills}) if $support_layer0->support_interface_fills; } - foreach my $copy (@{$object->copies}) { + foreach my $copy (@{$object->_shifted_copies}) { push @islands, map { $_->translate(@$copy); $_ } map $_->clone, @object_islands; } } @@ -751,14 +739,6 @@ sub write_gcode { # TODO: make sure we select the first *used* extruder print $fh $gcodegen->set_extruder($self->extruders->[0]); - # calculate X,Y shift to center print around specified origin - my $print_bb = $self->bounding_box; - my $print_size = $print_bb->size; - my @shift = ( - $Slic3r::Config->print_center->[X] - unscale($print_size->[X]/2 + $print_bb->x_min), - $Slic3r::Config->print_center->[Y] - unscale($print_size->[Y]/2 + $print_bb->y_min), - ); - # initialize a motion planner for object-to-object travel moves if ($Slic3r::Config->avoid_crossing_perimeters) { my $distance_from_objects = 1; @@ -771,9 +751,8 @@ sub write_gcode { # discard layers only containing thin walls (offset would fail on an empty polygon) if (@$convex_hull) { my $expolygon = Slic3r::ExPolygon->new($convex_hull); - $expolygon->translate(scale $shift[X], scale $shift[Y]); my @island = @{$expolygon->offset_ex(scale $distance_from_objects, 1, JT_SQUARE)}; - foreach my $copy (@{ $self->objects->[$obj_idx]->copies }) { + foreach my $copy (@{ $self->objects->[$obj_idx]->shifted_copies }) { push @islands, map { my $c = $_->clone; $c->translate(@$copy); $c } @island; } } @@ -800,7 +779,6 @@ sub write_gcode { my $layer_gcode = Slic3r::GCode::Layer->new( print => $self, gcodegen => $gcodegen, - shift => \@shift, ); # do all objects for each layer @@ -817,7 +795,7 @@ sub write_gcode { # this happens before Z goes down to layer 0 again, so that # no collision happens hopefully. if ($finished_objects > 0) { - $gcodegen->set_shift(map $shift[$_] + unscale $copy->[$_], X,Y); + $gcodegen->set_shift(map unscale $copy->[$_], X,Y); print $fh $gcodegen->retract; print $fh $gcodegen->G0(Slic3r::Point->new(0,0), undef, 0, 'move to origin position for next object'); } @@ -851,7 +829,7 @@ sub write_gcode { } } else { # order objects using a nearest neighbor search - my @obj_idx = @{chained_path([ map Slic3r::Point->new(@{$_->copies->[0]}), @{$self->objects} ])}; + my @obj_idx = @{chained_path([ map Slic3r::Point->new(@{$_->_shifted_copies->[0]}), @{$self->objects} ])}; # sort layers by Z my %layers = (); # print_z => [ [layers], [layers], [layers] ] by obj_idx @@ -872,7 +850,7 @@ sub write_gcode { foreach my $obj_idx (@obj_idx) { foreach my $layer (@{ $layers{$print_z}[$obj_idx] // [] }) { print $fh $buffer->append( - $layer_gcode->process_layer($layer, $layer->object->copies), + $layer_gcode->process_layer($layer, $layer->object->_shifted_copies), $layer->object . ref($layer), # differentiate $obj_id between normal layers and support layers $layer->id, $layer->print_z, diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 159953c5..333f8759 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -10,14 +10,16 @@ use Slic3r::Surface ':types'; 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); # XYZ in scaled coordinates -has 'copies' => (is => 'rw', trigger => 1); # in scaled coordinates -has 'copies_shift' => (is => 'rw'); # scaled coordinates to add to copies (to compensate for the alignment operated when creating the object but still preserving a coherent API for external callers) -has 'layers' => (is => 'rw', default => sub { [] }); -has 'support_layers' => (is => 'rw', default => sub { [] }); +has 'copies' => (is => 'ro'); # Slic3r::Point objects in scaled G-code coordinates has 'config_overrides' => (is => 'rw', default => sub { Slic3r::Config->new }); has 'config' => (is => 'rw'); has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ] + +has 'size' => (is => 'rw'); # XYZ in scaled coordinates +has '_copies_shift' => (is => 'rw'); # scaled coordinates to add to copies (to compensate for the alignment operated when creating the object but still preserving a coherent API for external callers) +has '_shifted_copies' => (is => 'rw'); # Slic3r::Point objects in scaled G-code coordinates in our coordinates +has 'layers' => (is => 'rw', default => sub { [] }); +has 'support_layers' => (is => 'rw', default => sub { [] }); has 'fill_maker' => (is => 'lazy'); sub BUILD { @@ -25,54 +27,28 @@ sub BUILD { $self->init_config; - # make layers taking custom heights into account - my $print_z = my $slice_z = my $height = my $id = 0; - - # add raft layers - if ($self->config->raft_layers > 0) { - $print_z += $Slic3r::Config->get_value('first_layer_height'); - $print_z += $Slic3r::Config->layer_height * ($self->config->raft_layers - 1); - $id += $self->config->raft_layers; - } - - # loop until we have at least one layer and the max slice_z reaches the object height - my $max_z = unscale $self->size->[Z]; - while (!@{$self->layers} || ($slice_z - $height) <= $max_z) { - # assign the default height to the layer according to the general settings - $height = ($id == 0) - ? $Slic3r::Config->get_value('first_layer_height') - : $Slic3r::Config->layer_height; - - # look for an applicable custom range - if (my $range = first { $_->[0] <= $slice_z && $_->[1] > $slice_z } @{$self->layer_height_ranges}) { - $height = $range->[2]; - - # if user set custom height to zero we should just skip the range and resume slicing over it - if ($height == 0) { - $slice_z += $range->[1] - $range->[0]; - next; - } - } - - $print_z += $height; - $slice_z += $height/2; - - ### Slic3r::debugf "Layer %d: height = %s; slice_z = %s; print_z = %s\n", $id, $height, $slice_z, $print_z; - - push @{$self->layers}, Slic3r::Layer->new( - object => $self, - id => $id, - height => $height, - print_z => $print_z, - slice_z => $slice_z, - ); - if (@{$self->layers} >= 2) { - $self->layers->[-2]->upper_layer($self->layers->[-1]); - } - $id++; - - $slice_z += $height/2; # add the other half layer - } + # translate meshes so that we work with smaller coordinates + { + # compute the bounding box of the supplied meshes + my @meshes = grep defined $_, @{$self->meshes}; # in no particular order + my $bb = Slic3r::Geometry::BoundingBox->merge(map $_->bounding_box, @meshes); + + # Translate meshes so that our toolpath generation algorithms work with smaller + # XY coordinates; this translation is an optimization and not strictly required. + # However, this also aligns object to Z = 0, which on the contrary is required + # since we don't assume input is already aligned. + $_->translate(@{$bb->vector_to_origin}) for @meshes; + + # We store the XY translation so that we can place copies correctly in the output G-code + # (copies are expressed in G-code coordinates and this translation is not publicly exposed). + $self->_copies_shift(Slic3r::Point->new_scale($bb->x_min, $bb->y_min)); + $self->_trigger_copies; + + # Scale the object size and store it + my $scaled_bb = $bb->clone; + $scaled_bb->scale(1 / &Slic3r::SCALING_FACTOR); + $self->size($scaled_bb->size); + } } sub _build_fill_maker { @@ -80,13 +56,32 @@ sub _build_fill_maker { return Slic3r::Fill->new(bounding_box => $self->bounding_box); } -# 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; - # order copies with a nearest neighbor search - @{$self->copies} = @{$self->copies}[@{chained_path($self->copies)}]; + return if !defined $self->_copies_shift; + + # order copies with a nearest neighbor search and translate them by _copies_shift + $self->_shifted_copies([ + map { + my $c = $_->clone; + $c->translate(@{ $self->_copies_shift }); + $c; + } @{$self->copies}[@{chained_path($self->copies)}] + ]); +} + +# in unscaled coordinates +sub add_copy { + my ($self, $x, $y) = @_; + push @{$self->copies}, Slic3r::Point->new_scale($x, $y); + $self->_trigger_copies; +} + +sub delete_last_copy { + my ($self) = @_; + pop @{$self->copies}; + $self->_trigger_copies; } sub init_config { @@ -106,10 +101,65 @@ sub bounding_box { return Slic3r::Geometry::BoundingBox->new_from_points([ map Slic3r::Point->new(@$_[X,Y]), [0,0], $self->size ]); } +# this should be idempotent sub slice { my $self = shift; my %params = @_; + # init layers + { + @{$self->layers} = (); + + # make layers taking custom heights into account + my $print_z = my $slice_z = my $height = my $id = 0; + + # add raft layers + if ($self->config->raft_layers > 0) { + $print_z += $Slic3r::Config->get_value('first_layer_height'); + $print_z += $Slic3r::Config->layer_height * ($self->config->raft_layers - 1); + $id += $self->config->raft_layers; + } + + # loop until we have at least one layer and the max slice_z reaches the object height + my $max_z = unscale $self->size->[Z]; + while (!@{$self->layers} || ($slice_z - $height) <= $max_z) { + # assign the default height to the layer according to the general settings + $height = ($id == 0) + ? $Slic3r::Config->get_value('first_layer_height') + : $Slic3r::Config->layer_height; + + # look for an applicable custom range + if (my $range = first { $_->[0] <= $slice_z && $_->[1] > $slice_z } @{$self->layer_height_ranges}) { + $height = $range->[2]; + + # if user set custom height to zero we should just skip the range and resume slicing over it + if ($height == 0) { + $slice_z += $range->[1] - $range->[0]; + next; + } + } + + $print_z += $height; + $slice_z += $height/2; + + ### Slic3r::debugf "Layer %d: height = %s; slice_z = %s; print_z = %s\n", $id, $height, $slice_z, $print_z; + + push @{$self->layers}, Slic3r::Layer->new( + object => $self, + id => $id, + height => $height, + print_z => $print_z, + slice_z => $slice_z, + ); + if (@{$self->layers} >= 2) { + $self->layers->[-2]->upper_layer($self->layers->[-1]); + } + $id++; + + $slice_z += $height/2; # add the other half layer + } + } + # make sure all layers contain layer region objects for all regions my $regions_count = $self->print->regions_count; foreach my $layer (@{ $self->layers }) { diff --git a/lib/Slic3r/Test/SectionCut.pm b/lib/Slic3r/Test/SectionCut.pm index 596d0c1d..dc67e839 100644 --- a/lib/Slic3r/Test/SectionCut.pm +++ b/lib/Slic3r/Test/SectionCut.pm @@ -79,7 +79,7 @@ sub _plot { my (@rectangles, @circles) = (); foreach my $object (@{$self->print->objects}) { - foreach my $copy (@{$object->copies}) { + foreach my $copy (@{$object->shifted_copies}) { foreach my $layer (@{$object->layers}, @{$object->support_layers}) { # get all ExtrusionPath objects my @paths = diff --git a/slic3r.pl b/slic3r.pl index 4b90d725..f35c1ff2 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -144,6 +144,7 @@ if (@ARGV) { # slicing from command line # if all input objects have defined position(s) apply duplication to the whole model $model->duplicate($config, $config->duplicate); } + $model->center_instances_around_point($config->print_center); if ($opt{info}) { $model->print_info; @@ -152,6 +153,7 @@ if (@ARGV) { # slicing from command line my $print = Slic3r::Print->new(config => $config); $print->add_model_object($_) for @{$model->objects}; + undef $model; # free memory $print->validate; my %params = ( output_file => $opt{output}, diff --git a/xs/src/TriangleMesh.cpp b/xs/src/TriangleMesh.cpp index 63d33b7c..2aadce4f 100644 --- a/xs/src/TriangleMesh.cpp +++ b/xs/src/TriangleMesh.cpp @@ -191,10 +191,8 @@ TriangleMesh::slice(const std::vector &z, std::vector &layers) FUTURE: parallelize slice_facet() and make_loops() */ - if (!this->repaired) this->repair(); - // build a table to map a facet_idx to its three edge indices - if (this->stl.v_shared == NULL) stl_generate_shared_vertices(&(this->stl)); + this->require_shared_vertices(); typedef std::pair t_edge; typedef std::vector t_edges; // edge_idx => a_id,b_id typedef std::map t_edges_map; // a_id,b_id => edge_idx @@ -607,7 +605,7 @@ TriangleMesh::horizontal_projection(ExPolygons &retval) const void TriangleMesh::convex_hull(Polygon* hull) { - if (this->stl.v_shared == NULL) stl_generate_shared_vertices(&(this->stl)); + this->require_shared_vertices(); Points pp; pp.reserve(this->stl.stats.shared_vertices); for (int i = 0; i < this->stl.stats.shared_vertices; i++) { @@ -617,6 +615,13 @@ TriangleMesh::convex_hull(Polygon* hull) Slic3r::Geometry::convex_hull(pp, hull); } +void +TriangleMesh::require_shared_vertices() +{ + if (!this->repaired) this->repair(); + if (this->stl.v_shared == NULL) stl_generate_shared_vertices(&(this->stl)); +} + #ifdef SLIC3RXS SV* TriangleMesh::to_SV() { diff --git a/xs/src/TriangleMesh.hpp b/xs/src/TriangleMesh.hpp index 0056d02a..12c2680e 100644 --- a/xs/src/TriangleMesh.hpp +++ b/xs/src/TriangleMesh.hpp @@ -37,6 +37,9 @@ class TriangleMesh stl_file stl; bool repaired; + private: + void require_shared_vertices(); + #ifdef SLIC3RXS SV* to_SV(); void ReadFromPerl(SV* vertices, SV* facets);