mirror of
https://github.com/FULU-Foundation/OrcaSlicer-bambulab.git
synced 2026-10-11 22:32:55 +02:00
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
72 files changed
+1927
-948
No files matched your search
@@ -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 {};
|
||||
|
||||
+20
-8
@@ -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 {
|
||||
|
||||
+47
-1
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -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";
|
||||
}
|
||||
|
||||
+22
-1
@@ -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;
|
||||
@@ -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,
|
||||
|
||||
+46
-41
@@ -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",
|
||||
|
||||
+53
-26
@@ -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;
|
||||
|
||||
+72
-42
@@ -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);
|
||||
# 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};
|
||||
}
|
||||
|
||||
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 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
|
||||
|
||||
+74
-47
@@ -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;
|
||||
+2
-2
@@ -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')),
|
||||
},
|
||||
|
||||
@@ -76,6 +76,20 @@ sub model {
|
||||
$facets = [
|
||||
[0,1,2],[1,0,3],[4,5,6],[5,4,7],[8,9,10],[9,11,12],[11,9,8],[13,14,15],[14,13,16],[17,2,1],[2,17,18],[19,11,20],[11,19,12],[17,21,18],[21,2,18],[2,21,22],[22,21,23],[8,22,23],[22,8,10],[24,25,26],[25,24,27],[23,1,8],[1,23,21],[1,21,17],[5,15,3],[15,5,7],[15,7,28],[28,7,26],[28,26,25],[8,14,11],[14,8,3],[3,8,1],[14,3,15],[11,14,20],[26,4,24],[4,26,7],[12,16,9],[16,12,19],[29,4,13],[4,29,24],[24,29,27],[9,22,10],[22,9,0],[0,9,16],[0,16,13],[0,13,6],[6,13,4],[2,22,0],[19,14,16],[14,19,20],[15,29,13],[29,25,27],[25,29,15],[25,15,28],[6,3,0],[3,6,5]
|
||||
];
|
||||
} elsif ($model_name eq 'A') {
|
||||
$vertices = [
|
||||
[513.075988769531,51.6074333190918,36.0009002685547],[516.648803710938,51.7324333190918,36.0009002685547],[513.495178222656,51.7324333190918,36.0009002685547],[489.391204833984,51.4824333190918,24.0011005401611],[488.928588867188,51.7324333190918,24.0011005401611],[492.06201171875,51.7324333190918,24.0011005401611],[496.840393066406,51.2324333190918,24.0011005401611],[495.195404052734,51.7324333190918,24.0011005401611],[498.981994628906,51.7324333190918,24.0011005401611],[506.966613769531,51.6074333190918,24.0011005401611],[510.342010498047,51.7324333190918,24.0011005401611],[507.163818359375,51.6074333190918,24.0011005401611],[512.515380859375,54.7190322875977,36.0009002685547],[514.161987304688,54.5058326721191,36.0009002685547],[493.06201171875,54.7190322875977,36.0009002685547],[495.195404052734,51.7324333190918,36.0009002685547],[496.195404052734,54.7190322875977,36.0009002685547],[497.195404052734,57.7058334350586,36.0009002685547],[500.851989746094,60.2658309936523,36.0009002685547],[498.915405273438,62.8258323669434,36.0009002685547],[506.701995849609,62.8258323669434,36.0009002685547],[503.648590087891,60.2658309936523,36.0009002685547],[508.381805419922,57.7058334350586,36.0009002685547],[496.418792724609,60.052433013916,36.0009002685547],[506.515197753906,72.2124328613281,36.0009002685547],[502.808807373047,74.5324325561523,36.0009002685547],[503.781982421875,71.6058349609375,36.0009002685547],[515.358764648438,55.4658317565918,36.0009002685547],[499.375183105469,76.9058380126953,36.0009002685547],[501.168792724609,78.0658340454102,36.0009002685547],[504.568786621094,78.0658340454102,36.0009002685547],[506.32861328125,81.599235534668,36.0009002685547],[502.928588867188,81.599235534668,36.0009002685547],[499.528594970703,81.599235534668,36.0009002685547],[498.20361328125,77.8658294677734,36.0009002685547],[495.195404052734,51.7324333190918,30.0011005401611],[498.981994628906,51.7324333190918,27.0011005401611],[506.555206298828,51.7324333190918,33.0009002685547],[506.555206298828,51.7324333190918,36.0009002685547],[510.342010498047,51.7324333190918,36.0009002685547],[512.515380859375,54.7190322875977,24.0011005401611],[509.361999511719,54.7190322875977,24.0011005401611],[508.381805419922,57.7058334350586,24.0011005401611],[506.701995849609,62.8258323669434,24.0011005401611],[509.188812255859,60.052433013916,24.0011005401611],[493.06201171875,54.7190322875977,24.0011005401611],[503.648590087891,60.2658309936523,24.0011005401611],[500.851989746094,60.2658309936523,24.0011005401611],[498.915405273438,62.8258323669434,24.0011005401611],[502.808807373047,62.8258323669434,24.0011005401611],[491.425201416016,54.5058326721191,24.0011005401611],[506.421813964844,76.9058380126953,24.0011005401611],[502.808807373047,74.5324325561523,24.0011005401611],[504.568786621094,78.0658340454102,24.0011005401611],[506.32861328125,81.599235534668,24.0011005401611],[507.618804931641,77.8658294677734,24.0011005401611],[499.221801757812,72.2124328613281,24.0011005401611],[501.835388183594,71.6058349609375,24.0011005401611],[501.168792724609,78.0658340454102,24.0011005401611],[499.528594970703,81.599235534668,24.0011005401611],[502.048583984375,79.8324356079102,24.0011005401611],[490.253601074219,55.4658317565918,24.0011005401611],[488.928588867188,51.7324333190918,30.0011005401611],[488.928588867188,51.7324333190918,36.0009002685547],[490.253601074219,55.4658317565918,31.5009002685547],[498.20361328125,77.8658294677734,34.5009002685547],[508.381805419922,57.7058334350586,30.0011005401611],[505.585388183594,57.7058334350586,27.0011005401611],[502.788818359375,57.7058334350586,36.0009002685547],[499.992004394531,57.7058334350586,33.0009002685547],[509.851989746094,53.2258338928223,33.0009002685547],[509.361999511719,54.7190322875977,36.0009002685547],[508.871795654297,56.2124328613281,27.0011005401611],[496.695404052734,56.2124328613281,33.0009002685547],[495.695404052734,53.2258338928223,27.0011005401611],[506.32861328125,81.599235534668,30.0011005401611],[507.618804931641,77.8658294677734,25.5011005401611],[515.358764648438,55.4658317565918,34.5009002685547],[501.228607177734,81.599235534668,33.0009002685547],[504.628601074219,81.599235534668,27.0011005401611],[503.781982421875,71.6058349609375,33.0009002685547],[502.808807373047,74.5324325561523,30.0011005401611],[498.915405273438,62.8258323669434,30.0011005401611],[500.861999511719,62.8258323669434,27.0011005401611],[502.808807373047,62.8258323669434,36.0009002685547],[504.755187988281,62.8258323669434,33.0009002685547],[501.835388183594,71.6058349609375,33.0009002685547],[499.888793945312,65.7524337768555,33.0009002685547],[499.888793945312,65.7524337768555,36.0009002685547],[513.128601074219,51.4824333190918,36.0009002685547],[513.075988769531,51.6074333190918,24.0011005401611],[516.648803710938,51.7324333190918,24.0011005401611],[513.128601074219,51.4824333190918,24.0011005401611],[513.495178222656,51.7324333190918,24.0011005401611],[506.966613769531,51.60Line truncated
|
||||
];
|
||||
$facets = [
|
||||
[0,1,2],[3,4,5],[6,7,8],[9,10,11],[12,2,1],[12,1,13],[14,15,16],[17,18,19],[20,21,22],[17,19,23],[24,25,26],[27,13,1],[28,25,29],[30,31,32],[28,33,34],[35,36,7],[37,38,39],[40,10,41],[42,43,44],[45,5,4],[46,47,48],[46,48,49],[45,4,50],[51,52,53],[51,54,55],[56,52,57],[58,59,60],[61,50,4],[62,63,64],[65,34,33],[66,67,42],[68,17,69],[70,71,22],[66,42,72],[73,16,15],[35,7,74],[75,76,54],[77,27,1],[78,32,31],[75,54,79],[80,26,25],[81,80,25],[82,83,48],[84,20,85],[81,25,86],[87,88,19],[0,89,1],[90,91,92],[90,10,93],[38,94,39],[94,95,39],[3,7,96],[97,15,98],[97,99,15],[92,91,100],[89,101,1],[102,39,95],[103,11,10],[104,96,7],[105,15,99],[106,61,4],[107,108,33],[76,55,54],[109,91,110],[111,23,19],[112,63,113],[114,115,48],[116,59,117],[118,20,119],[120,31,121],[122,44,43],[110,91,123],[124,125,126],[127,128,129],[127,130,124],[131,124,132],[126,133,134],[135,136,126],[137,138,127],[139,127,138],[128,140,141],[142,128,143],[144,140,145],[100,91,146],[147,148,134],[101,149,1],[102,150,39],[103,10,151],[145,140,152],[152,140,153],[148,154,134],[154,155,134],[156,15,105],[157,104,7],[36,8,7],[158,37,39],[159,19,88],[160,19,159],[161,59,58],[161,117,59],[162,31,30],[162,121,31],[163,43,164],[163,165,43],[166,167,43],[167,164,43],[168,57,52],[82,48,169],[114,170,171],[108,65,33],[64,63,112],[114,172,170],[160,173,170],[171,170,173],[172,174,170],[160,170,174],[175,176,177],[178,77,1],[179,31,120],[175,180,176],[181,182,176],[177,176,182],[180,183,176],[181,176,183],[184,42,67],[185,69,17],[160,111,19],[186,187,160],[188,189,114],[190,188,114],[114,48,191],[192,114,193],[194,160,195],[196,160,194],[197,198,181],[199,197,181],[122,43,165],[200,201,175],[202,175,203],[204,175,202],[205,119,20],[206,181,207],[208,209,15],[210,15,209],[211,10,9],[212,10,211],[213,214,215],[216,217,218],[219,14,17],[113,63,220],[221,222,48],[191,48,222],[22,223,20],[205,20,223],[224,40,42],[123,91,225],[214,226,215],[227,215,226],[218,217,228],[229,228,217],[215,230,213],[125,135,126],[217,216,231],[129,128,142],[216,213,232],[130,132,124],[213,216,233],[234,213,235],[236,227,237],[238,237,227],[239,240,216],[233,216,240],[241,242,229],[243,229,242],[215,227,244],[245,215,246],[217,247,229],[248,249,217],[232,213,250],[230,250,213],[133,147,134],[244,227,251],[236,252,227],[251,227,252],[231,216,253],[254,253,216],[141,140,144],[247,255,229],[241,229,256],[255,256,229],[257,241,258],[259,146,91],[260,261,236],[262,1,149],[263,264,241],[265,241,264],[266,236,267],[268,267,236],[49,48,83],[166,43,269],[270,271,272],[273,274,275],[276,274,277],[278,151,10],[279,280,272],[281,39,150],[272,282,279],[155,283,134],[274,276,284],[153,140,285],[286,276,287],[265,276,286],[288,289,279],[268,288,279],[290,291,272],[271,290,272],[292,274,293],[275,274,292],[294,265,295],[276,265,294],[296,297,268],[279,296,268],[241,265,298],[298,265,299],[236,300,268],[300,301,268],[107,33,78],[302,303,59],[304,305,279],[282,304,279],[306,276,307],[284,276,306],[185,17,73],[308,309,221],[158,39,70],[310,41,10],[15,311,208],[7,6,312],[313,314,6],[315,6,314],[316,208,317],[318,317,208],[258,241,319],[319,241,320],[261,321,236],[321,322,236],[6,315,323],[208,324,318],[270,325,318],[326,318,325],[327,328,315],[273,315,328],[118,329,20],[330,20,329],[331,332,25],[86,25,332],[333,334,52],[335,52,334],[115,336,48],[169,48,336],[62,106,4],[35,15,210],[35,337,15],[158,10,212],[158,310,10],[338,178,1],[339,59,116],[107,302,59],[66,22,340],[66,341,22],[185,221,342],[185,308,221],[75,31,179],[75,343,31],[166,20,330],[166,85,20],[81,52,335],[81,168,52],[82,19,344],[82,87,19],[108,339,345],[346,108,345],[64,347,348],[349,347,64],[178,109,350],[351,178,350],[179,352,353],[354,352,179],[355,208,356],[356,208,311],[357,358,6],[358,312,6],[68,22,21],[68,340,22],[221,48,47],[184,342,221],[359,270,360],[318,360,270],[361,362,273],[315,273,362],[272,102,270],[363,270,102],[274,273,103],[364,103,273],[21,19,18],[21,20,84],[184,46,42],[43,42,46],[12,22,71],[365,22,12],[14,98,15],[14,220,63],[40,93,10],[40,225,91],[45,221,309],[366,221,45],[313,367,212],[212,367,368],[36,369,367],[313,36,367],[316,37,367],[37,368,367],[210,367,369],[316,367,210],[362,370,315],[370,323,315],[360,318,371],[371,318,324],[372,331,159],[159,195,160],[373,115,56],[115,114,189],[52,56,161],[374,161,56],[25,28,331],[375,331,28],[376,333,163],[163,203,175],[377,118,24],[118,181,198],[25,24,162],[378,162,24],[52,51,333],[379,333,51],[167,380,381],[376,167,381],[377,381,330],[330,381,380],[335,381,382],[376,381,335],[373,383,169],[169,383,384],[168,385,383],[373,168,383],[372,87,383],[87,384,383],[377,80,381],[80,382,381],[86,383,385],[372,383,86],[106,348,347],[386,106,347],[375,65,346],[108,346,65],[64,112,349],[387,349,112],[171,190,114],[346,345,171],[374,190,345],[171,345,190],[349,172,347],[172,114,192],[386,347,192],[172,192,347],[173,160,196],[171,173,346],[375,346,196],[173,196,346],[172,349,174],[174,186,160],[387,186,349],[174,349,186],[64,348,62],[106,62,348],[108,107,339],[59,339,107],[374,345,1Line truncated
|
||||
];
|
||||
} elsif ($model_name eq 'gt2_teeth') {
|
||||
$vertices = [
|
||||
[15.8899993896484,19.444055557251,2.67489433288574],[15.9129991531372,19.1590557098389,2.67489433288574],[15.9039993286133,19.1500549316406,2.67489433288574],[15.9489994049072,19.2490558624268,2.67489433288574],[15.9579992294312,19.3570556640625,2.67489433288574],[15.8819999694824,18.690055847168,2.67489433288574],[15.8319997787476,17.7460556030273,2.67489433288574],[15.8489999771118,18.819055557251,2.67489433288574],[15.8589992523193,17.7190551757812,2.67489433288574],[15.8769998550415,19.0490550994873,2.67489433288574],[15.7529993057251,17.8080558776855,2.67489433288574],[15.7869997024536,19.5010547637939,2.67489433288574],[14.0329990386963,18.7170543670654,2.67489433288574],[13.9599990844727,18.7460556030273,2.67489433288574],[13.9869995117188,20.2840557098389,2.67489433288574],[14.2029991149902,20.149055480957,2.67489433288574],[14.1939992904663,19.9560546875,2.67489433288574],[14.1939992904663,20.1670551300049,2.67489433288574],[14.2119998931885,20.0590553283691,2.67489433288574],[12.1899995803833,19.1840553283691,2.67489433288574],[12.096999168396,19.1950550079346,2.67489433288574],[12.1099996566772,20.6690559387207,2.67489433288574],[11.382999420166,19.9750556945801,2.67489433288574],[11.2599992752075,19.2490558624268,2.67489433288574],[11.2369995117188,19.9320545196533,2.67489433288574],[11.5349998474121,20.0640544891357,2.67489433288574],[11.6259994506836,20.1550559997559,2.67489433288574],[11.6829986572266,20.2390556335449,2.67489433288574],[11.7369995117188,20.3570556640625,2.67489433288574],[11.8449993133545,20.645055770874,2.67489433288574],[11.7729988098145,20.4640560150146,2.67489433288574],[11.7799987792969,20.5370559692383,9.41389465332031],[11.7639999389648,20.4470558166504,2.67489433288574],[11.9559993743896,20.6810550689697,2.67489433288574],[12.3079996109009,20.6020545959473,2.67489433288574],[12.1959991455078,19.1860542297363,2.67489433288574],[12.2059993743896,20.6540546417236,2.67489433288574],[12.3489990234375,20.3740558624268,2.67489433288574],[12.3579998016357,20.2750549316406,2.67489433288574],[12.3669996261597,20.266056060791,2.67489433288574],[12.3849992752075,20.1670551300049,2.67489433288574],[12.4269990921021,20.0680541992188,2.67489433288574],[12.5029993057251,19.9540557861328,2.67489433288574],[12.6169996261597,19.8550548553467,2.67489433288574],[12.7449989318848,19.7800559997559,2.67489433288574],[12.7629995346069,19.7800559997559,2.67489433288574],[12.8799991607666,19.7350559234619,2.67489433288574],[13.0369997024536,19.7250556945801,2.67489433288574],[13.0149993896484,19.0340557098389,2.67489433288574],[11.1699991226196,19.2580547332764,2.67489433288574],[11.0959987640381,19.2580547332764,2.67489433288574],[11.1209993362427,19.9230556488037,2.67489433288574],[13.0599994659424,19.024055480957,2.67489433288574],[14.9049997329712,18.3170547485352,2.67489433288574],[14.8779993057251,18.3400554656982,2.67489433288574],[14.8779993057251,19.149055480957,2.67489433288574],[13.3039989471436,19.77805519104,2.67489433288574],[13.1589994430542,18.9890556335449,2.67489433288574],[13.1559991836548,19.7350559234619,2.67489433288574],[13.4269990921021,19.8600559234619,2.67489433288574],[13.5339994430542,19.9700546264648,2.67389440536499],[13.6359996795654,20.1220550537109,2.67489433288574],[13.6359996795654,20.1400547027588,2.67489433288574],[13.6719989776611,20.2210559844971,2.67489433288574],[13.6899995803833,20.2300548553467,2.67489433288574],[13.7509994506836,20.3010559082031,2.67489433288574],[13.8539991378784,20.3180541992188,2.67489433288574],[14.8329992294312,18.3580551147461,2.67489433288574],[14.1849994659424,19.8530559539795,2.67489433288574],[14.0769996643066,18.7000541687012,2.67489433288574],[14.1099996566772,20.2400550842285,2.67489433288574],[14.2009992599487,19.6230545043945,2.67489433288574],[14.2729997634888,19.4670543670654,2.67489433288574],[14.3379993438721,19.3790550231934,2.67489433288574],[14.4549999237061,19.2770557403564,2.67489433288574],[14.5899991989136,19.2040557861328,2.67489433288574],[14.6079998016357,19.2040557861328,2.67489433288574],[14.7209997177124,19.1600551605225,2.67489433288574],[15.1379995346069,19.210054397583,2.67489433288574],[14.9949998855591,18.2680549621582,2.67489433288574],[15.0029993057251,19.1580543518066,2.67489433288574],[15.2369995117188,19.2760543823242,2.67489433288574],[15.3779993057251,19.4060554504395,2.67489433288574],[15.4539995193481,19.520055770874,2.67489433288574],[15.471999168396,19.52805519104,2.67489433288574],[15.5449991226196,19.5830554962158,2.67489433288574],[15.6529998779297,19.573055267334,2.67489433288574],[15.7059993743896,17.8360557556152,2.67489433288574],[15.9449996948242,18.5560550689697,2.67489433288574],[15.8589992523193,18.9380550384521,2.67489433288574],[14.9589996337891,18.2950553894043,2.67489433288574],[15.7779998779297,19.5100555419922,2.67489433288574],[14.0049991607666,20.2750549316406,2.67489433288574],[12.3489990234375,20.5000553131104,2.67489433288574],[13.0689992904663,19.01505Line truncated
|
||||
];
|
||||
$facets = [
|
||||
[0,1,2],[0,3,1],[0,4,3],[5,6,7],[8,6,5],[2,9,0],[6,10,11],[12,13,14],[15,16,17],[18,16,15],[19,20,21],[22,23,24],[25,23,22],[26,23,25],[27,23,26],[28,23,27],[29,30,31],[29,32,30],[29,28,32],[33,28,29],[33,23,28],[21,23,33],[20,23,21],[34,35,36],[37,35,34],[38,35,37],[39,35,38],[40,35,39],[41,35,40],[42,35,41],[43,35,42],[44,35,43],[45,35,44],[46,35,45],[47,35,46],[48,35,47],[49,50,51],[52,48,47],[23,49,24],[53,54,55],[56,57,58],[59,57,56],[60,57,59],[61,57,60],[62,57,61],[63,57,62],[64,57,63],[65,57,64],[66,57,65],[13,57,66],[54,67,55],[68,69,70],[71,69,68],[72,69,71],[73,69,72],[74,69,73],[75,69,74],[76,69,75],[77,69,76],[67,69,77],[70,16,68],[70,17,16],[78,79,80],[81,79,78],[82,79,81],[83,79,82],[84,79,83],[85,79,84],[86,79,85],[87,79,86],[88,8,5],[11,7,6],[11,89,7],[11,9,89],[11,0,9],[55,90,53],[55,79,90],[55,80,79],[91,11,10],[92,69,12],[92,70,69],[34,93,37],[47,94,52],[47,95,94],[47,57,95],[47,58,57],[51,24,49],[21,35,19],[21,36,35],[14,92,12],[86,10,87],[86,91,10],[77,55,67],[66,14,13],[96,97,4],[98,99,100],[101,102,98],[103,101,98],[104,103,98],[105,106,107],[108,105,107],[109,108,107],[100,109,107],[110,111,112],[113,110,112],[114,115,116],[117,114,116],[118,119,120],[121,122,123],[124,121,123],[125,126,127],[128,129,130],[131,132,133],[71,131,133],[134,71,133],[135,134,133],[136,135,133],[137,138,139],[140,137,139],[141,140,139],[142,31,141],[142,141,139],[143,126,132],[144,145,146],[147,144,146],[127,147,146],[148,121,124],[149,148,124],[150,149,124],[151,150,124],[152,151,124],[153,152,124],[154,153,124],[155,154,124],[129,156,157],[130,129,157],[158,159,160],[161,158,160],[162,161,160],[163,162,160],[146,163,160],[164,165,166],[167,164,166],[168,169,170],[171,168,170],[139,171,170],[159,172,173],[123,174,142],[175,110,113],[173,175,113],[106,176,177],[178,106,177],[179,180,167],[112,179,167],[175,173,172],[119,118,181],[119,181,97],[119,97,96],[182,98,102],[182,102,183],[182,183,120],[182,120,119],[143,132,184],[184,185,143],[147,127,126],[174,123,122],[159,173,160],[126,125,133],[126,133,132],[186,187,188],[186,188,116],[186,116,115],[99,98,182],[109,100,99],[106,178,107],[114,117,177],[114,177,176],[128,130,187],[128,187,186],[135,136,157],[135,157,156],[163,146,145],[164,167,180],[179,112,111],[171,139,138],[189,155,166],[189,166,165],[149,150,93],[154,155,189],[31,142,174],[114,176,78],[81,78,176],[7,89,183],[89,9,120],[89,120,183],[78,80,114],[176,106,81],[88,5,103],[183,102,7],[118,120,9],[9,2,181],[9,181,118],[115,114,80],[82,81,106],[101,103,5],[102,101,5],[5,7,102],[97,181,2],[2,1,97],[1,3,97],[80,55,115],[172,159,59],[59,56,172],[3,4,97],[4,0,96],[105,108,82],[186,115,55],[82,106,105],[83,82,108],[60,59,159],[175,172,56],[119,96,0],[0,11,119],[108,109,84],[84,83,108],[55,77,186],[56,58,110],[56,110,175],[60,159,158],[11,91,182],[182,119,11],[91,86,182],[85,84,109],[86,85,99],[128,186,77],[58,111,110],[158,161,60],[26,25,137],[138,137,25],[99,182,86],[109,99,85],[77,76,128],[58,47,111],[61,60,161],[137,140,26],[27,26,140],[25,22,138],[129,128,76],[76,75,129],[75,74,129],[74,73,156],[73,72,135],[68,16,184],[68,184,132],[16,18,185],[161,162,62],[62,61,161],[179,111,47],[171,138,22],[156,129,74],[135,156,73],[134,135,72],[72,71,134],[68,132,131],[185,184,16],[18,15,185],[63,62,162],[28,27,140],[22,24,171],[71,68,131],[15,17,143],[15,143,185],[17,70,143],[70,92,126],[162,163,64],[64,63,162],[180,179,47],[47,46,180],[140,141,28],[168,171,24],[126,143,70],[92,14,147],[147,126,92],[14,66,144],[14,144,147],[65,64,163],[66,65,145],[46,45,180],[32,28,141],[24,51,168],[145,144,66],[163,145,65],[164,180,45],[45,44,164],[44,43,164],[43,42,165],[38,37,151],[150,151,37],[37,93,150],[141,31,30],[30,32,141],[169,168,51],[165,164,43],[189,165,42],[42,41,189],[40,39,152],[40,152,153],[151,152,39],[39,38,151],[93,34,149],[154,189,41],[153,154,41],[41,40,153],[148,149,34],[34,36,148],[36,21,121],[31,174,29],[121,148,36],[21,33,122],[21,122,121],[33,29,122],[174,122,29],[116,188,53],[104,98,10],[87,10,98],[98,100,87],[79,87,100],[79,100,107],[90,79,107],[90,107,178],[178,177,90],[53,90,177],[53,177,117],[117,116,53],[54,53,188],[54,188,187],[67,54,187],[67,187,130],[69,67,130],[69,130,157],[12,69,157],[12,157,136],[136,133,12],[12,133,125],[125,127,12],[13,12,127],[127,146,13],[57,13,146],[57,146,160],[95,57,160],[95,160,173],[173,113,95],[94,95,113],[113,112,94],[52,94,112],[48,52,112],[112,167,48],[35,48,167],[35,167,166],[19,35,166],[139,170,50],[50,49,139],[166,155,19],[20,19,155],[155,124,20],[23,20,124],[23,124,123],[49,23,123],[49,123,142],[142,139,49],[190,191,170],[192,191,190],[191,192,51],[191,51,50],[170,169,190],[169,51,192],[169,192,190],[170,191,50],[193,194,195],[196,197,198],[199,200,201],[198,202,203],[204,201,200],[205,204,200],[206,207,208],[206,208,205],[206,205,200],[207,206,209],[207,209,203],[207,203,202],[202,198,197],[197,196,210],[197,210,195],[197,195,194],[8,88,195],[8,195,210],[210,196,8],[196,198,8],[198,203,8],[203,209,8],[209,206,8],[206,200,8],[202,197,104],[207Line truncated
|
||||
];
|
||||
} else {
|
||||
return undef;
|
||||
}
|
||||
|
||||
@@ -19,9 +19,4 @@ sub center {
|
||||
return $self->bounding_box->center;
|
||||
}
|
||||
|
||||
sub facets_count {
|
||||
my $self = shift;
|
||||
return $self->stats->{number_of_facets};
|
||||
}
|
||||
|
||||
1;
|
||||
Reference in new issue
Block a user