Merge branch 'master' into avoid-crossing-perimeters

Conflicts:
	lib/Slic3r/GCode.pm
medial-thinwall
Alessandro Ranellucci 2012-10-24 11:49:31 +02:00
commit 1627268fd4
35 changed files with 1544 additions and 1159 deletions

View File

@ -12,7 +12,7 @@ my $build = Module::Build->new(
'File::Spec' => '0',
'Getopt::Long' => '0',
'Math::Clipper' => '1.09',
'Math::ConvexHull' => '1.0.4',
'Math::ConvexHull::MonotoneChain' => '0.01',
'Math::Geometry::Voronoi' => '1.3',
'Math::PlanePath' => '53',
'Moo' => '0.091009',

View File

@ -35,12 +35,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

View File

@ -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

View File

@ -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 {
@ -44,6 +44,7 @@ use Slic3r::GCode;
use Slic3r::GCode::MotionPlanner;
use Slic3r::Geometry qw(PI);
use Slic3r::Layer;
use Slic3r::Layer::Region;
use Slic3r::Line;
use Slic3r::Model;
use Slic3r::Point;
@ -51,6 +52,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";
@ -64,15 +66,16 @@ 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;
our $first_layer_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) {

View File

@ -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' => {
@ -951,6 +962,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]
@ -1058,15 +1072,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"

View File

@ -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');

View File

@ -160,6 +160,7 @@ sub detect_arcs {
$max_angle = deg2rad($max_angle || 15);
$len_epsilon ||= 10 / &Slic3r::SCALING_FACTOR;
my $parallel_degrees_limit = abs(Slic3r::Geometry::deg2rad(3));
my @points = @{$self->points};
my @paths = ();
@ -191,8 +192,8 @@ sub detect_arcs {
$s3_angle += 2*PI if $s3_angle < 0;
my $s1s2_angle = $s2_angle - $s1_angle;
my $s2s3_angle = $s3_angle - $s2_angle;
next if abs($s1s2_angle - $s2s3_angle) > $Slic3r::Geometry::parallel_degrees_limit;
next if abs($s1s2_angle) < $Slic3r::Geometry::parallel_degrees_limit; # ignore parallel lines
next if abs($s1s2_angle - $s2s3_angle) > $parallel_degrees_limit;
next if abs($s1s2_angle) < $parallel_degrees_limit; # ignore parallel lines
next if $s1s2_angle > $max_angle; # ignore too sharp vertices
my @arc_points = ($points[$i], $points[$i+3]), # first and last points
@ -205,7 +206,7 @@ sub detect_arcs {
my $line_angle = $line->atan;
$line_angle += 2*PI if $line_angle < 0;
my $anglediff = $line_angle - $last_line_angle;
last if abs($s1s2_angle - $anglediff) > $Slic3r::Geometry::parallel_degrees_limit;
last if abs($s1s2_angle - $anglediff) > $parallel_degrees_limit;
# point $j+1 belongs to the arc
$arc_points[-1] = $points[$j+1];

View File

@ -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;

View File

@ -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};
}

View File

@ -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];

View File

@ -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');

View File

@ -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;
@ -55,4 +57,14 @@ sub _build_spacing {
return $self->width - &Slic3r::OVERLAP_FACTOR * ($self->width - $min_flow_spacing);
}
sub _build_scaled_width {
my $self = shift;
return scale $self->width;
}
sub _build_scaled_spacing {
my $self = shift;
return scale $self->spacing;
}
1;

View File

@ -35,8 +35,8 @@ sub write_file {
for my $material_id (sort keys %{ $model->materials }) {
my $material = $model->materials->{$material_id};
printf $fh qq{ <material id="%d">\n}, $material_id;
for (keys %$material) {
printf $fh qq{ <metadata type=\"%s\">%s</metadata>\n}, $_, $material->{$_};
for (keys %{$material->attributes}) {
printf $fh qq{ <metadata type=\"%s\">%s</metadata>\n}, $_, $material->attributes->{$_};
}
printf $fh qq{ </material>\n};
}

View File

@ -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};
}

View File

@ -6,6 +6,7 @@ use Slic3r::ExtrusionPath ':roles';
use Slic3r::Geometry qw(scale unscale scaled_epsilon points_coincide PI X Y);
use Slic3r::Geometry::Clipper qw(union_ex);
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} );
@ -16,7 +17,7 @@ has 'external_mp' => (is => 'rw');
has 'layer_mp' => (is => 'rw');
has 'new_object' => (is => 'rw', default => sub {0});
has 'straight_once' => (is => 'rw', default => sub {1});
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} );
@ -54,11 +55,6 @@ my %role_speeds = (
&EXTR_ROLE_SUPPORTMATERIAL => 'perimeter',
);
sub extruder {
my $self = shift;
return $Slic3r::extruders->[$self->extruder_idx];
}
sub set_shift {
my $self = shift;
my @shift = @_;
@ -80,7 +76,7 @@ sub change_layer {
$self->layer($layer);
if ($Slic3r::Config->avoid_crossing_perimeters) {
$self->layer_mp(Slic3r::GCode::MotionPlanner->new(
islands => union_ex([ map @{$_->expolygon}, @{$layer->slices} ], undef, 1),
islands => union_ex([ map @$_, @{$layer->slices} ], undef, 1),
));
}
my $z = $Slic3r::Config->z_offset + $layer->print_z * &Slic3r::SCALING_FACTOR;
@ -117,7 +113,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);
}
@ -129,7 +124,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
@ -159,7 +154,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]);
}
}
@ -430,26 +425,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;
@ -482,7 +477,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 && $self->multiple_extruders) ? "T$tool " : "";
$gcode .= "M116 ; wait for temperature to be reached\n"
if $Slic3r::Config->gcode_flavor eq 'teacup' && $wait;

