mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-31 20:51:12 -06:00
Merge branch 'master' into sender
This commit is contained in:
commit
d2172b4383
47 changed files with 860 additions and 429 deletions
|
|
@ -243,7 +243,7 @@ sub validate {
|
|||
if !first { $_ eq $self->gcode_flavor } @{$Options->{gcode_flavor}{values}};
|
||||
|
||||
die "--use-firmware-retraction is only supported by Marlin firmware\n"
|
||||
if $self->use_firmware_retraction && $self->gcode_flavor ne 'reprap';
|
||||
if $self->use_firmware_retraction && $self->gcode_flavor ne 'reprap' && $self->gcode_flavor ne 'machinekit';
|
||||
|
||||
die "--use-firmware-retraction is not compatible with --wipe\n"
|
||||
if $self->use_firmware_retraction && first {$_} @{$self->wipe};
|
||||
|
|
|
|||
|
|
@ -49,9 +49,10 @@ sub make_fill {
|
|||
|
||||
Slic3r::debugf "Filling layer %d:\n", $layerm->id;
|
||||
|
||||
my $fill_density = $layerm->config->fill_density;
|
||||
my $infill_flow = $layerm->flow(FLOW_ROLE_INFILL);
|
||||
my $solid_infill_flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL);
|
||||
my $fill_density = $layerm->config->fill_density;
|
||||
my $infill_flow = $layerm->flow(FLOW_ROLE_INFILL);
|
||||
my $solid_infill_flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL);
|
||||
my $top_solid_infill_flow = $layerm->flow(FLOW_ROLE_TOP_SOLID_INFILL);
|
||||
|
||||
my @surfaces = ();
|
||||
|
||||
|
|
@ -75,7 +76,7 @@ sub make_fill {
|
|||
if ($groups[$i][0]->is_solid && (!$groups[$i][0]->is_bridge || $layerm->id == 0)) {
|
||||
$is_solid[$i] = 1;
|
||||
$fw[$i] = ($groups[$i][0]->surface_type == S_TYPE_TOP)
|
||||
? $layerm->flow(FLOW_ROLE_TOP_SOLID_INFILL)->width
|
||||
? $top_solid_infill_flow->width
|
||||
: $solid_infill_flow->width;
|
||||
$pattern[$i] = $groups[$i][0]->is_external
|
||||
? $layerm->config->external_fill_pattern
|
||||
|
|
|
|||
|
|
@ -584,11 +584,14 @@ sub wipe {
|
|||
$gcode .= $gcodegen->writer->extrude_to_xy(
|
||||
$gcodegen->point_to_gcode($line->b),
|
||||
-$dE,
|
||||
'retract' . ($gcodegen->enable_cooling_markers ? ';_WIPE' : ''),
|
||||
'wipe and retract' . ($gcodegen->enable_cooling_markers ? ';_WIPE' : ''),
|
||||
);
|
||||
$retracted += $dE;
|
||||
}
|
||||
$gcodegen->writer->extruder->set_retracted($gcodegen->writer->extruder->retracted + $retracted);
|
||||
|
||||
# prevent wiping again on same path
|
||||
$self->path(undef);
|
||||
}
|
||||
|
||||
return $gcode;
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ our $have_LWP = eval "use LWP::UserAgent; 1";
|
|||
|
||||
use Wx 0.9901 qw(:bitmap :dialog :icon :id :misc :systemsettings :toplevelwindow
|
||||
:filedialog);
|
||||
use Wx::Event qw(EVT_IDLE);
|
||||
use Wx::Event qw(EVT_IDLE EVT_COMMAND);
|
||||
use base 'Wx::App';
|
||||
|
||||
use constant FILE_WILDCARDS => {
|
||||
|
|
@ -70,6 +70,8 @@ our $medium_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
|||
$medium_font->SetPointSize(12);
|
||||
our $grey = Wx::Colour->new(200,200,200);
|
||||
|
||||
our $VERSION_CHECK_EVENT : shared = Wx::NewEventType;
|
||||
|
||||
sub OnInit {
|
||||
my ($self) = @_;
|
||||
|
||||
|
|
@ -143,6 +145,25 @@ sub OnInit {
|
|||
}
|
||||
});
|
||||
|
||||
EVT_COMMAND($self, -1, $VERSION_CHECK_EVENT, sub {
|
||||
my ($self, $event) = @_;
|
||||
my ($success, $response, $manual_check) = @{$event->GetData};
|
||||
|
||||
if ($success) {
|
||||
if ($response =~ /^obsolete ?= ?([a-z0-9.-]+,)*\Q$Slic3r::VERSION\E(?:,|$)/) {
|
||||
my $res = Wx::MessageDialog->new(undef, "A new version is available. Do you want to open the Slic3r website now?",
|
||||
'Update', wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_INFORMATION | wxICON_ERROR)->ShowModal;
|
||||
Wx::LaunchDefaultBrowser('http://slic3r.org/') if $res == wxID_YES;
|
||||
} else {
|
||||
Slic3r::GUI::show_info(undef, "You're using the latest version. No updates are available.") if $manual_check;
|
||||
}
|
||||
$Settings->{_}{last_version_check} = time();
|
||||
$self->save_settings;
|
||||
} else {
|
||||
Slic3r::GUI::show_error(undef, "Failed to check for updates. Try later.") if $manual_check;
|
||||
}
|
||||
});
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
@ -239,7 +260,7 @@ sub have_version_check {
|
|||
}
|
||||
|
||||
sub check_version {
|
||||
my ($self, %p) = @_;
|
||||
my ($self, $manual_check) = @_;
|
||||
|
||||
Slic3r::debugf "Checking for updates...\n";
|
||||
|
||||
|
|
@ -248,19 +269,9 @@ sub check_version {
|
|||
my $ua = LWP::UserAgent->new;
|
||||
$ua->timeout(10);
|
||||
my $response = $ua->get('http://slic3r.org/updatecheck');
|
||||
if ($response->is_success) {
|
||||
if ($response->decoded_content =~ /^obsolete ?= ?([a-z0-9.-]+,)*\Q$Slic3r::VERSION\E(?:,|$)/) {
|
||||
my $res = Wx::MessageDialog->new(undef, "A new version is available. Do you want to open the Slic3r website now?",
|
||||
'Update', wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_INFORMATION | wxICON_ERROR)->ShowModal;
|
||||
Wx::LaunchDefaultBrowser('http://slic3r.org/') if $res == wxID_YES;
|
||||
} else {
|
||||
Slic3r::GUI::show_info(undef, "You're using the latest version. No updates are available.") if $p{manual};
|
||||
}
|
||||
$Settings->{_}{last_version_check} = time();
|
||||
$self->save_settings;
|
||||
} else {
|
||||
Slic3r::GUI::show_error(undef, "Failed to check for updates. Try later.") if $p{manual};
|
||||
}
|
||||
Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $VERSION_CHECK_EVENT,
|
||||
threads::shared::shared_clone([ $response->is_success, $response->decoded_content, $manual_check ])));
|
||||
|
||||
Slic3r::thread_cleanup();
|
||||
})->detach;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ __PACKAGE__->mk_accessors( qw(_quat _dirty init
|
|||
enable_cutting
|
||||
enable_picking
|
||||
enable_moving
|
||||
on_viewport_changed
|
||||
on_hover
|
||||
on_select
|
||||
on_double_click
|
||||
|
|
@ -108,6 +109,7 @@ sub new {
|
|||
-($pos->y - $size->y/2) * ($zoom) / $self->_zoom,
|
||||
0,
|
||||
) if 0;
|
||||
$self->on_viewport_changed->() if $self->on_viewport_changed;
|
||||
$self->_dirty(1);
|
||||
$self->Refresh;
|
||||
});
|
||||
|
|
@ -207,6 +209,7 @@ sub mouse_event {
|
|||
);
|
||||
$self->_quat(mulquats($self->_quat, \@quat));
|
||||
}
|
||||
$self->on_viewport_changed->() if $self->on_viewport_changed;
|
||||
$self->Refresh;
|
||||
}
|
||||
$self->_drag_start_pos($pos);
|
||||
|
|
@ -220,6 +223,7 @@ sub mouse_event {
|
|||
$self->_camera_target->translate(
|
||||
@{$orig->vector_to($cur_pos)->negative},
|
||||
);
|
||||
$self->on_viewport_changed->() if $self->on_viewport_changed;
|
||||
$self->Refresh;
|
||||
}
|
||||
$self->_drag_start_xy($pos);
|
||||
|
|
@ -256,6 +260,17 @@ sub reset_objects {
|
|||
$self->_dirty(1);
|
||||
}
|
||||
|
||||
sub set_viewport_from_scene {
|
||||
my ($self, $scene) = @_;
|
||||
|
||||
$self->_sphi($scene->_sphi);
|
||||
$self->_stheta($scene->_stheta);
|
||||
$self->_camera_target($scene->_camera_target);
|
||||
$self->_zoom($scene->_zoom);
|
||||
$self->_quat($scene->_quat);
|
||||
$self->_dirty(1);
|
||||
}
|
||||
|
||||
sub zoom_to_bounding_box {
|
||||
my ($self, $bb) = @_;
|
||||
|
||||
|
|
@ -267,6 +282,8 @@ sub zoom_to_bounding_box {
|
|||
|
||||
# center view around bounding box center
|
||||
$self->_camera_target($bb->center);
|
||||
|
||||
$self->on_viewport_changed->() if $self->on_viewport_changed;
|
||||
}
|
||||
|
||||
sub zoom_to_bed {
|
||||
|
|
@ -347,25 +364,24 @@ sub set_bed_shape {
|
|||
}
|
||||
|
||||
{
|
||||
my @lines = ();
|
||||
my @polylines = ();
|
||||
for (my $x = $bed_bb->x_min; $x <= $bed_bb->x_max; $x += scale 10) {
|
||||
push @lines, Slic3r::Polyline->new([$x,$bed_bb->y_min], [$x,$bed_bb->y_max]);
|
||||
push @polylines, Slic3r::Polyline->new([$x,$bed_bb->y_min], [$x,$bed_bb->y_max]);
|
||||
}
|
||||
for (my $y = $bed_bb->y_min; $y <= $bed_bb->y_max; $y += scale 10) {
|
||||
push @lines, Slic3r::Polyline->new([$bed_bb->x_min,$y], [$bed_bb->x_max,$y]);
|
||||
push @polylines, Slic3r::Polyline->new([$bed_bb->x_min,$y], [$bed_bb->x_max,$y]);
|
||||
}
|
||||
# clip with a slightly grown expolygon because our lines lay on the contours and
|
||||
# may get erroneously clipped
|
||||
@lines = @{intersection_pl(\@lines, [ @{$expolygon->offset(+scaled_epsilon)} ])};
|
||||
my @lines = map Slic3r::Line->new(@$_[0,-1]),
|
||||
@{intersection_pl(\@polylines, [ @{$expolygon->offset(+scaled_epsilon)} ])};
|
||||
|
||||
# append bed contours
|
||||
foreach my $line (map @{$_->lines}, @$expolygon) {
|
||||
push @lines, $line->as_polyline;
|
||||
}
|
||||
push @lines, map @{$_->lines}, @$expolygon;
|
||||
|
||||
my @points = ();
|
||||
foreach my $polyline (@lines) {
|
||||
push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$polyline; #))
|
||||
foreach my $line (@lines) {
|
||||
push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$line; #))
|
||||
}
|
||||
$self->bed_grid_lines(OpenGL::Array->new_list(GL_FLOAT, @points));
|
||||
}
|
||||
|
|
@ -572,9 +588,10 @@ sub Resize {
|
|||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
my $depth = 10 * max(@{ $self->max_bounding_box->size });
|
||||
glOrtho(
|
||||
-$x/2, $x/2, -$y/2, $y/2,
|
||||
-200, 10 * max(@{ $self->max_bounding_box->size }),
|
||||
-$depth, 2*$depth,
|
||||
);
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
|
|
|
|||
|
|
@ -307,7 +307,7 @@ sub _repaint_canvas {
|
|||
@polylines = @{intersection_pl(\@polylines, [$bed_polygon])};
|
||||
|
||||
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(230,230,230), 1, wxSOLID));
|
||||
$dc->DrawLine(map @{$to_pixel->([map unscale($_), @$_])}, @$_) for @polylines;
|
||||
$dc->DrawLine(map @{$to_pixel->([map unscale($_), @$_])}, @$_[0,-1]) for @polylines;
|
||||
}
|
||||
|
||||
# draw bed contour
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ sub _init_menubar {
|
|||
Wx::LaunchDefaultBrowser('http://slic3r.org/');
|
||||
});
|
||||
my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", 'Check for new Slic3r versions', sub {
|
||||
wxTheApp->check_version(manual => 1);
|
||||
wxTheApp->check_version(1);
|
||||
});
|
||||
$versioncheck->Enable(wxTheApp->have_version_check);
|
||||
$self->_append_menu_item($helpMenu, "Slic3r &Manual", 'Open the Slic3r manual in your browser', sub {
|
||||
|
|
@ -678,7 +678,7 @@ sub config {
|
|||
} else {
|
||||
my $extruders_count = $self->{options_tabs}{printer}{extruders_count};
|
||||
$config->set("${_}_extruder", min($config->get("${_}_extruder"), $extruders_count))
|
||||
for qw(perimeter infill support_material support_material_interface);
|
||||
for qw(perimeter infill solid_infill support_material support_material_interface);
|
||||
}
|
||||
|
||||
return $config;
|
||||
|
|
|
|||
|
|
@ -96,6 +96,9 @@ sub new {
|
|||
$self->{canvas3D}->set_on_double_click($on_double_click);
|
||||
$self->{canvas3D}->set_on_right_click(sub { $on_right_click->($self->{canvas3D}, @_); });
|
||||
$self->{canvas3D}->set_on_instances_moved($on_instances_moved);
|
||||
$self->{canvas3D}->on_viewport_changed(sub {
|
||||
$self->{preview3D}->canvas->set_viewport_from_scene($self->{canvas3D});
|
||||
});
|
||||
}
|
||||
|
||||
# Initialize 2D preview canvas
|
||||
|
|
@ -109,6 +112,9 @@ sub new {
|
|||
# Initialize 3D toolpaths preview
|
||||
if ($Slic3r::GUI::have_OpenGL) {
|
||||
$self->{preview3D} = Slic3r::GUI::Plater::3DPreview->new($self->{preview_notebook}, $self->{print});
|
||||
$self->{preview3D}->canvas->on_viewport_changed(sub {
|
||||
$self->{canvas3D}->set_viewport_from_scene($self->{preview3D}->canvas);
|
||||
});
|
||||
$self->{preview_notebook}->AddPage($self->{preview3D}, 'Preview');
|
||||
$self->{preview3D_page_idx} = $self->{preview_notebook}->GetPageCount-1;
|
||||
}
|
||||
|
|
@ -988,9 +994,13 @@ sub pause_background_process {
|
|||
|
||||
if ($self->{process_thread} || $self->{export_thread}) {
|
||||
Slic3r::pause_all_threads();
|
||||
return 1;
|
||||
} elsif (defined $self->{apply_config_timer} && $self->{apply_config_timer}->IsRunning) {
|
||||
$self->{apply_config_timer}->Stop;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub resume_background_process {
|
||||
|
|
@ -1312,9 +1322,14 @@ sub update {
|
|||
$self->{model}->center_instances_around_point($self->bed_centerf);
|
||||
}
|
||||
|
||||
$self->pause_background_process;
|
||||
my $running = $self->pause_background_process;
|
||||
my $invalidated = $self->{print}->reload_model_instances();
|
||||
if ($invalidated) {
|
||||
|
||||
# The mere fact that no steps were invalidated when reloading model instances
|
||||
# doesn't mean that all steps were done: for example, validation might have
|
||||
# failed upon previous instance move, so we have no running thread and no steps
|
||||
# are invalidated on this move, thus we need to schedule a new run.
|
||||
if ($invalidated || !$running) {
|
||||
$self->schedule_background_process;
|
||||
} else {
|
||||
$self->resume_background_process;
|
||||
|
|
@ -1323,12 +1338,6 @@ sub update {
|
|||
$self->refresh_canvases;
|
||||
}
|
||||
|
||||
sub on_model_instances_changed {
|
||||
my ($self) = @_;
|
||||
|
||||
|
||||
}
|
||||
|
||||
sub on_extruders_change {
|
||||
my ($self, $num_extruders) = @_;
|
||||
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@ sub update_bed_size {
|
|||
push @polylines, Slic3r::Polyline->new([$bb->x_min, $y], [$bb->x_max, $y]);
|
||||
}
|
||||
@polylines = @{intersection_pl(\@polylines, [$polygon])};
|
||||
$self->{grid} = [ map $self->scaled_points_to_pixel(\@$_, 1), @polylines ];
|
||||
$self->{grid} = [ map $self->scaled_points_to_pixel([ @$_[0,-1] ], 1), @polylines ];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -291,7 +291,7 @@ sub Render {
|
|||
$brim_drawn = 1;
|
||||
}
|
||||
if ($self->print->step_done(STEP_SKIRT)
|
||||
&& ($self->print->config->skirt_height == -1 || $self->print->config->skirt_height > $layer->id)
|
||||
&& ($self->print->has_infinite_skirt() || $self->print->config->skirt_height > $layer->id)
|
||||
&& !$skirt_drawn) {
|
||||
$self->color([0, 0, 0]);
|
||||
$self->_draw(undef, $print_z, $_) for @{$self->print->skirt};
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@ sub new {
|
|||
|
||||
# buttons
|
||||
$self->{btn_save_preset} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new("$Slic3r::var/disk.png", wxBITMAP_TYPE_PNG),
|
||||
wxDefaultPosition, [16,16], wxBORDER_NONE);
|
||||
wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
|
||||
$self->{btn_delete_preset} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new("$Slic3r::var/delete.png", wxBITMAP_TYPE_PNG),
|
||||
wxDefaultPosition, [16,16], wxBORDER_NONE);
|
||||
wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
|
||||
$self->{btn_save_preset}->SetToolTipString("Save current " . lc($self->title));
|
||||
$self->{btn_delete_preset}->SetToolTipString("Delete this preset");
|
||||
$self->{btn_delete_preset}->Disable;
|
||||
|
|
@ -745,11 +745,13 @@ sub _update {
|
|||
perimeter_speed small_perimeter_speed external_perimeter_speed);
|
||||
|
||||
my $have_infill = $config->fill_density > 0;
|
||||
# infill_extruder uses the same logic as in Print::extruders()
|
||||
$self->get_field($_)->toggle($have_infill)
|
||||
for qw(fill_pattern infill_every_layers infill_only_where_needed solid_infill_every_layers
|
||||
solid_infill_below_area infill_extruder);
|
||||
|
||||
my $have_solid_infill = ($config->top_solid_layers > 0) || ($config->bottom_solid_layers > 0);
|
||||
# solid_infill_extruder uses the same logic as in Print::extruders()
|
||||
$self->get_field($_)->toggle($have_solid_infill)
|
||||
for qw(external_fill_pattern infill_first solid_infill_extruder solid_infill_extrusion_width
|
||||
solid_infill_speed);
|
||||
|
|
@ -772,6 +774,7 @@ sub _update {
|
|||
for qw(skirt_distance skirt_height);
|
||||
|
||||
my $have_brim = $config->brim_width > 0;
|
||||
# perimeter_extruder uses the same logic as in Print::extruders()
|
||||
$self->get_field('perimeter_extruder')->toggle($have_perimeters || $have_brim);
|
||||
|
||||
my $have_support_material = $config->support_material || $config->raft_layers > 0;
|
||||
|
|
|
|||
|
|
@ -286,7 +286,7 @@ sub process {
|
|||
# where 0.5*$pwidth < thickness < $pwidth, infill with width = 0.5*$pwidth
|
||||
my @gap_sizes = (
|
||||
[ $pwidth, 2*$pspacing, unscale 1.5*$pwidth ],
|
||||
[ 0.5*$pwidth, $pwidth, unscale 0.5*$pwidth ],
|
||||
[ 0.1*$pwidth, $pwidth, unscale 0.5*$pwidth ],
|
||||
);
|
||||
foreach my $gap_size (@gap_sizes) {
|
||||
my @gap_fill = $self->_fill_gaps(@$gap_size, \@gaps);
|
||||
|
|
@ -311,12 +311,22 @@ sub process {
|
|||
# we offset by half the perimeter spacing (to get to the actual infill boundary)
|
||||
# and then we offset back and forth by half the infill spacing to only consider the
|
||||
# non-collapsing regions
|
||||
my $inset = 0;
|
||||
if ($loop_number == 0) {
|
||||
# one loop
|
||||
$inset += $ext_pspacing/2;
|
||||
} elsif ($loop_number > 0) {
|
||||
# two or more loops
|
||||
$inset += $pspacing/2;
|
||||
}
|
||||
$inset -= $self->config->get_abs_value_over('infill_overlap', $pwidth);
|
||||
|
||||
my $min_perimeter_infill_spacing = $ispacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
|
||||
$self->fill_surfaces->append($_)
|
||||
for map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL), # use a bogus surface type
|
||||
@{offset2_ex(
|
||||
[ map @{$_->simplify_p(&Slic3r::SCALED_RESOLUTION)}, @{union_ex(\@last)} ],
|
||||
-($pspacing/2 - $self->config->get_abs_value_over('infill_overlap', $pwidth) + $min_perimeter_infill_spacing/2),
|
||||
-$inset -$min_perimeter_infill_spacing/2,
|
||||
+$min_perimeter_infill_spacing/2,
|
||||
)};
|
||||
}
|
||||
|
|
@ -384,8 +394,8 @@ sub _traverse_loops {
|
|||
push @paths, Slic3r::ExtrusionPath->new(
|
||||
polyline => $loop->polygon->split_at_first_point,
|
||||
role => $role,
|
||||
mm3_per_mm => $self->_mm3_per_mm,
|
||||
width => $self->perimeter_flow->width,
|
||||
mm3_per_mm => ($is_external ? $self->_ext_mm3_per_mm : $self->_mm3_per_mm),
|
||||
width => ($is_external ? $self->ext_perimeter_flow->width : $self->perimeter_flow->width),
|
||||
height => $self->layer_height,
|
||||
);
|
||||
}
|
||||
|
|
@ -441,11 +451,14 @@ sub _traverse_loops {
|
|||
sub _fill_gaps {
|
||||
my ($self, $min, $max, $w, $gaps) = @_;
|
||||
|
||||
$min *= (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
|
||||
|
||||
my $this = diff_ex(
|
||||
offset2([ map @$_, @$gaps ], -$min/2, +$min/2),
|
||||
offset2([ map @$_, @$gaps ], -$max/2, +$max/2),
|
||||
1,
|
||||
);
|
||||
|
||||
my @polylines = map @{$_->medial_axis($max, $min/2)}, @$this;
|
||||
return if !@polylines;
|
||||
|
||||
|
|
|
|||
|
|
@ -89,7 +89,8 @@ sub export_gcode {
|
|||
$self->config->setenv;
|
||||
for my $script (@{$self->config->post_process}) {
|
||||
Slic3r::debugf " '%s' '%s'\n", $script, $output_file;
|
||||
if (!-x $script) {
|
||||
# -x doesn't return true on Windows except for .exe files
|
||||
if (($^O eq 'MSWin32') ? !(-e $script) : !(-x $script)) {
|
||||
die "The configured post-processing script is not executable: check permissions. ($script)\n";
|
||||
}
|
||||
system($script, $output_file);
|
||||
|
|
@ -101,9 +102,6 @@ sub export_svg {
|
|||
my $self = shift;
|
||||
my %params = @_;
|
||||
|
||||
# is this needed?
|
||||
$self->init_extruders;
|
||||
|
||||
$_->slice for @{$self->objects};
|
||||
|
||||
my $fh = $params{output_fh};
|
||||
|
|
@ -208,8 +206,7 @@ sub make_skirt {
|
|||
# checking whether we need to generate them
|
||||
$self->skirt->clear;
|
||||
|
||||
if (($self->config->skirts == 0 || $self->config->skirt_height == 0)
|
||||
&& (!$self->config->ooze_prevention || @{$self->extruders} == 1)) {
|
||||
if (!$self->has_skirt) {
|
||||
$self->set_step_done(STEP_SKIRT);
|
||||
return;
|
||||
}
|
||||
|
|
@ -219,7 +216,7 @@ sub make_skirt {
|
|||
# The skirt_height option from config is expressed in layers, but our
|
||||
# object might have different layer heights, so we need to find the print_z
|
||||
# of the highest layer involved.
|
||||
# Note that unless skirt_height == -1 (which means it's printed on all layers)
|
||||
# Note that unless has_infinite_skirt() == true
|
||||
# the actual skirt might not reach this $skirt_height_z value since the print
|
||||
# order of objects on each layer is not guaranteed and will not generally
|
||||
# include the thickest object first. It is just guaranteed that a skirt is
|
||||
|
|
@ -227,10 +224,9 @@ sub make_skirt {
|
|||
# $skirt_height_z in this case is the highest possible skirt height for safety.
|
||||
my $skirt_height_z = -1;
|
||||
foreach my $object (@{$self->objects}) {
|
||||
my $skirt_height = ($self->config->skirt_height == -1 || $self->config->ooze_prevention)
|
||||
my $skirt_height = $self->has_infinite_skirt
|
||||
? scalar(@{$object->layers})
|
||||
: min($self->config->skirt_height, scalar(@{$object->layers}));
|
||||
|
||||
my $highest_layer = $object->get_layer($skirt_height - 1);
|
||||
$skirt_height_z = max($skirt_height_z, $highest_layer->print_z);
|
||||
}
|
||||
|
|
@ -278,10 +274,13 @@ sub make_skirt {
|
|||
my @extruders_e_per_mm = ();
|
||||
my $extruder_idx = 0;
|
||||
|
||||
my $skirts = $self->config->skirts;
|
||||
$skirts ||= 1 if $self->has_infinite_skirt;
|
||||
|
||||
# draw outlines from outside to inside
|
||||
# loop while we have less skirts than required or any extruder hasn't reached the min length if any
|
||||
my $distance = scale max($self->config->skirt_distance, $self->config->brim_width);
|
||||
for (my $i = $self->config->skirts; $i > 0; $i--) {
|
||||
for (my $i = $skirts; $i > 0; $i--) {
|
||||
$distance += scale $spacing;
|
||||
my $loop = offset([$convex_hull], $distance, 1, JT_ROUND, scale(0.1))->[0];
|
||||
$self->skirt->append(Slic3r::ExtrusionLoop->new_from_paths(
|
||||
|
|
@ -428,10 +427,10 @@ sub expanded_output_filepath {
|
|||
|
||||
my $filename = my $filename_base = basename($input_file);
|
||||
$filename_base =~ s/\.[^.]+$//; # without suffix
|
||||
my $extra = {
|
||||
input_filename => $filename,
|
||||
input_filename_base => $filename_base,
|
||||
};
|
||||
|
||||
# set filename in placeholder parser so that it's available also in custom G-code
|
||||
$self->placeholder_parser->set(input_filename => $filename);
|
||||
$self->placeholder_parser->set(input_filename_base => $filename_base);
|
||||
|
||||
if ($path && -d $path) {
|
||||
# if output path is an existing directory, we take that and append
|
||||
|
|
@ -447,7 +446,7 @@ sub expanded_output_filepath {
|
|||
|
||||
# make sure we use an up-to-date timestamp
|
||||
$self->placeholder_parser->update_timestamp;
|
||||
return $self->placeholder_parser->process($path, $extra);
|
||||
return $self->placeholder_parser->process($path);
|
||||
}
|
||||
|
||||
# This method assigns extruders to the volumes having a material
|
||||
|
|
|
|||
|
|
@ -195,12 +195,14 @@ sub export {
|
|||
# no collision happens hopefully.
|
||||
if ($finished_objects > 0) {
|
||||
$gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y));
|
||||
$gcodegen->enable_cooling_markers(0); # we're not filtering these moves through CoolingBuffer
|
||||
print $fh $gcodegen->retract;
|
||||
print $fh $gcodegen->travel_to(
|
||||
Slic3r::Point->new(0,0),
|
||||
undef,
|
||||
'move to origin position for next object',
|
||||
);
|
||||
$gcodegen->enable_cooling_markers(1);
|
||||
}
|
||||
|
||||
my @layers = sort { $a->print_z <=> $b->print_z } @{$object->layers}, @{$object->support_layers};
|
||||
|
|
@ -217,6 +219,7 @@ sub export {
|
|||
}
|
||||
$self->flush_filters;
|
||||
$finished_objects++;
|
||||
$self->_second_layer_things_done(0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -249,6 +252,7 @@ sub export {
|
|||
print $fh $gcodegen->writer->set_fan(0);
|
||||
printf $fh "%s\n", $gcodegen->placeholder_parser->process($self->config->end_gcode);
|
||||
print $fh $gcodegen->writer->update_progress($gcodegen->layer_count, $gcodegen->layer_count, 1); # 100%
|
||||
print $fh $gcodegen->writer->postamble;
|
||||
|
||||
# get filament stats
|
||||
$self->print->clear_filament_stats;
|
||||
|
|
@ -299,7 +303,7 @@ sub process_layer {
|
|||
if (defined $self->_spiral_vase) {
|
||||
$self->_spiral_vase->enable(
|
||||
($layer->id > 0 || $self->print->config->brim_width == 0)
|
||||
&& ($layer->id >= $self->print->config->skirt_height && $self->print->config->skirt_height != -1)
|
||||
&& ($layer->id >= $self->print->config->skirt_height && !$self->print->has_infinite_skirt)
|
||||
&& !defined(first { $_->config->bottom_solid_layers > $layer->id } @{$layer->regions})
|
||||
&& !defined(first { @{$_->perimeters} > 1 } @{$layer->regions})
|
||||
&& !defined(first { @{$_->fills} > 0 } @{$layer->regions})
|
||||
|
|
@ -332,14 +336,15 @@ sub process_layer {
|
|||
}) . "\n" if $self->print->config->layer_gcode;
|
||||
|
||||
# extrude skirt
|
||||
if (((values %{$self->_skirt_done}) < $self->print->config->skirt_height || $self->print->config->skirt_height == -1)
|
||||
&& !$self->_skirt_done->{$layer->print_z}) {
|
||||
if (((values %{$self->_skirt_done}) < $self->print->config->skirt_height || $self->print->has_infinite_skirt)
|
||||
&& !$self->_skirt_done->{$layer->print_z}
|
||||
&& !$layer->isa('Slic3r::Layer::Support')) {
|
||||
$self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0));
|
||||
$self->_gcodegen->avoid_crossing_perimeters->use_external_mp(1);
|
||||
my @extruder_ids = map { $_->id } @{$self->_gcodegen->writer->extruders};
|
||||
$gcode .= $self->_gcodegen->set_extruder($extruder_ids[0]);
|
||||
# skip skirt if we have a large brim
|
||||
if ($layer->id < $self->print->config->skirt_height || $self->print->config->skirt_height == -1) {
|
||||
if ($layer->id < $self->print->config->skirt_height || $self->print->has_infinite_skirt) {
|
||||
my $skirt_flow = $self->print->skirt_flow;
|
||||
|
||||
# distribute skirt loops across all extruders
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use List::Util qw(min max sum first);
|
|||
use Slic3r::Flow ':roles';
|
||||
use Slic3r::Geometry qw(X Y Z PI scale unscale chained_path);
|
||||
use Slic3r::Geometry::Clipper qw(diff diff_ex intersection intersection_ex union union_ex
|
||||
offset offset_ex offset2 offset2_ex CLIPPER_OFFSET_SCALE JT_MITER);
|
||||
offset offset_ex offset2 offset2_ex intersection_ppl CLIPPER_OFFSET_SCALE JT_MITER);
|
||||
use Slic3r::Print::State ':steps';
|
||||
use Slic3r::Surface ':types';
|
||||
|
||||
|
|
@ -344,7 +344,6 @@ sub make_perimeters {
|
|||
my $self = shift;
|
||||
|
||||
# prerequisites
|
||||
$self->print->init_extruders;
|
||||
$self->slice;
|
||||
|
||||
return if $self->step_done(STEP_PERIMETERS);
|
||||
|
|
@ -369,51 +368,59 @@ sub make_perimeters {
|
|||
my $region = $self->print->regions->[$region_id];
|
||||
my $region_perimeters = $region->config->perimeters;
|
||||
|
||||
if ($region->config->extra_perimeters && $region_perimeters > 0 && $region->config->fill_density > 0) {
|
||||
for my $i (0 .. ($self->layer_count - 2)) {
|
||||
my $layerm = $self->get_layer($i)->regions->[$region_id];
|
||||
my $upper_layerm = $self->get_layer($i+1)->regions->[$region_id];
|
||||
my $perimeter_spacing = $layerm->flow(FLOW_ROLE_PERIMETER)->scaled_spacing;
|
||||
my $ext_perimeter_spacing = $layerm->flow(FLOW_ROLE_EXTERNAL_PERIMETER)->scaled_spacing;
|
||||
|
||||
my $overlap = $perimeter_spacing; # one perimeter
|
||||
|
||||
my $diff = diff(
|
||||
offset([ map @{$_->expolygon}, @{$layerm->slices} ], -($ext_perimeter_spacing + ($region_perimeters-1) * $perimeter_spacing)),
|
||||
offset([ map @{$_->expolygon}, @{$upper_layerm->slices} ], -$overlap),
|
||||
);
|
||||
next if !@$diff;
|
||||
# if we need more perimeters, $diff should contain a narrow region that we can collapse
|
||||
|
||||
# we use a higher miterLimit here to handle areas with acute angles
|
||||
# in those cases, the default miterLimit would cut the corner and we'd
|
||||
# get a triangle that would trigger a non-needed extra perimeter
|
||||
$diff = diff(
|
||||
$diff,
|
||||
offset2($diff, -$perimeter_spacing, +$perimeter_spacing, CLIPPER_OFFSET_SCALE, JT_MITER, 5),
|
||||
1,
|
||||
);
|
||||
next if !@$diff;
|
||||
# diff contains the collapsed area
|
||||
|
||||
foreach my $slice (@{$layerm->slices}) {
|
||||
my $extra_perimeters = 0;
|
||||
CYCLE: while (1) {
|
||||
# compute polygons representing the thickness of the hypotetical new internal perimeter
|
||||
# of our slice
|
||||
$extra_perimeters++;
|
||||
my $hypothetical_perimeter = diff(
|
||||
offset($slice->expolygon->arrayref, -($perimeter_spacing * ($region_perimeters + $extra_perimeters-1))),
|
||||
offset($slice->expolygon->arrayref, -($perimeter_spacing * ($region_perimeters + $extra_perimeters))),
|
||||
next if !$region->config->extra_perimeters;
|
||||
next if $region_perimeters == 0;
|
||||
next if $region->config->fill_density == 0;
|
||||
|
||||
for my $i (0 .. ($self->layer_count - 2)) {
|
||||
my $layerm = $self->get_layer($i)->get_region($region_id);
|
||||
my $upper_layerm = $self->get_layer($i+1)->get_region($region_id);
|
||||
|
||||
my $perimeter_spacing = $layerm->flow(FLOW_ROLE_PERIMETER)->scaled_spacing;
|
||||
my $ext_perimeter_flow = $layerm->flow(FLOW_ROLE_EXTERNAL_PERIMETER);
|
||||
my $ext_perimeter_width = $ext_perimeter_flow->scaled_width;
|
||||
my $ext_perimeter_spacing = $ext_perimeter_flow->scaled_spacing;
|
||||
|
||||
foreach my $slice (@{$layerm->slices}) {
|
||||
while (1) {
|
||||
# compute the total thickness of perimeters
|
||||
my $perimeters_thickness = $ext_perimeter_width/2 + $ext_perimeter_spacing/2
|
||||
+ ($region_perimeters-1 + $slice->extra_perimeters) * $perimeter_spacing;
|
||||
|
||||
# define a critical area where we don't want the upper slice to fall into
|
||||
# (it should either lay over our perimeters or outside this area)
|
||||
my $critical_area_depth = $perimeter_spacing*1.5;
|
||||
my $critical_area = diff(
|
||||
offset($slice->expolygon->arrayref, -$perimeters_thickness),
|
||||
offset($slice->expolygon->arrayref, -($perimeters_thickness + $critical_area_depth)),
|
||||
);
|
||||
|
||||
# check whether a portion of the upper slices falls inside the critical area
|
||||
my $intersection = intersection_ppl(
|
||||
[ map $_->p, @{$upper_layerm->slices} ],
|
||||
$critical_area,
|
||||
);
|
||||
|
||||
# only add an additional loop if at least 30% of the slice loop would benefit from it
|
||||
my $total_loop_length = sum(map $_->length, map $_->p, @{$upper_layerm->slices}) // 0;
|
||||
my $total_intersection_length = sum(map $_->length, @$intersection) // 0;
|
||||
last unless $total_intersection_length > $total_loop_length*0.3;
|
||||
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output(
|
||||
"extra.svg",
|
||||
no_arrows => 1,
|
||||
expolygons => union_ex($critical_area),
|
||||
polylines => [ map $_->split_at_first_point, map $_->p, @{$upper_layerm->slices} ],
|
||||
);
|
||||
last CYCLE if !@$hypothetical_perimeter; # no extra perimeter is possible
|
||||
|
||||
# only add the perimeter if there's an intersection with the collapsed area
|
||||
last CYCLE if !@{ intersection($diff, $hypothetical_perimeter) };
|
||||
Slic3r::debugf " adding one more perimeter at layer %d\n", $layerm->id;
|
||||
$slice->extra_perimeters($extra_perimeters);
|
||||
}
|
||||
|
||||
$slice->extra_perimeters($slice->extra_perimeters + 1);
|
||||
}
|
||||
Slic3r::debugf " adding %d more perimeter(s) at layer %d\n",
|
||||
$slice->extra_perimeters, $layerm->id
|
||||
if $slice->extra_perimeters > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -524,7 +531,6 @@ sub generate_support_material {
|
|||
my $self = shift;
|
||||
|
||||
# prerequisites
|
||||
$self->print->init_extruders;
|
||||
$self->slice;
|
||||
|
||||
return if $self->step_done(STEP_SUPPORTMATERIAL);
|
||||
|
|
@ -547,7 +553,7 @@ sub _support_material {
|
|||
my ($self) = @_;
|
||||
|
||||
my $first_layer_flow = Slic3r::Flow->new_from_width(
|
||||
width => ($self->config->first_layer_extrusion_width || $self->config->support_material_extrusion_width),
|
||||
width => ($self->print->config->first_layer_extrusion_width || $self->config->support_material_extrusion_width),
|
||||
role => FLOW_ROLE_SUPPORT_MATERIAL,
|
||||
nozzle_diameter => $self->print->config->nozzle_diameter->[ $self->config->support_material_extruder-1 ]
|
||||
// $self->print->config->nozzle_diameter->[0],
|
||||
|
|
@ -650,7 +656,7 @@ sub detect_surfaces_type {
|
|||
|
||||
# if we have raft layers, consider bottom layer as a bridge
|
||||
# just like any other bottom surface lying on the void
|
||||
if ($self->config->raft_layers > 0) {
|
||||
if ($self->config->raft_layers > 0 && $self->config->support_material_contact_distance > 0) {
|
||||
$_->surface_type(S_TYPE_BOTTOMBRIDGE) for @bottom;
|
||||
} else {
|
||||
$_->surface_type(S_TYPE_BOTTOM) for @bottom;
|
||||
|
|
@ -714,61 +720,95 @@ sub clip_fill_surfaces {
|
|||
# We only want infill under ceilings; this is almost like an
|
||||
# internal support material.
|
||||
|
||||
my $additional_margin = scale 3*0;
|
||||
|
||||
my $overhangs = []; # arrayref of polygons
|
||||
for my $layer_id (reverse 0..($self->layer_count - 1)) {
|
||||
my $layer = $self->get_layer($layer_id);
|
||||
my @layer_internal = (); # arrayref of Surface objects
|
||||
my @new_internal = (); # arrayref of Surface objects
|
||||
# proceed top-down skipping bottom layer
|
||||
my $upper_internal = [];
|
||||
for my $layer_id (reverse 1..($self->layer_count - 1)) {
|
||||
my $layer = $self->get_layer($layer_id);
|
||||
my $lower_layer = $self->get_layer($layer_id-1);
|
||||
|
||||
# clip this layer's internal surfaces to @overhangs
|
||||
foreach my $layerm (@{$layer->regions}) {
|
||||
# detect things that we need to support
|
||||
my $overhangs = []; # Polygons
|
||||
|
||||
# we need to support any solid surface
|
||||
push @$overhangs, map $_->p,
|
||||
grep $_->is_solid, map @{$_->fill_surfaces}, @{$layer->regions};
|
||||
|
||||
# we also need to support perimeters when there's at least one full
|
||||
# unsupported loop
|
||||
{
|
||||
# get perimeters area as the difference between slices and fill_surfaces
|
||||
my $perimeters = diff(
|
||||
[ map @$_, @{$layer->slices} ],
|
||||
[ map $_->p, map @{$_->fill_surfaces}, @{$layer->regions} ],
|
||||
);
|
||||
|
||||
# only consider the area that is not supported by lower perimeters
|
||||
$perimeters = intersection(
|
||||
$perimeters,
|
||||
[ map $_->p, map @{$_->fill_surfaces}, @{$lower_layer->regions} ],
|
||||
1,
|
||||
);
|
||||
|
||||
# only consider perimeter areas that are at least one extrusion width thick
|
||||
my $pw = min(map $_->flow(FLOW_ROLE_PERIMETER)->scaled_width, @{$layer->regions});
|
||||
$perimeters = offset2($perimeters, -$pw, +$pw);
|
||||
|
||||
# append such thick perimeters to the areas that need support
|
||||
push @$overhangs, @$perimeters;
|
||||
}
|
||||
|
||||
# find new internal infill
|
||||
$upper_internal = my $new_internal = intersection(
|
||||
[
|
||||
@$overhangs,
|
||||
@$upper_internal,
|
||||
],
|
||||
[
|
||||
# our current internal fill boundaries
|
||||
map $_->p,
|
||||
grep $_->surface_type == S_TYPE_INTERNAL || $_->surface_type == S_TYPE_INTERNALVOID,
|
||||
map @{$_->fill_surfaces}, @{$lower_layer->regions}
|
||||
],
|
||||
);
|
||||
|
||||
# apply new internal infill to regions
|
||||
foreach my $layerm (@{$lower_layer->regions}) {
|
||||
my (@internal, @other) = ();
|
||||
foreach my $surface (map $_->clone, @{$layerm->fill_surfaces}) {
|
||||
if ($surface->surface_type == S_TYPE_INTERNAL) {
|
||||
if ($surface->surface_type == S_TYPE_INTERNAL || $surface->surface_type == S_TYPE_INTERNALVOID) {
|
||||
push @internal, $surface;
|
||||
} else {
|
||||
push @other, $surface;
|
||||
}
|
||||
}
|
||||
|
||||
# keep all the original internal surfaces to detect overhangs in this layer
|
||||
push @layer_internal, @internal;
|
||||
|
||||
push @new_internal, my @new = map Slic3r::Surface->new(
|
||||
my @new = map Slic3r::Surface->new(
|
||||
expolygon => $_,
|
||||
surface_type => S_TYPE_INTERNAL,
|
||||
),
|
||||
@{intersection_ex(
|
||||
[ map $_->p, @internal ],
|
||||
$overhangs,
|
||||
$new_internal,
|
||||
1,
|
||||
)};
|
||||
|
||||
push @new, map Slic3r::Surface->new(
|
||||
push @other, map Slic3r::Surface->new(
|
||||
expolygon => $_,
|
||||
surface_type => S_TYPE_INTERNALVOID,
|
||||
),
|
||||
@{diff_ex(
|
||||
[ map $_->p, @internal ],
|
||||
$overhangs,
|
||||
$new_internal,
|
||||
1,
|
||||
)};
|
||||
|
||||
# If there are voids it means that our internal infill is not adjacent to
|
||||
# perimeters. In this case it would be nice to add a loop around infill to
|
||||
# make it more robust and nicer. TODO.
|
||||
|
||||
$layerm->fill_surfaces->clear;
|
||||
$layerm->fill_surfaces->append($_) for (@new, @other);
|
||||
}
|
||||
|
||||
# get this layer's overhangs defined as the full slice minus the internal infill
|
||||
# (thus we also consider perimeters)
|
||||
if ($layer_id > 0) {
|
||||
my $solid = diff(
|
||||
[ map $_->p, map @{$_->fill_surfaces}, @{$layer->regions} ],
|
||||
[ map $_->p, @layer_internal ],
|
||||
);
|
||||
$overhangs = offset($solid, +$additional_margin);
|
||||
|
||||
push @$overhangs, map $_->p, @new_internal; # propagate upper overhangs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use warnings;
|
|||
|
||||
require Exporter;
|
||||
our @ISA = qw(Exporter);
|
||||
our @EXPORT_OK = qw(STEP_INIT_EXTRUDERS STEP_SLICE STEP_PERIMETERS STEP_PREPARE_INFILL
|
||||
our @EXPORT_OK = qw(STEP_SLICE STEP_PERIMETERS STEP_PREPARE_INFILL
|
||||
STEP_INFILL STEP_SUPPORTMATERIAL STEP_SKIRT STEP_BRIM);
|
||||
our %EXPORT_TAGS = (steps => \@EXPORT_OK);
|
||||
|
||||
|
|
|
|||
|
|
@ -723,6 +723,11 @@ sub generate_toolpaths {
|
|||
if (@$base) {
|
||||
my $filler = $fillers{support};
|
||||
$filler->angle($angles[ ($layer_id) % @angles ]);
|
||||
|
||||
# We don't use $base_flow->spacing because we need a constant spacing
|
||||
# value that guarantees that all layers are correctly aligned.
|
||||
$filler->spacing($flow->spacing);
|
||||
|
||||
my $density = $support_density;
|
||||
my $base_flow = $_flow;
|
||||
|
||||
|
|
@ -737,6 +742,10 @@ sub generate_toolpaths {
|
|||
$filler->angle($self->object_config->support_material_angle + 90);
|
||||
$density = 0.5;
|
||||
$base_flow = $self->first_layer_flow;
|
||||
|
||||
# use the proper spacing for first layer as we don't need to align
|
||||
# its pattern to the other layers
|
||||
$filler->spacing($base_flow->spacing);
|
||||
} else {
|
||||
# draw a perimeter all around support infill
|
||||
# TODO: use brim ordering algorithm
|
||||
|
|
@ -753,10 +762,6 @@ sub generate_toolpaths {
|
|||
$to_infill = offset_ex([ map @$_, @$to_infill ], -$_flow->scaled_spacing);
|
||||
}
|
||||
|
||||
# We don't use $base_flow->spacing because we need a constant spacing
|
||||
# value that guarantees that all layers are correctly aligned.
|
||||
$filler->spacing($flow->spacing);
|
||||
|
||||
my $mm3_per_mm = $base_flow->mm3_per_mm;
|
||||
foreach my $expolygon (@$to_infill) {
|
||||
my @p = $filler->fill_surface(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue