Merge branch 'master' into overhang

Conflicts:
	lib/Slic3r/GCode.pm
This commit is contained in:
Alessandro Ranellucci 2013-06-16 10:49:33 +02:00
commit 07407e5dbd
53 changed files with 1737 additions and 615 deletions

View file

@ -7,12 +7,12 @@ my $build = Module::Build->new(
dist_version => '0.1',
license => 'perl',
requires => {
'Boost::Geometry::Utils' => '0.08',
'Boost::Geometry::Utils' => '0.12',
'Encode::Locale' => '0',
'File::Basename' => '0',
'File::Spec' => '0',
'Getopt::Long' => '0',
'Math::Clipper' => '1.21',
'Math::Clipper' => '1.22',
'Math::ConvexHull::MonotoneChain' => '0.01',
'Math::Geometry::Voronoi' => '1.3',
'Math::PlanePath' => '53',

View file

@ -25,10 +25,12 @@ 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
@ -37,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
@ -64,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
@ -72,11 +76,14 @@ 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
@ -88,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

View file

@ -7,7 +7,7 @@ use strict;
use warnings;
require v5.10;
our $VERSION = "0.9.10-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.08;
use Boost::Geometry::Utils 0.12;
use Moo 0.091009;
use Slic3r::Config;
@ -46,10 +46,13 @@ 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;
@ -68,7 +71,6 @@ use constant SCALING_FACTOR => 0.000001;
use constant RESOLUTION => 0.0125;
use constant SCALED_RESOLUTION => RESOLUTION / SCALING_FACTOR;
use constant OVERLAP_FACTOR => 1;
use constant BRIDGE_OVERLAP_FACTOR => 0.2;
use constant SMALL_PERIMETER_LENGTH => (6.5 / SCALING_FACTOR) * 2 * PI;
use constant LOOP_CLIPPING_LENGTH_OVER_SPACING => 0.15;
use constant INFILL_OVERLAP_OVER_SPACING => 0.45;
@ -84,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' => {
@ -595,7 +596,7 @@ our $Options = {
},
'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,7 +798,7 @@ 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],
},
@ -806,7 +807,7 @@ END
tooltip => 'This flag will move the nozzle while retracting to minimize the possible blob on leaky extruders.',
cli => 'wipe!',
type => 'bool',
serialize => $serialize_comma,
serialize => $serialize_comma_bool,
deserialize => $deserialize_comma,
default => [0],
},
@ -1140,7 +1141,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/;
@ -1149,6 +1150,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
@ -1270,6 +1274,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
@ -1328,6 +1336,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 {
@ -1408,7 +1420,7 @@ sub read_ini {
my $ini = { _ => {} };
my $category = '_';
while (my $_ = <$fh>) {
while (<$fh>) {
s/\R+$//;
next if /^\s+/;
next if /^$/;

View file

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

@ -14,6 +14,7 @@ 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');
@ -38,7 +39,9 @@ sub _build_retract_speed_mm_min {
sub _build_scaled_wipe_distance {
my $self = shift;
return scale $self->retract_length / $self->retract_speed * $Slic3r::Config->travel_speed;
# 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 {

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, weak_ref => 1);
has 'object' => (is => 'ro', required => 1, weak_ref => 1);
has 'fillers' => (is => 'rw', default => sub { {} });
our %FillTypes = (
@ -40,7 +40,7 @@ sub filler {
}
$self->fillers->{$filler} ||= $FillTypes{$filler}->new(
bounding_box => [ $self->print->bounding_box ],
bounding_box => [ $self->object->bounding_box ],
);
return $self->fillers->{$filler};
}

View file

@ -36,6 +36,7 @@ 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) = @_;

View file

@ -38,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} ]; # 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
@ -77,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

@ -25,17 +25,17 @@ sub fill_surface {
my $line_oscillation = $distance_between_lines - $min_spacing;
my $is_line_pattern = $self->isa('Slic3r::Fill::Line');
my $cache_id = sprintf "d%s_s%s_a%s",
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 = [ @{$self->bounding_box} ]; # clone
$bounding_box->[$_] = 0 for X1, Y1;
my $bounding_box;
{
my $bb_expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_from_bounding_box($bounding_box));
$self->rotate_points($bb_expolygon, $rotate_vector);
$bounding_box = [ $bb_expolygon->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 ];
}
# define flow spacing according to requested density

View file

@ -98,8 +98,7 @@ sub _build_width {
sub _build_spacing {
my $self = shift;
my $width = $self->width;
return $width - (&Slic3r::BRIDGE_OVERLAP_FACTOR * $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

@ -53,7 +53,7 @@ sub _read_ascii {
my $facet;
my %vertices_map = ();
seek $fh, 0, 0;
while (my $_ = <$fh>) {
while (<$fh>) {
if (!$facet) {
/^\s*facet\s+normal\s+/ or next;
$facet = []; # ignore normal
@ -88,7 +88,7 @@ sub _read_binary {
my %vertices_map = ();
binmode $fh;
seek $fh, 80 + 4, 0;
while (read $fh, my $_, 4*4*3+2) {
while (read $fh, $_, 4*4*3+2) {
push @$facets, my $facet = [];
for (unpack 'x[f3](a[f3])3') { # ignore normal
my $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} );
@ -35,15 +36,14 @@ has 'dec' => (is => 'ro', default => sub { 3 } );
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 = (
@ -82,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;
}
@ -103,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;
@ -137,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);
@ -157,7 +157,7 @@ sub extrude_loop {
$self->wipe_path($extrusion_path->polyline);
# make a little move inwards before leaving loop
if ($loop->role == EXTR_ROLE_EXTERNAL_PERIMETER && $Slic3r::Config->perimeters > 1) {
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);
@ -188,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);
@ -205,12 +205,12 @@ 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->is_bridge) {
$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;
@ -252,19 +252,19 @@ sub extrude_path {
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;
}
@ -283,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);
@ -333,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 = @_;
@ -370,7 +372,7 @@ sub retract {
? undef
: [undef, $self->z + $self->extruder->retract_lift, 0, 'lift plate during travel'];
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
@ -381,7 +383,7 @@ 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"];
@ -417,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;
}
@ -426,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');
@ -436,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);
}
@ -446,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 {
@ -458,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", @_);
}
@ -533,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);
@ -543,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";
}
@ -575,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,
});
@ -584,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;
}
@ -607,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' : '');
}
}
}
@ -629,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;
}
@ -648,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;
}
@ -665,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

@ -6,7 +6,7 @@ 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');
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 {
@ -16,15 +16,17 @@ sub _build_min_print_speed {
sub append {
my $self = shift;
my ($gcode, $layer) = @_;
my ($gcode, $obj_id, $layer_id, $print_z) = @_;
# TODO: differentiate $obj_id between normal layers and support layers
my $return = "";
if (defined $self->last_z && $self->last_z != $layer->print_z) {
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($layer->print_z);
$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);
@ -39,6 +41,7 @@ sub flush {
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;

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,6 +10,7 @@ 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);
@ -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};
@ -81,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) {
@ -112,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;
@ -127,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;
@ -183,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

@ -26,7 +26,7 @@ sub parse {
my ($command, @args) = split /\s+/, $line;
my %args = map { /([A-Z])(.*)/; ($1 => $2) } @args;
# check retraction
# check motion
if ($command =~ /^G[01]$/) {
foreach my $axis (@AXES) {
if (exists $args{$axis}) {

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';
@ -43,6 +45,7 @@ use constant MI_DOCUMENTATION => &Wx::NewId;
our $datadir;
our $no_plater;
our $mode;
our $autosave;
our $Settings = {
_ => {
@ -116,12 +119,12 @@ 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)});
@ -250,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)
@ -286,6 +290,7 @@ sub check_version {
my %p = @_;
Slic3r::debugf "Checking for updates...\n";
@_ = ();
threads->create(sub {
my $ua = LWP::UserAgent->new;
$ua->timeout(10);

View file

@ -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, 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;
}
@ -544,7 +549,13 @@ sub export_gcode {
}
$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,
@ -693,11 +704,6 @@ sub make_model {
foreach my $plater_object (@{$self->{objects}}) {
my $model_object = $plater_object->get_model_object;
# if we need to alter the mesh, clone it first
if ($plater_object->scale != 1) {
$model_object = $model_object->clone;
}
my $new_model_object = $model->add_object(
vertices => $model_object->vertices,
input_file => $plater_object->input_file,
@ -710,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;
}
@ -738,6 +746,7 @@ sub make_thumbnail {
}
};
@_ = ();
$Slic3r::have_threads ? threads->create($cb)->detach : $cb->();
}
@ -759,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,
];
}
@ -801,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
@ -857,9 +868,12 @@ sub repaint {
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]) {
@ -874,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) = offset([$convex_hull], $parent->{config}->extruder_clearance_radius / 2 * $parent->{scaling_factor}, 1, JT_ROUND);
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);
@ -885,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) = offset([$convex_hull], $parent->{config}->skirt_distance * $parent->{scaling_factor}, 1, JT_ROUND);
($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;
@ -931,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) {
@ -1054,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
@ -1078,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);
}
}
@ -1120,55 +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, @$_;
}
# only simplify expolygons larger than the threshold
@{$thumbnail->expolygons} = map { ($_->area >= 1) ? $_->simplify(0.5) : $_ } @{$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;
@{$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;

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 }

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

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
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
@ -383,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 {
@ -461,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) = @_;

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

@ -9,7 +9,7 @@ our @EXPORT_OK = qw(safety_offset safety_offset_ex offset offset_ex collapse_ex
JT_SQUARE is_counter_clockwise union_pt offset2 offset2_ex traverse_pt
intersection);
use Math::Clipper 1.21 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;

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

@ -252,7 +252,7 @@ sub make_perimeters {
)};
my @loops = ();
foreach my $polynode (@$polynodes) {
foreach my $polynode (@nodes) {
push @loops, $traverse->($polynode->{children}, $depth+1, $is_contour);
my $polygon = Slic3r::Polygon->new($polynode->{outer} // [ reverse @{$polynode->{hole}} ]);
@ -317,7 +317,7 @@ sub _fill_gaps {
return unless $Slic3r::Config->gap_fill_speed > 0 && $Slic3r::Config->fill_density > 0 && @$gaps;
my $filler = $self->layer->object->print->fill_maker->filler('rectilinear');
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
@ -331,8 +331,12 @@ sub _fill_gaps {
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 = (1.5 * $w, $w, 0.4 * $w); # worth trying 0.2 too?
my @widths = ($w, 0.4 * $w); # worth trying 0.2 too?
foreach my $width (@widths) {
my $flow = $self->perimeter_flow->clone(width => $width);
@ -373,7 +377,7 @@ sub _fill_gaps {
push @{ $self->thin_fills },
map {
$_->polyline->simplify($flow->scaled_width / 3);
$_->simplify($flow->scaled_width/3);
$_->pack;
}
map Slic3r::ExtrusionPath->new(

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,7 @@ 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');
@ -123,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) = @_;
@ -134,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;
@ -167,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

@ -156,11 +156,12 @@ 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;
}

View file

@ -6,7 +6,8 @@ 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 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);
@ -21,7 +22,6 @@ has 'regions' => (is => 'rw', default => sub {[]});
has 'support_material_flow' => (is => 'rw');
has 'first_layer_support_material_flow' => (is => 'rw');
has 'has_support_material' => (is => 'lazy');
has 'fill_maker' => (is => 'lazy');
# ordered collection of extrusion paths to build skirt loops
has 'skirt' => (
@ -82,11 +82,8 @@ sub _build_has_support_material {
|| $self->config->support_material_enforce_layers > 0;
}
sub _build_fill_maker {
my $self = shift;
return Slic3r::Fill->new(print => $self);
}
# caller is responsible for supplying models whose objects don't collide
# and have explicit instance positions
sub add_model {
my $self = shift;
my ($model) = @_;
@ -103,57 +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 @defined_meshes = grep defined $_, @meshes;
my $complete_mesh = @defined_meshes == 1 ? $defined_meshes[0] : Slic3r::TriangleMesh->merge(@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} ]);
}
}
}
@ -282,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] ],
@ -370,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);
@ -416,7 +382,6 @@ sub export_gcode {
# this will generate extrusion paths for each layer
$status_cb->(80, "Infilling layers");
{
my $fill_maker = $self->fill_maker;
Slic3r::parallelize(
items => sub {
my @items = (); # [obj_idx, layer_id]
@ -433,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;
@ -455,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) ]);
}
},
);
@ -528,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"?>
@ -603,7 +571,7 @@ EOF
print $fh "</svg>\n";
close $fh;
print "Done.\n";
print "Done.\n" unless $params{quiet};
}
sub make_skirt {
@ -748,10 +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,
);
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
@ -769,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";
}
}
@ -813,190 +780,19 @@ sub write_gcode {
));
}
# prepare the SpiralVase processor if it's possible
my $spiralvase = $Slic3r::Config->spiral_vase
? Slic3r::GCode::SpiralVase->new
: undef;
# 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;
$second_layer_things_done = 1;
}
# set new layer, but don't move Z as support material contact areas may need an intermediate one
$gcode .= $gcodegen->change_layer($layer);
# 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->();
}
}
}
}
# apply spiral vase post-processing if this layer contains suitable geometry
$gcode = $spiralvase->process_layer($gcode, $layer)
if defined $spiralvase
&& ($layer->id > 0 || $Slic3r::Config->brim_width == 0)
&& ($layer->id >= $Slic3r::Config->skirt_height)
&& ($layer->id >= $Slic3r::Config->bottom_solid_layers);
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) {
@ -1024,19 +820,45 @@ sub write_gcode {
if $Slic3r::Config->first_layer_bed_temperature;
$print_first_layer_temperature->();
}
print $fh $buffer->append($extrude_layer->($layer, [$copy]), $layer);
print $fh $buffer->append(
$layer_gcode->process_layer($layer, [$copy]),
$layer->object."",
$layer->id,
$layer->print_z,
);
}
print $fh $buffer->flush;
$finished_objects++;
}
}
} else {
# 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,
);
print $fh $buffer->append($extrude_layer->($_, $_->object->copies), $_)
for sort { $a->print_z <=> $b->print_z } map @{$_->layers}, @{$self->objects};
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;
}