View File

@ -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);
}

View File

@ -5,7 +5,7 @@ use utf8;
use File::Basename qw(basename dirname);
use List::Util qw(max sum);
use Math::ConvexHull qw(convex_hull);
use Math::ConvexHull::MonotoneChain qw(convex_hull);
use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX);
use Slic3r::Geometry::Clipper qw(JT_ROUND);
use threads::shared qw(shared_clone);
@ -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;
@ -302,7 +301,7 @@ sub load_file {
name => basename($input_file),
input_file => $input_file,
input_file_object_id => $i,
mesh => $model->objects->[$i]->mesh,
model_object => $model->objects->[$i],
instances => [
$model->objects->[$i]->instances
? (map $_->offset, @{$model->objects->[$i]->instances})
@ -386,6 +385,7 @@ sub decrease {
my ($obj_idx, $object) = $self->selected_object;
if ($object->instances_count >= 2) {
pop @{$object->instances};
$self->{list}->SetItem($obj_idx, 1, $object->instances_count);
} else {
$self->remove;
}
@ -434,7 +434,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 {
@ -456,7 +456,14 @@ sub split_object {
my ($obj_idx, $current_object) = $self->selected_object;
my $current_copies_num = $current_object->instances_count;
my $mesh = $current_object->get_mesh;
my $model_object = $current_object->get_model_object;
if (@{$model_object->volumes} > 1) {
Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be splitted because it contains more than one volume/material.");
return;
}
my $mesh = $model_object->mesh;
$mesh->align_to_origin;
my @new_meshes = $mesh->split_mesh;
@ -503,7 +510,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;
@ -530,7 +537,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");
@ -557,7 +564,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),
},
);
}
@ -602,7 +609,6 @@ sub export_gcode2 {
}
$message .= ".";
$params{on_completed}->($message);
$print->cleanup;
};
$params{catch_error}->();
}
@ -656,7 +662,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;
@ -671,20 +677,24 @@ sub make_model {
my $self = shift;
my $model = Slic3r::Model->new;
foreach my $object (@{$self->{objects}}) {
my $mesh = $object->get_mesh;
$mesh->scale($object->scale);
my $model_object = $model->add_object(
vertices => $mesh->vertices,
input_file => $object->input_file,
foreach my $plater_object (@{$self->{objects}}) {
my $model_object = $plater_object->get_model_object;
my $new_model_object = $model->add_object(
vertices => $model_object->vertices,
input_file => $plater_object->input_file,
);
$model_object->add_volume(
facets => $mesh->facets,
);
$model_object->add_instance(
rotation => $object->rotate,
foreach my $volume (@{$model_object->volumes}) {
$new_model_object->add_volume(
material_id => $volume->material_id,
facets => $volume->facets,
);
$model->materials->{$volume->material_id || 0} ||= {};
}
$new_model_object->scale($plater_object->scale);
$new_model_object->add_instance(
rotation => $plater_object->rotate,
offset => [ @$_ ],
) for @{$object->instances};
) for @{$plater_object->instances};
}
return $model;
@ -702,7 +712,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);
}
};
@ -711,6 +721,9 @@ sub make_thumbnail {
sub on_thumbnail_made {
my $self = shift;
my ($obj_idx) = @_;
$self->{objects}[$obj_idx]->free_model_object;
$self->recenter;
$self->{canvas}->Refresh;
}
@ -1000,30 +1013,38 @@ sub OnDropFiles {
package Slic3r::GUI::Plater::Object;
use Moo;
use Math::ConvexHull qw(convex_hull);
use Math::ConvexHull::MonotoneChain qw(convex_hull);
use Slic3r::Geometry qw(X Y);
has 'name' => (is => 'rw', required => 1);
has 'input_file' => (is => 'rw', required => 1);
has 'input_file_object_id' => (is => 'rw'); # undef means keep mesh
has 'mesh' => (is => 'rw', required => 1, trigger => 1);
has 'input_file_object_id' => (is => 'rw'); # undef means keep model object
has 'model_object' => (is => 'rw', required => 1, trigger => 1);
has 'size' => (is => 'rw');
has 'scale' => (is => 'rw', default => sub { 1 });
has 'rotate' => (is => 'rw', default => sub { 0 });
has 'instances' => (is => 'rw', default => sub { [] }); # upward Y axis
has 'thumbnail' => (is => 'rw');
sub _trigger_mesh {
sub _trigger_model_object {
my $self = shift;
$self->size([$self->mesh->size]) if $self->mesh;
$self->size([$self->model_object->mesh->size]) if $self->model_object;
}
sub get_mesh {
sub free_model_object {
my $self = shift;
return $self->mesh->clone if $self->mesh;
# 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->model_object(undef);
}
sub get_model_object {
my $self = shift;
return $self->model_object if $self->model_object;
my $model = Slic3r::Model->read_from_file($self->input_file);
return $model->objects->[$self->input_file_object_id]->mesh;
return $model->objects->[$self->input_file_object_id];
}
sub instances_count {
@ -1035,7 +1056,7 @@ sub make_thumbnail {
my $self = shift;
my %params = @_;
my @points = map [ @$_[X,Y] ], @{$self->mesh->vertices};
my @points = map [ @$_[X,Y] ], @{$self->model_object->mesh->vertices};
my $convex_hull = Slic3r::Polygon->new(convex_hull(\@points));
for (@$convex_hull) {
@$_ = map $_ * $params{scaling_factor}, @$_;
@ -1046,7 +1067,7 @@ sub make_thumbnail {
$convex_hull->align_to_origin;
$self->thumbnail($convex_hull); # ignored in multi-threaded environments
$self->mesh(undef) if defined $self->input_file_object_id;
$self->free_model_object;
return $convex_hull;
}

View File

@ -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.

View File

@ -85,6 +85,12 @@ sub new {
});
EVT_BUTTON($self, $self->{btn_save_preset}, sub {
# since buttons (and choices too) don't get focus on Mac, we set focus manually
# to the treectrl so that the EVT_* events are fired for the input field having
# focus currently. is there anything better than this?
$self->{treectrl}->SetFocus;
my $preset = $self->current_preset;
my $default_name = $preset->{default} ? 'Untitled' : basename($preset->{name});
$default_name =~ s/\.ini$//i;
@ -405,7 +411,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)],
},
]);

View File

@ -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
);
@ -36,7 +36,7 @@ use constant X2 => 2;
use constant Y2 => 3;
use constant MIN => 0;
use constant MAX => 1;
our $parallel_degrees_limit = abs(deg2rad(3));
our $parallel_degrees_limit = abs(deg2rad(0.1));
sub epsilon () { 1E-4 }
sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR }
@ -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 {

View File

@ -14,12 +14,12 @@ our $clipper = Math::Clipper->new;
sub safety_offset {
my ($polygons, $factor) = @_;
return Math::Clipper::offset($polygons, $factor || (scale 1e-05), 100, JT_MITER, 2);
return Math::Clipper::offset($polygons, $factor || (scale 1e-05), 100000, JT_MITER, 2);
}
sub offset {
my ($polygons, $distance, $scale, $joinType, $miterLimit) = @_;
$scale ||= &Slic3r::SCALING_FACTOR * 1000000;
$scale ||= 100000;
$joinType = JT_MITER if !defined $joinType;
$miterLimit ||= 2;

View File

@ -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,494 +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->offset_ex(-1.5*$distance);
push @new_offsets, @offsets;
my $diff = diff_ex(
[ map @$_, $expolygon->offset_ex(-$distance) ],
[ map @$_, @offsets ],
);
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;
# detect the small gaps that we need to treat like thin polygons,
# thus generating the skeleton and using it to fill them
push @{ $self->thin_fills },
map $_->medial_axis(scale $self->perimeter_flow->width),
@gaps;
Slic3r::debugf " %d gaps filled\n", scalar @{ $self->thin_fills }
if @{ $self->thin_fills };
}
}
# 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;

533
lib/Slic3r/Layer/Region.pm Normal file
View File

@ -0,0 +1,533 @@
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->offset_ex(-1.5*$distance);
push @new_offsets, @offsets;
my $diff = diff_ex(
[ map @$_, $expolygon->offset_ex(-$distance) ],
[ map @$_, @offsets ],
);
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;
# detect the small gaps that we need to treat like thin polygons,
# thus generating the skeleton and using it to fill them
push @{ $self->thin_fills },
map $_->medial_axis($self->perimeter_flow->scaled_width),
@gaps;
Slic3r::debugf " %d gaps filled\n", scalar @{ $self->thin_fills }
if @{ $self->thin_fills };
}
}
# 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, 100000, 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;

