Merge branch 'master' into overhang
Conflicts: lib/Slic3r/GCode.pm
This commit is contained in:
commit
07407e5dbd
53 changed files with 1737 additions and 615 deletions
4
Build.PL
4
Build.PL
|
@ -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',
|
||||
|
|
8
MANIFEST
8
MANIFEST
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 /^$/;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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};
|
||||
}
|
||||
|
|
|
@ -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) = @_;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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+).*?/) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
207
lib/Slic3r/GCode/Layer.pm
Normal 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;
|
|
@ -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) = @_;
|
||||
|
|
|
@ -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}) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
200
lib/Slic3r/GUI/PreviewCanvas.pm
Normal file
200
lib/Slic3r/GUI/PreviewCanvas.pm
Normal 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;
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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 = @_;
|
||||
|
||||
|
|
|
@ -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) = @_;
|
||||
|
|
87
lib/Slic3r/Geometry/BoundingBox.pm
Normal file
87
lib/Slic3r/Geometry/BoundingBox.pm
Normal 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;
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) = @_;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) = @_;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -9,7 +9,7 @@ use constant Y => 1;
|
|||
|
||||
our $filltype = 'evenodd';
|
||||
|
||||
sub factor {return 30;
|
||||
sub factor {
|
||||
return &Slic3r::SCALING_FACTOR * 10;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
21
slic3r.pl
21
slic3r.pl
|
@ -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
|
||||
|
|
7
t/arcs.t
7
t/arcs.t
|
@ -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
89
t/cooling.t
Normal 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__
|
11
t/fill.t
11
t/fill.t
|
@ -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__
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
12
t/geometry.t
12
t/geometry.t
|
@ -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
20
t/print.t
Normal 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__
|
25
t/shells.t
25
t/shells.t
|
@ -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
46
t/skirt_brim.t
Normal 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__
|
24
t/slice.t
24
t/slice.t
|
@ -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;
|
||||
|
|
18
t/support.t
18
t/support.t
|
@ -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
23
t/svg.t
Normal 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
34
utils/dump-stl.pl
Normal 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
67
utils/view-mesh.pl
Normal 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__
|
|
@ -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]' \
|
||||
|
|
Loading…
Reference in a new issue