Merge branch 'master' into boost-medialaxis
Conflicts: lib/Slic3r/Layer/Region.pm xs/src/ExPolygon.cpp xs/src/Point.cpp xs/src/Point.hpp xs/src/TriangleMesh.cpp xs/t/01_trianglemesh.t
This commit is contained in:
commit
eadffe4a9e
@ -7,3 +7,5 @@ perl:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- stable
|
||||
|
||||
|
10
Build.PL
10
Build.PL
@ -17,7 +17,6 @@ my %prereqs = qw(
|
||||
Module::Build::WithXSpp 0.14
|
||||
Moo 1.003001
|
||||
Scalar::Util 0
|
||||
Storable 0
|
||||
Test::Harness 0
|
||||
Test::More 0
|
||||
IO::Scalar 0
|
||||
@ -28,7 +27,8 @@ my %recommends = qw(
|
||||
XML::SAX::ExpatXS 0
|
||||
);
|
||||
|
||||
my $gui = defined $ARGV[0] && $ARGV[0] eq '--gui';
|
||||
my $gui = grep { $_ eq '--gui' } @ARGV;
|
||||
my $xs_only = grep { $_ eq '--xs' } @ARGV;
|
||||
if ($gui) {
|
||||
%prereqs = qw(
|
||||
Wx 0.9918
|
||||
@ -38,6 +38,8 @@ if ($gui) {
|
||||
Wx::GLCanvas 0
|
||||
OpenGL 0
|
||||
);
|
||||
} elsif ($xs_only) {
|
||||
%prereqs = %recommends = ();
|
||||
}
|
||||
|
||||
my @missing_prereqs = ();
|
||||
@ -124,9 +126,7 @@ EOF
|
||||
# with current perl binary
|
||||
if (-e './xs/Build') {
|
||||
if ($^O eq 'MSWin32') {
|
||||
system 'cd', 'xs';
|
||||
system 'Build', 'distclean';
|
||||
system 'cd', '..';
|
||||
system '.\xs\Build', 'distclean';
|
||||
} else {
|
||||
system './xs/Build', 'distclean';
|
||||
}
|
||||
|
@ -140,8 +140,12 @@ sub thread_cleanup {
|
||||
*Slic3r::ExtrusionLoop::DESTROY = sub {};
|
||||
*Slic3r::ExtrusionPath::DESTROY = sub {};
|
||||
*Slic3r::ExtrusionPath::Collection::DESTROY = sub {};
|
||||
*Slic3r::Flow::DESTROY = sub {};
|
||||
*Slic3r::Geometry::BoundingBox::DESTROY = sub {};
|
||||
*Slic3r::Geometry::BoundingBoxf3::DESTROY = sub {};
|
||||
*Slic3r::Line::DESTROY = sub {};
|
||||
*Slic3r::Point::DESTROY = sub {};
|
||||
*Slic3r::Pointf3::DESTROY = sub {};
|
||||
*Slic3r::Polygon::DESTROY = sub {};
|
||||
*Slic3r::Polyline::DESTROY = sub {};
|
||||
*Slic3r::Polyline::Collection::DESTROY = sub {};
|
||||
|
@ -3,11 +3,12 @@ use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use List::Util qw(first);
|
||||
use List::Util qw(first max);
|
||||
|
||||
# cemetery of old config settings
|
||||
our @Ignore = qw(duplicate_x duplicate_y multiply_x multiply_y support_material_tool acceleration
|
||||
adjust_overhang_flow standby_temperature scale rotate duplicate duplicate_grid);
|
||||
adjust_overhang_flow standby_temperature scale rotate duplicate duplicate_grid
|
||||
rotate scale duplicate_grid);
|
||||
|
||||
our $Options = print_config_def();
|
||||
|
||||
@ -106,7 +107,7 @@ sub _handle_legacy {
|
||||
my ($opt_key, $value) = @_;
|
||||
|
||||
# handle legacy options
|
||||
return ($opt_key, $value) if first { $_ eq $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/;
|
||||
@ -170,7 +171,7 @@ sub save {
|
||||
sub setenv {
|
||||
my $self = shift;
|
||||
|
||||
foreach my $opt_key (sort keys %$Options) {
|
||||
foreach my $opt_key (@{$self->get_keys}) {
|
||||
$ENV{"SLIC3R_" . uc $opt_key} = $self->serialize($opt_key);
|
||||
}
|
||||
}
|
||||
@ -319,6 +320,15 @@ sub validate {
|
||||
if defined first { $_ } @{ $self->retract_layer_change };
|
||||
}
|
||||
|
||||
# extrusion widths
|
||||
{
|
||||
my $max_nozzle_diameter = max(@{ $self->nozzle_diameter });
|
||||
die "Invalid extrusion width (too large)\n"
|
||||
if defined first { $_ > 10 * $max_nozzle_diameter }
|
||||
map $self->get_abs_value_over("${_}_extrusion_width", $self->layer_height),
|
||||
qw(perimeter infill solid_infill top_infill support_material first_layer);
|
||||
}
|
||||
|
||||
# general validation, quick and dirty
|
||||
foreach my $opt_key (@{$self->get_keys}) {
|
||||
my $opt = $Options->{$opt_key};
|
||||
@ -345,6 +355,8 @@ sub validate {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub replace_options {
|
||||
@ -369,13 +381,13 @@ sub replace_options {
|
||||
$string =~ s/\[version\]/$Slic3r::VERSION/eg;
|
||||
|
||||
# build a regexp to match the available options
|
||||
my @options = grep !$Slic3r::Config::Options->{$_}{multiline},
|
||||
grep $self->has($_),
|
||||
keys %{$Slic3r::Config::Options};
|
||||
my @options = grep !$Slic3r::Config::Options->{$_}{multiline}, @{$self->get_keys};
|
||||
my $options_regex = join '|', @options;
|
||||
|
||||
# use that regexp to search and replace option names with option values
|
||||
$string =~ s/\[($options_regex)\]/$self->serialize($1)/eg;
|
||||
# it looks like passing $1 as argument to serialize() directly causes a segfault
|
||||
# (maybe some perl optimization? maybe regex captures are not regular SVs?)
|
||||
$string =~ s/\[($options_regex)\]/my $opt_key = $1; $self->serialize($opt_key)/eg;
|
||||
foreach my $opt_key (grep ref $self->$_ eq 'ARRAY', @options) {
|
||||
my $value = $self->$opt_key;
|
||||
$string =~ s/\[${opt_key}_${_}\]/$value->[$_]/eg for 0 .. $#$value;
|
||||
|
@ -83,8 +83,8 @@ sub extrude {
|
||||
}
|
||||
|
||||
sub extruded_volume {
|
||||
my ($self) = @_;
|
||||
return $self->absolute_E * ($self->filament_diameter**2) * PI/4;
|
||||
my ($self, $E) = @_;
|
||||
return $E * ($self->filament_diameter**2) * PI/4;
|
||||
}
|
||||
|
||||
sub e_per_mm {
|
||||
|
@ -64,8 +64,54 @@ sub make_fill {
|
||||
{
|
||||
my @surfaces_with_bridge_angle = grep defined $_->bridge_angle, @{$layerm->fill_surfaces};
|
||||
|
||||
# group surfaces by distinct properties
|
||||
my @groups = @{$layerm->fill_surfaces->group};
|
||||
|
||||
# merge compatible groups (we can generate continuous infill for them)
|
||||
{
|
||||
# cache flow widths and patterns used for all solid groups
|
||||
# (we'll use them for comparing compatible groups)
|
||||
my @is_solid = my @fw = my @pattern = ();
|
||||
for (my $i = 0; $i <= $#groups; $i++) {
|
||||
# we can only merge solid non-bridge surfaces, so discard
|
||||
# non-solid surfaces
|
||||
if ($groups[$i][0]->is_solid && (!$groups[$i][0]->is_bridge || $layerm->id == 0)) {
|
||||
$is_solid[$i] = 1;
|
||||
$fw[$i] = ($groups[$i][0]->surface_type == S_TYPE_TOP)
|
||||
? $layerm->flow(FLOW_ROLE_TOP_SOLID_INFILL)->width
|
||||
: $solid_infill_flow->width;
|
||||
$pattern[$i] = $groups[$i][0]->is_external
|
||||
? $layerm->config->solid_fill_pattern
|
||||
: 'rectilinear';
|
||||
} else {
|
||||
$is_solid[$i] = 0;
|
||||
$fw[$i] = 0;
|
||||
$pattern[$i] = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
# loop through solid groups
|
||||
for (my $i = 0; $i <= $#groups; $i++) {
|
||||
next if !$is_solid[$i];
|
||||
|
||||
# find compatible groups and append them to this one
|
||||
for (my $j = $i+1; $j <= $#groups; $j++) {
|
||||
next if !$is_solid[$j];
|
||||
|
||||
if ($fw[$i] == $fw[$j] && $pattern[$i] eq $pattern[$j]) {
|
||||
# groups are compatible, merge them
|
||||
push @{$groups[$i]}, @{$groups[$j]};
|
||||
splice @groups, $j, 1;
|
||||
splice @is_solid, $j, 1;
|
||||
splice @fw, $j, 1;
|
||||
splice @pattern, $j, 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# give priority to bridges
|
||||
my @groups = sort { defined $a->[0]->bridge_angle ? -1 : 0 } @{$layerm->fill_surfaces->group(1)};
|
||||
@groups = sort { defined $a->[0]->bridge_angle ? -1 : 0 } @groups;
|
||||
|
||||
foreach my $group (@groups) {
|
||||
my $union_p = union([ map $_->p, @$group ], 1);
|
||||
|
@ -53,9 +53,10 @@ sub fill_surface {
|
||||
$last_pos = $paths[-1]->last_point;
|
||||
}
|
||||
|
||||
# clip the paths to avoid the extruder to get exactly on the first point of the loop
|
||||
# clip the paths to prevent the extruder from getting exactly on the first point of the loop
|
||||
my $clip_length = scale($flow->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER;
|
||||
$_->clip_end($clip_length) for @paths;
|
||||
@paths = grep $_->is_valid, @paths; # remove empty paths (too short, thus eaten by clipping)
|
||||
|
||||
# TODO: return ExtrusionLoop objects to get better chained paths
|
||||
return { flow => $flow, no_sort => 1 }, @paths;
|
||||
|
@ -118,7 +118,7 @@ sub fill_surface {
|
||||
# clip paths again to prevent connection segments from crossing the expolygon boundaries
|
||||
@paths = @{intersection_pl(
|
||||
\@paths,
|
||||
[ @{$surface->expolygon->offset_ex(scaled_epsilon)} ],
|
||||
[ map @$_, @{$surface->expolygon->offset_ex(scaled_epsilon)} ],
|
||||
)};
|
||||
}
|
||||
|
||||
|
@ -60,12 +60,12 @@ sub fill_surface {
|
||||
$x += $line_spacing;
|
||||
}
|
||||
|
||||
# clip paths against a slightly offsetted expolygon, so that the first and last paths
|
||||
# clip paths against a slightly larger expolygon, so that the first and last paths
|
||||
# are kept even if the expolygon has vertical sides
|
||||
# the minimum offset for preventing edge lines from being clipped is scaled_epsilon;
|
||||
# however we use a larger offset to support expolygons with slightly skewed sides and
|
||||
# not perfectly straight
|
||||
my @polylines = @{intersection_pl(\@vertical_lines, $expolygon->offset($line_spacing*0.05))};
|
||||
my @polylines = @{intersection_pl(\@vertical_lines, $expolygon->offset(scale 0.02))};
|
||||
|
||||
# connect lines
|
||||
unless ($params{dont_connect} || !@polylines) { # prevent calling leftmost_point() on empty collections
|
||||
|
@ -37,10 +37,14 @@ sub write_file {
|
||||
printf $fh qq{ <metadata type="cad">Slic3r %s</metadata>\n}, $Slic3r::VERSION;
|
||||
for my $material_id (sort keys %{ $model->materials }) {
|
||||
my $material = $model->materials->{$material_id};
|
||||
printf $fh qq{ <material id="%d">\n}, $material_id;
|
||||
printf $fh qq{ <material id="%s">\n}, $material_id;
|
||||
for (keys %{$material->attributes}) {
|
||||
printf $fh qq{ <metadata type=\"%s\">%s</metadata>\n}, $_, $material->attributes->{$_};
|
||||
}
|
||||
my $config = $material->config;
|
||||
foreach my $opt_key (@{$config->get_keys}) {
|
||||
printf $fh qq{ <metadata type=\"slic3r.%s\">%s</metadata>\n}, $opt_key, $config->serialize($opt_key);
|
||||
}
|
||||
printf $fh qq{ </material>\n};
|
||||
}
|
||||
my $instances = '';
|
||||
|
@ -1,6 +1,8 @@
|
||||
package Slic3r::Format::STL;
|
||||
use Moo;
|
||||
|
||||
use File::Basename qw(basename);
|
||||
|
||||
sub read_file {
|
||||
my $self = shift;
|
||||
my ($file) = @_;
|
||||
@ -10,8 +12,11 @@ sub read_file {
|
||||
$mesh->repair;
|
||||
|
||||
my $model = Slic3r::Model->new;
|
||||
|
||||
my $material_id = basename($file);
|
||||
$model->set_material($material_id);
|
||||
my $object = $model->add_object;
|
||||
my $volume = $object->add_volume(mesh => $mesh);
|
||||
my $volume = $object->add_volume(mesh => $mesh, material_id => $material_id);
|
||||
return $model;
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ has 'standby_points' => (is => 'rw');
|
||||
has 'enable_loop_clipping' => (is => 'rw', default => sub {1});
|
||||
has 'enable_wipe' => (is => 'rw', default => sub {0}); # at least one extruder has wipe enabled
|
||||
has 'layer_count' => (is => 'ro', required => 1 );
|
||||
has '_layer_index' => (is => 'rw', default => sub {-1}); # just a counter
|
||||
has 'layer' => (is => 'rw');
|
||||
has 'region' => (is => 'rw');
|
||||
has '_layer_islands' => (is => 'rw');
|
||||
@ -75,7 +76,7 @@ my %role_speeds = (
|
||||
&EXTR_ROLE_SOLIDFILL => 'solid_infill',
|
||||
&EXTR_ROLE_TOPSOLIDFILL => 'top_solid_infill',
|
||||
&EXTR_ROLE_BRIDGE => 'bridge',
|
||||
&EXTR_ROLE_INTERNALBRIDGE => 'solid_infill',
|
||||
&EXTR_ROLE_INTERNALBRIDGE => 'bridge',
|
||||
&EXTR_ROLE_SKIRT => 'perimeter',
|
||||
&EXTR_ROLE_GAPFILL => 'gap_fill',
|
||||
);
|
||||
@ -104,6 +105,7 @@ sub change_layer {
|
||||
my ($self, $layer) = @_;
|
||||
|
||||
$self->layer($layer);
|
||||
$self->_layer_index($self->_layer_index + 1);
|
||||
|
||||
# avoid computing islands and overhangs if they're not needed
|
||||
$self->_layer_islands($layer->islands);
|
||||
@ -124,7 +126,7 @@ sub change_layer {
|
||||
my $gcode = "";
|
||||
if ($self->print_config->gcode_flavor =~ /^(?:makerware|sailfish)$/) {
|
||||
$gcode .= sprintf "M73 P%s%s\n",
|
||||
int(99 * ($layer->id / ($self->layer_count - 1))),
|
||||
int(99 * ($self->_layer_index / ($self->layer_count - 1))),
|
||||
($self->print_config->gcode_comments ? ' ; update progress' : '');
|
||||
}
|
||||
if ($self->print_config->first_layer_acceleration) {
|
||||
|
@ -48,13 +48,16 @@ sub process_layer {
|
||||
my $object = $layer->object;
|
||||
|
||||
# check whether we're going to apply spiralvase logic
|
||||
my $spiralvase = defined $self->spiralvase
|
||||
&& ($layer->id > 0 || $self->print->config->brim_width == 0)
|
||||
&& ($layer->id >= $self->print->config->skirt_height && $self->print->config->skirt_height != -1)
|
||||
&& !defined(first { $_->config->bottom_solid_layers > $layer->id } @{$layer->regions});
|
||||
if (defined $self->spiralvase) {
|
||||
$self->spiralvase->enable(
|
||||
($layer->id > 0 || $self->print->config->brim_width == 0)
|
||||
&& ($layer->id >= $self->print->config->skirt_height && $self->print->config->skirt_height != -1)
|
||||
&& !defined(first { $_->config->bottom_solid_layers > $layer->id } @{$layer->regions})
|
||||
);
|
||||
}
|
||||
|
||||
# if we're going to apply spiralvase to this layer, disable loop clipping
|
||||
$self->gcodegen->enable_loop_clipping(!$spiralvase);
|
||||
$self->gcodegen->enable_loop_clipping(!defined $self->spiralvase || !$self->spiralvase->enable);
|
||||
|
||||
if (!$self->second_layer_things_done && $layer->id == 1) {
|
||||
for my $extruder_id (sort keys %{$self->extruders}) {
|
||||
@ -186,8 +189,10 @@ sub process_layer {
|
||||
}
|
||||
|
||||
# apply spiral vase post-processing if this layer contains suitable geometry
|
||||
$gcode = $self->spiralvase->process_layer($gcode, $layer)
|
||||
if $spiralvase;
|
||||
# (we must feed all the G-code into the post-processor, including the first
|
||||
# bottom non-spiral layers otherwise it will mess with positions)
|
||||
$gcode = $self->spiralvase->process_layer($gcode)
|
||||
if defined $self->spiralvase;
|
||||
|
||||
# apply vibration limit if enabled
|
||||
$gcode = $self->vibration_limit->process($gcode)
|
||||
|
@ -10,6 +10,13 @@ has 'F' => (is => 'rw', default => sub {0});
|
||||
our $Verbose = 0;
|
||||
my @AXES = qw(X Y Z E);
|
||||
|
||||
sub clone {
|
||||
my $self = shift;
|
||||
return (ref $self)->new(
|
||||
map { $_ => $self->$_ } (@AXES, 'F'),
|
||||
);
|
||||
}
|
||||
|
||||
sub parse {
|
||||
my $self = shift;
|
||||
my ($gcode, $cb) = @_;
|
||||
|
@ -2,43 +2,68 @@ package Slic3r::GCode::SpiralVase;
|
||||
use Moo;
|
||||
|
||||
has 'config' => (is => 'ro', required => 1);
|
||||
has 'enable' => (is => 'rw', default => sub { 0 });
|
||||
has 'reader' => (is => 'ro', default => sub { Slic3r::GCode::Reader->new });
|
||||
|
||||
use Slic3r::Geometry qw(unscale);
|
||||
|
||||
sub process_layer {
|
||||
my $self = shift;
|
||||
my ($gcode, $layer) = @_;
|
||||
my ($gcode) = @_;
|
||||
|
||||
# This post-processor relies on several assumptions:
|
||||
# - all layers are processed through it, including those that are not supposed
|
||||
# to be transformed, in order to update the reader with the XY positions
|
||||
# - each call to this method includes a full layer, with a single Z move
|
||||
# at the beginning
|
||||
# - each layer is composed by suitable geometry (i.e. a single complete loop)
|
||||
# - loops were not clipped before calling this method
|
||||
|
||||
# if we're not going to modify G-code, just feed it to the reader
|
||||
# in order to update positions
|
||||
if (!$self->enable) {
|
||||
$self->reader->parse($gcode, sub {});
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
# get total XY length for this layer by summing all extrusion moves
|
||||
my $total_layer_length = 0;
|
||||
Slic3r::GCode::Reader->new->parse($gcode, sub {
|
||||
my $layer_height = 0;
|
||||
my $z = undef;
|
||||
$self->reader->clone->parse($gcode, sub {
|
||||
my ($reader, $cmd, $args, $info) = @_;
|
||||
$total_layer_length += $info->{dist_XY}
|
||||
if $cmd eq 'G1' && $info->{extruding};
|
||||
|
||||
if ($cmd eq 'G1') {
|
||||
if ($info->{extruding}) {
|
||||
$total_layer_length += $info->{dist_XY};
|
||||
} elsif (exists $args->{Z}) {
|
||||
$layer_height += $info->{dist_Z};
|
||||
$z //= $args->{Z};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
#use XXX; YYY [ $gcode, $layer_height, $z, $total_layer_length ];
|
||||
# remove layer height from initial Z
|
||||
$z -= $layer_height;
|
||||
|
||||
my $new_gcode = "";
|
||||
my $layer_height = $layer->height;
|
||||
my $z = $layer->print_z + $self->config->z_offset - $layer_height;
|
||||
my $newlayer = 0;
|
||||
Slic3r::GCode::Reader->new->parse($gcode, sub {
|
||||
$self->reader->parse($gcode, sub {
|
||||
my ($reader, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd eq 'G1' && exists $args->{Z}) {
|
||||
# if this is the initial Z move of the layer, replace it with a
|
||||
# (redundant) move to the last Z of previous layer
|
||||
my $line = $info->{raw};
|
||||
$line =~ s/Z([^ ]+)/Z$z/;
|
||||
$line =~ s/ Z[.0-9]+/ Z$z/;
|
||||
$new_gcode .= "$line\n";
|
||||
$newlayer = 1;
|
||||
} elsif ($cmd eq 'G1' && !exists $args->{Z} && $info->{dist_XY}) {
|
||||
# horizontal move
|
||||
my $line = $info->{raw};
|
||||
if ($info->{extruding}) {
|
||||
$z += $info->{dist_XY} * $layer_height / $total_layer_length;
|
||||
$line =~ s/^G1 /sprintf 'G1 Z%.3f ', $z/e;
|
||||
$new_gcode .= "$line\n";
|
||||
} elsif ($newlayer) {
|
||||
# remove the first travel move after layer change; extrusion
|
||||
# will just blend to the first loop vertex
|
||||
# TODO: should we adjust (stretch) E for the first loop segment?
|
||||
$newlayer = 0;
|
||||
} else {
|
||||
$new_gcode .= "$line\n";
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ use Slic3r::GUI::Plater;
|
||||
use Slic3r::GUI::Plater::ObjectPartsPanel;
|
||||
use Slic3r::GUI::Plater::ObjectPreviewDialog;
|
||||
use Slic3r::GUI::Plater::ObjectSettingsDialog;
|
||||
use Slic3r::GUI::Plater::OverrideSettingsPanel;
|
||||
use Slic3r::GUI::Preferences;
|
||||
use Slic3r::GUI::OptionsGroup;
|
||||
use Slic3r::GUI::SkeinPanel;
|
||||
@ -18,7 +19,8 @@ use Slic3r::GUI::Tab;
|
||||
|
||||
our $have_OpenGL = eval "use Slic3r::GUI::PreviewCanvas; 1";
|
||||
|
||||
use Wx 0.9901 qw(:bitmap :dialog :frame :icon :id :misc :systemsettings :toplevelwindow);
|
||||
use Wx 0.9901 qw(:bitmap :dialog :frame :icon :id :misc :systemsettings :toplevelwindow
|
||||
:filedialog);
|
||||
use Wx::Event qw(EVT_CLOSE EVT_MENU EVT_IDLE);
|
||||
use base 'Wx::App';
|
||||
|
||||
@ -349,6 +351,25 @@ sub output_path {
|
||||
: $dir;
|
||||
}
|
||||
|
||||
sub open_model {
|
||||
my ($self) = @_;
|
||||
|
||||
my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory}
|
||||
|| $Slic3r::GUI::Settings->{recent}{config_directory}
|
||||
|| '';
|
||||
|
||||
my $dialog = Wx::FileDialog->new($self, 'Choose one or more files (STL/OBJ/AMF):', $dir, "",
|
||||
&Slic3r::GUI::SkeinPanel::MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
|
||||
if ($dialog->ShowModal != wxID_OK) {
|
||||
$dialog->Destroy;
|
||||
return;
|
||||
}
|
||||
my @input_files = $dialog->GetPaths;
|
||||
$dialog->Destroy;
|
||||
|
||||
return @input_files;
|
||||
}
|
||||
|
||||
sub CallAfter {
|
||||
my $class = shift;
|
||||
my ($cb) = @_;
|
||||
|
@ -361,14 +361,7 @@ sub filament_presets {
|
||||
sub add {
|
||||
my $self = shift;
|
||||
|
||||
my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || '';
|
||||
my $dialog = Wx::FileDialog->new($self, 'Choose one or more files (STL/OBJ/AMF):', $dir, "", &Slic3r::GUI::SkeinPanel::MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
|
||||
if ($dialog->ShowModal != wxID_OK) {
|
||||
$dialog->Destroy;
|
||||
return;
|
||||
}
|
||||
my @input_files = $dialog->GetPaths;
|
||||
$dialog->Destroy;
|
||||
my @input_files = Slic3r::GUI::open_model($self);
|
||||
$self->load_file($_) for @input_files;
|
||||
}
|
||||
|
||||
@ -640,6 +633,8 @@ sub split_object {
|
||||
my $new_model = Slic3r::Model->new;
|
||||
|
||||
foreach my $mesh (@new_meshes) {
|
||||
$mesh->repair;
|
||||
|
||||
my $model_object = $new_model->add_object(
|
||||
input_file => $current_model_object->input_file,
|
||||
config => $current_model_object->config->clone,
|
||||
@ -917,6 +912,8 @@ sub update {
|
||||
$print_object->delete_all_copies;
|
||||
$print_object->add_copy(@{$_->offset}) for @{$model_object->instances};
|
||||
}
|
||||
|
||||
$self->{canvas}->Refresh;
|
||||
}
|
||||
|
||||
sub on_config_change {
|
||||
@ -1055,7 +1052,6 @@ sub repaint {
|
||||
if (@{$parent->{objects}} && $parent->{config}->skirts) {
|
||||
my @points = map @{$_->contour}, map @$_, map @{$_->instance_thumbnails}, @{$parent->{objects}};
|
||||
if (@points >= 3) {
|
||||
my @o = @{Slic3r::Geometry::Clipper::simplify_polygons([convex_hull(\@points)])};
|
||||
my ($convex_hull) = @{offset([convex_hull(\@points)], scale($parent->{config}->skirt_distance), 1, JT_ROUND, scale(0.1))};
|
||||
$dc->SetPen($parent->{skirt_pen});
|
||||
$dc->SetBrush($parent->{transparent_brush});
|
||||
|
@ -3,8 +3,9 @@ use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Wx qw(:misc :sizer :treectrl wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG);
|
||||
use Wx::Event qw(EVT_BUTTON EVT_TREE_ITEM_COLLAPSING);
|
||||
use File::Basename qw(basename);
|
||||
use Wx qw(:misc :sizer :treectrl :button wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG);
|
||||
use Wx::Event qw(EVT_BUTTON EVT_TREE_ITEM_COLLAPSING EVT_TREE_SEL_CHANGED);
|
||||
use base 'Wx::Panel';
|
||||
|
||||
use constant ICON_MATERIAL => 0;
|
||||
@ -18,12 +19,10 @@ sub new {
|
||||
|
||||
my $object = $self->{model_object} = $params{model_object};
|
||||
|
||||
$self->{sizer} = Wx::BoxSizer->new(wxVERTICAL);
|
||||
|
||||
# create TreeCtrl
|
||||
my $tree = $self->{tree} = Wx::TreeCtrl->new($self, -1, wxDefaultPosition, [-1, 200],
|
||||
my $tree = $self->{tree} = Wx::TreeCtrl->new($self, -1, wxDefaultPosition, [300, 100],
|
||||
wxTR_NO_BUTTONS | wxSUNKEN_BORDER | wxTR_HAS_VARIABLE_ROW_HEIGHT | wxTR_HIDE_ROOT
|
||||
| wxTR_MULTIPLE | wxTR_NO_BUTTONS);
|
||||
| wxTR_MULTIPLE | wxTR_NO_BUTTONS | wxTR_NO_LINES);
|
||||
{
|
||||
$self->{tree_icons} = Wx::ImageList->new(16, 16, 1);
|
||||
$tree->AssignImageList($self->{tree_icons});
|
||||
@ -31,31 +30,193 @@ sub new {
|
||||
$self->{tree_icons}->Add(Wx::Bitmap->new("$Slic3r::var/package.png", wxBITMAP_TYPE_PNG));
|
||||
$self->{tree_icons}->Add(Wx::Bitmap->new("$Slic3r::var/package_green.png", wxBITMAP_TYPE_PNG));
|
||||
|
||||
my $rootId = $tree->AddRoot("");
|
||||
my %nodes = (); # material_id => nodeId
|
||||
foreach my $volume (@{$object->volumes}) {
|
||||
my $material_id = $volume->material_id;
|
||||
$material_id //= '_';
|
||||
|
||||
if (!exists $nodes{$material_id}) {
|
||||
$nodes{$material_id} = $tree->AppendItem($rootId, $object->model->get_material_name($material_id), ICON_MATERIAL);
|
||||
}
|
||||
my $name = $volume->modifier ? 'Modifier mesh' : 'Solid mesh';
|
||||
my $icon = $volume->modifier ? ICON_MODIFIERMESH : ICON_SOLIDMESH;
|
||||
$tree->AppendItem($nodes{$material_id}, $name, $icon);
|
||||
}
|
||||
$tree->ExpandAll;
|
||||
$tree->AddRoot("");
|
||||
$self->reload_tree;
|
||||
}
|
||||
|
||||
# buttons
|
||||
$self->{btn_load_part} = Wx::Button->new($self, -1, "Load part…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
|
||||
$self->{btn_load_modifier} = Wx::Button->new($self, -1, "Load modifier…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
|
||||
$self->{btn_delete} = Wx::Button->new($self, -1, "Delete part", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
|
||||
if ($Slic3r::GUI::have_button_icons) {
|
||||
$self->{btn_load_part}->SetBitmap(Wx::Bitmap->new("$Slic3r::var/brick_add.png", wxBITMAP_TYPE_PNG));
|
||||
$self->{btn_load_modifier}->SetBitmap(Wx::Bitmap->new("$Slic3r::var/brick_add.png", wxBITMAP_TYPE_PNG));
|
||||
$self->{btn_delete}->SetBitmap(Wx::Bitmap->new("$Slic3r::var/brick_delete.png", wxBITMAP_TYPE_PNG));
|
||||
}
|
||||
|
||||
# buttons sizer
|
||||
my $buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$buttons_sizer->Add($self->{btn_load_part}, 0);
|
||||
$buttons_sizer->Add($self->{btn_load_modifier}, 0);
|
||||
$buttons_sizer->Add($self->{btn_delete}, 0);
|
||||
$self->{btn_load_part}->SetFont($Slic3r::GUI::small_font);
|
||||
$self->{btn_load_modifier}->SetFont($Slic3r::GUI::small_font);
|
||||
$self->{btn_delete}->SetFont($Slic3r::GUI::small_font);
|
||||
|
||||
# part settings panel
|
||||
$self->{settings_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new(
|
||||
$self,
|
||||
opt_keys => Slic3r::Config::PrintRegion->new->get_keys,
|
||||
);
|
||||
my $settings_sizer = Wx::StaticBoxSizer->new(Wx::StaticBox->new($self, -1, "Part Settings"), wxVERTICAL);
|
||||
$settings_sizer->Add($self->{settings_panel}, 1, wxEXPAND | wxALL, 0);
|
||||
|
||||
# left pane with tree
|
||||
my $left_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$left_sizer->Add($tree, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10);
|
||||
$left_sizer->Add($buttons_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10);
|
||||
$left_sizer->Add($settings_sizer, 1, wxEXPAND | wxALL, 0);
|
||||
|
||||
# right pane with preview canvas
|
||||
my $canvas;
|
||||
if ($Slic3r::GUI::have_OpenGL) {
|
||||
$canvas = $self->{canvas} = Slic3r::GUI::PreviewCanvas->new($self, $self->{model_object});
|
||||
$canvas->SetSize([500,500]);
|
||||
}
|
||||
|
||||
$self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$self->{sizer}->Add($left_sizer, 0, wxEXPAND | wxALL, 0);
|
||||
$self->{sizer}->Add($canvas, 1, wxEXPAND | wxALL, 0) if $canvas;
|
||||
|
||||
$self->SetSizer($self->{sizer});
|
||||
$self->{sizer}->SetSizeHints($self);
|
||||
|
||||
# attach events
|
||||
EVT_TREE_ITEM_COLLAPSING($self, $tree, sub {
|
||||
my ($self, $event) = @_;
|
||||
$event->Veto;
|
||||
});
|
||||
EVT_TREE_SEL_CHANGED($self, $tree, sub {
|
||||
my ($self, $event) = @_;
|
||||
$self->selection_changed;
|
||||
});
|
||||
EVT_BUTTON($self, $self->{btn_load_part}, sub { $self->on_btn_load(0) });
|
||||
EVT_BUTTON($self, $self->{btn_load_modifier}, sub { $self->on_btn_load(1) });
|
||||
EVT_BUTTON($self, $self->{btn_delete}, \&on_btn_delete);
|
||||
|
||||
$self->{sizer}->Add($tree, 0, wxEXPAND | wxALL, 10);
|
||||
$self->selection_changed;
|
||||
|
||||
$self->SetSizer($self->{sizer});
|
||||
$self->{sizer}->SetSizeHints($self);
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub reload_tree {
|
||||
my ($self) = @_;
|
||||
|
||||
my $object = $self->{model_object};
|
||||
my $tree = $self->{tree};
|
||||
my $rootId = $tree->GetRootItem;
|
||||
|
||||
$tree->DeleteChildren($rootId);
|
||||
|
||||
foreach my $volume_id (0..$#{$object->volumes}) {
|
||||
my $volume = $object->volumes->[$volume_id];
|
||||
|
||||
my $material_id = $volume->material_id // '_';
|
||||
my $material_name = $material_id eq '_'
|
||||
? sprintf("Part #%d", $volume_id+1)
|
||||
: $object->model->get_material_name($material_id);
|
||||
|
||||
my $icon = $volume->modifier ? ICON_MODIFIERMESH : ICON_SOLIDMESH;
|
||||
my $itemId = $tree->AppendItem($rootId, $material_name, $icon);
|
||||
$tree->SetPlData($itemId, {
|
||||
type => 'volume',
|
||||
volume_id => $volume_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
sub get_selection {
|
||||
my ($self) = @_;
|
||||
|
||||
my $nodeId = $self->{tree}->GetSelection;
|
||||
if ($nodeId->IsOk) {
|
||||
return $self->{tree}->GetPlData($nodeId);
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub selection_changed {
|
||||
my ($self) = @_;
|
||||
|
||||
# deselect all meshes
|
||||
if ($self->{canvas}) {
|
||||
$_->{selected} = 0 for @{$self->{canvas}->volumes};
|
||||
}
|
||||
|
||||
# disable things as if nothing is selected
|
||||
$self->{btn_delete}->Disable;
|
||||
$self->{settings_panel}->Disable;
|
||||
|
||||
my $itemData = $self->get_selection;
|
||||
if ($itemData && $itemData->{type} eq 'volume') {
|
||||
if ($self->{canvas}) {
|
||||
$self->{canvas}->volumes->[ $itemData->{volume_id} ]{selected} = 1;
|
||||
}
|
||||
$self->{btn_delete}->Enable;
|
||||
|
||||
my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ];
|
||||
my $material = $self->{model_object}->model->materials->{ $volume->material_id // '_' };
|
||||
$material //= $volume->assign_unique_material;
|
||||
$self->{settings_panel}->Enable;
|
||||
$self->{settings_panel}->set_config($material->config);
|
||||
}
|
||||
|
||||
$self->{canvas}->Render if $self->{canvas};
|
||||
}
|
||||
|
||||
sub on_btn_load {
|
||||
my ($self, $is_modifier) = @_;
|
||||
|
||||
my @input_files = Slic3r::GUI::open_model($self);
|
||||
foreach my $input_file (@input_files) {
|
||||
my $model = eval { Slic3r::Model->read_from_file($input_file) };
|
||||
if ($@) {
|
||||
Slic3r::GUI::show_error($self, $@);
|
||||
next;
|
||||
}
|
||||
|
||||
foreach my $object (@{$model->objects}) {
|
||||
foreach my $volume (@{$object->volumes}) {
|
||||
my $new_volume = $self->{model_object}->add_volume($volume);
|
||||
$new_volume->modifier($is_modifier);
|
||||
if (!defined $new_volume->material_id) {
|
||||
my $material_name = basename($input_file);
|
||||
$material_name =~ s/\.(stl|obj)$//i;
|
||||
$self->{model_object}->model->set_material($material_name);
|
||||
$new_volume->material_id($material_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$self->reload_tree;
|
||||
if ($self->{canvas}) {
|
||||
$self->{canvas}->load_object($self->{model_object});
|
||||
$self->{canvas}->Render;
|
||||
}
|
||||
}
|
||||
|
||||
sub on_btn_delete {
|
||||
my ($self) = @_;
|
||||
|
||||
my $itemData = $self->get_selection;
|
||||
if ($itemData && $itemData->{type} eq 'volume') {
|
||||
my $volume = $self->{model_object}->volumes->[$itemData->{volume_id}];
|
||||
|
||||
# if user is deleting the last solid part, throw error
|
||||
if (!$volume->modifier && scalar(grep !$_->modifier, @{$self->{model_object}->volumes}) == 1) {
|
||||
Slic3r::GUI::show_error($self, "You can't delete the last solid part from this object.");
|
||||
return;
|
||||
}
|
||||
|
||||
$self->{model_object}->delete_volume($itemData->{volume_id});
|
||||
}
|
||||
|
||||
$self->reload_tree;
|
||||
if ($self->{canvas}) {
|
||||
$self->{canvas}->load_object($self->{model_object});
|
||||
$self->{canvas}->Render;
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
@ -10,14 +10,13 @@ use base 'Wx::Dialog';
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, %params) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, "Settings for " . $params{object}->name, wxDefaultPosition, [500,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
|
||||
my $self = $class->SUPER::new($parent, -1, "Settings for " . $params{object}->name, wxDefaultPosition, [700,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
|
||||
$self->{$_} = $params{$_} for keys %params;
|
||||
|
||||
$self->{tabpanel} = Wx::Notebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL);
|
||||
$self->{tabpanel}->AddPage($self->{settings} = Slic3r::GUI::Plater::ObjectDialog::SettingsTab->new($self->{tabpanel}), "Settings");
|
||||
$self->{tabpanel}->AddPage($self->{layers} = Slic3r::GUI::Plater::ObjectDialog::LayersTab->new($self->{tabpanel}), "Layers");
|
||||
$self->{tabpanel}->AddPage($self->{parts} = Slic3r::GUI::Plater::ObjectPartsPanel->new($self->{tabpanel}, model_object => $params{model_object}), "Parts");
|
||||
$self->{tabpanel}->AddPage($self->{materials} = Slic3r::GUI::Plater::ObjectDialog::MaterialsTab->new($self->{tabpanel}), "Materials");
|
||||
|
||||
my $buttons = $self->CreateStdDialogButtonSizer(wxOK);
|
||||
EVT_BUTTON($self, wxID_OK, sub {
|
||||
@ -27,7 +26,6 @@ sub new {
|
||||
|
||||
# notify tabs
|
||||
$self->{layers}->Closing;
|
||||
$self->{materials}->Closing;
|
||||
|
||||
$self->EndModal(wxID_OK);
|
||||
$self->Destroy;
|
||||
@ -72,35 +70,12 @@ sub new {
|
||||
$self->{sizer}->Add($label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10);
|
||||
}
|
||||
|
||||
# option selector
|
||||
{
|
||||
# get all options with object scope and sort them by category+label
|
||||
my %settings = map { $_ => sprintf('%s > %s', $Slic3r::Config::Options->{$_}{category}, $Slic3r::Config::Options->{$_}{full_label} // $Slic3r::Config::Options->{$_}{label}) }
|
||||
grep { ($Slic3r::Config::Options->{$_}{scope} // '') eq 'object' }
|
||||
keys %$Slic3r::Config::Options;
|
||||
$self->{options} = [ sort { $settings{$a} cmp $settings{$b} } keys %settings ];
|
||||
my $choice = Wx::Choice->new($self, -1, wxDefaultPosition, [150, -1], [ map $settings{$_}, @{$self->{options}} ]);
|
||||
|
||||
# create the button
|
||||
my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new("$Slic3r::var/add.png", wxBITMAP_TYPE_PNG));
|
||||
EVT_BUTTON($self, $btn, sub {
|
||||
my $idx = $choice->GetSelection;
|
||||
return if $idx == -1; # lack of selected item, can happen on Windows
|
||||
my $opt_key = $self->{options}[$idx];
|
||||
$self->model_object->config->apply(Slic3r::Config->new_from_defaults($opt_key));
|
||||
$self->update_optgroup;
|
||||
});
|
||||
|
||||
my $h_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$h_sizer->Add($choice, 1, wxEXPAND | wxALL, 0);
|
||||
$h_sizer->Add($btn, 0, wxEXPAND | wxLEFT, 10);
|
||||
$self->{sizer}->Add($h_sizer, 0, wxEXPAND | wxALL, 10);
|
||||
}
|
||||
|
||||
$self->{options_sizer} = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$self->{sizer}->Add($self->{options_sizer}, 0, wxEXPAND | wxALL, 10);
|
||||
|
||||
$self->update_optgroup;
|
||||
$self->{settings_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new(
|
||||
$self,
|
||||
config => $self->model_object->config,
|
||||
opt_keys => [ map @{$_->get_keys}, Slic3r::Config::PrintObject->new, Slic3r::Config::PrintRegion->new ],
|
||||
);
|
||||
$self->{sizer}->Add($self->{settings_panel}, 1, wxEXPAND | wxLEFT | wxRIGHT, 10);
|
||||
|
||||
$self->SetSizer($self->{sizer});
|
||||
$self->{sizer}->SetSizeHints($self);
|
||||
@ -108,41 +83,6 @@ sub new {
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub update_optgroup {
|
||||
my $self = shift;
|
||||
|
||||
$self->{options_sizer}->Clear(1);
|
||||
|
||||
my $config = $self->model_object->config;
|
||||
my %categories = ();
|
||||
foreach my $opt_key (@{$config->get_keys}) {
|
||||
my $category = $Slic3r::Config::Options->{$opt_key}{category};
|
||||
$categories{$category} ||= [];
|
||||
push @{$categories{$category}}, $opt_key;
|
||||
}
|
||||
foreach my $category (sort keys %categories) {
|
||||
my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
|
||||
parent => $self,
|
||||
title => $category,
|
||||
config => $config,
|
||||
options => [ sort @{$categories{$category}} ],
|
||||
full_labels => 1,
|
||||
extra_column => sub {
|
||||
my ($line) = @_;
|
||||
my ($opt_key) = @{$line->{options}}; # we assume that we have one option per line
|
||||
my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new("$Slic3r::var/delete.png", wxBITMAP_TYPE_PNG));
|
||||
EVT_BUTTON($self, $btn, sub {
|
||||
delete $self->model_object->config->{$opt_key};
|
||||
Slic3r::GUI->CallAfter(sub { $self->update_optgroup });
|
||||
});
|
||||
return $btn;
|
||||
},
|
||||
);
|
||||
$self->{options_sizer}->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM, 10);
|
||||
}
|
||||
$self->Layout;
|
||||
}
|
||||
|
||||
sub CanClose {
|
||||
my $self = shift;
|
||||
|
||||
@ -264,83 +204,4 @@ sub _get_ranges {
|
||||
return sort { $a->[0] <=> $b->[0] } @ranges;
|
||||
}
|
||||
|
||||
package Slic3r::GUI::Plater::ObjectDialog::MaterialsTab;
|
||||
use Wx qw(:dialog :id :misc :sizer :systemsettings :button :icon);
|
||||
use Wx::Grid;
|
||||
use Wx::Event qw(EVT_BUTTON);
|
||||
use base 'Slic3r::GUI::Plater::ObjectDialog::BaseTab';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, %params) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize);
|
||||
$self->{object} = $params{object};
|
||||
|
||||
$self->{sizer} = Wx::BoxSizer->new(wxVERTICAL);
|
||||
|
||||
# descriptive text
|
||||
{
|
||||
my $label = Wx::StaticText->new($self, -1, "In this section you can assign object materials to your extruders.",
|
||||
wxDefaultPosition, [-1, 25]);
|
||||
$label->SetFont(Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
|
||||
$self->{sizer}->Add($label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10);
|
||||
}
|
||||
|
||||
# get unique materials used in this object
|
||||
$self->{materials} = [ $self->model_object->unique_materials ];
|
||||
|
||||
# get the current mapping
|
||||
$self->{mapping} = {};
|
||||
foreach my $material_id (@{ $self->{materials}}) {
|
||||
my $config = $self->model_object->model->materials->{ $material_id }->config;
|
||||
$self->{mapping}{$material_id} = ($config->perimeter_extruder // 0) + 1;
|
||||
}
|
||||
|
||||
if (@{$self->{materials}} > 0) {
|
||||
# build an OptionsGroup
|
||||
my $optgroup = Slic3r::GUI::OptionsGroup->new(
|
||||
parent => $self,
|
||||
title => 'Extruders',
|
||||
label_width => 300,
|
||||
options => [
|
||||
map {
|
||||
my $i = $_;
|
||||
my $material_id = $self->{materials}[$i];
|
||||
{
|
||||
opt_key => "material_extruder_$_",
|
||||
type => 'i',
|
||||
label => $self->model_object->model->get_material_name($material_id),
|
||||
min => 1,
|
||||
default => $self->{mapping}{$material_id} // 1,
|
||||
on_change => sub { $self->{mapping}{$material_id} = $_[0] },
|
||||
}
|
||||
} 0..$#{ $self->{materials} }
|
||||
],
|
||||
);
|
||||
$self->{sizer}->Add($optgroup->sizer, 0, wxEXPAND | wxALL, 10);
|
||||
} else {
|
||||
my $label = Wx::StaticText->new($self, -1, "This object does not contain named materials.",
|
||||
wxDefaultPosition, [-1, 25]);
|
||||
$label->SetFont(Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
|
||||
$self->{sizer}->Add($label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10);
|
||||
}
|
||||
|
||||
$self->SetSizer($self->{sizer});
|
||||
$self->{sizer}->SetSizeHints($self);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub Closing {
|
||||
my $self = shift;
|
||||
|
||||
# save mappings into the plater object
|
||||
foreach my $volume (@{$self->model_object->volumes}) {
|
||||
if (defined $volume->material_id) {
|
||||
my $config = $self->model_object->model->materials->{ $volume->material_id }->config;
|
||||
$config->set('extruder', $self->{mapping}{ $volume->material_id }-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
99
lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm
Normal file
99
lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm
Normal file
@ -0,0 +1,99 @@
|
||||
package Slic3r::GUI::Plater::OverrideSettingsPanel;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use File::Basename qw(basename);
|
||||
use Wx qw(:misc :sizer :button wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG);
|
||||
use Wx::Event qw(EVT_BUTTON);
|
||||
use base 'Wx::ScrolledWindow';
|
||||
|
||||
use constant ICON_MATERIAL => 0;
|
||||
use constant ICON_SOLIDMESH => 1;
|
||||
use constant ICON_MODIFIERMESH => 2;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, %params) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
$self->{config} = $params{config}; # may be passed as undef
|
||||
my @opt_keys = @{$params{opt_keys}};
|
||||
|
||||
$self->{sizer} = Wx::BoxSizer->new(wxVERTICAL);
|
||||
|
||||
# option selector
|
||||
{
|
||||
# get all options with object scope and sort them by category+label
|
||||
my %settings = map { $_ => sprintf('%s > %s', $Slic3r::Config::Options->{$_}{category}, $Slic3r::Config::Options->{$_}{full_label} // $Slic3r::Config::Options->{$_}{label}) } @opt_keys;
|
||||
$self->{options} = [ sort { $settings{$a} cmp $settings{$b} } keys %settings ];
|
||||
my $choice = Wx::Choice->new($self, -1, wxDefaultPosition, [150, -1], [ map $settings{$_}, @{$self->{options}} ]);
|
||||
|
||||
# create the button
|
||||
my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new("$Slic3r::var/add.png", wxBITMAP_TYPE_PNG));
|
||||
EVT_BUTTON($self, $btn, sub {
|
||||
my $idx = $choice->GetSelection;
|
||||
return if $idx == -1; # lack of selected item, can happen on Windows
|
||||
my $opt_key = $self->{options}[$idx];
|
||||
$self->{config}->apply(Slic3r::Config->new_from_defaults($opt_key));
|
||||
$self->update_optgroup;
|
||||
});
|
||||
|
||||
my $h_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$h_sizer->Add($choice, 1, wxEXPAND | wxALL, 0);
|
||||
$h_sizer->Add($btn, 0, wxEXPAND | wxLEFT, 10);
|
||||
$self->{sizer}->Add($h_sizer, 0, wxEXPAND | wxBOTTOM, 10);
|
||||
}
|
||||
|
||||
$self->{options_sizer} = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$self->{sizer}->Add($self->{options_sizer}, 0, wxEXPAND | wxALL, 0);
|
||||
|
||||
$self->SetSizer($self->{sizer});
|
||||
$self->SetScrollbars(0, 1, 0, 1);
|
||||
|
||||
$self->update_optgroup;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub set_config {
|
||||
my ($self, $config) = @_;
|
||||
$self->{config} = $config;
|
||||
$self->update_optgroup;
|
||||
}
|
||||
|
||||
sub update_optgroup {
|
||||
my $self = shift;
|
||||
|
||||
$self->{options_sizer}->Clear(1);
|
||||
return if !defined $self->{config};
|
||||
|
||||
my %categories = ();
|
||||
foreach my $opt_key (@{$self->{config}->get_keys}) {
|
||||
my $category = $Slic3r::Config::Options->{$opt_key}{category};
|
||||
$categories{$category} ||= [];
|
||||
push @{$categories{$category}}, $opt_key;
|
||||
}
|
||||
foreach my $category (sort keys %categories) {
|
||||
my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
|
||||
parent => $self,
|
||||
title => $category,
|
||||
config => $self->{config},
|
||||
options => [ sort @{$categories{$category}} ],
|
||||
full_labels => 1,
|
||||
extra_column => sub {
|
||||
my ($line) = @_;
|
||||
my ($opt_key) = @{$line->{options}}; # we assume that we have one option per line
|
||||
my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new("$Slic3r::var/delete.png", wxBITMAP_TYPE_PNG));
|
||||
EVT_BUTTON($self, $btn, sub {
|
||||
$self->{config}->erase($opt_key);
|
||||
Slic3r::GUI->CallAfter(sub { $self->update_optgroup });
|
||||
});
|
||||
return $btn;
|
||||
},
|
||||
);
|
||||
$self->{options_sizer}->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM, 10);
|
||||
}
|
||||
$self->Layout;
|
||||
}
|
||||
|
||||
1;
|
@ -18,6 +18,7 @@ __PACKAGE__->mk_accessors( qw(quat dirty init mview_init
|
||||
|
||||
use constant TRACKBALLSIZE => 0.8;
|
||||
use constant TURNTABLE_MODE => 1;
|
||||
use constant SELECTED_COLOR => [0,1,0,1];
|
||||
use constant COLORS => [ [1,1,1], [1,0.5,0.5], [0.5,1,0.5], [0.5,0.5,1] ];
|
||||
|
||||
sub new {
|
||||
@ -28,40 +29,7 @@ sub new {
|
||||
$self->sphi(45);
|
||||
$self->stheta(-45);
|
||||
|
||||
my $bb = $object->raw_mesh->bounding_box;
|
||||
my $center = $bb->center;
|
||||
$self->object_shift(Slic3r::Pointf3->new(-$center->x, -$center->y, -$bb->z_min)); #,,
|
||||
$bb->translate(@{ $self->object_shift });
|
||||
$self->object_bounding_box($bb);
|
||||
|
||||
# group mesh(es) by material
|
||||
my @materials = ();
|
||||
$self->volumes([]);
|
||||
foreach my $volume (@{$object->volumes}) {
|
||||
my $mesh = $volume->mesh->clone;
|
||||
$mesh->translate(@{ $self->object_shift });
|
||||
|
||||
my $material_id = $volume->material_id // '_';
|
||||
my $color_idx = first { $materials[$_] eq $material_id } 0..$#materials;
|
||||
if (!defined $color_idx) {
|
||||
push @materials, $material_id;
|
||||
$color_idx = $#materials;
|
||||
}
|
||||
push @{$self->volumes}, my $v = {
|
||||
color => COLORS->[ $color_idx % scalar(@{&COLORS}) ],
|
||||
};
|
||||
|
||||
{
|
||||
my $vertices = $mesh->vertices;
|
||||
my @verts = map @{ $vertices->[$_] }, map @$_, @{$mesh->facets};
|
||||
$v->{verts} = OpenGL::Array->new_list(GL_FLOAT, @verts);
|
||||
}
|
||||
|
||||
{
|
||||
my @norms = map { @$_, @$_, @$_ } @{$mesh->normals};
|
||||
$v->{norms} = OpenGL::Array->new_list(GL_FLOAT, @norms);
|
||||
}
|
||||
}
|
||||
$self->load_object($object);
|
||||
|
||||
EVT_PAINT($self, sub {
|
||||
my $dc = Wx::PaintDC->new($self);
|
||||
@ -101,6 +69,51 @@ sub new {
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub load_object {
|
||||
my ($self, $object) = @_;
|
||||
|
||||
my $bb = $object->raw_mesh->bounding_box;
|
||||
my $center = $bb->center;
|
||||
$self->object_shift(Slic3r::Pointf3->new(-$center->x, -$center->y, -$bb->z_min)); #,,
|
||||
$bb->translate(@{ $self->object_shift });
|
||||
$self->object_bounding_box($bb);
|
||||
|
||||
# group mesh(es) by material
|
||||
my @materials = ();
|
||||
$self->volumes([]);
|
||||
|
||||
# sort volumes: non-modifiers first
|
||||
my @volumes = sort { ($a->modifier // 0) <=> ($b->modifier // 0) } @{$object->volumes};
|
||||
foreach my $volume (@volumes) {
|
||||
my $mesh = $volume->mesh->clone;
|
||||
$mesh->translate(@{ $self->object_shift });
|
||||
|
||||
my $material_id = $volume->material_id // '_';
|
||||
my $color_idx = first { $materials[$_] eq $material_id } 0..$#materials;
|
||||
if (!defined $color_idx) {
|
||||
push @materials, $material_id;
|
||||
$color_idx = $#materials;
|
||||
}
|
||||
|
||||
my $color = [ @{COLORS->[ $color_idx % scalar(@{&COLORS}) ]} ];
|
||||
push @$color, $volume->modifier ? 0.5 : 1;
|
||||
push @{$self->volumes}, my $v = {
|
||||
color => $color,
|
||||
};
|
||||
|
||||
{
|
||||
my $vertices = $mesh->vertices;
|
||||
my @verts = map @{ $vertices->[$_] }, map @$_, @{$mesh->facets};
|
||||
$v->{verts} = OpenGL::Array->new_list(GL_FLOAT, @verts);
|
||||
}
|
||||
|
||||
{
|
||||
my @norms = map { @$_, @$_, @$_ } @{$mesh->normals};
|
||||
$v->{norms} = OpenGL::Array->new_list(GL_FLOAT, @norms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Given an axis and angle, compute quaternion.
|
||||
sub axis_to_quat {
|
||||
my ($ax, $phi) = @_;
|
||||
@ -438,6 +451,8 @@ sub Render {
|
||||
sub draw_mesh {
|
||||
my $self = shift;
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glEnable(GL_CULL_FACE);
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_NORMAL_ARRAY);
|
||||
@ -447,7 +462,11 @@ sub draw_mesh {
|
||||
|
||||
glCullFace(GL_BACK);
|
||||
glNormalPointer_p($volume->{norms});
|
||||
glColor3f(@{ $volume->{color} });
|
||||
if ($volume->{selected}) {
|
||||
glColor4f(@{ &SELECTED_COLOR });
|
||||
} else {
|
||||
glColor4f(@{ $volume->{color} });
|
||||
}
|
||||
glDrawArrays(GL_TRIANGLES, 0, $volume->{verts}->elements / 3);
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ use utf8;
|
||||
use File::Basename qw(basename);
|
||||
use List::Util qw(first);
|
||||
use Wx qw(:bookctrl :dialog :keycode :icon :id :misc :panel :sizer :window :systemsettings);
|
||||
use Wx::Event qw(EVT_BUTTON EVT_CHOICE EVT_KEY_DOWN EVT_TREE_SEL_CHANGED);
|
||||
use Wx::Event qw(EVT_BUTTON EVT_CHOICE EVT_KEY_DOWN);
|
||||
use base 'Wx::ScrolledWindow';
|
||||
|
||||
sub new {
|
||||
@ -71,7 +71,7 @@ sub load_config {
|
||||
my $self = shift;
|
||||
my ($config) = @_;
|
||||
|
||||
foreach my $opt_key (grep $self->{config}->has($_), keys %$config) {
|
||||
foreach my $opt_key (grep $self->{config}->has($_), @{$config->get_keys}) {
|
||||
my $value = $config->get($opt_key);
|
||||
$self->{config}->set($opt_key, $value);
|
||||
$_->set_value($opt_key, $value) for @{$self->{optgroups}};
|
||||
|
@ -355,7 +355,11 @@ sub combine_stls {
|
||||
my $new_object = $new_model->add_object;
|
||||
for my $m (0 .. $#models) {
|
||||
my $model = $models[$m];
|
||||
$new_model->set_material($m, { Name => basename($input_files[$m]) });
|
||||
|
||||
my $material_name = basename($input_files[$m]);
|
||||
$material_name =~ s/\.(stl|obj)$//i;
|
||||
|
||||
$new_model->set_material($m, { Name => $material_name });
|
||||
$new_object->add_volume(
|
||||
material_id => $m,
|
||||
mesh => $model->objects->[0]->volumes->[0]->mesh,
|
||||
|
@ -93,14 +93,14 @@ sub make_perimeters {
|
||||
# the minimum thickness of a single loop is:
|
||||
# width/2 + spacing/2 + spacing/2 + width/2
|
||||
@offsets = @{offset2(\@last, -(0.5*$pwidth + 0.5*$pspacing - 1), +(0.5*$pspacing - 1))};
|
||||
|
||||
|
||||
# look for thin walls
|
||||
if ($self->config->thin_walls) {
|
||||
my $diff = diff_ex(
|
||||
\@last,
|
||||
offset(\@offsets, +0.5*$pwidth),
|
||||
);
|
||||
push @thin_walls, grep abs($_->area) >= $gap_area_threshold, @$diff;
|
||||
push @thin_walls, @$diff;
|
||||
}
|
||||
} else {
|
||||
@offsets = @{offset2(\@last, -(1.5*$pspacing - 1), +(0.5*$pspacing - 1))};
|
||||
@ -132,7 +132,7 @@ sub make_perimeters {
|
||||
|
||||
# make sure we don't infill narrow parts that are already gap-filled
|
||||
# (we only consider this surface's gaps to reduce the diff() complexity)
|
||||
@last = @{diff(\@last, \@last_gaps)};
|
||||
@last = @{diff(\@last, [ map @$_, @last_gaps ])};
|
||||
|
||||
# create one more offset to be used as boundary for fill
|
||||
# we offset by half the perimeter spacing (to get to the actual infill boundary)
|
||||
@ -222,6 +222,9 @@ sub make_perimeters {
|
||||
$self->perimeters->append(@loops);
|
||||
|
||||
# process thin walls by collapsing slices to single passes
|
||||
my $min_thin_wall_width = $pwidth/3;
|
||||
my $min_thin_wall_length = 2*$pwidth;
|
||||
@thin_walls = @{offset2_ex([ map @$_, @thin_walls ], -0.5*$min_thin_wall_width, +0.5*$min_thin_wall_width)};
|
||||
if (@thin_walls) {
|
||||
my @p = map @{$_->medial_axis($pspacing)}, @thin_walls;
|
||||
|
||||
@ -237,7 +240,7 @@ sub make_perimeters {
|
||||
|
||||
my @paths = ();
|
||||
for my $p (@p) {
|
||||
next if $p->length <= $pspacing * 2;
|
||||
next if $p->length < $min_thin_wall_length;
|
||||
my %params = (
|
||||
role => EXTR_ROLE_EXTERNAL_PERIMETER,
|
||||
mm3_per_mm => $mm3_per_mm,
|
||||
@ -431,7 +434,7 @@ sub _detect_bridge_direction {
|
||||
my $perimeter_flow = $self->flow(FLOW_ROLE_PERIMETER);
|
||||
my $infill_flow = $self->flow(FLOW_ROLE_INFILL);
|
||||
|
||||
my $grown = $expolygon->offset_ex(+$perimeter_flow->scaled_width);
|
||||
my $grown = $expolygon->offset(+$perimeter_flow->scaled_width);
|
||||
my @lower = @{$lower_layer->slices}; # expolygons
|
||||
|
||||
# detect what edges lie on lower slices
|
||||
@ -439,7 +442,7 @@ sub _detect_bridge_direction {
|
||||
foreach my $lower (@lower) {
|
||||
# turn bridge contour and holes into polylines and then clip them
|
||||
# with each lower slice's contour
|
||||
my @clipped = @{intersection_pl([ map $_->split_at_first_point, map @$_, @$grown ], [$lower->contour])};
|
||||
my @clipped = @{intersection_pl([ map $_->split_at_first_point, @$grown ], [$lower->contour])};
|
||||
if (@clipped == 2) {
|
||||
# If the split_at_first_point() call above happens to split the polygon inside the clipping area
|
||||
# we would get two consecutive polylines instead of a single one, so we use this ugly hack to
|
||||
@ -490,49 +493,51 @@ sub _detect_bridge_direction {
|
||||
|
||||
# detect anchors as intersection between our bridge expolygon and the lower slices
|
||||
my $anchors = intersection_ex(
|
||||
[ @$grown ],
|
||||
$grown,
|
||||
[ map @$_, @lower ],
|
||||
1, # safety offset required to avoid Clipper from detecting empty intersection while Boost actually found some @edges
|
||||
);
|
||||
|
||||
# we'll now try several directions using a rudimentary visibility check:
|
||||
# bridge in several directions and then sum the length of lines having both
|
||||
# endpoints within anchors
|
||||
my %directions = (); # angle => score
|
||||
my $angle_increment = PI/36; # 5°
|
||||
my $line_increment = $infill_flow->scaled_width;
|
||||
for (my $angle = 0; $angle <= PI; $angle += $angle_increment) {
|
||||
# rotate everything - the center point doesn't matter
|
||||
$_->rotate($angle, [0,0]) for @$inset, @$anchors;
|
||||
if (@$anchors) {
|
||||
# we'll now try several directions using a rudimentary visibility check:
|
||||
# bridge in several directions and then sum the length of lines having both
|
||||
# endpoints within anchors
|
||||
my %directions = (); # angle => score
|
||||
my $angle_increment = PI/36; # 5°
|
||||
my $line_increment = $infill_flow->scaled_width;
|
||||
for (my $angle = 0; $angle <= PI; $angle += $angle_increment) {
|
||||
# rotate everything - the center point doesn't matter
|
||||
$_->rotate($angle, [0,0]) for @$inset, @$anchors;
|
||||
|
||||
# generate lines in this direction
|
||||
my $bounding_box = Slic3r::Geometry::BoundingBox->new_from_points([ map @$_, map @$_, @$anchors ]);
|
||||
# generate lines in this direction
|
||||
my $bounding_box = Slic3r::Geometry::BoundingBox->new_from_points([ map @$_, map @$_, @$anchors ]);
|
||||
|
||||
my @lines = ();
|
||||
for (my $x = $bounding_box->x_min; $x <= $bounding_box->x_max; $x += $line_increment) {
|
||||
push @lines, Slic3r::Polyline->new([$x, $bounding_box->y_min], [$x, $bounding_box->y_max]);
|
||||
my @lines = ();
|
||||
for (my $x = $bounding_box->x_min; $x <= $bounding_box->x_max; $x += $line_increment) {
|
||||
push @lines, Slic3r::Polyline->new([$x, $bounding_box->y_min], [$x, $bounding_box->y_max]);
|
||||
}
|
||||
|
||||
my @clipped_lines = map Slic3r::Line->new(@$_), @{ intersection_pl(\@lines, [ map @$_, @$inset ]) };
|
||||
|
||||
# remove any line not having both endpoints within anchors
|
||||
# NOTE: these calls to contains_point() probably need to check whether the point
|
||||
# is on the anchor boundaries too
|
||||
@clipped_lines = grep {
|
||||
my $line = $_;
|
||||
!(first { $_->contains_point($line->a) } @$anchors)
|
||||
&& !(first { $_->contains_point($line->b) } @$anchors);
|
||||
} @clipped_lines;
|
||||
|
||||
# sum length of bridged lines
|
||||
$directions{-$angle} = sum(map $_->length, @clipped_lines) // 0;
|
||||
}
|
||||
|
||||
my @clipped_lines = map Slic3r::Line->new(@$_), @{ intersection_pl(\@lines, [ map @$_, @$inset ]) };
|
||||
|
||||
# remove any line not having both endpoints within anchors
|
||||
# NOTE: these calls to contains_point() probably need to check whether the point
|
||||
# is on the anchor boundaries too
|
||||
@clipped_lines = grep {
|
||||
my $line = $_;
|
||||
!(first { $_->contains_point($line->a) } @$anchors)
|
||||
&& !(first { $_->contains_point($line->b) } @$anchors);
|
||||
} @clipped_lines;
|
||||
|
||||
# sum length of bridged lines
|
||||
$directions{-$angle} = sum(map $_->length, @clipped_lines) // 0;
|
||||
|
||||
# this could be slightly optimized with a max search instead of the sort
|
||||
my @sorted_directions = sort { $directions{$a} <=> $directions{$b} } keys %directions;
|
||||
|
||||
# the best direction is the one causing most lines to be bridged
|
||||
$bridge_angle = Slic3r::Geometry::rad2deg_dir($sorted_directions[-1]);
|
||||
}
|
||||
|
||||
# this could be slightly optimized with a max search instead of the sort
|
||||
my @sorted_directions = sort { $directions{$a} <=> $directions{$b} } keys %directions;
|
||||
|
||||
# the best direction is the one causing most lines to be bridged
|
||||
$bridge_angle = Slic3r::Geometry::rad2deg_dir($sorted_directions[-1]);
|
||||
}
|
||||
|
||||
Slic3r::debugf " Optimal infill angle of bridge on layer %d is %d degrees\n",
|
||||
|
@ -47,20 +47,7 @@ sub add_object {
|
||||
);
|
||||
|
||||
foreach my $volume (@{$object->volumes}) {
|
||||
$new_object->add_volume(
|
||||
material_id => $volume->material_id,
|
||||
mesh => $volume->mesh->clone,
|
||||
modifier => $volume->modifier,
|
||||
);
|
||||
|
||||
if (defined $volume->material_id) {
|
||||
# merge material attributes (should we rename materials in case of duplicates?)
|
||||
my %attributes = %{ $object->model->materials->{$volume->material_id}->attributes };
|
||||
if (exists $self->materials->{$volume->material_id}) {
|
||||
%attributes = (%attributes, %{ $self->materials->{$volume->material_id}->attributes });
|
||||
}
|
||||
$self->set_material($volume->material_id, {%attributes});
|
||||
}
|
||||
$new_object->add_volume($volume);
|
||||
}
|
||||
|
||||
$new_object->add_instance(
|
||||
@ -296,8 +283,6 @@ sub get_material_name {
|
||||
my $name;
|
||||
if (exists $self->materials->{$material_id}) {
|
||||
$name //= $self->materials->{$material_id}->attributes->{$_} for qw(Name name);
|
||||
} elsif ($material_id eq '_') {
|
||||
$name = 'Default material';
|
||||
}
|
||||
$name //= $material_id;
|
||||
return $name;
|
||||
@ -327,14 +312,48 @@ has '_bounding_box' => (is => 'rw');
|
||||
|
||||
sub add_volume {
|
||||
my $self = shift;
|
||||
my %args = @_;
|
||||
|
||||
push @{$self->volumes}, my $volume = Slic3r::Model::Volume->new(
|
||||
object => $self,
|
||||
%args,
|
||||
);
|
||||
my $new_volume;
|
||||
if (@_ == 1) {
|
||||
# we have a Model::Volume
|
||||
my ($volume) = @_;
|
||||
|
||||
$new_volume = Slic3r::Model::Volume->new(
|
||||
object => $self,
|
||||
material_id => $volume->material_id,
|
||||
mesh => $volume->mesh->clone,
|
||||
modifier => $volume->modifier,
|
||||
);
|
||||
|
||||
if (defined $volume->material_id) {
|
||||
# merge material attributes (should we rename materials in case of duplicates?)
|
||||
if (my $material = $volume->object->model->materials->{$volume->material_id}) {
|
||||
my %attributes = %{ $material->attributes };
|
||||
if (exists $self->model->materials->{$volume->material_id}) {
|
||||
%attributes = (%attributes, %{ $self->model->materials->{$volume->material_id}->attributes });
|
||||
}
|
||||
$self->model->set_material($volume->material_id, {%attributes});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
my %args = @_;
|
||||
$new_volume = Slic3r::Model::Volume->new(
|
||||
object => $self,
|
||||
%args,
|
||||
);
|
||||
}
|
||||
|
||||
push @{$self->volumes}, $new_volume;
|
||||
|
||||
# invalidate cached bounding box
|
||||
$self->_bounding_box(undef);
|
||||
return $volume;
|
||||
|
||||
return $new_volume;
|
||||
}
|
||||
|
||||
sub delete_volume {
|
||||
my ($self, $i) = @_;
|
||||
splice @{$self->volumes}, $i, 1;
|
||||
}
|
||||
|
||||
sub add_instance {
|
||||
@ -413,18 +432,17 @@ sub center_around_origin {
|
||||
# center this object around the origin
|
||||
my $bb = $self->raw_mesh->bounding_box;
|
||||
|
||||
# first align to origin on XYZ
|
||||
# first align to origin on XY
|
||||
my @shift = (
|
||||
-$bb->x_min,
|
||||
-$bb->y_min,
|
||||
-$bb->z_min,
|
||||
0,
|
||||
);
|
||||
|
||||
# then center it on XY
|
||||
my $size = $bb->size;
|
||||
$shift[X] -= $size->x/2;
|
||||
$shift[Y] -= $size->y/2; #//
|
||||
$shift[Z] -= $size->z/2;
|
||||
|
||||
$self->translate(@shift);
|
||||
|
||||
@ -484,7 +502,7 @@ sub print_info {
|
||||
my $self = shift;
|
||||
|
||||
printf "Info about %s:\n", basename($self->input_file);
|
||||
printf " size: x=%.3f y=%.3f z=%.3f\n", @{$self->size};
|
||||
printf " size: x=%.3f y=%.3f z=%.3f\n", @{$self->raw_mesh->bounding_box->size};
|
||||
if (my $stats = $self->mesh_stats) {
|
||||
printf " number of facets: %d\n", $stats->{number_of_facets};
|
||||
printf " number of shells: %d\n", $stats->{number_of_parts};
|
||||
@ -513,6 +531,15 @@ has 'material_id' => (is => 'rw');
|
||||
has 'mesh' => (is => 'rw', required => 1);
|
||||
has 'modifier' => (is => 'rw', defualt => sub { 0 });
|
||||
|
||||
sub assign_unique_material {
|
||||
my ($self) = @_;
|
||||
|
||||
my $model = $self->object->model;
|
||||
my $material_id = 1 + scalar keys %{$model->materials};
|
||||
$self->material_id($material_id);
|
||||
return $model->set_material($material_id);
|
||||
}
|
||||
|
||||
package Slic3r::Model::Instance;
|
||||
use Moo;
|
||||
|
||||
|
@ -4,7 +4,6 @@ use warnings;
|
||||
|
||||
use Slic3r::Geometry qw(A B X Y X1 X2 Y1 Y2);
|
||||
use Slic3r::Geometry::Clipper qw(JT_SQUARE);
|
||||
use Storable qw();
|
||||
|
||||
sub new_scale {
|
||||
my $class = shift;
|
||||
|
@ -315,9 +315,11 @@ sub init_extruders {
|
||||
}
|
||||
}
|
||||
|
||||
# this value is not supposed to be compared with $layer->id
|
||||
# since they have different semantics
|
||||
sub layer_count {
|
||||
my $self = shift;
|
||||
return max(map { scalar @{$_->layers} } @{$self->objects});
|
||||
return max(map $_->layer_count, @{$self->objects});
|
||||
}
|
||||
|
||||
sub regions_count {
|
||||
@ -444,15 +446,15 @@ sub process {
|
||||
items => sub {
|
||||
my @items = (); # [layer_id, region_id]
|
||||
for my $region_id (0 .. ($self->regions_count-1)) {
|
||||
push @items, map [$_, $region_id], 0..($object->layer_count-1);
|
||||
push @items, map [$_, $region_id], 0..$#{$object->layers};
|
||||
}
|
||||
@items;
|
||||
},
|
||||
thread_cb => sub {
|
||||
my $q = shift;
|
||||
while (defined (my $obj_layer = $q->dequeue)) {
|
||||
my ($layer_id, $region_id) = @$obj_layer;
|
||||
my $layerm = $object->layers->[$layer_id]->regions->[$region_id];
|
||||
my ($i, $region_id) = @$obj_layer;
|
||||
my $layerm = $object->layers->[$i]->regions->[$region_id];
|
||||
$layerm->fills->append( $object->fill_maker->make_fill($layerm) );
|
||||
}
|
||||
},
|
||||
@ -475,12 +477,10 @@ sub process {
|
||||
});
|
||||
|
||||
# make skirt
|
||||
$status_cb->(88, "Generating skirt");
|
||||
$status_cb->(88, "Generating skirt/brim");
|
||||
$print_step->(STEP_SKIRT, sub {
|
||||
$self->make_skirt;
|
||||
});
|
||||
|
||||
$status_cb->(88, "Generating skirt");
|
||||
$print_step->(STEP_BRIM, sub {
|
||||
$self->make_brim; # must come after make_skirt
|
||||
});
|
||||
@ -559,29 +559,31 @@ EOF
|
||||
($type eq 'contour' ? 'white' : 'black');
|
||||
};
|
||||
|
||||
my @layers = sort { $a->print_z <=> $b->print_z }
|
||||
map { @{$_->layers}, @{$_->support_layers} }
|
||||
@{$self->objects};
|
||||
|
||||
my $layer_id = -1;
|
||||
my @previous_layer_slices = ();
|
||||
for my $layer_id (0..$self->layer_count-1) {
|
||||
my @layers = map $_->layers->[$layer_id], @{$self->objects};
|
||||
printf $fh qq{ <g id="layer%d" slic3r:z="%s">\n}, $layer_id, +(grep defined $_, @layers)[0]->slice_z;
|
||||
for my $layer (@layers) {
|
||||
$layer_id++;
|
||||
# TODO: remove slic3r:z for raft layers
|
||||
printf $fh qq{ <g id="layer%d" slic3r:z="%s">\n}, $layer_id, unscale($layer->slice_z);
|
||||
|
||||
my @current_layer_slices = ();
|
||||
for my $obj_idx (0 .. $#{$self->objects}) {
|
||||
my $layer = $self->objects->[$obj_idx]->layers->[$layer_id] or next;
|
||||
|
||||
# sort slices so that the outermost ones come first
|
||||
my @slices = sort { $a->contour->contains_point($b->contour->first_point) ? 0 : 1 } @{$layer->slices};
|
||||
foreach my $copy (@{$self->objects->[$obj_idx]->_shifted_copies}) {
|
||||
foreach my $slice (@slices) {
|
||||
my $expolygon = $slice->clone;
|
||||
$expolygon->translate(@$copy);
|
||||
$print_polygon->($expolygon->contour, 'contour');
|
||||
$print_polygon->($_, 'hole') for @{$expolygon->holes};
|
||||
push @current_layer_slices, $expolygon;
|
||||
}
|
||||
# sort slices so that the outermost ones come first
|
||||
my @slices = sort { $a->contour->encloses_point($b->contour->[0]) ? 0 : 1 } @{$layer->slices};
|
||||
foreach my $copy (@{$layer->object->copies}) {
|
||||
foreach my $slice (@slices) {
|
||||
my $expolygon = $slice->clone;
|
||||
$expolygon->translate(@$copy);
|
||||
$print_polygon->($expolygon->contour, 'contour');
|
||||
$print_polygon->($_, 'hole') for @{$expolygon->holes};
|
||||
push @current_layer_slices, $expolygon;
|
||||
}
|
||||
}
|
||||
# generate support material
|
||||
if ($self->has_support_material && $layer_id > 0) {
|
||||
if ($self->has_support_material && $layer->id > 0) {
|
||||
my (@supported_slices, @unsupported_slices) = ();
|
||||
foreach my $expolygon (@current_layer_slices) {
|
||||
my $intersection = intersection_ex(
|
||||
@ -620,26 +622,50 @@ sub make_skirt {
|
||||
|
||||
$self->skirt->clear; # method must be idempotent
|
||||
|
||||
# First off we need to decide how tall the skirt must be.
|
||||
# The skirt_height option from config is expressed in layers, but our
|
||||
# object might have different layer heights, so we need to find the print_z
|
||||
# of the highest layer involved.
|
||||
# Note that unless skirt_height == -1 (which means it's printed on all layers)
|
||||
# the actual skirt might not reach this $skirt_height_z value since the print
|
||||
# order of objects on each layer is not guaranteed and will not generally
|
||||
# include the thickest object first. It is just guaranteed that a skirt is
|
||||
# prepended to the first 'n' layers (with 'n' = skirt_height).
|
||||
# $skirt_height_z in this case is the highest possible skirt height for safety.
|
||||
my $skirt_height_z = -1;
|
||||
foreach my $object (@{$self->objects}) {
|
||||
my $skirt_height = ($self->config->skirt_height == -1)
|
||||
? scalar(@{$object->layers})
|
||||
: min($self->config->skirt_height, scalar(@{$object->layers}));
|
||||
|
||||
my $highest_layer = $object->layers->[$skirt_height-1];
|
||||
$skirt_height_z = max($skirt_height_z, $highest_layer->print_z);
|
||||
}
|
||||
|
||||
# collect points from all layers contained in skirt height
|
||||
my @points = ();
|
||||
foreach my $obj_idx (0 .. $#{$self->objects}) {
|
||||
my $object = $self->objects->[$obj_idx];
|
||||
foreach my $object (@{$self->objects}) {
|
||||
my @object_points = ();
|
||||
|
||||
# get skirt layers
|
||||
my $skirt_height = ($self->config->skirt_height == -1)
|
||||
? 1 + $#{$object->layers}
|
||||
: 1 + min($self->config->skirt_height-1, $#{$object->layers}+1);
|
||||
|
||||
my @layer_points = (
|
||||
map @$_, map @$_, map @{$object->layers->[$_]->slices}, 0..($skirt_height-1),
|
||||
);
|
||||
if (@{ $object->support_layers }) {
|
||||
my @support_layers = map $object->support_layers->[$_], 0..min($self->config->skirt_height-1, $#{$object->support_layers});
|
||||
push @layer_points,
|
||||
(map @{$_->polyline}, map @{$_->support_fills}, grep $_->support_fills, @support_layers),
|
||||
(map @{$_->polyline}, map @{$_->support_interface_fills}, grep $_->support_interface_fills, @support_layers);
|
||||
# get object layers up to $skirt_height_z
|
||||
foreach my $layer (@{$object->layers}) {
|
||||
last if $layer->print_z > $skirt_height_z;
|
||||
push @object_points, map @$_, map @$_, @{$layer->slices};
|
||||
}
|
||||
|
||||
# get support layers up to $skirt_height_z
|
||||
foreach my $layer (@{$object->support_layers}) {
|
||||
last if $layer->print_z > $skirt_height_z;
|
||||
push @object_points, map @{$_->polyline}, @{$layer->support_fills} if $layer->support_fills;
|
||||
push @object_points, map @{$_->polyline}, @{$layer->support_interface_fills} if $layer->support_interface_fills;
|
||||
}
|
||||
|
||||
# repeat points for each object copy
|
||||
foreach my $copy (@{$object->_shifted_copies}) {
|
||||
my @copy_points = map $_->clone, @object_points;
|
||||
$_->translate(@$copy) for @copy_points;
|
||||
push @points, @copy_points;
|
||||
}
|
||||
push @points, map move_points($_, @layer_points), @{$object->_shifted_copies};
|
||||
}
|
||||
return if @points < 3; # at least three points required for a convex hull
|
||||
|
||||
@ -977,11 +1003,15 @@ sub write_gcode {
|
||||
$self->total_extruded_volume(0);
|
||||
foreach my $extruder_id (@{$self->extruders}) {
|
||||
my $extruder = $gcodegen->extruders->{$extruder_id};
|
||||
$self->total_used_filament($self->total_used_filament + $extruder->absolute_E);
|
||||
$self->total_extruded_volume($self->total_extruded_volume + $extruder->extruded_volume);
|
||||
# the final retraction doesn't really count as "used filament"
|
||||
my $used_filament = $extruder->absolute_E + $extruder->retract_length;
|
||||
my $extruded_volume = $extruder->extruded_volume($used_filament);
|
||||
|
||||
printf $fh "; filament used = %.1fmm (%.1fcm3)\n",
|
||||
$extruder->absolute_E, $extruder->extruded_volume/1000;
|
||||
$used_filament, $extruded_volume/1000;
|
||||
|
||||
$self->total_used_filament($self->total_used_filament + $used_filament);
|
||||
$self->total_extruded_volume($self->total_extruded_volume + $extruded_volume);
|
||||
}
|
||||
|
||||
# append full config
|
||||
|
@ -98,9 +98,12 @@ sub delete_all_copies {
|
||||
$self->_trigger_copies;
|
||||
}
|
||||
|
||||
# this is the *total* layer count
|
||||
# this value is not supposed to be compared with $layer->id
|
||||
# since they have different semantics
|
||||
sub layer_count {
|
||||
my $self = shift;
|
||||
return scalar @{ $self->layers };
|
||||
return scalar @{ $self->layers } + scalar @{ $self->support_layers };
|
||||
}
|
||||
|
||||
sub bounding_box {
|
||||
@ -124,17 +127,30 @@ sub slice {
|
||||
|
||||
# make layers taking custom heights into account
|
||||
my $print_z = my $slice_z = my $height = my $id = 0;
|
||||
my $first_object_layer_height = -1;
|
||||
|
||||
# add raft layers
|
||||
if ($self->config->raft_layers > 0) {
|
||||
$id += $self->config->raft_layers;
|
||||
|
||||
# raise first object layer Z by the thickness of the raft itself
|
||||
# plus the extra distance required by the support material logic
|
||||
$print_z += $self->config->get_value('first_layer_height');
|
||||
$print_z += $self->config->layer_height * ($self->config->raft_layers - 1);
|
||||
$id += $self->config->raft_layers;
|
||||
|
||||
# at this stage we don't know which nozzles are actually used for the first layer
|
||||
# so we compute the average of all of them
|
||||
my $nozzle_diameter = sum(@{$self->print->config->nozzle_diameter})/@{$self->print->config->nozzle_diameter};
|
||||
my $distance = Slic3r::Print::SupportMaterial::contact_distance($nozzle_diameter);
|
||||
|
||||
# force first layer print_z according to the contact distance
|
||||
# (the loop below will raise print_z by such height)
|
||||
$first_object_layer_height = $distance;
|
||||
}
|
||||
|
||||
# loop until we have at least one layer and the max slice_z reaches the object height
|
||||
my $max_z = unscale($self->size->z);
|
||||
while (!@{$self->layers} || ($slice_z - $height) <= $max_z) {
|
||||
while (($slice_z - $height) <= $max_z) {
|
||||
# assign the default height to the layer according to the general settings
|
||||
$height = ($id == 0)
|
||||
? $self->config->get_value('first_layer_height')
|
||||
@ -150,7 +166,11 @@ sub slice {
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($first_object_layer_height != -1 && !@{$self->layers}) {
|
||||
$height = $first_object_layer_height;
|
||||
}
|
||||
|
||||
$print_z += $height;
|
||||
$slice_z += $height/2;
|
||||
|
||||
@ -354,9 +374,9 @@ sub make_perimeters {
|
||||
my $region_perimeters = $region->config->perimeters;
|
||||
|
||||
if ($region->config->extra_perimeters && $region_perimeters > 0 && $region->config->fill_density > 0) {
|
||||
for my $layer_id (0 .. $self->layer_count-2) {
|
||||
my $layerm = $self->layers->[$layer_id]->regions->[$region_id];
|
||||
my $upper_layerm = $self->layers->[$layer_id+1]->regions->[$region_id];
|
||||
for my $i (0 .. $#{$self->layers}-1) {
|
||||
my $layerm = $self->layers->[$i]->regions->[$region_id];
|
||||
my $upper_layerm = $self->layers->[$i+1]->regions->[$region_id];
|
||||
my $perimeter_spacing = $layerm->flow(FLOW_ROLE_PERIMETER)->scaled_spacing;
|
||||
|
||||
my $overlap = $perimeter_spacing; # one perimeter
|
||||
@ -393,8 +413,7 @@ sub make_perimeters {
|
||||
|
||||
# only add the perimeter if there's an intersection with the collapsed area
|
||||
last CYCLE if !@{ intersection($diff, $hypothetical_perimeter) };
|
||||
|
||||
Slic3r::debugf " adding one more perimeter at layer %d\n", $layer_id;
|
||||
Slic3r::debugf " adding one more perimeter at layer %d\n", $layerm->id;
|
||||
$slice->extra_perimeters($extra_perimeters);
|
||||
}
|
||||
}
|
||||
@ -404,11 +423,11 @@ sub make_perimeters {
|
||||
|
||||
Slic3r::parallelize(
|
||||
threads => $self->print->config->threads,
|
||||
items => sub { 0 .. ($self->layer_count-1) },
|
||||
items => sub { 0 .. $#{$self->layers} },
|
||||
thread_cb => sub {
|
||||
my $q = shift;
|
||||
while (defined (my $layer_id = $q->dequeue)) {
|
||||
$self->layers->[$layer_id]->make_perimeters;
|
||||
while (defined (my $i = $q->dequeue)) {
|
||||
$self->layers->[$i]->make_perimeters;
|
||||
}
|
||||
},
|
||||
collect_cb => sub {},
|
||||
@ -428,7 +447,7 @@ sub detect_surfaces_type {
|
||||
Slic3r::debugf "Detecting solid surfaces...\n";
|
||||
|
||||
for my $region_id (0 .. ($self->print->regions_count-1)) {
|
||||
for my $i (0 .. ($self->layer_count-1)) {
|
||||
for my $i (0 .. $#{$self->layers}) {
|
||||
my $layerm = $self->layers->[$i]->regions->[$region_id];
|
||||
|
||||
# prepare a reusable subroutine to make surface differences
|
||||
@ -537,8 +556,8 @@ sub clip_fill_surfaces {
|
||||
my $overhangs = []; # arrayref of polygons
|
||||
for my $layer_id (reverse 0..$#{$self->layers}) {
|
||||
my $layer = $self->layers->[$layer_id];
|
||||
my @layer_internal = ();
|
||||
my @new_internal = ();
|
||||
my @layer_internal = (); # arrayref of Surface objects
|
||||
my @new_internal = (); # arrayref of Surface objects
|
||||
|
||||
# clip this layer's internal surfaces to @overhangs
|
||||
foreach my $layerm (@{$layer->regions}) {
|
||||
@ -572,10 +591,10 @@ sub clip_fill_surfaces {
|
||||
if ($layer_id > 0) {
|
||||
my $solid = diff(
|
||||
[ map @$_, @{$layer->slices} ],
|
||||
\@layer_internal,
|
||||
[ map $_->p, @layer_internal ],
|
||||
);
|
||||
$overhangs = offset($solid, +$additional_margin);
|
||||
push @$overhangs, @new_internal; # propagate upper overhangs
|
||||
push @$overhangs, map $_->p, @new_internal; # propagate upper overhangs
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -661,8 +680,8 @@ sub process_external_surfaces {
|
||||
|
||||
for my $region_id (0 .. ($self->print->regions_count-1)) {
|
||||
$self->layers->[0]->regions->[$region_id]->process_external_surfaces(undef);
|
||||
for my $layer_id (1 .. ($self->layer_count-1)) {
|
||||
$self->layers->[$layer_id]->regions->[$region_id]->process_external_surfaces($self->layers->[$layer_id-1]);
|
||||
for my $i (1 .. $#{$self->layers}) {
|
||||
$self->layers->[$i]->regions->[$region_id]->process_external_surfaces($self->layers->[$i-1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -673,7 +692,7 @@ sub discover_horizontal_shells {
|
||||
Slic3r::debugf "==> DISCOVERING HORIZONTAL SHELLS\n";
|
||||
|
||||
for my $region_id (0 .. ($self->print->regions_count-1)) {
|
||||
for (my $i = 0; $i < $self->layer_count; $i++) {
|
||||
for (my $i = 0; $i <= $#{$self->layers}; $i++) {
|
||||
my $layerm = $self->layers->[$i]->regions->[$region_id];
|
||||
|
||||
if ($layerm->config->solid_infill_every_layers && $layerm->config->fill_density > 0
|
||||
@ -705,10 +724,11 @@ sub discover_horizontal_shells {
|
||||
abs($n - $i) <= $solid_layers-1;
|
||||
($type == S_TYPE_TOP) ? $n-- : $n++) {
|
||||
|
||||
next if $n < 0 || $n >= $self->layer_count;
|
||||
next if $n < 0 || $n > $#{$self->layers};
|
||||
Slic3r::debugf " looking for neighbors on layer %d...\n", $n;
|
||||
|
||||
my $neighbor_fill_surfaces = $self->layers->[$n]->regions->[$region_id]->fill_surfaces;
|
||||
my $neighbor_layerm = $self->layers->[$n]->regions->[$region_id];
|
||||
my $neighbor_fill_surfaces = $neighbor_layerm->fill_surfaces;
|
||||
my @neighbor_fill_surfaces = map $_->clone, @$neighbor_fill_surfaces; # clone because we will use these surfaces even after clearing the collection
|
||||
|
||||
# find intersection between neighbor and current layer's surfaces
|
||||
@ -727,41 +747,49 @@ sub discover_horizontal_shells {
|
||||
);
|
||||
next EXTERNAL if !@$new_internal_solid;
|
||||
|
||||
# make sure the new internal solid is wide enough, as it might get collapsed when
|
||||
# spacing is added in Fill.pm
|
||||
if ($layerm->config->fill_density == 0) {
|
||||
# if we're printing a hollow object we discard any solid shell thinner
|
||||
# than a perimeter width, since it's probably just crossing a sloping wall
|
||||
# and it's not wanted in a hollow print even if it would make sense when
|
||||
# obeying the solid shell count option strictly (DWIM!)
|
||||
my $margin = $neighbor_layerm->flow(FLOW_ROLE_PERIMETER)->scaled_width;
|
||||
my $too_narrow = diff(
|
||||
$new_internal_solid,
|
||||
offset2($new_internal_solid, -$margin, +$margin, CLIPPER_OFFSET_SCALE, JT_MITER, 5),
|
||||
1,
|
||||
);
|
||||
$new_internal_solid = $solid = diff(
|
||||
$new_internal_solid,
|
||||
$too_narrow,
|
||||
) if @$too_narrow;
|
||||
}
|
||||
|
||||
# make sure the new internal solid is wide enough, as it might get collapsed
|
||||
# when spacing is added in Fill.pm
|
||||
{
|
||||
my $margin = 3 * $layerm->flow(FLOW_ROLE_SOLID_INFILL)->scaled_width; # require at least this size
|
||||
# we use a higher miterLimit here to handle areas with acute angles
|
||||
# in those cases, the default miterLimit would cut the corner and we'd
|
||||
# get a triangle in $too_narrow; if we grow it below then the shell
|
||||
# would have a different shape from the external surface and we'd still
|
||||
# have the same angle, so the next shell would be grown even more and so on.
|
||||
my $margin = 3 * $layerm->flow(FLOW_ROLE_SOLID_INFILL)->scaled_width; # require at least this size
|
||||
my $too_narrow = diff(
|
||||
$new_internal_solid,
|
||||
offset2($new_internal_solid, -$margin, +$margin, CLIPPER_OFFSET_SCALE, JT_MITER, 5),
|
||||
1,
|
||||
);
|
||||
|
||||
# if some parts are going to collapse, use a different strategy according to fill density
|
||||
if (@$too_narrow) {
|
||||
if ($layerm->config->fill_density > 0) {
|
||||
# if we have internal infill, grow the collapsing parts and add the extra area to
|
||||
# the neighbor layer as well as to our original surfaces so that we support this
|
||||
# additional area in the next shell too
|
||||
|
||||
# make sure our grown surfaces don't exceed the fill area
|
||||
my @grown = @{intersection(
|
||||
offset($too_narrow, +$margin),
|
||||
[ map $_->p, @neighbor_fill_surfaces ],
|
||||
)};
|
||||
$new_internal_solid = $solid = [ @grown, @$new_internal_solid ];
|
||||
} else {
|
||||
# if we're printing a hollow object, we discard such small parts
|
||||
$new_internal_solid = $solid = diff(
|
||||
$new_internal_solid,
|
||||
$too_narrow,
|
||||
);
|
||||
}
|
||||
# grow the collapsing parts and add the extra area to the neighbor layer
|
||||
# as well as to our original surfaces so that we support this
|
||||
# additional area in the next shell too
|
||||
|
||||
# make sure our grown surfaces don't exceed the fill area
|
||||
my @grown = @{intersection(
|
||||
offset($too_narrow, +$margin),
|
||||
[ map $_->p, @neighbor_fill_surfaces ],
|
||||
)};
|
||||
$new_internal_solid = $solid = [ @grown, @$new_internal_solid ];
|
||||
}
|
||||
}
|
||||
|
||||
@ -811,8 +839,7 @@ sub combine_infill {
|
||||
|
||||
return unless defined first { $_->config->infill_every_layers > 1 && $_->config->fill_density > 0 } @{$self->print->regions};
|
||||
|
||||
my $layer_count = $self->layer_count;
|
||||
my @layer_heights = map $self->layers->[$_]->height, 0 .. $layer_count-1;
|
||||
my @layer_heights = map $_->height, @{$self->layers};
|
||||
|
||||
for my $region_id (0 .. ($self->print->regions_count-1)) {
|
||||
my $region = $self->print->regions->[$region_id];
|
||||
@ -922,7 +949,7 @@ sub combine_infill {
|
||||
sub generate_support_material {
|
||||
my $self = shift;
|
||||
return unless ($self->config->support_material || $self->config->raft_layers > 0)
|
||||
&& $self->layer_count >= 2;
|
||||
&& scalar(@{$self->layers}) >= 2;
|
||||
|
||||
my $first_layer_flow = Slic3r::Flow->new_from_width(
|
||||
width => ($self->config->first_layer_extrusion_width || $self->config->support_material_extrusion_width),
|
||||
|
@ -190,7 +190,7 @@ sub contact_area {
|
||||
@{$layer->regions};
|
||||
my $nozzle_diameter = sum(@nozzle_diameters)/@nozzle_diameters;
|
||||
|
||||
my $contact_z = $layer->print_z - $nozzle_diameter * 1.5;
|
||||
my $contact_z = $layer->print_z - contact_distance($nozzle_diameter);
|
||||
###$contact_z = $layer->print_z - $layer->height;
|
||||
|
||||
# ignore this contact area if it's too low
|
||||
@ -739,4 +739,10 @@ sub overlapping_layers {
|
||||
} 0..$#$support_z;
|
||||
}
|
||||
|
||||
# class method
|
||||
sub contact_distance {
|
||||
my ($nozzle_diameter) = @_;
|
||||
return $nozzle_diameter * 1.5;
|
||||
}
|
||||
|
||||
1;
|
||||
|
@ -50,7 +50,7 @@ sub output {
|
||||
|
||||
my $g = $svg->group(
|
||||
style => {
|
||||
'stroke-width' => 2,
|
||||
'stroke-width' => 0,
|
||||
'stroke' => $colour || 'black',
|
||||
'fill' => ($type !~ /polygons/ ? 'none' : ($colour || 'grey')),
|
||||
'fill-type' => $filltype,
|
||||
@ -68,7 +68,7 @@ sub output {
|
||||
|
||||
my $g = $svg->group(
|
||||
style => {
|
||||
'stroke-width' => 2,
|
||||
'stroke-width' => ($method eq 'polyline') ? 1 : 0,
|
||||
'stroke' => $colour || 'black',
|
||||
'fill' => ($type !~ /polygons/ ? 'none' : ($colour || 'grey')),
|
||||
},
|
||||
|
File diff suppressed because one or more lines are too long
@ -19,9 +19,4 @@ sub center {
|
||||
return $self->bounding_box->center;
|
||||
}
|
||||
|
||||
sub facets_count {
|
||||
my $self = shift;
|
||||
return $self->stats->{number_of_facets};
|
||||
}
|
||||
|
||||
1;
|
||||
|
26
t/config.t
Normal file
26
t/config.t
Normal file
@ -0,0 +1,26 @@
|
||||
use Test::More tests => 2;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
BEGIN {
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
}
|
||||
|
||||
use Slic3r;
|
||||
use Slic3r::Test;
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('layer_height', 0.123);
|
||||
$config->setenv;
|
||||
is $ENV{SLIC3R_LAYER_HEIGHT}, '0.123', 'setenv';
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('perimeter_extrusion_width', '250%');
|
||||
ok $config->validate, 'percent extrusion width is validated';
|
||||
}
|
||||
|
||||
__END__
|
78
t/fill.t
78
t/fill.t
@ -2,16 +2,17 @@ use Test::More;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
plan tests => 34;
|
||||
plan tests => 42;
|
||||
|
||||
BEGIN {
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
}
|
||||
|
||||
use List::Util qw(first);
|
||||
use Slic3r;
|
||||
use Slic3r::Geometry qw(scale X Y);
|
||||
use Slic3r::Geometry::Clipper qw(diff_ex);
|
||||
use Slic3r::Geometry qw(scale X Y convex_hull);
|
||||
use Slic3r::Geometry::Clipper qw(union diff_ex);
|
||||
use Slic3r::Surface qw(:types);
|
||||
use Slic3r::Test;
|
||||
|
||||
@ -166,11 +167,41 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
|
||||
'chained path';
|
||||
}
|
||||
|
||||
for my $pattern (qw(hilbertcurve concentric)) {
|
||||
for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) {
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('fill_pattern', $pattern);
|
||||
$config->set('perimeters', 1);
|
||||
$config->set('skirts', 0);
|
||||
$config->set('fill_density', 0.2);
|
||||
$config->set('layer_height', 0.05);
|
||||
$config->set('perimeter_extruder', 1);
|
||||
$config->set('infill_extruder', 2);
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale => 2);
|
||||
ok my $gcode = Slic3r::Test::gcode($print), "successful $pattern infill generation";
|
||||
my $tool = undef;
|
||||
my @perimeter_points = my @infill_points = ();
|
||||
Slic3r::GCode::Reader->new->parse($gcode, sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd =~ /^T(\d+)/) {
|
||||
$tool = $1;
|
||||
} elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
|
||||
if ($tool == $config->perimeter_extruder-1) {
|
||||
push @perimeter_points, Slic3r::Point->new_scale($args->{X}, $args->{Y});
|
||||
} elsif ($tool == $config->infill_extruder-1) {
|
||||
push @infill_points, Slic3r::Point->new_scale($args->{X}, $args->{Y});
|
||||
}
|
||||
}
|
||||
});
|
||||
my $convex_hull = convex_hull(\@perimeter_points);
|
||||
ok !(defined first { !$convex_hull->contains_point($_) } @infill_points), "infill does not exceed perimeters ($pattern)";
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('infill_only_where_needed', 1);
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
ok Slic3r::Test::gcode($print), "successful $pattern infill generation";
|
||||
ok my $gcode = Slic3r::Test::gcode($print), "successful G-code generation when infill_only_where_needed is set";
|
||||
}
|
||||
|
||||
{
|
||||
@ -194,4 +225,41 @@ for my $pattern (qw(hilbertcurve concentric)) {
|
||||
"solid_infill_below_area and solid_infill_every_layers are ignored when fill_density is 0";
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('skirts', 0);
|
||||
$config->set('perimeters', 3);
|
||||
$config->set('fill_density', 0);
|
||||
$config->set('layer_height', 0.2);
|
||||
$config->set('first_layer_height', 0.2);
|
||||
$config->set('nozzle_diameter', [0.35]);
|
||||
$config->set('infill_extruder', 2);
|
||||
$config->set('infill_extrusion_width', 0.52);
|
||||
|
||||
my $print = Slic3r::Test::init_print('A', config => $config);
|
||||
my %infill = (); # Z => [ Line, Line ... ]
|
||||
my $tool = undef;
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd =~ /^T(\d+)/) {
|
||||
$tool = $1;
|
||||
} elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
|
||||
if ($tool == $config->infill_extruder-1) {
|
||||
my $z = 1 * $self->Z;
|
||||
$infill{$z} ||= [];
|
||||
push @{$infill{$z}}, Slic3r::Line->new_scale(
|
||||
[ $self->X, $self->Y ],
|
||||
[ $info->{new_X}, $info->{new_Y} ],
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
my $grow_d = scale($config->infill_extrusion_width)/2;
|
||||
my $layer0_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.2} } ]);
|
||||
my $layer1_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.4} } ]);
|
||||
my $diff = [ grep $_->area >= 2*$grow_d**2, @{diff_ex($layer0_infill, $layer1_infill)} ];
|
||||
is scalar(@$diff), 0, 'no missing parts in solid shell when fill_density is 0';
|
||||
}
|
||||
|
||||
__END__
|
||||
|
32
t/gcode.t
32
t/gcode.t
@ -1,4 +1,4 @@
|
||||
use Test::More tests => 6;
|
||||
use Test::More tests => 8;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
@ -68,4 +68,34 @@ use Slic3r::Test;
|
||||
is_deeply [ @z_moves[0..($layer_count-1)] ], [ @z_moves[$layer_count..$#z_moves] ], 'complete_objects generates the correct Z moves';
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('retract_length', [1000000]);
|
||||
$config->set('use_relative_e_distances', 1);
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
|
||||
});
|
||||
ok $print->total_used_filament > 0, 'final retraction is not considered in total used filament';
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('gcode_flavor', 'sailfish');
|
||||
$config->set('raft_layers', 3);
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
my @percent = ();
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd eq 'M73') {
|
||||
push @percent, $args->{P};
|
||||
}
|
||||
});
|
||||
# the extruder heater is turned off when M73 P100 is reached
|
||||
ok !(defined first { $_ > 100 } @percent), 'M73 is never given more than 100%';
|
||||
}
|
||||
|
||||
__END__
|
||||
|
10
t/multi.t
10
t/multi.t
@ -1,4 +1,4 @@
|
||||
use Test::More tests => 1;
|
||||
use Test::More tests => 2;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
@ -58,4 +58,12 @@ use Slic3r::Test;
|
||||
ok !(first { $convex_hull->contains_point($_) } @toolchange_points), 'all toolchanges happen outside skirt';
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('support_material_extruder', 3);
|
||||
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
ok Slic3r::Test::gcode($print), 'no errors when using non-consecutive extruders';
|
||||
}
|
||||
|
||||
__END__
|
||||
|
@ -151,6 +151,7 @@ use Slic3r::Test;
|
||||
$config->set('perimeter_speed', 99);
|
||||
$config->set('external_perimeter_speed', 99);
|
||||
$config->set('small_perimeter_speed', 99);
|
||||
$config->set('thin_walls', 0);
|
||||
|
||||
my $print = Slic3r::Test::init_print('ipadstand', config => $config);
|
||||
my %perimeters = (); # z => number of loops
|
||||
|
126
t/shells.t
126
t/shells.t
@ -1,4 +1,4 @@
|
||||
use Test::More tests => 12;
|
||||
use Test::More tests => 10;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
@ -7,8 +7,9 @@ BEGIN {
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
}
|
||||
|
||||
use List::Util qw(first);
|
||||
use List::Util qw(first sum);
|
||||
use Slic3r;
|
||||
use Slic3r::Geometry qw(epsilon);
|
||||
use Slic3r::Test;
|
||||
|
||||
{
|
||||
@ -17,6 +18,7 @@ use Slic3r::Test;
|
||||
$config->set('perimeters', 0);
|
||||
$config->set('solid_infill_speed', 99);
|
||||
$config->set('top_solid_infill_speed', 99);
|
||||
$config->set('bridge_speed', 72);
|
||||
$config->set('first_layer_speed', '100%');
|
||||
$config->set('cooling', 0);
|
||||
|
||||
@ -26,19 +28,25 @@ use Slic3r::Test;
|
||||
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
|
||||
my %layers_with_shells = (); # Z => $count
|
||||
my %z = (); # Z => 1
|
||||
my %layers_with_solid_infill = (); # Z => $count
|
||||
my %layers_with_bridge_infill = (); # Z => $count
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($self->Z > 0) {
|
||||
$layers_with_shells{$self->Z} //= 0;
|
||||
$layers_with_shells{$self->Z} = 1
|
||||
if $info->{extruding}
|
||||
&& $info->{dist_XY} > 0
|
||||
&& ($args->{F} // $self->F) == $config->solid_infill_speed*60;
|
||||
$z{ $self->Z } = 1;
|
||||
if ($info->{extruding} && $info->{dist_XY} > 0) {
|
||||
my $F = $args->{F} // $self->F;
|
||||
$layers_with_solid_infill{$self->Z} = 1
|
||||
if $F == $config->solid_infill_speed*60;
|
||||
$layers_with_bridge_infill{$self->Z} = 1
|
||||
if $F == $config->bridge_speed*60;
|
||||
}
|
||||
}
|
||||
});
|
||||
my @shells = @layers_with_shells{sort { $a <=> $b } keys %layers_with_shells};
|
||||
my @z = sort { $a <=> $b } keys %z;
|
||||
my @shells = map $layers_with_solid_infill{$_} || $layers_with_bridge_infill{$_}, @z;
|
||||
fail "insufficient number of bottom solid layers"
|
||||
unless !defined(first { !$_ } @shells[0..$config->bottom_solid_layers-1]);
|
||||
fail "excessive number of bottom solid layers"
|
||||
@ -47,9 +55,17 @@ use Slic3r::Test;
|
||||
unless !defined(first { !$_ } @shells[-$config->top_solid_layers..-1]);
|
||||
fail "excessive number of top solid layers"
|
||||
unless scalar(grep $_, @shells[($#shells/2)..$#shells]) == $config->top_solid_layers;
|
||||
if ($config->top_solid_layers > 0) {
|
||||
fail "unexpected solid infill speed in first solid layer over sparse infill"
|
||||
if $layers_with_solid_infill{ $z[-$config->top_solid_layers] };
|
||||
die "bridge speed not used in first solid layer over sparse infill"
|
||||
if !$layers_with_bridge_infill{ $z[-$config->top_solid_layers] };
|
||||
}
|
||||
1;
|
||||
};
|
||||
|
||||
$config->set('top_solid_layers', 3);
|
||||
$config->set('bottom_solid_layers', 3);
|
||||
ok $test->(), "proper number of shells is applied";
|
||||
|
||||
$config->set('top_solid_layers', 0);
|
||||
@ -68,6 +84,7 @@ use Slic3r::Test;
|
||||
$config->set('bottom_solid_layers', 0);
|
||||
$config->set('top_solid_layers', 3);
|
||||
$config->set('cooling', 0);
|
||||
$config->set('bridge_speed', 99);
|
||||
$config->set('solid_infill_speed', 99);
|
||||
$config->set('top_solid_infill_speed', 99);
|
||||
$config->set('first_layer_speed', '100%');
|
||||
@ -147,6 +164,7 @@ use Slic3r::Test;
|
||||
$config->set('bottom_solid_layers', 0);
|
||||
$config->set('skirts', 0);
|
||||
$config->set('first_layer_height', '100%');
|
||||
$config->set('start_gcode', '');
|
||||
|
||||
# TODO: this needs to be tested with a model with sloping edges, where starting
|
||||
# points of each layer are not aligned - in that case we would test that no
|
||||
@ -161,21 +179,95 @@ use Slic3r::Test;
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
$started_extruding = 1 if $info->{extruding};
|
||||
push @z_steps, ($args->{Z} - $self->Z)
|
||||
if $started_extruding && exists $args->{Z};
|
||||
$travel_moves_after_first_extrusion++
|
||||
if $info->{travel} && $started_extruding && !exists $args->{Z};
|
||||
if ($cmd eq 'G1') {
|
||||
$started_extruding = 1 if $info->{extruding};
|
||||
push @z_steps, $info->{dist_Z}
|
||||
if $started_extruding && $info->{dist_Z} > 0;
|
||||
$travel_moves_after_first_extrusion++
|
||||
if $info->{travel} && $started_extruding && !exists $args->{Z};
|
||||
}
|
||||
});
|
||||
is $travel_moves_after_first_extrusion, 0, "no gaps in spiral vase ($description)";
|
||||
ok !(grep { $_ > $config->layer_height } @z_steps), "no gaps in Z ($description)";
|
||||
|
||||
# we allow one travel move after first extrusion: i.e. when moving to the first
|
||||
# spiral point after moving to second layer (bottom layer had loop clipping, so
|
||||
# we're slightly distant from the starting point of the loop)
|
||||
ok $travel_moves_after_first_extrusion <= 1, "no gaps in spiral vase ($description)";
|
||||
ok !(grep { $_ > $config->layer_height + epsilon } @z_steps), "no gaps in Z ($description)";
|
||||
};
|
||||
|
||||
$test->('20mm_cube', 'solid model');
|
||||
$test->('40x10', 'hollow model');
|
||||
|
||||
$config->set('z_offset', -10);
|
||||
$test->('20mm_cube', 'solid model with negative z-offset');
|
||||
|
||||
### Disabled because the current unreliable medial axis code doesn't
|
||||
### always produce valid loops.
|
||||
###$test->('40x10', 'hollow model with negative z-offset');
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('spiral_vase', 1);
|
||||
$config->set('bottom_solid_layers', 0);
|
||||
$config->set('skirts', 0);
|
||||
$config->set('first_layer_height', '100%');
|
||||
$config->set('layer_height', 0.4);
|
||||
$config->set('start_gcode', '');
|
||||
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
my $z_moves = 0;
|
||||
my @this_layer = (); # [ dist_Z, dist_XY ], ...
|
||||
|
||||
my $bottom_layer_not_flat = 0;
|
||||
my $null_z_moves_not_layer_changes = 0;
|
||||
my $null_z_moves_not_multiples_of_layer_height = 0;
|
||||
my $sum_of_partial_z_equals_to_layer_height = 0;
|
||||
my $all_layer_segments_have_same_slope = 0;
|
||||
my $horizontal_extrusions = 0;
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd eq 'G1') {
|
||||
if ($z_moves < 2) {
|
||||
# skip everything up to the second Z move
|
||||
# (i.e. start of second layer)
|
||||
if (exists $args->{Z}) {
|
||||
$z_moves++;
|
||||
$bottom_layer_not_flat = 1
|
||||
if $info->{dist_Z} > 0 && $info->{dist_Z} != $config->layer_height;
|
||||
}
|
||||
} elsif ($info->{dist_Z} == 0 && $args->{Z}) {
|
||||
$null_z_moves_not_layer_changes = 1
|
||||
if $info->{dist_XY} != 0;
|
||||
|
||||
# % doesn't work easily with floats
|
||||
$null_z_moves_not_multiples_of_layer_height = 1
|
||||
if abs(($args->{Z} / $config->layer_height) * $config->layer_height - $args->{Z}) > epsilon;
|
||||
|
||||
my $total_dist_XY = sum(map $_->[1], @this_layer);
|
||||
$sum_of_partial_z_equals_to_layer_height = 1
|
||||
if abs(sum(map $_->[0], @this_layer) - $config->layer_height) > epsilon;
|
||||
exit if $sum_of_partial_z_equals_to_layer_height;
|
||||
foreach my $segment (@this_layer) {
|
||||
# check that segment's dist_Z is proportioned to its dist_XY
|
||||
$all_layer_segments_have_same_slope = 1
|
||||
if abs($segment->[0]*$total_dist_XY/$config->layer_height - $segment->[1]) > epsilon;
|
||||
}
|
||||
|
||||
@this_layer = ();
|
||||
} elsif ($info->{extruding} && $info->{dist_XY} > 0) {
|
||||
$horizontal_extrusions = 1
|
||||
if $info->{dist_Z} == 0;
|
||||
push @this_layer, [ $info->{dist_Z}, $info->{dist_XY} ];
|
||||
}
|
||||
}
|
||||
});
|
||||
ok !$bottom_layer_not_flat, 'bottom layer is flat when using spiral vase';
|
||||
ok !$null_z_moves_not_layer_changes, 'null Z moves are layer changes';
|
||||
ok !$null_z_moves_not_multiples_of_layer_height, 'null Z moves are multiples of layer height';
|
||||
ok !$sum_of_partial_z_equals_to_layer_height, 'sum of partial Z increments equals to a full layer height';
|
||||
ok !$all_layer_segments_have_same_slope, 'all layer segments have the same slope';
|
||||
ok !$horizontal_extrusions, 'no horizontal extrusions';
|
||||
}
|
||||
|
||||
__END__
|
||||
|
31
t/support.t
31
t/support.t
@ -1,4 +1,4 @@
|
||||
use Test::More tests => 14;
|
||||
use Test::More tests => 15;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
@ -112,7 +112,7 @@ use Slic3r::Test;
|
||||
if ($layer_id <= $config->raft_layers) {
|
||||
# this is a raft layer or the first object layer
|
||||
my $line = Slic3r::Line->new_scale([ $self->X, $self->Y ], [ $info->{new_X}, $info->{new_Y} ]);
|
||||
my @path = $line->grow(scale($config->support_material_extrusion_width/2));
|
||||
my @path = @{$line->grow(scale($config->support_material_extrusion_width/2))};
|
||||
if ($layer_id < $config->raft_layers) {
|
||||
# this is a raft layer
|
||||
push @raft, @path;
|
||||
@ -129,4 +129,31 @@ use Slic3r::Test;
|
||||
'first object layer is completely supported by raft';
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('skirts', 0);
|
||||
$config->set('raft_layers', 2);
|
||||
$config->set('layer_height', 0.35);
|
||||
$config->set('first_layer_height', 0.3);
|
||||
$config->set('nozzle_diameter', [0.5]);
|
||||
$config->set('support_material_extruder', 2);
|
||||
$config->set('support_material_interface_extruder', 2);
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
my %raft_z = (); # z => 1
|
||||
my $tool = undef;
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd =~ /^T(\d+)/) {
|
||||
$tool = $1;
|
||||
} elsif ($info->{extruding} && $info->{dist_XY} > 0) {
|
||||
if ($tool == $config->support_material_extruder-1) {
|
||||
$raft_z{$self->Z} = 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
is scalar(keys %raft_z), $config->raft_layers, 'correct number of raft layers is generated';
|
||||
}
|
||||
|
||||
__END__
|
||||
|
48
t/thin.t
Normal file
48
t/thin.t
Normal file
@ -0,0 +1,48 @@
|
||||
use Test::More tests => 1;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
BEGIN {
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
}
|
||||
|
||||
use Slic3r;
|
||||
use List::Util qw(first);
|
||||
use Slic3r::Geometry qw(epsilon);
|
||||
use Slic3r::Test;
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('layer_height', 0.2);
|
||||
$config->set('first_layer_height', '100%');
|
||||
$config->set('extrusion_width', 0.5);
|
||||
$config->set('first_layer_extrusion_width', '200%'); # check this one too
|
||||
$config->set('skirts', 0);
|
||||
$config->set('thin_walls', 1);
|
||||
|
||||
my $print = Slic3r::Test::init_print('gt2_teeth', config => $config);
|
||||
|
||||
my %extrusion_paths = (); # Z => count of continuous extrusions
|
||||
my $extruding = 0;
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd eq 'G1') {
|
||||
if ($info->{extruding} && $info->{dist_XY}) {
|
||||
if (!$extruding) {
|
||||
$extrusion_paths{$self->Z} //= 0;
|
||||
$extrusion_paths{$self->Z}++;
|
||||
}
|
||||
$extruding = 1;
|
||||
} else {
|
||||
$extruding = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ok !(first { $_ != 3 } values %extrusion_paths),
|
||||
'no superfluous thin walls are generated for toothed profile';
|
||||
}
|
||||
|
||||
__END__
|
@ -18,6 +18,7 @@ $ARGV[0] or usage(1);
|
||||
|
||||
if (-e $ARGV[0]) {
|
||||
my $model = Slic3r::Format::STL->read_file($ARGV[0]);
|
||||
$model->objects->[0]->add_instance(offset => [0,0]);
|
||||
my $mesh = $model->mesh;
|
||||
$mesh->repair;
|
||||
printf "VERTICES = %s\n", join ',', map "[$_->[0],$_->[1],$_->[2]]", @{$mesh->vertices};
|
||||
|
@ -36,6 +36,7 @@ my $build = Module::Build::WithXSpp->new(
|
||||
cstring
|
||||
cstdlib
|
||||
ostream
|
||||
sstream
|
||||
)]
|
||||
);
|
||||
|
||||
|
@ -111,12 +111,12 @@ ConfigBase::get(t_config_option_key opt_key) {
|
||||
return newRV_noinc((SV*)av);
|
||||
} else if (ConfigOptionString* optv = dynamic_cast<ConfigOptionString*>(opt)) {
|
||||
// we don't serialize() because that would escape newlines
|
||||
return newSVpvn(optv->value.c_str(), optv->value.length());
|
||||
return newSVpvn_utf8(optv->value.c_str(), optv->value.length(), true);
|
||||
} else if (ConfigOptionStrings* optv = dynamic_cast<ConfigOptionStrings*>(opt)) {
|
||||
AV* av = newAV();
|
||||
av_fill(av, optv->values.size()-1);
|
||||
for (std::vector<std::string>::iterator it = optv->values.begin(); it != optv->values.end(); ++it)
|
||||
av_store(av, it - optv->values.begin(), newSVpvn(it->c_str(), it->length()));
|
||||
av_store(av, it - optv->values.begin(), newSVpvn_utf8(it->c_str(), it->length(), true));
|
||||
return newRV_noinc((SV*)av);
|
||||
} else if (ConfigOptionPoint* optv = dynamic_cast<ConfigOptionPoint*>(opt)) {
|
||||
return optv->point.to_SV_pureperl();
|
||||
@ -136,7 +136,7 @@ ConfigBase::get(t_config_option_key opt_key) {
|
||||
return newRV_noinc((SV*)av);
|
||||
} else {
|
||||
std::string serialized = opt->serialize();
|
||||
return newSVpvn(serialized.c_str(), serialized.length());
|
||||
return newSVpvn_utf8(serialized.c_str(), serialized.length(), true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,7 +152,7 @@ ConfigBase::get_at(t_config_option_key opt_key, size_t i) {
|
||||
} else if (ConfigOptionStrings* optv = dynamic_cast<ConfigOptionStrings*>(opt)) {
|
||||
// we don't serialize() because that would escape newlines
|
||||
std::string val = optv->get_at(i);
|
||||
return newSVpvn(val.c_str(), val.length());
|
||||
return newSVpvn_utf8(val.c_str(), val.length(), true);
|
||||
} else if (ConfigOptionPoints* optv = dynamic_cast<ConfigOptionPoints*>(opt)) {
|
||||
return optv->get_at(i).to_SV_pureperl();
|
||||
} else if (ConfigOptionBools* optv = dynamic_cast<ConfigOptionBools*>(opt)) {
|
||||
@ -289,6 +289,11 @@ DynamicConfig::keys(t_config_option_keys *keys) {
|
||||
keys->push_back(it->first);
|
||||
}
|
||||
|
||||
void
|
||||
DynamicConfig::erase(const t_config_option_key opt_key) {
|
||||
this->options.erase(opt_key);
|
||||
}
|
||||
|
||||
void
|
||||
StaticConfig::keys(t_config_option_keys *keys) {
|
||||
for (t_optiondef_map::const_iterator it = this->def->begin(); it != this->def->end(); ++it) {
|
||||
|
@ -1,9 +1,7 @@
|
||||
#ifndef slic3r_Config_hpp_
|
||||
#define slic3r_Config_hpp_
|
||||
|
||||
#include <myinit.h>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <climits>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
@ -11,6 +9,7 @@
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <myinit.h>
|
||||
#include "Point.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
@ -379,7 +378,6 @@ class ConfigOptionDef
|
||||
std::string tooltip;
|
||||
std::string sidetext;
|
||||
std::string cli;
|
||||
std::string scope;
|
||||
t_config_option_key ratio_over;
|
||||
bool multiline;
|
||||
bool full_width;
|
||||
@ -430,6 +428,7 @@ class DynamicConfig : public ConfigBase
|
||||
~DynamicConfig();
|
||||
ConfigOption* option(const t_config_option_key opt_key, bool create = false);
|
||||
void keys(t_config_option_keys *keys);
|
||||
void erase(const t_config_option_key opt_key);
|
||||
|
||||
private:
|
||||
DynamicConfig(const DynamicConfig& other); // we disable this by making it private and unimplemented
|
||||
|
@ -20,6 +20,7 @@ ExPolygon::operator Points() const
|
||||
ExPolygon::operator Polygons() const
|
||||
{
|
||||
Polygons polygons;
|
||||
polygons.reserve(this->holes.size() + 1);
|
||||
polygons.push_back(this->contour);
|
||||
for (Polygons::const_iterator it = this->holes.begin(); it != this->holes.end(); ++it) {
|
||||
polygons.push_back(*it);
|
||||
@ -77,7 +78,7 @@ ExPolygon::is_valid() const
|
||||
bool
|
||||
ExPolygon::contains_line(const Line* line) const
|
||||
{
|
||||
Polylines pl(1);
|
||||
Polylines pl;
|
||||
pl.push_back(*line);
|
||||
|
||||
Polylines pl_out;
|
||||
@ -98,7 +99,8 @@ ExPolygon::contains_point(const Point* point) const
|
||||
Polygons
|
||||
ExPolygon::simplify_p(double tolerance) const
|
||||
{
|
||||
Polygons pp(this->holes.size() + 1);
|
||||
Polygons pp;
|
||||
pp.reserve(this->holes.size() + 1);
|
||||
|
||||
// contour
|
||||
Polygon p = this->contour;
|
||||
@ -211,6 +213,8 @@ void
|
||||
ExPolygon::from_SV_check(SV* expoly_sv)
|
||||
{
|
||||
if (sv_isobject(expoly_sv) && (SvTYPE(SvRV(expoly_sv)) == SVt_PVMG)) {
|
||||
if (!sv_isa(expoly_sv, "Slic3r::ExPolygon") && !sv_isa(expoly_sv, "Slic3r::ExPolygon::Ref"))
|
||||
CONFESS("Not a valid Slic3r::ExPolygon object");
|
||||
// a XS ExPolygon was supplied
|
||||
*this = *(ExPolygon *)SvIV((SV*)SvRV( expoly_sv ));
|
||||
} else {
|
||||
|
@ -1,9 +1,19 @@
|
||||
#include "Line.hpp"
|
||||
#include "Polyline.hpp"
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
std::string
|
||||
Line::wkt() const
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << "LINESTRING(" << this->a.x << " " << this->a.y << ","
|
||||
<< this->b.x << " " << this->b.y << ")";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
Line::operator Polyline() const
|
||||
{
|
||||
Polyline pl;
|
||||
@ -88,6 +98,8 @@ void
|
||||
Line::from_SV_check(SV* line_sv)
|
||||
{
|
||||
if (sv_isobject(line_sv) && (SvTYPE(SvRV(line_sv)) == SVt_PVMG)) {
|
||||
if (!sv_isa(line_sv, "Slic3r::Line") && !sv_isa(line_sv, "Slic3r::Line::Ref"))
|
||||
CONFESS("Not a valid Slic3r::Line object");
|
||||
*this = *(Line*)SvIV((SV*)SvRV( line_sv ));
|
||||
} else {
|
||||
this->from_SV(line_sv);
|
||||
|
@ -17,6 +17,7 @@ class Line
|
||||
Point b;
|
||||
Line() {};
|
||||
explicit Line(Point _a, Point _b): a(_a), b(_b) {};
|
||||
std::string wkt() const;
|
||||
operator Polyline() const;
|
||||
void scale(double factor);
|
||||
void translate(double x, double y);
|
||||
|
@ -1,14 +1,24 @@
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
#include "Point.hpp"
|
||||
#include "Line.hpp"
|
||||
#include <cmath>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
bool
|
||||
Point::operator==(const Point& rhs) const {
|
||||
Point::operator==(const Point& rhs) const
|
||||
{
|
||||
return this->coincides_with(rhs);
|
||||
}
|
||||
|
||||
std::string
|
||||
Point::wkt() const
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << "POINT(" << this->x << " " << this->y << ")";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void
|
||||
Point::scale(double factor)
|
||||
{
|
||||
@ -105,8 +115,8 @@ Point::distance_to(const Line &line) const
|
||||
{
|
||||
if (line.a.coincides_with(&line.b)) return this->distance_to(&line.a);
|
||||
|
||||
double n = (line.b.x - line.a.x) * (line.a.y - this->y)
|
||||
- (line.a.x - this->x) * (line.b.y - line.a.y);
|
||||
double n = (double)(line.b.x - line.a.x) * (double)(line.a.y - this->y)
|
||||
- (double)(line.a.x - this->x) * (double)(line.b.y - line.a.y);
|
||||
|
||||
return std::abs(n) / line.length();
|
||||
}
|
||||
@ -121,7 +131,7 @@ Point::distance_to(const Line &line) const
|
||||
double
|
||||
Point::ccw(const Point &p1, const Point &p2) const
|
||||
{
|
||||
return (p2.x - p1.x)*(this->y - p1.y) - (p2.y - p1.y)*(this->x - p1.x);
|
||||
return (double)(p2.x - p1.x)*(double)(this->y - p1.y) - (double)(p2.y - p1.y)*(double)(this->x - p1.x);
|
||||
}
|
||||
|
||||
double
|
||||
@ -174,6 +184,8 @@ void
|
||||
Point::from_SV_check(SV* point_sv)
|
||||
{
|
||||
if (sv_isobject(point_sv) && (SvTYPE(SvRV(point_sv)) == SVt_PVMG)) {
|
||||
if (!sv_isa(point_sv, "Slic3r::Point") && !sv_isa(point_sv, "Slic3r::Point::Ref"))
|
||||
CONFESS("Not a valid Slic3r::Point object");
|
||||
*this = *(Point*)SvIV((SV*)SvRV( point_sv ));
|
||||
} else {
|
||||
this->from_SV(point_sv);
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <vector>
|
||||
#include <math.h>
|
||||
#include <boost/polygon/polygon.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@ -22,6 +23,7 @@ class Point
|
||||
coord_t y;
|
||||
explicit Point(coord_t _x = 0, coord_t _y = 0): x(_x), y(_y) {};
|
||||
bool operator==(const Point& rhs) const;
|
||||
std::string wkt() const;
|
||||
void scale(double factor);
|
||||
void translate(double x, double y);
|
||||
void rotate(double angle, Point* center);
|
||||
|
@ -172,6 +172,15 @@ Polygon::to_SV_clone_ref() const {
|
||||
sv_setref_pv( sv, "Slic3r::Polygon", new Polygon(*this) );
|
||||
return sv;
|
||||
}
|
||||
|
||||
void
|
||||
Polygon::from_SV_check(SV* poly_sv)
|
||||
{
|
||||
if (sv_isobject(poly_sv) && !sv_isa(poly_sv, "Slic3r::Polygon") && !sv_isa(poly_sv, "Slic3r::Polygon::Ref"))
|
||||
CONFESS("Not a valid Slic3r::Polygon object");
|
||||
|
||||
MultiPoint::from_SV_check(poly_sv);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ class Polygon : public MultiPoint {
|
||||
void simplify(double tolerance, Polygons &polygons) const;
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
void from_SV_check(SV* poly_sv);
|
||||
SV* to_SV_ref();
|
||||
SV* to_SV_clone_ref() const;
|
||||
#endif
|
||||
|
@ -5,7 +5,7 @@ namespace Slic3r {
|
||||
|
||||
Polyline::operator Polylines() const
|
||||
{
|
||||
Polylines polylines(1);
|
||||
Polylines polylines;
|
||||
polylines.push_back(*this);
|
||||
return polylines;
|
||||
}
|
||||
@ -110,6 +110,15 @@ Polyline::to_SV_clone_ref() const
|
||||
sv_setref_pv( sv, "Slic3r::Polyline", new Polyline(*this) );
|
||||
return sv;
|
||||
}
|
||||
|
||||
void
|
||||
Polyline::from_SV_check(SV* poly_sv)
|
||||
{
|
||||
if (!sv_isa(poly_sv, "Slic3r::Polyline") && !sv_isa(poly_sv, "Slic3r::Polyline::Ref"))
|
||||
CONFESS("Not a valid Slic3r::Polyline object");
|
||||
|
||||
MultiPoint::from_SV_check(poly_sv);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ class Polyline : public MultiPoint {
|
||||
void simplify(double tolerance);
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
void from_SV_check(SV* poly_sv);
|
||||
SV* to_SV_ref();
|
||||
SV* to_SV_clone_ref() const;
|
||||
#endif
|
||||
|
@ -82,7 +82,6 @@ class PrintConfigDef
|
||||
Options["bottom_solid_layers"].category = "Layers and Perimeters";
|
||||
Options["bottom_solid_layers"].tooltip = "Number of solid layers to generate on bottom surfaces.";
|
||||
Options["bottom_solid_layers"].cli = "bottom-solid-layers=i";
|
||||
Options["bottom_solid_layers"].scope = "object";
|
||||
Options["bottom_solid_layers"].full_label = "Bottom solid layers";
|
||||
|
||||
Options["bridge_acceleration"].type = coFloat;
|
||||
@ -171,7 +170,6 @@ class PrintConfigDef
|
||||
Options["extra_perimeters"].category = "Layers and Perimeters";
|
||||
Options["extra_perimeters"].tooltip = "Add more perimeters when needed for avoiding gaps in sloping walls.";
|
||||
Options["extra_perimeters"].cli = "extra-perimeters!";
|
||||
Options["extra_perimeters"].scope = "object";
|
||||
|
||||
Options["extruder"].type = coInt;
|
||||
Options["extruder"].label = "Extruder";
|
||||
@ -211,6 +209,7 @@ class PrintConfigDef
|
||||
|
||||
Options["extrusion_width"].type = coFloatOrPercent;
|
||||
Options["extrusion_width"].label = "Default extrusion width";
|
||||
Options["extrusion_width"].category = "Extrusion Width";
|
||||
Options["extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width. If left to zero, Slic3r calculates a width automatically. If expressed as percentage (for example: 230%) it will be computed over layer height.";
|
||||
Options["extrusion_width"].sidetext = "mm or % (leave 0 for auto)";
|
||||
Options["extrusion_width"].cli = "extrusion-width=s";
|
||||
@ -236,6 +235,7 @@ class PrintConfigDef
|
||||
|
||||
Options["fill_angle"].type = coInt;
|
||||
Options["fill_angle"].label = "Fill angle";
|
||||
Options["fill_angle"].category = "Infill";
|
||||
Options["fill_angle"].tooltip = "Default base angle for infill orientation. Cross-hatching will be applied to this. Bridges will be infilled using the best direction Slic3r can detect, so this setting does not affect them.";
|
||||
Options["fill_angle"].sidetext = "°";
|
||||
Options["fill_angle"].cli = "fill-angle=i";
|
||||
@ -246,14 +246,12 @@ class PrintConfigDef
|
||||
Options["fill_density"].category = "Infill";
|
||||
Options["fill_density"].tooltip = "Density of internal infill, expressed in the range 0 - 1.";
|
||||
Options["fill_density"].cli = "fill-density=f";
|
||||
Options["fill_density"].scope = "object";
|
||||
|
||||
Options["fill_pattern"].type = coEnum;
|
||||
Options["fill_pattern"].label = "Fill pattern";
|
||||
Options["fill_pattern"].category = "Infill";
|
||||
Options["fill_pattern"].tooltip = "Fill pattern for general low-density infill.";
|
||||
Options["fill_pattern"].cli = "fill-pattern=s";
|
||||
Options["fill_pattern"].scope = "object";
|
||||
Options["fill_pattern"].enum_keys_map = ConfigOptionEnum<InfillPattern>::get_enum_values();
|
||||
Options["fill_pattern"].enum_values.push_back("rectilinear");
|
||||
Options["fill_pattern"].enum_values.push_back("line");
|
||||
@ -285,12 +283,14 @@ class PrintConfigDef
|
||||
|
||||
Options["first_layer_extrusion_width"].type = coFloatOrPercent;
|
||||
Options["first_layer_extrusion_width"].label = "First layer";
|
||||
Options["first_layer_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for first layer. You can use this to force fatter extrudates for better adhesion. If expressed as percentage (for example 120%) if will be computed over layer height.";
|
||||
Options["first_layer_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for first layer. You can use this to force fatter extrudates for better adhesion. If expressed as percentage (for example 120%) if will be computed over first layer height.";
|
||||
Options["first_layer_extrusion_width"].sidetext = "mm or % (leave 0 for default)";
|
||||
Options["first_layer_extrusion_width"].cli = "first-layer-extrusion-width=s";
|
||||
Options["first_layer_extrusion_width"].ratio_over = "first_layer_height";
|
||||
|
||||
Options["first_layer_height"].type = coFloatOrPercent;
|
||||
Options["first_layer_height"].label = "First layer height";
|
||||
Options["first_layer_height"].category = "Layers and Perimeters";
|
||||
Options["first_layer_height"].tooltip = "When printing with very low layer heights, you might still want to print a thicker bottom layer to improve adhesion and tolerance for non perfect build plates. This can be expressed as an absolute value or as a percentage (for example: 150%) over the default layer height.";
|
||||
Options["first_layer_height"].sidetext = "mm or %";
|
||||
Options["first_layer_height"].cli = "first-layer-height=s";
|
||||
@ -360,17 +360,18 @@ class PrintConfigDef
|
||||
Options["infill_every_layers"].tooltip = "This feature allows to combine infill and speed up your print by extruding thicker infill layers while preserving thin perimeters, thus accuracy.";
|
||||
Options["infill_every_layers"].sidetext = "layers";
|
||||
Options["infill_every_layers"].cli = "infill-every-layers=i";
|
||||
Options["infill_every_layers"].scope = "object";
|
||||
Options["infill_every_layers"].full_label = "Combine infill every n layers";
|
||||
Options["infill_every_layers"].min = 1;
|
||||
|
||||
Options["infill_extruder"].type = coInt;
|
||||
Options["infill_extruder"].label = "Infill extruder";
|
||||
Options["infill_extruder"].category = "Extruders";
|
||||
Options["infill_extruder"].tooltip = "The extruder to use when printing infill.";
|
||||
Options["infill_extruder"].cli = "infill-extruder=i";
|
||||
|
||||
Options["infill_extrusion_width"].type = coFloatOrPercent;
|
||||
Options["infill_extrusion_width"].label = "Infill";
|
||||
Options["infill_extrusion_width"].category = "Extrusion Width";
|
||||
Options["infill_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for infill. You may want to use fatter extrudates to speed up the infill and make your parts stronger. If expressed as percentage (for example 90%) if will be computed over layer height.";
|
||||
Options["infill_extrusion_width"].sidetext = "mm or % (leave 0 for default)";
|
||||
Options["infill_extrusion_width"].cli = "infill-extrusion-width=s";
|
||||
@ -385,7 +386,6 @@ class PrintConfigDef
|
||||
Options["infill_only_where_needed"].category = "Infill";
|
||||
Options["infill_only_where_needed"].tooltip = "This option will limit infill to the areas actually needed for supporting ceilings (it will act as internal support material).";
|
||||
Options["infill_only_where_needed"].cli = "infill-only-where-needed!";
|
||||
Options["infill_only_where_needed"].scope = "object";
|
||||
|
||||
Options["infill_speed"].type = coFloat;
|
||||
Options["infill_speed"].label = "Infill";
|
||||
@ -405,6 +405,7 @@ class PrintConfigDef
|
||||
|
||||
Options["layer_height"].type = coFloat;
|
||||
Options["layer_height"].label = "Layer height";
|
||||
Options["layer_height"].category = "Layers and Perimeters";
|
||||
Options["layer_height"].tooltip = "This setting controls the height (and thus the total number) of the slices/layers. Thinner layers give better accuracy but take more time to print.";
|
||||
Options["layer_height"].sidetext = "mm";
|
||||
Options["layer_height"].cli = "layer-height=f";
|
||||
@ -472,7 +473,6 @@ class PrintConfigDef
|
||||
Options["overhangs"].category = "Layers and Perimeters";
|
||||
Options["overhangs"].tooltip = "Experimental option to adjust flow for overhangs (bridge flow will be used), to apply bridge speed to them and enable fan.";
|
||||
Options["overhangs"].cli = "overhangs!";
|
||||
Options["overhangs"].scope = "object";
|
||||
|
||||
Options["perimeter_acceleration"].type = coFloat;
|
||||
Options["perimeter_acceleration"].label = "Perimeters";
|
||||
@ -482,12 +482,14 @@ class PrintConfigDef
|
||||
|
||||
Options["perimeter_extruder"].type = coInt;
|
||||
Options["perimeter_extruder"].label = "Perimeter extruder";
|
||||
Options["perimeter_extruder"].category = "Extruders";
|
||||
Options["perimeter_extruder"].tooltip = "The extruder to use when printing perimeters.";
|
||||
Options["perimeter_extruder"].cli = "perimeter-extruder=i";
|
||||
Options["perimeter_extruder"].aliases.push_back("perimeters_extruder");
|
||||
|
||||
Options["perimeter_extrusion_width"].type = coFloatOrPercent;
|
||||
Options["perimeter_extrusion_width"].label = "Perimeters";
|
||||
Options["perimeter_extrusion_width"].category = "Extrusion Width";
|
||||
Options["perimeter_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for perimeters. You may want to use thinner extrudates to get more accurate surfaces. If expressed as percentage (for example 90%) if will be computed over layer height.";
|
||||
Options["perimeter_extrusion_width"].sidetext = "mm or % (leave 0 for default)";
|
||||
Options["perimeter_extrusion_width"].cli = "perimeter-extrusion-width=s";
|
||||
@ -505,7 +507,6 @@ class PrintConfigDef
|
||||
Options["perimeters"].category = "Layers and Perimeters";
|
||||
Options["perimeters"].tooltip = "This option sets the number of perimeters to generate for each layer. Note that Slic3r may increase this number automatically when it detects sloping surfaces which benefit from a higher number of perimeters if the Extra Perimeters option is enabled.";
|
||||
Options["perimeters"].cli = "perimeters=i";
|
||||
Options["perimeters"].scope = "object";
|
||||
Options["perimeters"].aliases.push_back("perimeter_offsets");
|
||||
|
||||
Options["post_process"].type = coStrings;
|
||||
@ -528,7 +529,6 @@ class PrintConfigDef
|
||||
Options["raft_layers"].tooltip = "The object will be raised by this number of layers, and support material will be generated under it.";
|
||||
Options["raft_layers"].sidetext = "layers";
|
||||
Options["raft_layers"].cli = "raft-layers=i";
|
||||
Options["raft_layers"].scope = "object";
|
||||
|
||||
Options["randomize_start"].type = coBool;
|
||||
Options["randomize_start"].label = "Randomize starting points";
|
||||
@ -627,7 +627,6 @@ class PrintConfigDef
|
||||
Options["solid_fill_pattern"].category = "Infill";
|
||||
Options["solid_fill_pattern"].tooltip = "Fill pattern for top/bottom infill.";
|
||||
Options["solid_fill_pattern"].cli = "solid-fill-pattern=s";
|
||||
Options["solid_fill_pattern"].scope = "object";
|
||||
Options["solid_fill_pattern"].enum_keys_map = ConfigOptionEnum<InfillPattern>::get_enum_values();
|
||||
Options["solid_fill_pattern"].enum_values.push_back("rectilinear");
|
||||
Options["solid_fill_pattern"].enum_values.push_back("concentric");
|
||||
@ -646,7 +645,6 @@ class PrintConfigDef
|
||||
Options["solid_infill_below_area"].tooltip = "Force solid infill for regions having a smaller area than the specified threshold.";
|
||||
Options["solid_infill_below_area"].sidetext = "mm²";
|
||||
Options["solid_infill_below_area"].cli = "solid-infill-below-area=f";
|
||||
Options["solid_infill_below_area"].scope = "object";
|
||||
|
||||
Options["solid_infill_every_layers"].type = coInt;
|
||||
Options["solid_infill_every_layers"].label = "Solid infill every";
|
||||
@ -654,11 +652,11 @@ class PrintConfigDef
|
||||
Options["solid_infill_every_layers"].tooltip = "This feature allows to force a solid layer every given number of layers. Zero to disable.";
|
||||
Options["solid_infill_every_layers"].sidetext = "layers";
|
||||
Options["solid_infill_every_layers"].cli = "solid-infill-every-layers=i";
|
||||
Options["solid_infill_every_layers"].scope = "object";
|
||||
Options["solid_infill_every_layers"].min = 0;
|
||||
|
||||
Options["solid_infill_extrusion_width"].type = coFloatOrPercent;
|
||||
Options["solid_infill_extrusion_width"].label = "Solid infill";
|
||||
Options["solid_infill_extrusion_width"].category = "Extrusion Width";
|
||||
Options["solid_infill_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for infill for solid surfaces. If expressed as percentage (for example 90%) if will be computed over layer height.";
|
||||
Options["solid_infill_extrusion_width"].sidetext = "mm or % (leave 0 for default)";
|
||||
Options["solid_infill_extrusion_width"].cli = "solid-infill-extrusion-width=s";
|
||||
@ -714,7 +712,6 @@ class PrintConfigDef
|
||||
Options["support_material"].category = "Support material";
|
||||
Options["support_material"].tooltip = "Enable support material generation.";
|
||||
Options["support_material"].cli = "support-material!";
|
||||
Options["support_material"].scope = "object";
|
||||
|
||||
Options["support_material_angle"].type = coInt;
|
||||
Options["support_material_angle"].label = "Pattern angle";
|
||||
@ -722,7 +719,6 @@ class PrintConfigDef
|
||||
Options["support_material_angle"].tooltip = "Use this setting to rotate the support material pattern on the horizontal plane.";
|
||||
Options["support_material_angle"].sidetext = "°";
|
||||
Options["support_material_angle"].cli = "support-material-angle=i";
|
||||
Options["support_material_angle"].scope = "object";
|
||||
|
||||
Options["support_material_enforce_layers"].type = coInt;
|
||||
Options["support_material_enforce_layers"].label = "Enforce support for the first";
|
||||
@ -730,22 +726,24 @@ class PrintConfigDef
|
||||
Options["support_material_enforce_layers"].tooltip = "Generate support material for the specified number of layers counting from bottom, regardless of whether normal support material is enabled or not and regardless of any angle threshold. This is useful for getting more adhesion of objects having a very thin or poor footprint on the build plate.";
|
||||
Options["support_material_enforce_layers"].sidetext = "layers";
|
||||
Options["support_material_enforce_layers"].cli = "support-material-enforce-layers=f";
|
||||
Options["support_material_enforce_layers"].scope = "object";
|
||||
Options["support_material_enforce_layers"].full_label = "Enforce support for the first n layers";
|
||||
|
||||
Options["support_material_extruder"].type = coInt;
|
||||
Options["support_material_extruder"].label = "Support material extruder";
|
||||
Options["support_material_extruder"].category = "Extruders";
|
||||
Options["support_material_extruder"].tooltip = "The extruder to use when printing support material. This affects brim and raft too.";
|
||||
Options["support_material_extruder"].cli = "support-material-extruder=i";
|
||||
|
||||
Options["support_material_extrusion_width"].type = coFloatOrPercent;
|
||||
Options["support_material_extrusion_width"].label = "Support material";
|
||||
Options["support_material_extrusion_width"].category = "Extrusion Width";
|
||||
Options["support_material_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for support material. If expressed as percentage (for example 90%) if will be computed over layer height.";
|
||||
Options["support_material_extrusion_width"].sidetext = "mm or % (leave 0 for default)";
|
||||
Options["support_material_extrusion_width"].cli = "support-material-extrusion-width=s";
|
||||
|
||||
Options["support_material_interface_extruder"].type = coInt;
|
||||
Options["support_material_interface_extruder"].label = "Support material interface extruder";
|
||||
Options["support_material_interface_extruder"].category = "Extruders";
|
||||
Options["support_material_interface_extruder"].tooltip = "The extruder to use when printing support material interface. This affects raft too.";
|
||||
Options["support_material_interface_extruder"].cli = "support-material-interface-extruder=i";
|
||||
|
||||
@ -755,7 +753,6 @@ class PrintConfigDef
|
||||
Options["support_material_interface_layers"].tooltip = "Number of interface layers to insert between the object(s) and support material.";
|
||||
Options["support_material_interface_layers"].sidetext = "layers";
|
||||
Options["support_material_interface_layers"].cli = "support-material-interface-layers=i";
|
||||
Options["support_material_interface_layers"].scope = "object";
|
||||
|
||||
Options["support_material_interface_spacing"].type = coFloat;
|
||||
Options["support_material_interface_spacing"].label = "Interface pattern spacing";
|
||||
@ -763,14 +760,12 @@ class PrintConfigDef
|
||||
Options["support_material_interface_spacing"].tooltip = "Spacing between interface lines. Set zero to get a solid interface.";
|
||||
Options["support_material_interface_spacing"].sidetext = "mm";
|
||||
Options["support_material_interface_spacing"].cli = "support-material-interface-spacing=f";
|
||||
Options["support_material_interface_spacing"].scope = "object";
|
||||
|
||||
Options["support_material_pattern"].type = coEnum;
|
||||
Options["support_material_pattern"].label = "Pattern";
|
||||
Options["support_material_pattern"].category = "Support material";
|
||||
Options["support_material_pattern"].tooltip = "Pattern used to generate support material.";
|
||||
Options["support_material_pattern"].cli = "support-material-pattern=s";
|
||||
Options["support_material_pattern"].scope = "object";
|
||||
Options["support_material_pattern"].enum_keys_map = ConfigOptionEnum<SupportMaterialPattern>::get_enum_values();
|
||||
Options["support_material_pattern"].enum_values.push_back("rectilinear");
|
||||
Options["support_material_pattern"].enum_values.push_back("rectilinear-grid");
|
||||
@ -787,10 +782,10 @@ class PrintConfigDef
|
||||
Options["support_material_spacing"].tooltip = "Spacing between support material lines.";
|
||||
Options["support_material_spacing"].sidetext = "mm";
|
||||
Options["support_material_spacing"].cli = "support-material-spacing=f";
|
||||
Options["support_material_spacing"].scope = "object";
|
||||
|
||||
Options["support_material_speed"].type = coFloat;
|
||||
Options["support_material_speed"].label = "Support material";
|
||||
Options["support_material_speed"].category = "Support material";
|
||||
Options["support_material_speed"].tooltip = "Speed for printing support material.";
|
||||
Options["support_material_speed"].sidetext = "mm/s";
|
||||
Options["support_material_speed"].cli = "support-material-speed=f";
|
||||
@ -801,7 +796,6 @@ class PrintConfigDef
|
||||
Options["support_material_threshold"].tooltip = "Support material will not be generated for overhangs whose slope angle (90° = vertical) is above the given threshold. In other words, this value represent the most horizontal slope (measured from the horizontal plane) that you can print without support material. Set to zero for automatic detection (recommended).";
|
||||
Options["support_material_threshold"].sidetext = "°";
|
||||
Options["support_material_threshold"].cli = "support-material-threshold=i";
|
||||
Options["support_material_threshold"].scope = "object";
|
||||
|
||||
Options["temperature"].type = coInts;
|
||||
Options["temperature"].label = "Other layers";
|
||||
@ -816,7 +810,6 @@ class PrintConfigDef
|
||||
Options["thin_walls"].category = "Layers and Perimeters";
|
||||
Options["thin_walls"].tooltip = "Detect single-width walls (parts where two extrusions don't fit and we need to collapse them into a single trace).";
|
||||
Options["thin_walls"].cli = "thin-walls!";
|
||||
Options["thin_walls"].scope = "object";
|
||||
|
||||
Options["threads"].type = coInt;
|
||||
Options["threads"].label = "Threads";
|
||||
@ -837,6 +830,7 @@ class PrintConfigDef
|
||||
|
||||
Options["top_infill_extrusion_width"].type = coFloatOrPercent;
|
||||
Options["top_infill_extrusion_width"].label = "Top solid infill";
|
||||
Options["top_infill_extrusion_width"].category = "Extrusion Width";
|
||||
Options["top_infill_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for infill for top surfaces. You may want to use thinner extrudates to fill all narrow regions and get a smoother finish. If expressed as percentage (for example 90%) if will be computed over layer height.";
|
||||
Options["top_infill_extrusion_width"].sidetext = "mm or % (leave 0 for default)";
|
||||
Options["top_infill_extrusion_width"].cli = "top-infill-extrusion-width=s";
|
||||
@ -853,7 +847,6 @@ class PrintConfigDef
|
||||
Options["top_solid_layers"].category = "Layers and Perimeters";
|
||||
Options["top_solid_layers"].tooltip = "Number of solid layers to generate on top surfaces.";
|
||||
Options["top_solid_layers"].cli = "top-solid-layers=i";
|
||||
Options["top_solid_layers"].scope = "object";
|
||||
Options["top_solid_layers"].full_label = "Top solid layers";
|
||||
|
||||
Options["travel_speed"].type = coFloat;
|
||||
@ -981,7 +974,6 @@ class PrintRegionConfig : public virtual StaticConfig
|
||||
ConfigOptionFloat solid_infill_below_area;
|
||||
ConfigOptionFloatOrPercent solid_infill_extrusion_width;
|
||||
ConfigOptionInt solid_infill_every_layers;
|
||||
ConfigOptionInt solid_layers;
|
||||
ConfigOptionBool thin_walls;
|
||||
ConfigOptionFloatOrPercent top_infill_extrusion_width;
|
||||
ConfigOptionInt top_solid_layers;
|
||||
@ -1029,7 +1021,6 @@ class PrintRegionConfig : public virtual StaticConfig
|
||||
if (opt_key == "solid_infill_below_area") return &this->solid_infill_below_area;
|
||||
if (opt_key == "solid_infill_extrusion_width") return &this->solid_infill_extrusion_width;
|
||||
if (opt_key == "solid_infill_every_layers") return &this->solid_infill_every_layers;
|
||||
if (opt_key == "solid_layers") return &this->solid_layers;
|
||||
if (opt_key == "thin_walls") return &this->thin_walls;
|
||||
if (opt_key == "top_infill_extrusion_width") return &this->top_infill_extrusion_width;
|
||||
if (opt_key == "top_solid_layers") return &this->top_solid_layers;
|
||||
|
@ -16,6 +16,13 @@ Surface::is_solid() const
|
||||
|| this->surface_type == stInternalSolid;
|
||||
}
|
||||
|
||||
bool
|
||||
Surface::is_external() const
|
||||
{
|
||||
return this->surface_type == stTop
|
||||
|| this->surface_type == stBottom;
|
||||
}
|
||||
|
||||
bool
|
||||
Surface::is_bridge() const
|
||||
{
|
||||
@ -24,6 +31,15 @@ Surface::is_bridge() const
|
||||
}
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
void
|
||||
Surface::from_SV_check(SV* surface_sv)
|
||||
{
|
||||
if (!sv_isa(surface_sv, "Slic3r::Surface") && !sv_isa(surface_sv, "Slic3r::Surface::Ref"))
|
||||
CONFESS("Not a valid Slic3r::Surface object");
|
||||
// a XS Surface was supplied
|
||||
*this = *(Surface *)SvIV((SV*)SvRV( surface_sv ));
|
||||
}
|
||||
|
||||
SV*
|
||||
Surface::to_SV_ref() {
|
||||
SV* sv = newSV(0);
|
||||
|
@ -18,9 +18,11 @@ class Surface
|
||||
unsigned short extra_perimeters;
|
||||
double area() const;
|
||||
bool is_solid() const;
|
||||
bool is_external() const;
|
||||
bool is_bridge() const;
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
void from_SV_check(SV* surface_sv);
|
||||
SV* to_SV_ref();
|
||||
SV* to_SV_clone_ref() const;
|
||||
#endif
|
||||
|
@ -21,14 +21,14 @@ SurfaceCollection::simplify(double tolerance)
|
||||
|
||||
/* group surfaces by common properties */
|
||||
void
|
||||
SurfaceCollection::group(std::vector<SurfacesPtr> *retval, bool merge_solid)
|
||||
SurfaceCollection::group(std::vector<SurfacesPtr> *retval)
|
||||
{
|
||||
for (Surfaces::iterator it = this->surfaces.begin(); it != this->surfaces.end(); ++it) {
|
||||
// find a group with the same properties
|
||||
SurfacesPtr* group = NULL;
|
||||
for (std::vector<SurfacesPtr>::iterator git = retval->begin(); git != retval->end(); ++git) {
|
||||
Surface* gkey = git->front();
|
||||
if ((gkey->surface_type == it->surface_type || (merge_solid && gkey->is_solid() && it->is_solid()))
|
||||
if ( gkey->surface_type == it->surface_type
|
||||
&& gkey->thickness == it->thickness
|
||||
&& gkey->thickness_layers == it->thickness_layers
|
||||
&& gkey->bridge_angle == it->bridge_angle) {
|
||||
|
@ -11,7 +11,7 @@ class SurfaceCollection
|
||||
public:
|
||||
Surfaces surfaces;
|
||||
void simplify(double tolerance);
|
||||
void group(std::vector<SurfacesPtr> *retval, bool merge_solid = false);
|
||||
void group(std::vector<SurfacesPtr> *retval);
|
||||
};
|
||||
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -12,6 +12,7 @@
|
||||
namespace Slic3r {
|
||||
|
||||
class TriangleMesh;
|
||||
class TriangleMeshSlicer;
|
||||
typedef std::vector<TriangleMesh*> TriangleMeshPtrs;
|
||||
|
||||
class TriangleMesh
|
||||
@ -30,8 +31,6 @@ class TriangleMesh
|
||||
void translate(float x, float y, float z);
|
||||
void align_to_origin();
|
||||
void rotate(double angle, Point* center);
|
||||
void slice(const std::vector<double> &z, std::vector<Polygons>* layers);
|
||||
void slice(const std::vector<double> &z, std::vector<ExPolygons>* layers);
|
||||
TriangleMeshPtrs split() const;
|
||||
void merge(const TriangleMesh* mesh);
|
||||
void horizontal_projection(ExPolygons &retval) const;
|
||||
@ -47,9 +46,10 @@ class TriangleMesh
|
||||
|
||||
private:
|
||||
void require_shared_vertices();
|
||||
friend class TriangleMeshSlicer;
|
||||
};
|
||||
|
||||
enum FacetEdgeType { feNone, feTop, feBottom };
|
||||
enum FacetEdgeType { feNone, feTop, feBottom, feHorizontal };
|
||||
|
||||
class IntersectionPoint : public Point
|
||||
{
|
||||
@ -75,6 +75,26 @@ class IntersectionLine
|
||||
typedef std::vector<IntersectionLine> IntersectionLines;
|
||||
typedef std::vector<IntersectionLine*> IntersectionLinePtrs;
|
||||
|
||||
class TriangleMeshSlicer
|
||||
{
|
||||
public:
|
||||
TriangleMesh* mesh;
|
||||
TriangleMeshSlicer(TriangleMesh* _mesh);
|
||||
~TriangleMeshSlicer();
|
||||
void slice(const std::vector<float> &z, std::vector<Polygons>* layers);
|
||||
void slice(const std::vector<float> &z, std::vector<ExPolygons>* layers);
|
||||
void slice_facet(float slice_z, const stl_facet &facet, const int &facet_idx, const float &min_z, const float &max_z, std::vector<IntersectionLine>* lines) const;
|
||||
void cut(float z, TriangleMesh* upper, TriangleMesh* lower);
|
||||
|
||||
private:
|
||||
typedef std::vector< std::vector<int> > t_facets_edges;
|
||||
t_facets_edges facets_edges;
|
||||
stl_vertex* v_scaled_shared;
|
||||
void make_loops(std::vector<IntersectionLine> &lines, Polygons* loops);
|
||||
void make_expolygons(const Polygons &loops, ExPolygons* slices);
|
||||
void make_expolygons(std::vector<IntersectionLine> &lines, ExPolygons* slices);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -52,7 +52,6 @@ static void stl_which_vertices_to_change(stl_file *stl, stl_hash_edge *edge_a,
|
||||
int *facet2, int *vertex2,
|
||||
stl_vertex *new_vertex1, stl_vertex *new_vertex2);
|
||||
static void stl_remove_degenerate(stl_file *stl, int facet);
|
||||
static void stl_add_facet(stl_file *stl, stl_facet *new_facet);
|
||||
extern int stl_check_normal_vector(stl_file *stl,
|
||||
int facet_num, int normal_fix_flag);
|
||||
static void stl_update_connects_remove_1(stl_file *stl, int facet_num);
|
||||
@ -1100,7 +1099,7 @@ Try using a smaller tolerance or don't do a nearby check\n"); */
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
void
|
||||
stl_add_facet(stl_file *stl, stl_facet *new_facet)
|
||||
{
|
||||
stl->stats.facets_added += 1;
|
||||
|
@ -180,4 +180,5 @@ extern void stl_allocate(stl_file *stl);
|
||||
static void stl_read(stl_file *stl, int first_facet, int first);
|
||||
extern void stl_facet_stats(stl_file *stl, stl_facet facet, int first);
|
||||
extern void stl_reallocate(stl_file *stl);
|
||||
extern void stl_add_facet(stl_file *stl, stl_facet *new_facet);
|
||||
extern void stl_get_size(stl_file *stl);
|
||||
|
@ -54,6 +54,7 @@ stl_initialize(stl_file *stl)
|
||||
stl->stats.number_of_parts = 0;
|
||||
stl->stats.original_num_facets = 0;
|
||||
stl->stats.number_of_facets = 0;
|
||||
stl->stats.facets_malloced = 0;
|
||||
stl->stats.volume = -1.0;
|
||||
|
||||
stl->neighbors_start = NULL;
|
||||
|
@ -6,6 +6,7 @@
|
||||
#undef seekdir
|
||||
#include <ostream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
extern "C" {
|
||||
@ -23,6 +24,7 @@ extern "C" {
|
||||
#define PI 3.141592653589793238
|
||||
#define scale_(val) (val / SCALING_FACTOR)
|
||||
#define unscale(val) (val * SCALING_FACTOR)
|
||||
#define SCALED_EPSILON scale_(EPSILON)
|
||||
typedef long coord_t;
|
||||
typedef double coordf_t;
|
||||
|
||||
|
@ -4,7 +4,7 @@ use strict;
|
||||
use warnings;
|
||||
|
||||
use Slic3r::XS;
|
||||
use Test::More tests => 41;
|
||||
use Test::More tests => 46;
|
||||
|
||||
is Slic3r::TriangleMesh::hello_world(), 'Hello world!',
|
||||
'hello world';
|
||||
@ -83,9 +83,42 @@ my $cube = {
|
||||
my $result = $m->slice(\@z);
|
||||
my $SCALING_FACTOR = 0.000001;
|
||||
for my $i (0..$#z) {
|
||||
is scalar(@{$result->[$i]}), 1, 'number of returned polygons per layer';
|
||||
is scalar(@{$result->[$i]}), 1, "number of returned polygons per layer (z = " . $z[$i] . ")";
|
||||
is $result->[$i][0]->area, 20*20/($SCALING_FACTOR**2), 'size of returned polygon';
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
my $m = Slic3r::TriangleMesh->new;
|
||||
$m->ReadFromPerl(
|
||||
[ [0,0,0],[0,0,20],[0,5,0],[0,5,20],[50,0,0],[50,0,20],[15,5,0],[35,5,0],[15,20,0],[50,5,0],[35,20,0],[15,5,10],[50,5,20],[35,5,10],[35,20,10],[15,20,10] ],
|
||||
[ [0,1,2],[2,1,3],[1,0,4],[5,1,4],[0,2,4],[4,2,6],[7,6,8],[4,6,7],[9,4,7],[7,8,10],[2,3,6],[11,3,12],[7,12,9],[13,12,7],[6,3,11],[11,12,13],[3,1,5],[12,3,5],[5,4,9],[12,5,9],[13,7,10],[14,13,10],[8,15,10],[10,15,14],[6,11,8],[8,11,15],[15,11,13],[14,15,13] ],
|
||||
);
|
||||
$m->repair;
|
||||
my $slices = $m->slice([ 5, 10 ]);
|
||||
is $slices->[0][0]->area, $slices->[1][0]->area, 'slicing a tangent plane includes its area';
|
||||
}
|
||||
|
||||
{
|
||||
my $m = Slic3r::TriangleMesh->new;
|
||||
$m->ReadFromPerl($cube->{vertices}, $cube->{facets});
|
||||
$m->repair;
|
||||
{
|
||||
my $upper = Slic3r::TriangleMesh->new;
|
||||
my $lower = Slic3r::TriangleMesh->new;
|
||||
$m->cut(0, $upper, $lower);
|
||||
#$upper->repair; $lower->repair;
|
||||
is $upper->facets_count, 10, 'upper mesh has all facets except those belonging to the slicing plane';
|
||||
is $lower->facets_count, 0, 'lower mesh has no facets';
|
||||
}
|
||||
{
|
||||
my $upper = Slic3r::TriangleMesh->new;
|
||||
my $lower = Slic3r::TriangleMesh->new;
|
||||
$m->cut(10, $upper, $lower);
|
||||
#$upper->repair; $lower->repair;
|
||||
is $upper->facets_count, 14, 'upper mesh has the right number of facets';
|
||||
is $lower->facets_count, 14, 'lower mesh has the right number of facets';
|
||||
}
|
||||
}
|
||||
|
||||
__END__
|
||||
|
@ -4,7 +4,7 @@ use strict;
|
||||
use warnings;
|
||||
|
||||
use Slic3r::XS;
|
||||
use Test::More tests => 8;
|
||||
use Test::More tests => 10;
|
||||
|
||||
my $point = Slic3r::Point->new(10, 15);
|
||||
is_deeply [ @$point ], [10, 15], 'point roundtrip';
|
||||
@ -30,4 +30,20 @@ ok !$point->coincides_with($point2), 'coincides_with';
|
||||
ok $nearest->coincides_with($point2), 'nearest_point';
|
||||
}
|
||||
|
||||
{
|
||||
my $line = Slic3r::Line->new(
|
||||
[18335846,18335845],
|
||||
[18335846,1664160],
|
||||
);
|
||||
$point = Slic3r::Point->new(1664161,18335848);
|
||||
is $point->distance_to_line($line), 16671685, 'distance_to_line() does not overflow';
|
||||
}
|
||||
|
||||
{
|
||||
my $p0 = Slic3r::Point->new(76975850,89989996);
|
||||
my $p1 = Slic3r::Point->new(76989990,109989991);
|
||||
my $p2 = Slic3r::Point->new(76989987,89989994);
|
||||
ok $p0->ccw($p1, $p2) < 0, 'ccw() does not overflow';
|
||||
}
|
||||
|
||||
__END__
|
||||
|
@ -4,7 +4,7 @@ use strict;
|
||||
use warnings;
|
||||
|
||||
use Slic3r::XS;
|
||||
use Test::More tests => 16;
|
||||
use Test::More tests => 15;
|
||||
|
||||
my $square = [ # ccw
|
||||
[100, 100],
|
||||
@ -71,7 +71,6 @@ is $surface->extra_perimeters, 2, 'extra_perimeters';
|
||||
);
|
||||
my $collection = Slic3r::Surface::Collection->new(@surfaces);
|
||||
is scalar(@{$collection->group}), 2, 'group() returns correct number of groups';
|
||||
is scalar(@{$collection->group(1)}), 1, 'group() returns correct number of solid groups';
|
||||
}
|
||||
|
||||
__END__
|
||||
|
@ -24,6 +24,7 @@
|
||||
%code{% THIS->apply(*other, true); %};
|
||||
std::vector<std::string> get_keys()
|
||||
%code{% THIS->keys(&RETVAL); %};
|
||||
void erase(t_config_option_key opt_key);
|
||||
};
|
||||
|
||||
%name{Slic3r::Config::Print} class PrintConfig {
|
||||
@ -146,14 +147,13 @@ print_config_def()
|
||||
throw "Unknown option type";
|
||||
}
|
||||
(void)hv_stores( hv, "type", newSVpv(opt_type, 0) );
|
||||
(void)hv_stores( hv, "label", newSVpvn(optdef->label.c_str(), optdef->label.length()) );
|
||||
(void)hv_stores( hv, "label", newSVpvn_utf8(optdef->label.c_str(), optdef->label.length(), true) );
|
||||
if (!optdef->full_label.empty())
|
||||
(void)hv_stores( hv, "full_label", newSVpvn(optdef->full_label.c_str(), optdef->full_label.length()) );
|
||||
(void)hv_stores( hv, "full_label", newSVpvn_utf8(optdef->full_label.c_str(), optdef->full_label.length(), true) );
|
||||
(void)hv_stores( hv, "category", newSVpvn(optdef->category.c_str(), optdef->category.length()) );
|
||||
(void)hv_stores( hv, "tooltip", newSVpvn(optdef->tooltip.c_str(), optdef->tooltip.length()) );
|
||||
(void)hv_stores( hv, "sidetext", newSVpvn(optdef->sidetext.c_str(), optdef->sidetext.length()) );
|
||||
(void)hv_stores( hv, "tooltip", newSVpvn_utf8(optdef->tooltip.c_str(), optdef->tooltip.length(), true) );
|
||||
(void)hv_stores( hv, "sidetext", newSVpvn_utf8(optdef->sidetext.c_str(), optdef->sidetext.length(), true) );
|
||||
(void)hv_stores( hv, "cli", newSVpvn(optdef->cli.c_str(), optdef->cli.length()) );
|
||||
(void)hv_stores( hv, "scope", newSVpvn(optdef->scope.c_str(), optdef->scope.length()) );
|
||||
(void)hv_stores( hv, "ratio_over", newSVpvn(optdef->ratio_over.c_str(), optdef->ratio_over.length()) );
|
||||
(void)hv_stores( hv, "multiline", newSViv(optdef->multiline ? 1 : 0) );
|
||||
(void)hv_stores( hv, "full_width", newSViv(optdef->full_width ? 1 : 0) );
|
||||
@ -195,7 +195,7 @@ print_config_def()
|
||||
AV* av = newAV();
|
||||
av_fill(av, optdef->enum_labels.size()-1);
|
||||
for (std::vector<std::string>::iterator it = optdef->enum_labels.begin(); it != optdef->enum_labels.end(); ++it)
|
||||
av_store(av, it - optdef->enum_labels.begin(), newSVpvn(it->c_str(), it->length()));
|
||||
av_store(av, it - optdef->enum_labels.begin(), newSVpvn_utf8(it->c_str(), it->length(), true));
|
||||
(void)hv_stores( hv, "labels", newRV_noinc((SV*)av) );
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,9 @@
|
||||
Point* nearest_point(Points points)
|
||||
%code{% const char* CLASS = "Slic3r::Point"; RETVAL = new Point(*(THIS->nearest_point(points))); %};
|
||||
double distance_to(Point* point);
|
||||
%name{distance_to_line} double distance_to(Line* line);
|
||||
double ccw(Point* p1, Point* p2)
|
||||
%code{% RETVAL = THIS->ccw(*p1, *p2); %};
|
||||
|
||||
%{
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
%code{% RETVAL = THIS->thickness_layers; %};
|
||||
double area();
|
||||
bool is_solid() const;
|
||||
bool is_external() const;
|
||||
bool is_bridge() const;
|
||||
%{
|
||||
|
||||
|
@ -22,7 +22,7 @@ SurfaceCollection::new(...)
|
||||
RETVAL->surfaces.resize(items-1);
|
||||
for (unsigned int i = 1; i < items; i++) {
|
||||
// Note: a COPY of the input is stored
|
||||
RETVAL->surfaces[i-1] = *(Surface *)SvIV((SV*)SvRV( ST(i) ));
|
||||
RETVAL->surfaces[i-1].from_SV_check(ST(i));
|
||||
}
|
||||
OUTPUT:
|
||||
RETVAL
|
||||
@ -56,8 +56,9 @@ void
|
||||
SurfaceCollection::append(...)
|
||||
CODE:
|
||||
for (unsigned int i = 1; i < items; i++) {
|
||||
// Note: a COPY of the input is stored
|
||||
THIS->surfaces.push_back(*(Surface *)SvIV((SV*)SvRV( ST(i) )));
|
||||
Surface surface;
|
||||
surface.from_SV_check( ST(i) );
|
||||
THIS->surfaces.push_back(surface);
|
||||
}
|
||||
|
||||
void
|
||||
@ -75,12 +76,11 @@ SurfaceCollection::set_surface_type(index, surface_type)
|
||||
THIS->surfaces[index].surface_type = surface_type;
|
||||
|
||||
SV*
|
||||
SurfaceCollection::group(merge_solid = false)
|
||||
bool merge_solid
|
||||
SurfaceCollection::group()
|
||||
CODE:
|
||||
// perform grouping
|
||||
std::vector<SurfacesPtr> groups;
|
||||
THIS->group(&groups, merge_solid);
|
||||
THIS->group(&groups);
|
||||
|
||||
// build return arrayref
|
||||
AV* av = newAV();
|
||||
|
@ -32,6 +32,8 @@
|
||||
RETVAL = new BoundingBoxf3();
|
||||
THIS->bounding_box(RETVAL);
|
||||
%};
|
||||
int facets_count()
|
||||
%code{% RETVAL = THIS->stl.stats.number_of_facets; %};
|
||||
%{
|
||||
|
||||
SV*
|
||||
@ -137,8 +139,13 @@ SV*
|
||||
TriangleMesh::slice(z)
|
||||
std::vector<double>* z
|
||||
CODE:
|
||||
// convert doubles to floats
|
||||
std::vector<float> z_f(z->begin(), z->end());
|
||||
delete z;
|
||||
|
||||
std::vector<ExPolygons> layers;
|
||||
THIS->slice(*z, &layers);
|
||||
TriangleMeshSlicer mslicer(THIS);
|
||||
mslicer.slice(z_f, &layers);
|
||||
|
||||
AV* layers_av = newAV();
|
||||
av_extend(layers_av, layers.size()-1);
|
||||
@ -155,6 +162,15 @@ TriangleMesh::slice(z)
|
||||
OUTPUT:
|
||||
RETVAL
|
||||
|
||||
void
|
||||
TriangleMesh::cut(z, upper, lower)
|
||||
float z;
|
||||
TriangleMesh* upper;
|
||||
TriangleMesh* lower;
|
||||
CODE:
|
||||
TriangleMeshSlicer mslicer(THIS);
|
||||
mslicer.cut(z, upper, lower);
|
||||
|
||||
std::vector<double>
|
||||
TriangleMesh::bb3()
|
||||
CODE:
|
||||
|
Loading…
Reference in New Issue
Block a user