View file

@ -4,16 +4,18 @@ 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;
@ -76,6 +78,12 @@ sub BUILD {
}
}
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;
@ -126,6 +134,13 @@ sub get_layer_range {
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 = @_;
@ -166,14 +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(s) if empty
pop @{$self->layers} while !map @{$_->lines}, @{$self->layers->[-1]->regions};
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
@ -260,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 {
@ -284,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
@ -307,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);
}
@ -376,7 +383,6 @@ sub detect_surfaces_type {
1,
);
return map Slic3r::Surface->new(expolygon => $_, surface_type => $result_type),
grep $_->is_printable($layerm->perimeter_flow->scaled_width),
@$expolygons;
};
@ -590,7 +596,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};
}
@ -928,7 +935,7 @@ sub generate_support_material {
push @angles, $angles[0] + 90;
}
my $filler = $self->print->fill_maker->filler($pattern);
my $filler = $self->fill_maker->filler($pattern);
my $make_pattern = sub {
my ($expolygon, $density) = @_;
@ -1013,7 +1020,7 @@ sub generate_support_material {
# make a solid base on bottom layer
if ($layer_id == 0) {
my $filler = $self->print->fill_maker->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

@ -9,7 +9,7 @@ use constant Y => 1;
our $filltype = 'evenodd';
sub factor {return 30;
sub factor {
return &Slic3r::SCALING_FACTOR * 10;
}

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;

View file

@ -10,9 +10,9 @@ 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;
@ -29,13 +29,13 @@ use constant I_FACET_EDGE => 6;
use constant FE_TOP => 0;
use constant FE_BOTTOM => 1;
# always make sure this method is idempotent
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++) {
@ -324,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]);
}
}
@ -365,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) = @_;
@ -385,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 {

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

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

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

@ -2,7 +2,7 @@ use Test::More;
use strict;
use warnings;
plan tests => 9;
plan tests => 10;
BEGIN {
use FindBin;
@ -109,14 +109,17 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
$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) = @_;
fail "solid_infill_below_area should be ignored when fill_density is 0"
if $info->{extruding};
$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';
}
#==========================================================

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

@ -1,4 +1,4 @@
use Test::More tests => 2;
use Test::More tests => 3;
use strict;
use warnings;
@ -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->analyze;
}
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;

View file

@ -14,8 +14,24 @@ 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 Slic3r::Test::gcode($print), 'no conflict between raft/support and brim';
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__

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__

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]' \