View File

@ -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,22 @@ 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 || {},
);
}
sub scale {
my $self = shift;
$_->scale(@_) for @{$self->objects};
}
# flattens everything to a single mesh
sub mesh {
my $self = shift;
@ -47,7 +64,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 +83,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;
}
@ -89,6 +118,17 @@ sub mesh {
);
}
sub scale {
my $self = shift;
my ($factor) = @_;
return if $factor == 1;
# transform vertex coordinates
foreach my $vertex (@{$self->vertices}) {
$vertex->[$_] *= $factor for X,Y,Z;
}
}
package Slic3r::Model::Volume;
use Moo;

View File

@ -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 {

View File

@ -4,18 +4,21 @@ use Moo;
use File::Basename qw(basename fileparse);
use File::Spec;
use List::Util qw(max);
use Math::ConvexHull 1.0.4 qw(convex_hull);
use Math::ConvexHull::MonotoneChain 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,87 @@ 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 default flows
$Slic3r::flow = $self->extruders->[0]->make_flow(
width => $self->config->extrusion_width,
);
$Slic3r::first_layer_flow = $self->extruders->[0]->make_flow(
layer_height => $self->config->get_value('first_layer_height'),
width => $self->config->first_layer_extrusion_width,
);
# calculate regions' flows
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 +247,11 @@ sub layer_count {
return $count;
}
sub regions_count {
my $self = shift;
return scalar @{$self->regions};
}
sub duplicate {
my $self = shift;
@ -212,18 +263,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 +283,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 +299,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 +321,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 +336,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 +350,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 +363,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 +373,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,16 +420,16 @@ 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");
$self->make_skirt;
$self->make_brim;
$self->make_brim; # must come after make_skirt
# output everything to a G-code file
my $output_file = $self->expanded_output_filepath($params{output_file});
@ -399,6 +461,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 +501,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 +528,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{ <line x1="%s" y1="%s" x2="%s" y2="%s" style="stroke-width: 2; stroke: white" />\n},
map @$_, $support_point, $anchor_point;
}
@ -488,11 +555,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
@ -500,45 +567,49 @@ sub make_skirt {
my $convex_hull = convex_hull(\@points);
# draw outlines from outside to inside
my $flow = $Slic3r::first_layer_flow || $Slic3r::flow;
my @skirt = ();
for (my $i = $Slic3r::Config->skirts; $i > 0; $i--) {
my $distance = scale ($Slic3r::Config->skirt_distance + ($flow->spacing * $i));
my $distance = scale ($Slic3r::Config->skirt_distance + ($Slic3r::first_layer_flow->spacing * $i));
my $outline = Math::Clipper::offset([$convex_hull], $distance, &Slic3r::SCALING_FACTOR * 100, JT_ROUND);
push @skirt, Slic3r::ExtrusionLoop->pack(
polygon => Slic3r::Polygon->new(@{$outline->[0]}),
role => EXTR_ROLE_SKIRT,
push @{$self->skirt}, Slic3r::ExtrusionLoop->pack(
polygon => Slic3r::Polygon->new(@{$outline->[0]}),
role => EXTR_ROLE_SKIRT,
flow_spacing => $Slic3r::first_layer_flow->spacing,
);
}
unshift @{$self->skirt}, @skirt;
}
sub make_brim {
my $self = shift;
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 = $Slic3r::first_layer_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;
}
}
my $num_loops = sprintf "%.0f", $Slic3r::Config->brim_width / $flow->width;
# if brim touches skirt, make it around skirt too
if ($Slic3r::Config->skirt_distance + (($Slic3r::Config->skirts - 1) * $Slic3r::first_layer_flow->spacing) <= $Slic3r::Config->brim_width) {
push @islands, map $_->unpack->split_at_first_point->polyline->grow($grow_distance), @{$self->skirt};
}
my $num_loops = sprintf "%.0f", $Slic3r::Config->brim_width / $Slic3r::first_layer_flow->width;
for my $i (reverse 1 .. $num_loops) {
# JT_SQUARE ensures no vertex is outside the given offset distance
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)};
polygon => Slic3r::Polygon->new($_),
role => EXTR_ROLE_SKIRT,
flow_spacing => $Slic3r::first_layer_flow->spacing,
) for @{Math::Clipper::offset(\@islands, $i * $Slic3r::first_layer_flow->scaled_spacing, 100, JT_SQUARE)};
# TODO: we need the offset inwards/offset outwards logic to avoid overlapping extrusions
}
}
@ -570,26 +641,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";
@ -618,9 +691,9 @@ sub write_gcode {
my @islands = ();
foreach my $obj_idx (0 .. $#{$self->objects}) {
my @island = Slic3r::ExPolygon->new(convex_hull([
map @{$_->expolygon->contour}, map @{$_->slices}, @{$self->objects->[$obj_idx]->layers},
map @{$_->contour}, map @{$_->slices}, @{$self->objects->[$obj_idx]->layers},
]))->translate(scale $shift[X], scale $shift[Y])->offset_ex(scale $distance_from_objects, 1, JT_SQUARE);
foreach my $copy (@{$self->copies->[$obj_idx]}) {
foreach my $copy (@{ $self->objects->[$obj_idx]->copies }) {
push @islands, map $_->clone->translate(@$copy), @island;
}
}
@ -639,9 +712,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;
@ -656,7 +729,7 @@ sub write_gcode {
$gcodegen->set_shift(@shift);
$gcode .= $gcodegen->set_acceleration($Slic3r::Config->perimeter_acceleration);
# skip skirt if we have a large brim
if ($layer_id < $Slic3r::Config->skirt_height && ($layer_id != 0 || $Slic3r::Config->skirt_distance + (($Slic3r::Config->skirts - 1) * $Slic3r::flow->spacing) > $Slic3r::Config->brim_width)) {
if ($layer_id < $Slic3r::Config->skirt_height) {
$gcode .= $gcodegen->extrude_loop($_, 'skirt') for @{$self->skirt};
}
$skirt_done++;
@ -665,7 +738,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};
@ -687,25 +760,30 @@ sub write_gcode {
$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);
}
@ -759,7 +837,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.
@ -790,7 +868,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);
}
@ -814,7 +892,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

View File

@ -2,20 +2,16 @@ package Slic3r::Print::Object;
use Moo;
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Geometry qw(scale unscale deg2rad);
use Slic3r::Geometry qw(scale unscale deg2rad scaled_epsilon);
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 - scaled_epsilon) ];
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 ]};

View File

@ -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;

View File

@ -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 {

View File

@ -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})

View File

@ -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,

View File

@ -25,12 +25,14 @@ my %opt = ();
{
my $input_file = $ARGV[0];
my $mesh;
$mesh = Slic3r::Format::STL->read_file($input_file) if $input_file =~ /\.stl$/i;
die "This script doesn't support AMF yet\n" if $input_file =~ /\.amf$/i;
die "Unable to read file\n" if !$mesh;
my $model;
$model = Slic3r::Format::STL->read_file($input_file) if $input_file =~ /\.stl$/i;
die "Unable to read file\n" if !$model;
printf "Info about %s:\n", basename($input_file);
my $mesh = $model->mesh;
$mesh->check_manifoldness;
printf " number of facets: %d\n", scalar @{$mesh->facets};
printf " size: x=%s y=%s z=%s\n", $mesh->size;

View File

@ -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;
}
}

View File

@ -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 $!;
#}
}

View File

@ -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 {