Merge branch 'master' into new-support2

Conflicts:
	lib/Slic3r/Print.pm
new-support2
Alessandro Ranellucci 2013-06-16 10:34:52 +02:00
commit 77f453acf8
33 changed files with 512 additions and 172 deletions

View File

@ -30,6 +30,7 @@ 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
@ -75,12 +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

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.
@ -112,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 {
@ -51,6 +51,8 @@ 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;

View File

@ -74,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' => {
@ -596,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,
@ -1141,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/;
@ -1150,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
@ -1271,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
@ -1413,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

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

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

@ -22,7 +22,6 @@ 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} );
@ -90,7 +89,7 @@ sub change_layer {
}
my $gcode = "";
if ($self->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))),
($self->config->gcode_comments ? ' ; update progress' : '');
@ -284,7 +283,7 @@ sub travel_to {
$travel->translate(-$self->shift_x, -$self->shift_y);
if ($travel->length < scale $self->extruder->retract_before_travel
|| ($self->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);
@ -327,7 +326,7 @@ sub _plan {
my $need_retract = !$self->config->only_retract_when_crossing_perimeters;
if (!$need_retract) {
$need_retract = 1;
foreach my $slice (@{$self->layer->slices}) {
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
@ -341,7 +340,8 @@ sub _plan {
# append the actual path and return
$self->speed('travel');
$gcode .= join '', map $self->G0($_->[B], undef, 0, $comment || ""), @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;
}
@ -419,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 $self->config->gcode_flavor !~ /^(?:mach3|makerbot)$/;
$gcode .= $self->reset_e;
$gcode .= "M103 ; extruder off\n" if $self->config->gcode_flavor eq 'makerware';
return $gcode;
}
@ -428,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');
@ -438,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);
}
@ -448,8 +452,9 @@ sub unretract {
sub reset_e {
my $self = shift;
return "" if $self->config->gcode_flavor =~ /^(?:mach3|makerware)$/;
$self->extrusion_distance(0);
$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;
}
@ -546,10 +551,10 @@ sub _Gx {
# output extrusion distance
if ($e && $self->config->extrusion_axis) {
$self->extrusion_distance(0) if $self->config->use_relative_e_distances;
$self->extrusion_distance($self->extrusion_distance + $e);
$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", $self->config->extrusion_axis, $self->extrusion_distance;
$gcode .= sprintf " %s%.5f", $self->config->extrusion_axis, $self->extruder->e;
}
$gcode .= sprintf " ; %s", $comment if $comment && $self->config->gcode_comments;
@ -586,18 +591,16 @@ sub set_extruder {
# set the new extruder
$self->extruder($extruder);
my $toolchange_gcode = sprintf "%s%d%s\n",
($self->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,
($self->config->gcode_comments ? ' ; change extruder' : '');
if ($self->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;
}
@ -611,12 +614,12 @@ sub set_fan {
if ($speed == 0) {
my $code = $self->config->gcode_flavor eq 'teacup'
? 'M106 S0'
: $self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/
: $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/
? 'M127'
: 'M107';
return sprintf "$code%s\n", ($self->config->gcode_comments ? ' ; disable fan' : '');
} else {
if ($self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) {
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", ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'),
@ -631,14 +634,14 @@ sub set_temperature {
my $self = shift;
my ($temperature, $wait, $tool) = @_;
return "" if $wait && $self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/;
return "" if $wait && $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/;
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",
($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature,
(defined $tool && ($self->multiple_extruders || $self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/)) ? "T$tool " : "";
(defined $tool && ($self->multiple_extruders || $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/)) ? "T$tool " : "";
$gcode .= "M116 ; wait for temperature to be reached\n"
if $self->config->gcode_flavor eq 'teacup' && $wait;
@ -651,7 +654,7 @@ sub set_bed_temperature {
my ($temperature, $wait) = @_;
my ($code, $comment) = ($wait && $self->config->gcode_flavor ne 'teacup')
? (($self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ ? 'M109' : 'M190'), 'wait for bed temperature to be reached')
? (($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",
($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature;

View File

@ -18,8 +18,6 @@ sub append {
my $self = shift;
my ($gcode, $obj_id, $layer_id, $print_z) = @_;
# TODO: differentiate $obj_id between normal layers and support layers
my $return = "";
if (exists $self->last_z->{$obj_id} && $self->last_z->{$obj_id} != $print_z) {
$return = $self->flush;

View File

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

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 {
@ -699,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,
@ -716,9 +716,10 @@ 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, # around center point
scaling_factor => $plater_object->scale,
offset => Slic3r::Point->new($_),
) for @{$plater_object->instances};
}
@ -767,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,
];
}
@ -809,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
@ -865,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]) {
@ -939,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) {
@ -1062,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
@ -1086,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);
}
}
@ -1128,54 +1139,58 @@ sub instances_count {
sub make_thumbnail {
my $self = shift;
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

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

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

@ -35,6 +35,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}} ]);
@ -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

@ -119,6 +119,7 @@ sub arrange_objects {
$object->add_instance(
offset => $_,
rotation => $instance->rotation,
scaling_factor => $instance->scaling_factor,
) for move_points($instance->offset, @positions);
}
}
@ -213,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});
}
@ -259,6 +261,7 @@ sub split_meshes {
$new_object->add_instance(
offset => [ $_->offset->[X] + $extents[X][MIN], $_->offset->[Y] + $extents[Y][MIN] ],
rotation => $_->rotation,
scaling_factor => $_->scaling_factor,
) for @{ $object->instances // [] };
}
}
@ -274,7 +277,7 @@ package Slic3r::Model::Object;
use Moo;
use List::Util qw(first);
use Slic3r::Geometry qw(X Y Z MIN move_points move_points_3D);
use Slic3r::Geometry qw(X Y Z MIN MAX move_points move_points_3D);
use Storable qw(dclone);
has 'input_file' => (is => 'rw');
@ -338,13 +341,27 @@ sub extents {
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;
$self->move(map -$extents[$_][MIN], X,Y,Z);
my @shift = map -$extents[$_][MIN], X,Y,Z;
$self->move(@shift);
return @shift;
}
sub move {
@ -410,6 +427,7 @@ use Moo;
has 'object' => (is => 'ro', weak_ref => 1, required => 1);
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 MAX 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);
@ -105,6 +106,9 @@ sub add_model {
$model->split_meshes if $Slic3r::Config->avoid_crossing_perimeters && !$Slic3r::Config->complete_objects;
foreach my $object (@{ $model->objects }) {
# 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}) {
@ -119,21 +123,24 @@ sub add_model {
foreach my $mesh (grep $_, @meshes) {
$mesh->check_manifoldness;
# we ignore the per-instance rotation currently and only
# 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
$mesh->rotate($object->instances->[0]->rotation, $mesh->center)
if @{ $object->instances // [] };
if ($object->instances && @{$object->instances}) {
$mesh->rotate($object->instances->[0]->rotation, $object->center);
$mesh->scale($object->instances->[0]->scaling_factor);
}
$mesh->scale(1 / &Slic3r::SCALING_FACTOR);
}
# align the object to origin; not sure this is required by the toolpath generation
# algorithms, but it's good practice to avoid negative coordinates; it probably
# provides also some better performance in infill generation
my @extents = Slic3r::Geometry::bounding_box_3D([ map @{$_->used_vertices}, grep $_, @meshes ]);
foreach my $mesh (grep $_, @meshes) {
$mesh->move(map -$extents[$_][MIN], X,Y,Z);
}
# 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
push @{$self->objects}, Slic3r::Print::Object->new(
@ -141,10 +148,10 @@ sub add_model {
meshes => [ @meshes ],
copies => [
$object->instances
? (map [ (scale $_->offset->[X]) + $extents[X][MIN], (scale $_->offset->[Y]) + $extents[Y][MIN] ], @{$object->instances})
? (map [ scale($_->offset->[X] - $align[X]) - $align2[X], scale($_->offset->[Y] - $align[Y]) - $align2[Y] ], @{$object->instances})
: [0,0],
],
size => [ map $extents[$_][MAX] - $extents[$_][MIN], (X,Y,Z) ],
size => $bb->size, # transformed size
input_file => $object->input_file,
layer_height_ranges => $object->layer_height_ranges,
);
@ -322,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);
@ -480,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"?>
@ -555,7 +571,7 @@ EOF
print $fh "</svg>\n";
close $fh;
print "Done.\n";
print "Done.\n" unless $params{quiet};
}
sub make_skirt {
@ -704,7 +720,7 @@ sub write_gcode {
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
@ -722,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|teacup|makerbot|sailfish)$/) {
if ($Slic3r::Config->use_relative_e_distances) {
print $fh "M83 ; use relative distances for extrusion\n";
} else {
print $fh "M82 ; use absolute distances for extrusion\n";
}
if ($Slic3r::Config->use_relative_e_distances) {
print $fh "M83 ; use relative distances for extrusion\n";
} else {
print $fh "M82 ; use absolute distances for extrusion\n";
}
}
@ -778,7 +792,7 @@ sub write_gcode {
# 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) {
@ -820,18 +834,35 @@ sub write_gcode {
}
}
} 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 => [ [layers], [layers], [layers] ] by obj_idx
foreach my $obj_idx (0 .. $#{$self->objects}) {
my $object = $self->objects->[$obj_idx];
foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
$layers{ $layer->print_z } ||= [];
$layers{ $layer->print_z }[$obj_idx] ||= [];
push @{$layers{ $layer->print_z }[$obj_idx]}, $layer;
}
}
my $buffer = Slic3r::GCode::CoolingBuffer->new(
config => $Slic3r::Config,
gcodegen => $gcodegen,
);
my @layers = sort { $a->print_z <=> $b->print_z } map { @{$_->layers}, @{$_->support_layers} } @{$self->objects};
foreach my $layer (@layers) {
print $fh $buffer->append(
$layer_gcode->process_layer($layer, $layer->object->copies),
$layer->object."",
$layer->id,
$layer->print_z,
);
foreach my $print_z (sort { $a <=> $b } keys %layers) {
foreach my $obj_idx (@obj_idx) {
foreach my $layer (@{ $layers{$print_z}[$obj_idx] // [] }) {
print $fh $buffer->append(
$layer_gcode->process_layer($layer, $layer->object->copies),
$layer->object . ref($layer), # differentiate $obj_id between normal layers and support layers
$layer->id,
$layer->print_z,
);
}
}
}
print $fh $buffer->flush;
}

View File

@ -11,7 +11,7 @@ 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 '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 'support_layers' => (is => 'rw', default => sub { [] });
@ -191,7 +191,6 @@ sub slice {
# remove last layer(s) if empty
pop @{$self->layers} while @{$self->layers} && (!map @{$_->lines}, @{$self->layers->[-1]->regions});
die "Invalid or too thin input file: no layers could be generated\n" if !@{$self->layers};
foreach my $layer (@{ $self->layers }) {
# make sure all layers contain layer region objects for all regions
@ -278,9 +277,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 {
@ -388,7 +384,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;
};
@ -602,7 +597,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};
}

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,12 +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;
my $object = $model->add_object(vertices => $vertices);
$object->add_volume(facets => $facets);
$object->add_instance(offset => [0,0]);
$object->add_instance(
offset => [0,0],
rotation => $params{rotation} // 0,
);
return $model;
}
@ -46,7 +56,7 @@ sub init_print {
my $print = Slic3r::Print->new(config => $config);
$model_name = [$model_name] if ref($model_name) ne 'ARRAY';
$print->add_model(model($_)) for @$model_name;
$print->add_model(model($_, %params)) for @$model_name;
$print->validate;
return $print;

View File

@ -408,6 +408,11 @@ sub extents {
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->used_vertices);

View File

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

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

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__

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