diff --git a/MANIFEST b/MANIFEST index c4c53288c..2cbf9fc2c 100644 --- a/MANIFEST +++ b/MANIFEST @@ -34,12 +34,14 @@ lib/Slic3r/GUI/Plater.pm lib/Slic3r/GUI/SkeinPanel.pm lib/Slic3r/GUI/Tab.pm lib/Slic3r/Layer.pm +lib/Slic3r/Layer/Region.pm lib/Slic3r/Line.pm lib/Slic3r/Model.pm lib/Slic3r/Point.pm lib/Slic3r/Polygon.pm lib/Slic3r/Polyline.pm lib/Slic3r/Print.pm +lib/Slic3r/Print/Region.pm lib/Slic3r/Print/Object.pm lib/Slic3r/Surface.pm lib/Slic3r/SVG.pm diff --git a/README.markdown b/README.markdown index 1dfe6552b..5ca1ac240 100644 --- a/README.markdown +++ b/README.markdown @@ -153,7 +153,9 @@ The author of the Silk icon set is Mark James. --first-layer-height Layer height for first layer (mm or %, default: 100%) --infill-every-layers Infill every N layers (default: 1) - + --solid-infill-every-layers + Force a solid layer every N layers (default: 0) + Print options: --perimeters Number of perimeters/horizontal skins (range: 0+, default: 3) --solid-layers Number of solid layers to do for top/bottom surfaces diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index f6bd643c4..e80357767 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -7,7 +7,7 @@ use strict; use warnings; require v5.10; -our $VERSION = "0.9.3-dev"; +our $VERSION = "0.9.4-dev"; our $debug = 0; sub debugf { @@ -43,6 +43,7 @@ use Slic3r::Format::STL; use Slic3r::GCode; use Slic3r::Geometry qw(PI); use Slic3r::Layer; +use Slic3r::Layer::Region; use Slic3r::Line; use Slic3r::Model; use Slic3r::Point; @@ -50,6 +51,7 @@ use Slic3r::Polygon; use Slic3r::Polyline; use Slic3r::Print; use Slic3r::Print::Object; +use Slic3r::Print::Region; use Slic3r::Surface; use Slic3r::TriangleMesh; eval "use Slic3r::Build"; @@ -63,15 +65,15 @@ use constant SMALL_PERIMETER_LENGTH => (6.5 / SCALING_FACTOR) * 2 * PI; # process. They should belong to the Print object, but we are keeping # them here because it makes accessing them slightly faster. our $Config; -our $extruders; -our ($flow, $first_layer_flow, $perimeter_flow, $infill_flow, $support_material_flow); +our $flow; sub parallelize { my %params = @_; if (!$params{disable} && $Slic3r::have_threads && $Config->threads > 1) { + my @items = (ref $params{items} eq 'CODE') ? $params{items}->() : @{$params{items}}; my $q = Thread::Queue->new; - $q->enqueue(@{ $params{items} }, (map undef, 1..$Config->threads)); + $q->enqueue(@items, (map undef, 1..$Config->threads)); my $thread_cb = sub { $params{thread_cb}->($q) }; foreach my $th (map threads->create($thread_cb), 1..$Config->threads) { diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 312352312..1c78843fd 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -3,6 +3,8 @@ use strict; use warnings; use utf8; +use List::Util qw(first); + use constant PI => 4 * atan2(1, 1); # cemetery of old config settings @@ -351,6 +353,15 @@ our $Options = { min => 1, default => 1, }, + 'solid_infill_every_layers' => { + label => 'Solid infill every', + tooltip => 'This feature allows to force a solid layer every given number of layers. Zero to disable.', + sidetext => 'layers', + cli => 'solid-infill-every-layers=i', + type => 'i', + min => 0, + default => 0, + }, # flow options 'extrusion_width' => { @@ -944,6 +955,9 @@ sub set { $opt_key =~ s/^bottom_layer_speed$/first_layer_speed/; $value = $value =~ /^\d+(?:\.\d+)?$/ && $value != 0 ? ($value*100) . "%" : 0; } + if ($opt_key eq 'threads' && !$Slic3r::have_threads) { + $value = 1; + } if (!exists $Options->{$opt_key}) { $opt_key = +(grep { $Options->{$_}{aliases} && grep $_ eq $opt_key, @{$Options->{$_}{aliases}} } keys %$Options)[0] @@ -1051,15 +1065,18 @@ sub validate { # --fill-pattern die "Invalid value for --fill-pattern\n" - if !exists $Slic3r::Fill::FillTypes{$self->fill_pattern}; + if !first { $_ eq $self->fill_pattern } @{$Options->{fill_pattern}{values}}; # --solid-fill-pattern die "Invalid value for --solid-fill-pattern\n" - if !exists $Slic3r::Fill::FillTypes{$self->solid_fill_pattern}; + if !first { $_ eq $self->solid_fill_pattern } @{$Options->{solid_fill_pattern}{values}}; # --fill-density die "Invalid value for --fill-density\n" if $self->fill_density < 0 || $self->fill_density > 1; + die "The selected fill pattern is not supposed to work at 100% density\n" + if $self->fill_density == 1 + && !first { $_ eq $self->fill_pattern } @{$Options->{solid_fill_pattern}{values}}; # --infill-every-layers die "Invalid value for --infill-every-layers\n" diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index 26f0d471b..6a3e946c1 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -82,14 +82,6 @@ sub safety_offset { ); } -sub offset_ex { - my $self = shift; - my @offsets = $self->offset(@_); - - # apply holes to the right contours - return @{ union_ex(\@offsets) }; -} - sub noncollapsing_offset_ex { my $self = shift; my ($distance, @params) = @_; diff --git a/lib/Slic3r/Extruder.pm b/lib/Slic3r/Extruder.pm index 4b7b6b19a..3b77a3497 100644 --- a/lib/Slic3r/Extruder.pm +++ b/lib/Slic3r/Extruder.pm @@ -9,7 +9,9 @@ use constant OPTIONS => [qw( retract_length retract_lift retract_speed retract_restart_extra retract_before_travel retract_length_toolchange retract_restart_extra_toolchange )]; -has $_ => (is => 'ro', required => 1) for @{&OPTIONS}; + +has 'id' => (is => 'rw', required => 1); +has $_ => (is => 'ro', required => 1) for @{&OPTIONS}; has 'retracted' => (is => 'rw', default => sub {0} ); has 'e_per_mm3' => (is => 'lazy'); diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index 5c694d95e..4ae1f5415 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -99,7 +99,7 @@ sub make_fill { # add spacing between adjacent surfaces { - my $distance = scale $layer->infill_flow->spacing / 2; + my $distance = $layer->infill_flow->scaled_spacing / 2; my @offsets = (); foreach my $surface (@surfaces) { my $expolygon = $surface->expolygon; diff --git a/lib/Slic3r/Fill/Concentric.pm b/lib/Slic3r/Fill/Concentric.pm index c0d172cfe..699b526d9 100644 --- a/lib/Slic3r/Fill/Concentric.pm +++ b/lib/Slic3r/Fill/Concentric.pm @@ -63,7 +63,7 @@ sub fill_surface { my $path = $loop->split_at_index($index); # clip the path to avoid the extruder to get exactly on the first point of the loop - $path->clip_end(scale($self->layer ? $self->layer->flow->width : $Slic3r::flow->width) * 0.15); + $path->clip_end($self->layer ? $self->layer->flow->scaled_width : $Slic3r::flow->scaled_width * 0.15); push @paths, $path->points if @{$path->points}; } diff --git a/lib/Slic3r/Fill/Honeycomb.pm b/lib/Slic3r/Fill/Honeycomb.pm index 1e407e2e6..70dd1e2ad 100644 --- a/lib/Slic3r/Fill/Honeycomb.pm +++ b/lib/Slic3r/Fill/Honeycomb.pm @@ -21,7 +21,7 @@ sub fill_surface { # infill math my $min_spacing = scale $params{flow_spacing}; my $distance = $min_spacing / $params{density}; - my $overlap_distance = scale($self->layer ? $self->layer->flow->width : $Slic3r::flow->width) * 0.4; + my $overlap_distance = $self->layer ? $self->layer->flow->scaled_width : $Slic3r::flow->scaled_width * 0.4; my $cache_id = sprintf "d%s_s%s_a%s", $params{density}, $params{flow_spacing}, $rotate_vector->[0][0]; diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm index 121249eaa..b4569dbc8 100644 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ b/lib/Slic3r/Fill/Rectilinear.pm @@ -31,7 +31,7 @@ sub fill_surface { $flow_spacing = unscale $distance_between_lines; } - my $overlap_distance = scale($self->layer ? $self->layer->flow->width : $Slic3r::flow->width) * 0.4; + my $overlap_distance = $self->layer ? $self->layer->flow->scaled_width : $Slic3r::flow->scaled_width * 0.4; my $x = $bounding_box->[X1]; my $is_line_pattern = $self->isa('Slic3r::Fill::Line'); diff --git a/lib/Slic3r/Flow.pm b/lib/Slic3r/Flow.pm index 5e8f23a2b..a035b9d37 100644 --- a/lib/Slic3r/Flow.pm +++ b/lib/Slic3r/Flow.pm @@ -1,13 +1,15 @@ package Slic3r::Flow; use Moo; -use Slic3r::Geometry qw(PI); +use Slic3r::Geometry qw(PI scale); has 'nozzle_diameter' => (is => 'ro', required => 1); has 'layer_height' => (is => 'ro', default => sub { $Slic3r::Config->layer_height }); has 'width' => (is => 'rwp', builder => 1); has 'spacing' => (is => 'lazy'); +has 'scaled_width' => (is => 'lazy'); +has 'scaled_spacing' => (is => 'lazy'); sub BUILD { my $self = shift; @@ -65,4 +67,14 @@ sub clone { ); } +sub _build_scaled_width { + my $self = shift; + return scale $self->width; +} + +sub _build_scaled_spacing { + my $self = shift; + return scale $self->spacing; +} + 1; diff --git a/lib/Slic3r/Format/AMF.pm b/lib/Slic3r/Format/AMF.pm index 78d2656c5..d654ca1a5 100644 --- a/lib/Slic3r/Format/AMF.pm +++ b/lib/Slic3r/Format/AMF.pm @@ -35,8 +35,8 @@ sub write_file { 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->{$_}; + for (keys %{$material->attributes}) { + printf $fh qq{ %s\n}, $_, $material->attributes->{$_}; } printf $fh qq{ \n}; } diff --git a/lib/Slic3r/Format/AMF/Parser.pm b/lib/Slic3r/Format/AMF/Parser.pm index 5e0b28178..173dfc091 100644 --- a/lib/Slic3r/Format/AMF/Parser.pm +++ b/lib/Slic3r/Format/AMF/Parser.pm @@ -37,10 +37,10 @@ sub start_element { $self->{_vertex_idx} = $1-1; } elsif ($data->{LocalName} eq 'material') { my $material_id = $self->_get_attribute($data, 'id') || '_'; - $self->{_material} = $self->{_model}->materials->{ $material_id } = {}; + $self->{_material} = $self->{_model}->set_material($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} } = ""; + $self->{_material}->attributes->{ $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}) { @@ -63,7 +63,7 @@ sub characters { } elsif ($self->{_triangle} && defined $self->{_vertex_idx}) { $self->{_triangle}[ $self->{_vertex_idx} ] .= $data->{Data}; } elsif ($self->{_material_metadata_type}) { - $self->{_material}{ $self->{_material_metadata_type} } .= $data->{Data}; + $self->{_material}->attributes->{ $self->{_material_metadata_type} } .= $data->{Data}; } elsif ($self->{_instance_property}) { $self->{_instance}{ $self->{_instance_property} } .= $data->{Data}; } diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 4f29fa0b9..a135be271 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -5,13 +5,14 @@ use List::Util qw(first); use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(scale unscale scaled_epsilon points_coincide PI X Y); +has 'multiple_extruders' => (is => 'ro', default => sub {0} ); has 'layer' => (is => 'rw'); has 'shift_x' => (is => 'rw', default => sub {0} ); has 'shift_y' => (is => 'rw', default => sub {0} ); has 'z' => (is => 'rw', default => sub {0} ); has 'speed' => (is => 'rw'); -has 'extruder_idx' => (is => 'rw'); +has 'extruder' => (is => 'rw'); has 'extrusion_distance' => (is => 'rw', default => sub {0} ); has 'elapsed_time' => (is => 'rw', default => sub {0} ); # seconds has 'total_extrusion_length' => (is => 'rw', default => sub {0} ); @@ -49,11 +50,6 @@ my %role_speeds = ( &EXTR_ROLE_SUPPORTMATERIAL => 'perimeter', ); -sub extruder { - my $self = shift; - return $Slic3r::extruders->[$self->extruder_idx]; -} - sub change_layer { my $self = shift; my ($layer) = @_; @@ -93,7 +89,6 @@ sub extrude_loop { # or randomize if requested my $last_pos = $self->last_pos; if ($Slic3r::Config->randomize_start && $loop->role == EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER) { - srand $self->layer->id * 10; $last_pos = Slic3r::Point->new(scale $Slic3r::Config->print_center->[X], scale $Slic3r::Config->bed_size->[Y]); $last_pos->rotate(rand(2*PI), $Slic3r::Config->print_center); } @@ -105,7 +100,7 @@ sub extrude_loop { # clip the path to avoid the extruder to get exactly on the first point of the loop; # if polyline was shorter than the clipping distance we'd get a null polyline, so # we discard it in that case - $extrusion_path->clip_end(scale($self->layer ? $self->layer->flow->width : $Slic3r::flow->width) * 0.15); + $extrusion_path->clip_end($self->layer ? $self->layer->flow->scaled_width : $Slic3r::flow->scaled_width * 0.15); return '' if !@{$extrusion_path->polyline}; # extrude along the path @@ -135,7 +130,7 @@ sub extrude_path { { 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}) { + if (!$Slic3r::Config->only_retract_when_crossing_perimeters || $path->role != EXTR_ROLE_FILL || !first { $_->encloses_line($travel, scaled_epsilon) } @{$self->layer->slices}) { $gcode .= $self->retract(travel_to => $path->points->[0]); } } @@ -374,26 +369,26 @@ sub _Gx { return "$gcode\n"; } -sub set_tool { +sub set_extruder { my $self = shift; - my ($tool) = @_; + my ($extruder) = @_; - # return nothing if this tool was already selected - return "" if (defined $self->extruder_idx) && ($self->extruder_idx == $tool); + # return nothing if this extruder was already selected + return "" if (defined $self->extruder) && ($self->extruder->id == $extruder); # if we are running a single-extruder setup, just set the extruder and return nothing - if (@{$Slic3r::extruders} == 1) { - $self->extruder_idx($tool); + if (!$self->multiple_extruders) { + $self->extruder($extruder); return ""; } - # trigger retraction on the current tool (if any) + # trigger retraction on the current extruder (if any) my $gcode = ""; - $gcode .= $self->retract(toolchange => 1) if defined $self->extruder_idx; + $gcode .= $self->retract(toolchange => 1) if defined $self->extruder; - # set the new tool - $self->extruder_idx($tool); - $gcode .= sprintf "T%d%s\n", $tool, ($Slic3r::Config->gcode_comments ? ' ; change tool' : ''); + # set the new extruder + $self->extruder($extruder); + $gcode .= sprintf "T%d%s\n", $extruder->id, ($Slic3r::Config->gcode_comments ? ' ; change extruder' : ''); $gcode .= $self->reset_e; return $gcode; @@ -426,7 +421,7 @@ sub set_temperature { : ('M104', 'set temperature'); my $gcode = sprintf "$code %s%d %s; $comment\n", ($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature, - (defined $tool && $tool != $self->extruder_idx) ? "T$tool " : ""; + (defined $tool && $tool != $self->extruder->id) ? "T$tool " : ""; $gcode .= "M116 ; wait for temperature to be reached\n" if $Slic3r::Config->gcode_flavor eq 'teacup' && $wait; diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 887d35320..8bfd4d1fc 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -21,6 +21,7 @@ use constant MI_QUICK_SLICE => &Wx::NewId; 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_COMBINE_STLS => &Wx::NewId; use constant MI_PLATER_EXPORT_GCODE => &Wx::NewId; use constant MI_PLATER_EXPORT_STL => &Wx::NewId; @@ -33,6 +34,7 @@ use constant MI_TAB_PRINTER => &Wx::NewId; use constant MI_CONF_WIZARD => &Wx::NewId; use constant MI_WEBSITE => &Wx::NewId; +use constant MI_DOCUMENTATION => &Wx::NewId; our $datadir; our $Settings; @@ -90,6 +92,8 @@ sub OnInit { $fileMenu->AppendSeparator(); $fileMenu->Append(MI_SLICE_SVG, "Slice to SV&G…\tCtrl+G", 'Slice file to SVG'); $fileMenu->AppendSeparator(); + $fileMenu->Append(MI_COMBINE_STLS, "Combine multi-material STL files…", 'Combine multiple STL files into a single multi-material AMF file'); + $fileMenu->AppendSeparator(); $fileMenu->Append(wxID_EXIT, "&Quit", 'Quit Slic3r'); EVT_MENU($frame, MI_LOAD_CONF, sub { $self->{skeinpanel}->load_config_file }); EVT_MENU($frame, MI_EXPORT_CONF, sub { $self->{skeinpanel}->export_config }); @@ -99,6 +103,7 @@ sub OnInit { EVT_MENU($frame, MI_QUICK_SAVE_AS, sub { $self->{skeinpanel}->do_slice(save_as => 1); $repeat->Enable(defined $Slic3r::GUI::SkeinPanel::last_input_file) }); EVT_MENU($frame, MI_SLICE_SVG, sub { $self->{skeinpanel}->do_slice(save_as => 1, export_svg => 1) }); + EVT_MENU($frame, MI_COMBINE_STLS, sub { $self->{skeinpanel}->combine_stls }); EVT_MENU($frame, wxID_EXIT, sub {$_[0]->Close(0)}); } @@ -130,10 +135,14 @@ sub OnInit { my $helpMenu = Wx::Menu->new; { $helpMenu->Append(MI_CONF_WIZARD, "&Configuration $Slic3r::GUI::ConfigWizard::wizard…", "Run Configuration $Slic3r::GUI::ConfigWizard::wizard"); + $helpMenu->AppendSeparator(); $helpMenu->Append(MI_WEBSITE, "Slic3r &Website", 'Open the Slic3r website in your browser'); + $helpMenu->Append(MI_DOCUMENTATION, "&Documentation", 'Open the Slic3r documentation in your browser'); + $helpMenu->AppendSeparator(); $helpMenu->Append(wxID_ABOUT, "&About Slic3r", 'Show about dialog'); EVT_MENU($frame, MI_CONF_WIZARD, sub { $self->{skeinpanel}->config_wizard }); EVT_MENU($frame, MI_WEBSITE, sub { Wx::LaunchDefaultBrowser('http://slic3r.org/') }); + EVT_MENU($frame, MI_DOCUMENTATION, sub { Wx::LaunchDefaultBrowser('https://github.com/alexrj/Slic3r/wiki/Documentation') }); EVT_MENU($frame, wxID_ABOUT, \&about); } diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 5f83117c4..37dd165fa 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -158,9 +158,8 @@ sub new { EVT_COMMAND($self, -1, $THUMBNAIL_DONE_EVENT, sub { my ($self, $event) = @_; my ($obj_idx, $thumbnail) = @{$event->GetData}; - $self->{objects}[$obj_idx]->thumbnail($thumbnail); - $self->mesh(undef); - $self->on_thumbnail_made; + $self->{objects}[$obj_idx]->thumbnail($thumbnail->clone); + $self->on_thumbnail_made($obj_idx); }); EVT_COMMAND($self, -1, $PROGRESS_BAR_EVENT, sub { @@ -275,7 +274,7 @@ sub load { my $self = shift; my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || ''; - my $dialog = Wx::FileDialog->new($self, 'Choose one or more files (STL/OBJ/AMF):', $dir, "", $Slic3r::GUI::SkeinPanel::model_wildcard, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); + my $dialog = Wx::FileDialog->new($self, 'Choose one or more files (STL/OBJ/AMF):', $dir, "", &Slic3r::GUI::SkeinPanel::MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); if ($dialog->ShowModal != wxID_OK) { $dialog->Destroy; return; @@ -433,7 +432,7 @@ sub arrange { my $total_parts = sum(map $_->instances_count, @{$self->{objects}}) or return; my @size = (); for my $a (X,Y) { - $size[$a] = $self->to_units(max(map $_->thumbnail->size->[$a], @{$self->{objects}})); + $size[$a] = max(map $_->rotated_size->[$a], @{$self->{objects}}); } eval { @@ -502,7 +501,7 @@ sub export_gcode { { $self->{output_file} = $print->expanded_output_filepath($self->{output_file}, $self->{objects}[0]->input_file); my $dlg = Wx::FileDialog->new($self, 'Save G-code file as:', dirname($self->{output_file}), - basename($self->{output_file}), $Slic3r::GUI::SkeinPanel::gcode_wildcard, wxFD_SAVE); + basename($self->{output_file}), &Slic3r::GUI::SkeinPanel::FILE_WILDCARDS->{gcode}, wxFD_SAVE); if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; return; @@ -529,7 +528,7 @@ sub export_gcode { ); }); $self->statusbar->SetCancelCallback(sub { - $self->{export_thread}->kill('KILL'); + $self->{export_thread}->kill('KILL')->join; $self->{export_thread} = undef; $self->statusbar->StopBusy; $self->statusbar->SetStatusText("Export cancelled"); @@ -556,7 +555,7 @@ sub _init_print { return Slic3r::Print->new( config => $self->skeinpanel->config, extra_variables => { - map { $_ => $self->skeinpanel->{options_tabs}{$_}->current_preset->{name} } qw(print filament printer), + map { +"${_}_preset" => $self->skeinpanel->{options_tabs}{$_}->current_preset->{name} } qw(print filament printer), }, ); } @@ -601,7 +600,6 @@ sub export_gcode2 { } $message .= "."; $params{on_completed}->($message); - $print->cleanup; }; $params{catch_error}->(); } @@ -655,7 +653,7 @@ sub _get_export_file { $output_file = $self->_init_print->expanded_output_filepath($output_file, $self->{objects}[0]->input_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); + basename($output_file), &Slic3r::GUI::SkeinPanel::MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; return undef; @@ -701,7 +699,7 @@ sub make_thumbnail { Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $THUMBNAIL_DONE_EVENT, shared_clone([ $obj_idx, $thumbnail ]))); threads->exit; } else { - $self->on_thumbnail_made; + $self->on_thumbnail_made($obj_idx); } }; @@ -710,6 +708,9 @@ sub make_thumbnail { sub on_thumbnail_made { my $self = shift; + my ($obj_idx) = @_; + + $self->{objects}[$obj_idx]->free_mesh; $self->recenter; $self->{canvas}->Refresh; } @@ -1017,6 +1018,14 @@ sub _trigger_mesh { $self->size([$self->mesh->size]) if $self->mesh; } +sub free_mesh { + my $self = shift; + + # only delete mesh from memory if we can retrieve it from the original file + return unless $self->input_file && $self->input_file_object_id; + $self->mesh(undef); +} + sub get_mesh { my $self = shift; diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index 6ef5a64de..8cdc8d7b2 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -13,6 +13,16 @@ our $last_input_file; our $last_output_file; our $last_config; +use constant FILE_WILDCARDS => { + stl => 'STL files (*.stl)|*.stl;*.STL', + obj => 'OBJ files (*.obj)|*.obj;*.OBJ', + amf => 'AMF files (*.amf)|*.amf;*.AMF;*.xml;*.XML', + ini => 'INI files *.ini|*.ini;*.INI', + gcode => 'G-code files *.gcode|*.gcode;*.GCODE|G-code files *.g|*.g;*.G', + svg => 'SVG files *.svg|*.svg;*.SVG', +}; +use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(stl obj amf)}; + sub new { my $class = shift; my ($parent) = @_; @@ -41,11 +51,6 @@ sub new { return $self; } -our $model_wildcard = "STL files (*.stl)|*.stl;*.STL|OBJ files (*.obj)|*.obj;*.OBJ|AMF files (*.amf)|*.amf;*.AMF;*.xml;*.XML"; -our $ini_wildcard = "INI files *.ini|*.ini;*.INI"; -our $gcode_wildcard = "G-code files *.gcode|*.gcode;*.GCODE|G-code files *.g|*.g;*.G"; -our $svg_wildcard = "SVG files *.svg|*.svg;*.SVG"; - sub do_slice { my $self = shift; my %params = @_; @@ -70,7 +75,7 @@ sub do_slice { my $input_file; if (!$params{reslice}) { - my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF):', $dir, "", $model_wildcard, wxFD_OPEN | wxFD_FILE_MUST_EXIST); + my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF):', $dir, "", MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST); if ($dialog->ShowModal != wxID_OK) { $dialog->Destroy; return; @@ -96,7 +101,7 @@ sub do_slice { Slic3r::GUI->save_settings; my $print = Slic3r::Print->new(config => $config); - $print->add_objects_from_file($input_file); + $print->add_model(Slic3r::Model->read_from_file($input_file)); $print->validate; # select output file @@ -107,7 +112,7 @@ sub do_slice { $output_file = $print->expanded_output_filepath($output_file); $output_file =~ s/\.gcode$/.svg/i if $params{export_svg}; my $dlg = Wx::FileDialog->new($self, 'Save ' . ($params{export_svg} ? 'SVG' : 'G-code') . ' file as:', dirname($output_file), - basename($output_file), $params{export_svg} ? $svg_wildcard : $gcode_wildcard, wxFD_SAVE); + basename($output_file), $params{export_svg} ? FILE_WILDCARDS->{svg} : FILE_WILDCARDS->{gcode}, wxFD_SAVE); if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; return; @@ -172,7 +177,7 @@ sub export_config { my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; my $filename = $last_config ? basename($last_config) : "config.ini"; my $dlg = Wx::FileDialog->new($self, 'Save configuration as:', $dir, $filename, - $ini_wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if ($dlg->ShowModal == wxID_OK) { my $file = $dlg->GetPath; $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); @@ -191,7 +196,7 @@ sub load_config_file { return unless $self->check_unsaved_changes; my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', $dir, "config.ini", - $ini_wildcard, wxFD_OPEN | wxFD_FILE_MUST_EXIST); + FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); return unless $dlg->ShowModal == wxID_OK; ($file) = $dlg->GetPaths; $dlg->Destroy; @@ -221,6 +226,58 @@ sub config_wizard { } } +sub combine_stls { + my $self = shift; + + # get input files + my @input_files = (); + my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; + { + my $dlg_message = 'Choose one or more files to combine (STL/OBJ)'; + while (1) { + my $dialog = Wx::FileDialog->new($self, "$dlg_message:", $dir, "", MODEL_WILDCARD, + wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); + if ($dialog->ShowModal != wxID_OK) { + $dialog->Destroy; + last; + } + push @input_files, $dialog->GetPaths; + $dialog->Destroy; + $dlg_message .= " or hit Cancel if you have finished"; + $dir = dirname($input_files[0]); + } + return if !@input_files; + } + + # get output file + my $output_file = $input_files[0]; + { + $output_file =~ s/\.(?:stl|obj)$/.amf.xml/i; + my $dlg = Wx::FileDialog->new($self, 'Save multi-material AMF file as:', dirname($output_file), + basename($output_file), FILE_WILDCARDS->{amf}, wxFD_SAVE); + if ($dlg->ShowModal != wxID_OK) { + $dlg->Destroy; + return; + } + $output_file = $dlg->GetPath; + } + + my @models = map Slic3r::Model->read_from_file($_), @input_files; + my $new_model = Slic3r::Model->new; + my $new_object = $new_model->add_object; + for my $m (0 .. $#models) { + my $model = $models[$m]; + $new_model->set_material($m, { Name => basename($input_files[$m]) }); + $new_object->add_volume( + material_id => $m, + facets => $model->objects->[0]->volumes->[0]->facets, + vertices => $model->objects->[0]->vertices, + ); + } + + Slic3r::Format::AMF->write_file($output_file, $new_model); +} + =head2 config This method collects all config values from the tabs and merges them into a single config object. diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 16107b548..69eab8820 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 only_retract_when_crossing_perimeters)], + options => [qw(infill_every_layers solid_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 cd1e59b32..b5bec3a8d 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -20,7 +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 + scaled_epsilon bounding_box_3D size_3D ); @@ -242,6 +242,7 @@ sub polygon_lines { sub nearest_point { my ($point, $points) = @_; my $index = nearest_point_index(@_); + return undef if !defined $index; return $points->[$index]; } @@ -706,6 +707,27 @@ sub bounding_box_intersect { return 1; } +# 3D +sub bounding_box_3D { + my ($points) = @_; + + my @extents = (map [undef, undef], X,Y,Z); + foreach my $point (@$points) { + for (X,Y,Z) { + $extents[$_][MIN] = $point->[$_] if !defined $extents[$_][MIN] || $point->[$_] < $extents[$_][MIN]; + $extents[$_][MAX] = $point->[$_] if !defined $extents[$_][MAX] || $point->[$_] > $extents[$_][MAX]; + } + } + return @extents; +} + +sub size_3D { + my ($points) = @_; + + my @extents = bounding_box_3D($points); + return map $extents[$_][MAX] - $extents[$_][MIN], (X,Y,Z); +} + sub angle3points { my ($p1, $p2, $p3) = @_; # p1 is the center @@ -897,9 +919,9 @@ sub arrange { my $skirt_margin; if ($Config->skirts > 0) { my $flow = Slic3r::Flow->new( - layer_height => $Config->first_layer_height, + layer_height => $Config->get_value('first_layer_height'), nozzle_diameter => $Config->nozzle_diameter->[0], # TODO: actually look for the extruder used for skirt - width => $Config->first_layer_extrusion_width, + width => $Config->get_value('first_layer_extrusion_width'), ); $skirt_margin = ($flow->spacing * $Config->skirts + $Config->skirt_distance) * 2; } else { diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm index 2c1bc52ba..bc65064a9 100644 --- a/lib/Slic3r/Layer.pm +++ b/lib/Slic3r/Layer.pm @@ -1,60 +1,24 @@ package Slic3r::Layer; use Moo; -use Math::Clipper ':all'; -use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Geometry qw(scale unscale collinear X Y A B PI rad2deg_dir bounding_box_center shortest_path); -use Slic3r::Geometry::Clipper qw(safety_offset union_ex diff_ex intersection_ex xor_ex is_counter_clockwise); -use Slic3r::Surface ':types'; +use Slic3r::Geometry::Clipper qw(union_ex); -# a sequential number of layer, starting at 0 -has 'id' => ( - is => 'rw', - #isa => 'Int', - required => 1, -); +has 'id' => (is => 'rw', required => 1); # sequential number of layer, 0-based +has 'object' => (is => 'ro', weak_ref => 1, required => 1); +has 'regions' => (is => 'ro', default => sub { [] }); +has 'slicing_errors' => (is => 'rw'); -has 'slicing_errors' => (is => 'rw'); +has 'slice_z' => (is => 'lazy'); +has 'print_z' => (is => 'lazy'); +has 'height' => (is => 'lazy'); +has 'flow' => (is => 'ro', default => sub { $Slic3r::flow }); -has 'slice_z' => (is => 'lazy'); -has 'print_z' => (is => 'lazy'); -has 'height' => (is => 'lazy'); -has 'flow' => (is => 'lazy'); -has 'perimeter_flow' => (is => 'lazy'); -has 'infill_flow' => (is => 'lazy'); - -# collection of spare segments generated by slicing the original geometry; -# these need to be merged in continuos (closed) polylines -has 'lines' => (is => 'rw', default => sub { [] }); - -# collection of surfaces generated by slicing the original geometry -has 'slices' => (is => 'rw'); - -# collection of polygons or polylines representing thin walls contained -# in the original geometry -has 'thin_walls' => (is => 'rw'); - -# collection of polygons or polylines representing thin infill regions that -# need to be filled with a medial axis -has 'thin_fills' => (is => 'rw'); - -# collection of expolygons generated by offsetting the innermost perimeter(s) -# they represent boundaries of areas to fill, typed (top/bottom/internal) -has 'surfaces' => (is => 'rw'); - -# collection of surfaces for infill generation. the difference between surfaces -# fill_surfaces is that this one honors fill_density == 0 and turns small internal -# surfaces into solid ones -has 'fill_surfaces' => (is => 'rw'); - -# ordered collection of extrusion paths/loops to build all perimeters -has 'perimeters' => (is => 'rw'); +# collection of expolygons generated by slicing the original geometry; +# also known as 'islands' (all regions are merged here) +has 'slices' => (is => 'rw'); # ordered collection of extrusion paths to fill surfaces for support material -has 'support_fills' => (is => 'rw'); - -# ordered collection of extrusion paths to fill surfaces -has 'fills' => (is => 'rw'); +has 'support_fills' => (is => 'rw'); # Z used for slicing sub _build_slice_z { @@ -78,522 +42,37 @@ sub _build_height { return $self->id == 0 ? $Slic3r::Config->get_value('first_layer_height') : $Slic3r::Config->layer_height; } -sub _build_flow { +sub region { my $self = shift; - return $self->id == 0 && $Slic3r::first_layer_flow - ? $Slic3r::first_layer_flow - : $Slic3r::flow; -} - -sub _build_perimeter_flow { - my $self = shift; - return $self->id == 0 && $Slic3r::first_layer_flow - ? $Slic3r::first_layer_flow - : $Slic3r::perimeter_flow; -} - -sub _build_infill_flow { - my $self = shift; - return $self->id == 0 && $Slic3r::first_layer_flow - ? $Slic3r::first_layer_flow - : $Slic3r::infill_flow; -} - -# build polylines from lines -sub make_surfaces { - my $self = shift; - my ($loops) = @_; + my ($region_id) = @_; - { - my $safety_offset = scale 0.1; - # merge everything - my $expolygons = [ map $_->offset_ex(-$safety_offset), @{union_ex(safety_offset($loops, $safety_offset))} ]; - - Slic3r::debugf " %d surface(s) having %d holes detected from %d polylines\n", - scalar(@$expolygons), scalar(map $_->holes, @$expolygons), scalar(@$loops); - - $self->slices([ - map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL), - @$expolygons - ]); - } - - # the contours must be offsetted by half extrusion width inwards - { - my $distance = scale $self->perimeter_flow->width / 2; - my @surfaces = @{$self->slices}; - @{$self->slices} = (); - foreach my $surface (@surfaces) { - push @{$self->slices}, map Slic3r::Surface->new - (expolygon => $_, surface_type => S_TYPE_INTERNAL), - map $_->offset_ex(+$distance), - $surface->expolygon->offset_ex(-2*$distance); - } - - # now detect thin walls by re-outgrowing offsetted surfaces and subtracting - # them from the original slices - my $outgrown = Math::Clipper::offset([ map $_->p, @{$self->slices} ], $distance); - my $diff = diff_ex( - [ map $_->p, @surfaces ], - $outgrown, - 1, - ); - - $self->thin_walls([]); - if (@$diff) { - my $area_threshold = scale($self->perimeter_flow->spacing) ** 2; - @$diff = grep $_->area > ($area_threshold), @$diff; - - @{$self->thin_walls} = map $_->medial_axis(scale $self->perimeter_flow->width), @$diff; - - Slic3r::debugf " %d thin walls detected\n", scalar(@{$self->thin_walls}) if @{$self->thin_walls}; - } - } - - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output(undef, "surfaces.svg", - polygons => [ map $_->contour, @{$self->slices} ], - red_polygons => [ map $_->p, map @{$_->holes}, @{$self->slices} ], + if (!defined $self->regions->[$region_id]) { + $self->regions->[$region_id] = Slic3r::Layer::Region->new( + layer => $self, + region => $self->object->print->regions->[$region_id], ); } + return $self->regions->[$region_id]; +} + +# merge all regions' slices to get islands +sub make_slices { + my $self = shift; + + # optimization for single-region layers + my @regions_with_slices = grep { @{$_->slices} } @{$self->regions}; + if (@regions_with_slices == 1) { + $self->slices([ map $_->expolygon, @{$regions_with_slices[0]->slices} ]); + return; + } + + $self->slices(union_ex([ map $_->p, map @{$_->slices}, @{$self->regions} ])); } sub make_perimeters { my $self = shift; Slic3r::debugf "Making perimeters for layer %d\n", $self->id; - - my $gap_area_threshold = scale($self->perimeter_flow->width)** 2; - - # this array will hold one arrayref per original surface (island); - # each item of this arrayref is an arrayref representing a depth (from outer - # perimeters to inner); each item of this arrayref is an ExPolygon: - # @perimeters = ( - # [ # first island - # [ Slic3r::ExPolygon, Slic3r::ExPolygon... ], #depth 0: outer loop - # [ Slic3r::ExPolygon, Slic3r::ExPolygon... ], #depth 1: inner loop - # ], - # [ # second island - # ... - # ] - # ) - my @perimeters = (); # one item per depth; each item - - # organize islands using a shortest path search - my @surfaces = @{shortest_path([ - map [ $_->contour->[0], $_ ], @{$self->slices}, - ])}; - - $self->perimeters([]); - $self->surfaces([]); - $self->thin_fills([]); - - # for each island: - foreach my $surface (@surfaces) { - my @last_offsets = ($surface->expolygon); - - # experimental hole compensation (see ArcCompensation in the RepRap wiki) - if (0) { - foreach my $hole ($last_offsets[0]->holes) { - my $circumference = abs($hole->length); - next unless $circumference <= &Slic3r::SMALL_PERIMETER_LENGTH; - # this compensation only works for circular holes, while it would - # overcompensate for hexagons and other shapes having straight edges. - # so we require a minimum number of vertices. - next unless $circumference / @$hole >= scale 3 * $Slic3r::flow->width; - - # revert the compensation done in make_surfaces() and get the actual radius - # of the hole - my $radius = ($circumference / PI / 2) - scale $self->perimeter_flow->spacing/2; - my $new_radius = (scale($self->perimeter_flow->width) + sqrt((scale($self->perimeter_flow->width)**2) + (4*($radius**2)))) / 2; - # holes are always turned to contours, so reverse point order before and after - $hole->reverse; - my @offsetted = $hole->offset(+ ($new_radius - $radius)); - # skip arc compensation when hole is not round (thus leads to multiple offsets) - @$hole = map Slic3r::Point->new($_), @{ $offsetted[0] } if @offsetted == 1; - $hole->reverse; - } - } - - my $distance = scale $self->perimeter_flow->spacing; - my @gaps = (); - - # generate perimeters inwards (loop 0 is the external one) - my $loop_number = $Slic3r::Config->perimeters + ($surface->additional_inner_perimeters || 0); - push @perimeters, [[@last_offsets]]; - for (my $loop = 1; $loop < $loop_number; $loop++) { - # offsetting a polygon can result in one or many offset polygons - my @new_offsets = (); - foreach my $expolygon (@last_offsets) { - my @offsets = map $_->offset_ex(+0.5*$distance), $expolygon->noncollapsing_offset_ex(-1.5*$distance); - push @new_offsets, @offsets; - - # where the above check collapses the expolygon, then there's no room for an inner loop - # and we can extract the gap for later processing - my $diff = diff_ex( - [ map @$_, $expolygon->offset_ex(-0.5*$distance) ], - [ map @$_, map $_->offset_ex(+0.5*$distance), @offsets ], # should these be offsetted in a single pass? - ); - push @gaps, grep $_->area >= $gap_area_threshold, @$diff; - } - @last_offsets = @new_offsets; - - last if !@last_offsets; - push @{ $perimeters[-1] }, [@last_offsets]; - } - - # create one more offset to be used as boundary for fill - { - my @fill_boundaries = map $_->offset_ex(-$distance), @last_offsets; - $_->simplify(scale &Slic3r::RESOLUTION) for @fill_boundaries; - push @{ $self->surfaces }, @fill_boundaries; - } - - # fill gaps using dynamic extrusion width - { - # detect the small gaps that we need to treat like thin polygons, - # thus generating the skeleton and using it to fill them - my $w = $self->perimeter_flow->width; - my @widths = (1.5 * $w, $w, 0.5 * $w, 0.2 * $w); - foreach my $width (@widths) { - my $scaled_width = scale $width; - - # extract the gaps having this width - my @this_width = map $_->offset_ex(+0.5*$scaled_width), map $_->noncollapsing_offset_ex(-0.5*$scaled_width), @gaps; - - # fill them - my %path_args = ( - role => EXTR_ROLE_SOLIDFILL, - flow_spacing => $self->perimeter_flow->clone(width => $width)->spacing, - ); - push @{ $self->thin_fills }, map { - $_->isa('Slic3r::Polygon') - ? (map $_->pack, Slic3r::ExtrusionLoop->new(polygon => $_, %path_args)->split_at_first_point) # we should keep these as loops - : Slic3r::ExtrusionPath->pack(polyline => $_, %path_args), - } map $_->medial_axis($scaled_width), @this_width; - - Slic3r::debugf " %d gaps filled with extrusion width = %s\n", scalar @this_width, $width - if @{ $self->thin_fills }; - - # check what's left - @gaps = @{diff_ex( - [ map @$_, @gaps ], - [ map @$_, @this_width ], - )}; - } - } - } - - # process one island (original surface) at time - foreach my $island (@perimeters) { - # do holes starting from innermost one - my @holes = (); - my %is_external = (); - my @hole_depths = map [ map $_->holes, @$_ ], @$island; - - # organize the outermost hole loops using a shortest path search - @{$hole_depths[0]} = @{shortest_path([ - map [ $_->[0], $_ ], @{$hole_depths[0]}, - ])}; - - CYCLE: while (map @$_, @hole_depths) { - shift @hole_depths while !@{$hole_depths[0]}; - - # take first available hole - push @holes, shift @{$hole_depths[0]}; - $is_external{$#holes} = 1; - - my $current_depth = 0; - while (1) { - $current_depth++; - - # look for the hole containing this one if any - next CYCLE if !$hole_depths[$current_depth]; - my $parent_hole; - for (@{$hole_depths[$current_depth]}) { - if ($_->encloses_point($holes[-1]->[0])) { - $parent_hole = $_; - last; - } - } - next CYCLE if !$parent_hole; - - # look for other holes contained in such parent - for (@{$hole_depths[$current_depth-1]}) { - if ($parent_hole->encloses_point($_->[0])) { - # we have a sibling, so let's move onto next iteration - next CYCLE; - } - } - - push @holes, $parent_hole; - @{$hole_depths[$current_depth]} = grep $_ ne $parent_hole, @{$hole_depths[$current_depth]}; - } - } - - # do holes, then contours starting from innermost one - $self->_add_perimeter($holes[$_], $is_external{$_} ? EXTR_ROLE_EXTERNAL_PERIMETER : undef) - for reverse 0 .. $#holes; - for my $depth (reverse 0 .. $#$island) { - my $role = $depth == $#$island ? EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER - : $depth == 0 ? EXTR_ROLE_EXTERNAL_PERIMETER - : EXTR_ROLE_PERIMETER; - $self->_add_perimeter($_, $role) for map $_->contour, @{$island->[$depth]}; - } - } - - # add thin walls as perimeters - { - my @thin_paths = (); - my %properties = ( - role => EXTR_ROLE_EXTERNAL_PERIMETER, - flow_spacing => $self->perimeter_flow->spacing, - ); - for (@{ $self->thin_walls }) { - push @thin_paths, $_->isa('Slic3r::Polygon') - ? Slic3r::ExtrusionLoop->pack(polygon => $_, %properties) - : Slic3r::ExtrusionPath->pack(polyline => $_, %properties); - } - my $collection = Slic3r::ExtrusionPath::Collection->new(paths => \@thin_paths); - push @{ $self->perimeters }, $collection->shortest_path; - } -} - -sub _add_perimeter { - my $self = shift; - my ($polygon, $role) = @_; - - return unless $polygon->is_printable($self->perimeter_flow->width); - push @{ $self->perimeters }, Slic3r::ExtrusionLoop->pack( - polygon => $polygon, - role => (abs($polygon->length) <= &Slic3r::SMALL_PERIMETER_LENGTH) ? EXTR_ROLE_SMALLPERIMETER : ($role // EXTR_ROLE_PERIMETER), #/ - flow_spacing => $self->perimeter_flow->spacing, - ); -} - -sub prepare_fill_surfaces { - my $self = shift; - - my @surfaces = @{$self->surfaces}; - - # if no solid layers are requested, turn top/bottom surfaces to internal - # note that this modifies $self->surfaces in place - if ($Slic3r::Config->solid_layers == 0) { - $_->surface_type(S_TYPE_INTERNAL) for grep $_->surface_type != S_TYPE_INTERNAL, @surfaces; - } - - # if hollow object is requested, remove internal 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 solid surfaces at layer %d\n", scalar(@small), $self->id if @small > 0; - } - - $self->fill_surfaces([@surfaces]); -} - -# make bridges printable -sub process_bridges { - my $self = shift; - - # no bridges are possible if we have no internal surfaces - return if $Slic3r::Config->fill_density == 0; - - my @bridges = (); - - # a bottom surface on a layer > 0 is either a bridge or a overhang - # or a combination of both; any top surface is a candidate for - # reverse bridge processing - - my @solid_surfaces = grep { - ($_->surface_type == S_TYPE_BOTTOM && $self->id > 0) || $_->surface_type == S_TYPE_TOP - } @{$self->fill_surfaces} or return; - - my @internal_surfaces = grep { $_->surface_type == S_TYPE_INTERNAL || $_->surface_type == S_TYPE_INTERNALSOLID } @{$self->slices}; - - SURFACE: foreach my $surface (@solid_surfaces) { - my $expolygon = $surface->expolygon->safety_offset; - my $description = $surface->surface_type == S_TYPE_BOTTOM ? 'bridge/overhang' : 'reverse bridge'; - - # offset the contour and intersect it with the internal surfaces to discover - # which of them has contact with our bridge - my @supporting_surfaces = (); - my ($contour_offset) = $expolygon->contour->offset(scale $self->flow->spacing * sqrt(2)); - foreach my $internal_surface (@internal_surfaces) { - my $intersection = intersection_ex([$contour_offset], [$internal_surface->p]); - if (@$intersection) { - push @supporting_surfaces, $internal_surface; - } - } - - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output(undef, "bridge_surfaces.svg", - green_polygons => [ map $_->p, @supporting_surfaces ], - red_polygons => [ @$expolygon ], - ); - } - - Slic3r::debugf "Found $description on layer %d with %d support(s)\n", - $self->id, scalar(@supporting_surfaces); - - next SURFACE unless @supporting_surfaces; - - my $bridge_angle = undef; - if ($surface->surface_type == S_TYPE_BOTTOM) { - # detect optimal bridge angle - - my $bridge_over_hole = 0; - my @edges = (); # edges are POLYLINES - foreach my $supporting_surface (@supporting_surfaces) { - my @surface_edges = map $_->clip_with_polygon($contour_offset), - ($supporting_surface->contour, $supporting_surface->holes); - - if (@supporting_surfaces == 1 && @surface_edges == 1 - && @{$supporting_surface->contour} == @{$surface_edges[0]}) { - $bridge_over_hole = 1; - } - push @edges, grep { @$_ } @surface_edges; - } - Slic3r::debugf " Bridge is supported on %d edge(s)\n", scalar(@edges); - Slic3r::debugf " and covers a hole\n" if $bridge_over_hole; - - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output(undef, "bridge_edges.svg", - polylines => [ map $_->p, @edges ], - ); - } - - if (@edges == 2) { - my @chords = map Slic3r::Line->new($_->[0], $_->[-1]), @edges; - my @midpoints = map $_->midpoint, @chords; - my $line_between_midpoints = Slic3r::Line->new(@midpoints); - $bridge_angle = rad2deg_dir($line_between_midpoints->direction); - } elsif (@edges == 1) { - # TODO: this case includes both U-shaped bridges and plain overhangs; - # we need a trapezoidation algorithm to detect the actual bridged area - # and separate it from the overhang area. - # in the mean time, we're treating as overhangs all cases where - # our supporting edge is a straight line - if (@{$edges[0]} > 2) { - my $line = Slic3r::Line->new($edges[0]->[0], $edges[0]->[-1]); - $bridge_angle = rad2deg_dir($line->direction); - } - } elsif (@edges) { - my $center = bounding_box_center([ map @$_, @edges ]); - my $x = my $y = 0; - foreach my $point (map @$, @edges) { - my $line = Slic3r::Line->new($center, $point); - my $dir = $line->direction; - my $len = $line->length; - $x += cos($dir) * $len; - $y += sin($dir) * $len; - } - $bridge_angle = rad2deg_dir(atan2($y, $x)); - } - - Slic3r::debugf " Optimal infill angle of bridge on layer %d is %d degrees\n", - $self->id, $bridge_angle if defined $bridge_angle; - } - - # now, extend our bridge by taking a portion of supporting surfaces - { - # offset the bridge by the specified amount of mm (minimum 3) - my $bridge_overlap = scale 3; - my ($bridge_offset) = $expolygon->contour->offset($bridge_overlap); - - # calculate the new bridge - my $intersection = intersection_ex( - [ @$expolygon, map $_->p, @supporting_surfaces ], - [ $bridge_offset ], - ); - - push @bridges, map Slic3r::Surface->new( - expolygon => $_, - surface_type => $surface->surface_type, - bridge_angle => $bridge_angle, - ), @$intersection; - } - } - - # now we need to merge bridges to avoid overlapping - { - # build a list of unique bridge types - my @surface_groups = Slic3r::Surface->group(@bridges); - - # merge bridges of the same type, removing any of the bridges already merged; - # the order of @surface_groups determines the priority between bridges having - # different surface_type or bridge_angle - @bridges = (); - foreach my $surfaces (@surface_groups) { - my $union = union_ex([ map $_->p, @$surfaces ]); - my $diff = diff_ex( - [ map @$_, @$union ], - [ map $_->p, @bridges ], - ); - - push @bridges, map Slic3r::Surface->new( - expolygon => $_, - surface_type => $surfaces->[0]->surface_type, - bridge_angle => $surfaces->[0]->bridge_angle, - ), @$union; - } - } - - # apply bridges to layer - { - my @surfaces = @{$self->fill_surfaces}; - @{$self->fill_surfaces} = (); - - # intersect layer surfaces with bridges to get actual bridges - foreach my $bridge (@bridges) { - my $actual_bridge = intersection_ex( - [ map $_->p, @surfaces ], - [ $bridge->p ], - ); - - push @{$self->fill_surfaces}, map Slic3r::Surface->new( - expolygon => $_, - surface_type => $bridge->surface_type, - bridge_angle => $bridge->bridge_angle, - ), @$actual_bridge; - } - - # difference between layer surfaces and bridges are the other surfaces - foreach my $group (Slic3r::Surface->group(@surfaces)) { - my $difference = diff_ex( - [ map $_->p, @$group ], - [ map $_->p, @bridges ], - ); - push @{$self->fill_surfaces}, map Slic3r::Surface->new( - expolygon => $_, - surface_type => $group->[0]->surface_type), @$difference; - } - } + $_->make_perimeters for @{$self->regions}; } 1; diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm new file mode 100644 index 000000000..a25ef993f --- /dev/null +++ b/lib/Slic3r/Layer/Region.pm @@ -0,0 +1,561 @@ +package Slic3r::Layer::Region; +use Moo; + +use Math::Clipper ':all'; +use Slic3r::ExtrusionPath ':roles'; +use Slic3r::Geometry qw(scale shortest_path); +use Slic3r::Geometry::Clipper qw(safety_offset union_ex diff_ex intersection_ex); +use Slic3r::Surface ':types'; + +has 'layer' => ( + is => 'ro', + weak_ref => 1, + required => 1, + handles => [qw(id slice_z print_z height flow)], +); +has 'region' => (is => 'ro', required => 1); +has 'perimeter_flow' => (is => 'lazy'); +has 'infill_flow' => (is => 'lazy'); + +# collection of spare segments generated by slicing the original geometry; +# these need to be merged in continuos (closed) polylines +has 'lines' => (is => 'rw', default => sub { [] }); + +# collection of surfaces generated by slicing the original geometry +has 'slices' => (is => 'rw', default => sub { [] }); + +# collection of polygons or polylines representing thin walls contained +# in the original geometry +has 'thin_walls' => (is => 'rw', default => sub { [] }); + +# collection of polygons or polylines representing thin infill regions that +# need to be filled with a medial axis +has 'thin_fills' => (is => 'rw', default => sub { [] }); + +# collection of expolygons generated by offsetting the innermost perimeter(s) +# they represent boundaries of areas to fill, typed (top/bottom/internal) +has 'surfaces' => (is => 'rw', default => sub { [] }); + +# collection of surfaces for infill generation. the difference between surfaces +# fill_surfaces is that this one honors fill_density == 0 and turns small internal +# surfaces into solid ones +has 'fill_surfaces' => (is => 'rw', default => sub { [] }); + +# ordered collection of extrusion paths/loops to build all perimeters +has 'perimeters' => (is => 'rw', default => sub { [] }); + +# ordered collection of extrusion paths to fill surfaces +has 'fills' => (is => 'rw', default => sub { [] }); + +sub _build_perimeter_flow { + my $self = shift; + return $self->id == 0 + ? $self->region->first_layer_flows->{perimeter} + : $self->region->flows->{perimeter} +} + +sub _build_infill_flow { + my $self = shift; + return $self->id == 0 + ? $self->region->first_layer_flows->{infill} + : $self->region->flows->{infill} +} + +# build polylines from lines +sub make_surfaces { + my $self = shift; + my ($loops) = @_; + + return if !@$loops; + { + my $safety_offset = scale 0.1; + # merge everything + my $expolygons = [ map $_->offset_ex(-$safety_offset), @{union_ex(safety_offset($loops, $safety_offset))} ]; + + Slic3r::debugf " %d surface(s) having %d holes detected from %d polylines\n", + scalar(@$expolygons), scalar(map $_->holes, @$expolygons), scalar(@$loops); + + $self->slices([ + map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL), + @$expolygons + ]); + } + + # the contours must be offsetted by half extrusion width inwards + { + my $distance = $self->perimeter_flow->scaled_width / 2; + my @surfaces = @{$self->slices}; + @{$self->slices} = (); + foreach my $surface (@surfaces) { + push @{$self->slices}, map Slic3r::Surface->new + (expolygon => $_, surface_type => S_TYPE_INTERNAL), + map $_->offset_ex(+$distance), + $surface->expolygon->offset_ex(-2*$distance); + } + + # now detect thin walls by re-outgrowing offsetted surfaces and subtracting + # them from the original slices + my $outgrown = Math::Clipper::offset([ map $_->p, @{$self->slices} ], $distance); + my $diff = diff_ex( + [ map $_->p, @surfaces ], + $outgrown, + 1, + ); + + $self->thin_walls([]); + if (@$diff) { + my $area_threshold = $self->perimeter_flow->scaled_spacing ** 2; + @$diff = grep $_->area > ($area_threshold), @$diff; + + @{$self->thin_walls} = map $_->medial_axis($self->perimeter_flow->scaled_width), @$diff; + + Slic3r::debugf " %d thin walls detected\n", scalar(@{$self->thin_walls}) if @{$self->thin_walls}; + } + } + + if (0) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output(undef, "surfaces.svg", + polygons => [ map $_->contour, @{$self->slices} ], + red_polygons => [ map $_->p, map @{$_->holes}, @{$self->slices} ], + ); + } +} + +sub make_perimeters { + my $self = shift; + + my $gap_area_threshold = $self->perimeter_flow->scaled_width ** 2; + + # this array will hold one arrayref per original surface (island); + # each item of this arrayref is an arrayref representing a depth (from outer + # perimeters to inner); each item of this arrayref is an ExPolygon: + # @perimeters = ( + # [ # first island + # [ Slic3r::ExPolygon, Slic3r::ExPolygon... ], #depth 0: outer loop + # [ Slic3r::ExPolygon, Slic3r::ExPolygon... ], #depth 1: inner loop + # ], + # [ # second island + # ... + # ] + # ) + my @perimeters = (); # one item per depth; each item + + # organize islands using a shortest path search + my @surfaces = @{shortest_path([ + map [ $_->contour->[0], $_ ], @{$self->slices}, + ])}; + + $self->perimeters([]); + $self->surfaces([]); + $self->thin_fills([]); + + # for each island: + foreach my $surface (@surfaces) { + my @last_offsets = ($surface->expolygon); + + # experimental hole compensation (see ArcCompensation in the RepRap wiki) + if (0) { + foreach my $hole ($last_offsets[0]->holes) { + my $circumference = abs($hole->length); + next unless $circumference <= &Slic3r::SMALL_PERIMETER_LENGTH; + # this compensation only works for circular holes, while it would + # overcompensate for hexagons and other shapes having straight edges. + # so we require a minimum number of vertices. + next unless $circumference / @$hole >= 3 * $Slic3r::flow->scaled_width; + + # revert the compensation done in make_surfaces() and get the actual radius + # of the hole + my $radius = ($circumference / PI / 2) - $self->perimeter_flow->scaled_spacing/2; + my $new_radius = ($self->perimeter_flow->scaled_width + sqrt(($self->perimeter_flow->scaled_width ** 2) + (4*($radius**2)))) / 2; + # holes are always turned to contours, so reverse point order before and after + $hole->reverse; + my @offsetted = $hole->offset(+ ($new_radius - $radius)); + # skip arc compensation when hole is not round (thus leads to multiple offsets) + @$hole = map Slic3r::Point->new($_), @{ $offsetted[0] } if @offsetted == 1; + $hole->reverse; + } + } + + my $distance = $self->perimeter_flow->scaled_spacing; + my @gaps = (); + + # generate perimeters inwards (loop 0 is the external one) + my $loop_number = $Slic3r::Config->perimeters + ($surface->additional_inner_perimeters || 0); + push @perimeters, [[@last_offsets]]; + for (my $loop = 1; $loop < $loop_number; $loop++) { + # offsetting a polygon can result in one or many offset polygons + my @new_offsets = (); + foreach my $expolygon (@last_offsets) { + my @offsets = map $_->offset_ex(+0.5*$distance), $expolygon->noncollapsing_offset_ex(-1.5*$distance); + push @new_offsets, @offsets; + + # where the above check collapses the expolygon, then there's no room for an inner loop + # and we can extract the gap for later processing + my $diff = diff_ex( + [ map @$_, $expolygon->offset_ex(-0.5*$distance) ], + [ map @$_, map $_->offset_ex(+0.5*$distance), @offsets ], # should these be offsetted in a single pass? + ); + push @gaps, grep $_->area >= $gap_area_threshold, @$diff; + } + @last_offsets = @new_offsets; + + last if !@last_offsets; + push @{ $perimeters[-1] }, [@last_offsets]; + } + + # create one more offset to be used as boundary for fill + { + my @fill_boundaries = map $_->offset_ex(-$distance), @last_offsets; + $_->simplify(scale &Slic3r::RESOLUTION) for @fill_boundaries; + push @{ $self->surfaces }, @fill_boundaries; + } + + # fill gaps using dynamic extrusion width + { + # detect the small gaps that we need to treat like thin polygons, + # thus generating the skeleton and using it to fill them + my $w = $self->perimeter_flow->width; + my @widths = (1.5 * $w, $w, 0.5 * $w, 0.2 * $w); + foreach my $width (@widths) { + my $scaled_width = scale $width; + + # extract the gaps having this width + my @this_width = map $_->offset_ex(+0.5*$scaled_width), map $_->noncollapsing_offset_ex(-0.5*$scaled_width), @gaps; + + # fill them + my %path_args = ( + role => EXTR_ROLE_SOLIDFILL, + flow_spacing => $self->perimeter_flow->clone(width => $width)->spacing, + ); + push @{ $self->thin_fills }, map { + $_->isa('Slic3r::Polygon') + ? (map $_->pack, Slic3r::ExtrusionLoop->new(polygon => $_, %path_args)->split_at_first_point) # we should keep these as loops + : Slic3r::ExtrusionPath->pack(polyline => $_, %path_args), + } map $_->medial_axis($scaled_width), @this_width; + + Slic3r::debugf " %d gaps filled with extrusion width = %s\n", scalar @this_width, $width + if @{ $self->thin_fills }; + + # check what's left + @gaps = @{diff_ex( + [ map @$_, @gaps ], + [ map @$_, @this_width ], + )}; + } + } + } + + # process one island (original surface) at time + foreach my $island (@perimeters) { + # do holes starting from innermost one + my @holes = (); + my %is_external = (); + my @hole_depths = map [ map $_->holes, @$_ ], @$island; + + # organize the outermost hole loops using a shortest path search + @{$hole_depths[0]} = @{shortest_path([ + map [ $_->[0], $_ ], @{$hole_depths[0]}, + ])}; + + CYCLE: while (map @$_, @hole_depths) { + shift @hole_depths while !@{$hole_depths[0]}; + + # take first available hole + push @holes, shift @{$hole_depths[0]}; + $is_external{$#holes} = 1; + + my $current_depth = 0; + while (1) { + $current_depth++; + + # look for the hole containing this one if any + next CYCLE if !$hole_depths[$current_depth]; + my $parent_hole; + for (@{$hole_depths[$current_depth]}) { + if ($_->encloses_point($holes[-1]->[0])) { + $parent_hole = $_; + last; + } + } + next CYCLE if !$parent_hole; + + # look for other holes contained in such parent + for (@{$hole_depths[$current_depth-1]}) { + if ($parent_hole->encloses_point($_->[0])) { + # we have a sibling, so let's move onto next iteration + next CYCLE; + } + } + + push @holes, $parent_hole; + @{$hole_depths[$current_depth]} = grep $_ ne $parent_hole, @{$hole_depths[$current_depth]}; + } + } + + # do holes, then contours starting from innermost one + $self->_add_perimeter($holes[$_], $is_external{$_} ? EXTR_ROLE_EXTERNAL_PERIMETER : undef) + for reverse 0 .. $#holes; + for my $depth (reverse 0 .. $#$island) { + my $role = $depth == $#$island ? EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER + : $depth == 0 ? EXTR_ROLE_EXTERNAL_PERIMETER + : EXTR_ROLE_PERIMETER; + $self->_add_perimeter($_, $role) for map $_->contour, @{$island->[$depth]}; + } + } + + # add thin walls as perimeters + { + my @thin_paths = (); + my %properties = ( + role => EXTR_ROLE_EXTERNAL_PERIMETER, + flow_spacing => $self->perimeter_flow->spacing, + ); + for (@{ $self->thin_walls }) { + push @thin_paths, $_->isa('Slic3r::Polygon') + ? Slic3r::ExtrusionLoop->pack(polygon => $_, %properties) + : Slic3r::ExtrusionPath->pack(polyline => $_, %properties); + } + my $collection = Slic3r::ExtrusionPath::Collection->new(paths => \@thin_paths); + push @{ $self->perimeters }, $collection->shortest_path; + } +} + +sub _add_perimeter { + my $self = shift; + my ($polygon, $role) = @_; + + return unless $polygon->is_printable($self->perimeter_flow->width); + push @{ $self->perimeters }, Slic3r::ExtrusionLoop->pack( + polygon => $polygon, + role => (abs($polygon->length) <= &Slic3r::SMALL_PERIMETER_LENGTH) ? EXTR_ROLE_SMALLPERIMETER : ($role // EXTR_ROLE_PERIMETER), #/ + flow_spacing => $self->perimeter_flow->spacing, + ); +} + +sub prepare_fill_surfaces { + my $self = shift; + + my @surfaces = @{$self->surfaces}; + + # if no solid layers are requested, turn top/bottom surfaces to internal + # note that this modifies $self->surfaces in place + if ($Slic3r::Config->solid_layers == 0) { + $_->surface_type(S_TYPE_INTERNAL) for grep $_->surface_type != S_TYPE_INTERNAL, @surfaces; + } + + # if hollow object is requested, remove internal 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 = $self->infill_flow->scaled_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 solid surfaces at layer %d\n", scalar(@small), $self->id if @small > 0; + } + + $self->fill_surfaces([@surfaces]); +} + +# make bridges printable +sub process_bridges { + my $self = shift; + + # no bridges are possible if we have no internal surfaces + return if $Slic3r::Config->fill_density == 0; + + my @bridges = (); + + # a bottom surface on a layer > 0 is either a bridge or a overhang + # or a combination of both; any top surface is a candidate for + # reverse bridge processing + + my @solid_surfaces = grep { + ($_->surface_type == S_TYPE_BOTTOM && $self->id > 0) || $_->surface_type == S_TYPE_TOP + } @{$self->fill_surfaces} or return; + + my @internal_surfaces = grep { $_->surface_type == S_TYPE_INTERNAL || $_->surface_type == S_TYPE_INTERNALSOLID } @{$self->slices}; + + SURFACE: foreach my $surface (@solid_surfaces) { + my $expolygon = $surface->expolygon->safety_offset; + my $description = $surface->surface_type == S_TYPE_BOTTOM ? 'bridge/overhang' : 'reverse bridge'; + + # offset the contour and intersect it with the internal surfaces to discover + # which of them has contact with our bridge + my @supporting_surfaces = (); + my ($contour_offset) = $expolygon->contour->offset(scale $self->flow->spacing * sqrt(2)); + foreach my $internal_surface (@internal_surfaces) { + my $intersection = intersection_ex([$contour_offset], [$internal_surface->p]); + if (@$intersection) { + push @supporting_surfaces, $internal_surface; + } + } + + if (0) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output(undef, "bridge_surfaces.svg", + green_polygons => [ map $_->p, @supporting_surfaces ], + red_polygons => [ @$expolygon ], + ); + } + + Slic3r::debugf "Found $description on layer %d with %d support(s)\n", + $self->id, scalar(@supporting_surfaces); + + next SURFACE unless @supporting_surfaces; + + my $bridge_angle = undef; + if ($surface->surface_type == S_TYPE_BOTTOM) { + # detect optimal bridge angle + + my $bridge_over_hole = 0; + my @edges = (); # edges are POLYLINES + foreach my $supporting_surface (@supporting_surfaces) { + my @surface_edges = map $_->clip_with_polygon($contour_offset), + ($supporting_surface->contour, $supporting_surface->holes); + + if (@supporting_surfaces == 1 && @surface_edges == 1 + && @{$supporting_surface->contour} == @{$surface_edges[0]}) { + $bridge_over_hole = 1; + } + push @edges, grep { @$_ } @surface_edges; + } + Slic3r::debugf " Bridge is supported on %d edge(s)\n", scalar(@edges); + Slic3r::debugf " and covers a hole\n" if $bridge_over_hole; + + if (0) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output(undef, "bridge_edges.svg", + polylines => [ map $_->p, @edges ], + ); + } + + if (@edges == 2) { + my @chords = map Slic3r::Line->new($_->[0], $_->[-1]), @edges; + my @midpoints = map $_->midpoint, @chords; + my $line_between_midpoints = Slic3r::Line->new(@midpoints); + $bridge_angle = Slic3r::Geometry::rad2deg_dir($line_between_midpoints->direction); + } elsif (@edges == 1) { + # TODO: this case includes both U-shaped bridges and plain overhangs; + # we need a trapezoidation algorithm to detect the actual bridged area + # and separate it from the overhang area. + # in the mean time, we're treating as overhangs all cases where + # our supporting edge is a straight line + if (@{$edges[0]} > 2) { + my $line = Slic3r::Line->new($edges[0]->[0], $edges[0]->[-1]); + $bridge_angle = Slic3r::Geometry::rad2deg_dir($line->direction); + } + } elsif (@edges) { + my $center = Slic3r::Geometry::bounding_box_center([ map @$_, @edges ]); + my $x = my $y = 0; + foreach my $point (map @$, @edges) { + my $line = Slic3r::Line->new($center, $point); + my $dir = $line->direction; + my $len = $line->length; + $x += cos($dir) * $len; + $y += sin($dir) * $len; + } + $bridge_angle = Slic3r::Geometry::rad2deg_dir(atan2($y, $x)); + } + + Slic3r::debugf " Optimal infill angle of bridge on layer %d is %d degrees\n", + $self->id, $bridge_angle if defined $bridge_angle; + } + + # now, extend our bridge by taking a portion of supporting surfaces + { + # offset the bridge by the specified amount of mm (minimum 3) + my $bridge_overlap = scale 3; + my ($bridge_offset) = $expolygon->contour->offset($bridge_overlap); + + # calculate the new bridge + my $intersection = intersection_ex( + [ @$expolygon, map $_->p, @supporting_surfaces ], + [ $bridge_offset ], + ); + + push @bridges, map Slic3r::Surface->new( + expolygon => $_, + surface_type => $surface->surface_type, + bridge_angle => $bridge_angle, + ), @$intersection; + } + } + + # now we need to merge bridges to avoid overlapping + { + # build a list of unique bridge types + my @surface_groups = Slic3r::Surface->group(@bridges); + + # merge bridges of the same type, removing any of the bridges already merged; + # the order of @surface_groups determines the priority between bridges having + # different surface_type or bridge_angle + @bridges = (); + foreach my $surfaces (@surface_groups) { + my $union = union_ex([ map $_->p, @$surfaces ]); + my $diff = diff_ex( + [ map @$_, @$union ], + [ map $_->p, @bridges ], + ); + + push @bridges, map Slic3r::Surface->new( + expolygon => $_, + surface_type => $surfaces->[0]->surface_type, + bridge_angle => $surfaces->[0]->bridge_angle, + ), @$union; + } + } + + # apply bridges to layer + { + my @surfaces = @{$self->fill_surfaces}; + @{$self->fill_surfaces} = (); + + # intersect layer surfaces with bridges to get actual bridges + foreach my $bridge (@bridges) { + my $actual_bridge = intersection_ex( + [ map $_->p, @surfaces ], + [ $bridge->p ], + ); + + push @{$self->fill_surfaces}, map Slic3r::Surface->new( + expolygon => $_, + surface_type => $bridge->surface_type, + bridge_angle => $bridge->bridge_angle, + ), @$actual_bridge; + } + + # difference between layer surfaces and bridges are the other surfaces + foreach my $group (Slic3r::Surface->group(@surfaces)) { + my $difference = diff_ex( + [ map $_->p, @$group ], + [ map $_->p, @bridges ], + ); + push @{$self->fill_surfaces}, map Slic3r::Surface->new( + expolygon => $_, + surface_type => $group->[0]->surface_type), @$difference; + } + } +} + +1; diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index 2d7a33155..1e2a6b2ba 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -15,6 +15,7 @@ sub read_from_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"; + $_->input_file($input_file) for @{$model->objects}; return $model; } @@ -26,6 +27,16 @@ sub add_object { return $object; } +sub set_material { + my $self = shift; + my ($material_id, $attributes) = @_; + + return $self->materials->{$material_id} = Slic3r::Model::Region->new( + model => $self, + attributes => $attributes || {}, + ); +} + # flattens everything to a single mesh sub mesh { my $self = shift; @@ -47,7 +58,7 @@ sub mesh { return Slic3r::TriangleMesh->merge(@meshes); } -package Slic3r::Model::Material; +package Slic3r::Model::Region; use Moo; has 'model' => (is => 'ro', weak_ref => 1, required => 1); @@ -66,8 +77,20 @@ has 'instances' => (is => 'rw'); sub add_volume { my $self = shift; + my %args = @_; - my $volume = Slic3r::Model::Volume->new(object => $self, @_); + if (my $vertices = delete $args{vertices}) { + my $v_offset = @{$self->vertices}; + push @{$self->vertices}, @$vertices; + + @{$args{facets}} = map { + my $f = [@$_]; + $f->[$_] += $v_offset for -3..-1; + $f; + } @{$args{facets}}; + } + + my $volume = Slic3r::Model::Volume->new(object => $self, %args); push @{$self->volumes}, $volume; return $volume; } @@ -83,11 +106,6 @@ sub add_instance { sub mesh { my $self = shift; - my $vertices = []; - my $facets = []; - - - return Slic3r::TriangleMesh->new( vertices => $self->vertices, facets => [ map @{$_->facets}, @{$self->volumes} ], diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm index 474b55d94..a870cd37b 100644 --- a/lib/Slic3r/Polyline.pm +++ b/lib/Slic3r/Polyline.pm @@ -88,7 +88,7 @@ sub length { # this only applies to polylines sub grow { my $self = shift; - return Slic3r::Polygon->new(@$self, CORE::reverse @$self[1..-2])->offset(@_); + return Slic3r::Polygon->new(@$self, CORE::reverse @$self[1..($#$self-1)])->offset(@_); } sub nearest_point_to { diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 8accf1dd9..9231fa9cc 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -6,16 +6,19 @@ use File::Spec; use List::Util qw(max); 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 nearest_point); +use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN PI scale unscale move_points nearest_point); use Slic3r::Geometry::Clipper qw(diff_ex union_ex intersection_ex offset JT_ROUND JT_SQUARE); 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'); -has 'processing_time' => (is => 'rw', required => 0); +has 'processing_time' => (is => 'rw'); +has 'extruders' => (is => 'rw', default => sub {[]}); +has 'regions' => (is => 'rw', default => sub {[]}); +has 'support_material_flow' => (is => 'rw'); +has 'first_layer_support_material_flow' => (is => 'rw'); # ordered collection of extrusion paths to build skirt loops has 'skirt' => ( @@ -52,91 +55,77 @@ sub _trigger_config { $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)) { - $Slic3r::extruders->[$t] ||= Slic3r::Extruder->new( - map { $_ => $self->config->get($_)->[$t] // $self->config->get($_)->[0] } #/ - @{&Slic3r::Extruder::OPTIONS} - ); - } - - # calculate flow - $Slic3r::flow = $Slic3r::extruders->[0]->make_flow(width => $self->config->extrusion_width); - if ($self->config->first_layer_extrusion_width) { - $Slic3r::first_layer_flow = $Slic3r::extruders->[0]->make_flow( - layer_height => $self->config->get_value('first_layer_height'), - width => $self->config->first_layer_extrusion_width, - ); - } - for (qw(perimeter infill support_material)) { - no strict 'refs'; - ${"Slic3r::${_}_flow"} = $Slic3r::extruders->[ $self->config->get("${_}_extruder")-1 ] - ->make_flow(width => $self->config->get("${_}_extrusion_width") || $self->config->extrusion_width); - } - - Slic3r::debugf "Default flow width = %s (spacing = %s)\n", - $Slic3r::flow->width, $Slic3r::flow->spacing; - # 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'; } -sub add_objects_from_file { - my $self = shift; - my ($input_file) = @_; - - my $model = Slic3r::Model->read_from_file($input_file); - - 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; - - 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, input_file => $object->input_file); - - if ($object->instances) { - # replace the default [0,0] instance with the custom ones - @{$self->copies->[-1]} = map [ scale $_->offset->[X], scale $_->offset->[Y] ], @{$object->instances}; + # append/merge materials and preserve a mapping between the original material ID + # and our numeric material index + my %materials = (); + { + my @material_ids = sort keys %{$model->materials}; + @material_ids = (0) if !@material_ids; + for (my $i = $self->regions_count; $i < @material_ids; $i++) { + push @{$self->regions}, Slic3r::Print::Region->new; + $materials{$material_ids[$i]} = $#{$self->regions}; } } - return @print_objects; -} - -sub add_object_from_mesh { - my $self = shift; - my ($mesh, %attributes) = @_; - - $mesh->rotate($Slic3r::Config->rotate); - $mesh->scale($Slic3r::Config->scale / &Slic3r::SCALING_FACTOR); - $mesh->align_to_origin; - - # initialize print object - my $object = Slic3r::Print::Object->new( - mesh => $mesh, - size => [ $mesh->size ], - %attributes, - ); - - push @{$self->objects}, $object; - push @{$self->copies}, [[0, 0]]; - return $object; + foreach my $object (@{ $model->objects }) { + 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; + $meshes[$region_id] = $meshes[$region_id] + ? Slic3r::TriangleMesh->merge($meshes[$region_id], $mesh) + : $mesh; + } + + foreach my $mesh (@meshes) { + 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); + } + + $mesh->rotate($Slic3r::Config->rotate); + $mesh->scale($Slic3r::Config->scale / &Slic3r::SCALING_FACTOR); + } + + my $complete_mesh = Slic3r::TriangleMesh->merge(grep defined $_, @meshes); + + # initialize print object + my $print_object = Slic3r::Print::Object->new( + print => $self, + meshes => [ @meshes ], + size => [ $complete_mesh->size ], + input_file => $object->input_file + ); + 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}; + } + } } sub validate { @@ -149,11 +138,11 @@ sub validate { for my $obj_idx (0 .. $#{$self->objects}) { my $clearance; { - my @points = map [ @$_[X,Y] ], @{$self->objects->[$obj_idx]->mesh->vertices}; + my @points = map [ @$_[X,Y] ], map @{$_->vertices}, @{$self->objects->[$obj_idx]->meshes}; my $convex_hull = Slic3r::Polygon->new(convex_hull(\@points)); $clearance = +($convex_hull->offset(scale $Slic3r::Config->extruder_clearance_radius / 2, 1, JT_ROUND))[0]; } - for my $copy (@{$self->copies->[$obj_idx]}) { + for my $copy (@{$self->objects->[$obj_idx]->copies}) { my $copy_clearance = $clearance->clone; $copy_clearance->translate(@$copy); if (@{ intersection_ex(\@a, [$copy_clearance]) }) { @@ -168,30 +157,79 @@ sub validate { { my @obj_copies = $self->object_copies; pop @obj_copies; # ignore the last copy: its height doesn't matter - if (grep { +($self->objects->[$_->[0]]->mesh->size)[Z] > scale $Slic3r::Config->extruder_clearance_height } @obj_copies) { + my $scaled_clearance = scale $Slic3r::Config->extruder_clearance_height; + if (grep { +($_->size)[Z] > $scaled_clearance } map @{$self->objects->[$_->[0]]->meshes}, @obj_copies) { die "Some objects are too tall and cannot be printed without extruder collisions.\n"; } } } } +sub init_extruders { + my $self = shift; + + # map regions to extruders (ghetto mapping for now) + my %extruder_mapping = map { $_ => $_ } 0..$#{$self->regions}; + + # initialize all extruder(s) we need + my @used_extruders = ( + 0, + (map $self->config->get("${_}_extruder")-1, qw(perimeter infill support_material)), + (values %extruder_mapping), + ); + for my $extruder_id (keys %{{ map {$_ => 1} @used_extruders }}) { + $self->extruders->[$extruder_id] = Slic3r::Extruder->new( + id => $extruder_id, + map { $_ => $self->config->get($_)->[$extruder_id] // $self->config->get($_)->[0] } #/ + @{&Slic3r::Extruder::OPTIONS} + ); + } + + # calculate regions' flows + $Slic3r::flow = $self->extruders->[0]->make_flow(width => $self->config->extrusion_width); + for my $region_id (0 .. $#{$self->regions}) { + my $region = $self->regions->[$region_id]; + + # per-role extruders and flows + for (qw(perimeter infill)) { + $region->extruders->{$_} = ($self->regions_count > 1) + ? $self->extruders->[$extruder_mapping{$region_id}] + : $self->extruders->[$self->config->get("${_}_extruder")-1]; + $region->flows->{$_} = $region->extruders->{$_}->make_flow( + width => $self->config->get("${_}_extrusion_width") || $self->config->extrusion_width, + ); + $region->first_layer_flows->{$_} = $region->extruders->{$_}->make_flow( + layer_height => $self->config->get_value('first_layer_height'), + width => $self->config->first_layer_extrusion_width, + ); + } + } + + # calculate support material flow + if ($self->config->support_material) { + my $extruder = $self->extruders->[$self->config->support_material_extruder-1]; + $self->support_material_flow($extruder->make_flow( + width => $self->config->support_material_extrusion_width || $self->config->extrusion_width, + )); + $self->first_layer_support_material_flow($extruder->make_flow( + layer_height => $self->config->get_value('first_layer_height'), + width => $self->config->first_layer_extrusion_width, + )); + } + + Slic3r::debugf "Default flow width = %s (spacing = %s)\n", + $Slic3r::flow->width, $Slic3r::flow->spacing; +} + sub object_copies { my $self = shift; my @oc = (); for my $obj_idx (0 .. $#{$self->objects}) { - push @oc, map [ $obj_idx, $_ ], @{$self->copies->[$obj_idx]}; + push @oc, map [ $obj_idx, $_ ], @{$self->objects->[$obj_idx]->copies}; } return @oc; } -sub cleanup { - my $self = shift; - $_->cleanup for @{$self->objects}; - @{$self->skirt} = (); - $self->total_extrusion_length(0); - $self->processing_time(0); -} - sub layer_count { my $self = shift; my $count = 0; @@ -201,6 +239,11 @@ sub layer_count { return $count; } +sub regions_count { + my $self = shift; + return scalar @{$self->regions}; +} + sub duplicate { my $self = shift; @@ -212,18 +255,18 @@ sub duplicate { # generate offsets for copies my $dist = scale $Slic3r::Config->duplicate_distance; - @{$self->copies->[0]} = (); + @{$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->copies->[0]}, [ + 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 $copies (@{$self->copies}) { - @$copies = map [0,0], 1..$Slic3r::Config->duplicate; + foreach my $object (@{$self->objects}) { + @{$object->copies} = map [0,0], 1..$Slic3r::Config->duplicate; } $self->arrange_objects; } @@ -232,16 +275,14 @@ sub duplicate { sub arrange_objects { my $self = shift; - my $total_parts = scalar map @$_, @{$self->copies}; + 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); - for my $obj_idx (0..$#{$self->objects}) { - @{$self->copies->[$obj_idx]} = splice @positions, 0, scalar @{$self->copies->[$obj_idx]}; - } + @{$_->copies} = splice @positions, 0, scalar @{$_->copies} for @{$self->objects}; } sub bounding_box { @@ -250,7 +291,7 @@ sub bounding_box { my @points = (); foreach my $obj_idx (0 .. $#{$self->objects}) { my $object = $self->objects->[$obj_idx]; - foreach my $copy (@{$self->copies->[$obj_idx]}) { + foreach my $copy (@{$self->objects->[$obj_idx]->copies}) { push @points, [ $copy->[X], $copy->[Y] ], [ $copy->[X] + $object->size->[X], $copy->[Y] ], @@ -272,12 +313,12 @@ sub export_gcode { my $self = shift; my %params = @_; + $self->init_extruders; my $status_cb = $params{status_cb} || sub {}; my $t0 = [gettimeofday]; # skein the STL into layers # each layer has surfaces with holes - $status_cb->(5, "Processing input file"); $status_cb->(10, "Processing triangulated mesh"); $_->slice(keep_meshes => $params{keep_meshes}) for @{$self->objects}; @@ -287,9 +328,12 @@ sub export_gcode { $status_cb->(20, "Generating perimeters"); $_->make_perimeters for @{$self->objects}; - # simplify slices, we only need the max resolution for perimeters - $_->simplify(scale &Slic3r::RESOLUTION) - for map @{$_->expolygon}, map @{$_->slices}, map @{$_->layers}, @{$self->objects}; + # simplify slices (both layer and region slices), + # we only need the max resolution for perimeters + foreach my $layer (map @{$_->layers}, @{$self->objects}) { + $_->simplify(scale &Slic3r::RESOLUTION) + for @{$layer->slices}, (map $_->expolygon, map @{$_->slices}, @{$layer->regions}); + } # this will clip $layer->surfaces to the infill boundaries # and split them in top/bottom/internal surfaces; @@ -298,12 +342,12 @@ sub export_gcode { # decide what surfaces are to be filled $status_cb->(35, "Preparing infill surfaces"); - $_->prepare_fill_surfaces for map @{$_->layers}, @{$self->objects}; + $_->prepare_fill_surfaces for map @{$_->regions}, map @{$_->layers}, @{$self->objects}; # this will detect bridges and reverse bridges # and rearrange top/bottom/internal surfaces $status_cb->(45, "Detect bridges"); - $_->process_bridges for map @{$_->layers}, @{$self->objects}; + $_->process_bridges for map @{$_->regions}, map @{$_->layers}, @{$self->objects}; # detect which fill surfaces are near external layers # they will be split in internal and internal-solid surfaces @@ -311,7 +355,7 @@ sub export_gcode { $_->discover_horizontal_shells for @{$self->objects}; # free memory - $_->surfaces(undef) for map @{$_->layers}, @{$self->objects}; + $_->surfaces(undef) for map @{$_->regions}, map @{$_->layers}, @{$self->objects}; # combine fill surfaces to honor the "infill every N layers" option $status_cb->(70, "Combining infill"); @@ -321,35 +365,45 @@ sub export_gcode { $status_cb->(80, "Infilling layers"); { my $fill_maker = Slic3r::Fill->new('print' => $self); - - my @items = (); # [obj_idx, layer_id] - foreach my $obj_idx (0 .. $#{$self->objects}) { - push @items, map [$obj_idx, $_], 0..$#{$self->objects->[$obj_idx]->layers}; - } Slic3r::parallelize( - items => [@items], + items => sub { + my @items = (); # [obj_idx, layer_id] + for my $obj_idx (0 .. $#{$self->objects}) { + for my $region_id (0 .. ($self->regions_count-1)) { + push @items, map [$obj_idx, $_, $region_id], 0..($self->objects->[$obj_idx]->layer_count-1); + } + } + @items; + }, thread_cb => sub { my $q = shift; $Slic3r::Geometry::Clipper::clipper = Math::Clipper->new; my $fills = {}; while (defined (my $obj_layer = $q->dequeue)) { - my ($obj_idx, $layer_id) = @$obj_layer; + my ($obj_idx, $layer_id, $region_id) = @$obj_layer; $fills->{$obj_idx} ||= {}; - $fills->{$obj_idx}{$layer_id} = [ $fill_maker->make_fill($self->objects->[$obj_idx]->layers->[$layer_id]) ]; + $fills->{$obj_idx}{$layer_id} ||= {}; + $fills->{$obj_idx}{$layer_id}{$region_id} = [ + $fill_maker->make_fill($self->objects->[$obj_idx]->layers->[$layer_id]->regions->[$region_id]), + ]; } return $fills; }, collect_cb => sub { my $fills = shift; foreach my $obj_idx (keys %$fills) { + my $object = $self->objects->[$obj_idx]; foreach my $layer_id (keys %{$fills->{$obj_idx}}) { - $self->objects->[$obj_idx]->layers->[$layer_id]->fills($fills->{$obj_idx}{$layer_id}); + my $layer = $object->layers->[$layer_id]; + foreach my $region_id (keys %{$fills->{$obj_idx}{$layer_id}}) { + $layer->regions->[$region_id]->fills($fills->{$obj_idx}{$layer_id}{$region_id}); + } } } }, no_threads_cb => sub { - foreach my $layer (map @{$_->layers}, @{$self->objects}) { - $layer->fills([ $fill_maker->make_fill($layer) ]); + foreach my $layerm (map @{$_->regions}, map @{$_->layers}, @{$self->objects}) { + $layerm->fills([ $fill_maker->make_fill($layerm) ]); } }, ); @@ -358,11 +412,11 @@ sub export_gcode { # generate support material if ($Slic3r::Config->support_material) { $status_cb->(85, "Generating support material"); - $_->generate_support_material(print => $self) for @{$self->objects}; + $_->generate_support_material for @{$self->objects}; } # free memory (note that support material needs fill_surfaces) - $_->fill_surfaces(undef) for map @{$_->layers}, @{$self->objects}; + $_->fill_surfaces(undef) for map @{$_->regions}, map @{$_->layers}, @{$self->objects}; # make skirt $status_cb->(88, "Generating skirt"); @@ -399,6 +453,10 @@ sub export_svg { my $self = shift; my %params = @_; + # this shouldn't be needed, but we're currently relying on ->make_surfaces() which + # calls ->perimeter_flow + $self->init_extruders; + $_->slice(keep_meshes => $params{keep_meshes}) for @{$self->objects}; $self->arrange_objects; @@ -435,10 +493,10 @@ EOF my $layer = $self->objects->[$obj_idx]->layers->[$layer_id] or next; # sort slices so that the outermost ones come first - my @slices = sort { $a->expolygon->contour->encloses_point($b->expolygon->contour->[0]) ? 0 : 1 } @{$layer->slices}; - foreach my $copy (@{$self->copies->[$obj_idx]}) { + my @slices = sort { $a->contour->encloses_point($b->contour->[0]) ? 0 : 1 } @{$layer->slices}; + foreach my $copy (@{$self->objects->[$obj_idx]->copies}) { foreach my $slice (@slices) { - my $expolygon = $slice->expolygon->clone; + my $expolygon = $slice->clone; $expolygon->translate(@$copy); $print_polygon->($expolygon->contour, 'contour'); $print_polygon->($_, 'hole') for $expolygon->holes; @@ -462,8 +520,9 @@ EOF foreach my $expolygon (@unsupported_slices) { # look for the nearest point to this island among all # supported points - my $support_point = nearest_point($expolygon->contour->[0], \@supported_points); - my $anchor_point = nearest_point($support_point, $expolygon->contour->[0]); + my $support_point = nearest_point($expolygon->contour->[0], \@supported_points) + or next; + my $anchor_point = nearest_point($support_point, $expolygon->contour); printf $fh qq{ \n}, map @$_, $support_point, $anchor_point; } @@ -488,11 +547,11 @@ sub make_skirt { foreach my $obj_idx (0 .. $#{$self->objects}) { my @layers = map $self->objects->[$obj_idx]->layer($_), 0..($skirt_height-1); my @layer_points = ( - (map @$_, map @{$_->expolygon}, map @{$_->slices}, @layers), - (map @$_, map @{$_->thin_walls}, @layers), + (map @$_, map @$_, map @{$_->slices}, @layers), + (map @$_, map @{$_->thin_walls}, map @{$_->regions}, @layers), (map @{$_->unpack->polyline}, map @{$_->support_fills->paths}, grep $_->support_fills, @layers), ); - push @points, map move_points($_, @layer_points), @{$self->copies->[$obj_idx]}; + push @points, map move_points($_, @layer_points), @{$self->objects->[$obj_idx]->copies}; } return if @points < 3; # at least three points required for a convex hull @@ -518,16 +577,16 @@ sub make_brim { return unless $Slic3r::Config->brim_width > 0; my $flow = $Slic3r::first_layer_flow || $Slic3r::flow; - my $grow_distance = scale $flow->width / 2; + my $grow_distance = $flow->scaled_width / 2; my @islands = (); # array of polygons foreach my $obj_idx (0 .. $#{$self->objects}) { my $layer0 = $self->objects->[$obj_idx]->layers->[0]; my @object_islands = ( (map $_->contour, @{$layer0->slices}), - (map { $_->isa('Slic3r::Polygon') ? $_ : $_->grow($grow_distance) } @{$layer0->thin_walls}), + (map { $_->isa('Slic3r::Polygon') ? $_ : $_->grow($grow_distance) } map @{$_->thin_walls}, @{$layer0->regions}), (map $_->unpack->polyline->grow($grow_distance), map @{$_->support_fills->paths}, grep $_->support_fills, $layer0), ); - foreach my $copy (@{$self->copies->[$obj_idx]}) { + foreach my $copy (@{$self->objects->[$obj_idx]->copies}) { push @islands, map $_->clone->translate(@$copy), @object_islands; } } @@ -538,7 +597,8 @@ sub make_brim { push @{$self->brim}, Slic3r::ExtrusionLoop->pack( polygon => Slic3r::Polygon->new($_), role => EXTR_ROLE_SKIRT, - ) for @{Math::Clipper::offset(\@islands, $i * scale $flow->spacing, 100, JT_SQUARE)}; + ) for @{Math::Clipper::offset(\@islands, $i * $flow->scaled_spacing, 100, JT_SQUARE)}; + # TODO: we need the offset inwards/offset outwards logic to avoid overlapping extrusions } } @@ -570,26 +630,28 @@ sub write_gcode { print $fh "\n"; # set up our extruder object - my $gcodegen = Slic3r::GCode->new; + my $gcodegen = Slic3r::GCode->new( + multiple_extruders => (@{$self->extruders} > 1), + ); my $min_print_speed = 60 * $Slic3r::Config->min_print_speed; my $dec = $gcodegen->dec; - print $fh $gcodegen->set_tool(0); + print $fh $gcodegen->set_extruder($self->extruders->[0]); print $fh $gcodegen->set_fan(0, 1) if $Slic3r::Config->cooling && $Slic3r::Config->disable_fan_first_layers; # 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; my $print_first_layer_temperature = sub { - for my $t (grep $Slic3r::extruders->[$_], 0 .. $#{$Slic3r::Config->first_layer_temperature}) { - printf $fh $gcodegen->set_temperature($Slic3r::extruders->[$t]->first_layer_temperature, 0, $t) - if $Slic3r::extruders->[$t]->first_layer_temperature; + for my $t (grep $self->extruders->[$_], 0 .. $#{$Slic3r::Config->first_layer_temperature}) { + printf $fh $gcodegen->set_temperature($self->extruders->[$t]->first_layer_temperature, 0, $t) + if $self->extruders->[$t]->first_layer_temperature; } }; $print_first_layer_temperature->(); printf $fh "%s\n", $Slic3r::Config->replace_options($Slic3r::Config->start_gcode); - for my $t (grep $Slic3r::extruders->[$_], 0 .. $#{$Slic3r::Config->first_layer_temperature}) { - printf $fh $gcodegen->set_temperature($Slic3r::extruders->[$t]->first_layer_temperature, 1, $t) - if $Slic3r::extruders->[$t]->first_layer_temperature && $Slic3r::Config->start_gcode !~ /M109/i; + for my $t (grep $self->extruders->[$_], 0 .. $#{$Slic3r::Config->first_layer_temperature}) { + printf $fh $gcodegen->set_temperature($self->extruders->[$t]->first_layer_temperature, 1, $t) + if $self->extruders->[$t]->first_layer_temperature && $Slic3r::Config->start_gcode !~ /M109/i; } print $fh "G90 ; use absolute coordinates\n"; print $fh "G21 ; set units to millimeters\n"; @@ -619,9 +681,9 @@ sub write_gcode { my $gcode = ""; if ($layer_id == 1) { - for my $t (grep $Slic3r::extruders->[$_], 0 .. $#{$Slic3r::Config->temperature}) { - $gcode .= $gcodegen->set_temperature($Slic3r::extruders->[$t]->temperature, 0, $t) - if $Slic3r::extruders->[$t]->temperature && $Slic3r::extruders->[$t]->temperature != $Slic3r::extruders->[$t]->first_layer_temperature; + for my $t (grep $self->extruders->[$_], 0 .. $#{$Slic3r::Config->temperature}) { + $gcode .= $gcodegen->set_temperature($self->extruders->[$t]->temperature, 0, $t) + if $self->extruders->[$t]->temperature && $self->extruders->[$t]->temperature != $self->extruders->[$t]->first_layer_temperature; } $gcode .= $gcodegen->set_bed_temperature($Slic3r::Config->bed_temperature) if $Slic3r::Config->first_layer_bed_temperature && $Slic3r::Config->bed_temperature != $Slic3r::Config->first_layer_bed_temperature; @@ -645,7 +707,7 @@ sub write_gcode { # extrude brim if ($layer_id == 0 && !$brim_done) { - $gcode .= $gcodegen->set_tool($Slic3r::Config->support_material_extruder-1); + $gcode .= $gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]); $gcodegen->shift_x($shift[X]); $gcodegen->shift_y($shift[Y]); $gcode .= $gcodegen->extrude_loop($_, 'brim') for @{$self->brim}; @@ -663,25 +725,30 @@ sub write_gcode { $gcodegen->shift_x($shift[X] + unscale $copy->[X]); $gcodegen->shift_y($shift[Y] + unscale $copy->[Y]); - # extrude perimeters - $gcode .= $gcodegen->set_tool($Slic3r::Config->perimeter_extruder-1); - $gcode .= $gcodegen->extrude($_, 'perimeter') for @{ $layer->perimeters }; - - # extrude fills - $gcode .= $gcodegen->set_tool($Slic3r::Config->infill_extruder-1); - $gcode .= $gcodegen->set_acceleration($Slic3r::Config->infill_acceleration); - for my $fill (@{ $layer->fills }) { - if ($fill->isa('Slic3r::ExtrusionPath::Collection')) { - $gcode .= $gcodegen->extrude($_, 'fill') - for $fill->shortest_path($gcodegen->last_pos); - } else { - $gcode .= $gcodegen->extrude($fill, 'fill') ; + foreach my $region_id (0 .. ($self->regions_count-1)) { + my $layerm = $layer->regions->[$region_id]; + my $region = $self->regions->[$region_id]; + + # extrude perimeters + $gcode .= $gcodegen->set_extruder($region->extruders->{perimeter}); + $gcode .= $gcodegen->extrude($_, 'perimeter') for @{ $layerm->perimeters }; + + # extrude fills + $gcode .= $gcodegen->set_extruder($region->extruders->{infill}); + $gcode .= $gcodegen->set_acceleration($Slic3r::Config->infill_acceleration); + for my $fill (@{ $layerm->fills }) { + if ($fill->isa('Slic3r::ExtrusionPath::Collection')) { + $gcode .= $gcodegen->extrude($_, 'fill') + for $fill->shortest_path($gcodegen->last_pos); + } else { + $gcode .= $gcodegen->extrude($fill, 'fill') ; + } } } - + # extrude support material if ($layer->support_fills) { - $gcode .= $gcodegen->set_tool($Slic3r::Config->support_material_extruder-1); + $gcode .= $gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]); $gcode .= $gcodegen->extrude_path($_, 'support material') for $layer->support_fills->shortest_path($gcodegen->last_pos); } @@ -733,7 +800,7 @@ sub write_gcode { my $finished_objects = 0; for my $obj_idx (@obj_idx) { - for my $copy (@{ $self->copies->[$obj_idx] }) { + for my $copy (@{ $self->objects->[$obj_idx]->copies }) { # move to the origin position for the copy we're going to print. # this happens before Z goes down to layer 0 again, so that # no collision happens hopefully. @@ -762,7 +829,7 @@ sub write_gcode { for my $layer_id (0..$self->layer_count-1) { my @object_copies = (); for my $obj_idx (grep $self->objects->[$_]->layers->[$layer_id], 0..$#{$self->objects}) { - push @object_copies, map [ $obj_idx, $_ ], @{ $self->copies->[$obj_idx] }; + push @object_copies, map [ $obj_idx, $_ ], @{ $self->objects->[$obj_idx]->copies }; } print $fh $extrude_layer->($layer_id, \@object_copies); } @@ -786,7 +853,7 @@ sub write_gcode { sub total_extrusion_volume { my $self = shift; - return $self->total_extrusion_length * ($Slic3r::extruders->[0]->filament_diameter**2) * PI/4 / 1000; + return $self->total_extrusion_length * ($self->extruders->[0]->filament_diameter**2) * PI/4 / 1000; } # this method will return the supplied input file path after expanding its diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 696d72ca3..ccb9723ff 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -6,16 +6,12 @@ use Slic3r::Geometry qw(scale unscale deg2rad); use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex union_ex); use Slic3r::Surface ':types'; +has 'print' => (is => 'ro', weak_ref => 1, required => 1); has 'input_file' => (is => 'rw', required => 0); -has 'mesh' => (is => 'rw', required => 0); +has 'meshes' => (is => 'rw', default => sub { [] }); # by region_id has 'size' => (is => 'rw', required => 1); - -has 'layers' => ( - traits => ['Array'], - is => 'rw', - #isa => 'ArrayRef[Slic3r::Layer]', - default => sub { [] }, -); +has 'copies' => (is => 'rw', default => sub {[ [0,0] ]}); +has 'layers' => (is => 'rw', default => sub { [] }); sub layer_count { my $self = shift; @@ -27,11 +23,8 @@ sub layer { my ($layer_id) = @_; # extend our print by creating all necessary layers - - if ($self->layer_count < $layer_id + 1) { - for (my $i = $self->layer_count; $i <= $layer_id; $i++) { - push @{ $self->layers }, Slic3r::Layer->new(id => $i); - } + for (my $i = $self->layer_count; $i <= $layer_id; $i++) { + push @{ $self->layers }, Slic3r::Layer->new(id => $i, object => $self); } return $self->layers->[$layer_id]; @@ -42,22 +35,24 @@ sub slice { my %params = @_; # process facets - { + for my $region_id (0 .. $#{$self->meshes}) { + my $mesh = $self->meshes->[$region_id]; # ignore undef meshes + my $apply_lines = sub { my $lines = shift; foreach my $layer_id (keys %$lines) { - my $layer = $self->layer($layer_id); - push @{$layer->lines}, @{$lines->{$layer_id}}; + my $layerm = $self->layer($layer_id)->region($region_id); + push @{$layerm->lines}, @{$lines->{$layer_id}}; } }; Slic3r::parallelize( - disable => ($#{$self->mesh->facets} < 500), # don't parallelize when too few facets - items => [ 0..$#{$self->mesh->facets} ], + disable => ($#{$mesh->facets} < 500), # don't parallelize when too few facets + items => [ 0..$#{$mesh->facets} ], thread_cb => sub { my $q = shift; my $result_lines = {}; while (defined (my $facet_id = $q->dequeue)) { - my $lines = $self->mesh->slice_facet($self, $facet_id); + my $lines = $mesh->slice_facet($self, $facet_id); foreach my $layer_id (keys %$lines) { $result_lines->{$layer_id} ||= []; push @{ $result_lines->{$layer_id} }, @{ $lines->{$layer_id} }; @@ -69,8 +64,8 @@ sub slice { $apply_lines->($_[0]); }, no_threads_cb => sub { - for (0..$#{$self->mesh->facets}) { - my $lines = $self->mesh->slice_facet($self, $_); + for (0..$#{$mesh->facets}) { + my $lines = $mesh->slice_facet($self, $_); $apply_lines->($lines); } }, @@ -79,13 +74,16 @@ sub slice { die "Invalid input file\n" if !@{$self->layers}; # free memory - $self->mesh(undef) unless $params{keep_meshes}; + $self->meshes(undef) unless $params{keep_meshes}; # remove last layer if empty - # (we might have created it because of the $max_layer = ... + 1 code below) - pop @{$self->layers} if !@{$self->layers->[-1]->lines}; + # (we might have created it because of the $max_layer = ... + 1 code in TriangleMesh) + pop @{$self->layers} if !map @{$_->lines}, @{$self->layers->[-1]->regions}; foreach my $layer (@{ $self->layers }) { + # make sure all layers contain layer region objects for all regions + $layer->region($_) for 0 .. ($self->print->regions_count-1); + Slic3r::debugf "Making surfaces for layer %d (slice z = %f):\n", $layer->id, unscale $layer->slice_z if $Slic3r::debug; @@ -97,10 +95,17 @@ sub slice { # inside a closed polyline) # build surfaces from sparse lines - $layer->make_surfaces(Slic3r::TriangleMesh::make_loops($layer)); + foreach my $layerm (@{$layer->regions}) { + my ($slicing_errors, $loops) = Slic3r::TriangleMesh::make_loops($layerm->lines); + $layer->slicing_errors(1) if $slicing_errors; + $layerm->make_surfaces($loops); + + # free memory + $layerm->lines(undef); + } - # free memory - $layer->lines(undef); + # merge all regions' slices to get islands + $layer->make_slices; } # detect slicing errors @@ -118,35 +123,42 @@ sub slice { # neighbor layers Slic3r::debugf "Attempting to repair layer %d\n", $i; - my (@upper_surfaces, @lower_surfaces); - for (my $j = $i+1; $j <= $#{$self->layers}; $j++) { - if (!$self->layers->[$j]->slicing_errors) { - @upper_surfaces = @{$self->layers->[$j]->slices}; - last; + foreach my $region_id (0 .. $#{$layer->regions}) { + my $layerm = $layer->region($region_id); + + my (@upper_surfaces, @lower_surfaces); + for (my $j = $i+1; $j <= $#{$self->layers}; $j++) { + if (!$self->layers->[$j]->slicing_errors) { + @upper_surfaces = @{$self->layers->[$j]->region($region_id)->slices}; + last; + } } - } - for (my $j = $i-1; $j >= 0; $j--) { - if (!$self->layers->[$j]->slicing_errors) { - @lower_surfaces = @{$self->layers->[$j]->slices}; - last; + for (my $j = $i-1; $j >= 0; $j--) { + if (!$self->layers->[$j]->slicing_errors) { + @lower_surfaces = @{$self->layers->[$j]->region($region_id)->slices}; + last; + } } + + my $union = union_ex([ + map $_->expolygon->contour, @upper_surfaces, @lower_surfaces, + ]); + my $diff = diff_ex( + [ map @$_, @$union ], + [ map $_->expolygon->holes, @upper_surfaces, @lower_surfaces, ], + ); + + @{$layerm->slices} = map Slic3r::Surface->new + (expolygon => $_, surface_type => S_TYPE_INTERNAL), + @$diff; } - - my $union = union_ex([ - map $_->expolygon->contour, @upper_surfaces, @lower_surfaces, - ]); - my $diff = diff_ex( - [ map @$_, @$union ], - [ map $_->expolygon->holes, @upper_surfaces, @lower_surfaces, ], - ); - - @{$layer->slices} = map Slic3r::Surface->new - (expolygon => $_, surface_type => S_TYPE_INTERNAL), - @$diff; + + # update layer slices after repairing the single regions + $layer->make_slices; } # remove empty layers from bottom - while (@{$self->layers} && !@{$self->layers->[0]->slices} && !@{$self->layers->[0]->thin_walls}) { + while (@{$self->layers} && !@{$self->layers->[0]->slices} && !map @{$_->thin_walls}, @{$self->layers->[0]->regions}) { shift @{$self->layers}; for (my $i = 0; $i <= $#{$self->layers}; $i++) { $self->layers->[$i]->id($i); @@ -157,11 +169,6 @@ sub slice { if !@{$self->layers}; } -sub cleanup { - my $self = shift; - @{$self->layers} = (); -} - sub make_perimeters { my $self = shift; @@ -170,53 +177,56 @@ sub make_perimeters { # this algorithm makes sure that almost one perimeter is overlapping if ($Slic3r::Config->extra_perimeters && $Slic3r::Config->perimeters > 0) { - for my $layer_id (0 .. $self->layer_count-2) { - my $layer = $self->layers->[$layer_id]; - my $upper_layer = $self->layers->[$layer_id+1]; - - my $overlap = $layer->perimeter_flow->spacing; # one perimeter - - # compute polygons representing the thickness of the first external perimeter of - # the upper layer slices - my $upper = diff_ex( - [ map @$_, map $_->expolygon->offset_ex(+ 0.5 * scale $layer->perimeter_flow->spacing), @{$upper_layer->slices} ], - [ map @$_, map $_->expolygon->offset_ex(- scale($overlap) + (0.5 * scale $layer->perimeter_flow->spacing)), @{$upper_layer->slices} ], - ); - next if !@$upper; - - # we need to limit our detection to the areas which would actually benefit from - # more perimeters. so, let's compute the area we want to ignore - my $ignore = []; - { - my $diff = diff_ex( - [ map @$_, map $_->expolygon->offset_ex(- ($Slic3r::Config->perimeters-0.5) * scale $layer->perimeter_flow->spacing), @{$layer->slices} ], - [ map @{$_->expolygon}, @{$upper_layer->slices} ], + for my $region_id (0 .. ($self->print->regions_count-1)) { + for my $layer_id (0 .. $self->layer_count-2) { + my $layerm = $self->layers->[$layer_id]->regions->[$region_id]; + my $upper_layerm = $self->layers->[$layer_id+1]->regions->[$region_id]; + my $perimeter_flow = $layerm->perimeter_flow; + + my $overlap = $perimeter_flow->spacing; # one perimeter + + # compute polygons representing the thickness of the first external perimeter of + # the upper layer slices + my $upper = diff_ex( + [ map @$_, map $_->expolygon->offset_ex(+ 0.5 * $perimeter_flow->scaled_spacing), @{$upper_layerm->slices} ], + [ map @$_, map $_->expolygon->offset_ex(- scale($overlap) + (0.5 * $perimeter_flow->scaled_spacing)), @{$upper_layerm->slices} ], ); - $ignore = [ map @$_, map $_->offset_ex(scale $layer->perimeter_flow->spacing), @$diff ]; - } - - foreach my $slice (@{$layer->slices}) { - my $hypothetical_perimeter_num = $Slic3r::Config->perimeters + 1; - CYCLE: while (1) { - # compute polygons representing the thickness of the hypotetical new internal perimeter - # of our slice - my $hypothetical_perimeter; - { - my $outer = [ map @$_, $slice->expolygon->offset_ex(- ($hypothetical_perimeter_num-1.5) * scale $layer->perimeter_flow->spacing) ]; - last CYCLE if !@$outer; - my $inner = [ map @$_, $slice->expolygon->offset_ex(- ($hypothetical_perimeter_num-0.5) * scale $layer->perimeter_flow->spacing) ]; - last CYCLE if !@$inner; - $hypothetical_perimeter = diff_ex($outer, $inner); + next if !@$upper; + + # we need to limit our detection to the areas which would actually benefit from + # more perimeters. so, let's compute the area we want to ignore + my $ignore = []; + { + my $diff = diff_ex( + [ map @$_, map $_->expolygon->offset_ex(- ($Slic3r::Config->perimeters-0.5) * $perimeter_flow->scaled_spacing), @{$layerm->slices} ], + [ map @{$_->expolygon}, @{$upper_layerm->slices} ], + ); + $ignore = [ map @$_, map $_->offset_ex($perimeter_flow->scaled_spacing), @$diff ]; + } + + foreach my $slice (@{$layerm->slices}) { + my $hypothetical_perimeter_num = $Slic3r::Config->perimeters + 1; + CYCLE: while (1) { + # compute polygons representing the thickness of the hypotetical new internal perimeter + # of our slice + my $hypothetical_perimeter; + { + my $outer = [ map @$_, $slice->expolygon->offset_ex(- ($hypothetical_perimeter_num-1.5) * $perimeter_flow->scaled_spacing) ]; + last CYCLE if !@$outer; + my $inner = [ map @$_, $slice->expolygon->offset_ex(- ($hypothetical_perimeter_num-0.5) * $perimeter_flow->scaled_spacing) ]; + last CYCLE if !@$inner; + $hypothetical_perimeter = diff_ex($outer, $inner); + } + last CYCLE if !@$hypothetical_perimeter; + + + my $intersection = intersection_ex([ map @$_, @$upper ], [ map @$_, @$hypothetical_perimeter ]); + $intersection = diff_ex([ map @$_, @$intersection ], $ignore) if @$ignore; + last CYCLE if !@{ $intersection }; + Slic3r::debugf " adding one more perimeter at layer %d\n", $layer_id; + $slice->additional_inner_perimeters(($slice->additional_inner_perimeters || 0) + 1); + $hypothetical_perimeter_num++; } - last CYCLE if !@$hypothetical_perimeter; - - - my $intersection = intersection_ex([ map @$_, @$upper ], [ map @$_, @$hypothetical_perimeter ]); - $intersection = diff_ex([ map @$_, @$intersection ], $ignore) if @$ignore; - last CYCLE if !@{ $intersection }; - Slic3r::debugf " adding one more perimeter at layer %d\n", $layer_id; - $slice->additional_inner_perimeters(($slice->additional_inner_perimeters || 0) + 1); - $hypothetical_perimeter_num++; } } } @@ -231,75 +241,80 @@ sub detect_surfaces_type { # prepare a reusable subroutine to make surface differences my $surface_difference = sub { - my ($subject_surfaces, $clip_surfaces, $result_type, $layer) = @_; + my ($subject_surfaces, $clip_surfaces, $result_type, $layerm) = @_; my $expolygons = diff_ex( [ map { ref $_ eq 'ARRAY' ? $_ : ref $_ eq 'Slic3r::ExPolygon' ? @$_ : $_->p } @$subject_surfaces ], [ map { ref $_ eq 'ARRAY' ? $_ : ref $_ eq 'Slic3r::ExPolygon' ? @$_ : $_->p } @$clip_surfaces ], 1, ); - return grep $_->contour->is_printable($layer->flow->width), + return grep $_->contour->is_printable($layerm->flow->width), map Slic3r::Surface->new(expolygon => $_, surface_type => $result_type), @$expolygons; }; - for (my $i = 0; $i < $self->layer_count; $i++) { - my $layer = $self->layers->[$i]; - my $upper_layer = $self->layers->[$i+1]; - my $lower_layer = $i > 0 ? $self->layers->[$i-1] : undef; - - my (@bottom, @top, @internal) = (); - - # find top surfaces (difference between current surfaces - # of current layer and upper one) - if ($upper_layer) { - @top = $surface_difference->($layer->slices, $upper_layer->slices, S_TYPE_TOP, $layer); - } else { - # if no upper layer, all surfaces of this one are solid - @top = @{$layer->slices}; - $_->surface_type(S_TYPE_TOP) for @top; + for my $region_id (0 .. ($self->print->regions_count-1)) { + for (my $i = 0; $i < $self->layer_count; $i++) { + my $layerm = $self->layers->[$i]->regions->[$region_id]; + + # comparison happens against the *full* slices (considering all regions) + my $upper_layer = $self->layers->[$i+1]; + my $lower_layer = $i > 0 ? $self->layers->[$i-1] : undef; + + my (@bottom, @top, @internal) = (); + + # find top surfaces (difference between current surfaces + # of current layer and upper one) + if ($upper_layer) { + @top = $surface_difference->($layerm->slices, $upper_layer->slices, S_TYPE_TOP, $layerm); + } else { + # if no upper layer, all surfaces of this one are solid + @top = @{$layerm->slices}; + $_->surface_type(S_TYPE_TOP) for @top; + } + + # find bottom surfaces (difference between current surfaces + # of current layer and lower one) + if ($lower_layer) { + @bottom = $surface_difference->($layerm->slices, $lower_layer->slices, S_TYPE_BOTTOM, $layerm); + } else { + # if no lower layer, all surfaces of this one are solid + @bottom = @{$layerm->slices}; + $_->surface_type(S_TYPE_BOTTOM) for @bottom; + } + + # now, if the object contained a thin membrane, we could have overlapping bottom + # and top surfaces; let's do an intersection to discover them and consider them + # as bottom surfaces (to allow for bridge detection) + if (@top && @bottom) { + my $overlapping = intersection_ex([ map $_->p, @top ], [ map $_->p, @bottom ]); + Slic3r::debugf " layer %d contains %d membrane(s)\n", $layerm->id, scalar(@$overlapping); + @top = $surface_difference->([@top], $overlapping, S_TYPE_TOP, $layerm); + } + + # find internal surfaces (difference between top/bottom surfaces and others) + @internal = $surface_difference->($layerm->slices, [@top, @bottom], S_TYPE_INTERNAL, $layerm); + + # save surfaces to layer + @{$layerm->slices} = (@bottom, @top, @internal); + + Slic3r::debugf " layer %d has %d bottom, %d top and %d internal surfaces\n", + $layerm->id, scalar(@bottom), scalar(@top), scalar(@internal); } - # find bottom surfaces (difference between current surfaces - # of current layer and lower one) - if ($lower_layer) { - @bottom = $surface_difference->($layer->slices, $lower_layer->slices, S_TYPE_BOTTOM, $layer); - } else { - # if no lower layer, all surfaces of this one are solid - @bottom = @{$layer->slices}; - $_->surface_type(S_TYPE_BOTTOM) for @bottom; - } - - # now, if the object contained a thin membrane, we could have overlapping bottom - # and top surfaces; let's do an intersection to discover them and consider them - # as bottom surfaces (to allow for bridge detection) - if (@top && @bottom) { - my $overlapping = intersection_ex([ map $_->p, @top ], [ map $_->p, @bottom ]); - Slic3r::debugf " layer %d contains %d membrane(s)\n", $layer->id, scalar(@$overlapping); - @top = $surface_difference->([@top], $overlapping, S_TYPE_TOP, $layer); - } - - # find internal surfaces (difference between top/bottom surfaces and others) - @internal = $surface_difference->($layer->slices, [@top, @bottom], S_TYPE_INTERNAL, $layer); - - # save surfaces to layer - @{$layer->slices} = (@bottom, @top, @internal); - - Slic3r::debugf " layer %d has %d bottom, %d top and %d internal surfaces\n", - $layer->id, scalar(@bottom), scalar(@top), scalar(@internal); - } - - # clip surfaces to the fill boundaries - foreach my $layer (@{$self->layers}) { - my $fill_boundaries = [ map @$_, @{$layer->surfaces} ]; - @{$layer->surfaces} = (); - foreach my $surface (@{$layer->slices}) { - my $intersection = intersection_ex( - [ $surface->p ], - $fill_boundaries, - ); - push @{$layer->surfaces}, map Slic3r::Surface->new - (expolygon => $_, surface_type => $surface->surface_type), - @$intersection; + # clip surfaces to the fill boundaries + foreach my $layer (@{$self->layers}) { + my $layerm = $layer->regions->[$region_id]; + my $fill_boundaries = [ map @$_, @{$layerm->surfaces} ]; + @{$layerm->surfaces} = (); + foreach my $surface (@{$layerm->slices}) { + my $intersection = intersection_ex( + [ $surface->p ], + $fill_boundaries, + ); + push @{$layerm->surfaces}, map Slic3r::Surface->new + (expolygon => $_, surface_type => $surface->surface_type), + @$intersection; + } } } } @@ -309,84 +324,92 @@ sub discover_horizontal_shells { Slic3r::debugf "==> DISCOVERING HORIZONTAL SHELLS\n"; - my $area_threshold = scale($Slic3r::flow->spacing) ** 2; + my $area_threshold = $Slic3r::flow->scaled_spacing ** 2; - for (my $i = 0; $i < $self->layer_count; $i++) { - my $layer = $self->layers->[$i]; - foreach my $type (S_TYPE_TOP, S_TYPE_BOTTOM) { - # find surfaces of current type for current layer - # and offset them to take perimeters into account - my @surfaces = map $_->offset($Slic3r::Config->perimeters * scale $layer->perimeter_flow->width), - grep $_->surface_type == $type, @{$layer->fill_surfaces} or next; - my $surfaces_p = [ map $_->p, @surfaces ]; - Slic3r::debugf "Layer %d has %d surfaces of type '%s'\n", - $i, scalar(@surfaces), ($type == S_TYPE_TOP ? 'top' : 'bottom'); + for my $region_id (0 .. ($self->print->regions_count-1)) { + for (my $i = 0; $i < $self->layer_count; $i++) { + my $layerm = $self->layers->[$i]->regions->[$region_id]; - for (my $n = $type == S_TYPE_TOP ? $i-1 : $i+1; - abs($n - $i) <= $Slic3r::Config->solid_layers-1; - $type == S_TYPE_TOP ? $n-- : $n++) { + if ($Slic3r::Config->solid_infill_every_layers && ($i % $Slic3r::Config->solid_infill_every_layers) == 0) { + $_->surface_type(S_TYPE_INTERNALSOLID) + for grep $_->surface_type == S_TYPE_INTERNAL, @{$layerm->fill_surfaces}; + } + + foreach my $type (S_TYPE_TOP, S_TYPE_BOTTOM) { + # find surfaces of current type for current layer + # and offset them to take perimeters into account + my @surfaces = map $_->offset($Slic3r::Config->perimeters * $layerm->perimeter_flow->scaled_width), + grep $_->surface_type == $type, @{$layerm->fill_surfaces} or next; + my $surfaces_p = [ map $_->p, @surfaces ]; + Slic3r::debugf "Layer %d has %d surfaces of type '%s'\n", + $i, scalar(@surfaces), ($type == S_TYPE_TOP ? 'top' : 'bottom'); - next if $n < 0 || $n >= $self->layer_count; - Slic3r::debugf " looking for neighbors on layer %d...\n", $n; - - my @neighbor_surfaces = @{$self->layers->[$n]->surfaces}; - my @neighbor_fill_surfaces = @{$self->layers->[$n]->fill_surfaces}; - - # find intersection between neighbor and current layer's surfaces - # intersections have contours and holes - my $new_internal_solid = intersection_ex( - $surfaces_p, - [ map $_->p, grep { $_->surface_type == S_TYPE_INTERNAL || $_->surface_type == S_TYPE_INTERNALSOLID } @neighbor_surfaces ], - undef, 1, - ); - next if !@$new_internal_solid; - - # internal-solid are the union of the existing internal-solid surfaces - # and new ones - my $internal_solid = union_ex([ - ( map $_->p, grep $_->surface_type == S_TYPE_INTERNALSOLID, @neighbor_fill_surfaces ), - ( map @$_, @$new_internal_solid ), - ]); - - # subtract intersections from layer surfaces to get resulting inner surfaces - my $internal = diff_ex( - [ map $_->p, grep $_->surface_type == S_TYPE_INTERNAL, @neighbor_fill_surfaces ], - [ map @$_, @$internal_solid ], - 1, - ); - Slic3r::debugf " %d internal-solid and %d internal surfaces found\n", - scalar(@$internal_solid), scalar(@$internal); - - # Note: due to floating point math we're going to get some very small - # polygons as $internal; they will be removed by removed_small_features() - - # assign resulting inner surfaces to layer - my $neighbor_fill_surfaces = $self->layers->[$n]->fill_surfaces; - @$neighbor_fill_surfaces = (); - push @$neighbor_fill_surfaces, Slic3r::Surface->new - (expolygon => $_, surface_type => S_TYPE_INTERNAL) - for @$internal; - - # assign new internal-solid surfaces to layer - push @$neighbor_fill_surfaces, Slic3r::Surface->new - (expolygon => $_, surface_type => S_TYPE_INTERNALSOLID) - for @$internal_solid; - - # assign top and bottom surfaces to layer - foreach my $s (Slic3r::Surface->group(grep { $_->surface_type == S_TYPE_TOP || $_->surface_type == S_TYPE_BOTTOM } @neighbor_fill_surfaces)) { - my $solid_surfaces = diff_ex( - [ map $_->p, @$s ], - [ map @$_, @$internal_solid, @$internal ], + for (my $n = $type == S_TYPE_TOP ? $i-1 : $i+1; + abs($n - $i) <= $Slic3r::Config->solid_layers-1; + $type == S_TYPE_TOP ? $n-- : $n++) { + + next if $n < 0 || $n >= $self->layer_count; + Slic3r::debugf " looking for neighbors on layer %d...\n", $n; + + my @neighbor_surfaces = @{$self->layers->[$n]->regions->[$region_id]->surfaces}; + my @neighbor_fill_surfaces = @{$self->layers->[$n]->regions->[$region_id]->fill_surfaces}; + + # find intersection between neighbor and current layer's surfaces + # intersections have contours and holes + my $new_internal_solid = intersection_ex( + $surfaces_p, + [ map $_->p, grep { $_->surface_type == S_TYPE_INTERNAL || $_->surface_type == S_TYPE_INTERNALSOLID } @neighbor_surfaces ], + undef, 1, + ); + next if !@$new_internal_solid; + + # internal-solid are the union of the existing internal-solid surfaces + # and new ones + my $internal_solid = union_ex([ + ( map $_->p, grep $_->surface_type == S_TYPE_INTERNALSOLID, @neighbor_fill_surfaces ), + ( map @$_, @$new_internal_solid ), + ]); + + # subtract intersections from layer surfaces to get resulting inner surfaces + my $internal = diff_ex( + [ map $_->p, grep $_->surface_type == S_TYPE_INTERNAL, @neighbor_fill_surfaces ], + [ map @$_, @$internal_solid ], 1, ); + Slic3r::debugf " %d internal-solid and %d internal surfaces found\n", + scalar(@$internal_solid), scalar(@$internal); + + # Note: due to floating point math we're going to get some very small + # polygons as $internal; they will be removed by removed_small_features() + + # assign resulting inner surfaces to layer + my $neighbor_fill_surfaces = $self->layers->[$n]->regions->[$region_id]->fill_surfaces; + @$neighbor_fill_surfaces = (); push @$neighbor_fill_surfaces, Slic3r::Surface->new - (expolygon => $_, surface_type => $s->[0]->surface_type, bridge_angle => $s->[0]->bridge_angle) - for @$solid_surfaces; + (expolygon => $_, surface_type => S_TYPE_INTERNAL) + for @$internal; + + # assign new internal-solid surfaces to layer + push @$neighbor_fill_surfaces, Slic3r::Surface->new + (expolygon => $_, surface_type => S_TYPE_INTERNALSOLID) + for @$internal_solid; + + # assign top and bottom surfaces to layer + foreach my $s (Slic3r::Surface->group(grep { $_->surface_type == S_TYPE_TOP || $_->surface_type == S_TYPE_BOTTOM } @neighbor_fill_surfaces)) { + my $solid_surfaces = diff_ex( + [ map $_->p, @$s ], + [ map @$_, @$internal_solid, @$internal ], + 1, + ); + push @$neighbor_fill_surfaces, Slic3r::Surface->new + (expolygon => $_, surface_type => $s->[0]->surface_type, bridge_angle => $s->[0]->bridge_angle) + for @$solid_surfaces; + } } } + + @{$layerm->fill_surfaces} = grep $_->expolygon->area > $area_threshold, @{$layerm->fill_surfaces}; } - - @{$layer->fill_surfaces} = grep $_->expolygon->area > $area_threshold, @{$layer->fill_surfaces}; } } @@ -395,99 +418,99 @@ sub combine_infill { my $self = shift; return unless $Slic3r::Config->infill_every_layers > 1 && $Slic3r::Config->fill_density > 0; - my $area_threshold = scale($Slic3r::flow->spacing) ** 2; + my $area_threshold = $Slic3r::flow->scaled_spacing ** 2; - # start from bottom, skip first layer - for (my $i = 1; $i < $self->layer_count; $i++) { - my $layer = $self->layer($i); - - # skip layer if no internal fill surfaces - next if !grep $_->surface_type == S_TYPE_INTERNAL, @{$layer->fill_surfaces}; - - # for each possible depth, look for intersections with the lower layer - # we do this from the greater depth to the smaller - for (my $d = $Slic3r::Config->infill_every_layers - 1; $d >= 1; $d--) { - next if ($i - $d) < 0; - my $lower_layer = $self->layer($i - 1); + for my $region_id (0 .. ($self->print->regions_count-1)) { + # start from bottom, skip first layer + for (my $i = 1; $i < $self->layer_count; $i++) { + my $layerm = $self->layers->[$i]->regions->[$region_id]; - # select surfaces of the lower layer having the depth we're looking for - my @lower_surfaces = grep $_->depth_layers == $d && $_->surface_type == S_TYPE_INTERNAL, - @{$lower_layer->fill_surfaces}; - next if !@lower_surfaces; + # skip layer if no internal fill surfaces + next if !grep $_->surface_type == S_TYPE_INTERNAL, @{$layerm->fill_surfaces}; - # calculate intersection between our surfaces and theirs - my $intersection = intersection_ex( - [ map $_->p, grep $_->depth_layers <= $d, @lower_surfaces ], - [ map $_->p, grep $_->surface_type == S_TYPE_INTERNAL, @{$layer->fill_surfaces} ], - undef, 1, - ); - - # purge intersections, skip tiny regions - @$intersection = grep $_->area > $area_threshold, @$intersection; - next if !@$intersection; - - # new fill surfaces of the current layer are: - # - any non-internal surface - # - intersections found (with a $d + 1 depth) - # - any internal surface not belonging to the intersection (with its original depth) - { - my @new_surfaces = (); - push @new_surfaces, grep $_->surface_type != S_TYPE_INTERNAL, @{$layer->fill_surfaces}; - push @new_surfaces, map Slic3r::Surface->new - (expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $d + 1), @$intersection; + # for each possible depth, look for intersections with the lower layer + # we do this from the greater depth to the smaller + for (my $d = $Slic3r::Config->infill_every_layers - 1; $d >= 1; $d--) { + next if ($i - $d) < 0; + my $lower_layerm = $self->layer($i - 1)->regions->[$region_id]; - foreach my $depth (reverse $d..$Slic3r::Config->infill_every_layers) { + # select surfaces of the lower layer having the depth we're looking for + my @lower_surfaces = grep $_->depth_layers == $d && $_->surface_type == S_TYPE_INTERNAL, + @{$lower_layerm->fill_surfaces}; + next if !@lower_surfaces; + + # calculate intersection between our surfaces and theirs + my $intersection = intersection_ex( + [ map $_->p, grep $_->depth_layers <= $d, @lower_surfaces ], + [ map $_->p, grep $_->surface_type == S_TYPE_INTERNAL, @{$layerm->fill_surfaces} ], + undef, 1, + ); + + # purge intersections, skip tiny regions + @$intersection = grep $_->area > $area_threshold, @$intersection; + next if !@$intersection; + + # new fill surfaces of the current layer are: + # - any non-internal surface + # - intersections found (with a $d + 1 depth) + # - any internal surface not belonging to the intersection (with its original depth) + { + my @new_surfaces = (); + push @new_surfaces, grep $_->surface_type != S_TYPE_INTERNAL, @{$layerm->fill_surfaces}; push @new_surfaces, map Slic3r::Surface->new - (expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $depth), - - # difference between our internal layers with depth == $depth - # and the intersection found - @{diff_ex( - [ - map $_->p, grep $_->surface_type == S_TYPE_INTERNAL && $_->depth_layers == $depth, - @{$layer->fill_surfaces}, - ], - [ map @$_, @$intersection ], - 1, - )}; + (expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $d + 1), @$intersection; + + foreach my $depth (reverse $d..$Slic3r::Config->infill_every_layers) { + push @new_surfaces, map Slic3r::Surface->new + (expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $depth), + + # difference between our internal layers with depth == $depth + # and the intersection found + @{diff_ex( + [ + map $_->p, grep $_->surface_type == S_TYPE_INTERNAL && $_->depth_layers == $depth, + @{$layerm->fill_surfaces}, + ], + [ map @$_, @$intersection ], + 1, + )}; + } + @{$layerm->fill_surfaces} = @new_surfaces; } - @{$layer->fill_surfaces} = @new_surfaces; - } - - # now we remove the intersections from lower layer - { - my @new_surfaces = (); - push @new_surfaces, grep $_->surface_type != S_TYPE_INTERNAL, @{$lower_layer->fill_surfaces}; - foreach my $depth (1..$Slic3r::Config->infill_every_layers) { - push @new_surfaces, map Slic3r::Surface->new - (expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $depth), - - # difference between internal layers with depth == $depth - # and the intersection found - @{diff_ex( - [ - map $_->p, grep $_->surface_type == S_TYPE_INTERNAL && $_->depth_layers == $depth, - @{$lower_layer->fill_surfaces}, - ], - [ map @$_, @$intersection ], - 1, - )}; + + # now we remove the intersections from lower layer + { + my @new_surfaces = (); + push @new_surfaces, grep $_->surface_type != S_TYPE_INTERNAL, @{$lower_layerm->fill_surfaces}; + foreach my $depth (1..$Slic3r::Config->infill_every_layers) { + push @new_surfaces, map Slic3r::Surface->new + (expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $depth), + + # difference between internal layers with depth == $depth + # and the intersection found + @{diff_ex( + [ + map $_->p, grep $_->surface_type == S_TYPE_INTERNAL && $_->depth_layers == $depth, + @{$lower_layerm->fill_surfaces}, + ], + [ map @$_, @$intersection ], + 1, + )}; + } + @{$lower_layerm->fill_surfaces} = @new_surfaces; } - @{$lower_layer->fill_surfaces} = @new_surfaces; } - - } } } sub generate_support_material { my $self = shift; - my %params = @_; + my $flow = $self->print->support_material_flow; my $threshold_rad = deg2rad($Slic3r::Config->support_material_threshold + 1); # +1 makes the threshold inclusive my $overhang_width = $threshold_rad == 0 ? undef : scale $Slic3r::Config->layer_height * ((cos $threshold_rad) / (sin $threshold_rad)); - my $distance_from_object = 1.5 * scale $Slic3r::support_material_flow->width; + my $distance_from_object = 1.5 * $flow->scaled_width; # determine support regions in each layer (for upper layers) Slic3r::debugf "Detecting regions\n"; @@ -504,22 +527,22 @@ sub generate_support_material { @current_support_regions = @{diff_ex( [ map @$_, @current_support_regions ], - [ map @{$_->expolygon}, @{$layer->slices} ], + [ map @$_, @{$layer->slices} ], )}; $layers{$i} = diff_ex( [ map @$_, @current_support_regions ], - [ map @$_, map $_->expolygon->offset_ex($distance_from_object), @{$layer->slices} ], + [ map @$_, map $_->offset_ex($distance_from_object), @{$layer->slices} ], ); - $_->simplify(scale $Slic3r::support_material_flow->spacing * 2) for @{$layers{$i}}; + $_->simplify($flow->scaled_spacing * 2) for @{$layers{$i}}; # step 2: get layer overhangs and put them into queue for adding support inside lower layers # we need an angle threshold for this my @overhangs = (); if ($lower_layer) { @overhangs = map $_->offset_ex(2 * $overhang_width), @{diff_ex( - [ map @$_, map $_->expolygon->offset_ex(-$overhang_width), @{$layer->slices} ], - [ map @{$_->expolygon}, @{$lower_layer->slices} ], + [ map @$_, map $_->offset_ex(-$overhang_width), @{$layer->slices} ], + [ map @$_, @{$lower_layer->slices} ], 1, )}; } @@ -533,10 +556,10 @@ sub generate_support_material { my $support_patterns = []; # in case we want cross-hatching { # 0.5 makes sure the paths don't get clipped externally when applying them to layers - my @support_material_areas = map $_->offset_ex(- 0.5 * scale $Slic3r::support_material_flow->width), + my @support_material_areas = map $_->offset_ex(- 0.5 * $flow->scaled_width), @{union_ex([ map $_->contour, map @$_, values %layers ])}; - my $fill = Slic3r::Fill->new(print => $params{print}); + my $fill = Slic3r::Fill->new(print => $self->print); my $filler = $fill->filler($Slic3r::Config->support_material_pattern); $filler->angle($Slic3r::Config->support_material_angle); { @@ -544,8 +567,8 @@ sub generate_support_material { foreach my $expolygon (@support_material_areas) { my @paths = $filler->fill_surface( Slic3r::Surface->new(expolygon => $expolygon), - density => $Slic3r::support_material_flow->spacing / $Slic3r::Config->support_material_spacing, - flow_spacing => $Slic3r::support_material_flow->spacing, + density => $flow->spacing / $Slic3r::Config->support_material_spacing, + flow_spacing => $flow->spacing, ); my $params = shift @paths; @@ -578,6 +601,11 @@ sub generate_support_material { foreach my $expolygon (@$expolygons) { push @paths, map $_->pack, + map { + $_->flow_spacing($self->print->first_layer_support_material_flow->spacing) + if $layer_id == 0; + $_; + } map $_->clip_with_expolygon($expolygon), map $_->clip_with_polygon($expolygon->bounding_box_polygon), @{$support_patterns->[ $layer_id % @$support_patterns ]}; diff --git a/lib/Slic3r/Print/Region.pm b/lib/Slic3r/Print/Region.pm new file mode 100644 index 000000000..253fde2ba --- /dev/null +++ b/lib/Slic3r/Print/Region.pm @@ -0,0 +1,8 @@ +package Slic3r::Print::Region; +use Moo; + +has 'extruders' => (is => 'rw', default => sub { {} }); # by role +has 'flows' => (is => 'rw', default => sub { {} }); # by role +has 'first_layer_flows' => (is => 'rw', default => sub { {} }); # by role + +1; diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm index 7f9d929b1..770a2a8c4 100644 --- a/lib/Slic3r/TriangleMesh.pm +++ b/lib/Slic3r/TriangleMesh.pm @@ -167,9 +167,8 @@ sub unpack_line { } sub make_loops { - my ($layer) = @_; - - my @lines = map unpack_line($_), @{$layer->lines}; + my ($lines) = @_; + my @lines = map unpack_line($_), @$lines; # remove tangent edges { @@ -258,6 +257,7 @@ sub make_loops { (0..$#lines); my (@polygons, @failed_loops, %visited_lines) = (); + my $slicing_errors = 0; CYCLE: for (my $i = 0; $i <= $#lines; $i++) { my $line = $lines[$i]; next if $visited_lines{$line}; @@ -272,24 +272,24 @@ sub make_loops { $next_line = $lines[$by_a_id{$line->[I_B_ID]}]; } else { Slic3r::debugf " line has no next_facet_index or b_id\n"; - $layer->slicing_errors(1); + $slicing_errors = 1; push @failed_loops, [@points] if @points; next CYCLE; } if (!$next_line || $visited_lines{$next_line}) { Slic3r::debugf " failed to close this loop\n"; - $layer->slicing_errors(1); + $slicing_errors = 1; push @failed_loops, [@points] if @points; next CYCLE; } elsif (defined $next_line->[I_PREV_FACET_INDEX] && $next_line->[I_PREV_FACET_INDEX] != $line->[I_FACET_INDEX]) { Slic3r::debugf " wrong prev_facet_index\n"; - $layer->slicing_errors(1); + $slicing_errors = 1; push @failed_loops, [@points] if @points; next CYCLE; } elsif (defined $next_line->[I_A_ID] && $next_line->[I_A_ID] != $line->[I_B_ID]) { Slic3r::debugf " wrong a_id\n"; - $layer->slicing_errors(1); + $slicing_errors = 1; push @failed_loops, [@points] if @points; next CYCLE; } @@ -313,7 +313,7 @@ sub make_loops { if $Slic3r::debug; } - return [@polygons]; + return ($slicing_errors, [@polygons]); } sub rotate { @@ -381,21 +381,12 @@ sub duplicate { sub extents { my $self = shift; - my @extents = (map [undef, undef], X,Y,Z); - foreach my $vertex (@{$self->vertices}) { - for (X,Y,Z) { - $extents[$_][MIN] = $vertex->[$_] if !defined $extents[$_][MIN] || $vertex->[$_] < $extents[$_][MIN]; - $extents[$_][MAX] = $vertex->[$_] if !defined $extents[$_][MAX] || $vertex->[$_] > $extents[$_][MAX]; - } - } - return @extents; + return Slic3r::Geometry::bounding_box_3D($self->vertices); } sub size { my $self = shift; - - my @extents = $self->extents; - return map $extents[$_][MAX] - $extents[$_][MIN], (X,Y,Z); + return Slic3r::Geometry::size_3D($self->vertices); } sub slice_facet { diff --git a/slic3r.pl b/slic3r.pl index e5c63b99e..bcda50571 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -88,9 +88,9 @@ if (@ARGV) { # slicing from command line while (my $input_file = shift @ARGV) { my $print = Slic3r::Print->new(config => $config); - $print->add_objects_from_file($input_file); + $print->add_model(Slic3r::Model->read_from_file($input_file)); if ($opt{merge}) { - $print->add_objects_from_file($_) for splice @ARGV, 0; + $print->add_model(Slic3r::Model->read_from_file($_)) for splice @ARGV, 0; } $print->duplicate; $print->arrange_objects if @{$print->objects} > 1; @@ -201,6 +201,8 @@ $j --first-layer-height Layer height for first layer (mm or %, default: $config->{first_layer_height}) --infill-every-layers Infill every N layers (default: $config->{infill_every_layers}) + --solid-infill-every-layers + Force a solid layer every N layers (default: $config->{solid_infill_every_layers}) Print options: --perimeters Number of perimeters/horizontal skins (range: 0+, default: $config->{perimeters}) diff --git a/t/fill.t b/t/fill.t index dd8cdf938..27b9da41d 100644 --- a/t/fill.t +++ b/t/fill.t @@ -16,7 +16,9 @@ use Slic3r::Surface qw(:types); sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } { - my $filler = Slic3r::Fill::Rectilinear->new(print => Slic3r::Print->new); + my $print = Slic3r::Print->new; + $print->init_extruders; + my $filler = Slic3r::Fill::Rectilinear->new(print => $print); my $surface_width = 250; my $distance = $filler->adjust_solid_spacing( width => $surface_width, diff --git a/utils/post-processing/decimate.pl b/utils/post-processing/decimate.pl new file mode 100755 index 000000000..9e2938c5f --- /dev/null +++ b/utils/post-processing/decimate.pl @@ -0,0 +1,53 @@ +#!/usr/bin/perl -i~ + +use strict; +use warnings; + +my %lastpos = (X => 10000, Y => 10000, Z => 10000, E => 10000, F => 10000); +my %pos = (X => 0, Y => 0, Z => 0, E => 0, F => 0); + +my $mindist = 0.33; + +my $mindistz = 0.005; + +my $mindistsq = $mindist * $mindist; + +sub dist { + my $sq = 0; + for (qw/X Y Z E/) { + $sq += ($pos{$_} - $lastpos{$_}) ** 2; + } + return $sq; +} + +while (<>) { + if (m#\bG[01]\b#) { + while (m#([XYZEF])(\d+(\.\d+)?)#gi) { + $pos{uc $1} = $2; + } + if ( + ( + /X/ && + /Y/ && + (dist() >= $mindistsq) + ) || + (abs($pos{Z} - $lastpos{Z}) > $mindistz) || + (!/X/ || !/Y/) + ) { + print; + %lastpos = %pos; + } + elsif (($pos{F} - $lastpos{F}) != 0) { + printf "G1 F%s\n", $pos{F}; + $lastpos{F} = $pos{F}; + } + } + else { + if (m#\bG92\b#) { + while (m#([XYZEF])(\d+(\.\d+)?)#gi) { + $lastpos{uc $1} = $2; + } + } + print; + } +} diff --git a/utils/post-processing/z-every-line.pl b/utils/post-processing/z-every-line.pl index 49d5f4502..aaf57e172 100755 --- a/utils/post-processing/z-every-line.pl +++ b/utils/post-processing/z-every-line.pl @@ -8,17 +8,17 @@ my $z = 0; # read stdin and any/all files passed as parameters one line at a time while (<>) { # if we find a Z word, save it - $z = $1 if /Z(\d+(\.\d+)?)/; + $z = $1 if /Z\s*(\d+(\.\d+)?)/; # if we don't have Z, but we do have X and Y if (!/Z/ && /X/ && /Y/ && $z > 0) { # chop off the end of the line (incl. comments), saving chopped section in $1 - s/\s*([\r\n\;\(].*)//s; + s/\s*([\r\n\;\(].*)/" Z$z $1"/es; # print start of line, insert our Z value then re-add the chopped end of line - print "$_ Z$z $1"; + # print "$_ Z$z $1"; } - else { + #else { # nothing interesting, print line as-is - print; - } + print or die $!; + #} } diff --git a/utils/stl-to-amf.pl b/utils/stl-to-amf.pl index 78e5989b4..e1adec7ce 100755 --- a/utils/stl-to-amf.pl +++ b/utils/stl-to-amf.pl @@ -35,19 +35,11 @@ my %opt = (); 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_model->set_material($m, { Name => basename($ARGV[$m]) }); $new_object->add_volume( - material_id => $material_id, - facets => [@new_facets], + material_id => $m, + facets => $model->objects->[0]->volumes->[0]->facets, + vertices => $model->objects->[0]->vertices, ); } } else {