Merge branch 'master' into internal-support

internal-support
Alessandro Ranellucci 2013-06-16 11:02:36 +02:00
commit 0f9e9246d9
71 changed files with 3215 additions and 1350 deletions

View File

@ -7,18 +7,19 @@ my $build = Module::Build->new(
dist_version => '0.1',
license => 'perl',
requires => {
'Boost::Geometry::Utils' => '0.06',
'Boost::Geometry::Utils' => '0.12',
'Encode::Locale' => '0',
'File::Basename' => '0',
'File::Spec' => '0',
'Getopt::Long' => '0',
'Math::Clipper' => '1.17',
'Math::Clipper' => '1.22',
'Math::ConvexHull::MonotoneChain' => '0.01',
'Math::Geometry::Voronoi' => '1.3',
'Math::PlanePath' => '53',
'Moo' => '0.091009',
'perl' => '5.10.0',
'Scalar::Util' => '0',
'Storable' => '0',
'Time::HiRes' => '0',
},
build_requires => {
@ -28,7 +29,6 @@ my $build = Module::Build->new(
recommends => {
'Class::XSAccessor' => '0',
'Growl::GNTP' => '0.15',
'Net::DBus' => '0',
'XML::SAX::ExpatXS' => '0',
'Wx' => '0.9901',
},

View File

@ -24,8 +24,13 @@ lib/Slic3r/Format/AMF/Parser.pm
lib/Slic3r/Format/OBJ.pm
lib/Slic3r/Format/STL.pm
lib/Slic3r/GCode.pm
lib/Slic3r/GCode/CoolingBuffer.pm
lib/Slic3r/GCode/Layer.pm
lib/Slic3r/GCode/MotionPlanner.pm
lib/Slic3r/GCode/Reader.pm
lib/Slic3r/GCode/SpiralVase.pm
lib/Slic3r/Geometry.pm
lib/Slic3r/Geometry/BoundingBox.pm
lib/Slic3r/Geometry/Clipper.pm
lib/Slic3r/GUI.pm
lib/Slic3r/GUI/AboutDialog.pm
@ -34,6 +39,7 @@ lib/Slic3r/GUI/OptionsGroup.pm
lib/Slic3r/GUI/Plater.pm
lib/Slic3r/GUI/Plater/ObjectDialog.pm
lib/Slic3r/GUI/Preferences.pm
lib/Slic3r/GUI/PreviewCanvas.pm
lib/Slic3r/GUI/SkeinPanel.pm
lib/Slic3r/GUI/SimpleTab.pm
lib/Slic3r/GUI/Tab.pm
@ -50,6 +56,7 @@ lib/Slic3r/Print/Object.pm
lib/Slic3r/Surface.pm
lib/Slic3r/SVG.pm
lib/Slic3r/Test.pm
lib/Slic3r/Test/SectionCut.pm
lib/Slic3r/TriangleMesh.pm
MANIFEST This list of files
README.markdown
@ -60,6 +67,7 @@ t/clean_polylines.t
t/clipper.t
t/collinear.t
t/combineinfill.t
t/cooling.t
t/custom_gcode.t
t/dynamic.t
t/fill.t
@ -68,13 +76,18 @@ t/geometry.t
t/layers.t
t/loops.t
t/polyclip.t
t/print.t
t/retraction.t
t/serialize.t
t/shells.t
t/slice.t
t/skirt_brim.t
t/support.t
t/svg.t
t/vibrationlimit.t
utils/amf-to-stl.pl
utils/file_info.pl
utils/gcode_sectioncut.pl
utils/post-processing/filament-weight.pl
utils/post-processing/prowl-notification.pl
utils/post-processing/z-every-line.pl
@ -82,6 +95,7 @@ utils/post-processing/decimate.pl
utils/post-processing/flowrate.pl
utils/split_stl.pl
utils/stl-to-amf.pl
utils/view-mesh.pl
utils/zsh/functions/_slic3r
utils/zsh/README.markdown
var/add.png

View File

@ -7,7 +7,7 @@ A: Yes.
## What's it?
Slic3r is a G-code generator for 3D printers. It's compatible with RepRaps,
Makerbots, Ultimakers and many more machines.
makerwares, Ultimakers and many more machines.
See the [project homepage](http://slic3r.org/) at slic3r.org and the
[documentation](https://github.com/alexrj/Slic3r/wiki/Documentation) on the Slic3r wiki for more information.
@ -93,6 +93,7 @@ The author of the Silk icon set is Mark James.
GUI options:
--no-plater Disable the plater tab
--gui-mode Overrides the configured mode (simple/expert)
--autosave <file> Automatically export current configuration to the specified file
Output options:
--output-filename-format
@ -111,7 +112,7 @@ The author of the Silk icon set is Mark James.
(default: 100,100)
--z-offset Additional height in mm to add to vertical coordinates
(+/-, default: 0)
--gcode-flavor The type of G-code to generate (reprap/teacup/makerbot/sailfish/mach3/no-extrusion,
--gcode-flavor The type of G-code to generate (reprap/teacup/makerware/sailfish/mach3/no-extrusion,
default: reprap)
--use-relative-e-distances Enable this to get relative E values
--gcode-arcs Use G2/G3 commands for native arcs (experimental, not supported
@ -198,6 +199,8 @@ The author of the Silk icon set is Mark James.
--randomize-start Randomize starting point across layers (default: yes)
--avoid-crossing-perimeters Optimize travel moves so that no perimeters are crossed (default: no)
--external-perimeters-first Reverse perimeter order. (default: no)
--spiral-vase Experimental option to raise Z gradually when printing single-walled vases
(default: no)
--only-retract-when-crossing-perimeters
Disable retraction when travelling between infill paths inside the same island.
(default: yes)
@ -240,6 +243,7 @@ The author of the Silk icon set is Mark James.
--retract-lift Lift Z by the given distance in mm when retracting (default: 0)
--retract-layer-change
Enforce a retraction before each Z move (default: yes)
--wipe Wipe the nozzle while doing a retraction (default: no)
Retraction options for multi-extruder setups:
--retract-length-toolchange

View File

@ -7,7 +7,7 @@ use strict;
use warnings;
require v5.10;
our $VERSION = "0.9.9-dev";
our $VERSION = "0.9.11-dev";
our $debug = 0;
sub debugf {
@ -29,7 +29,7 @@ our $var = "$FindBin::Bin/var";
use Encode;
use Encode::Locale;
use Boost::Geometry::Utils 0.06;
use Boost::Geometry::Utils 0.12;
use Moo 0.091009;
use Slic3r::Config;
@ -45,8 +45,14 @@ use Slic3r::Format::AMF;
use Slic3r::Format::OBJ;
use Slic3r::Format::STL;
use Slic3r::GCode;
use Slic3r::GCode::CoolingBuffer;
use Slic3r::GCode::Layer;
use Slic3r::GCode::MotionPlanner;
use Slic3r::GCode::Reader;
use Slic3r::GCode::SpiralVase;
use Slic3r::Geometry qw(PI);
use Slic3r::Geometry::BoundingBox;
use Slic3r::Geometry::Clipper;
use Slic3r::Layer;
use Slic3r::Layer::Region;
use Slic3r::Line;
@ -59,7 +65,7 @@ use Slic3r::Print::Object;
use Slic3r::Print::Region;
use Slic3r::Surface;
use Slic3r::TriangleMesh;
eval "use Slic3r::Build";
our $build = eval "use Slic3r::Build; 1";
use constant SCALING_FACTOR => 0.000001;
use constant RESOLUTION => 0.0125;
@ -80,6 +86,7 @@ sub parallelize {
$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) {
$params{collect_cb}->($th->join);
}

View File

@ -9,6 +9,7 @@ use List::Util qw(first);
our @Ignore = qw(duplicate_x duplicate_y multiply_x multiply_y support_material_tool acceleration);
my $serialize_comma = sub { join ',', @{$_[0]} };
my $serialize_comma_bool = sub { join ',', map $_ // 0, @{$_[0]} };
my $deserialize_comma = sub { [ split /,/, $_[0] ] };
our $Options = {
@ -73,8 +74,8 @@ our $Options = {
tooltip => 'Some G/M-code commands, including temperature control and others, are not universal. Set this option to your printer\'s firmware to get a compatible output. The "No extrusion" flavor prevents Slic3r from exporting any extrusion value at all.',
cli => 'gcode-flavor=s',
type => 'select',
values => [qw(reprap teacup makerbot sailfish mach3 no-extrusion)],
labels => ['RepRap (Marlin/Sprinter)', 'Teacup', 'MakerBot', 'Sailfish', 'Mach3/EMC', 'No extrusion'],
values => [qw(reprap teacup makerware sailfish mach3 no-extrusion)],
labels => ['RepRap (Marlin/Sprinter/Repetier)', 'Teacup', 'MakerWare (MakerBot)', 'Sailfish (MakerBot)', 'Mach3/EMC', 'No extrusion'],
default => 'reprap',
},
'use_relative_e_distances' => {
@ -593,9 +594,16 @@ our $Options = {
type => 'bool',
default => 0,
},
'spiral_vase' => {
label => 'Spiral vase',
tooltip => 'This experimental feature will raise Z gradually while printing a single-walled object in order to remove any visible seam. By enabling this option other settings will be overridden to enforce a single perimeter, no infill, no top solid layers, no support material. You can still set any number of bottom solid layers as well as skirt/brim loops. It won\'t work when printing more than an object.',
cli => 'spiral-vase!',
type => 'bool',
default => 0,
},
'only_retract_when_crossing_perimeters' => {
label => 'Only retract when crossing perimeters',
tooltip => 'Disables retraction when travelling between infill paths inside the same island.',
tooltip => 'Disables retraction when the travel path does not exceed the upper layer\'s perimeters (and thus any ooze will be probably invisible).',
cli => 'only-retract-when-crossing-perimeters!',
type => 'bool',
default => 1,
@ -797,10 +805,19 @@ END
tooltip => 'This flag enforces a retraction whenever a Z move is done.',
cli => 'retract-layer-change!',
type => 'bool',
serialize => $serialize_comma,
serialize => $serialize_comma_bool,
deserialize => $deserialize_comma,
default => [1],
},
'wipe' => {
label => 'Wipe before retract',
tooltip => 'This flag will move the nozzle while retracting to minimize the possible blob on leaky extruders.',
cli => 'wipe!',
type => 'bool',
serialize => $serialize_comma_bool,
deserialize => $deserialize_comma,
default => [0],
},
'retract_length_toolchange' => {
label => 'Length',
tooltip => 'When retraction is triggered before changing tool, filament is pulled back by the specified amount (the length is measured on raw filament, before it enters the extruder).',
@ -824,8 +841,8 @@ END
# cooling options
'cooling' => {
label => 'Enable cooling',
tooltip => 'This flag enables all the cooling features.',
label => 'Enable auto cooling',
tooltip => 'This flag enables the automatic cooling logic that adjusts print speed and fan speed according to layer printing time.',
cli => 'cooling!',
type => 'bool',
default => 1,
@ -1072,7 +1089,7 @@ sub new_from_cli {
}
$args{$_} = $Options->{$_}{deserialize}->($args{$_})
for grep exists $args{$_}, qw(print_center bed_size duplicate_grid extruder_offset retract_layer_change);
for grep exists $args{$_}, qw(print_center bed_size duplicate_grid extruder_offset retract_layer_change wipe);
return $class->new(%args);
}
@ -1131,7 +1148,7 @@ sub set {
my ($opt_key, $value, $deserialize) = @_;
# handle legacy options
return if $opt_key ~~ @Ignore;
return if first { $_ eq $opt_key } @Ignore;
if ($opt_key =~ /^(extrusion_width|bottom_layer_speed|first_layer_height)_ratio$/) {
$opt_key = $1;
$opt_key =~ s/^bottom_layer_speed$/first_layer_speed/;
@ -1140,6 +1157,9 @@ sub set {
if ($opt_key eq 'threads' && !$Slic3r::have_threads) {
$value = 1;
}
if ($opt_key eq 'gcode_flavor' && $value eq 'makerbot') {
$value = 'makerware';
}
# For historical reasons, the world's full of configs having these very low values;
# to avoid unexpected behavior we need to ignore them. Banning these two hard-coded
@ -1261,6 +1281,10 @@ sub validate {
die "Invalid value for --top-solid-layers\n" if $self->top_solid_layers < 0;
die "Invalid value for --bottom-solid-layers\n" if $self->bottom_solid_layers < 0;
# --gcode-flavor
die "Invalid value for --gcode-flavor\n"
if !first { $_ eq $self->gcode_flavor } @{$Options->{gcode_flavor}{values}};
# --print-center
die "Invalid value for --print-center\n"
if !ref $self->print_center
@ -1319,6 +1343,10 @@ sub validate {
if $self->extruder_clearance_radius <= 0;
die "Invalid value for --extruder-clearance-height\n"
if $self->extruder_clearance_height <= 0;
# --extrusion-multiplier
die "Invalid value for --extrusion-multiplier\n"
if defined first { $_ <= 0 } @{$self->extrusion_multiplier};
}
sub replace_options {
@ -1399,7 +1427,7 @@ sub read_ini {
my $ini = { _ => {} };
my $category = '_';
while (my $_ = <$fh>) {
while (<$fh>) {
s/\R+$//;
next if /^\s+/;
next if /^$/;

View File

@ -7,7 +7,7 @@ use warnings;
use Boost::Geometry::Utils;
use List::Util qw(first);
use Math::Geometry::Voronoi;
use Slic3r::Geometry qw(X Y A B point_in_polygon same_line line_length epsilon);
use Slic3r::Geometry qw(X Y A B point_in_polygon same_line epsilon);
use Slic3r::Geometry::Clipper qw(union_ex JT_MITER);
# the constructor accepts an array of polygons
@ -62,9 +62,15 @@ sub is_valid {
&& (!first { $_->is_counter_clockwise } $self->holes);
}
sub boost_polygon {
# returns false if the expolygon is too tight to be printed
sub is_printable {
my $self = shift;
return Boost::Geometry::Utils::polygon(@$self);
my ($width) = @_;
# try to get an inwards offset
# for a distance equal to half of the extrusion width;
# if no offset is possible, then expolygon is not printable.
return Slic3r::Geometry::Clipper::offset($self, -$width / 2) ? 1 : 0;
}
sub wkt {
@ -85,15 +91,7 @@ sub offset_ex {
sub safety_offset {
my $self = shift;
# we're offsetting contour and holes separately
# because Clipper doesn't return polygons in the same order as
# we feed them to it
return (ref $self)->new(
$self->contour->safety_offset,
@{ Slic3r::Geometry::Clipper::safety_offset([$self->holes]) },
);
return Slic3r::Geometry::Clipper::safety_offset_ex($self, @_);
}
sub noncollapsing_offset_ex {
@ -128,7 +126,7 @@ sub encloses_line {
# optimization
return @$clip == 1 && same_line($clip->[0], $line);
} else {
return @$clip == 1 && abs(line_length($clip->[0]) - $line->length) < $tolerance;
return @$clip == 1 && abs(Boost::Geometry::Utils::linestring_length($clip->[0]) - $line->length) < $tolerance;
}
}
@ -158,11 +156,6 @@ sub bounding_box_polygon {
]);
}
sub bounding_box_center {
my $self = shift;
return Slic3r::Geometry::bounding_box_center($self->contour);
}
sub clip_line {
my $self = shift;
my ($line) = @_; # line must be a Slic3r::Line object
@ -342,11 +335,25 @@ sub align_to_origin {
my @bb = Slic3r::Geometry::bounding_box([ map @$_, map @$_, @{$self->expolygons} ]);
$_->translate(-$bb[X1], -$bb[Y1]) for @{$self->expolygons};
$self;
}
sub scale {
my $self = shift;
$_->scale(@_) for @{$self->expolygons};
$self;
}
sub rotate {
my $self = shift;
$_->rotate(@_) for @{$self->expolygons};
$self;
}
sub translate {
my $self = shift;
$_->translate(@_) for @{$self->expolygons};
$self;
}
sub size {

View File

@ -1,23 +1,25 @@
package Slic3r::Extruder;
use Moo;
use Slic3r::Geometry qw(PI);
use Slic3r::Geometry qw(PI scale);
use constant OPTIONS => [qw(
extruder_offset
nozzle_diameter filament_diameter extrusion_multiplier temperature first_layer_temperature
retract_length retract_lift retract_speed retract_restart_extra retract_before_travel
retract_layer_change retract_length_toolchange retract_restart_extra_toolchange
retract_layer_change retract_length_toolchange retract_restart_extra_toolchange wipe
)];
has 'id' => (is => 'rw', required => 1);
has $_ => (is => 'ro', required => 1) for @{&OPTIONS};
has 'bridge_flow' => (is => 'lazy');
has 'e' => (is => 'rw', default => sub {0} );
has 'retracted' => (is => 'rw', default => sub {0} );
has 'restart_extra' => (is => 'rw', default => sub {0} );
has 'e_per_mm3' => (is => 'lazy');
has 'retract_speed_mm_min' => (is => 'lazy');
has 'scaled_wipe_distance' => (is => 'lazy'); # scaled mm
has '_mm3_per_mm_cache' => (is => 'ro', default => sub {{}});
sub _build_bridge_flow {
@ -35,6 +37,13 @@ sub _build_retract_speed_mm_min {
return $self->retract_speed * 60;
}
sub _build_scaled_wipe_distance {
my $self = shift;
# reduce feedrate a bit; travel speed is often too high to move on existing material
# too fast = ripping of existing material; too slow = short wipe path, thus more blob
return scale($self->retract_length / $self->retract_speed * $Slic3r::Config->travel_speed * 0.8);
}
sub make_flow {
my $self = shift;
return Slic3r::Flow->new(nozzle_diameter => $self->nozzle_diameter, @_);

View File

@ -4,7 +4,7 @@ use Moo;
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(EXTR_ROLE_PERIMETER EXTR_ROLE_EXTERNAL_PERIMETER
EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER
EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER EXTR_ROLE_OVERHANG_PERIMETER
EXTR_ROLE_FILL EXTR_ROLE_SOLIDFILL EXTR_ROLE_TOPSOLIDFILL EXTR_ROLE_BRIDGE
EXTR_ROLE_INTERNALBRIDGE EXTR_ROLE_SKIRT EXTR_ROLE_SUPPORTMATERIAL EXTR_ROLE_GAPFILL);
our %EXPORT_TAGS = (roles => \@EXPORT_OK);
@ -20,11 +20,12 @@ has 'polyline' => (
# height is the vertical thickness of the extrusion expressed in mm
has 'height' => (is => 'rw');
has 'flow_spacing' => (is => 'rw');
has 'flow_spacing' => (is => 'rw', required => 1);
has 'role' => (is => 'rw', required => 1);
use constant EXTR_ROLE_PERIMETER => 0;
use constant EXTR_ROLE_EXTERNAL_PERIMETER => 2;
use constant EXTR_ROLE_EXTERNAL_PERIMETER => 1;
use constant EXTR_ROLE_OVERHANG_PERIMETER => 2;
use constant EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER => 3;
use constant EXTR_ROLE_FILL => 4;
use constant EXTR_ROLE_SOLIDFILL => 5;
@ -97,12 +98,11 @@ sub first_point {
return $self->polyline->[0];
}
sub is_printable { 1 }
sub is_perimeter {
my $self = shift;
return $self->role == EXTR_ROLE_PERIMETER
|| $self->role == EXTR_ROLE_EXTERNAL_PERIMETER
|| $self->role == EXTR_ROLE_OVERHANG_PERIMETER
|| $self->role == EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER;
}
@ -113,6 +113,13 @@ sub is_fill {
|| $self->role == EXTR_ROLE_TOPSOLIDFILL;
}
sub is_bridge {
my $self = shift;
return $self->role == EXTR_ROLE_BRIDGE
|| $self->role == EXTR_ROLE_INTERNALBRIDGE
|| $self->role == EXTR_ROLE_OVERHANG_PERIMETER;
}
sub split_at_acute_angles {
my $self = shift;
@ -237,6 +244,7 @@ sub detect_arcs {
my $arc = Slic3r::ExtrusionPath::Arc->new(
polyline => Slic3r::Polyline->new(\@arc_points),
role => $self->role,
flow_spacing => $self->flow_spacing,
orientation => $orientation,
center => $arc_center,
radius => $arc_center->distance_to($points[$i]),
@ -246,6 +254,7 @@ sub detect_arcs {
push @paths, (ref $self)->new(
polyline => Slic3r::Polyline->new(@points[0..$i]),
role => $self->role,
flow_spacing => $self->flow_spacing,
height => $self->height,
) if $i > 0;
@ -265,6 +274,7 @@ sub detect_arcs {
push @paths, (ref $self)->new(
polyline => Slic3r::Polyline->new(\@points),
role => $self->role,
flow_spacing => $self->flow_spacing,
height => $self->height,
) if @points > 1;

View File

@ -2,6 +2,7 @@ package Slic3r::ExtrusionPath::Collection;
use Moo;
has 'paths' => (is => 'rw', default => sub { [] });
has 'no_sort' => (is => 'rw');
# no-op
sub unpack { $_[0] }
@ -15,6 +16,8 @@ sub chained_path {
my $self = shift;
my ($start_near) = @_;
return @{$self->paths} if $self->no_sort;
# make sure we pass the same path objects to the Collection constructor
# and the ->chained_path() method because the latter will reverse the
# paths in-place when needed and we need to return them that way

View File

@ -17,7 +17,7 @@ use Slic3r::Geometry::Clipper qw(union_ex diff diff_ex intersection_ex offset);
use Slic3r::Surface ':types';
has 'print' => (is => 'ro', required => 1);
has 'object' => (is => 'ro', required => 1, weak_ref => 1);
has 'fillers' => (is => 'rw', default => sub { {} });
our %FillTypes = (
@ -39,10 +39,9 @@ sub filler {
return $FillTypes{$filler}->new;
}
if (!$self->fillers->{$filler}) {
my $f = $self->fillers->{$filler} = $FillTypes{$filler}->new;
$f->bounding_box([ $self->print->bounding_box ]) if $f->can('bounding_box');
}
$self->fillers->{$filler} ||= $FillTypes{$filler}->new(
bounding_box => [ $self->object->bounding_box ],
);
return $self->fillers->{$filler};
}
@ -179,6 +178,7 @@ sub make_fill {
# save into layer
next unless @paths;
push @fills, Slic3r::ExtrusionPath::Collection->new(
no_sort => $params->{no_sort},
paths => [
map Slic3r::ExtrusionPath->pack(
polyline => Slic3r::Polyline->new(@$_),

View File

@ -5,6 +5,7 @@ use Slic3r::Geometry qw(PI);
has 'layer_id' => (is => 'rw');
has 'angle' => (is => 'rw', default => sub { $Slic3r::Config->fill_angle });
has 'bounding_box' => (is => 'ro', required => 1);
sub angles () { [0, PI/2] }
@ -15,7 +16,7 @@ sub infill_direction {
# set infill angle
my (@rotate, @shift);
$rotate[0] = Slic3r::Geometry::deg2rad($self->angle);
$rotate[1] = $surface->expolygon->bounding_box_center;
$rotate[1] = Slic3r::Geometry::bounding_box_center($self->bounding_box);
@shift = @{$rotate[1]};
if (defined $self->layer_id) {
@ -35,29 +36,24 @@ sub infill_direction {
return [\@rotate, \@shift];
}
# this method accepts any object that implements rotate() and translate()
sub rotate_points {
my $self = shift;
my ($expolygon, $rotate_vector) = @_;
my @rotate = @{$rotate_vector->[0]};
my @shift = @{$rotate_vector->[1]};
# rotate points as needed
if ($rotate[0]) {
$expolygon->rotate(@rotate);
$expolygon->translate(@shift);
}
# rotate points
$expolygon->rotate(@{$rotate_vector->[0]});
$expolygon->translate(@{$rotate_vector->[1]});
}
sub rotate_points_back {
my $self = shift;
my ($paths, $rotate_vector) = @_;
my @rotate = @{$rotate_vector->[0]};
my @shift = @{$rotate_vector->[1]};
my @rotate = (-$rotate_vector->[0][0], $rotate_vector->[0][1]);
my $shift = [ map -$_, @{$rotate_vector->[1]} ];
if ($rotate[0]) {
@$paths = map [ Slic3r::Geometry::rotate_points(-$rotate[0], $rotate[1], @$_) ],
map [ Slic3r::Geometry::move_points([map -$_, @shift], @$_) ], @$paths;
}
@$paths = map [ Slic3r::Geometry::rotate_points(@rotate, @$_) ],
map [ Slic3r::Geometry::move_points($shift, @$_) ], @$paths;
}
sub adjust_solid_spacing {

View File

@ -3,8 +3,8 @@ use Moo;
extends 'Slic3r::Fill::Base';
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Geometry qw(scale unscale X1 Y1 X2 Y2);
use Slic3r::Geometry qw(scale unscale X1 X2);
use Slic3r::Geometry::Clipper qw(offset2 union_pt traverse_pt PFT_EVENODD);
sub fill_surface {
my $self = shift;
@ -27,48 +27,22 @@ sub fill_surface {
$flow_spacing = unscale $distance;
}
my @contour_loops = ();
my @hole_loops = ();
my @last_offsets = ($expolygon->offset_ex($distance));
while (@last_offsets) {
my @new_offsets = ();
foreach my $last_expolygon (@last_offsets) {
my @offsets = $last_expolygon->offset_ex(-$distance);
foreach my $offset (@offsets) {
push @new_offsets, $offset;
push @contour_loops, $offset->contour;
push @hole_loops, $offset->holes;
}
}
@last_offsets = @new_offsets;
my @loops = my @last = @$expolygon;
while (@last) {
push @loops, @last = offset2(\@last, -1.5*$distance, +0.5*$distance);
}
my @loops = (@contour_loops, reverse @hole_loops);
# generate paths from the outermost to the innermost, to avoid
# adhesion problems of the first central tiny loops
my @paths = map Slic3r::Polygon->new(@$_)->split_at_first_point,
reverse traverse_pt( union_pt(\@loops, PFT_EVENODD) );
# make paths
my @paths = ();
my $cur_pos = Slic3r::Point->new(
($bounding_box->[X1] + $bounding_box->[X2]) / 2,
($bounding_box->[Y1] + $bounding_box->[Y2]) / 2,
);
foreach my $loop (@loops) {
# extrude all loops ccw
$loop->make_counter_clockwise;
# find the point of the loop that is closest to the current extruder position
my $index = $loop->nearest_point_index_to($cur_pos);
$cur_pos = $loop->[0];
# split the loop at the starting point and make a path
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 $flow_spacing * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_SPACING);
push @paths, $path if @$path;
}
# clip the paths to avoid the extruder to get exactly on the first point of the loop
my $clip_length = scale $flow_spacing * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_SPACING;
$_->clip_end($clip_length) for @paths;
return { flow_spacing => $flow_spacing }, @paths;
# TODO: return ExtrusionLoop objects to get better chained paths
return { flow_spacing => $flow_spacing, no_sort => 1 }, @paths;
}
1;

View File

@ -3,7 +3,6 @@ use Moo;
extends 'Slic3r::Fill::Base';
has 'bounding_box' => (is => 'rw');
has 'cache' => (is => 'rw', default => sub {{}});
use Slic3r::Geometry qw(PI X1 Y1 X2 Y2 X Y scale);
@ -25,7 +24,7 @@ sub fill_surface {
my $cache_id = sprintf "d%s_s%s_a%s",
$params{density}, $params{flow_spacing}, $rotate_vector->[0][0];
if (!$self->cache->{$cache_id} || !defined $self->bounding_box) {
if (!$self->cache->{$cache_id}) {
# hexagons math
my $hex_side = $distance / (sqrt(3)/2);
@ -39,10 +38,12 @@ sub fill_surface {
# adjust actual bounding box to the nearest multiple of our hex pattern
# and align it so that it matches across layers
my $bounding_box = [ $self->bounding_box ? @{$self->bounding_box} : $expolygon->bounding_box ];
my $bounding_box = [ @{$self->bounding_box} ]; # clone
$bounding_box->[$_] = 0 for X1, Y1;
{
my $bb_polygon = Slic3r::Polygon->new_from_bounding_box($bounding_box);
$bb_polygon->scale(sqrt 2);
$bb_polygon->rotate($rotate_vector->[0][0], $hex_center);
$bounding_box = [ Slic3r::Geometry::bounding_box($bb_polygon) ];
# $bounding_box->[X1] and [Y1] represent the displacement between new bounding box offset and old one
@ -78,13 +79,14 @@ sub fill_surface {
$self->cache->{$cache_id} = [@polygons];
}
# build polylines from polygons without re-appending the initial point:
# consider polygons as polylines without re-appending the initial point:
# this cuts the last segment on purpose, so that the jump to the next
# path is more straight
my @paths = map Slic3r::Polyline->new(@$_), map @$_, @{intersection_ex(
$self->cache->{$cache_id},
$expolygon,
)};
my @paths = map Slic3r::Polyline->new($_),
@{ Boost::Geometry::Utils::polygon_multi_linestring_intersection(
$expolygon,
$self->cache->{$cache_id},
) };
return { flow_spacing => $params{flow_spacing} },
Slic3r::Polyline::Collection->new(polylines => \@paths)->chained_path;

View File

@ -3,6 +3,8 @@ use Moo;
extends 'Slic3r::Fill::Base';
has 'cache' => (is => 'rw', default => sub {{}});
use Slic3r::Geometry qw(X1 Y1 X2 Y2 A B X Y scale unscale scaled_epsilon);
sub fill_surface {
@ -16,39 +18,56 @@ sub fill_surface {
my ($expolygon_off) = $expolygon->offset_ex(scale $params{flow_spacing}/2);
return {} if !$expolygon_off; # skip some very small polygons (which shouldn't arrive here)
my $bounding_box = [ $expolygon->bounding_box ];
my $flow_spacing = $params{flow_spacing};
my $min_spacing = scale $params{flow_spacing};
my $distance_between_lines = $min_spacing / $params{density};
my $line_oscillation = $distance_between_lines - $min_spacing;
my $flow_spacing = $params{flow_spacing};
if ($params{density} == 1 && !$params{dont_adjust}) {
$distance_between_lines = $self->adjust_solid_spacing(
width => $bounding_box->[X2] - $bounding_box->[X1],
distance => $distance_between_lines,
);
$flow_spacing = unscale $distance_between_lines;
}
my $x = $bounding_box->[X1];
my $is_line_pattern = $self->isa('Slic3r::Fill::Line');
my @vertical_lines = ();
for (my $i = 0; $x <= $bounding_box->[X2] + scaled_epsilon; $i++) {
my $vertical_line = Slic3r::Line->new([$x, $bounding_box->[Y2]], [$x, $bounding_box->[Y1]]);
if ($is_line_pattern && $i % 2) {
$vertical_line->[A][X] += $line_oscillation;
$vertical_line->[B][X] -= $line_oscillation;
my $cache_id = sprintf "d%s_s%.2f_a%.2f",
$params{density}, $params{flow_spacing}, $rotate_vector->[0][0];
if (!$self->cache->{$cache_id}) {
# compute bounding box
my $bounding_box;
{
my $bb_polygon = Slic3r::Polygon->new_from_bounding_box($self->bounding_box);
$bb_polygon->scale(sqrt 2);
$self->rotate_points($bb_polygon, $rotate_vector);
$bounding_box = [ $bb_polygon->bounding_box ];
}
push @vertical_lines, $vertical_line;
$x += $distance_between_lines;
# define flow spacing according to requested density
if ($params{density} == 1 && !$params{dont_adjust}) {
$distance_between_lines = $self->adjust_solid_spacing(
width => $bounding_box->[X2] - $bounding_box->[X1],
distance => $distance_between_lines,
);
$flow_spacing = unscale $distance_between_lines;
}
# generate the basic pattern
my $x = $bounding_box->[X1];
my @vertical_lines = ();
for (my $i = 0; $x <= $bounding_box->[X2] + scaled_epsilon; $i++) {
my $vertical_line = Slic3r::Line->new([$x, $bounding_box->[Y2]], [$x, $bounding_box->[Y1]]);
if ($is_line_pattern && $i % 2) {
$vertical_line->[A][X] += $line_oscillation;
$vertical_line->[B][X] -= $line_oscillation;
}
push @vertical_lines, $vertical_line;
$x += $distance_between_lines;
}
$self->cache->{$cache_id} = [@vertical_lines];
}
# clip paths against a slightly offsetted expolygon, so that the first and last paths
# are kept even if the expolygon has vertical sides
my @paths = @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection(
+($expolygon->offset_ex(scaled_epsilon))[0], # TODO: we should use all the resulting expolygons and clip the linestrings to a multipolygon object
[ @vertical_lines ],
[ @{ $self->cache->{$cache_id} } ],
) };
# connect lines

View File

@ -38,7 +38,9 @@ sub _build_width {
my $min = $self->nozzle_diameter * 1.05;
my $max;
if ($self->role ne 'infill') {
if ($self->role eq 'perimeter') {
$min = $max = $self->nozzle_diameter;
} elsif ($self->role ne 'infill') {
# do not limit width for sparse infill so that we use full native flow for it
$max = $self->nozzle_diameter * 1.7;
}
@ -96,8 +98,7 @@ sub _build_width {
sub _build_spacing {
my $self = shift;
my $width = $self->width;
return $width + &Slic3r::OVERLAP_FACTOR * ($width * PI / 4 - $width);
return $self->width + 0.05;
}
1;

View File

@ -8,7 +8,7 @@ sub read_file {
Slic3r::open(\my $fh, '<', $file) or die "Failed to open $file\n";
my $vertices = [];
my $facets = [];
while (my $_ = <$fh>) {
while (<$fh>) {
if (/^v ([^ ]+)\s+([^ ]+)\s+([^ ]+)/) {
push @$vertices, [$1, $2, $3];
} elsif (/^f (\d+).*? (\d+).*? (\d+).*?/) {

View File

@ -33,26 +33,11 @@ sub read_file {
}
my $facets = [];
$mode eq 'ascii'
? _read_ascii($fh, $facets)
: _read_binary($fh, $facets);
close $fh;
my $vertices = [];
{
my %vertices_map = (); # given a vertex's coordinates, what's its index?
for (my $f = 0; $f <= $#$facets; $f++) {
for (-3..-1) {
my $point_id = join ',', @{$facets->[$f][$_]};
if (exists $vertices_map{$point_id}) {
$facets->[$f][$_] = $vertices_map{$point_id};
} else {
push @$vertices, $facets->[$f][$_];
$facets->[$f][$_] = $vertices_map{$point_id} = $#$vertices;
}
}
}
}
$mode eq 'ascii'
? _read_ascii($fh, $facets, $vertices)
: _read_binary($fh, $facets, $vertices);
close $fh;
my $model = Slic3r::Model->new;
my $object = $model->add_object(vertices => $vertices);
@ -61,23 +46,32 @@ sub read_file {
}
sub _read_ascii {
my ($fh, $facets) = @_;
my ($fh, $facets, $vertices) = @_;
my $point_re = qr/([^ ]+)\s+([^ ]+)\s+([^ ]+)/;
my $point_re = qr/(([^ ]+)\s+([^ ]+)\s+([^ ]+))/;
my $facet;
my %vertices_map = ();
seek $fh, 0, 0;
while (my $_ = <$fh>) {
while (<$fh>) {
if (!$facet) {
/^\s*facet\s+normal\s+$point_re/ or next;
$facet = []; # ignore normal: [$1, $2, $3]
/^\s*facet\s+normal\s+/ or next;
$facet = []; # ignore normal
} else {
if (/^\s*endfacet/) {
push @$facets, $facet;
undef $facet;
} else {
/^\s*vertex\s+$point_re/o or next;
push @$facet, [map $_ * 1, $1, $2, $3];
my $vertex_id = $1;
my $vertex_idx;
if (exists $vertices_map{$vertex_id}) {
$vertex_idx = $vertices_map{$vertex_id};
} else {
push @$vertices, [map $_ * 1, $2, $3, $4];
$vertex_idx = $vertices_map{$vertex_id} = $#$vertices;
}
push @$facet, $vertex_idx;
}
}
}
@ -87,15 +81,25 @@ sub _read_ascii {
}
sub _read_binary {
my ($fh, $facets) = @_;
my ($fh, $facets, $vertices) = @_;
die "bigfloat" unless length(pack "f", 1) == 4;
my %vertices_map = ();
binmode $fh;
seek $fh, 80 + 4, 0;
while (read $fh, my $_, 4*4*3+2) {
my @v = unpack '(f<3)4';
push @$facets, [ [@v[3..5]], [@v[6..8]], [@v[9..11]] ]; # ignore normal: [@v[0..2]]
while (read $fh, $_, 4*4*3+2) {
push @$facets, my $facet = [];
for (unpack 'x[f3](a[f3])3') { # ignore normal
my $vertex_idx;
if (exists $vertices_map{$_}) {
$vertex_idx = $vertices_map{$_};
} else {
push @$vertices, [ unpack 'f<3', $_ ];
$vertex_idx = $vertices_map{$_} = $#$vertices;
}
push @$facet, $vertex_idx;
}
}
}

View File

@ -6,6 +6,7 @@ use Slic3r::ExtrusionPath ':roles';
use Slic3r::Geometry qw(scale unscale scaled_epsilon points_coincide PI X Y B);
use Slic3r::Geometry::Clipper qw(union_ex);
has 'config' => (is => 'ro', required => 1);
has 'multiple_extruders' => (is => 'ro', default => sub {0} );
has 'layer_count' => (is => 'ro', required => 1 );
has 'layer' => (is => 'rw');
@ -15,12 +16,12 @@ has 'shift_y' => (is => 'rw', default => sub {0} );
has 'z' => (is => 'rw');
has 'speed' => (is => 'rw');
has 'speeds' => (is => 'lazy'); # mm/min
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' => (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} );
has 'lifted' => (is => 'rw', default => sub {0} );
@ -28,26 +29,27 @@ has 'last_pos' => (is => 'rw', default => sub { Slic3r::Point->new(0,0
has 'last_speed' => (is => 'rw', default => sub {""});
has 'last_f' => (is => 'rw', default => sub {""});
has 'last_fan_speed' => (is => 'rw', default => sub {0});
has 'wipe_path' => (is => 'rw');
has 'dec' => (is => 'ro', default => sub { 3 } );
# used for vibration limit:
has 'last_dir' => (is => 'ro', default => sub { [0,0] });
has 'dir_time' => (is => 'ro', default => sub { [0,0] });
# calculate speeds (mm/min)
has 'speeds' => (
is => 'ro',
default => sub {+{
map { $_ => 60 * $Slic3r::Config->get_value("${_}_speed") }
sub _build_speeds {
my $self = shift;
return {
map { $_ => 60 * $self->config->get_value("${_}_speed") }
qw(travel perimeter small_perimeter external_perimeter infill
solid_infill top_solid_infill support_material bridge gap_fill retract),
}},
);
};
}
# assign speeds to roles
my %role_speeds = (
&EXTR_ROLE_PERIMETER => 'perimeter',
&EXTR_ROLE_EXTERNAL_PERIMETER => 'external_perimeter',
&EXTR_ROLE_OVERHANG_PERIMETER => 'external_perimeter',
&EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER => 'perimeter',
&EXTR_ROLE_FILL => 'infill',
&EXTR_ROLE_SOLIDFILL => 'solid_infill',
@ -64,10 +66,12 @@ sub set_shift {
my @shift = @_;
# if shift increases (goes towards right), last_pos decreases because it goes towards left
$self->last_pos->translate(
my @translate = (
scale ($self->shift_x - $shift[X]),
scale ($self->shift_y - $shift[Y]),
);
$self->last_pos->translate(@translate);
$self->wipe_path->translate(@translate) if $self->wipe_path;
$self->shift_x($shift[X]);
$self->shift_y($shift[Y]);
@ -78,17 +82,17 @@ sub change_layer {
my ($layer) = @_;
$self->layer($layer);
if ($Slic3r::Config->avoid_crossing_perimeters) {
if ($self->config->avoid_crossing_perimeters) {
$self->layer_mp(Slic3r::GCode::MotionPlanner->new(
islands => union_ex([ map @$_, @{$layer->slices} ], undef, 1),
));
}
my $gcode = "";
if ($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) {
if ($self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/) {
$gcode .= sprintf "M73 P%s%s\n",
int(99 * ($layer->id / ($self->layer_count - 1))),
($Slic3r::Config->gcode_comments ? ' ; update progress' : '');
($self->config->gcode_comments ? ' ; update progress' : '');
}
return $gcode;
}
@ -99,7 +103,7 @@ sub move_z {
my ($z, $comment) = @_;
$z *= &Slic3r::SCALING_FACTOR;
$z += $Slic3r::Config->z_offset;
$z += $self->config->z_offset;
my $gcode = "";
my $current_z = $self->z;
@ -133,9 +137,9 @@ sub extrude_loop {
# find the point of the loop that is closest to the current extruder position
# or randomize if requested
my $last_pos = $self->last_pos;
if ($Slic3r::Config->randomize_start && $loop->role == EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER) {
$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);
if ($self->config->randomize_start && $loop->role == EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER) {
$last_pos = Slic3r::Point->new(scale $self->config->print_center->[X], scale $self->config->bed_size->[Y]);
$last_pos->rotate(rand(2*PI), $self->config->print_center);
}
my $start_index = $loop->nearest_point_index_to($last_pos);
@ -150,9 +154,10 @@ sub extrude_loop {
# extrude along the path
my $gcode = $self->extrude_path($extrusion_path, $description);
$self->wipe_path($extrusion_path->polyline);
# make a little move inwards before leaving loop
if ($loop->role == EXTR_ROLE_EXTERNAL_PERIMETER) {
if ($loop->role == EXTR_ROLE_EXTERNAL_PERIMETER && $self->config->perimeters > 1) {
# detect angle between last and first segment
# the side depends on the original winding order of the polygon (left for contours, right for holes)
my @points = $was_clockwise ? (-2, 1) : (1, -2);
@ -183,7 +188,7 @@ sub extrude_path {
$path->simplify(&Slic3r::SCALED_RESOLUTION);
# detect arcs
if ($Slic3r::Config->gcode_arcs && !$recursive) {
if ($self->config->gcode_arcs && !$recursive) {
my $gcode = "";
foreach my $arc_path ($path->detect_arcs) {
$gcode .= $self->extrude_path($arc_path, $description, 1);
@ -200,17 +205,17 @@ sub extrude_path {
# adjust acceleration
my $acceleration;
if ($Slic3r::Config->perimeter_acceleration && $path->is_perimeter) {
$acceleration = $Slic3r::Config->perimeter_acceleration;
} elsif ($Slic3r::Config->infill_acceleration && $path->is_fill) {
$acceleration = $Slic3r::Config->infill_acceleration;
} elsif ($Slic3r::Config->infill_acceleration && ($path->role == EXTR_ROLE_BRIDGE || $path->role == EXTR_ROLE_INTERNALBRIDGE)) {
$acceleration = $Slic3r::Config->bridge_acceleration;
if ($self->config->perimeter_acceleration && $path->is_perimeter) {
$acceleration = $self->config->perimeter_acceleration;
} elsif ($self->config->infill_acceleration && $path->is_fill) {
$acceleration = $self->config->infill_acceleration;
} elsif ($self->config->infill_acceleration && $path->is_bridge) {
$acceleration = $self->config->bridge_acceleration;
}
$gcode .= $self->set_acceleration($acceleration) if $acceleration;
my $area; # mm^3 of extrudate per mm of tool movement
if ($path->role == EXTR_ROLE_BRIDGE || $path->role == EXTR_ROLE_INTERNALBRIDGE) {
if ($path->is_bridge) {
my $s = $path->flow_spacing;
$area = ($s**2) * PI/4;
} else {
@ -236,27 +241,30 @@ sub extrude_path {
$path_length = unscale $path->length;
$gcode .= $self->G2_G3($path->points->[-1], $path->orientation,
$path->center, $e * unscale $path_length, $description);
$self->wipe_path(undef);
} else {
foreach my $line ($path->lines) {
my $line_length = unscale $line->length;
$path_length += $line_length;
$gcode .= $self->G1($line->[B], undef, $e * $line_length, $description);
}
$self->wipe_path(Slic3r::Polyline->new([ reverse @{$path->points} ]))
if $self->extruder->wipe;
}
if ($Slic3r::Config->cooling) {
if ($self->config->cooling) {
my $path_time = $path_length / $self->speeds->{$self->last_speed} * 60;
if ($self->layer->id == 0) {
$path_time = $Slic3r::Config->first_layer_speed =~ /^(\d+(?:\.\d+)?)%$/
$path_time = $self->config->first_layer_speed =~ /^(\d+(?:\.\d+)?)%$/
? $path_time / ($1/100)
: $path_length / $Slic3r::Config->first_layer_speed * 60;
: $path_length / $self->config->first_layer_speed * 60;
}
$self->elapsed_time($self->elapsed_time + $path_time);
}
# reset acceleration
$gcode .= $self->set_acceleration($Slic3r::Config->default_acceleration)
if $acceleration && $Slic3r::Config->default_acceleration;
$gcode .= $self->set_acceleration($self->config->default_acceleration)
if $acceleration && $self->config->default_acceleration;
return $gcode;
}
@ -275,46 +283,18 @@ sub travel_to {
$travel->translate(-$self->shift_x, -$self->shift_y);
if ($travel->length < scale $self->extruder->retract_before_travel
|| ($Slic3r::Config->only_retract_when_crossing_perimeters && first { $_->encloses_line($travel, scaled_epsilon) } @{$self->layer->slices})
|| ($self->config->only_retract_when_crossing_perimeters && first { $_->encloses_line($travel, scaled_epsilon) } @{$self->layer->upper_layer_slices})
|| ($role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands_enclose_line($travel))
) {
$self->straight_once(0);
$self->speed('travel');
$gcode .= $self->G0($point, undef, 0, $comment || "");
} elsif (!$Slic3r::Config->avoid_crossing_perimeters || $self->straight_once) {
} elsif (!$self->config->avoid_crossing_perimeters || $self->straight_once) {
$self->straight_once(0);
$gcode .= $self->retract(travel_to => $point);
$self->speed('travel');
$gcode .= $self->G0($point, undef, 0, $comment || "");
} else {
my $plan = sub {
my $mp = shift;
my $gcode = "";
my @travel = $mp->shortest_path($self->last_pos, $point)->lines;
# if the path is not contained in a single island we need to retract
my $need_retract = !$Slic3r::Config->only_retract_when_crossing_perimeters;
if (!$need_retract) {
$need_retract = 1;
foreach my $slice (@{$self->layer->slices}) {
# discard the island if at any line is not enclosed in it
next if first { !$slice->encloses_line($_, scaled_epsilon) } @travel;
# okay, this island encloses the full travel path
$need_retract = 0;
last;
}
}
# do the retract (the travel_to argument is broken)
$gcode .= $self->retract(travel_to => $point) if $need_retract;
# append the actual path and return
$self->speed('travel');
$gcode .= join '', map $self->G0($_->[B], undef, 0, $comment || ""), @travel;
return $gcode;
};
if ($self->new_object) {
$self->new_object(0);
@ -325,16 +305,46 @@ sub travel_to {
# calculate path (external_mp uses G-code coordinates so we temporary need a null shift)
$self->set_shift(0,0);
$gcode .= $plan->($self->external_mp);
$gcode .= $self->_plan($self->external_mp, $point, $comment);
$self->set_shift(@shift);
} else {
$gcode .= $plan->($self->layer_mp);
$gcode .= $self->_plan($self->layer_mp, $point, $comment);
}
}
return $gcode;
}
sub _plan {
my $self = shift;
my ($mp, $point, $comment) = @_;
my $gcode = "";
my @travel = $mp->shortest_path($self->last_pos, $point)->lines;
# if the path is not contained in a single island we need to retract
my $need_retract = !$self->config->only_retract_when_crossing_perimeters;
if (!$need_retract) {
$need_retract = 1;
foreach my $slice (@{$self->layer->upper_layer_slices}) {
# discard the island if at any line is not enclosed in it
next if first { !$slice->encloses_line($_, scaled_epsilon) } @travel;
# okay, this island encloses the full travel path
$need_retract = 0;
last;
}
}
# do the retract (the travel_to argument is broken)
$gcode .= $self->retract(travel_to => $point) if $need_retract;
# append the actual path and return
$self->speed('travel');
# use G1 because we rely on paths being straight (G0 may make round paths)
$gcode .= join '', map $self->G1($_->[B], undef, 0, $comment || ""), @travel;
return $gcode;
}
sub retract {
my $self = shift;
my %params = @_;
@ -347,16 +357,23 @@ sub retract {
# if we already retracted, reduce the required amount of retraction
$length -= $self->extruder->retracted;
return "" unless $length > 0;
my $gcode = "";
# wipe
my $wipe_path;
if ($self->extruder->wipe && $self->wipe_path) {
$wipe_path = Slic3r::Polyline->new([ $self->last_pos, @{$self->wipe_path}[1..$#{$self->wipe_path}] ])
->clip_start($self->extruder->scaled_wipe_distance);
}
# prepare moves
$self->speed('retract');
my $retract = [undef, undef, -$length, $comment];
my $lift = ($self->extruder->retract_lift == 0 || defined $params{move_z}) && !$self->lifted
? undef
: [undef, $self->z + $self->extruder->retract_lift, 0, 'lift plate during travel'];
my $gcode = "";
if (($Slic3r::Config->g0 || $Slic3r::Config->gcode_flavor eq 'mach3') && $params{travel_to}) {
if (($self->config->g0 || $self->config->gcode_flavor eq 'mach3') && $params{travel_to}) {
$self->speed('travel');
if ($lift) {
# combine lift and retract
$lift->[2] = $retract->[2];
@ -366,13 +383,27 @@ sub retract {
my $travel = [$params{travel_to}, undef, $retract->[2], "travel and $comment"];
$gcode .= $self->G0(@$travel);
}
} elsif (($Slic3r::Config->g0 || $Slic3r::Config->gcode_flavor eq 'mach3') && defined $params{move_z}) {
} elsif (($self->config->g0 || $self->config->gcode_flavor eq 'mach3') && defined $params{move_z}) {
# combine Z change and retraction
$self->speed('travel');
my $travel = [undef, $params{move_z}, $retract->[2], "change layer and $comment"];
$gcode .= $self->G0(@$travel);
} else {
$gcode .= $self->G1(@$retract);
# check that we have a positive wipe length
if ($wipe_path && (my $total_wipe_length = $wipe_path->length)) {
$self->speed('travel');
# subdivide the retraction
for (1 .. $#$wipe_path) {
my $segment_length = $wipe_path->[$_-1]->distance_to($wipe_path->[$_]);
$gcode .= $self->G1($wipe_path->[$_], undef, $retract->[2] * ($segment_length / $total_wipe_length), $retract->[3] . ";_WIPE");
}
} else {
$self->speed('retract');
$gcode .= $self->G1(@$retract);
}
if (!$self->lifted) {
$self->speed('travel');
if (defined $params{move_z} && $self->extruder->retract_lift > 0) {
my $travel = [undef, $params{move_z} + $self->extruder->retract_lift, 0, 'move to next layer (' . $self->layer->id . ') and lift'];
$gcode .= $self->G0(@$travel);
@ -388,7 +419,9 @@ sub retract {
# reset extrusion distance during retracts
# this makes sure we leave sufficient precision in the firmware
$gcode .= $self->reset_e if $Slic3r::Config->gcode_flavor !~ /^(?:mach3|makerbot)$/;
$gcode .= $self->reset_e;
$gcode .= "M103 ; extruder off\n" if $self->config->gcode_flavor eq 'makerware';
return $gcode;
}
@ -397,6 +430,7 @@ sub unretract {
my $self = shift;
my $gcode = "";
$gcode .= "M101 ; extruder on\n" if $self->config->gcode_flavor eq 'makerware';
if ($self->lifted) {
$self->speed('travel');
@ -407,7 +441,8 @@ sub unretract {
my $to_unretract = $self->extruder->retracted + $self->extruder->restart_extra;
if ($to_unretract) {
$self->speed('retract');
$gcode .= $self->G0(undef, undef, $to_unretract, "compensate retraction");
# use G1 instead of G0 because G0 will blend the restart with the previous travel move
$gcode .= $self->G1(undef, undef, $to_unretract, "compensate retraction");
$self->extruder->retracted(0);
$self->extruder->restart_extra(0);
}
@ -417,10 +452,11 @@ sub unretract {
sub reset_e {
my $self = shift;
return "" if $self->config->gcode_flavor =~ /^(?:mach3|makerware)$/;
$self->extrusion_distance(0);
return sprintf "G92 %s0%s\n", $Slic3r::Config->extrusion_axis, ($Slic3r::Config->gcode_comments ? ' ; reset extrusion distance' : '')
if $Slic3r::Config->extrusion_axis && !$Slic3r::Config->use_relative_e_distances;
$self->extruder->e(0) if $self->extruder;
return sprintf "G92 %s0%s\n", $self->config->extrusion_axis, ($self->config->gcode_comments ? ' ; reset extrusion distance' : '')
if $self->config->extrusion_axis && !$self->config->use_relative_e_distances;
}
sub set_acceleration {
@ -429,12 +465,12 @@ sub set_acceleration {
return "" if !$acceleration;
return sprintf "M204 S%s%s\n",
$acceleration, ($Slic3r::Config->gcode_comments ? ' ; adjust acceleration' : '');
$acceleration, ($self->config->gcode_comments ? ' ; adjust acceleration' : '');
}
sub G0 {
my $self = shift;
return $self->G1(@_) if !($Slic3r::Config->g0 || $Slic3r::Config->gcode_flavor eq 'mach3');
return $self->G1(@_) if !($self->config->g0 || $self->config->gcode_flavor eq 'mach3');
return $self->_G0_G1("G0", @_);
}
@ -504,9 +540,9 @@ sub _Gx {
? ($self->extruder->retract_speed_mm_min)
: $self->speeds->{$self->speed} // $self->speed;
if ($e && $self->layer && $self->layer->id == 0 && $comment !~ /retract/) {
$F = $Slic3r::Config->first_layer_speed =~ /^(\d+(?:\.\d+)?)%$/
$F = $self->config->first_layer_speed =~ /^(\d+(?:\.\d+)?)%$/
? ($F * $1/100)
: $Slic3r::Config->first_layer_speed * 60;
: $self->config->first_layer_speed * 60;
}
$self->last_speed($self->speed);
$self->last_f($F);
@ -514,14 +550,14 @@ sub _Gx {
$gcode .= sprintf " F%.${dec}f", $F if defined $F;
# output extrusion distance
if ($e && $Slic3r::Config->extrusion_axis) {
$self->extrusion_distance(0) if $Slic3r::Config->use_relative_e_distances;
$self->extrusion_distance($self->extrusion_distance + $e);
if ($e && $self->config->extrusion_axis) {
$self->extruder->e(0) if $self->config->use_relative_e_distances;
$self->extruder->e($self->extruder->e + $e);
$self->total_extrusion_length($self->total_extrusion_length + $e);
$gcode .= sprintf " %s%.5f", $Slic3r::Config->extrusion_axis, $self->extrusion_distance;
$gcode .= sprintf " %s%.5f", $self->config->extrusion_axis, $self->extruder->e;
}
$gcode .= sprintf " ; %s", $comment if $comment && $Slic3r::Config->gcode_comments;
$gcode .= sprintf " ; %s", $comment if $comment && $self->config->gcode_comments;
if ($append_bridge_off) {
$gcode .= "\n;_BRIDGE_FAN_END";
}
@ -546,8 +582,8 @@ sub set_extruder {
$gcode .= $self->retract(toolchange => 1) if defined $self->extruder;
# append custom toolchange G-code
if (defined $self->extruder && $Slic3r::Config->toolchange_gcode) {
$gcode .= sprintf "%s\n", $Slic3r::Config->replace_options($Slic3r::Config->toolchange_gcode, {
if (defined $self->extruder && $self->config->toolchange_gcode) {
$gcode .= sprintf "%s\n", $self->config->replace_options($self->config->toolchange_gcode, {
previous_extruder => $self->extruder->id,
next_extruder => $extruder->id,
});
@ -555,18 +591,16 @@ sub set_extruder {
# set the new extruder
$self->extruder($extruder);
my $toolchange_gcode = sprintf "%s%d%s\n",
($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ ? 'M108 T' : 'T'),
$gcode .= sprintf "%s%d%s\n",
($self->config->gcode_flavor eq 'makerware'
? 'M135 T'
: $self->config->gcode_flavor eq 'sailfish'
? 'M108 T'
: 'T'),
$extruder->id,
($Slic3r::Config->gcode_comments ? ' ; change extruder' : '');
($self->config->gcode_comments ? ' ; change extruder' : '');
if ($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) {
$gcode .= $self->reset_e;
$gcode .= $toolchange_gcode;
} else {
$gcode .= $toolchange_gcode;
$gcode .= $self->reset_e;
}
$gcode .= $self->reset_e;
return $gcode;
}
@ -578,18 +612,18 @@ sub set_fan {
if ($self->last_fan_speed != $speed || $dont_save) {
$self->last_fan_speed($speed) if !$dont_save;
if ($speed == 0) {
my $code = $Slic3r::Config->gcode_flavor eq 'teacup'
my $code = $self->config->gcode_flavor eq 'teacup'
? 'M106 S0'
: $Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/
: $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/
? 'M127'
: 'M107';
return sprintf "$code%s\n", ($Slic3r::Config->gcode_comments ? ' ; disable fan' : '');
return sprintf "$code%s\n", ($self->config->gcode_comments ? ' ; disable fan' : '');
} else {
if ($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) {
return sprintf "M126%s\n", ($Slic3r::Config->gcode_comments ? ' ; enable fan' : '');
if ($self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/) {
return sprintf "M126%s\n", ($self->config->gcode_comments ? ' ; enable fan' : '');
} else {
return sprintf "M106 %s%d%s\n", ($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'),
(255 * $speed / 100), ($Slic3r::Config->gcode_comments ? ' ; enable fan' : '');
return sprintf "M106 %s%d%s\n", ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'),
(255 * $speed / 100), ($self->config->gcode_comments ? ' ; enable fan' : '');
}
}
}
@ -600,17 +634,17 @@ sub set_temperature {
my $self = shift;
my ($temperature, $wait, $tool) = @_;
return "" if $wait && $Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/;
return "" if $wait && $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/;
my ($code, $comment) = ($wait && $Slic3r::Config->gcode_flavor ne 'teacup')
my ($code, $comment) = ($wait && $self->config->gcode_flavor ne 'teacup')
? ('M109', 'wait for temperature to be reached')
: ('M104', 'set temperature');
my $gcode = sprintf "$code %s%d %s; $comment\n",
($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature,
(defined $tool && ($self->multiple_extruders || $Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/)) ? "T$tool " : "";
($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature,
(defined $tool && ($self->multiple_extruders || $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/)) ? "T$tool " : "";
$gcode .= "M116 ; wait for temperature to be reached\n"
if $Slic3r::Config->gcode_flavor eq 'teacup' && $wait;
if $self->config->gcode_flavor eq 'teacup' && $wait;
return $gcode;
}
@ -619,14 +653,14 @@ sub set_bed_temperature {
my $self = shift;
my ($temperature, $wait) = @_;
my ($code, $comment) = ($wait && $Slic3r::Config->gcode_flavor ne 'teacup')
? (($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ ? 'M109' : 'M190'), 'wait for bed temperature to be reached')
my ($code, $comment) = ($wait && $self->config->gcode_flavor ne 'teacup')
? (($self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/ ? 'M109' : 'M190'), 'wait for bed temperature to be reached')
: ('M140', 'set bed temperature');
my $gcode = sprintf "$code %s%d ; $comment\n",
($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature;
($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature;
$gcode .= "M116 ; wait for bed temperature to be reached\n"
if $Slic3r::Config->gcode_flavor eq 'teacup' && $wait;
if $self->config->gcode_flavor eq 'teacup' && $wait;
return $gcode;
}
@ -636,8 +670,8 @@ sub _limit_frequency {
my $self = shift;
my ($point) = @_;
return '' if $Slic3r::Config->vibration_limit == 0;
my $min_time = 1 / ($Slic3r::Config->vibration_limit * 60); # in minutes
return '' if $self->config->vibration_limit == 0;
my $min_time = 1 / ($self->config->vibration_limit * 60); # in minutes
# calculate the move vector and move direction
my $vector = Slic3r::Line->new($self->last_pos, $point)->vector;

View File

@ -0,0 +1,83 @@
package Slic3r::GCode::CoolingBuffer;
use Moo;
has 'config' => (is => 'ro', required => 1);
has 'gcodegen' => (is => 'ro', required => 1);
has 'gcode' => (is => 'rw', default => sub {""});
has 'elapsed_time' => (is => 'rw', default => sub {0});
has 'layer_id' => (is => 'rw');
has 'last_z' => (is => 'rw', default => sub { {} }); # obj_id => z (basically a 'last seen' table)
has 'min_print_speed' => (is => 'lazy');
sub _build_min_print_speed {
my $self = shift;
return 60 * $self->config->min_print_speed;
}
sub append {
my $self = shift;
my ($gcode, $obj_id, $layer_id, $print_z) = @_;
# TODO: differentiate $obj_id between normal layers and support layers
my $return = "";
if (exists $self->last_z->{$obj_id} && $self->last_z->{$obj_id} != $print_z) {
$return = $self->flush;
}
$self->layer_id($layer_id);
$self->last_z->{$obj_id} = $print_z;
$self->gcode($self->gcode . $gcode);
$self->elapsed_time($self->elapsed_time + $self->gcodegen->elapsed_time);
$self->gcodegen->elapsed_time(0);
return $return;
}
sub flush {
my $self = shift;
my $gcode = $self->gcode;
my $elapsed = $self->elapsed_time;
$self->gcode("");
$self->elapsed_time(0);
$self->last_z({}); # reset the whole table otherwise we would compute overlapping times
my $fan_speed = $self->config->fan_always_on ? $self->config->min_fan_speed : 0;
my $speed_factor = 1;
if ($self->config->cooling) {
Slic3r::debugf "Layer %d estimated printing time: %d seconds\n", $self->layer_id, $elapsed;
if ($elapsed < $self->config->slowdown_below_layer_time) {
$fan_speed = $self->config->max_fan_speed;
$speed_factor = $elapsed / $self->config->slowdown_below_layer_time;
} elsif ($elapsed < $self->config->fan_below_layer_time) {
$fan_speed = $self->config->max_fan_speed - ($self->config->max_fan_speed - $self->config->min_fan_speed)
* ($elapsed - $self->config->slowdown_below_layer_time)
/ ($self->config->fan_below_layer_time - $self->config->slowdown_below_layer_time); #/
}
Slic3r::debugf " fan = %d%%, speed = %d%%\n", $fan_speed, $speed_factor * 100;
if ($speed_factor < 1) {
my $dec = $self->gcodegen->dec;
$gcode =~ s/^(?=.*? [XY])(?=.*? E)(?!;_WIPE)(?<!;_BRIDGE_FAN_START\n)(G1 .*?F)(\d+(?:\.\d+)?)/
my $new_speed = $2 * $speed_factor;
$1 . sprintf("%.${dec}f", $new_speed < $self->min_print_speed ? $self->min_print_speed : $new_speed)
/gexm;
}
}
$fan_speed = 0 if $self->layer_id < $self->config->disable_fan_first_layers;
$gcode = $self->gcodegen->set_fan($fan_speed) . $gcode;
# bridge fan speed
if (!$self->config->cooling || $self->config->bridge_fan_speed == 0 || $self->layer_id < $self->config->disable_fan_first_layers) {
$gcode =~ s/^;_BRIDGE_FAN_(?:START|END)\n//gm;
} else {
$gcode =~ s/^;_BRIDGE_FAN_START\n/ $self->gcodegen->set_fan($self->config->bridge_fan_speed, 1) /gmex;
$gcode =~ s/^;_BRIDGE_FAN_END\n/ $self->gcodegen->set_fan($fan_speed, 1) /gmex;
}
$gcode =~ s/;_WIPE//g;
return $gcode;
}
1;

207
lib/Slic3r/GCode/Layer.pm Normal file
View File

@ -0,0 +1,207 @@
package Slic3r::GCode::Layer;
use Moo;
use List::Util qw(first);
use Slic3r::Geometry qw(X Y unscale);
has 'print' => (is => 'ro', required => 1, handles => [qw(extruders)]);
has 'gcodegen' => (is => 'ro', required => 1);
has 'shift' => (is => 'ro', required => 1);
has 'spiralvase' => (is => 'lazy');
has 'skirt_done' => (is => 'rw', default => sub { {} }); # print_z => 1
has 'brim_done' => (is => 'rw');
has 'second_layer_things_done' => (is => 'rw');
has '_last_obj_copy' => (is => 'rw');
sub _build_spiralvase {
my $self = shift;
return $Slic3r::Config->spiral_vase
? Slic3r::GCode::SpiralVase->new
: undef;
}
sub process_layer {
my $self = shift;
my ($layer, $object_copies) = @_;
my $gcode = "";
if (!$self->second_layer_things_done && $layer->id == 1) {
for my $t (grep $self->extruders->[$_], 0 .. $#{$Slic3r::Config->temperature}) {
$gcode .= $self->gcodegen->set_temperature($self->extruders->[$t]->temperature, 0, $t)
if $self->print->extruders->[$t]->temperature && $self->extruders->[$t]->temperature != $self->extruders->[$t]->first_layer_temperature;
}
$gcode .= $self->gcodegen->set_bed_temperature($Slic3r::Config->bed_temperature)
if $Slic3r::Config->bed_temperature && $Slic3r::Config->bed_temperature != $Slic3r::Config->first_layer_bed_temperature;
$self->second_layer_things_done(1);
}
# set new layer, but don't move Z as support material contact areas may need an intermediate one
$gcode .= $self->gcodegen->change_layer($layer);
# prepare callback to call as soon as a Z command is generated
$self->gcodegen->move_z_callback(sub {
$self->gcodegen->move_z_callback(undef); # circular ref or not?
return "" if !$Slic3r::Config->layer_gcode;
return $Slic3r::Config->replace_options($Slic3r::Config->layer_gcode) . "\n";
});
# extrude skirt
if ((values %{$self->skirt_done}) < $Slic3r::Config->skirt_height && !$self->skirt_done->{$layer->print_z}) {
$self->gcodegen->set_shift(@{$self->shift});
$gcode .= $self->gcodegen->set_extruder($self->extruders->[0]); # move_z requires extruder
$gcode .= $self->gcodegen->move_z($layer->print_z);
# skip skirt if we have a large brim
if ($layer->id < $Slic3r::Config->skirt_height) {
# distribute skirt loops across all extruders
for my $i (0 .. $#{$self->print->skirt}) {
# when printing layers > 0 ignore 'min_skirt_length' and
# just use the 'skirts' setting; also just use the current extruder
last if ($layer->id > 0) && ($i >= $Slic3r::Config->skirts);
$gcode .= $self->gcodegen->set_extruder($self->extruders->[ ($i/@{$self->extruders}) % @{$self->extruders} ])
if $layer->id == 0;
$gcode .= $self->gcodegen->extrude_loop($self->print->skirt->[$i], 'skirt');
}
}
$self->skirt_done->{$layer->print_z} = 1;
$self->gcodegen->straight_once(1);
}
# extrude brim
if (!$self->brim_done) {
$gcode .= $self->gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]); # move_z requires extruder
$gcode .= $self->gcodegen->move_z($layer->print_z);
$self->gcodegen->set_shift(@{$self->shift});
$gcode .= $self->gcodegen->extrude_loop($_, 'brim') for @{$self->print->brim};
$self->brim_done(1);
$self->gcodegen->straight_once(1);
}
for my $copy (@$object_copies) {
$self->gcodegen->new_object(1) if ($self->_last_obj_copy // '') ne "$copy";
$self->_last_obj_copy("$copy");
$self->gcodegen->set_shift(map $self->shift->[$_] + unscale $copy->[$_], X,Y);
# extrude support material before other things because it might use a lower Z
# and also because we avoid travelling on other things when printing it
if ($self->print->has_support_material) {
$gcode .= $self->gcodegen->move_z($layer->support_material_contact_z)
if ($layer->support_contact_fills && @{ $layer->support_contact_fills->paths });
$gcode .= $self->gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]);
if ($layer->support_contact_fills) {
$gcode .= $self->gcodegen->extrude_path($_, 'support material contact area')
for $layer->support_contact_fills->chained_path($self->gcodegen->last_pos);
}
$gcode .= $self->gcodegen->move_z($layer->print_z);
if ($layer->support_fills) {
$gcode .= $self->gcodegen->extrude_path($_, 'support material')
for $layer->support_fills->chained_path($self->gcodegen->last_pos);
}
}
# set actual Z - this will force a retraction
$gcode .= $self->gcodegen->move_z($layer->print_z);
# tweak region ordering to save toolchanges
my @region_ids = 0 .. ($self->print->regions_count-1);
if ($self->gcodegen->multiple_extruders) {
my $last_extruder = $self->gcodegen->extruder;
my $best_region_id = first { $self->print->regions->[$_]->extruders->{perimeter} eq $last_extruder } @region_ids;
@region_ids = ($best_region_id, grep $_ != $best_region_id, @region_ids) if $best_region_id;
}
foreach my $region_id (@region_ids) {
my $layerm = $layer->regions->[$region_id];
my $region = $self->print->regions->[$region_id];
my @islands = ();
if ($Slic3r::Config->avoid_crossing_perimeters) {
push @islands, { perimeters => [], fills => [] }
for 1 .. (@{$layer->slices} || 1); # make sure we have at least one island hash to avoid failure of the -1 subscript below
PERIMETER: foreach my $perimeter (@{$layerm->perimeters}) {
my $p = $perimeter->unpack;
for my $i (0 .. $#{$layer->slices}-1) {
if ($layer->slices->[$i]->contour->encloses_point($p->first_point)) {
push @{ $islands[$i]{perimeters} }, $p;
next PERIMETER;
}
}
push @{ $islands[-1]{perimeters} }, $p; # optimization
}
FILL: foreach my $fill (@{$layerm->fills}) {
my $f = $fill->unpack;
for my $i (0 .. $#{$layer->slices}-1) {
if ($layer->slices->[$i]->contour->encloses_point($f->first_point)) {
push @{ $islands[$i]{fills} }, $f;
next FILL;
}
}
push @{ $islands[-1]{fills} }, $f; # optimization
}
} else {
push @islands, {
perimeters => $layerm->perimeters,
fills => $layerm->fills,
};
}
foreach my $island (@islands) {
# give priority to infill if we were already using its extruder and it wouldn't
# be good for perimeters
if ($Slic3r::Config->infill_first
|| ($self->gcodegen->multiple_extruders && $region->extruders->{infill} eq $self->gcodegen->extruder) && $region->extruders->{infill} ne $region->extruders->{perimeter}) {
$gcode .= $self->_extrude_infill($island, $region);
$gcode .= $self->_extrude_perimeters($island, $region);
} else {
$gcode .= $self->_extrude_perimeters($island, $region);
$gcode .= $self->_extrude_infill($island, $region);
}
}
}
}
# apply spiral vase post-processing if this layer contains suitable geometry
$gcode = $self->spiralvase->process_layer($gcode, $layer)
if defined $self->spiralvase
&& ($layer->id > 0 || $Slic3r::Config->brim_width == 0)
&& ($layer->id >= $Slic3r::Config->skirt_height)
&& ($layer->id >= $Slic3r::Config->bottom_solid_layers);
return $gcode;
}
sub _extrude_perimeters {
my $self = shift;
my ($island, $region) = @_;
return "" if !@{ $island->{perimeters} };
my $gcode = "";
$gcode .= $self->gcodegen->set_extruder($region->extruders->{perimeter});
$gcode .= $self->gcodegen->extrude($_, 'perimeter') for @{ $island->{perimeters} };
return $gcode;
}
sub _extrude_infill {
my $self = shift;
my ($island, $region) = @_;
return "" if !@{ $island->{fills} };
my $gcode = "";
$gcode .= $self->gcodegen->set_extruder($region->extruders->{infill});
for my $fill (@{ $island->{fills} }) {
if ($fill->isa('Slic3r::ExtrusionPath::Collection')) {
$gcode .= $self->gcodegen->extrude($_, 'fill')
for $fill->chained_path($self->gcodegen->last_pos);
} else {
$gcode .= $self->gcodegen->extrude($fill, 'fill') ;
}
}
return $gcode;
}
1;

View File

@ -10,10 +10,11 @@ has '_contours_ex' => (is => 'rw', default => sub { [] }); # arrayref of array
has '_pointmap' => (is => 'rw', default => sub { {} }); # { id => $point }
has '_edges' => (is => 'rw', default => sub { {} }); # node_idx => { node_idx => distance, ... }
has '_crossing_edges' => (is => 'rw', default => sub { {} }); # edge_idx => bool
has '_tolerance' => (is => 'lazy');
use List::Util qw(first);
use Slic3r::Geometry qw(A B scale epsilon nearest_point);
use Slic3r::Geometry::Clipper qw(diff_ex JT_MITER);
use Slic3r::Geometry::Clipper qw(diff_ex offset JT_MITER);
# clearance (in mm) from the perimeters
has '_inner_margin' => (is => 'ro', default => sub { scale 0.5 });
@ -30,31 +31,14 @@ use constant CROSSING_FACTOR => 20;
use constant INFINITY => 'inf';
sub _build__tolerance { scale epsilon }
# setup our configuration space
sub BUILD {
my $self = shift;
my $edges = $self->_edges;
my $crossing_edges = $self->_crossing_edges;
my $tolerance = scale epsilon;
# given an expolygon, this subroutine connects all its visible points
my $add_expolygon = sub {
my ($expolygon, $crosses_perimeter) = @_;
my @points = map @$_, @$expolygon;
for my $i (0 .. $#points) {
for my $j (($i+1) .. $#points) {
my $line = Slic3r::Line->new($points[$i], $points[$j]);
if ($expolygon->encloses_line($line, $tolerance)) {
my $dist = $line->length * ($crosses_perimeter ? CROSSING_FACTOR : 1);
$edges->{$points[$i]}{$points[$j]} = $dist;
$edges->{$points[$j]}{$points[$i]} = $dist;
$crossing_edges->{$points[$i]}{$points[$j]} = 1;
$crossing_edges->{$points[$j]}{$points[$i]} = 1;
}
}
}
};
# simplify islands
@{$self->islands} = map $_->simplify($self->_inner_margin), @{$self->islands};
@ -68,7 +52,8 @@ sub BUILD {
: [ $self->islands->[$i]->offset_ex(-$self->_inner_margin) ];
# offset the island outwards to make the boundaries for external movements
$self->_outer->[$i] = [ $self->islands->[$i]->contour->offset($self->_outer_margin) ];
$self->_outer->[$i] = [ offset([ $self->islands->[$i]->contour], $self->_outer_margin) ];
bless $_, 'Slic3r::Polygon' for @{ $self->_outer->[$i] };
# if internal motion is enabled, build a set of utility expolygons representing
# the outer boundaries (as contours) and the inner boundaries (as holes). whenever
@ -80,21 +65,17 @@ sub BUILD {
);
# lines enclosed in inner expolygons are visible
$add_expolygon->($_) for @{ $self->_inner->[$i] };
$self->_add_expolygon($_) for @{ $self->_inner->[$i] };
# lines enclosed in expolygons covering perimeters are visible
# (but discouraged)
$add_expolygon->($_, 1) for @{ $self->_contours_ex->[$i] };
$self->_add_expolygon($_, 1) for @{ $self->_contours_ex->[$i] };
}
}
my $intersects = sub {
my ($polygon, $line) = @_;
@{Boost::Geometry::Utils::polygon_multi_linestring_intersection([$polygon], [$line])} > 0;
};
{
my @outer = (map @$_, @{$self->_outer});
my @outer_ex = map [$_], @outer; # as ExPolygons
# lines of outer polygons connect visible points
for my $i (0 .. $#outer) {
@ -111,7 +92,7 @@ sub BUILD {
for my $m (0 .. $#{$outer[$i]}) {
for my $n (0 .. $#{$outer[$j]}) {
my $line = Slic3r::Line->new($outer[$i][$m], $outer[$j][$n]);
if (!first { $intersects->($_, $line) } @outer) {
if (!@{Boost::Geometry::Utils::multi_polygon_multi_linestring_intersection(\@outer_ex, [$line])}) {
# this line does not cross any polygon
my $dist = $line->length;
$edges->{$outer[$i][$m]}{$outer[$j][$n]} = $dist;
@ -126,12 +107,13 @@ sub BUILD {
# lines connecting inner polygons contours are visible but discouraged
if (!$self->no_internal) {
my @inner = (map $_->contour, map @$_, @{$self->_inner});
my @inner_ex = map [$_], @inner; # as ExPolygons
for my $i (0 .. $#inner) {
for my $j (($i+1) .. $#inner) {
for my $m (0 .. $#{$inner[$i]}) {
for my $n (0 .. $#{$inner[$j]}) {
my $line = Slic3r::Line->new($inner[$i][$m], $inner[$j][$n]);
if (!first { $intersects->($_, $line) } @inner) {
if (!@{Boost::Geometry::Utils::multi_polygon_multi_linestring_intersection(\@inner_ex, [$line])}) {
# this line does not cross any polygon
my $dist = $line->length * CROSSING_FACTOR;
$edges->{$inner[$i][$m]}{$inner[$j][$n]} = $dist;
@ -182,6 +164,29 @@ sub BUILD {
}
}
# given an expolygon, this subroutine connects all its visible points
sub _add_expolygon {
my $self = shift;
my ($expolygon, $crosses_perimeter) = @_;
my $edges = $self->_edges;
my $crossing_edges = $self->_crossing_edges;
my @points = map @$_, @$expolygon;
for my $i (0 .. $#points) {
for my $j (($i+1) .. $#points) {
my $line = Slic3r::Line->new($points[$i], $points[$j]);
if ($expolygon->encloses_line($line, $self->_tolerance)) {
my $dist = $line->length * ($crosses_perimeter ? CROSSING_FACTOR : 1);
$edges->{$points[$i]}{$points[$j]} = $dist;
$edges->{$points[$j]}{$points[$i]} = $dist;
$crossing_edges->{$points[$i]}{$points[$j]} = 1;
$crossing_edges->{$points[$j]}{$points[$i]} = 1;
}
}
}
}
sub find_node {
my $self = shift;
my ($point, $near_to) = @_;

View File

@ -0,0 +1,66 @@
package Slic3r::GCode::Reader;
use Moo;
has 'gcode' => (is => 'ro', required => 1);
has 'X' => (is => 'rw', default => sub {0});
has 'Y' => (is => 'rw', default => sub {0});
has 'Z' => (is => 'rw', default => sub {0});
has 'E' => (is => 'rw', default => sub {0});
has 'F' => (is => 'rw', default => sub {0});
our $Verbose = 0;
my @AXES = qw(X Y Z E);
sub parse {
my $self = shift;
my ($cb) = @_;
foreach my $raw_line (split /\R+/, $self->gcode) {
print "$raw_line\n" if $Verbose || $ENV{SLIC3R_TESTS_GCODE};
my $line = $raw_line;
$line =~ s/\s*;(.*)//; # strip comment
next if $line eq '';
my %info = (comment => $1, raw => $raw_line);
# parse command
my ($command, @args) = split /\s+/, $line;
my %args = map { /([A-Z])(.*)/; ($1 => $2) } @args;
# check motion
if ($command =~ /^G[01]$/) {
foreach my $axis (@AXES) {
if (exists $args{$axis}) {
$info{"dist_$axis"} = $args{$axis} - $self->$axis;
$info{"new_$axis"} = $args{$axis};
} else {
$info{"dist_$axis"} = 0;
$info{"new_$axis"} = $self->$axis;
}
}
$info{dist_XY} = Slic3r::Line->new([0,0], [@info{qw(dist_X dist_Y)}])->length;
if (exists $args{E}) {
if ($info{dist_E} > 0) {
$info{extruding} = 1;
} elsif ($info{dist_E} < 0) {
$info{retracting} = 1
}
} else {
$info{travel} = 1;
}
}
# run callback
$cb->($self, $command, \%args, \%info);
# update coordinates
if ($command =~ /^(?:G[01]|G92)$/) {
for (@AXES, 'F') {
$self->$_($args{$_}) if exists $args{$_};
}
}
# TODO: update temperatures
}
}
1;

View File

@ -0,0 +1,40 @@
package Slic3r::GCode::SpiralVase;
use Moo;
use Slic3r::Geometry qw(unscale);
sub process_layer {
my $self = shift;
my ($gcode, $layer) = @_;
my $total_layer_length = 0;
Slic3r::GCode::Reader->new(gcode => $gcode)->parse(sub {
my ($reader, $cmd, $args, $info) = @_;
$total_layer_length += $info->{dist_XY}
if $cmd eq 'G1' && $info->{extruding};
});
my $new_gcode = "";
my $layer_height = $layer->height;
my $z = unscale($layer->print_z) - $layer_height;
Slic3r::GCode::Reader->new(gcode => $gcode)->parse(sub {
my ($reader, $cmd, $args, $info) = @_;
if ($cmd eq 'G1' && exists $args->{Z}) {
my $line = $info->{raw};
$line =~ s/Z([^ ]+)/Z$z/;
$new_gcode .= "$line\n";
} elsif ($cmd eq 'G1' && !exists $args->{Z} && $info->{extruding} && $info->{dist_XY}) {
$z += $info->{dist_XY} * $layer_height / $total_layer_length;
my $line = $info->{raw};
$line =~ s/^G1 /sprintf 'G1 Z%.3f ', $z/e;
$new_gcode .= "$line\n";
} else {
$new_gcode .= "$info->{raw}\n";
}
});
return $new_gcode;
}
1;

View File

@ -14,6 +14,8 @@ use Slic3r::GUI::SkeinPanel;
use Slic3r::GUI::SimpleTab;
use Slic3r::GUI::Tab;
our $have_OpenGL = 0 && eval "use Slic3r::GUI::PreviewCanvas; 1";
use Wx 0.9901 qw(:bitmap :dialog :frame :icon :id :misc :systemsettings :toplevelwindow);
use Wx::Event qw(EVT_CLOSE EVT_MENU);
use base 'Wx::App';
@ -37,15 +39,18 @@ use constant MI_TAB_PRINTER => &Wx::NewId;
use constant MI_CONF_WIZARD => &Wx::NewId;
use constant MI_WEBSITE => &Wx::NewId;
use constant MI_VERSIONCHECK => &Wx::NewId;
use constant MI_DOCUMENTATION => &Wx::NewId;
our $datadir;
our $no_plater;
our $mode;
our $autosave;
our $Settings = {
_ => {
mode => 'simple',
version_check => 1,
},
};
@ -114,20 +119,21 @@ sub OnInit {
$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 });
EVT_MENU($frame, MI_QUICK_SLICE, sub { $self->{skeinpanel}->do_slice;
EVT_MENU($frame, MI_QUICK_SLICE, sub { $self->{skeinpanel}->quick_slice;
$repeat->Enable(defined $Slic3r::GUI::SkeinPanel::last_input_file) });
EVT_MENU($frame, MI_REPEAT_QUICK, sub { $self->{skeinpanel}->do_slice(reslice => 1) });
EVT_MENU($frame, MI_QUICK_SAVE_AS, sub { $self->{skeinpanel}->do_slice(save_as => 1);
EVT_MENU($frame, MI_REPEAT_QUICK, sub { $self->{skeinpanel}->quick_slice(reslice => 1) });
EVT_MENU($frame, MI_QUICK_SAVE_AS, sub { $self->{skeinpanel}->quick_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_SLICE_SVG, sub { $self->{skeinpanel}->quick_slice(save_as => 1, export_svg => 1) });
EVT_MENU($frame, MI_COMBINE_STLS, sub { $self->{skeinpanel}->combine_stls });
EVT_MENU($frame, wxID_PREFERENCES, sub { Slic3r::GUI::Preferences->new($frame)->ShowModal });
EVT_MENU($frame, wxID_EXIT, sub {$_[0]->Close(0)});
}
# Plater menu
my $platerMenu = Wx::Menu->new;
{
my $platerMenu;
unless ($no_plater) {
$platerMenu = Wx::Menu->new;
$platerMenu->Append(MI_PLATER_EXPORT_GCODE, "Export G-code...", 'Export current plate as G-code');
$platerMenu->Append(MI_PLATER_EXPORT_STL, "Export STL...", 'Export current plate as STL');
$platerMenu->Append(MI_PLATER_EXPORT_AMF, "Export AMF...", 'Export current plate as AMF');
@ -139,14 +145,15 @@ sub OnInit {
# Window menu
my $windowMenu = Wx::Menu->new;
{
$windowMenu->Append(MI_TAB_PLATER, "Select &Plater Tab\tCtrl+1", 'Show the plater');
my $tab_count = $no_plater ? 3 : 4;
$windowMenu->Append(MI_TAB_PLATER, "Select &Plater Tab\tCtrl+1", 'Show the plater') unless $no_plater;
$windowMenu->Append(MI_TAB_PRINT, "Select P&rint Settings Tab\tCtrl+2", 'Show the print settings');
$windowMenu->Append(MI_TAB_FILAMENT, "Select &Filament Settings Tab\tCtrl+3", 'Show the filament settings');
$windowMenu->Append(MI_TAB_PRINTER, "Select Print&er Settings Tab\tCtrl+4", 'Show the printer settings');
EVT_MENU($frame, MI_TAB_PLATER, sub { $self->{skeinpanel}->select_tab(0) });
EVT_MENU($frame, MI_TAB_PRINT, sub { $self->{skeinpanel}->select_tab(1) });
EVT_MENU($frame, MI_TAB_FILAMENT, sub { $self->{skeinpanel}->select_tab(2) });
EVT_MENU($frame, MI_TAB_PRINTER, sub { $self->{skeinpanel}->select_tab(3) });
EVT_MENU($frame, MI_TAB_PLATER, sub { $self->{skeinpanel}->select_tab(0) }) unless $no_plater;
EVT_MENU($frame, MI_TAB_PRINT, sub { $self->{skeinpanel}->select_tab($tab_count-3) });
EVT_MENU($frame, MI_TAB_FILAMENT, sub { $self->{skeinpanel}->select_tab($tab_count-2) });
EVT_MENU($frame, MI_TAB_PRINTER, sub { $self->{skeinpanel}->select_tab($tab_count-1) });
}
# Help menu
@ -155,11 +162,14 @@ sub OnInit {
$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');
my $versioncheck = $helpMenu->Append(MI_VERSIONCHECK, "Check for &Updates...", 'Check for new Slic3r versions');
$versioncheck->Enable(Slic3r::GUI->have_version_check);
$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_VERSIONCHECK, sub { Slic3r::GUI->check_version(manual => 1) });
EVT_MENU($frame, MI_DOCUMENTATION, sub { Wx::LaunchDefaultBrowser('https://github.com/alexrj/Slic3r/wiki/Documentation') });
EVT_MENU($frame, wxID_ABOUT, \&about);
}
@ -170,7 +180,7 @@ sub OnInit {
{
my $menubar = Wx::MenuBar->new;
$menubar->Append($fileMenu, "&File");
$menubar->Append($platerMenu, "&Plater");
$menubar->Append($platerMenu, "&Plater") if $platerMenu;
$menubar->Append($windowMenu, "&Window");
$menubar->Append($helpMenu, "&Help");
$frame->SetMenuBar($menubar);
@ -192,6 +202,11 @@ sub OnInit {
$self->{skeinpanel}->config_wizard if $run_wizard;
Slic3r::GUI->check_version
if Slic3r::GUI->have_version_check
&& ($Settings->{_}{version_check} // 1)
&& (!$Settings->{_}{last_version_check} || (time - $Settings->{_}{last_version_check}) >= 86400);
return 1;
}
@ -222,6 +237,12 @@ sub show_error {
Wx::MessageDialog->new($self, $message, 'Error', wxOK | wxICON_ERROR)->ShowModal;
}
sub show_info {
my $self = shift;
my ($message, $title) = @_;
Wx::MessageDialog->new($self, $message, $title || 'Notice', wxOK | wxICON_INFORMATION)->ShowModal;
}
sub fatal_error {
my $self = shift;
$self->show_error(@_);
@ -232,6 +253,7 @@ sub warning_catcher {
my ($self, $message_dialog) = @_;
return sub {
my $message = shift;
return if $message =~ /GLUquadricObjPtr|Attempt to free unreferenced scalar/;
my @params = ($message, 'Warning', wxOK | wxICON_WARNING);
$message_dialog
? $message_dialog->(@params)
@ -257,6 +279,47 @@ sub save_settings {
Slic3r::Config->write_ini("$datadir/slic3r.ini", $Settings);
}
sub have_version_check {
my $class = shift;
return $Slic3r::have_threads && $Slic3r::build && eval "use LWP::UserAgent; 1";
}
sub check_version {
my $class = shift;
my %p = @_;
Slic3r::debugf "Checking for updates...\n";
@_ = ();
threads->create(sub {
my $ua = LWP::UserAgent->new;
$ua->timeout(10);
my $response = $ua->get('http://slic3r.org/updatecheck');
if ($response->is_success) {
if ($response->decoded_content =~ /^obsolete ?= ?([a-z0-9.-]+,)*\Q$Slic3r::VERSION\E(?:,|$)/) {
my $res = Wx::MessageDialog->new(undef, "A new version is available. Do you want to open the Slic3r website now?",
'Update', wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_INFORMATION | wxICON_ERROR)->ShowModal;
Wx::LaunchDefaultBrowser('http://slic3r.org/') if $res == wxID_YES;
} else {
Slic3r::GUI::show_info(undef, "You're using the latest version. No updates are available.") if $p{manual};
}
$Settings->{_}{last_version_check} = time();
Slic3r::GUI->save_settings;
} else {
Slic3r::GUI::show_error(undef, "Failed to check for updates. Try later.") if $p{manual};
}
})->detach;
}
sub output_path {
my $class = shift;
my ($dir) = @_;
return ($Settings->{_}{last_output_path} && $Settings->{_}{remember_output_path})
? $Settings->{_}{last_output_path}
: $dir;
}
package Slic3r::GUI::ProgressStatusBar;
use Wx qw(:gauge :misc);
use base 'Wx::StatusBar';
@ -424,7 +487,8 @@ sub notify {
$self->{growler}->notify(Event => 'SKEIN_DONE', Title => $title, Message => $message)
if $self->{growler};
};
if (eval 'use Net::DBus; 1') {
# Net::DBus is broken in multithreaded environment
if (0 && eval 'use Net::DBus; 1') {
eval {
my $session = Net::DBus->session;
my $serv = $session->get_service('org.freedesktop.Notifications');

View File

@ -167,16 +167,20 @@ sub _build_field {
? Wx::SpinCtrl->new($self->parent, -1, $opt->{default}, wxDefaultPosition, $size, $style, $opt->{min} || 0, $opt->{max} || 2147483647, $opt->{default})
: Wx::TextCtrl->new($self->parent, -1, $opt->{default}, wxDefaultPosition, $size, $style);
$field->Disable if $opt->{readonly};
$self->_setters->{$opt_key} = sub { $field->SetValue($_[0]) };
my $on_change = sub { $self->_on_change($opt_key, $field->GetValue) };
$opt->{type} eq 'i'
? EVT_SPINCTRL ($self->parent, $field, $on_change)
: EVT_TEXT ($self->parent, $field, $on_change);
if ($opt->{type} eq 'i') {
$self->_setters->{$opt_key} = sub { $field->SetValue($_[0]) };
EVT_SPINCTRL ($self->parent, $field, $on_change);
} else {
$self->_setters->{$opt_key} = sub { $field->ChangeValue($_[0]) };
EVT_TEXT ($self->parent, $field, $on_change);
}
$tooltip .= " (default: " . $opt->{default} . ")" if ($opt->{default});
} elsif ($opt->{type} eq 'bool') {
$field = Wx::CheckBox->new($self->parent, -1, "");
$field->SetValue($opt->{default});
$field->Disable if $opt->{readonly};
EVT_CHECKBOX($self->parent, $field, sub { $self->_on_change($opt_key, $field->GetValue); });
$self->_setters->{$opt_key} = sub { $field->SetValue($_[0]) };
$tooltip .= " (default: " . ($opt->{default} ? 'yes' : 'no') . ")" if defined($opt->{default});

View File

@ -5,9 +5,9 @@ use utf8;
use File::Basename qw(basename dirname);
use List::Util qw(max sum first);
use Math::Clipper qw(offset JT_ROUND);
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);
use Wx qw(:bitmap :brush :button :cursor :dialog :filedialog :font :keycode :icon :id :listctrl :misc :panel :pen :sizer :toolbar :window);
use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED EVT_LIST_ITEM_DESELECTED EVT_LIST_ITEM_SELECTED EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL EVT_CHOICE);
@ -159,6 +159,7 @@ sub new {
EVT_COMMAND($self, -1, $THUMBNAIL_DONE_EVENT, sub {
my ($self, $event) = @_;
my ($obj_idx, $thumbnail) = @{$event->GetData};
return if !$self->{objects}[$obj_idx]; # object was deleted before thumbnail generation completed
$self->{objects}[$obj_idx]->thumbnail($thumbnail->clone);
$self->on_thumbnail_made($obj_idx);
});
@ -325,7 +326,7 @@ sub load_file {
$object->check_manifoldness;
# we only consider the rotation of the first instance for now
$object->set_rotation($model->objects->[$i]->instances->[0]->rotation)
$object->rotate($model->objects->[$i]->instances->[0]->rotation)
if $model->objects->[$i]->instances;
push @{ $self->{objects} }, $object;
@ -424,9 +425,10 @@ sub rotate {
if (!defined $angle) {
$angle = Wx::GetNumberFromUser("", "Enter the rotation angle:", "Rotate", $object->rotate, -364, 364, $self);
return if !$angle || $angle == -1;
$angle = 0 - $angle; # rotate clockwise (be consistent with button icon)
}
$object->set_rotation($object->rotate + $angle);
$object->rotate($object->rotate + $angle);
$self->recenter;
$self->{canvas}->Refresh;
}
@ -436,12 +438,15 @@ sub changescale {
my ($obj_idx, $object) = $self->selected_object;
# we need thumbnail to be computed before allowing scaling
return if !$object->thumbnail;
# max scale factor should be above 2540 to allow importing files exported in inches
my $scale = Wx::GetNumberFromUser("", "Enter the scale % for the selected object:", "Scale", $object->scale*100, 0, 5000, $self);
my $scale = Wx::GetNumberFromUser("", "Enter the scale % for the selected object:", "Scale", $object->scale*100, 0, 100000, $self);
return if !$scale || $scale == -1;
$self->{list}->SetItem($obj_idx, 2, "$scale%");
$object->set_scale($scale / 100);
$object->scale($scale / 100);
$self->arrange;
}
@ -451,7 +456,7 @@ sub arrange {
my $total_parts = sum(map $_->instances_count, @{$self->{objects}}) or return;
my @size = ();
for my $a (X,Y) {
$size[$a] = max(map $_->size->[$a], @{$self->{objects}});
$size[$a] = max(map $_->transformed_size->[$a], @{$self->{objects}});
}
eval {
@ -476,7 +481,7 @@ sub split_object {
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.");
Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be split because it contains more than one volume/material.");
return;
}
@ -485,7 +490,7 @@ sub split_object {
my @new_meshes = $mesh->split_mesh;
if (@new_meshes == 1) {
Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be splitted because it already contains a single part.");
Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be split because it already contains a single part.");
return;
}
@ -525,24 +530,32 @@ sub export_gcode {
}
# get config before spawning the thread because ->config needs GetParent and it's not available there
my $print = $self->_init_print;
my $print = $self->skeinpanel->init_print;
# select output file
$self->{output_file} = $main::opt{output};
{
$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}),
my $dlg = Wx::FileDialog->new($self, 'Save G-code file as:', Slic3r::GUI->output_path(dirname($self->{output_file})),
basename($self->{output_file}), &Slic3r::GUI::SkeinPanel::FILE_WILDCARDS->{gcode}, wxFD_SAVE);
if ($dlg->ShowModal != wxID_OK) {
$dlg->Destroy;
return;
}
$Slic3r::GUI::Settings->{_}{last_output_path} = dirname($dlg->GetPath);
Slic3r::GUI->save_settings;
$self->{output_file} = $Slic3r::GUI::SkeinPanel::last_output_file = $dlg->GetPath;
$dlg->Destroy;
}
$self->statusbar->StartBusy;
# It looks like declaring a local $SIG{__WARN__} prevents the ugly
# "Attempt to free unreferenced scalar" warning...
local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self);
if ($Slic3r::have_threads) {
@_ = ();
$self->{export_thread} = threads->create(sub {
$self->export_gcode2(
$print,
@ -580,21 +593,6 @@ sub export_gcode {
}
}
sub _init_print {
my $self = shift;
my %extra_variables = ();
if ($self->skeinpanel->{mode} eq 'expert') {
$extra_variables{"${_}_preset"} = $self->skeinpanel->{options_tabs}{$_}->current_preset->{name}
for qw(print filament printer);
}
return Slic3r::Print->new(
config => $self->skeinpanel->config,
extra_variables => { %extra_variables },
);
}
sub export_gcode2 {
my $self = shift;
my ($print, $output_file, %params) = @_;
@ -685,7 +683,7 @@ sub _get_export_file {
my $output_file = $main::opt{output};
{
$output_file = $self->_init_print->expanded_output_filepath($output_file, $self->{objects}[0]->input_file);
$output_file = $self->skeinpanel->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);
@ -705,6 +703,7 @@ sub make_model {
my $model = Slic3r::Model->new;
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,
@ -717,13 +716,15 @@ sub make_model {
);
$model->set_material($volume->material_id || 0, {});
}
$new_model_object->scale($plater_object->scale);
$new_model_object->align_to_origin;
$new_model_object->add_instance(
rotation => $plater_object->rotate,
offset => [ @$_ ],
rotation => $plater_object->rotate, # around center point
scaling_factor => $plater_object->scale,
offset => Slic3r::Point->new($_),
) for @{$plater_object->instances};
}
$model->align_to_origin;
return $model;
}
@ -745,6 +746,7 @@ sub make_thumbnail {
}
};
@_ = ();
$Slic3r::have_threads ? threads->create($cb)->detach : $cb->();
}
@ -766,15 +768,17 @@ sub recenter {
my @print_bb = Slic3r::Geometry::bounding_box([
map {
my $obj = $_;
map {
my $instance = $_;
$instance, [ map $instance->[$_] + $obj->size->[$_], X,Y ];
} @{$obj->instances};
my $bb = $obj->transformed_bounding_box;
my @points = ($bb->min_point, $bb->max_point);
map Slic3r::Geometry::move_points($_, @points), @{$obj->instances};
} @{$self->{objects}},
]);
# $self->{shift} contains the offset in pixels to add to object instances in order to center them
# it is expressed in upwards Y
$self->{shift} = [
($self->{canvas}->GetSize->GetWidth - $self->to_pixel($print_bb[X2] + $print_bb[X1])) / 2,
($self->{canvas}->GetSize->GetHeight - $self->to_pixel($print_bb[Y2] + $print_bb[Y1])) / 2,
$self->to_pixel(-$print_bb[X1]) + ($self->{canvas}->GetSize->GetWidth - $self->to_pixel($print_bb[X2] - $print_bb[X1])) / 2,
$self->to_pixel(-$print_bb[Y1]) + ($self->{canvas}->GetSize->GetHeight - $self->to_pixel($print_bb[Y2] - $print_bb[Y1])) / 2,
];
}
@ -808,10 +812,10 @@ sub _update_bed_size {
# supposing the preview canvas is square, calculate the scaling factor
# to constrain print bed area inside preview
my $bed_size = $self->{config}->bed_size;
my $canvas_side = CANVAS_SIZE->[X]; # when the canvas is not rendered yet, its GetSize() method returns 0,0
my $bed_largest_side = $bed_size->[X] > $bed_size->[Y] ? $bed_size->[X] : $bed_size->[Y];
$self->{scaling_factor} = $canvas_side / $bed_largest_side;
# when the canvas is not rendered yet, its GetSize() method returns 0,0
$self->{scaling_factor} = CANVAS_SIZE->[X] / max(@{ $self->{config}->bed_size });
$_->thumbnail_scaling_factor($self->{scaling_factor}) for @{ $self->{objects} };
$self->recenter;
}
# this is called on the canvas
@ -861,12 +865,15 @@ sub repaint {
@{$parent->{object_previews}} = ();
for my $obj_idx (0 .. $#{$parent->{objects}}) {
my $object = $parent->{objects}[$obj_idx];
next unless $object->thumbnail;
next unless $object->thumbnail && @{$object->thumbnail->expolygons};
for my $instance_idx (0 .. $#{$object->instances}) {
my $instance = $object->instances->[$instance_idx];
push @{$parent->{object_previews}}, [ $obj_idx, $instance_idx, $object->thumbnail->clone ];
$_->translate(map $parent->to_pixel($instance->[$_]) + $parent->{shift}[$_], (X,Y))
for @{$parent->{object_previews}->[-1][2]->expolygons};
my $thumbnail = $object->transformed_thumbnail
->clone
->translate(map $parent->to_pixel($instance->[$_]) + $parent->{shift}[$_], (X,Y));
push @{$parent->{object_previews}}, [ $obj_idx, $instance_idx, $thumbnail ];
my $drag_object = $self->{drag_object};
if (defined $drag_object && $obj_idx == $drag_object->[0] && $instance_idx == $drag_object->[1]) {
@ -881,7 +888,7 @@ sub repaint {
# if sequential printing is enabled and we have more than one object
if ($parent->{config}->complete_objects && (map @{$_->instances}, @{$parent->{objects}}) > 1) {
my $convex_hull = Slic3r::Polygon->new(convex_hull([ map @{$_->contour}, @{$parent->{object_previews}->[-1][2]->expolygons} ]));
my $clearance = +($convex_hull->offset($parent->{config}->extruder_clearance_radius / 2 * $parent->{scaling_factor}, 1, JT_ROUND))[0];
my ($clearance) = @{offset([$convex_hull], $parent->{config}->extruder_clearance_radius / 2 * $parent->{scaling_factor}, 100, JT_ROUND)};
$dc->SetPen($parent->{clearance_pen});
$dc->SetBrush($parent->{transparent_brush});
$dc->DrawPolygon($parent->_y($clearance), 0, 0);
@ -892,7 +899,7 @@ sub repaint {
# draw skirt
if (@{$parent->{object_previews}} && $parent->{config}->skirts) {
my $convex_hull = Slic3r::Polygon->new(convex_hull([ map @{$_->contour}, map @{$_->[2]->expolygons}, @{$parent->{object_previews}} ]));
$convex_hull = +($convex_hull->offset($parent->{config}->skirt_distance * $parent->{scaling_factor}, 1, JT_ROUND))[0];
($convex_hull) = @{offset([$convex_hull], $parent->{config}->skirt_distance * $parent->{scaling_factor}, 100, JT_ROUND)};
$dc->SetPen($parent->{skirt_pen});
$dc->SetBrush($parent->{transparent_brush});
$dc->DrawPolygon($parent->_y($convex_hull), 0, 0) if $convex_hull;
@ -938,7 +945,6 @@ sub mouse_event {
my ($obj_idx, $instance_idx, $thumbnail) = @$preview;
my $instance = $parent->{objects}[$obj_idx]->instances->[$instance_idx];
$instance->[$_] = $parent->to_units($pos->[$_] - $self->{drag_start_pos}[$_] - $parent->{shift}[$_]) for X,Y;
$instance = $parent->_y([$instance])->[0];
$parent->Refresh;
}
} elsif ($event->Moving) {
@ -1061,19 +1067,21 @@ sub OnDropFiles {
package Slic3r::GUI::Plater::Object;
use Moo;
use Math::ConvexHull::MonotoneChain qw(convex_hull);
use Slic3r::Geometry qw(X Y Z);
use Math::ConvexHull::MonotoneChain qw();
use Slic3r::Geometry qw(X Y Z MIN MAX deg2rad);
has 'name' => (is => 'rw', required => 1);
has 'input_file' => (is => 'rw', required => 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 'bounding_box' => (is => 'rw'); # 3D bb of original object (aligned to origin) with no rotation or scaling
has 'convex_hull' => (is => 'rw'); # 2D convex hull of original object (aligned to origin) with no rotation or scaling
has 'scale' => (is => 'rw', default => sub { 1 }, trigger => \&_transform_thumbnail);
has 'rotate' => (is => 'rw', default => sub { 0 }, trigger => \&_transform_thumbnail); # around object center point
has 'instances' => (is => 'rw', default => sub { [] }); # upward Y axis
has 'thumbnail' => (is => 'rw');
has 'thumbnail_scaling_factor' => (is => 'rw');
has 'thumbnail' => (is => 'rw', trigger => \&_transform_thumbnail);
has 'transformed_thumbnail' => (is => 'rw');
has 'thumbnail_scaling_factor' => (is => 'rw', trigger => \&_transform_thumbnail);
has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ]
# statistics
@ -1085,10 +1093,14 @@ has 'is_manifold' => (is => 'rw');
sub _trigger_model_object {
my $self = shift;
if ($self->model_object) {
$self->model_object->align_to_origin;
$self->bounding_box($self->model_object->bounding_box);
my $mesh = $self->model_object->mesh;
$self->size([$mesh->size]);
$self->convex_hull(Slic3r::Polygon->new(Math::ConvexHull::MonotoneChain::convex_hull($mesh->used_vertices)));
$self->facets(scalar @{$mesh->facets});
$self->vertices(scalar @{$mesh->vertices});
$self->materials($self->model_object->materials_count);
}
}
@ -1104,7 +1116,7 @@ sub free_model_object {
my $self = shift;
# only delete mesh from memory if we can retrieve it from the original file
return unless $self->input_file && $self->input_file_object_id;
return unless $self->input_file && defined $self->input_file_object_id;
$self->model_object(undef);
}
@ -1127,54 +1139,58 @@ sub instances_count {
sub make_thumbnail {
my $self = shift;
my @points = map [ @$_[X,Y] ], @{$self->model_object->mesh->vertices};
my $mesh = $self->model_object->mesh;
my $mesh = $self->model_object->mesh; # $self->model_object is already aligned to origin
my $thumbnail = Slic3r::ExPolygon::Collection->new(
expolygons => (@{$mesh->facets} <= 5000)
? $mesh->horizontal_projection
: [ Slic3r::ExPolygon->new(convex_hull($mesh->vertices)) ],
: [ Slic3r::ExPolygon->new($self->convex_hull) ],
);
for (map @$_, map @$_, @{$thumbnail->expolygons}) {
@$_ = map $_ * $self->thumbnail_scaling_factor, @$_;
}
@{$thumbnail->expolygons} = map $_->simplify(0.5), grep $_->area >= 1, @{$thumbnail->expolygons};
foreach my $expolygon (@{$thumbnail->expolygons}) {
$expolygon->rotate(Slic3r::Geometry::deg2rad($self->rotate));
$expolygon->scale($self->scale);
}
@{$thumbnail->expolygons} = grep @$_, @{$thumbnail->expolygons};
$thumbnail->align_to_origin;
# only simplify expolygons larger than the threshold
@{$thumbnail->expolygons} = grep @$_,
map { ($_->area >= 1) ? $_->simplify(0.5) : $_ }
@{$thumbnail->expolygons};
$self->thumbnail($thumbnail); # ignored in multi-threaded environments
$self->free_model_object;
return $thumbnail;
}
sub set_rotation {
sub _transform_thumbnail {
my $self = shift;
my ($angle) = @_;
if ($self->thumbnail) {
$self->thumbnail->rotate(Slic3r::Geometry::deg2rad($angle - $self->rotate));
$self->thumbnail->align_to_origin;
my $z_size = $self->size->[Z];
$self->size([ (map $_ / $self->thumbnail_scaling_factor, @{$self->thumbnail->size}), $z_size ]);
}
$self->rotate($angle);
return unless $self->thumbnail;
my $t = $self->_apply_transform($self->thumbnail);
$t->scale($self->thumbnail_scaling_factor);
$self->transformed_thumbnail($t);
}
sub set_scale {
# bounding box with applied rotation and scaling
sub transformed_bounding_box {
my $self = shift;
my ($scale) = @_;
my $factor = $scale / $self->scale;
return if $factor == 1;
$self->size->[$_] *= $factor for X,Y,Z;
if ($self->thumbnail) {
$_->scale($factor) for @{$self->thumbnail->expolygons};
$self->thumbnail->align_to_origin;
}
$self->scale($scale);
my $bb = Slic3r::Geometry::BoundingBox->new_from_points($self->_apply_transform($self->convex_hull));
$bb->extents->[Z] = $self->bounding_box->clone->extents->[Z];
return $bb;
}
sub _apply_transform {
my $self = shift;
my ($entity) = @_; # can be anything that implements ->clone(), ->rotate() and ->scale()
# the order of these transformations MUST be the same everywhere, including
# in Slic3r::Print->add_model()
return $entity
->clone
->rotate(deg2rad($self->rotate), $self->bounding_box->center_2D)
->scale($self->scale);
}
sub transformed_size {
my $self = shift;
return $self->transformed_bounding_box->size;
}
1;

View File

@ -10,10 +10,12 @@ use base 'Wx::Dialog';
sub new {
my $class = shift;
my ($parent, %params) = @_;
my $self = $class->SUPER::new($parent, -1, "Object", wxDefaultPosition, [500,350]);
my $self = $class->SUPER::new($parent, -1, "Object", wxDefaultPosition, [500,350], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
$self->{object} = $params{object};
$self->{tabpanel} = Wx::Notebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL);
$self->{tabpanel}->AddPage($self->{preview} = Slic3r::GUI::Plater::ObjectDialog::PreviewTab->new($self->{tabpanel}, object => $self->{object}), "Preview")
if $Slic3r::GUI::have_OpenGL;
$self->{tabpanel}->AddPage($self->{info} = Slic3r::GUI::Plater::ObjectDialog::InfoTab->new($self->{tabpanel}, object => $self->{object}), "Info");
$self->{tabpanel}->AddPage($self->{layers} = Slic3r::GUI::Plater::ObjectDialog::LayersTab->new($self->{tabpanel}, object => $self->{object}), "Layers");
@ -33,6 +35,7 @@ sub new {
$sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
$self->SetSizer($sizer);
$self->SetMinSize($self->GetSize);
return $self;
}
@ -75,7 +78,7 @@ sub get_properties {
my $object = $self->{object};
return [
['Name' => $object->name],
['Size' => sprintf "%.2f x %.2f x %.2f", @{$object->size}],
['Size' => sprintf "%.2f x %.2f x %.2f", @{$object->transformed_size}],
['Facets' => $object->facets],
['Vertices' => $object->vertices],
['Materials' => $object->materials],
@ -83,6 +86,24 @@ sub get_properties {
];
}
package Slic3r::GUI::Plater::ObjectDialog::PreviewTab;
use Wx qw(:dialog :id :misc :sizer :systemsettings);
use base 'Wx::Panel';
sub new {
my $class = shift;
my ($parent, %params) = @_;
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize);
$self->{object} = $params{object};
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
$sizer->Add(Slic3r::GUI::PreviewCanvas->new($self, $self->{object}->get_model_object->mesh), 1, wxEXPAND, 0);
$self->SetSizer($sizer);
$sizer->SetSizeHints($self);
return $self;
}
package Slic3r::GUI::Plater::ObjectDialog::LayersTab;
use Wx qw(:dialog :id :misc :sizer :systemsettings);
use Wx::Grid;
@ -98,7 +119,7 @@ sub new {
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
{
my $label = Wx::StaticText->new($self, -1, "You can use this section to override the default layer height for parts of this object.",
my $label = Wx::StaticText->new($self, -1, "You can use this section to override the default layer height for parts of this object. Set layer height to zero to skip portions of the input file.",
wxDefaultPosition, [-1, 25]);
$label->SetFont(Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
$sizer->Add($label, 0, wxEXPAND | wxALL, 10);
@ -162,7 +183,7 @@ sub CanClose {
Slic3r::GUI::show_error($self, "Invalid Z range $min-$max.");
return 0;
}
if ($height <= 0) {
if ($height < 0) {
Slic3r::GUI::show_error($self, "Invalid layer height $height.");
return 0;
}

View File

@ -22,6 +22,21 @@ sub new {
values => ['simple','expert'],
default => $Slic3r::GUI::Settings->{_}{mode},
},
{
opt_key => 'version_check',
type => 'bool',
label => 'Check for updates',
tooltip => 'If this is enabled, Slic3r will check for updates daily and display a reminder if a newer version is available.',
default => $Slic3r::GUI::Settings->{_}{version_check} // 1,
readonly => !Slic3r::GUI->have_version_check,
},
{
opt_key => 'remember_output_path',
type => 'bool',
label => 'Remember output directory',
tooltip => 'If this is enabled, Slic3r will prompt the last output directory instead of the one containing the input files.',
default => $Slic3r::GUI::Settings->{_}{remember_output_path},
},
],
on_change => sub { $self->{values}{$_[0]} = $_[1] },
label_width => 100,

View File

@ -0,0 +1,200 @@
package Slic3r::GUI::PreviewCanvas;
use strict;
use warnings;
use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_TIMER EVT_MOUSEWHEEL);
# must load OpenGL *before* Wx::GLCanvas
use OpenGL qw(:glconstants :glfunctions);
use base qw(Wx::GLCanvas Class::Accessor);
use Slic3r::Geometry qw(X Y Z MIN MAX triangle_normal normalize deg2rad tan);
use Wx::GLCanvas qw(:all);
__PACKAGE__->mk_accessors( qw(timer x_rot y_rot dirty init mesh_center zoom
verts norms) );
sub new {
my ($class, $parent, $mesh) = @_;
my $self = $class->SUPER::new($parent);
# prepare mesh
{
$self->mesh_center($mesh->center);
$self->zoom(0.1);
my @verts = map $self->zoom * $_, map @{ $mesh->vertices->[$_] }, map @$_, @{$mesh->facets};
$self->verts(OpenGL::Array->new_list(GL_FLOAT, @verts));
my @norms = map { @$_, @$_, @$_ } map normalize(triangle_normal(map $mesh->vertices->[$_], @$_)), @{$mesh->facets};
$self->norms(OpenGL::Array->new_list(GL_FLOAT, @norms));
}
my $timer = $self->timer( Wx::Timer->new($self) );
$timer->Start(50);
$self->x_rot(0);
$self->y_rot(0);
EVT_PAINT($self, sub {
my $dc = Wx::PaintDC->new($self);
$self->Render($dc);
});
EVT_SIZE($self, sub { $self->dirty(1) });
EVT_IDLE($self, sub {
return unless $self->dirty;
return if !$self->IsShownOnScreen;
$self->Resize( $self->GetSizeWH );
$self->Refresh;
});
EVT_TIMER($self, -1, sub {
my ($self, $e) = @_;
$self->x_rot( $self->x_rot - 1 );
$self->y_rot( $self->y_rot + 2 );
$self->dirty(1);
Wx::WakeUpIdle;
});
EVT_MOUSEWHEEL($self, sub {
my ($self, $e) = @_;
my $zoom = $self->zoom * (1.0 - $e->GetWheelRotation() / $e->GetWheelDelta() / 10);
$zoom = 0.001 if $zoom < 0.001;
$zoom = 0.1 if $zoom > 0.1;
$self->zoom($zoom);
$self->Refresh;
});
return $self;
}
sub GetContext {
my ($self) = @_;
if (Wx::wxVERSION >= 2.009) {
return $self->{context} ||= Wx::GLContext->new($self);
} else {
return $self->SUPER::GetContext;
}
}
sub SetCurrent {
my ($self, $context) = @_;
if (Wx::wxVERSION >= 2.009) {
return $self->SUPER::SetCurrent($context);
} else {
return $self->SUPER::SetCurrent;
}
}
sub Resize {
my ($self, $x, $y) = @_;
return unless $self->GetContext;
$self->dirty(0);
$self->SetCurrent($self->GetContext);
glViewport(0, 0, $x, $y);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
my_gluPerspective(45, $x/$y, .5, 100);
glMatrixMode(GL_MODELVIEW);
}
sub my_gluPerspective {
my ($fov, $ratio, $near, $far) = @_;
my $top = tan(deg2rad($fov)*0.5) * $near;
my $bottom = -$top;
my $left = $ratio * $bottom;
my $right = $ratio * $top;
glFrustum( $left, $right, $bottom, $top, $near, $far );
}
sub DESTROY {
my $self = shift;
$self->timer->Stop;
$self->timer(undef);
}
sub InitGL {
my $self = shift;
return if $self->init;
return unless $self->GetContext;
$self->init(1);
glEnable(GL_NORMALIZE);
glEnable(GL_LIGHTING);
glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
# Settings for our light.
my @LightPos = (0, 0, 2, 1.0);
my @LightAmbient = (0.1, 0.1, 0.1, 1.0);
my @LightDiffuse = (0.7, 0.5, 0.5, 1.0);
my @LightSpecular = (0.1, 0.1, 0.1, 0.1);
# Enables Smooth Color Shading; try GL_FLAT for (lack of) fun.
glShadeModel(GL_SMOOTH);
# Set up a light, turn it on.
glLightfv_p(GL_LIGHT1, GL_POSITION, @LightPos);
glLightfv_p(GL_LIGHT1, GL_AMBIENT, @LightAmbient);
glLightfv_p(GL_LIGHT1, GL_DIFFUSE, @LightDiffuse);
glLightfv_p(GL_LIGHT1, GL_SPECULAR, @LightSpecular);
glEnable(GL_LIGHT1);
# A handy trick -- have surface material mirror the color.
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
glEnable(GL_COLOR_MATERIAL);
}
sub Render {
my ($self, $dc) = @_;
return unless $self->GetContext;
$self->SetCurrent($self->GetContext);
$self->InitGL;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glTranslatef( 0, 0, -5 );
# this needs to get a lot better...
glRotatef( $self->x_rot, 1, 0, 0 );
glRotatef( $self->y_rot, 0, 0, 1 );
glTranslatef(map -$_ * $self->zoom, @{ $self->mesh_center });
$self->draw_mesh;
glPopMatrix();
glFlush();
$self->SwapBuffers();
}
sub draw_mesh {
my $self = shift;
glEnable(GL_CULL_FACE);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
glVertexPointer_p(3, $self->verts);
glCullFace(GL_BACK);
glNormalPointer_p($self->norms);
glDrawArrays(GL_TRIANGLES, 0, $self->verts->elements / 3);
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
}
1;

View File

@ -78,6 +78,17 @@ sub load_config {
}
}
sub set_value {
my $self = shift;
my ($opt_key, $value) = @_;
my $changed = 0;
foreach my $optgroup (@{$self->{optgroups}}) {
$changed = 1 if $optgroup->set_value($opt_key, $value);
}
return $changed;
}
sub is_dirty { 0 }
sub config { $_[0]->{config}->clone }
@ -172,11 +183,6 @@ sub build {
},
],
);
$self->append_optgroup(
title => 'Cooling',
options => [qw(cooling)],
);
}
package Slic3r::GUI::SimpleTab::Printer;

View File

@ -49,9 +49,12 @@ sub new {
$self->{tabpanel},
on_value_change => sub {
$self->{plater}->on_config_change(@_) if $self->{plater}; # propagate config change events to the plater
if ($self->{mode} eq 'simple' && $init) { # don't save while loading for the first time
# save config
$self->config->save("$Slic3r::GUI::datadir/simple.ini");
if ($init) { # don't save while loading for the first time
if ($self->{mode} eq 'simple') {
# save config
$self->config->save("$Slic3r::GUI::datadir/simple.ini");
}
$self->config->save($Slic3r::GUI::autosave) if $Slic3r::GUI::autosave;
}
},
on_presets_changed => sub {
@ -73,7 +76,7 @@ sub new {
return $self;
}
sub do_slice {
sub quick_slice {
my $self = shift;
my %params = @_;
@ -122,7 +125,7 @@ sub do_slice {
$Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_file);
Slic3r::GUI->save_settings;
my $print = Slic3r::Print->new(config => $config);
my $print = $self->init_print;
$print->add_model(Slic3r::Model->read_from_file($input_file));
$print->validate;
@ -133,7 +136,8 @@ sub do_slice {
} elsif ($params{save_as}) {
$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),
my $dlg = Wx::FileDialog->new($self, 'Save ' . ($params{export_svg} ? 'SVG' : 'G-code') . ' file as:',
Slic3r::GUI->output_path(dirname($output_file)),
basename($output_file), $params{export_svg} ? FILE_WILDCARDS->{svg} : FILE_WILDCARDS->{gcode}, wxFD_SAVE);
if ($dlg->ShowModal != wxID_OK) {
$dlg->Destroy;
@ -141,6 +145,8 @@ sub do_slice {
}
$output_file = $dlg->GetPath;
$last_output_file = $output_file unless $params{export_svg};
$Slic3r::GUI::Settings->{_}{last_output_path} = dirname($output_file);
Slic3r::GUI->save_settings;
$dlg->Destroy;
}
@ -186,6 +192,21 @@ sub do_slice {
Slic3r::GUI::catch_error($self, sub { $process_dialog->Destroy if $process_dialog });
}
sub init_print {
my $self = shift;
my %extra_variables = ();
if ($self->{mode} eq 'expert') {
$extra_variables{"${_}_preset"} = $self->{options_tabs}{$_}->current_preset->{name}
for qw(print filament printer);
}
return Slic3r::Print->new(
config => $self->config,
extra_variables => { %extra_variables },
);
}
sub export_config {
my $self = shift;
@ -243,7 +264,9 @@ sub config_wizard {
return unless $self->check_unsaved_changes;
if (my $config = Slic3r::GUI::ConfigWizard->new($self)->run) {
$_->select_default_preset for values %{$self->{options_tabs}};
if ($self->{mode} eq 'expert') {
$_->select_default_preset for values %{$self->{options_tabs}};
}
$self->load_config($config);
}
}
@ -311,7 +334,7 @@ sub config {
# retrieve filament presets and build a single config object for them
my $filament_config;
if ($self->{plater}->filament_presets == 1 || $self->{mode} eq 'simple') {
if (!$self->{plater} || $self->{plater}->filament_presets == 1 || $self->{mode} eq 'simple') {
$filament_config = $self->{options_tabs}{filament}->config;
} else {
# TODO: handle dirty presets.

View File

@ -197,16 +197,21 @@ sub on_select_preset {
$self->{config}->set($opt_key, $preset_config->get($opt_key))
if $preset_config->has($opt_key);
}
($preset->{default} || $preset->{external})
? $self->{btn_delete_preset}->Disable
: $self->{btn_delete_preset}->Enable;
$self->on_preset_loaded;
$self->reload_values;
$self->set_dirty(0);
$Slic3r::GUI::Settings->{presets}{$self->name} = $preset->{file} ? basename($preset->{file}) : '';
};
Slic3r::GUI::catch_error($self);
($preset->{default} || $preset->{external})
? $self->{btn_delete_preset}->Disable
: $self->{btn_delete_preset}->Enable;
if ($@) {
$@ = "I was unable to load the selected config file: $@";
Slic3r::GUI::catch_error($self);
$self->select_default_preset;
}
$self->on_preset_loaded;
$self->reload_values;
$self->set_dirty(0);
$Slic3r::GUI::Settings->{presets}{$self->name} = $preset->{file} ? basename($preset->{file}) : '';
Slic3r::GUI->save_settings;
}
@ -407,7 +412,7 @@ sub build {
},
{
title => 'Advanced',
options => [qw(avoid_crossing_perimeters external_perimeters_first)],
options => [qw(avoid_crossing_perimeters external_perimeters_first spiral_vase)],
},
]);
@ -559,8 +564,9 @@ sub build {
$self->add_options_page('Cooling', 'hourglass.png', optgroups => [
{
title => 'Enable',
options => [qw(cooling)],
options => [qw(fan_always_on cooling)],
lines => [
Slic3r::GUI::OptionsGroup->single_option_line('fan_always_on'),
Slic3r::GUI::OptionsGroup->single_option_line('cooling'),
{
label => '',
@ -570,7 +576,7 @@ sub build {
},
{
title => 'Fan settings',
options => [qw(min_fan_speed max_fan_speed bridge_fan_speed disable_fan_first_layers fan_always_on)],
options => [qw(min_fan_speed max_fan_speed bridge_fan_speed disable_fan_first_layers)],
lines => [
{
label => 'Fan speed',
@ -578,7 +584,6 @@ sub build {
},
Slic3r::GUI::OptionsGroup->single_option_line('bridge_fan_speed'),
Slic3r::GUI::OptionsGroup->single_option_line('disable_fan_first_layers'),
Slic3r::GUI::OptionsGroup->single_option_line('fan_always_on'),
],
},
{
@ -595,6 +600,15 @@ sub _update_description {
my $config = $self->config;
my $msg = "";
my $fan_other_layers = $config->fan_always_on
? sprintf "will always run at %d%%%s.", $config->min_fan_speed,
($config->disable_fan_first_layers > 1
? " except for the first " . $config->disable_fan_first_layers . " layers"
: $config->disable_fan_first_layers == 1
? " except for the first layer"
: "")
: "will be turned off.";
if ($config->cooling) {
$msg = sprintf "If estimated layer time is below ~%ds, fan will run at 100%% and print speed will be reduced so that no less than %ds are spent on that layer (however, speed will never be reduced below %dmm/s).",
$config->slowdown_below_layer_time, $config->slowdown_below_layer_time, $config->min_print_speed;
@ -602,11 +616,9 @@ sub _update_description {
$msg .= sprintf "\nIf estimated layer time is greater, but still below ~%ds, fan will run at a proportionally decreasing speed between %d%% and %d%%.",
$config->fan_below_layer_time, $config->max_fan_speed, $config->min_fan_speed;
}
if ($config->fan_always_on) {
$msg .= sprintf "\nDuring the other layers, fan will always run at %d%%.", $config->min_fan_speed;
} else {
$msg .= "\nDuring the other layers, fan will be turned off."
}
$msg .= "\nDuring the other layers, fan $fan_other_layers"
} else {
$msg = "Fan $fan_other_layers";
}
$self->{description_line}->SetText($msg);
}
@ -686,7 +698,7 @@ sub build {
$self->_build_extruder_pages;
}
sub _extruder_options { qw(nozzle_diameter extruder_offset retract_length retract_lift retract_speed retract_restart_extra retract_before_travel
sub _extruder_options { qw(nozzle_diameter extruder_offset retract_length retract_lift retract_speed retract_restart_extra retract_before_travel wipe
retract_layer_change retract_length_toolchange retract_restart_extra_toolchange) }
sub config {
@ -720,7 +732,7 @@ sub _build_extruder_pages {
title => 'Retraction',
options => [
map "${_}#${extruder_idx}",
qw(retract_length retract_lift retract_speed retract_restart_extra retract_before_travel retract_layer_change)
qw(retract_length retract_lift retract_speed retract_restart_extra retract_before_travel retract_layer_change wipe)
],
},
{

View File

@ -7,7 +7,7 @@ our @ISA = qw(Exporter);
our @EXPORT_OK = qw(
PI X Y Z A B X1 Y1 X2 Y2 MIN MAX epsilon slope line_atan lines_parallel
line_point_belongs_to_segment points_coincide distance_between_points
comparable_distance_between_points chained_path_items chained_path_points
chained_path_items chained_path_points normalize tan move_points_3D
line_length midpoint point_in_polygon point_in_segment segment_in_segment
point_is_on_left_of_segment polyline_lines polygon_lines nearest_point
point_along_segment polygon_segment_having_point polygon_has_subsegment
@ -45,6 +45,11 @@ sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR }
sub scale ($) { $_[0] / &Slic3r::SCALING_FACTOR }
sub unscale ($) { $_[0] * &Slic3r::SCALING_FACTOR }
sub tan {
my ($angle) = @_;
return (sin $angle) / (cos $angle);
}
sub slope {
my ($line) = @_;
return undef if abs($line->[B][X] - $line->[A][X]) < epsilon; # line is vertical
@ -115,11 +120,6 @@ sub distance_between_points {
return sqrt((($p1->[X] - $p2->[X])**2) + ($p1->[Y] - $p2->[Y])**2);
}
sub comparable_distance_between_points {
my ($p1, $p2) = @_;
return (($p1->[X] - $p2->[X])**2) + (($p1->[Y] - $p2->[Y])**2);
}
sub point_line_distance {
my ($point, $line) = @_;
return distance_between_points($point, $line->[A])
@ -248,14 +248,27 @@ sub nearest_point_index {
my ($point, $points) = @_;
my ($nearest_point_index, $distance) = ();
my $point_x = $point->[X];
my $point_y = $point->[Y];
for my $i (0..$#$points) {
my $d = comparable_distance_between_points($point, $points->[$i]);
if (!defined $distance || $d < $distance) {
$nearest_point_index = $i;
$distance = $d;
return $i if $distance < epsilon;
}
my $d = ($point_x - $points->[$i]->[X])**2;
# If the X distance of the candidate is > than the total distance of the
# best previous candidate, we know we don't want it
next if (defined $distance && $d > $distance);
# If the total distance of the candidate is > than the total distance of the
# best previous candidate, we know we don't want it
$d += ($point_y - $points->[$i]->[Y])**2;
next if (defined $distance && $d > $distance);
$nearest_point_index = $i;
$distance = $d;
last if $distance < epsilon;
}
return $nearest_point_index;
}
@ -375,6 +388,15 @@ sub move_points {
return map Slic3r::Point->new($shift->[X] + $_->[X], $shift->[Y] + $_->[Y]), @points;
}
sub move_points_3D {
my ($shift, @points) = @_;
return map [
$shift->[X] + $_->[X],
$shift->[Y] + $_->[Y],
$shift->[Z] + $_->[Z],
], @points;
}
# implementation of Liang-Barsky algorithm
# polygon must be convex and ccw
sub clip_segment_polygon {
@ -453,6 +475,14 @@ sub triangle_normal {
return normal($u, $v);
}
sub normalize {
my ($line) = @_;
my $len = sqrt( ($line->[X]**2) + ($line->[Y]**2) + ($line->[Z]**2) )
or return [0, 0, 0]; # to avoid illegal division by zero
return [ map $_ / $len, @$line ];
}
# 2D dot product
sub dot {
my ($u, $v) = @_;
@ -671,10 +701,10 @@ sub bounding_box {
}
sub bounding_box_center {
my @bounding_box = bounding_box(@_);
my ($bounding_box) = @_;
return Slic3r::Point->new(
($bounding_box[X2] + $bounding_box[X1]) / 2,
($bounding_box[Y2] + $bounding_box[Y1]) / 2,
($bounding_box->[X2] + $bounding_box->[X1]) / 2,
($bounding_box->[Y2] + $bounding_box->[Y1]) / 2,
);
}

View File

@ -0,0 +1,87 @@
package Slic3r::Geometry::BoundingBox;
use Moo;
use Slic3r::Geometry qw(X Y Z MIN MAX X1 Y1 X2 Y2);
use Storable qw();
has 'extents' => (is => 'ro', required => 1);
sub clone { Storable::dclone($_[0]) }
# 2D
sub new_from_points {
my $class = shift;
my ($points) = @_;
my $bb = [ Slic3r::Geometry::bounding_box($points) ];
return $class->new(extents => [
[ $bb->[X1], $bb->[X2] ],
[ $bb->[Y1], $bb->[Y2] ],
]);
}
# 3D
sub new_from_points_3D {
my $class = shift;
my ($points) = @_;
return $class->new(extents => [ Slic3r::Geometry::bounding_box_3D($points) ]);
}
# four-arguments 2D bb
sub bb {
my $self = shift;
my $extents = $self->extents;
return [ $extents->[X][MIN], $extents->[Y][MIN], $extents->[X][MAX], $extents->[Y][MAX] ];
}
sub polygon {
my $self = shift;
return Slic3r::Polygon->new_from_bounding_box($self->bb);
}
# note to $self
sub rotate {
die "Rotating an axis-aligned bounding box doesn't make any sense";
}
sub scale {
my $self = shift;
my ($factor) = @_;
for (@{$self->extents}) {
$_ *= $factor for @$_[MIN,MAX];
}
$self;
}
sub size {
my $self = shift;
my $extents = $self->extents;
return [ map $extents->[$_][MAX] - $extents->[$_][MIN], grep $extents->[$_], (X,Y,Z) ];
}
sub center {
my $self = shift;
my $extents = $self->extents;
return [ map +($extents->[$_][MAX] + $extents->[$_][MIN])/2, grep $extents->[$_], (X,Y,Z) ];
}
sub center_2D {
my $self = shift;
return Slic3r::Point->new(@{$self->center}[X,Y]);
}
sub min_point {
my $self = shift;
return Slic3r::Point->new($self->extents->[X][MIN], $self->extents->[Y][MIN]);
}
sub max_point {
my $self = shift;
return Slic3r::Point->new($self->extents->[X][MAX], $self->extents->[Y][MAX]);
}
1;

View File

@ -4,17 +4,24 @@ use warnings;
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(safety_offset offset offset_ex collapse_ex
our @EXPORT_OK = qw(safety_offset safety_offset_ex offset offset_ex collapse_ex
diff_ex diff union_ex intersection_ex xor_ex PFT_EVENODD JT_MITER JT_ROUND
JT_SQUARE is_counter_clockwise);
JT_SQUARE is_counter_clockwise union_pt offset2 offset2_ex traverse_pt
intersection);
use Math::Clipper 1.17 qw(:cliptypes :polyfilltypes :jointypes is_counter_clockwise area);
use Math::Clipper 1.22 qw(:cliptypes :polyfilltypes :jointypes is_counter_clockwise area);
use Slic3r::Geometry qw(scale);
our $clipper = Math::Clipper->new;
sub safety_offset {
my ($polygons, $factor) = @_;
return Math::Clipper::offset($polygons, $factor // (scale 1e-05), 100000, JT_MITER, 2);
return Math::Clipper::int_offset($polygons, $factor // (scale 1e-05), 100000, JT_MITER, 2);
}
sub safety_offset_ex {
my ($polygons, $factor) = @_;
return map Slic3r::ExPolygon->new($_),
@{Math::Clipper::ex_int_offset($polygons, $factor // (scale 1e-05), 100000, JT_MITER, 2)};
}
sub offset {
@ -23,13 +30,38 @@ sub offset {
$joinType //= JT_MITER;
$miterLimit //= 3;
my $offsets = Math::Clipper::offset($polygons, $distance, $scale, $joinType, $miterLimit);
my $offsets = Math::Clipper::int_offset($polygons, $distance, $scale, $joinType, $miterLimit);
return @$offsets;
}
sub offset2 {
my ($polygons, $distance1, $distance2, $scale, $joinType, $miterLimit) = @_;
$scale ||= 100000;
$joinType //= JT_MITER;
$miterLimit //= 3;
my $offsets = Math::Clipper::int_offset2($polygons, $distance1, $distance2, $scale, $joinType, $miterLimit);
return @$offsets;
}
sub offset_ex {
# offset polygons and then apply holes to the right contours
return @{ union_ex([ offset(@_) ]) };
my ($polygons, $distance, $scale, $joinType, $miterLimit) = @_;
$scale ||= 100000;
$joinType //= JT_MITER;
$miterLimit //= 3;
my $offsets = Math::Clipper::ex_int_offset($polygons, $distance, $scale, $joinType, $miterLimit);
return map Slic3r::ExPolygon->new($_), @$offsets;
}
sub offset2_ex {
my ($polygons, $delta1, $delta2, $scale, $joinType, $miterLimit) = @_;
$scale ||= 100000;
$joinType //= JT_MITER;
$miterLimit //= 3;
my $offsets = Math::Clipper::ex_int_offset2($polygons, $delta1, $delta2, $scale, $joinType, $miterLimit);
return map Slic3r::ExPolygon->new($_), @$offsets;
}
sub diff_ex {
@ -67,6 +99,14 @@ sub union_ex {
];
}
sub union_pt {
my ($polygons, $jointype, $safety_offset) = @_;
$jointype = PFT_NONZERO unless defined $jointype;
$clipper->clear;
$clipper->add_subject_polygons($safety_offset ? safety_offset($polygons) : $polygons);
return $clipper->pt_execute(CT_UNION, $jointype, $jointype);
}
sub intersection_ex {
my ($subject, $clip, $jointype, $safety_offset) = @_;
$jointype = PFT_NONZERO unless defined $jointype;
@ -79,6 +119,18 @@ sub intersection_ex {
];
}
sub intersection {
my ($subject, $clip, $jointype, $safety_offset) = @_;
$jointype = PFT_NONZERO unless defined $jointype;
$clipper->clear;
$clipper->add_subject_polygons($subject);
$clipper->add_clip_polygons($safety_offset ? safety_offset($clip) : $clip);
return [
map Slic3r::Polygon->new($_),
@{ $clipper->execute(CT_INTERSECTION, $jointype, $jointype) },
];
}
sub xor_ex {
my ($subject, $clip, $jointype) = @_;
$jointype = PFT_NONZERO unless defined $jointype;
@ -93,11 +145,7 @@ sub xor_ex {
sub collapse_ex {
my ($polygons, $width) = @_;
my @result = offset(
[ offset($polygons, -$width/2,) ],
+$width/2,
);
return union_ex([@result]);
return [ offset2_ex($polygons, -$width/2, +$width/2) ];
}
sub simplify_polygon {
@ -110,4 +158,22 @@ sub simplify_polygons {
return @{ Math::Clipper::simplify_polygons($polygons, $pft // PFT_NONZERO) };
}
sub traverse_pt {
my ($polynodes) = @_;
# use a nearest neighbor search to order these children
# TODO: supply second argument to chained_path_items() too?
my @nodes = @{Slic3r::Geometry::chained_path_items(
[ map [ ($_->{outer} ? $_->{outer}[0] : $_->{hole}[0]), $_ ], @$polynodes ],
)};
my @polygons = ();
foreach my $polynode (@$polynodes) {
# traverse the next depth
push @polygons, traverse_pt($polynode->{children});
push @polygons, $polynode->{outer} // [ reverse @{$polynode->{hole}} ];
}
return @polygons;
}
1;

View File

@ -54,6 +54,13 @@ sub support_material_contact_z {
return $self->print_z - ($self->height - $self->support_material_contact_height) / &Slic3r::SCALING_FACTOR;
}
sub upper_layer_slices {
my $self = shift;
my $upper_layer = $self->object->layers->[ $self->id + 1 ] or return [];
return $upper_layer->slices;
}
sub region {
my $self = shift;
my ($region_id) = @_;

View File

@ -1,9 +1,11 @@
package Slic3r::Layer::Region;
use Moo;
use List::Util qw(sum first);
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Geometry qw(PI scale chained_path_items points_coincide);
use Slic3r::Geometry::Clipper qw(safety_offset union_ex diff_ex intersection_ex);
use Slic3r::Geometry qw(PI X1 X2 Y1 Y2 A B scale chained_path_items points_coincide);
use Slic3r::Geometry::Clipper qw(safety_offset union_ex diff_ex intersection_ex
offset offset2_ex PFT_EVENODD union_pt traverse_pt diff intersection);
use Slic3r::Surface ':types';
has 'layer' => (
@ -95,12 +97,9 @@ sub make_surfaces {
# detect thin walls by offsetting slices by half extrusion inwards
{
my $width = $self->perimeter_flow->scaled_width;
my $outgrown = union_ex([
Slic3r::Geometry::Clipper::offset(
[Slic3r::Geometry::Clipper::offset([ map @$_, map $_->expolygon, @{$self->slices} ], -$width)],
+$width,
),
]);
my $outgrown = [
offset2_ex([ map @$_, map $_->expolygon, @{$self->slices} ], -$width, +$width),
];
my $diff = diff_ex(
[ map $_->p, @{$self->slices} ],
[ map @$_, @$outgrown ],
@ -138,7 +137,7 @@ sub _merge_loops {
# winding order.
# TODO: find a faster algorithm for this.
my @loops = sort { $a->encloses_point($b->[0]) ? 0 : 1 } @$loops; # outer first
$safety_offset //= scale 0.1;
$safety_offset //= scale 0.0499;
@loops = @{ safety_offset(\@loops, $safety_offset) };
my $expolygons = [];
while (my $loop = shift @loops) {
@ -162,269 +161,143 @@ sub make_perimeters {
my $perimeter_spacing = $self->perimeter_flow->scaled_spacing;
my $infill_spacing = $self->solid_infill_flow->scaled_spacing;
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 nearest-neighbor search
my @surfaces = @{chained_path_items([
map [ $_->contour->[0], $_ ], @{$self->slices},
])};
my $gap_area_threshold = $self->perimeter_flow->scaled_width ** 2;
$self->perimeters([]);
$self->fill_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 * $self->perimeter_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 @gaps = ();
# generate perimeters inwards (loop 0 is the external one)
my @contours = (); # array of Polygons with ccw orientation
my @holes = (); # array of Polygons with cw orientation
my @gaps = (); # array of ExPolygons
# we need to process each island separately because we might have different
# extra perimeters for each one
foreach my $surface (@{$self->slices}) {
# detect how many perimeters must be generated for this island
my $loop_number = $Slic3r::Config->perimeters + ($surface->extra_perimeters || 0);
push @perimeters, [] if $loop_number > 0;
# do one more loop (<= instead of <) so that we can detect gaps even after the desired
# number of perimeters has been generated
for (my $loop = 0; $loop <= $loop_number; $loop++) {
my $spacing = $perimeter_spacing;
$spacing /= 2 if $loop == 0;
# generate loops
# (one more than necessary so that we can detect gaps even after the desired
# number of perimeters has been generated)
my @last = @{$surface->expolygon};
for my $i (0 .. $loop_number) {
# external loop only needs half inset distance
my $spacing = ($i == 0)
? $perimeter_spacing / 2
: $perimeter_spacing;
# offsetting a polygon can result in one or many offset polygons
my @new_offsets = ();
foreach my $expolygon (@last_offsets) {
my @offsets = @{union_ex([
Slic3r::Geometry::Clipper::offset(
[Slic3r::Geometry::Clipper::offset($expolygon, -1.5*$spacing)],
+0.5*$spacing,
),
])};
push @new_offsets, @offsets;
# where the above check collapses the expolygon, then there's no room for an inner loop
# and we can extract the gap for later processing
my @offsets = offset2_ex(\@last, -1.5*$spacing, +0.5*$spacing);
my @contours_offsets = map $_->contour, @offsets;
my @holes_offsets = map $_->holes, @offsets;
@offsets = (@contours_offsets, @holes_offsets); # turn @offsets from ExPolygons to Polygons
# where offset2() collapses the expolygon, then there's no room for an inner loop
# and we can extract the gap for later processing
{
my $diff = diff_ex(
[ map @$_, $expolygon->offset_ex(-0.5*$spacing) ],
[ offset(\@last, -0.5*$spacing) ],
# +2 on the offset here makes sure that Clipper float truncation
# won't shrink the clip polygon to be smaller than intended.
[ Slic3r::Geometry::Clipper::offset([map @$_, @offsets], +0.5*$spacing + 2) ],
[ offset(\@offsets, +0.5*$spacing + 2) ],
);
push @gaps, grep $_->area >= $gap_area_threshold, @$diff;
}
last if !@new_offsets || $loop == $loop_number;
@last_offsets = @new_offsets;
# sort loops before storing them
@last_offsets = @{chained_path_items([
map [ $_->contour->[0], $_ ], @last_offsets,
])};
push @{ $perimeters[-1] }, [@last_offsets];
last if !@offsets || $i == $loop_number;
push @contours, @contours_offsets;
push @holes, @holes_offsets;
@last = @offsets;
}
# create one more offset to be used as boundary for fill
{
# we offset by half the perimeter spacing (to get to the actual infill boundary)
# and then we offset back and forth by the infill spacing to only consider the
# non-collapsing regions
push @{ $self->fill_surfaces },
map $_->simplify(&Slic3r::SCALED_RESOLUTION),
@{union_ex([
Slic3r::Geometry::Clipper::offset(
[Slic3r::Geometry::Clipper::offset([ map @$_, @last_offsets ], -($perimeter_spacing/2 + $infill_spacing))],
+$infill_spacing,
),
])};
}
# fill gaps
if ($Slic3r::Config->gap_fill_speed > 0 && $Slic3r::Config->fill_density > 0 && @gaps) {
my $filler = Slic3r::Fill::Rectilinear->new(layer_id => $self->layer->id);
# we should probably use this code to handle thin walls and remove that logic from
# make_surfaces(), but we need to enable dynamic extrusion width before as we can't
# use zigzag for thin walls.
# in the mean time we subtract thin walls from the detected gaps so that we don't
# reprocess them, causing overlapping thin walls and zigzag.
@gaps = @{diff_ex(
[ map @$_, @gaps ],
[ map $_->grow($self->perimeter_flow->scaled_width), @{$self->{thin_walls}} ],
1,
)};
my $w = $self->perimeter_flow->width;
my @widths = (1.5 * $w, $w, 0.4 * $w); # worth trying 0.2 too?
foreach my $width (@widths) {
my $flow = $self->perimeter_flow->clone(width => $width);
# extract the gaps having this width
my @this_width = map $_->offset_ex(+0.5*$flow->scaled_width),
map $_->noncollapsing_offset_ex(-0.5*$flow->scaled_width),
@gaps;
if (0) { # remember to re-enable t/dynamic.t
# fill gaps using dynamic extrusion width, by treating them like thin polygons,
# thus generating the skeleton and using it to fill them
my %path_args = (
role => EXTR_ROLE_SOLIDFILL,
flow_spacing => $flow->spacing,
);
push @{ $self->thin_fills }, map {
$_->isa('Slic3r::Polygon')
? (map $_->pack, Slic3r::ExtrusionLoop->new(polygon => $_, %path_args)->split_at_first_point) # we should keep these as loops
: Slic3r::ExtrusionPath->pack(polyline => $_, %path_args),
} map $_->medial_axis($flow->scaled_width), @this_width;
Slic3r::debugf " %d gaps filled with extrusion width = %s\n", scalar @this_width, $width
if @{ $self->thin_fills };
} else {
# fill gaps using zigzag infill
# since this is infill, we have to offset by half-extrusion width inwards
my @infill = map $_->offset_ex(-0.5*$flow->scaled_width), @this_width;
foreach my $expolygon (@infill) {
my @paths = $filler->fill_surface(
Slic3r::Surface->new(expolygon => $expolygon),
density => 1,
flow_spacing => $flow->spacing,
);
my $params = shift @paths;
push @{ $self->thin_fills },
map {
$_->polyline->simplify($flow->scaled_width / 3);
$_->pack;
}
map Slic3r::ExtrusionPath->new(
polyline => Slic3r::Polyline->new(@$_),
role => EXTR_ROLE_GAPFILL,
height => $self->height,
flow_spacing => $params->{flow_spacing},
), @paths;
}
}
# check what's left
@gaps = @{diff_ex(
[ map @$_, @gaps ],
[ map @$_, @this_width ],
)};
}
}
# we offset by half the perimeter spacing (to get to the actual infill boundary)
# and then we offset back and forth by the infill spacing to only consider the
# non-collapsing regions
push @{ $self->fill_surfaces },
map $_->simplify(&Slic3r::SCALED_RESOLUTION),
offset2_ex(
\@last,
-($perimeter_spacing/2 + $infill_spacing),
+$infill_spacing,
);
}
# process one island (original surface) at time
# islands are already sorted with a nearest-neighbor search
foreach my $island (@perimeters) {
# do holes starting from innermost one
my @holes = ();
my %is_external = ();
$self->_fill_gaps(\@gaps);
# TODO: can these be removed?
@contours = grep $_->is_printable($self->perimeter_flow->scaled_width), @contours;
@holes = grep $_->is_printable($self->perimeter_flow->scaled_width), @holes;
# find nesting hierarchies separately for contours and holes
my $contours_pt = union_pt(\@contours, PFT_EVENODD);
my $holes_pt = union_pt(\@holes, PFT_EVENODD);
# get lower layer slices for overhang check
my @lower_slices = $self->id == 0
? ()
: map @$_, @{$self->layer->object->layers->[$self->id-1]->slices};
# prepare a coderef for traversing the PolyTree object
# external contours are root items of $contours_pt
# internal contours are the ones next to external
my $traverse;
$traverse = sub {
my ($polynodes, $depth, $is_contour) = @_;
# each item of @$island contains the expolygons having the same depth;
# for each depth we build an arrayref containing all the holes
my @hole_depths = map [ map $_->holes, @$_ ], @$island;
# use a nearest neighbor search to order these children
# TODO: supply second argument to chained_path_items() too?
my @nodes = @{Slic3r::Geometry::chained_path_items(
[ map [ ($_->{outer} ? $_->{outer}[0] : $_->{hole}[0]), $_ ], @$polynodes ],
)};
# organize the outermost hole loops using a nearest-neighbor search
@{$hole_depths[0]} = @{chained_path_items([
map [ $_->[0], $_ ], @{$hole_depths[0]},
])};
# loop while we have spare holes
CYCLE: while (map @$_, @hole_depths) {
# remove first depth container if it contains no holes anymore
shift @hole_depths while !@{$hole_depths[0]};
my @loops = ();
foreach my $polynode (@nodes) {
push @loops, $traverse->($polynode->{children}, $depth+1, $is_contour);
# take first available hole
push @holes, shift @{$hole_depths[0]};
$is_external{$#holes} = 1;
my $polygon = Slic3r::Polygon->new($polynode->{outer} // [ reverse @{$polynode->{hole}} ]);
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]};
my $role = EXTR_ROLE_PERIMETER;
if ($is_contour ? $depth == 0 : !@{ $polynode->{children} }) {
# external perimeters are root level in case of contours
# and items with no children in case of holes
$role = EXTR_ROLE_EXTERNAL_PERIMETER;
} elsif ($depth == 1 && $is_contour) {
$role = EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER;
}
if ($self->id > 0) {
my $is_overhang = $is_contour
? @{diff([$polygon], [ @lower_slices, offset([$polygon], -$self->perimeter_flow->scaled_width) ])}
: !@{intersection([$polygon], \@lower_slices)};
$role = EXTR_ROLE_OVERHANG_PERIMETER if $is_overhang;
}
push @loops, Slic3r::ExtrusionLoop->pack(
polygon => $polygon,
role => $role,
flow_spacing => $self->perimeter_flow->spacing,
);
}
# first do holes
$self->_add_perimeter($holes[$_], $is_external{$_} ? EXTR_ROLE_EXTERNAL_PERIMETER : undef)
for reverse 0 .. $#holes;
# then do contours according to the user settings
my @contour_order = 0 .. $#$island;
@contour_order = reverse @contour_order if !$Slic3r::Config->external_perimeters_first;
for my $depth (@contour_order) {
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]};
}
}
return @loops;
};
# order loops from inner to outer (in terms of object slices)
my @loops = (
(reverse $traverse->($holes_pt, 0)),
$traverse->($contours_pt, 0, 1),
);
# if brim will be printed, reverse the order of perimeters so that
# we continue inwards after having finished the brim
if ($self->layer->id == 0 && $Slic3r::Config->brim_width > 0) {
@{$self->perimeters} = reverse @{$self->perimeters};
}
# TODO: add test for perimeter order
@loops = reverse @loops
if $Slic3r::Config->external_perimeters_first
|| ($self->layer->id == 0 && $Slic3r::Config->brim_width > 0);
# append perimeters
push @{ $self->perimeters }, @loops;
# add thin walls as perimeters
push @{ $self->perimeters }, Slic3r::ExtrusionPath::Collection->new(paths => [
@ -438,16 +311,90 @@ sub make_perimeters {
])->chained_path;
}
sub _add_perimeter {
sub _fill_gaps {
my $self = shift;
my ($polygon, $role) = @_;
my ($gaps) = @_;
return unless $polygon->is_printable($self->perimeter_flow->scaled_width);
push @{ $self->perimeters }, Slic3r::ExtrusionLoop->pack(
polygon => $polygon,
role => ($role // EXTR_ROLE_PERIMETER),
flow_spacing => $self->perimeter_flow->spacing,
);
return unless $Slic3r::Config->gap_fill_speed > 0 && $Slic3r::Config->fill_density > 0 && @$gaps;
my $filler = $self->layer->object->fill_maker->filler('rectilinear');
$filler->layer_id($self->layer->id);
# we should probably use this code to handle thin walls and remove that logic from
# make_surfaces(), but we need to enable dynamic extrusion width before as we can't
# use zigzag for thin walls.
# in the mean time we subtract thin walls from the detected gaps so that we don't
# reprocess them, causing overlapping thin walls and zigzag.
@$gaps = @{diff_ex(
[ map @$_, @$gaps ],
[ map $_->grow($self->perimeter_flow->scaled_width), @{$self->{thin_walls}} ],
1,
)};
# medial axis-based gap fill should benefit from detection of larger gaps too, so
# we could try with 1.5*$w for example, but that doesn't work well for zigzag fill
# because it tends to create very sparse points along the gap when the infill direction
# is not parallel to the gap (1.5*$w thus may only work well with a straight line)
my $w = $self->perimeter_flow->width;
my @widths = ($w, 0.4 * $w); # worth trying 0.2 too?
foreach my $width (@widths) {
my $flow = $self->perimeter_flow->clone(width => $width);
# extract the gaps having this width
my @this_width = map $_->offset_ex(+0.5*$flow->scaled_width),
map $_->noncollapsing_offset_ex(-0.5*$flow->scaled_width),
@$gaps;
if (0) { # remember to re-enable t/dynamic.t
# fill gaps using dynamic extrusion width, by treating them like thin polygons,
# thus generating the skeleton and using it to fill them
my %path_args = (
role => EXTR_ROLE_SOLIDFILL,
flow_spacing => $flow->spacing,
);
push @{ $self->thin_fills }, map {
$_->isa('Slic3r::Polygon')
? (map $_->pack, Slic3r::ExtrusionLoop->new(polygon => $_, %path_args)->split_at_first_point) # we should keep these as loops
: Slic3r::ExtrusionPath->pack(polyline => $_, %path_args),
} map $_->medial_axis($flow->scaled_width), @this_width;
Slic3r::debugf " %d gaps filled with extrusion width = %s\n", scalar @this_width, $width
if @{ $self->thin_fills };
} else {
# fill gaps using zigzag infill
# since this is infill, we have to offset by half-extrusion width inwards
my @infill = map $_->offset_ex(-0.5*$flow->scaled_width), @this_width;
foreach my $expolygon (@infill) {
my @paths = $filler->fill_surface(
Slic3r::Surface->new(expolygon => $expolygon),
density => 1,
flow_spacing => $flow->spacing,
);
my $params = shift @paths;
push @{ $self->thin_fills },
map {
$_->simplify($flow->scaled_width/3);
$_->pack;
}
map Slic3r::ExtrusionPath->new(
polyline => Slic3r::Polyline->new(@$_),
role => EXTR_ROLE_GAPFILL,
height => $self->height,
flow_spacing => $params->{flow_spacing},
), @paths;
}
}
# check what's left
@$gaps = @{diff_ex(
[ map @$_, @$gaps ],
[ map @$_, @this_width ],
)};
}
}
sub prepare_fill_surfaces {
@ -462,7 +409,7 @@ sub prepare_fill_surfaces {
}
# turn too small internal regions into solid regions according to the user setting
{
if ($Slic3r::Config->fill_density > 0) {
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, @{$self->fill_surfaces};
$_->surface_type(S_TYPE_INTERNALSOLID) for @small;
@ -479,17 +426,23 @@ sub process_external_surfaces {
my @top = grep $_->surface_type == S_TYPE_TOP, @{$self->fill_surfaces};
my @bottom = grep $_->surface_type == S_TYPE_BOTTOM, @{$self->fill_surfaces};
# if we're slicing with no infill, we can't extend external surfaces
# over non-existent infill
my @fill_boundaries = $Slic3r::Config->fill_density > 0
? @{$self->fill_surfaces}
: grep $_->surface_type != S_TYPE_INTERNAL, @{$self->fill_surfaces};
# offset them and intersect the results with the actual fill boundaries
my $margin = scale 3; # TODO: ensure this is greater than the total thickness of the perimeters
@top = @{intersection_ex(
[ Slic3r::Geometry::Clipper::offset([ map $_->p, @top ], +$margin) ],
[ map $_->p, @{$self->fill_surfaces} ],
[ map $_->p, @fill_boundaries ],
undef,
1, # to ensure adjacent expolygons are unified
)};
@bottom = @{intersection_ex(
[ Slic3r::Geometry::Clipper::offset([ map $_->p, @bottom ], +$margin) ],
[ map $_->p, @{$self->fill_surfaces} ],
[ map $_->p, @fill_boundaries ],
undef,
1, # to ensure adjacent expolygons are unified
)};
@ -523,79 +476,118 @@ sub process_external_surfaces {
}
# detect bridge direction (skip bottom layer)
if ($self->id > 0) {
my @bottom = grep $_->surface_type == S_TYPE_BOTTOM, @{$self->fill_surfaces}; # surfaces
my @lower = @{$self->layer->object->layers->[ $self->id - 1 ]->slices}; # expolygons
foreach my $surface (@bottom) {
# detect what edges lie on lower slices
my @edges = (); # polylines
foreach my $lower (@lower) {
# turn bridge contour and holes into polylines and then clip them
# with each lower slice's contour
my @clipped = map $_->split_at_first_point->clip_with_polygon($lower->contour), @{$surface->expolygon};
if (@clipped == 2) {
# If the split_at_first_point() call above happens to split the polygon inside the clipping area
# we would get two consecutive polylines instead of a single one, so we use this ugly hack to
# recombine them back into a single one in order to trigger the @edges == 2 logic below.
# This needs to be replaced with something way better.
if (points_coincide($clipped[0][0], $clipped[-1][-1])) {
@clipped = (Slic3r::Polyline->new(@{$clipped[-1]}, @{$clipped[0]}));
}
if (points_coincide($clipped[-1][0], $clipped[0][-1])) {
@clipped = (Slic3r::Polyline->new(@{$clipped[0]}, @{$clipped[1]}));
}
$self->_detect_bridges if $self->id > 0;
}
sub _detect_bridges {
my $self = shift;
my @bottom = grep $_->surface_type == S_TYPE_BOTTOM, @{$self->fill_surfaces}; # surfaces
my @lower = @{$self->layer->object->layers->[ $self->id - 1 ]->slices}; # expolygons
foreach my $surface (@bottom) {
# detect what edges lie on lower slices
my @edges = (); # polylines
foreach my $lower (@lower) {
# turn bridge contour and holes into polylines and then clip them
# with each lower slice's contour
my @clipped = map $_->split_at_first_point->clip_with_polygon($lower->contour), @{$surface->expolygon};
if (@clipped == 2) {
# If the split_at_first_point() call above happens to split the polygon inside the clipping area
# we would get two consecutive polylines instead of a single one, so we use this ugly hack to
# recombine them back into a single one in order to trigger the @edges == 2 logic below.
# This needs to be replaced with something way better.
if (points_coincide($clipped[0][0], $clipped[-1][-1])) {
@clipped = (Slic3r::Polyline->new(@{$clipped[-1]}, @{$clipped[0]}));
}
push @edges, @clipped;
}
Slic3r::debugf "Found bridge on layer %d with %d support(s)\n", $self->id, scalar(@edges);
next if !@edges;
my $bridge_angle = undef;
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output("bridge.svg",
polygons => [ $surface->p ],
red_polygons => [ map @$_, @lower ],
polylines => [ @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);
if (points_coincide($clipped[-1][0], $clipped[0][-1])) {
@clipped = (Slic3r::Polyline->new(@{$clipped[0]}, @{$clipped[1]}));
}
} 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;
$surface->bridge_angle($bridge_angle);
push @edges, @clipped;
}
Slic3r::debugf "Found bridge on layer %d with %d support(s)\n", $self->id, scalar(@edges);
next if !@edges;
my $bridge_angle = undef;
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output("bridge_$surface.svg",
expolygons => [ $surface->expolygon ],
red_expolygons => [ @lower ],
polylines => [ @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) {
# inset the bridge expolygon; we'll use this one to clip our test lines
my $inset = [ $surface->expolygon->offset_ex($self->infill_flow->scaled_width) ];
# detect anchors as intersection between our bridge expolygon and the lower slices
my $anchors = intersection_ex(
[ $surface->p ],
[ map @$_, @lower ],
);
# we'll now try several directions using a rudimentary visibility check:
# bridge in several directions and then sum the length of lines having both
# endpoints within anchors
my %directions = (); # angle => score
my $angle_increment = PI/36; # 5°
my $line_increment = $self->infill_flow->scaled_width;
for (my $angle = 0; $angle <= PI; $angle += $angle_increment) {
# rotate everything - the center point doesn't matter
$_->rotate($angle, [0,0]) for @$inset, @$anchors;
# generate lines in this direction
my $bounding_box = [ Slic3r::Geometry::bounding_box([ map @$_, map @$_, @$anchors ]) ];
my @lines = ();
for (my $x = $bounding_box->[X1]; $x <= $bounding_box->[X2]; $x += $line_increment) {
push @lines, [ [$x, $bounding_box->[Y1]], [$x, $bounding_box->[Y2]] ];
}
# TODO: use a multi_polygon_multi_linestring_intersection() call
my @clipped_lines = map @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection($_, \@lines) }, @$inset;
# remove any line not having both endpoints within anchors
@clipped_lines = grep {
my $line = $_;
!(first { $_->encloses_point_quick($line->[A]) } @$anchors)
&& !(first { $_->encloses_point_quick($line->[B]) } @$anchors);
} @clipped_lines;
# sum length of bridged lines
$directions{-$angle} = sum(map Slic3r::Geometry::line_length($_), @clipped_lines) // 0;
}
# this could be slightly optimized with a max search instead of the sort
my @sorted_directions = sort { $directions{$a} <=> $directions{$b} } keys %directions;
# the best direction is the one causing most lines to be bridged
$bridge_angle = Slic3r::Geometry::rad2deg_dir($sorted_directions[-1]);
}
Slic3r::debugf " Optimal infill angle of bridge on layer %d is %d degrees\n",
$self->id, $bridge_angle if defined $bridge_angle;
$surface->bridge_angle($bridge_angle);
}
}

View File

@ -2,7 +2,9 @@ package Slic3r::Line;
use strict;
use warnings;
use Boost::Geometry::Utils;
# a line is a two-points line
use parent 'Slic3r::Polyline';
use Slic3r::Geometry qw(A B X Y);
sub new {
@ -14,11 +16,6 @@ sub new {
return $self;
}
sub boost_linestring {
my $self = shift;
return Boost::Geometry::Utils::linestring($self);
}
sub coincides_with {
my $self = shift;
my ($line) = @_;
@ -27,11 +24,6 @@ sub coincides_with {
|| ($self->a->coincides_with($line->b) && $self->b->coincides_with($line->a));
}
sub length {
my $self = shift;
return Slic3r::Geometry::line_length($self);
}
sub vector {
my $self = shift;
return (ref $self)->new([0,0], [map $self->[B][$_] - $self->[A][$_], X,Y]);
@ -67,17 +59,4 @@ sub midpoint {
);
}
sub reverse {
my $self = shift;
@$self = reverse @$self;
}
sub translate {
my $self = shift;
my ($x, $y) = @_;
@$self = Slic3r::Geometry::move_points([$x, $y], @$self);
bless $_, 'Slic3r::Point' for @$self;
return $self;
}
1;

View File

@ -1,7 +1,8 @@
package Slic3r::Model;
use Moo;
use Slic3r::Geometry qw(X Y Z);
use List::Util qw(first max);
use Slic3r::Geometry qw(X Y Z MIN move_points);
has 'materials' => (is => 'ro', default => sub { {} });
has 'objects' => (is => 'ro', default => sub { [] });
@ -19,6 +20,38 @@ sub read_from_file {
return $model;
}
sub merge {
my $class = shift;
my @models = @_;
my $new_model = $class->new;
foreach my $model (@models) {
# merge material attributes (should we rename them in case of duplicates?)
$new_model->set_material($_, { %{$model->materials->{$_}}, %{$model->materials->{$_} || {}} })
for keys %{$model->materials};
foreach my $object (@{$model->objects}) {
my $new_object = $new_model->add_object(
input_file => $object->input_file,
vertices => $object->vertices,
layer_height_ranges => $object->layer_height_ranges,
);
$new_object->add_volume(
material_id => $_->material_id,
facets => $_->facets,
) for @{$object->volumes};
$new_object->add_instance(
offset => $_->offset,
rotation => $_->rotation,
) for @{ $object->instances // [] };
}
}
return $new_model;
}
sub add_object {
my $self = shift;
@ -39,10 +72,137 @@ sub set_material {
sub scale {
my $self = shift;
$_->scale(@_) for @{$self->objects};
}
sub arrange_objects {
my $self = shift;
my ($config) = @_;
# do we have objects with no position?
if (first { !defined $_->instances } @{$self->objects}) {
# we shall redefine positions for all objects
my ($copies, @positions) = $self->_arrange(
config => $config,
items => $self->objects,
);
# apply positions to objects
foreach my $object (@{$self->objects}) {
$object->align_to_origin;
$object->instances([]);
$object->add_instance(
offset => $_,
rotation => 0,
) for splice @positions, 0, $copies;
}
} else {
# we only have objects with defined position
# align the whole model to origin as it is
$self->align_to_origin;
# arrange this model as a whole
my ($copies, @positions) = $self->_arrange(
config => $config,
items => [$self],
);
# apply positions to objects by translating the current positions
foreach my $object (@{$self->objects}) {
my @old_instances = @{$object->instances};
$object->instances([]);
foreach my $instance (@old_instances) {
$object->add_instance(
offset => $_,
rotation => $instance->rotation,
scaling_factor => $instance->scaling_factor,
) for move_points($instance->offset, @positions);
}
}
}
}
sub _arrange {
my $self = shift;
my %params = @_;
my $config = $params{config};
my @items = @{$params{items}}; # can be Model or Object objects, they have to implement size()
if ($config->duplicate_grid->[X] > 1 || $config->duplicate_grid->[Y] > 1) {
if (@items > 1) {
die "Grid duplication is not supported with multiple objects\n";
}
my @positions = ();
my $size = $items[0]->size;
my $dist = $config->duplicate_distance;
for my $x_copy (1..$config->duplicate_grid->[X]) {
for my $y_copy (1..$config->duplicate_grid->[Y]) {
push @positions, [
($size->[X] + $dist) * ($x_copy-1),
($size->[Y] + $dist) * ($y_copy-1),
];
}
}
return ($config->duplicate_grid->[X] * $config->duplicate_grid->[Y]), @positions;
} else {
my $total_parts = $config->duplicate * @items;
my $partx = max(map $_->size->[X], @items);
my $party = max(map $_->size->[Y], @items);
return $config->duplicate,
Slic3r::Geometry::arrange
($total_parts, $partx, $party, (map $_, @{$config->bed_size}),
$config->min_object_distance, $config);
}
}
sub vertices {
my $self = shift;
return [ map @{$_->vertices}, @{$self->objects} ];
}
sub used_vertices {
my $self = shift;
return [ map @{$_->used_vertices}, @{$self->objects} ];
}
sub size {
my $self = shift;
return [ Slic3r::Geometry::size_3D($self->used_vertices) ];
}
sub extents {
my $self = shift;
return Slic3r::Geometry::bounding_box_3D($self->used_vertices);
}
sub align_to_origin {
my $self = shift;
# calculate the displacements needed to
# have lowest value for each axis at coordinate 0
{
my @extents = $self->extents;
$self->move(map -$extents[$_][MIN], X,Y,Z);
}
# align all instances to 0,0 as well
{
my @instances = map @{$_->instances}, @{$self->objects};
my @extents = Slic3r::Geometry::bounding_box_3D([ map $_->offset, @instances ]);
$_->offset->translate(-$extents[X][MIN], -$extents[Y][MIN]) for @instances;
}
}
sub move {
my $self = shift;
$_->move(@_) for @{$self->objects};
}
# flattens everything to a single mesh
sub mesh {
my $self = shift;
@ -54,6 +214,7 @@ sub mesh {
my $mesh = $object->mesh->clone;
if ($instance) {
$mesh->rotate($instance->rotation);
$mesh->scale($instance->scaling_factor);
$mesh->align_to_origin;
$mesh->move(@{$instance->offset});
}
@ -64,6 +225,48 @@ sub mesh {
return Slic3r::TriangleMesh->merge(@meshes);
}
# this method splits objects into multiple distinct objects by walking their meshes
sub split_meshes {
my $self = shift;
my @objects = @{$self->objects};
@{$self->objects} = ();
foreach my $object (@objects) {
if (@{$object->volumes} > 1) {
# We can't split meshes if there's more than one material, because
# we can't group the resulting meshes by object afterwards
push @{$self->objects}, $object;
next;
}
my $volume = $object->volumes->[0];
foreach my $mesh ($volume->mesh->split_mesh) {
my $new_object = $self->add_object(
input_file => $object->input_file,
layer_height_ranges => $object->layer_height_ranges,
);
$new_object->add_volume(
vertices => $mesh->vertices,
facets => $mesh->facets,
material_id => $volume->material_id,
);
# let's now align the new object to the origin and put its displacement
# (extents) in the instances info
my @extents = $mesh->extents;
$new_object->align_to_origin;
# add one instance per original instance applying the displacement
$new_object->add_instance(
offset => [ $_->offset->[X] + $extents[X][MIN], $_->offset->[Y] + $extents[Y][MIN] ],
rotation => $_->rotation,
scaling_factor => $_->scaling_factor,
) for @{ $object->instances // [] };
}
}
}
package Slic3r::Model::Region;
use Moo;
@ -74,7 +277,8 @@ package Slic3r::Model::Object;
use Moo;
use List::Util qw(first);
use Slic3r::Geometry qw(X Y Z);
use Slic3r::Geometry qw(X Y Z MIN MAX move_points move_points_3D);
use Storable qw(dclone);
has 'input_file' => (is => 'rw');
has 'model' => (is => 'ro', weak_ref => 1, required => 1);
@ -122,6 +326,49 @@ sub mesh {
);
}
sub used_vertices {
my $self = shift;
return [ map $self->vertices->[$_], map @$_, map @{$_->facets}, @{$self->volumes} ];
}
sub size {
my $self = shift;
return [ Slic3r::Geometry::size_3D($self->used_vertices) ];
}
sub extents {
my $self = shift;
return Slic3r::Geometry::bounding_box_3D($self->used_vertices);
}
sub center {
my $self = shift;
my @extents = $self->extents;
return [ map +($extents[$_][MAX] + $extents[$_][MIN])/2, X,Y,Z ];
}
sub bounding_box {
my $self = shift;
return Slic3r::Geometry::BoundingBox->new(extents => [ $self->extents ]);
}
sub align_to_origin {
my $self = shift;
# calculate the displacements needed to
# have lowest value for each axis at coordinate 0
my @extents = $self->extents;
my @shift = map -$extents[$_][MIN], X,Y,Z;
$self->move(@shift);
return @shift;
}
sub move {
my $self = shift;
@{$self->vertices} = move_points_3D([ @_ ], @{$self->vertices});
}
sub scale {
my $self = shift;
my ($factor) = @_;
@ -133,6 +380,19 @@ sub scale {
}
}
sub rotate {
my $self = shift;
my ($deg) = @_;
return if $deg == 0;
my $rad = Slic3r::Geometry::deg2rad($deg);
# transform vertex coordinates
foreach my $vertex (@{$self->vertices}) {
@$vertex = (@{ +(Slic3r::Geometry::rotate_points($rad, undef, [ $vertex->[X], $vertex->[Y] ]))[0] }, $vertex->[Z]);
}
}
sub materials_count {
my $self = shift;
@ -145,6 +405,8 @@ sub check_manifoldness {
return (first { !$_->mesh->check_manifoldness } @{$self->volumes}) ? 0 : 1;
}
sub clone { dclone($_[0]) }
package Slic3r::Model::Volume;
use Moo;
@ -164,7 +426,8 @@ package Slic3r::Model::Instance;
use Moo;
has 'object' => (is => 'ro', weak_ref => 1, required => 1);
has 'rotation' => (is => 'rw', default => sub { 0 });
has 'offset' => (is => 'rw');
has 'rotation' => (is => 'rw', default => sub { 0 }); # around mesh center point
has 'scaling_factor' => (is => 'rw', default => sub { 1 });
has 'offset' => (is => 'rw'); # must be Slic3r::Point object
1;

View File

@ -35,6 +35,13 @@ sub distance_to {
return Slic3r::Geometry::distance_between_points($self, $point);
}
sub scale {
my $self = shift;
my ($factor) = @_;
$_ *= $factor for @$self;
$self;
}
sub rotate {
my $self = shift;
my ($angle, $center) = @_;

View File

@ -27,16 +27,6 @@ sub lines {
return polygon_lines($self);
}
sub boost_polygon {
my $self = shift;
return Boost::Geometry::Utils::polygon($self);
}
sub boost_linestring {
my $self = shift;
return Boost::Geometry::Utils::linestring([@$self, $self->[0]]);
}
sub wkt {
my $self = shift;
return sprintf "POLYGON((%s))", join ',', map "$_->[0] $_->[1]", @$self;
@ -95,16 +85,6 @@ sub area {
return Slic3r::Geometry::Clipper::area($self);
}
sub safety_offset {
my $self = shift;
return (ref $self)->new(Slic3r::Geometry::Clipper::safety_offset([$self])->[0]);
}
sub offset {
my $self = shift;
return map Slic3r::Polygon->new($_), Slic3r::Geometry::Clipper::offset([$self], @_);
}
sub grow {
my $self = shift;
return $self->split_at_first_point->grow(@_);
@ -138,7 +118,7 @@ sub subdivide {
}
}
# returns false if the polyline is too tight to be printed
# returns false if the polygon is too tight to be printed
sub is_printable {
my $self = shift;
my ($width) = @_;
@ -152,7 +132,7 @@ sub is_printable {
# detect them and we would be discarding them.
my $p = $self->clone;
$p->make_counter_clockwise;
return $p->offset(-$width / 2) ? 1 : 0;
return Slic3r::Geometry::Clipper::offset([$p], -$width / 2) ? 1 : 0;
}
sub is_valid {

View File

@ -5,6 +5,7 @@ use warnings;
use Scalar::Util qw(reftype);
use Slic3r::Geometry qw(A B X Y X1 X2 Y1 Y2 polyline_remove_parallel_continuous_edges polyline_remove_acute_vertices
polyline_lines move_points same_point);
use Slic3r::Geometry::Clipper qw(JT_SQUARE);
# the constructor accepts an array(ref) of points
sub new {
@ -44,11 +45,6 @@ sub lines {
return polyline_lines($self);
}
sub boost_linestring {
my $self = shift;
return Boost::Geometry::Utils::linestring($self);
}
sub wkt {
my $self = shift;
return sprintf "LINESTRING((%s))", join ',', map "$_->[0] $_->[1]", @$self;
@ -79,14 +75,19 @@ sub reverse {
sub length {
my $self = shift;
my $length = 0;
$length += $_->length for $self->lines;
return $length;
return Boost::Geometry::Utils::linestring_length($self);
}
sub grow {
my $self = shift;
return Slic3r::Polygon->new(@$self, CORE::reverse @$self[1..($#$self-1)])->offset(@_);
my ($distance, $scale, $joinType, $miterLimit) = @_;
$joinType //= JT_SQUARE;
return map Slic3r::Polygon->new($_),
Slic3r::Geometry::Clipper::offset(
[ Slic3r::Polygon->new(@$self, CORE::reverse @$self[1..($#$self-1)]) ],
$distance, $scale, $joinType, $miterLimit,
);
}
sub nearest_point_to {
@ -155,15 +156,17 @@ sub translate {
sub scale {
my $self = shift;
my ($factor) = @_;
return if $factor == 1;
# transform point coordinates
foreach my $point (@$self) {
$point->[$_] *= $factor for X,Y;
if ($factor != 1) {
foreach my $point (@$self) {
$point->[$_] *= $factor for X,Y;
}
}
return $self;
}
# removes the given distance from the end of the polyline
sub clip_end {
my $self = shift;
my ($distance) = @_;
@ -184,6 +187,30 @@ sub clip_end {
}
}
# only keeps the given distance at the beginning of the polyline
sub clip_start {
my $self = shift;
my ($distance) = @_;
my $points = [ $self->[0] ];
for (my $i = 1; $distance > 0 && $i <= $#$self; $i++) {
my $point = $self->[$i];
my $segment_length = $point->distance_to($self->[$i-1]);
if ($segment_length <= $distance) {
$distance -= $segment_length;
push @$points, $point;
next;
}
my $new_point = Slic3r::Geometry::point_along_segment($self->[$i-1], $point, $distance);
push @$points, Slic3r::Point->new($new_point);
$distance = 0;
}
return (ref $self)->new($points);
}
package Slic3r::Polyline::Collection;
use Moo;

View File

@ -6,8 +6,10 @@ use File::Spec;
use List::Util qw(max first);
use Math::ConvexHull::MonotoneChain qw(convex_hull);
use Slic3r::ExtrusionPath ':roles';
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 Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX PI scale unscale move_points
nearest_point chained_path);
use Slic3r::Geometry::Clipper qw(diff_ex union_ex union_pt intersection_ex offset
offset2 traverse_pt JT_ROUND JT_SQUARE PFT_EVENODD);
use Time::HiRes qw(gettimeofday tv_interval);
has 'config' => (is => 'rw', default => sub { Slic3r::Config->new_from_defaults }, trigger => 1);
@ -61,6 +63,16 @@ sub _trigger_config {
# 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';
# enforce some settings when spiral_vase is set
if ($self->config->spiral_vase) {
$self->config->set('perimeters', 1);
$self->config->set('fill_density', 0);
$self->config->set('top_solid_layers', 0);
$self->config->set('support_material', 0);
$self->config->set('support_material_enforce_layers', 0);
$self->config->set('retract_layer_change', [0]); # TODO: only apply this to the spiral layers
}
}
sub _build_has_support_material {
@ -70,6 +82,8 @@ sub _build_has_support_material {
|| $self->config->support_material_enforce_layers > 0;
}
# caller is responsible for supplying models whose objects don't collide
# and have explicit instance positions
sub add_model {
my $self = shift;
my ($model) = @_;
@ -86,56 +100,61 @@ sub add_model {
}
}
# optimization: if avoid_crossing_perimeters is enabled, split
# this mesh into distinct objects so that we reduce the complexity
# of the graphs
$model->split_meshes if $Slic3r::Config->avoid_crossing_perimeters && !$Slic3r::Config->complete_objects;
foreach my $object (@{ $model->objects }) {
my @meshes = (); # by region_id
# we align object to origin before applying transformations
my @align = $object->align_to_origin;
# extract meshes by material
my @meshes = (); # by region_id
foreach my $volume (@{$object->volumes}) {
# should the object contain multiple volumes of the same material, merge them
my $region_id = defined $volume->material_id ? $materials{$volume->material_id} : 0;
my $mesh = $volume->mesh->clone;
# should the object contain multiple volumes of the same material, merge them
$meshes[$region_id] = $meshes[$region_id]
? Slic3r::TriangleMesh->merge($meshes[$region_id], $mesh)
: $mesh;
}
foreach my $mesh (@meshes) {
next unless $mesh;
foreach my $mesh (grep $_, @meshes) {
$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);
# the order of these transformations must be the same as the one used in plater
# to make the object positioning consistent with the visual preview
# we ignore the per-instance transformations currently and only
# consider the first one
if ($object->instances && @{$object->instances}) {
$mesh->rotate($object->instances->[0]->rotation, $object->center);
$mesh->scale($object->instances->[0]->scaling_factor);
}
$mesh->rotate($Slic3r::Config->rotate);
$mesh->scale($Slic3r::Config->scale / &Slic3r::SCALING_FACTOR);
$mesh->scale(1 / &Slic3r::SCALING_FACTOR);
}
my $complete_mesh = Slic3r::TriangleMesh->merge(grep defined $_, @meshes);
# we also align object after transformations so that we only work with positive coordinates
# and the assumption that bounding_box === size works
my $bb = Slic3r::Geometry::BoundingBox->new_from_points_3D([ map @{$_->used_vertices}, grep $_, @meshes ]);
my @align2 = map -$bb->extents->[$_][MIN], (X,Y,Z);
$_->move(@align2) for grep $_, @meshes;
# initialize print object
my $print_object = Slic3r::Print::Object->new(
push @{$self->objects}, Slic3r::Print::Object->new(
print => $self,
meshes => [ @meshes ],
size => [ $complete_mesh->size ],
copies => [
$object->instances
? (map [ scale($_->offset->[X] - $align[X]) - $align2[X], scale($_->offset->[Y] - $align[Y]) - $align2[Y] ], @{$object->instances})
: [0,0],
],
size => $bb->size, # transformed size
input_file => $object->input_file,
layer_height_ranges => $object->layer_height_ranges,
);
push @{$self->objects}, $print_object;
# align object to origin
{
my @extents = $complete_mesh->extents;
foreach my $mesh (grep defined $_, @meshes) {
$mesh->move(map -$extents[$_][MIN], X,Y,Z);
}
}
if ($object->instances) {
# replace the default [0,0] instance with the custom ones
$print_object->copies([ map [ scale $_->offset->[X], scale $_->offset->[Y] ], @{$object->instances} ]);
}
}
}
@ -151,7 +170,9 @@ sub validate {
{
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];
($clearance) = map Slic3r::Polygon->new($_),
Slic3r::Geometry::Clipper::offset(
[$convex_hull], scale $Slic3r::Config->extruder_clearance_radius / 2, 1, JT_ROUND);
}
for my $copy (@{$self->objects->[$obj_idx]->copies}) {
my $copy_clearance = $clearance->clone;
@ -174,6 +195,15 @@ sub validate {
}
}
}
if ($Slic3r::Config->spiral_vase) {
if ((map @{$_->copies}, @{$self->objects}) > 1) {
die "The Spiral Vase option can only be used when printing a single object.\n";
}
if (@{$self->regions} > 1) {
die "The Spiral Vase option can only be used when printing single material objects.\n";
}
}
}
sub init_extruders {
@ -253,54 +283,12 @@ sub regions_count {
return scalar @{$self->regions};
}
sub duplicate {
my $self = shift;
if ($Slic3r::Config->duplicate_grid->[X] > 1 || $Slic3r::Config->duplicate_grid->[Y] > 1) {
if (@{$self->objects} > 1) {
die "Grid duplication is not supported with multiple objects\n";
}
my $object = $self->objects->[0];
# generate offsets for copies
my $dist = scale $Slic3r::Config->duplicate_distance;
@{$self->objects->[0]->copies} = ();
for my $x_copy (1..$Slic3r::Config->duplicate_grid->[X]) {
for my $y_copy (1..$Slic3r::Config->duplicate_grid->[Y]) {
push @{$self->objects->[0]->copies}, [
($object->size->[X] + $dist) * ($x_copy-1),
($object->size->[Y] + $dist) * ($y_copy-1),
];
}
}
} elsif ($Slic3r::Config->duplicate > 1) {
foreach my $object (@{$self->objects}) {
@{$object->copies} = map [0,0], 1..$Slic3r::Config->duplicate;
}
$self->arrange_objects;
}
}
sub arrange_objects {
my $self = shift;
my $total_parts = scalar map @{$_->copies}, @{$self->objects};
my $partx = max(map $_->size->[X], @{$self->objects});
my $party = max(map $_->size->[Y], @{$self->objects});
my @positions = Slic3r::Geometry::arrange
($total_parts, $partx, $party, (map scale $_, @{$Slic3r::Config->bed_size}), scale $Slic3r::Config->min_object_distance, $self->config);
@{$_->copies} = splice @positions, 0, scalar @{$_->copies} for @{$self->objects};
}
sub bounding_box {
my $self = shift;
my @points = ();
foreach my $obj_idx (0 .. $#{$self->objects}) {
my $object = $self->objects->[$obj_idx];
foreach my $copy (@{$self->objects->[$obj_idx]->copies}) {
foreach my $object (@{$self->objects}) {
foreach my $copy (@{$object->copies}) {
push @points,
[ $copy->[X], $copy->[Y] ],
[ $copy->[X] + $object->size->[X], $copy->[Y] ],
@ -341,6 +329,13 @@ sub export_gcode {
$status_cb->(10, "Processing triangulated mesh");
$_->slice for @{$self->objects};
# remove empty layers and abort if there are no more
# as some algorithms assume all objects have at least one layer
# note: this will change object indexes
@{$self->objects} = grep @{$_->layers}, @{$self->objects};
die "No layers were detected. You might want to repair your STL file(s) or check their size and retry.\n"
if !@{$self->objects};
if ($Slic3r::Config->resolution) {
$status_cb->(15, "Simplifying input");
$self->_simplify_slices(scale $Slic3r::Config->resolution);
@ -387,7 +382,6 @@ sub export_gcode {
# this will generate extrusion paths for each layer
$status_cb->(80, "Infilling layers");
{
my $fill_maker = Slic3r::Fill->new('print' => $self);
Slic3r::parallelize(
items => sub {
my @items = (); # [obj_idx, layer_id]
@ -404,10 +398,11 @@ sub export_gcode {
my $fills = {};
while (defined (my $obj_layer = $q->dequeue)) {
my ($obj_idx, $layer_id, $region_id) = @$obj_layer;
my $object = $self->objects->[$obj_idx];
$fills->{$obj_idx} ||= {};
$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]),
$object->fill_maker->make_fill($object->layers->[$layer_id]->regions->[$region_id]),
];
}
return $fills;
@ -426,7 +421,7 @@ sub export_gcode {
},
no_threads_cb => sub {
foreach my $layerm (map @{$_->regions}, map @{$_->layers}, @{$self->objects}) {
$layerm->fills([ $fill_maker->make_fill($layerm) ]);
$layerm->fills([ $layerm->layer->object->fill_maker->make_fill($layerm) ]);
}
},
);
@ -457,6 +452,10 @@ sub export_gcode {
printf " fills = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->fills), map @{$_->regions}, map @{$_->layers}, @{$self->objects})/1024/1024;
printf " print object = %.1fMb\n", Devel::Size::total_size($self)/1024/1024;
}
if (0) {
eval "use Slic3r::Test::SectionCut";
Slic3r::Test::SectionCut->new(print => $self)->export_svg("section_cut.svg");
}
# output everything to a G-code file
my $output_file = $self->expanded_output_filepath($params{output_file});
@ -495,13 +494,15 @@ sub export_svg {
$self->init_extruders;
$_->slice for @{$self->objects};
$self->arrange_objects;
my $output_file = $self->expanded_output_filepath($params{output_file});
$output_file =~ s/\.gcode$/.svg/i;
my $fh = $params{output_fh};
if ($params{output_file}) {
my $output_file = $self->expanded_output_filepath($params{output_file});
$output_file =~ s/\.gcode$/.svg/i;
Slic3r::open(\$fh, ">", $output_file) or die "Failed to open $output_file for writing\n";
print "Exporting to $output_file..." unless $params{quiet};
}
Slic3r::open(\my $fh, ">", $output_file) or die "Failed to open $output_file for writing\n";
print "Exporting to $output_file...";
my $print_size = $self->size;
print $fh sprintf <<"EOF", unscale($print_size->[X]), unscale($print_size->[Y]);
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
@ -570,7 +571,7 @@ EOF
print $fh "</svg>\n";
close $fh;
print "Done.\n";
print "Done.\n" unless $params{quiet};
}
sub make_skirt {
@ -660,16 +661,20 @@ sub make_brim {
push @islands, map $_->unpack->split_at_first_point->polyline->grow($grow_distance), @{$self->skirt};
}
my @loops = ();
my $num_loops = sprintf "%.0f", $Slic3r::Config->brim_width / $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,
flow_spacing => $flow->spacing,
) for Slic3r::Geometry::Clipper::offset(\@islands, ($i - 0.5) * $flow->scaled_spacing, undef, JT_SQUARE); # -0.5 because islands are not represented by their centerlines
# -0.5 because islands are not represented by their centerlines
# TODO: we need the offset inwards/offset outwards logic to avoid overlapping extrusions
push @loops, offset2(\@islands, ($i - 1.5) * $flow->scaled_spacing, +1.0 * $flow->scaled_spacing, undef, JT_SQUARE);
}
@{$self->brim} = map Slic3r::ExtrusionLoop->pack(
polygon => Slic3r::Polygon->new($_),
role => EXTR_ROLE_SKIRT,
flow_spacing => $flow->spacing,
), reverse traverse_pt( union_pt(\@loops, PFT_EVENODD) );
}
sub write_gcode {
@ -711,12 +716,11 @@ sub write_gcode {
# set up our extruder object
my $gcodegen = Slic3r::GCode->new(
config => $self->config,
multiple_extruders => (@{$self->extruders} > 1),
layer_count => $self->layer_count,
);
my $min_print_speed = 60 * $Slic3r::Config->min_print_speed;
my $dec = $gcodegen->dec;
print $fh "G21 ; set units to millimeters\n";
print $fh "G21 ; set units to millimeters\n" if $Slic3r::Config->gcode_flavor ne 'makerware';
print $fh $gcodegen->set_fan(0, 1) if $Slic3r::Config->cooling && $Slic3r::Config->disable_fan_first_layers;
# write start commands to file
@ -734,15 +738,13 @@ sub write_gcode {
printf $fh $gcodegen->set_temperature($self->extruders->[$t]->first_layer_temperature, 1, $t)
if $self->extruders->[$t]->first_layer_temperature && $Slic3r::Config->start_gcode !~ /M(?:109|104)/i;
}
print $fh "G90 ; use absolute coordinates\n";
print $fh "G90 ; use absolute coordinates\n" if $Slic3r::Config->gcode_flavor ne 'makerware';
if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|teacup)$/) {
printf $fh $gcodegen->reset_e;
if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|makerbot|sailfish)$/) {
if ($Slic3r::Config->use_relative_e_distances) {
print $fh "M83 ; use relative distances for extrusion\n";
} else {
print $fh "M82 ; use absolute distances for extrusion\n";
}
if ($Slic3r::Config->use_relative_e_distances) {
print $fh "M83 ; use relative distances for extrusion\n";
} else {
print $fh "M82 ; use absolute distances for extrusion\n";
}
}
@ -778,212 +780,19 @@ sub write_gcode {
));
}
# prepare the logic to print one layer
my $skirt_done = 0; # count of skirt layers done
my $brim_done = 0;
my $second_layer_things_done = 0;
my $last_obj_copy = "";
my $extrude_layer = sub {
my ($layer, $object_copies) = @_;
my $gcode = "";
if (!$second_layer_things_done && $layer->id == 1) {
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->bed_temperature && $Slic3r::Config->bed_temperature != $Slic3r::Config->first_layer_bed_temperature;
}
# set new layer, but don't move Z as support material contact areas may need an intermediate one
$gcode .= $gcodegen->change_layer($layer);
$gcodegen->elapsed_time(0);
# prepare callback to call as soon as a Z command is generated
$gcodegen->move_z_callback(sub {
$gcodegen->move_z_callback(undef); # circular ref or not?
return "" if !$Slic3r::Config->layer_gcode;
return $Slic3r::Config->replace_options($Slic3r::Config->layer_gcode) . "\n";
});
# extrude skirt
if ($skirt_done < $Slic3r::Config->skirt_height) {
$gcodegen->set_shift(@shift);
$gcode .= $gcodegen->set_extruder($self->extruders->[0]); # move_z requires extruder
$gcode .= $gcodegen->move_z($gcodegen->layer->print_z);
# skip skirt if we have a large brim
if ($layer->id < $Slic3r::Config->skirt_height) {
# distribute skirt loops across all extruders
for my $i (0 .. $#{$self->skirt}) {
# when printing layers > 0 ignore 'min_skirt_length' and
# just use the 'skirts' setting; also just use the current extruder
last if ($layer->id > 0) && ($i >= $Slic3r::Config->skirts);
$gcode .= $gcodegen->set_extruder($self->extruders->[ ($i/@{$self->extruders}) % @{$self->extruders} ])
if $layer->id == 0;
$gcode .= $gcodegen->extrude_loop($self->skirt->[$i], 'skirt');
}
}
$skirt_done++;
$gcodegen->straight_once(1);
}
# extrude brim
if (!$brim_done) {
$gcode .= $gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]); # move_z requires extruder
$gcode .= $gcodegen->move_z($gcodegen->layer->print_z);
$gcodegen->set_shift(@shift);
$gcode .= $gcodegen->extrude_loop($_, 'brim') for @{$self->brim};
$brim_done = 1;
$gcodegen->straight_once(1);
}
for my $copy (@$object_copies) {
$gcodegen->new_object(1) if $last_obj_copy && $last_obj_copy ne "$copy";
$last_obj_copy = "$copy";
$gcodegen->set_shift(map $shift[$_] + unscale $copy->[$_], X,Y);
# extrude support material before other things because it might use a lower Z
# and also because we avoid travelling on other things when printing it
if ($self->has_support_material) {
$gcode .= $gcodegen->move_z($layer->support_material_contact_z)
if ($layer->support_contact_fills && @{ $layer->support_contact_fills->paths });
$gcode .= $gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]);
if ($layer->support_contact_fills) {
$gcode .= $gcodegen->extrude_path($_, 'support material contact area')
for $layer->support_contact_fills->chained_path($gcodegen->last_pos);
}
$gcode .= $gcodegen->move_z($layer->print_z);
if ($layer->support_fills) {
$gcode .= $gcodegen->extrude_path($_, 'support material')
for $layer->support_fills->chained_path($gcodegen->last_pos);
}
}
# set actual Z - this will force a retraction
$gcode .= $gcodegen->move_z($layer->print_z);
# tweak region ordering to save toolchanges
my @region_ids = 0 .. ($self->regions_count-1);
if ($gcodegen->multiple_extruders) {
my $last_extruder = $gcodegen->extruder;
my $best_region_id = first { $self->regions->[$_]->extruders->{perimeter} eq $last_extruder } @region_ids;
@region_ids = ($best_region_id, grep $_ != $best_region_id, @region_ids) if $best_region_id;
}
foreach my $region_id (@region_ids) {
my $layerm = $layer->regions->[$region_id];
my $region = $self->regions->[$region_id];
my @islands = ();
if ($Slic3r::Config->avoid_crossing_perimeters) {
push @islands, map +{ perimeters => [], fills => [] }, @{$layer->slices};
PERIMETER: foreach my $perimeter (@{$layerm->perimeters}) {
my $p = $perimeter->unpack;
for my $i (0 .. $#{$layer->slices}-1) {
if ($layer->slices->[$i]->contour->encloses_point($p->first_point)) {
push @{ $islands[$i]{perimeters} }, $p;
next PERIMETER;
}
}
push @{ $islands[-1]{perimeters} }, $p; # optimization
}
FILL: foreach my $fill (@{$layerm->fills}) {
my $f = $fill->unpack;
for my $i (0 .. $#{$layer->slices}-1) {
if ($layer->slices->[$i]->contour->encloses_point($f->first_point)) {
push @{ $islands[$i]{fills} }, $f;
next FILL;
}
}
push @{ $islands[-1]{fills} }, $f; # optimization
}
} else {
push @islands, {
perimeters => $layerm->perimeters,
fills => $layerm->fills,
};
}
foreach my $island (@islands) {
my $extrude_perimeters = sub {
return if !@{ $island->{perimeters} };
$gcode .= $gcodegen->set_extruder($region->extruders->{perimeter});
$gcode .= $gcodegen->extrude($_, 'perimeter') for @{ $island->{perimeters} };
};
my $extrude_fills = sub {
return if !@{ $island->{fills} };
$gcode .= $gcodegen->set_extruder($region->extruders->{infill});
for my $fill (@{ $island->{fills} }) {
if ($fill->isa('Slic3r::ExtrusionPath::Collection')) {
$gcode .= $gcodegen->extrude($_, 'fill')
for $fill->chained_path($gcodegen->last_pos);
} else {
$gcode .= $gcodegen->extrude($fill, 'fill') ;
}
}
};
# give priority to infill if we were already using its extruder and it wouldn't
# be good for perimeters
if ($Slic3r::Config->infill_first
|| ($gcodegen->multiple_extruders && $region->extruders->{infill} eq $gcodegen->extruder) && $region->extruders->{infill} ne $region->extruders->{perimeter}) {
$extrude_fills->();
$extrude_perimeters->();
} else {
$extrude_perimeters->();
$extrude_fills->();
}
}
}
}
return if !$gcode;
my $fan_speed = $Slic3r::Config->fan_always_on ? $Slic3r::Config->min_fan_speed : 0;
my $speed_factor = 1;
if ($Slic3r::Config->cooling) {
my $layer_time = $gcodegen->elapsed_time;
Slic3r::debugf "Layer %d estimated printing time: %d seconds\n", $layer->id, $layer_time;
if ($layer_time < $Slic3r::Config->slowdown_below_layer_time) {
$fan_speed = $Slic3r::Config->max_fan_speed;
$speed_factor = $layer_time / $Slic3r::Config->slowdown_below_layer_time;
} elsif ($layer_time < $Slic3r::Config->fan_below_layer_time) {
$fan_speed = $Slic3r::Config->max_fan_speed - ($Slic3r::Config->max_fan_speed - $Slic3r::Config->min_fan_speed)
* ($layer_time - $Slic3r::Config->slowdown_below_layer_time)
/ ($Slic3r::Config->fan_below_layer_time - $Slic3r::Config->slowdown_below_layer_time); #/
}
Slic3r::debugf " fan = %d%%, speed = %d%%\n", $fan_speed, $speed_factor * 100;
if ($speed_factor < 1) {
$gcode =~ s/^(?=.*? [XY])(?=.*? E)(?<!;_BRIDGE_FAN_START\n)(G1 .*?F)(\d+(?:\.\d+)?)/
my $new_speed = $2 * $speed_factor;
$1 . sprintf("%.${dec}f", $new_speed < $min_print_speed ? $min_print_speed : $new_speed)
/gexm;
}
$fan_speed = 0 if $layer->id < $Slic3r::Config->disable_fan_first_layers;
}
$gcode = $gcodegen->set_fan($fan_speed) . $gcode;
# bridge fan speed
if (!$Slic3r::Config->cooling || $Slic3r::Config->bridge_fan_speed == 0 || $layer->id < $Slic3r::Config->disable_fan_first_layers) {
$gcode =~ s/^;_BRIDGE_FAN_(?:START|END)\n//gm;
} else {
$gcode =~ s/^;_BRIDGE_FAN_START\n/ $gcodegen->set_fan($Slic3r::Config->bridge_fan_speed, 1) /gmex;
$gcode =~ s/^;_BRIDGE_FAN_END\n/ $gcodegen->set_fan($fan_speed, 1) /gmex;
}
return $gcode;
};
# prepare the layer processor
my $layer_gcode = Slic3r::GCode::Layer->new(
print => $self,
gcodegen => $gcodegen,
shift => \@shift,
);
# do all objects for each layer
if ($Slic3r::Config->complete_objects) {
# print objects from the smallest to the tallest to avoid collisions
# when moving onto next object starting point
my @obj_idx = sort { $self->objects->[$a]->layer_count <=> $self->objects->[$b]->layer_count } 0..$#{$self->objects};
my @obj_idx = sort { $self->objects->[$a]->size->[Z] <=> $self->objects->[$b]->size->[Z] } 0..$#{$self->objects};
my $finished_objects = 0;
for my $obj_idx (@obj_idx) {
@ -997,6 +806,11 @@ sub write_gcode {
print $fh $gcodegen->G0(Slic3r::Point->new(0,0), undef, 0, 'move to origin position for next object');
}
my $buffer = Slic3r::GCode::CoolingBuffer->new(
config => $Slic3r::Config,
gcodegen => $gcodegen,
);
for my $layer (@{$self->objects->[$obj_idx]->layers}) {
# if we are printing the bottom layer of an object, and we have already finished
# another one, set first layer temperatures. this happens before the Z move
@ -1006,22 +820,53 @@ sub write_gcode {
if $Slic3r::Config->first_layer_bed_temperature;
$print_first_layer_temperature->();
}
print $fh $extrude_layer->($layer, [$copy]);
print $fh $buffer->append(
$layer_gcode->process_layer($layer, [$copy]),
$layer->object."",
$layer->id,
$layer->print_z,
);
}
print $fh $buffer->flush;
$finished_objects++;
}
}
} else {
print $fh $extrude_layer->($_, $_->object->copies)
for sort { $a->print_z <=> $b->print_z }
map @{$_->layers}, @{$self->objects};
# order objects using a nearest neighbor search
my @obj_idx = chained_path([ map $_->copies->[0], @{$self->objects} ]);
# sort layers by Z
my %layers = (); # print_z => [ layer, layer, layer ] by obj_idx
foreach my $obj_idx (0 .. $#{$self->objects}) {
foreach my $layer (@{$self->objects->[$obj_idx]->layers}) {
$layers{ $layer->print_z } ||= [];
$layers{ $layer->print_z }[$obj_idx] = $layer; # turn this into [$layer] when merging support layers
}
}
my $buffer = Slic3r::GCode::CoolingBuffer->new(
config => $Slic3r::Config,
gcodegen => $gcodegen,
);
foreach my $print_z (sort { $a <=> $b } keys %layers) {
foreach my $obj_idx (@obj_idx) {
next unless my $layer = $layers{$print_z}[$obj_idx];
print $fh $buffer->append(
$layer_gcode->process_layer($layer, $layer->object->copies),
$layer->object."",
$layer->id,
$layer->print_z,
);
}
}
print $fh $buffer->flush;
}
# save statistic data
$self->total_extrusion_length($gcodegen->total_extrusion_length);
# write end commands to file
print $fh $gcodegen->retract;
print $fh $gcodegen->retract if $gcodegen->extruder; # empty prints don't even set an extruder
print $fh $gcodegen->set_fan(0);
printf $fh "%s\n", $Slic3r::Config->replace_options($Slic3r::Config->end_gcode);

View File

@ -4,40 +4,66 @@ use Moo;
use List::Util qw(min sum first);
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Geometry qw(Z PI scale unscale deg2rad rad2deg scaled_epsilon chained_path_points);
use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex union_ex offset collapse_ex);
use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex union_ex offset collapse_ex
offset2 diff intersection);
use Slic3r::Surface ':types';
has 'print' => (is => 'ro', weak_ref => 1, required => 1);
has 'input_file' => (is => 'rw', required => 0);
has 'meshes' => (is => 'rw', default => sub { [] }); # by region_id
has 'size' => (is => 'rw', required => 1);
has 'copies' => (is => 'rw', default => sub {[ [0,0] ]}, trigger => 1);
has 'size' => (is => 'rw', required => 1); # XYZ in scaled coordinates
has 'copies' => (is => 'rw', trigger => 1); # in scaled coordinates
has 'layers' => (is => 'rw', default => sub { [] });
has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ]
has 'fill_maker' => (is => 'lazy');
sub BUILD {
my $self = shift;
# make layers
my $print_z = my $slice_z = my $raft_z = 0;
while (!@{$self->layers} || $self->layers->[-1]->slice_z < $self->size->[Z]) {
my $id = $#{$self->layers} + 1;
my $height = $Slic3r::Config->layer_height;
$height = $Slic3r::Config->get_value('first_layer_height') if $id == 0;
if (my $range = first { $_->[0] <= ($print_z + $_->[2]) && $_->[1] >= ($print_z + $_->[2]) } @{$self->layer_height_ranges}) {
$height = $range->[2];
}
# make layers taking custom heights into account
my $print_z = my $slice_z = my $height = 0;
# add raft layers
for my $id (0 .. $Slic3r::Config->raft_layers-1) {
$height = ($id == 0)
? $Slic3r::Config->get_value('first_layer_height')
: $Slic3r::Config->layer_height;
$print_z += $height;
if ($id < $Slic3r::Config->raft_layers) {
# this is a raft layer
$raft_z += $height;
$slice_z = -1;
} else {
$slice_z = $print_z - ($height/2) - $raft_z;
push @{$self->layers}, Slic3r::Layer->new(
object => $self,
id => $id,
height => $height,
print_z => scale $print_z,
slice_z => -1,
);
}
# loop until we have at least one layer and the max slice_z reaches the object height
my $max_z = unscale $self->size->[Z];
while (!@{$self->layers} || ($slice_z - $height) <= $max_z) {
my $id = $#{$self->layers} + 1;
# assign the default height to the layer according to the general settings
$height = ($id == 0)
? $Slic3r::Config->get_value('first_layer_height')
: $Slic3r::Config->layer_height;
# look for an applicable custom range
if (my $range = first { $_->[0] <= $slice_z && $_->[1] > $slice_z } @{$self->layer_height_ranges}) {
$height = $range->[2];
# if user set custom height to zero we should just skip the range and resume slicing over it
if ($height == 0) {
$slice_z += $range->[1] - $range->[0];
next;
}
}
$print_z += $height;
$slice_z += $height/2;
### Slic3r::debugf "Layer %d: height = %s; slice_z = %s; print_z = %s\n", $id, $height, $slice_z, $print_z;
push @{$self->layers}, Slic3r::Layer->new(
@ -47,9 +73,17 @@ sub BUILD {
print_z => scale $print_z,
slice_z => scale $slice_z,
);
$slice_z += $height/2; # add the other half layer
}
}
sub _build_fill_maker {
my $self = shift;
return Slic3r::Fill->new(object => $self);
}
# This should be probably moved in Print.pm at the point where we sort Layer objects
sub _trigger_copies {
my $self = shift;
return unless @{$self->copies} > 1;
@ -69,22 +103,44 @@ sub get_layer_range {
# $min_layer is the uppermost layer having slice_z <= $min_z
# $max_layer is the lowermost layer having slice_z >= $max_z
my ($min_layer, $max_layer) = (0, undef);
for my $i (0 .. $#{$self->layers}) {
if ($self->layers->[$i]->slice_z >= $min_z) {
$min_layer = $i - 1;
for my $k ($i .. $#{$self->layers}) {
if ($self->layers->[$k]->slice_z >= $max_z) {
$max_layer = $k - 1;
last;
}
}
my ($min_layer, $max_layer);
my ($bottom, $top) = (0, $#{$self->layers});
while (1) {
my $mid = $bottom+int(($top - $bottom)/2);
if ($mid == $top || $mid == $bottom) {
$min_layer = $mid;
last;
}
if ($self->layers->[$mid]->slice_z >= $min_z) {
$top = $mid;
} else {
$bottom = $mid;
}
}
$top = $#{$self->layers};
while (1) {
my $mid = $bottom+int(($top - $bottom)/2);
if ($mid == $top || $mid == $bottom) {
$max_layer = $mid;
last;
}
if ($self->layers->[$mid]->slice_z < $max_z) {
$bottom = $mid;
} else {
$top = $mid;
}
}
return ($min_layer, $max_layer);
}
sub bounding_box {
my $self = shift;
# since the object is aligned to origin, bounding box coincides with size
return Slic3r::Geometry::bounding_box([ [0,0], $self->size ]);
}
sub slice {
my $self = shift;
my %params = @_;
@ -125,15 +181,15 @@ sub slice {
}
},
);
$self->meshes->[$region_id] = undef; # free memory
}
die "Invalid input file\n" if !@{$self->layers};
# free memory
$self->meshes(undef);
# remove last layer if empty
# (we might have created it because of the $max_layer = ... + 1 code in TriangleMesh)
pop @{$self->layers} if !map @{$_->lines}, @{$self->layers->[-1]->regions};
# remove last layer(s) if empty
pop @{$self->layers} while @{$self->layers} && (!map @{$_->lines}, @{$self->layers->[-1]->regions});
foreach my $layer (@{ $self->layers }) {
# make sure all layers contain layer region objects for all regions
@ -220,9 +276,6 @@ sub slice {
$self->layers->[$i]->id($i);
}
}
warn "No layers were detected. You might want to repair your STL file and retry.\n"
if !@{$self->layers};
}
sub make_perimeters {
@ -244,19 +297,17 @@ sub make_perimeters {
my $overlap = $perimeter_spacing; # one perimeter
my $diff = diff_ex(
my $diff = diff(
[ offset([ map @{$_->expolygon}, @{$layerm->slices} ], -($Slic3r::Config->perimeters * $perimeter_spacing)) ],
[ offset([ map @{$_->expolygon}, @{$upper_layerm->slices} ], -$overlap) ],
);
next if !@$diff;
# if we need more perimeters, $diff should contain a narrow region that we can collapse
$diff = diff_ex(
[ map @$_, @$diff ],
[ offset(
[ offset([ map @$_, @$diff ], -$perimeter_spacing) ],
+$perimeter_spacing
) ],
$diff = diff(
$diff,
[ offset2($diff, -$perimeter_spacing, +$perimeter_spacing) ],
1,
);
next if !@$diff;
# diff contains the collapsed area
@ -267,18 +318,14 @@ sub make_perimeters {
# compute polygons representing the thickness of the hypotetical new internal perimeter
# of our slice
$extra_perimeters++;
my $hypothetical_perimeter = diff_ex(
my $hypothetical_perimeter = diff(
[ offset($slice->expolygon, -($perimeter_spacing * ($Slic3r::Config->perimeters + $extra_perimeters-1))) ],
[ offset($slice->expolygon, -($perimeter_spacing * ($Slic3r::Config->perimeters + $extra_perimeters))) ],
);
last CYCLE if !@$hypothetical_perimeter; # no extra perimeter is possible
# only add the perimeter if there's an intersection with the collapsed area
my $intersection = intersection_ex(
[ map @$_, @$diff ],
[ map @$_, @$hypothetical_perimeter ],
);
last CYCLE if !@$intersection;
last CYCLE if !@{ intersection($diff, $hypothetical_perimeter) };
Slic3r::debugf " adding one more perimeter at layer %d\n", $layer_id;
$slice->extra_perimeters($extra_perimeters);
}
@ -335,8 +382,7 @@ sub detect_surfaces_type {
[ map @$_, @$clip_surfaces ],
1,
);
return grep $_->contour->is_printable($layerm->perimeter_flow->scaled_width),
map Slic3r::Surface->new(expolygon => $_, surface_type => $result_type),
return map Slic3r::Surface->new(expolygon => $_, surface_type => $result_type),
@$expolygons;
};
@ -587,7 +633,8 @@ sub discover_horizontal_shells {
for (my $i = 0; $i < $self->layer_count; $i++) {
my $layerm = $self->layers->[$i]->regions->[$region_id];
if ($Slic3r::Config->solid_infill_every_layers && ($i % $Slic3r::Config->solid_infill_every_layers) == 0) {
if ($Slic3r::Config->solid_infill_every_layers && $Slic3r::Config->fill_density > 0
&& ($i % $Slic3r::Config->solid_infill_every_layers) == 0) {
$_->surface_type(S_TYPE_INTERNALSOLID)
for grep $_->surface_type == S_TYPE_INTERNAL, @{$layerm->fill_surfaces};
}
@ -634,10 +681,15 @@ sub discover_horizontal_shells {
# if some parts are going to collapse, let's grow them and add the extra area to the neighbor layer
# as well as to our original surfaces so that we support this additional area in the next shell too
if (@$too_narrow) {
# consider the actual fill area
my @fill_boundaries = $Slic3r::Config->fill_density > 0
? @neighbor_fill_surfaces
: grep $_->surface_type != S_TYPE_INTERNAL, @neighbor_fill_surfaces;
# make sure our grown surfaces don't exceed the fill area
my @grown = map @$_, @{intersection_ex(
[ offset([ map @$_, @$too_narrow ], +$margin) ],
[ map $_->p, @neighbor_fill_surfaces ],
[ map $_->p, @fill_boundaries ],
)};
$new_internal_solid = union_ex([ @grown, (map @$_, @$new_internal_solid) ]);
$solid = union_ex([ @grown, (map @$_, @$solid) ]);
@ -840,8 +892,7 @@ sub generate_support_material {
[ map @$_, @current_layer_offsetted_slices ],
);
$layers_contact_areas{$i} = [
map $_->simplify($flow->scaled_spacing),
@{collapse_ex([ map @$_, @{$layers_contact_areas{$i}} ], $flow->scaled_width)},
@{collapse_ex([ map @$_, @{$layers_contact_areas{$i}} ], $flow->scaled_width)},
];
# to define interface regions of this layer we consider the overhangs of all the upper layers
@ -854,8 +905,7 @@ sub generate_support_material {
],
);
$layers_interfaces{$i} = [
map $_->simplify($flow->scaled_spacing),
@{collapse_ex([ map @$_, @{$layers_interfaces{$i}} ], $flow->scaled_width)},
@{collapse_ex([ map @$_, @{$layers_interfaces{$i}} ], $flow->scaled_width)},
];
# generate support material in current layer (for upper layers)
@ -876,8 +926,7 @@ sub generate_support_material {
],
);
$layers{$i} = [
map $_->simplify($flow->scaled_spacing),
@{collapse_ex([ map @$_, @{$layers{$i}} ], $flow->scaled_width)},
@{collapse_ex([ map @$_, @{$layers{$i}} ], $flow->scaled_width)},
];
# get layer overhangs and put them into queue for adding support inside lower layers;
@ -923,10 +972,7 @@ sub generate_support_material {
push @angles, $angles[0] + 90;
}
my $filler = Slic3r::Fill->filler($pattern);
$filler->bounding_box([ Slic3r::Geometry::bounding_box([ map @$_, map @$_, @areas ]) ])
if $filler->can('bounding_box');
my $filler = $self->fill_maker->filler($pattern);
my $make_pattern = sub {
my ($expolygon, $density) = @_;
@ -1011,7 +1057,7 @@ sub generate_support_material {
# make a solid base on bottom layer
if ($layer_id == 0) {
my $filler = Slic3r::Fill->filler('rectilinear');
my $filler = $self->fill_maker->filler('rectilinear');
$filler->angle($Slic3r::Config->support_material_angle + 90);
foreach my $expolygon (@$islands) {
my @paths = $filler->fill_surface(

View File

@ -29,7 +29,7 @@ sub new {
my $self = [
map delete $args{$_}, qw(expolygon surface_type thickness thickness_layers bridge_angle extra_perimeters),
];
$self->[S_THICKNESS_LAYERS] = 1;
$self->[S_THICKNESS_LAYERS] //= 1;
bless $self, $class;
$self;

View File

@ -16,7 +16,7 @@ my %cuboids = (
);
sub model {
my ($model_name) = @_;
my ($model_name, %params) = @_;
my ($vertices, $facets);
if ($cuboids{$model_name}) {
@ -27,10 +27,22 @@ sub model {
$facets = [
[0,1,2], [0,2,3], [4,5,6], [4,6,7], [0,4,7], [0,7,1], [1,7,6], [1,6,2], [2,6,5], [2,5,3], [4,0,3], [4,3,5],
],
} elsif ($model_name eq 'V') {
$vertices = [
[-14,0,20],[-14,15,20],[0,0,0],[0,15,0],[-4,0,20],[-4,15,20],[5,0,7.14286],[10,0,0],[24,0,20],[14,0,20],[10,15,0],[5,15,7.14286],[14,15,20],[24,15,20]
];
$facets = [
[0,1,2],[2,1,3],[1,0,4],[5,1,4],[4,0,2],[6,4,2],[7,6,2],[8,9,7],[9,6,7],[2,3,7],[7,3,10],[1,5,3],[3,5,11],[11,12,13],[11,13,3],[3,13,10],[5,4,6],[11,5,6],[6,9,11],[11,9,12],[12,9,8],[13,12,8],[8,7,10],[13,8,10]
],
}
my $model = Slic3r::Model->new;
$model->add_object(vertices => $vertices)->add_volume(facets => $facets);
my $object = $model->add_object(vertices => $vertices);
$object->add_volume(facets => $facets);
$object->add_instance(
offset => [0,0],
rotation => $params{rotation} // 0,
);
return $model;
}
@ -42,7 +54,9 @@ sub init_print {
$config->set('gcode_comments', 1) if $ENV{SLIC3R_TESTS_GCODE};
my $print = Slic3r::Print->new(config => $config);
$print->add_model(model($model_name));
$model_name = [$model_name] if ref($model_name) ne 'ARRAY';
$print->add_model(model($_, %params)) for @$model_name;
$print->validate;
return $print;
@ -77,68 +91,4 @@ sub add_facet {
}
}
package Slic3r::Test::GCodeReader;
use Moo;
has 'gcode' => (is => 'ro', required => 1);
has 'X' => (is => 'rw', default => sub {0});
has 'Y' => (is => 'rw', default => sub {0});
has 'Z' => (is => 'rw', default => sub {0});
has 'E' => (is => 'rw', default => sub {0});
has 'F' => (is => 'rw', default => sub {0});
our $Verbose = 0;
my @AXES = qw(X Y Z E);
sub parse {
my $self = shift;
my ($cb) = @_;
foreach my $line (split /\n/, $self->gcode) {
print "$line\n" if $Verbose || $ENV{SLIC3R_TESTS_GCODE};
$line =~ s/\s*;(.*)//; # strip comment
next if $line eq '';
my $comment = $1;
# parse command
my ($command, @args) = split /\s+/, $line;
my %args = map { /([A-Z])(.*)/; ($1 => $2) } @args;
my %info = ();
# check retraction
if ($command =~ /^G[01]$/) {
if (!exists $args{E}) {
$info{travel} = 1;
}
foreach my $axis (@AXES) {
if (!exists $args{$axis}) {
$info{"dist_$axis"} = 0;
next;
}
$info{"dist_$axis"} = $args{$axis} - $self->$axis;
}
$info{dist_XY} = Slic3r::Line->new([0,0], [@info{qw(dist_X dist_Y)}])->length;
if (exists $args{E}) {
if ($info{dist_E} > 0) {
$info{extruding} = 1;
} elsif ($info{dist_E} < 0) {
$info{retracting} = 1
}
}
}
# run callback
$cb->($self, $command, \%args, \%info);
# update coordinates
if ($command =~ /^(?:G[01]|G92)$/) {
for (@AXES, 'F') {
$self->$_($args{$_}) if exists $args{$_};
}
}
# TODO: update temperatures
}
}
1;

View File

@ -0,0 +1,155 @@
package Slic3r::Test::SectionCut;
use Moo;
use List::Util qw(first max);
use Slic3r::Geometry qw(X Y A B X1 Y1 X2 Y2 unscale);
use Slic3r::Geometry::Clipper qw(union_ex);
use SVG;
has 'scale' => (is => 'ro', default => sub {30});
has 'print' => (is => 'ro', required => 1);
has 'y_percent' => (is => 'ro', default => sub {0.5});
has 'line' => (is => 'lazy');
has 'height' => (is => 'rw');
sub _build_line {
my $self = shift;
my @bb = $self->print->bounding_box;
my $y = ($bb[Y2]-$bb[Y1]) * $self->y_percent;
return [ [ $bb[X1], $y ], [ $bb[X2], $y ] ]
}
sub export_svg {
my $self = shift;
my ($filename) = @_;
my $print_size = $self->print->size;
$self->height(unscale max(map $_->print_z, map @{$_->layers}, @{$self->print->objects}));
my $svg = SVG->new(
width => $self->scale * unscale($print_size->[X]),
height => $self->scale * $self->height,
);
my $group = sub {
my %p = @_;
my $g = $svg->group(style => $p{style});
my $items = $self->_plot($p{filter});
$g->rectangle(%$_) for @{ $items->{rectangles} };
$g->circle(%$_) for @{ $items->{circles} };
};
$group->(
filter => sub { map @{$_->perimeters}, @{$_[0]->regions} },
style => {
'stroke-width' => 1,
'stroke' => 'grey',
'fill' => 'red',
},
);
$group->(
filter => sub { map @{$_->fills}, @{$_[0]->regions} },
style => {
'stroke-width' => 1,
'stroke' => '#444444',
'fill' => 'grey',
},
);
$group->(
filter => sub { $_[0]->support_fills, $_[0]->support_contact_fills },
style => {
'stroke-width' => 1,
'stroke' => '#444444',
'fill' => '#22FF00',
},
);
Slic3r::open(\my $fh, '>', $filename);
print $fh $svg->xmlify;
close $fh;
printf "Section cut SVG written to %s\n", $filename;
}
sub _plot {
my $self = shift;
my ($filter) = @_;
my (@rectangles, @circles) = ();
foreach my $object (@{$self->print->objects}) {
foreach my $copy (@{$object->copies}) {
foreach my $layer (@{$object->layers}) {
# get all ExtrusionPath objects
my @paths =
map { $_->polyline->translate(@$copy); $_ }
map { $_->isa('Slic3r::ExtrusionLoop') ? $_->split_at_first_point : $_ }
map { ref($_) =~ /::Packed$/ ? $_->unpack : $_ }
map { $_->isa('Slic3r::ExtrusionPath::Collection') ? @{$_->paths} : $_ }
grep defined $_,
$filter->($layer);
foreach my $path (@paths) {
foreach my $line ($path->lines) {
my @intersections = @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection(
Slic3r::ExPolygon->new($line->grow(Slic3r::Geometry::scale $path->flow_spacing/2)),
[ $self->line ],
) };
die "Intersection has more than two points!\n" if first { @$_ > 2 } @intersections;
if ($path->is_bridge) {
foreach my $line (@intersections) {
my $radius = $path->flow_spacing / 2;
my $width = unscale abs($line->[B][X] - $line->[A][X]);
if ((10 * Slic3r::Geometry::scale $radius) < $width) {
# we're cutting the path in the longitudinal direction, so we've got a rectangle
push @rectangles, {
'x' => $self->scale * unscale $line->[A][X],
'y' => $self->scale * $self->_y(unscale($layer->print_z)),
'width' => $self->scale * $width,
'height' => $self->scale * $radius * 2,
'rx' => $self->scale * $radius * 0.35,
'ry' => $self->scale * $radius * 0.35,
};
} else {
push @circles, {
'cx' => $self->scale * (unscale($line->[A][X]) + $radius),
'cy' => $self->scale * $self->_y(unscale($layer->print_z) - $radius),
'r' => $self->scale * $radius,
};
}
}
} else {
push @rectangles, map {
my $height = $path->height // $layer->height;
{
'x' => $self->scale * unscale $_->[A][X],
'y' => $self->scale * $self->_y(unscale($layer->print_z)),
'width' => $self->scale * unscale(abs($_->[B][X] - $_->[A][X])),
'height' => $self->scale * $height,
'rx' => $self->scale * $height * 0.35,
'ry' => $self->scale * $height * 0.35,
};
} @intersections;
}
}
}
}
}
}
return {
rectangles => \@rectangles,
circles => \@circles,
};
}
sub _y {
my $self = shift;
my ($y) = @_;
return $self->height - $y;
}
1;

View File

@ -3,15 +3,16 @@ use Moo;
use Slic3r::Geometry qw(X Y Z A B unscale same_point);
use Slic3r::Geometry::Clipper qw(union_ex);
use Storable;
# public
has 'vertices' => (is => 'ro', required => 1); # id => [$x,$y,$z]
has 'facets' => (is => 'ro', required => 1); # id => [ $v1_id, $v2_id, $v3_id ]
# private
has 'edges' => (is => 'ro', default => sub { [] }); # id => [ $v1_id, $v2_id ]
has 'facets_edges' => (is => 'ro', default => sub { [] }); # id => [ $e1_id, $e2_id, $e3_id ]
has 'edges_facets' => (is => 'ro', default => sub { [] }); # id => [ $f1_id, $f2_id, (...) ]
has 'edges' => (is => 'rw'); # id => [ $v1_id, $v2_id ]
has 'facets_edges' => (is => 'rw'); # id => [ $e1_id, $e2_id, $e3_id ]
has 'edges_facets' => (is => 'rw'); # id => [ $f1_id, $f2_id, (...) ]
use constant MIN => 0;
use constant MAX => 1;
@ -28,13 +29,13 @@ use constant I_FACET_EDGE => 6;
use constant FE_TOP => 0;
use constant FE_BOTTOM => 1;
# always make sure BUILD is idempotent
sub BUILD {
sub analyze {
my $self = shift;
@{$self->edges} = ();
@{$self->facets_edges} = ();
@{$self->edges_facets} = ();
return if defined $self->edges;
$self->edges([]);
$self->facets_edges([]);
$self->edges_facets([]);
my %table = (); # edge_coordinates => edge_id
for (my $facet_id = 0; $facet_id <= $#{$self->facets}; $facet_id++) {
@ -92,11 +93,7 @@ sub merge {
}
sub clone {
my $self = shift;
return (ref $self)->new(
vertices => [ map [ @$_ ], @{$self->vertices} ],
facets => [ map [ @$_ ], @{$self->facets} ],
);
Storable::dclone($_[0])
}
sub _facet_edges {
@ -148,6 +145,8 @@ sub clean {
sub check_manifoldness {
my $self = shift;
$self->analyze;
# look for any edges not connected to exactly two facets
my ($first_bad_edge_id) =
grep { @{ $self->edges_facets->[$_] } != 2 } 0..$#{$self->edges_facets};
@ -157,6 +156,10 @@ sub check_manifoldness {
map @{$self->vertices->[$_]}, @{$self->edges->[$first_bad_edge_id]};
return 0;
}
# empty the edges array as we don't really need it anymore
@{$self->edges} = ();
return 1;
}
@ -321,14 +324,14 @@ sub make_loops {
sub rotate {
my $self = shift;
my ($deg) = @_;
my ($deg, $center) = @_;
return if $deg == 0;
my $rad = Slic3r::Geometry::deg2rad($deg);
# transform vertex coordinates
foreach my $vertex (@{$self->vertices}) {
@$vertex = (@{ +(Slic3r::Geometry::rotate_points($rad, undef, [ $vertex->[X], $vertex->[Y] ]))[0] }, $vertex->[Z]);
@$vertex = (@{ +(Slic3r::Geometry::rotate_points($rad, $center, [ $vertex->[X], $vertex->[Y] ]))[0] }, $vertex->[Z]);
}
}
@ -362,6 +365,19 @@ sub align_to_origin {
$self->move(map -$extents[$_][MIN], X,Y,Z);
}
sub center_around_origin {
my $self = shift;
$self->move(map -$_, @{ $self->center });
}
sub center {
my $self = shift;
my @extents = $self->extents;
return [ map +($extents[$_][MAX] + $extents[$_][MIN])/2, X,Y,Z ];
}
sub duplicate {
my $self = shift;
my (@shifts) = @_;
@ -382,14 +398,24 @@ sub duplicate {
$self->BUILD;
}
sub used_vertices {
my $self = shift;
return [ map $self->vertices->[$_], map @$_, @{$self->facets} ];
}
sub extents {
my $self = shift;
return Slic3r::Geometry::bounding_box_3D($self->vertices);
return Slic3r::Geometry::bounding_box_3D($self->used_vertices);
}
sub bounding_box {
my $self = shift;
return Slic3r::Geometry::BoundingBox->new(extents => [ $self->extents ]);
}
sub size {
my $self = shift;
return Slic3r::Geometry::size_3D($self->vertices);
return Slic3r::Geometry::size_3D($self->used_vertices);
}
sub slice_facet {
@ -540,6 +566,8 @@ sub get_connected_facets {
sub split_mesh {
my $self = shift;
$self->analyze;
my @meshes = ();
# loop while we have remaining facets

View File

@ -26,6 +26,7 @@ my %cli_options = ();
'save=s' => \$opt{save},
'load=s@' => \$opt{load},
'autosave=s' => \$opt{autosave},
'ignore-nonexistent-config' => \$opt{ignore_nonexistent_config},
'no-plater' => \$opt{no_plater},
'gui-mode=s' => \$opt{gui_mode},
@ -78,6 +79,7 @@ if (!@ARGV && !$opt{save} && eval "require Slic3r::GUI; 1") {
$Slic3r::GUI::datadir = $opt{datadir};
$Slic3r::GUI::no_plater = $opt{no_plater};
$Slic3r::GUI::mode = $opt{gui_mode};
$Slic3r::GUI::autosave = $opt{autosave};
}
$gui = Slic3r::GUI->new;
$gui->{skeinpanel}->load_config_file($_) for @{$opt{load}};
@ -91,13 +93,19 @@ if (@ARGV) { # slicing from command line
$config->validate;
while (my $input_file = shift @ARGV) {
my $print = Slic3r::Print->new(config => $config);
$print->add_model(Slic3r::Model->read_from_file($input_file));
my $model;
if ($opt{merge}) {
$print->add_model(Slic3r::Model->read_from_file($_)) for splice @ARGV, 0;
my @models = map Slic3r::Model->read_from_file($_), $input_file, (splice @ARGV, 0);
$model = Slic3r::Model->merge(@models);
} else {
$model = Slic3r::Model->read_from_file($input_file);
}
$print->duplicate;
$print->arrange_objects if @{$print->objects} > 1;
$_->scale($config->scale) for @{$model->objects};
$_->rotate($config->rotate) for @{$model->objects};
$model->arrange_objects($config);
my $print = Slic3r::Print->new(config => $config);
$print->add_model($model);
$print->validate;
my %params = (
output_file => $opt{output},
@ -146,6 +154,7 @@ $j
GUI options:
--no-plater Disable the plater tab
--gui-mode Overrides the configured mode (simple/expert)
--autosave <file> Automatically export current configuration to the specified file
Output options:
--output-filename-format
@ -164,7 +173,7 @@ $j
(default: $config->{print_center}->[0],$config->{print_center}->[1])
--z-offset Additional height in mm to add to vertical coordinates
(+/-, default: $config->{z_offset})
--gcode-flavor The type of G-code to generate (reprap/teacup/makerbot/sailfish/mach3/no-extrusion,
--gcode-flavor The type of G-code to generate (reprap/teacup/makerware/sailfish/mach3/no-extrusion,
default: $config->{gcode_flavor})
--use-relative-e-distances Enable this to get relative E values
--gcode-arcs Use G2/G3 commands for native arcs (experimental, not supported
@ -251,6 +260,8 @@ $j
--randomize-start Randomize starting point across layers (default: yes)
--avoid-crossing-perimeters Optimize travel moves so that no perimeters are crossed (default: no)
--external-perimeters-first Reverse perimeter order. (default: no)
--spiral-vase Experimental option to raise Z gradually when printing single-walled vases
(default: no)
--only-retract-when-crossing-perimeters
Disable retraction when travelling between infill paths inside the same island.
(default: no)
@ -293,6 +304,7 @@ $j
--retract-lift Lift Z by the given distance in mm when retracting (default: $config->{retract_lift}[0])
--retract-layer-change
Enforce a retraction before each Z move (default: yes)
--wipe Wipe the nozzle while doing a retraction (default: no)
Retraction options for multi-extruder setups:
--retract-length-toolchange

View File

@ -2,7 +2,7 @@ use Test::More;
use strict;
use warnings;
plan tests => 12;
plan tests => 13;
BEGIN {
use FindBin;
@ -20,7 +20,7 @@ use Slic3r::Geometry qw(scaled_epsilon scale X Y);
[306517.1,219034.23], [286979.42,248012.49], [258001.16,267550.17], [222515.14,274714.47],
[187029.11,267550.17], [158050.85,248012.49], [138513.17,219034.23], [131348.87,183548.2],
[86948.77,175149.09], [119825.35,100585],
), role => EXTR_ROLE_FILL);
), role => EXTR_ROLE_FILL, flow_spacing => 0.5);
my $collection = Slic3r::ExtrusionPath::Collection->new(paths => [$path]);
$collection->detect_arcs(30);
@ -42,10 +42,12 @@ use Slic3r::Geometry qw(scaled_epsilon scale X Y);
my $path1 = Slic3r::ExtrusionPath->new(
polyline => Slic3r::Polyline->new(@points),
role => EXTR_ROLE_FILL,
flow_spacing => 0.5,
);
my $path2 = Slic3r::ExtrusionPath->new(
polyline => Slic3r::Polyline->new(reverse @points),
role => EXTR_ROLE_FILL,
flow_spacing => 0.5,
);
my $collection1 = Slic3r::ExtrusionPath::Collection->new(paths => [$path1]);
@ -66,6 +68,7 @@ use Slic3r::Geometry qw(scaled_epsilon scale X Y);
is $collection1->paths->[0]->orientation, 'cw', 'cw orientation was correctly detected';
is $collection2->paths->[0]->orientation, 'ccw', 'ccw orientation was correctly detected';
is $collection1->paths->[0]->flow_spacing, $path1->flow_spacing, 'flow spacing was correctly preserved';
my $center1 = [ map sprintf('%.0f', $_), @{ $collection1->paths->[0]->center } ];
ok abs($center1->[X] - scale 10) < scaled_epsilon && abs($center1->[Y] - scale 10) < scaled_epsilon, 'center was correctly detected';

View File

@ -30,21 +30,22 @@ use Math::Clipper ':all';
$clipper->add_subject_polygons([ $square, $hole_in_square ]);
$clipper->add_clip_polygons([ $square2 ]);
my $intersection = $clipper->ex_execute(CT_INTERSECTION, PFT_NONZERO, PFT_NONZERO);
is_deeply $intersection, [
{
holes => [
[
[14, 14],
[14, 16],
[16, 16],
[16, 14],
[14, 14],
],
],
outer => [
[10, 18],
[10, 12],
[20, 12],
[20, 18],
[10, 18],
[10, 12],
],
},
], 'hole is preserved after intersection';
@ -60,14 +61,15 @@ use Math::Clipper ':all';
my $clipper = Math::Clipper->new;
$clipper->add_subject_polygons([ $contour1, $contour2, $hole ]);
my $union = $clipper->ex_execute(CT_UNION, PFT_NONZERO, PFT_NONZERO);
is_deeply $union, [{ holes => [], outer => [ [0,40], [0,0], [40,0], [40,40] ] }],
is_deeply $union, [{ holes => [], outer => [ [40,0], [40,40], [0,40], [0,0] ] }],
'union of two ccw and one cw is a contour with no holes';
$clipper->clear;
$clipper->add_subject_polygons([ $contour1, $contour2 ]);
$clipper->add_clip_polygons([ $hole ]);
my $diff = $clipper->ex_execute(CT_DIFFERENCE, PFT_NONZERO, PFT_NONZERO);
is_deeply $diff, [{ holes => [[ [15,25], [25,25], [25,15], [15,15] ]], outer => [ [0,40], [0,0], [40,0], [40,40] ] }],
is_deeply $diff, [{ holes => [[ [15,15], [15,25], [25,25], [25,15] ]], outer => [ [40,0], [40,40], [0,40], [0,0] ] }],
'difference of a cw from two ccw is a contour with one hole';
}

89
t/cooling.t Normal file
View File

@ -0,0 +1,89 @@
use Test::More;
use strict;
use warnings;
plan tests => 8;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
}
use Slic3r;
sub buffer {
my $config = shift || Slic3r::Config->new_from_defaults;
my $buffer = Slic3r::GCode::CoolingBuffer->new(
config => $config,
gcodegen => Slic3r::GCode->new(config => $config, layer_count => 10),
);
return $buffer;
}
my $config = Slic3r::Config->new_from_defaults;
$config->set('disable_fan_first_layers', 0);
{
my $buffer = buffer($config);
$buffer->gcodegen->elapsed_time($buffer->config->slowdown_below_layer_time + 1);
my $gcode = $buffer->append('G1 X100 E1 F3000', 0, 0, 0.4) . $buffer->flush;
like $gcode, qr/F3000/, 'speed is not altered when elapsed time is greater than slowdown threshold';
}
{
my $buffer = buffer($config);
$buffer->gcodegen->elapsed_time($buffer->config->slowdown_below_layer_time - 1);
my $gcode = $buffer->append("G1 X50 F2500\nG1 X100 E1 F3000\nG1 E4 F400", 0, 0, 0.4) . $buffer->flush;
unlike $gcode, qr/F3000/, 'speed is altered when elapsed time is lower than slowdown threshold';
like $gcode, qr/F2500/, 'speed is not altered for travel moves';
like $gcode, qr/F400/, 'speed is not altered for extruder-only moves';
}
{
my $buffer = buffer($config);
$buffer->gcodegen->elapsed_time($buffer->config->fan_below_layer_time + 1);
my $gcode = $buffer->append('G1 X100 E1 F3000', 0, 0, 0.4) . $buffer->flush;
unlike $gcode, qr/M106/, 'fan is not activated when elapsed time is greater than fan threshold';
}
{
my $buffer = buffer($config);
my $gcode = "";
for my $obj_id (0 .. 1) {
# use an elapsed time which is < the slowdown threshold but greater than it when summed twice
$buffer->gcodegen->elapsed_time($buffer->config->slowdown_below_layer_time - 1);
$gcode .= $buffer->append("G1 X100 E1 F3000\n", $obj_id, 0, 0.4);
}
$gcode .= $buffer->flush;
like $gcode, qr/F3000/, 'slowdown is computed on all objects printing at same Z';
}
{
my $buffer = buffer($config);
my $gcode = "";
for my $layer_id (0 .. 1) {
for my $obj_id (0 .. 1) {
# use an elapsed time which is < the threshold but greater than it when summed twice
$buffer->gcodegen->elapsed_time($buffer->config->fan_below_layer_time - 1);
$gcode .= $buffer->append("G1 X100 E1 F3000\n", $obj_id, $layer_id, 0.4 + 0.4*$layer_id + 0.1*$obj_id); # print same layer at distinct heights
}
}
$gcode .= $buffer->flush;
unlike $gcode, qr/M106/, 'fan activation is computed on all objects printing at different Z';
}
{
my $buffer = buffer($config);
my $gcode = "";
for my $layer_id (0 .. 1) {
for my $obj_id (0 .. 1) {
# use an elapsed time which is < the threshold even when summed twice
$buffer->gcodegen->elapsed_time($buffer->config->fan_below_layer_time/2 - 1);
$gcode .= $buffer->append("G1 X100 E1 F3000\n", $obj_id, $layer_id, 0.4 + 0.4*$layer_id + 0.1*$obj_id); # print same layer at distinct heights
}
}
$gcode .= $buffer->flush;
like $gcode, qr/M106/, 'fan activation is computed on all objects printing at different Z';
}
__END__

View File

@ -20,7 +20,7 @@ use Slic3r::Test;
my $print = Slic3r::Test::init_print('2x20x10', config => $conf);
my $last_move_was_z_change = 0;
Slic3r::Test::GCodeReader->new(gcode => Slic3r::Test::gcode($print))->parse(sub {
Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub {
my ($self, $cmd, $args, $info) = @_;
if ($last_move_was_z_change && $cmd ne $config->layer_gcode) {

View File

@ -2,7 +2,7 @@ use Test::More;
use strict;
use warnings;
plan tests => 9;
plan tests => 10;
BEGIN {
use FindBin;
@ -19,7 +19,10 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
{
my $print = Slic3r::Print->new;
$print->init_extruders;
my $filler = Slic3r::Fill::Rectilinear->new(print => $print);
my $filler = Slic3r::Fill::Rectilinear->new(
print => $print,
bounding_box => [ 0, 0, 10, 10 ],
);
my $surface_width = 250;
my $distance = $filler->adjust_solid_spacing(
width => $surface_width,
@ -30,10 +33,13 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
}
{
my $filler = Slic3r::Fill::Rectilinear->new;
my $expolygon = Slic3r::ExPolygon->new([ scale_points [0,0], [50,0], [50,50], [0,50] ]);
my $filler = Slic3r::Fill::Rectilinear->new(
bounding_box => [ $expolygon->bounding_box ],
);
my $surface = Slic3r::Surface->new(
surface_type => S_TYPE_TOP,
expolygon => Slic3r::ExPolygon->new([ scale_points [0,0], [50,0], [50,50], [0,50] ]),
expolygon => $expolygon,
);
foreach my $angle (0, 45) {
$surface->expolygon->rotate(Slic3r::Geometry::deg2rad($angle), [0,0]);
@ -95,4 +101,25 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
ok Slic3r::Test::gcode($print), 'successful hilbertcurve infill generation';
}
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('skirts', 0);
$config->set('perimeters', 0);
$config->set('fill_density', 0);
$config->set('top_solid_layers', 0);
$config->set('bottom_solid_layers', 0);
$config->set('solid_infill_below_area', 20000000);
$config->set('solid_infill_every_layers', 2);
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
my %layers_with_extrusion = ();
Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub {
my ($self, $cmd, $args, $info) = @_;
$layers_with_extrusion{$self->Z} = 1 if $info->{extruding};
});
ok !%layers_with_extrusion,
"solid_infill_below_area and solid_infill_every_layers are ignored when fill_density is 0";
}
__END__

View File

@ -11,8 +11,10 @@ use Slic3r;
use Slic3r::Geometry qw(scale);
{
local $Slic3r::Config = Slic3r::Config->new_from_defaults;
my $gcodegen = Slic3r::GCode->new(layer_count => 1);
my $gcodegen = Slic3r::GCode->new(
config => Slic3r::Config->new_from_defaults,
layer_count => 1,
);
$gcodegen->set_shift(10, 10);
is_deeply $gcodegen->last_pos, [scale -10, scale -10], 'last_pos is shifted correctly';
}

View File

@ -2,7 +2,7 @@ use Test::More;
use strict;
use warnings;
plan tests => 23;
plan tests => 24;
BEGIN {
use FindBin;
@ -173,4 +173,12 @@ is Slic3r::Geometry::can_connect_points(@$points, $polygons), 0, 'can_connect_po
is_deeply $result, [ [10, 0], [5, 5], [0, 0], [10, 0] ], 'split_at_index';
}
#==========================================================
#==========================================================
{
my $bb = Slic3r::Geometry::BoundingBox->new_from_points([ [0, 1], [10, 2], [20, 2] ]);
$bb->scale(2);
is_deeply $bb->extents, [ [0,40], [2,4] ], 'bounding box is scaled correctly';
}
#==========================================================

View File

@ -21,7 +21,7 @@ my $test = sub {
my @z = ();
my @increments = ();
Slic3r::Test::GCodeReader->new(gcode => Slic3r::Test::gcode($print))->parse(sub {
Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub {
my ($self, $cmd, $args, $info) = @_;
if ($info->{dist_Z}) {

View File

@ -40,6 +40,7 @@ use Slic3r::Test;
[ [5,5,0], [5,15,10], [5,5,10] ];
my $mesh = Slic3r::TriangleMesh->new(vertices => \@vertices, facets => \@facets);
$mesh->analyze;
my @lines = map $mesh->intersect_facet($_, 10), 0..$#facets;
my $loops = Slic3r::TriangleMesh::make_loops(\@lines);
is scalar(@$loops), 3, 'correct number of loops detected';

View File

@ -144,8 +144,8 @@ is_deeply $intersection, [ [120, 120], [180, 160] ], 'internal lines are preserv
my $intersections = $expolygon->clip_line($line);
is_deeply $intersections, [
[ [152, 287], [152, 214], ],
[ [152, 107], [152, 35] ],
[ [152.742, 288.086660915295], [152.742, 215.178843238354], ],
[ [152.742, 108.087506777797], [152.742, 35.1664774739315] ],
], 'line is clipped to square with hole';
}

20
t/print.t Normal file
View File

@ -0,0 +1,20 @@
use Test::More tests => 1;
use strict;
use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
}
use List::Util qw(first);
use Slic3r;
use Slic3r::Test;
{
my $print = Slic3r::Test::init_print('20mm_cube', rotation => 45);
ok !(first { $_ < 0 } map @$_, map @{$_->used_vertices}, grep $_, map @{$_->meshes}, @{$print->objects}),
"object is still in positive coordinate space even after rotation";
}
__END__

View File

@ -25,7 +25,7 @@ my $test = sub {
my $lifted = 0;
my $changed_tool = 0;
my $wait_for_toolchange = 0;
Slic3r::Test::GCodeReader->new(gcode => Slic3r::Test::gcode($print))->parse(sub {
Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub {
my ($self, $cmd, $args, $info) = @_;
if ($cmd =~ /^T(\d+)/) {
@ -52,6 +52,7 @@ my $test = sub {
if !_eq($info->{dist_Z}, -$print->extruders->[$tool]->retract_lift);
$lifted = 0;
}
fail 'move Z at travel speed' if ($args->{F} // $self->F) != $conf->travel_speed * 60;
}
if ($info->{retracting}) {
$retracted[$tool] = 1;
@ -89,6 +90,7 @@ my $test = sub {
};
$config->set('first_layer_height', $config->layer_height);
$config->set('first_layer_speed', '100%');
$config->set('start_gcode', ''); # to avoid dealing with the nozzle lift in start G-code
$config->set('retract_length', [1.5]);
$config->set('retract_before_travel', [3]);

View File

@ -1,4 +1,4 @@
use Test::More tests => 2;
use Test::More tests => 3;
use strict;
use warnings;
@ -23,7 +23,7 @@ use Slic3r::Test;
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
my %layers_with_shells = (); # Z => $count
Slic3r::Test::GCodeReader->new(gcode => Slic3r::Test::gcode($print))->parse(sub {
Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub {
my ($self, $cmd, $args, $info) = @_;
if ($self->Z > 0) {
@ -45,4 +45,27 @@ use Slic3r::Test;
ok $test->(), "proper number of shells is applied even when fill density is none";
}
# issue #1161
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('layer_height', 0.3);
$config->set('first_layer_height', '100%');
$config->set('bottom_solid_layers', 0);
$config->set('top_solid_layers', 3);
$config->set('cooling', 0);
$config->set('solid_infill_speed', 99);
$config->set('top_solid_infill_speed', 99);
my $print = Slic3r::Test::init_print('V', config => $config);
my %layers_with_solid_infill = (); # Z => 1
Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub {
my ($self, $cmd, $args, $info) = @_;
$layers_with_solid_infill{$self->Z} = 1
if $info->{extruding} && ($args->{F} // $self->F) == $config->solid_infill_speed*60;
});
is scalar(map $layers_with_solid_infill{$_}, grep $_ <= 7.2, keys %layers_with_solid_infill), 3,
"correct number of top solid shells is generated in V-shaped object";
}
__END__

46
t/skirt_brim.t Normal file
View File

@ -0,0 +1,46 @@
use Test::More tests => 1;
use strict;
use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
}
use List::Util qw(first);
use Slic3r;
use Slic3r::Test;
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('skirts', 1);
$config->set('skirt_height', 2);
$config->set('perimeters', 0);
$config->set('perimeter_speed', 99);
$config->set('cooling', 0); # to prevent speeds to be altered
$config->set('first_layer_speed', '100%'); # to prevent speeds to be altered
my $test = sub {
my ($conf) = @_;
$conf ||= $config;
my $print = Slic3r::Test::init_print(['20mm_cube','20mm_cube'], config => $config);
my %layers_with_skirt = (); # Z => $count
Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub {
my ($self, $cmd, $args, $info) = @_;
if (defined $self->Z) {
$layers_with_skirt{$self->Z} //= 0;
$layers_with_skirt{$self->Z} = 1
if $info->{extruding} && ($args->{F} // $self->F) == $config->perimeter_speed*60;
}
});
fail "wrong number of layers with skirt"
unless (grep $_, values %layers_with_skirt) == $config->skirt_height;
};
ok $test->(), "skirt_height is honored when printing multiple objects too";
}
__END__

View File

@ -16,8 +16,6 @@ my @lines;
my $z = 20;
my @points = ([3, 4], [8, 5], [1, 9]); # XY coordinates of the facet vertices
my $mesh = Slic3r::TriangleMesh->new(facets => [], vertices => []);
# NOTE:
# the first point of the intersection lines is replaced by -1 because TriangleMesh.pm
# is saving memory and doesn't store point A anymore since it's not actually needed.
@ -104,21 +102,23 @@ my @upper = intersect(20, 20, 10);
is $lower[0][Slic3r::TriangleMesh::I_FACET_EDGE], Slic3r::TriangleMesh::FE_BOTTOM, 'bottom edge on layer';
is $upper[0][Slic3r::TriangleMesh::I_FACET_EDGE], Slic3r::TriangleMesh::FE_TOP, 'upper edge on layer';
my $mesh;
sub intersect {
$mesh = Slic3r::TriangleMesh->new(
facets => [],
vertices => [],
);
push @{$mesh->facets}, [ [0,0,0], @{vertices(@_)} ];
$mesh->analyze;
return map Slic3r::TriangleMesh::unpack_line($_), $mesh->intersect_facet($#{$mesh->facets}, $z);
}
sub vertices {
push @{$mesh->vertices}, map [ @{$points[$_]}, $_[$_] ], 0..2;
[ ($#{$mesh->vertices}-2) .. $#{$mesh->vertices} ]
}
sub add_facet {
push @{$mesh->facets}, [ [0,0,0], @{vertices(@_)} ];
$mesh->BUILD;
}
sub intersect {
add_facet(@_);
return map Slic3r::TriangleMesh::unpack_line($_), $mesh->intersect_facet($#{$mesh->facets}, $z);
}
sub lines {
my @lines = intersect(@_);
#$_->a->[X] = sprintf('%.0f', $_->a->[X]) for @lines;

37
t/support.t Normal file
View File

@ -0,0 +1,37 @@
use Test::More tests => 1;
use strict;
use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
}
use Slic3r;
use Slic3r::Test;
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('raft_layers', 3);
$config->set('brim_width', 6);
$config->set('skirts', 0);
$config->set('support_material_extruder', 2);
$config->set('layer_height', 0.4);
$config->set('first_layer_height', '100%');
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
ok my $gcode = Slic3r::Test::gcode($print), 'no conflict between raft/support and brim';
my $tool = 0;
Slic3r::GCode::Reader->new(gcode => $gcode)->parse(sub {
my ($self, $cmd, $args, $info) = @_;
if ($cmd =~ /^T(\d+)/) {
$tool = $1;
} elsif ($info->{extruding} && $self->Z <= ($config->raft_layers * $config->layer_height)) {
fail 'not extruding raft/brim with support material extruder'
if $tool != ($config->support_material_extruder-1);
}
});
}
__END__

23
t/svg.t Normal file
View File

@ -0,0 +1,23 @@
use Test::More tests => 1;
use strict;
use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
}
use Slic3r;
use Slic3r::Test;
{
my $print = Slic3r::Test::init_print('20mm_cube');
eval {
my $fh = IO::Scalar->new(\my $gcode);
$print->export_svg(output_fh => $fh, quiet => 1);
$fh->close;
};
ok !$@, 'successful SVG export';
}
__END__

View File

@ -28,7 +28,7 @@ my $test = sub {
my %dir_time = (X => 0, Y => 0);
my %dir_sleep_time = (X => 0, Y => 0);
my $last_cmd_pause = 0;
Slic3r::Test::GCodeReader->new(gcode => Slic3r::Test::gcode($print))->parse(sub {
Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub {
my ($self, $cmd, $args, $info) = @_;
if ($cmd !~ /^G[01]$/) {

34
utils/dump-stl.pl Normal file
View File

@ -0,0 +1,34 @@
#!/usr/bin/perl
# This script dumps a STL file into Perl syntax for writing tests
use strict;
use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
}
use Slic3r;
$|++;
$ARGV[0] or usage(1);
{
my $model = Slic3r::Format::STL->read_file($ARGV[0]);
my $mesh = $model->mesh;
printf "VERTICES = %s\n", join ',', map "[$_->[0],$_->[1],$_->[2]]", @{$mesh->vertices};
printf "FACETS = %s\n", join ',', map "[$_->[0],$_->[1],$_->[2]]", @{$mesh->facets};
}
sub usage {
my ($exit_code) = @_;
print <<"EOF";
Usage: dump-stl.pl file.stl
EOF
exit ($exit_code || 0);
}
__END__

133
utils/gcode_sectioncut.pl Normal file
View File

@ -0,0 +1,133 @@
#!/usr/bin/perl
# This script generates section cuts from a given G-Code file
use strict;
use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
}
use Getopt::Long qw(:config no_auto_abbrev);
use IO::All;
use List::Util qw(max);
use Slic3r;
use Slic3r::Geometry qw(X Y A B X1 Y1 X2 Y2);
use Slic3r::Geometry::Clipper qw(JT_SQUARE);
use Slic3r::Test;
use SVG;
my %opt = (
layer_height => 0.2,
extrusion_width => 0.5,
scale => 30,
);
{
my %options = (
'help' => sub { usage() },
'layer-height|h=f' => \$opt{layer_height},
'extrusion-width|w=f' => \$opt{extrusion_width},
'scale|s=i' => \$opt{scale},
);
GetOptions(%options) or usage(1);
$ARGV[0] or usage(1);
}
{
my $input_file = $ARGV[0];
my $output_file = $input_file;
$output_file =~ s/\.(?:gcode|gco|ngc|g)$/.svg/;
# read paths
my %paths = (); # z => [ path, path ... ]
Slic3r::GCode::Reader->new(gcode => io($input_file)->all)->parse(sub {
my ($self, $cmd, $args, $info) = @_;
if ($cmd eq 'G1' && $info->{extruding}) {
$paths{ $self->Z } ||= [];
push @{ $paths{ $self->Z } }, Slic3r::Line->new(
[ $self->X, $self->Y ],
[ $info->{new_X}, $info->{new_Y} ],
);
}
});
# calculate print extents
my @bounding_box = Slic3r::Geometry::bounding_box([ map @$_, map @$_, values %paths ]);
my @size = (
($bounding_box[X2] - $bounding_box[X1]),
($bounding_box[Y2] - $bounding_box[Y1]),
);
# calculate section line
my $section_y = ($bounding_box[Y2] + $bounding_box[Y1]) / 2;
my $section_line = [
[ $bounding_box[X1], $section_y ],
[ $bounding_box[X2], $section_y ],
];
# initialize output
my $max_z = max(keys %paths);
my $svg = SVG->new(
width => $opt{scale} * $size[X],
height => $opt{scale} * $max_z,
);
# put everything into a group
my $g = $svg->group(style => {
'stroke-width' => 1,
'stroke' => '#444444',
'fill' => 'grey',
});
# draw paths
foreach my $z (sort keys %paths) {
foreach my $line (@{ $paths{$z} }) {
my @intersections = @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection(
Slic3r::ExPolygon->new(_grow($line, $opt{extrusion_width}/2)),
[ $section_line ],
) };
$g->rectangle(
'x' => $opt{scale} * ($_->[A][X] - $bounding_box[X1]),
'y' => $opt{scale} * ($max_z - $z),
'width' => $opt{scale} * abs($_->[B][X] - $_->[A][X]),
'height' => $opt{scale} * $opt{layer_height},
'rx' => $opt{scale} * $opt{layer_height} * 0.35,
'ry' => $opt{scale} * $opt{layer_height} * 0.35,
) for @intersections;
}
}
# write output
Slic3r::open(\my $fh, '>', $output_file);
print $fh $svg->xmlify;
close $fh;
printf "Section cut SVG written to %s\n", $output_file;
}
# replace built-in Line->grow method which relies on int_offset()
sub _grow {
my ($line, $distance) = @_;
my $polygon = [ @$line, CORE::reverse @$line[1..($#$line-1)] ];
return @{Math::Clipper::offset([$polygon], $distance, 100000, JT_SQUARE, 2)};
}
sub usage {
my ($exit_code) = @_;
print <<"EOF";
Usage: gcode_sectioncut.pl [ OPTIONS ] file.gcode
--help Output this usage screen and exit
--layer-height, -h Use the specified layer height
--extrusion-width, -w Use the specified extrusion width
--scale Factor for converting G-code units to SVG units
EOF
exit ($exit_code || 0);
}
__END__

67
utils/view-mesh.pl Normal file
View File

@ -0,0 +1,67 @@
#!/usr/bin/perl
# This script displays 3D preview of a mesh
use strict;
use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
}
use Getopt::Long qw(:config no_auto_abbrev);
use Slic3r;
use Slic3r::GUI;
$|++;
my %opt = ();
{
my %options = (
'help' => sub { usage() },
);
GetOptions(%options) or usage(1);
$ARGV[0] or usage(1);
}
{
my $model = Slic3r::Model->read_from_file($ARGV[0]);
$Slic3r::ViewMesh::mesh = $model->mesh;
my $app = Slic3r::ViewMesh->new;
$app->MainLoop;
}
sub usage {
my ($exit_code) = @_;
print <<"EOF";
Usage: view-mesh.pl [ OPTIONS ] file.stl
--help Output this usage screen and exit
EOF
exit ($exit_code || 0);
}
package Slic3r::ViewMesh;
use Wx qw(:sizer);
use base qw(Wx::App);
our $mesh;
sub OnInit {
my $self = shift;
my $frame = Wx::Frame->new(undef, -1, 'Mesh Viewer', [-1, -1], [500, 400]);
my $panel = Wx::Panel->new($frame, -1);
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
$sizer->Add(Slic3r::GUI::PreviewCanvas->new($panel, $mesh), 1, wxEXPAND, 0);
$panel->SetSizer($sizer);
$sizer->SetSizeHints($panel);
$frame->Show(1);
}
__END__

View File

@ -22,7 +22,7 @@ _arguments -S \
'*--nozzle-diameter[specify nozzle diameter]:nozzle diameter in mm' \
'--print-center[specify print center coordinates]:print center coordinates in mm,mm' \
'--z-offset[specify Z-axis offset]:Z-axis offset in mm' \
'--gcode-flavor[specify the type of G-code to generate]:G-code flavor:(reprap teacup makerbot sailfish mach3 no-extrusion)' \
'--gcode-flavor[specify the type of G-code to generate]:G-code flavor:(reprap teacup makerware sailfish mach3 no-extrusion)' \
'(--use-relative-e-distances --no-use-relative-e-distances)'--{no-,}use-relative-e-distances'[disable/enable relative E values]' \
'--extrusion-axis[specify letter associated with the extrusion axis]:extrusion axis letter' \
'(--gcode-arcs --no-gcode-arcs)'--{no-,}gcode-arcs'[disable/enable G2/G3 commands for native arcs]' \